File Coverage

blib/lib/MySQL/Workbench/Parser/MySQLParser.pm
Criterion Covered Total %
statement 62 160 38.7
branch 11 102 10.7
condition 1 37 2.7
subroutine 11 12 91.6
pod 2 2 100.0
total 87 313 27.8


line stmt bran cond sub pod time code
1             package MySQL::Workbench::Parser::MySQLParser;
2              
3              
4 11     11   73 use strict;
  11         20  
  11         283  
5 11     11   51 use warnings;
  11         19  
  11         537  
6              
7             our $VERSION = '1.09';
8              
9             our $DEBUG;
10             $DEBUG = 0 unless defined $DEBUG;
11              
12 11     11   67 use Data::Dumper;
  11         20  
  11         481  
13 11     11   6224 use Storable qw(dclone);
  11         31492  
  11         683  
14 11     11   77 use DBI qw(:sql_types);
  11         23  
  11         3387  
15 11     11   11073 use Parse::RecDescent;
  11         318110  
  11         82  
16 11     11   514 use SQL::Translator;
  11         221  
  11         350  
17 11     11   152 use SQL::Translator::Utils qw/parse_mysql_version/;
  11         21  
  11         632  
18              
19 11     11   65 use base qw(Exporter);
  11         20  
  11         1541  
20             our @EXPORT_OK = qw(parse);
21              
22             our %type_mapping = ();
23              
24 11     11   69 use constant DEFAULT_PARSER_VERSION => 40000;
  11         47  
  11         19906  
25              
26             our $GRAMMAR = << 'END_OF_GRAMMAR';
27              
28             {
29             my ( $database_name, %tables, $table_order, @table_comments, %views,
30             $view_order, %procedures, $proc_order );
31             my $delimiter = ';';
32             }
33              
34             #
35             # The "eofile" rule makes the parser fail if any "statement" rule
36             # fails. Otherwise, the first successful match by a "statement"
37             # won't cause the failure needed to know that the parse, as a whole,
38             # failed. -ky
39             #
40             startrule : statement(s) eofile {
41             {
42             database_name => $database_name,
43             tables => \%tables,
44             views => \%views,
45             procedures => \%procedures,
46             }
47             }
48              
49             eofile : /^\Z/
50              
51             statement : comment
52             | use
53             | set
54             | drop
55             | create
56             | alter
57             | insert
58             | delimiter
59             | empty_statement
60             |
61              
62             use : /use/i NAME "$delimiter"
63             {
64             $database_name = $item[2];
65             @table_comments = ();
66             }
67              
68             set : /set/i not_delimiter "$delimiter"
69             { @table_comments = () }
70              
71             drop : /drop/i TABLE not_delimiter "$delimiter"
72              
73             drop : /drop/i NAME(s) "$delimiter"
74             { @table_comments = () }
75              
76             bit:
77             /(b'[01]{1,64}')/ |
78             /(b"[01]{1,64}")/
79              
80             string :
81             # MySQL strings, unlike common SQL strings, can be double-quoted or
82             # single-quoted.
83              
84             SQSTRING | DQSTRING
85              
86             nonstring : /[^;\'"]+/
87              
88             statement_body : string | nonstring
89              
90             insert : /insert/i statement_body(s?) "$delimiter"
91              
92             delimiter : /delimiter/i /[\S]+/
93             { $delimiter = $item[2] }
94              
95             empty_statement : "$delimiter"
96              
97             alter : ALTER TABLE table_name alter_specification(s /,/) "$delimiter"
98             {
99             my $table_name = $item{'table_name'};
100             die "Cannot ALTER table '$table_name'; it does not exist"
101             unless $tables{ $table_name };
102             for my $definition ( @{ $item[4] } ) {
103             $definition->{'extra'}->{'alter'} = 1;
104             push @{ $tables{ $table_name }{'constraints'} }, $definition;
105             }
106             }
107              
108             alter_specification : ADD foreign_key_def
109             { $return = $item[2] }
110              
111             create : CREATE /database/i NAME "$delimiter"
112             { @table_comments = () }
113              
114             create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) /(,\s*)?\)/ table_option(s?) "$delimiter"
115             {
116             my $table_name = $item{'table_name'};
117             die "There is more than one definition for $table_name"
118             if ($tables{$table_name});
119              
120             $tables{ $table_name }{'order'} = ++$table_order;
121             $tables{ $table_name }{'table_name'} = $table_name;
122              
123             if ( @table_comments ) {
124             $tables{ $table_name }{'comments'} = [ @table_comments ];
125             @table_comments = ();
126             }
127              
128             my $i = 1;
129             for my $definition ( @{ $item[7] } ) {
130             if ( $definition->{'supertype'} eq 'field' ) {
131             my $field_name = $definition->{'name'};
132             $tables{ $table_name }{'fields'}{ $field_name } =
133             { %$definition, order => $i };
134             $i++;
135              
136             if ( $definition->{'is_primary_key'} ) {
137             push @{ $tables{ $table_name }{'constraints'} },
138             {
139             type => 'primary_key',
140             fields => [ $field_name ],
141             }
142             ;
143             }
144             }
145             elsif ( $definition->{'supertype'} eq 'constraint' ) {
146             push @{ $tables{ $table_name }{'constraints'} }, $definition;
147             }
148             elsif ( $definition->{'supertype'} eq 'index' ) {
149             push @{ $tables{ $table_name }{'indices'} }, $definition;
150             }
151             }
152              
153             if ( my @options = @{ $item{'table_option(s?)'} } ) {
154             for my $option ( @options ) {
155             my ( $key, $value ) = each %$option;
156             if ( $key eq 'comment' ) {
157             push @{ $tables{ $table_name }{'comments'} }, $value;
158             }
159             else {
160             push @{ $tables{ $table_name }{'table_options'} }, $option;
161             }
162             }
163             }
164              
165             1;
166             }
167              
168             opt_if_not_exists : /if not exists/i
169              
170             create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' "$delimiter"
171             {
172             @table_comments = ();
173             push @{ $tables{ $item{'table_name'} }{'indices'} },
174             {
175             name => $item[4],
176             type => $item[2][0] ? 'unique' : 'normal',
177             fields => $item[8],
178             }
179             ;
180             }
181              
182             create : CREATE /trigger/i NAME not_delimiter "$delimiter"
183             {
184             @table_comments = ();
185             }
186              
187             create : CREATE PROCEDURE NAME not_delimiter "$delimiter"
188             {
189             @table_comments = ();
190             my $func_name = $item[3];
191             my $owner = '';
192             my $sql = "$item[1] $item[2] $item[3] $item[4]";
193              
194             $procedures{ $func_name }{'order'} = ++$proc_order;
195             $procedures{ $func_name }{'name'} = $func_name;
196             $procedures{ $func_name }{'owner'} = $owner;
197             $procedures{ $func_name }{'sql'} = $sql;
198             }
199              
200             PROCEDURE : /procedure/i
201             | /function/i
202              
203             create : CREATE or_replace(?) create_view_option(s?) /view/i NAME /as/i view_select_statement "$delimiter"
204             {
205             @table_comments = ();
206             my $view_name = $item{'NAME'};
207             my $select_sql = $item{'view_select_statement'};
208             my $options = $item{'create_view_option(s?)'};
209              
210             my $sql = join(q{ },
211             grep { defined and length }
212             map { ref $_ eq 'ARRAY' ? @$_ : $_ }
213             $item{'CREATE'},
214             $item{'or_replace(?)'},
215             $options,
216             $view_name,
217             'as select',
218             join(', ',
219             map {
220             sprintf('%s%s',
221             $_->{'name'},
222             $_->{'alias'} ? ' as ' . $_->{'alias'} : ''
223             )
224             }
225             @{ $select_sql->{'columns'} || [] }
226             ),
227             ' from ',
228             join(', ',
229             map {
230             $_->{'join'} ?
231             () :
232             sprintf('%s%s',
233             $_->{'name'},
234             $_->{'alias'} ? ' as ' . $_->{'alias'} : ''
235             )
236             }
237             @{ $select_sql->{'from'}{'tables'} || [] }
238             ),
239             join(' ',
240             map {
241             $_->{'join'} ?
242             sprintf('%s%s%s ON %s',
243             $_->{'join'},
244             $_->{'name'},
245             $_->{'alias'} ? ' as ' . $_->{'alias'} : '',
246             $_->{'on'}
247             ) :
248             ();
249             }
250             @{ $select_sql->{'from'}{'tables'} || [] }
251             ),
252             $select_sql->{'from'}{'where'}
253             ? 'where ' . $select_sql->{'from'}{'where'}
254             : ''
255             ,
256             );
257              
258             # Hack to strip database from function calls in SQL
259             $sql =~ s#`\w+`\.(`\w+`\()##g;
260              
261             $views{ $view_name }{'order'} = ++$view_order;
262             $views{ $view_name }{'name'} = $view_name;
263             $views{ $view_name }{'sql'} = $sql;
264             $views{ $view_name }{'options'} = $options;
265             $views{ $view_name }{'select'} = $item{'view_select_statement'};
266             $views{ $view_name }{'from'}{'tables'} = $select_sql->{'from'}{'tables'};
267             }
268              
269             create_view_option : view_algorithm | view_sql_security | view_definer
270              
271             or_replace : /or replace/i
272              
273             view_algorithm : /algorithm/i /=/ WORD
274             {
275             $return = "$item[1]=$item[3]";
276             }
277              
278             view_definer : /definer=\S+/i
279              
280             view_sql_security : /sql \s+ security \s+ (definer|invoker)/ixs
281              
282             not_delimiter : /.*?(?=$delimiter)/is
283              
284             view_select_statement : /[(]?/ /select/i view_column_def /from/i view_table_def /[)]?/
285             {
286             $return = {
287             columns => $item{'view_column_def'},
288             from => $item{'view_table_def'},
289             };
290             }
291              
292             view_column_def : /(.*?)(?=\bfrom\b)/ixs
293             {
294             # split on commas not in parens,
295             # e.g., "concat_ws(\' \', first, last) as first_last"
296             my @tmp = $1 =~ /((?:[^(,]+|\(.*?\))+)/g;
297             my @cols;
298             for my $col ( @tmp ) {
299             my ( $name, $alias ) = map {
300             s/^\s+|\s+$//g;
301             s/[`]//g;
302             $_
303             } split /\s+as\s+/i, $col;
304              
305             push @cols, { name => $name, alias => $alias || '' };
306             }
307              
308             $return = \@cols;
309             }
310              
311             not_delimiter : /.*?(?=$delimiter)/is
312              
313             view_table_def : not_delimiter
314             {
315             my $clause = $item[1];
316             my $where = $1 if $clause =~ s/\bwhere \s+ (.*)//ixs;
317             $clause =~ s/[)]\s*$//;
318              
319             my $joins = '';
320             $joins = $1 if $clause =~ s/
321             \b((?:(?:left|right)\s+)?
322             (?:(?:inner|outer)\s+)
323             join .*)\z
324             //ixs;
325              
326             my @tables;
327             for my $tbl ( split( /\s*,\s*/, $clause ) ) {
328             my ( $name, $alias ) = split /\s+as\s+/i, $tbl;
329             push @tables, { name => $name, alias => $alias || '', join => 0 };
330             }
331              
332             my @all_joins = split /
333             \b((?:(?:left|right)\s+)?
334             (?:(?:inner|outer)\s+)
335             join)
336             /ixsg, $joins;
337              
338             shift @all_joins if @all_joins;
339              
340             while ( @all_joins ) {
341             my $join = shift @all_joins;
342             my ( $table, $on ) = split /\s+on\s+/i, shift @all_joins;
343             my ( $name, $alias ) = split /\s+as\s+/i, $table;
344             push @tables, { name => $name, alias => $alias || '', join => $join, on => $on };
345             }
346              
347             $return = {
348             tables => \@tables,
349             where => $where || '',
350             };
351             }
352              
353             view_column_alias : /as/i NAME
354             { $return = $item[2] }
355              
356             create_definition : constraint
357             | index
358             | field
359             | comment
360             |
361              
362             comment : /^\s*(?:#|-{2}).*\n/
363             {
364             my $comment = $item[1];
365             $comment =~ s/^\s*(#|--)\s*//;
366             $comment =~ s/\s*$//;
367             $return = $comment;
368             }
369              
370             comment : m{ / \* (?! \!) .*? \* / }xs
371             {
372             my $comment = $item[2];
373             $comment = substr($comment, 0, -2);
374             $comment =~ s/^\s*|\s*$//g;
375             $return = $comment;
376             }
377              
378             comment_like_command : m{/\*!(\d+)?}s
379              
380             comment_end : m{ \* / }xs
381              
382             field_comment : /^\s*(?:#|-{2}).*\n/
383             {
384             my $comment = $item[1];
385             $comment =~ s/^\s*(#|--)\s*//;
386             $comment =~ s/\s*$//;
387             $return = $comment;
388             }
389              
390              
391             blank : /\s*/
392              
393             field : field_comment(s?) field_name data_type field_qualifier(s?) reference_definition(?) on_update(?) field_comment(s?)
394             {
395             my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
396             if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
397             $qualifiers{ $_ } = 1 for @type_quals;
398             }
399              
400             my $null = defined $qualifiers{'not_null'}
401             ? $qualifiers{'not_null'} : 1;
402             delete $qualifiers{'not_null'};
403              
404             my @comments = ( @{ $item[1] }, (exists $qualifiers{comment} ? delete $qualifiers{comment} : ()) , @{ $item[7] } );
405              
406             $return = {
407             supertype => 'field',
408             name => $item{'field_name'},
409             data_type => $item{'data_type'}{'type'},
410             size => $item{'data_type'}{'size'},
411             list => $item{'data_type'}{'list'},
412             null => $null,
413             constraints => $item{'reference_definition(?)'},
414             comments => [ @comments ],
415             %qualifiers,
416             }
417             }
418             |
419              
420             field_qualifier : not_null
421             {
422             $return = {
423             null => $item{'not_null'},
424             }
425             }
426              
427             field_qualifier : default_val
428             {
429             $return = {
430             default => $item{'default_val'},
431             }
432             }
433              
434             field_qualifier : auto_inc
435             {
436             $return = {
437             is_auto_inc => $item{'auto_inc'},
438             }
439             }
440              
441             field_qualifier : primary_key
442             {
443             $return = {
444             is_primary_key => $item{'primary_key'},
445             }
446             }
447              
448             field_qualifier : unsigned
449             {
450             $return = {
451             is_unsigned => $item{'unsigned'},
452             }
453             }
454              
455             field_qualifier : /character set/i WORD
456             {
457             $return = {
458             'CHARACTER SET' => $item[2],
459             }
460             }
461              
462             field_qualifier : /collate/i WORD
463             {
464             $return = {
465             COLLATE => $item[2],
466             }
467             }
468              
469             field_qualifier : /on update/i CURRENT_TIMESTAMP
470             {
471             $return = {
472             'ON UPDATE' => $item[2],
473             }
474             }
475              
476             field_qualifier : /unique/i KEY(?)
477             {
478             $return = {
479             is_unique => 1,
480             }
481             }
482              
483             field_qualifier : KEY
484             {
485             $return = {
486             has_index => 1,
487             }
488             }
489              
490             field_qualifier : /comment/i string
491             {
492             $return = {
493             comment => $item[2],
494             }
495             }
496              
497             reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete(?) on_update(?)
498             {
499             $return = {
500             type => 'foreign_key',
501             reference_table => $item[2],
502             reference_fields => $item[3][0],
503             match_type => $item[4][0],
504             on_delete => $item[5][0],
505             on_update => $item[6][0],
506             }
507             }
508              
509             match_type : /match full/i { 'full' }
510             |
511             /match partial/i { 'partial' }
512              
513             on_delete : /on delete/i reference_option
514             { $item[2] }
515              
516             on_update :
517             /on update/i CURRENT_TIMESTAMP
518             { $item[2] }
519             |
520             /on update/i reference_option
521             { $item[2] }
522              
523             reference_option: /restrict/i |
524             /cascade/i |
525             /set null/i |
526             /no action/i |
527             /set default/i
528             { $item[1] }
529              
530             index : normal_index
531             | fulltext_index
532             | spatial_index
533             |
534              
535             table_name : NAME
536              
537             field_name : NAME
538              
539             index_name : NAME
540              
541             data_type : WORD parens_value_list(s?) type_qualifier(s?)
542             {
543             my $type = $item[1];
544             my $size; # field size, applicable only to non-set fields
545             my $list; # set list, applicable only to sets (duh)
546              
547             if ( uc($type) =~ /^(SET|ENUM)$/ ) {
548             $size = undef;
549             $list = $item[2][0];
550             }
551             else {
552             $size = $item[2][0];
553             $list = [];
554             }
555              
556              
557             $return = {
558             type => $type,
559             size => $size,
560             list => $list,
561             qualifiers => $item[3],
562             }
563             }
564              
565             parens_field_list : '(' field_name(s /,/) ')'
566             { $item[2] }
567              
568             parens_value_list : '(' VALUE(s /,/) ')'
569             { $item[2] }
570              
571             type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
572             { lc $item[1] }
573              
574             field_type : WORD
575              
576             create_index : /create/i /index/i
577              
578             not_null : /not/i /null/i
579             { $return = 0 }
580             |
581             /null/i
582             { $return = 1 }
583              
584             unsigned : /unsigned/i { $return = 0 }
585              
586             default_val :
587             /default/i CURRENT_TIMESTAMP
588             {
589             $return = $item[2];
590             }
591             |
592             /default/i VALUE
593             {
594             $return = $item[2];
595             }
596             |
597             /default/i bit
598             {
599             $item[2] =~ s/b['"]([01]+)['"]/$1/g;
600             $return = $item[2];
601             }
602             |
603             /default/i /[\w\d:.-]+/
604             {
605             $return = $item[2];
606             }
607             |
608             /default/i NAME # column value, allowed in MariaDB
609             {
610             $return = $item[2];
611             }
612              
613             auto_inc : /auto_increment/i { 1 }
614              
615             primary_key : /primary/i /key/i { 1 }
616              
617             constraint : primary_key_def
618             | unique_key_def
619             | foreign_key_def
620             | check_def
621             |
622              
623             expr : /[^)]* \( [^)]+ \) [^)]*/x # parens, balanced one deep
624             | /[^)]+/
625              
626             check_def : check_def_begin '(' expr ')'
627             {
628             $return = {
629             supertype => 'constraint',
630             type => 'check',
631             name => $item[1],
632             expression => $item[3],
633             }
634             }
635              
636             check_def_begin : /constraint/i /check/i NAME
637             { $return = $item[3] }
638             |
639             /constraint/i NAME /check/i
640             { $return = $item[2] }
641             |
642             /constraint/i /check/i
643             { $return = '' }
644              
645             foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
646             {
647             $return = {
648             supertype => 'constraint',
649             type => 'foreign_key',
650             name => $item[1],
651             fields => $item[2],
652             %{ $item{'reference_definition'} },
653             }
654             }
655              
656             foreign_key_def_begin : /constraint/i /foreign key/i NAME
657             { $return = $item[3] }
658             |
659             /constraint/i NAME /foreign key/i
660             { $return = $item[2] }
661             |
662             /constraint/i /foreign key/i
663             { $return = '' }
664             |
665             /foreign key/i NAME
666             { $return = $item[2] }
667             |
668             /foreign key/i
669             { $return = '' }
670              
671             primary_key_def : primary_key index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
672             {
673             $return = {
674             supertype => 'constraint',
675             type => 'primary_key',
676             fields => $item[4],
677             options => $item[2][0] || $item[6][0],
678             };
679             }
680             # In theory, and according to the doc, names should not be allowed here, but
681             # MySQL accept (and ignores) them, so we are not going to be less :)
682             | primary_key index_name_not_using(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
683             {
684             $return = {
685             supertype => 'constraint',
686             type => 'primary_key',
687             fields => $item[4],
688             options => $item[6][0],
689             };
690             }
691              
692             unique_key_def : UNIQUE KEY(?) index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
693             {
694             $return = {
695             supertype => 'constraint',
696             name => $item[3][0],
697             type => 'unique',
698             fields => $item[6],
699             options => $item[4][0] || $item[8][0],
700             }
701             }
702              
703             normal_index : KEY index_name_not_using(?) index_type(?) '(' name_with_opt_paren(s /,/) ')' index_type(?)
704             {
705             $return = {
706             supertype => 'index',
707             type => 'normal',
708             name => $item[2][0],
709             fields => $item[5],
710             options => $item[3][0] || $item[7][0],
711             }
712             }
713              
714             index_name_not_using : QUOTED_NAME
715             | /(\b(?!using)\w+\b)/ { $return = ($1 =~ /^using/i) ? undef : $1 }
716              
717             index_type : /using (btree|hash|rtree)/i { $return = uc $1 }
718              
719             fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
720             {
721             $return = {
722             supertype => 'index',
723             type => 'fulltext',
724             name => $item{'index_name(?)'}[0],
725             fields => $item[5],
726             }
727             }
728              
729             spatial_index : /spatial/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
730             {
731             $return = {
732             supertype => 'index',
733             type => 'spatial',
734             name => $item{'index_name(?)'}[0],
735             fields => $item[5],
736             }
737             }
738              
739             name_with_opt_paren : NAME parens_value_list(s?)
740             { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
741              
742             UNIQUE : /unique/i
743              
744             KEY : /key/i | /index/i
745              
746             table_option : /comment/i /=/ string
747             {
748             $return = { comment => $item[3] };
749             }
750             | /(default )?(charset|character set)/i /\s*=?\s*/ NAME
751             {
752             $return = { 'CHARACTER SET' => $item[3] };
753             }
754             | /collate/i NAME
755             {
756             $return = { 'COLLATE' => $item[2] }
757             }
758             | /union/i /\s*=\s*/ '(' table_name(s /,/) ')'
759             {
760             $return = { $item[1] => $item[4] };
761             }
762             | WORD /\s*=\s*/ table_option_value
763             {
764             $return = { $item[1] => $item[3] };
765             }
766              
767             table_option_value : VALUE
768             | NAME
769              
770             default : /default/i
771              
772             ADD : /add/i
773              
774             ALTER : /alter/i
775              
776             CREATE : /create/i
777              
778             TEMPORARY : /temporary/i
779              
780             TABLE : /table/i
781              
782             WORD : /\w+/
783              
784             DIGITS : /\d+/
785              
786             COMMA : ','
787              
788             BACKTICK : '`'
789              
790             DOUBLE_QUOTE: '"'
791              
792             SINGLE_QUOTE: "'"
793              
794             QUOTED_NAME : BQSTRING
795             | SQSTRING
796             | DQSTRING
797              
798             # MySQL strings, unlike common SQL strings, can have the delmiters
799             # escaped either by doubling or by backslashing.
800             BQSTRING: BACKTICK /(?:[^\\`]|``|\\.)*/ BACKTICK
801             { ($return = $item[3]) =~ s/(\\[\\`]|``)/substr($1,1)/ge }
802              
803             DQSTRING: DOUBLE_QUOTE /(?:[^\\"]|""|\\.)*/ DOUBLE_QUOTE
804             { ($return = $item[3]) =~ s/(\\[\\"]|"")/substr($1,1)/ge }
805              
806             SQSTRING: SINGLE_QUOTE /(?:[^\\']|''|\\.)*/ SINGLE_QUOTE
807             { ($return = $item[3]) =~ s/(\\[\\']|'')/substr($1,1)/ge }
808              
809              
810             NAME: QUOTED_NAME
811             | /\w+/
812              
813             VALUE : /[-+]?\d*\.?\d+(?:[eE]\d+)?/
814             { $item[1] }
815             | SQSTRING
816             | DQSTRING
817             | /NULL/i
818             { 'NULL' }
819              
820             # always a scalar-ref, so that it is treated as a function and not quoted by consumers
821             CURRENT_TIMESTAMP :
822             /current_timestamp(\(\))?/i { \'CURRENT_TIMESTAMP' }
823             | /now\(\)/i { \'CURRENT_TIMESTAMP' }
824              
825             END_OF_GRAMMAR
826              
827             sub parse {
828 2     2 1 6 my ( $translator, $data ) = @_;
829              
830             # Enable warnings within the Parse::RecDescent module.
831             # Make sure the parser dies when it encounters an error
832 2 50       7 local $::RD_ERRORS = 1 unless defined $::RD_ERRORS;
833             # Enable warnings. This will warn on unused rules &c.
834 2 50       6 local $::RD_WARN = 1 unless defined $::RD_WARN;
835             # Give out hints to help fix problems.
836 2 50       7 local $::RD_HINT = 1 unless defined $::RD_HINT;
837 2 50       34 local $::RD_TRACE = $translator->trace ? 1 : undef;
838 2         18 local $DEBUG = $translator->debug;
839              
840 2         111 my $parser = Parse::RecDescent->new($MySQL::Workbench::Parser::MySQLParser::GRAMMAR);
841              
842             # Preprocess for MySQL-specific and not-before-version comments
843             # from mysqldump
844             my $parser_version = parse_mysql_version(
845 2   50     1099285 $translator->parser_args->{mysql_parser_version}, 'mysql'
846             ) || DEFAULT_PARSER_VERSION;
847              
848 2         140 while ( $data =~
849 0 0 0     0 s#/\*!(\d{5})?(.*?)\*/#($1 && $1 > $parser_version ? '' : $2)#es
850             ) {
851             # do nothing; is there a better way to write this? -- ky
852             }
853              
854 2         29 my $result = $parser->startrule($data);
855 2 50       22126 return $translator->error( "Parse failed." ) unless defined $result;
856 2 50       8 warn "Parse result:".Dumper( $result ) if $DEBUG;
857              
858 2         48 my $schema = $translator->schema;
859 2 50       3344 $schema->name($result->{'database_name'}) if $result->{'database_name'};
860              
861             my @tables = sort {
862             $result->{'tables'}{ $a }{'order'}
863             <=>
864 0         0 $result->{'tables'}{ $b }{'order'}
865 2         6 } keys %{ $result->{'tables'} };
  2         11  
866              
867 2         6 for my $table_name ( @tables ) {
868 0         0 my $tdata = $result->{tables}{ $table_name };
869             my $table = $schema->add_table(
870 0 0       0 name => $tdata->{'table_name'},
871             ) or die $schema->error;
872              
873 0         0 $table->comments( $tdata->{'comments'} );
874              
875             my @fields = sort {
876             $tdata->{'fields'}->{$a}->{'order'}
877             <=>
878 0         0 $tdata->{'fields'}->{$b}->{'order'}
879 0         0 } keys %{ $tdata->{'fields'} };
  0         0  
880              
881 0         0 for my $fname ( @fields ) {
882 0         0 my $fdata = $tdata->{'fields'}{ $fname };
883             my $field = $table->add_field(
884             name => $fdata->{'name'},
885             data_type => $fdata->{'data_type'},
886             size => $fdata->{'size'},
887             default_value => $fdata->{'default'},
888             is_auto_increment => $fdata->{'is_auto_inc'},
889             is_nullable => $fdata->{'null'},
890 0 0       0 comments => $fdata->{'comments'},
891             ) or die $table->error;
892              
893 0 0       0 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
894              
895 0         0 for my $qual ( qw[ binary unsigned zerofill list collate ],
896             'character set', 'on update' ) {
897 0 0 0     0 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
898 0 0 0     0 next if ref $val eq 'ARRAY' && !@$val;
899 0         0 $field->extra( $qual, $val );
900             }
901             }
902              
903 0 0       0 if ( $fdata->{'has_index'} ) {
904             $table->add_index(
905             name => '',
906             type => 'NORMAL',
907 0 0       0 fields => $fdata->{'name'},
908             ) or die $table->error;
909             }
910              
911 0 0       0 if ( $fdata->{'is_unique'} ) {
912             $table->add_constraint(
913             name => '',
914             type => 'UNIQUE',
915 0 0       0 fields => $fdata->{'name'},
916             ) or die $table->error;
917             }
918              
919 0         0 for my $cdata ( @{ $fdata->{'constraints'} } ) {
  0         0  
920 0 0       0 next unless $cdata->{'type'} eq 'foreign_key';
921 0   0     0 $cdata->{'fields'} ||= [ $field->name ];
922 0         0 push @{ $tdata->{'constraints'} }, $cdata;
  0         0  
923             }
924              
925             }
926              
927 0 0       0 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
  0         0  
928             my $index = $table->add_index(
929             name => $idata->{'name'},
930             type => uc $idata->{'type'},
931 0 0       0 fields => $idata->{'fields'},
932             ) or die $table->error;
933             }
934              
935 0 0       0 if ( my @options = @{ $tdata->{'table_options'} || [] } ) {
  0 0       0  
936 0         0 my @cleaned_options;
937             my @ignore_opts = $translator->parser_args->{'ignore_opts'}
938 0 0       0 ? split( /,/, $translator->parser_args->{'ignore_opts'} )
939             : ();
940 0 0       0 if (@ignore_opts) {
941 0         0 my $ignores = { map { $_ => 1 } @ignore_opts };
  0         0  
942 0         0 foreach my $option (@options) {
943             # make sure the option isn't in ignore list
944 0         0 my ($option_key) = keys %$option;
945 0 0       0 if ( !exists $ignores->{$option_key} ) {
946 0         0 push @cleaned_options, $option;
947             }
948             }
949             } else {
950 0         0 @cleaned_options = @options;
951             }
952 0 0       0 $table->options( \@cleaned_options ) or die $table->error;
953             }
954              
955 0 0       0 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
  0         0  
956             my $constraint = $table->add_constraint(
957             name => $cdata->{'name'},
958             type => $cdata->{'type'},
959             fields => $cdata->{'fields'},
960             expression => $cdata->{'expression'},
961             reference_table => $cdata->{'reference_table'},
962             reference_fields => $cdata->{'reference_fields'},
963             match_type => $cdata->{'match_type'} || '',
964             on_delete => $cdata->{'on_delete'}
965             || $cdata->{'on_delete_do'},
966             on_update => $cdata->{'on_update'}
967 0 0 0     0 || $cdata->{'on_update_do'},
      0        
      0        
968             ) or die $table->error;
969             }
970              
971             # After the constrains and PK/idxs have been created,
972             # we normalize fields
973 0         0 normalize_field($_) for $table->get_fields;
974             }
975              
976             my @procedures = sort {
977             $result->{procedures}->{ $a }->{'order'}
978             <=>
979 0         0 $result->{procedures}->{ $b }->{'order'}
980 2         4 } keys %{ $result->{procedures} };
  2         8  
981              
982 2         6 for my $proc_name ( @procedures ) {
983             $schema->add_procedure(
984             name => $proc_name,
985             owner => $result->{procedures}->{$proc_name}->{owner},
986             sql => $result->{procedures}->{$proc_name}->{sql},
987 0         0 );
988             }
989              
990             my @views = sort {
991             $result->{views}->{ $a }->{'order'}
992             <=>
993 0         0 $result->{views}->{ $b }->{'order'}
994 2         4 } keys %{ $result->{views} };
  2         35  
995              
996 2         6 for my $view_name ( @views ) {
997 2         6 my $view = $result->{'views'}{ $view_name };
998 5 50       25 my @flds = map { $_->{'alias'} || $_->{'name'} }
999 2 50       4 @{ $view->{'select'}{'columns'} || [] };
  2         8  
1000 3 50       19 my @from = map { $_->{'alias'} || $_->{'name'} }
1001 2 50       4 @{ $view->{'from'}{'tables'} || [] };
  2         9  
1002              
1003             $schema->add_view(
1004             name => $view_name,
1005             sql => $view->{'sql'},
1006             order => $view->{'order'},
1007             fields => \@flds,
1008             tables => \@from,
1009 2         18 options => $view->{'options'}
1010             );
1011             }
1012              
1013 2         1142 return 1;
1014             }
1015              
1016             # Takes a field, and returns
1017             sub normalize_field {
1018 0     0 1   my ($field) = @_;
1019 0           my ($size, $type, $list, $unsigned, $changed);
1020              
1021 0           $size = $field->size;
1022 0           $type = $field->data_type;
1023 0   0       $list = $field->extra->{list} || [];
1024 0           $unsigned = defined($field->extra->{unsigned});
1025              
1026 0 0 0       if ( !ref $size && $size eq 0 ) {
1027 0 0         if ( lc $type eq 'tinyint' ) {
    0          
    0          
    0          
    0          
    0          
1028 0           $changed = $size != 4 - $unsigned;
1029 0           $size = 4 - $unsigned;
1030             }
1031             elsif ( lc $type eq 'smallint' ) {
1032 0           $changed = $size != 6 - $unsigned;
1033 0           $size = 6 - $unsigned;
1034             }
1035             elsif ( lc $type eq 'mediumint' ) {
1036 0           $changed = $size != 9 - $unsigned;
1037 0           $size = 9 - $unsigned;
1038             }
1039             elsif ( $type =~ /^int(eger)?$/i ) {
1040 0   0       $changed = $size != 11 - $unsigned || $type ne 'int';
1041 0           $type = 'int';
1042 0           $size = 11 - $unsigned;
1043             }
1044             elsif ( lc $type eq 'bigint' ) {
1045 0           $changed = $size != 20;
1046 0           $size = 20;
1047             }
1048             elsif ( lc $type =~ /(float|double|decimal|numeric|real|fixed|dec)/ ) {
1049 0 0 0       my $old_size = (ref $size || '') eq 'ARRAY' ? $size : [];
1050 0   0       $changed = @$old_size != 2
1051             || $old_size->[0] != 8
1052             || $old_size->[1] != 2;
1053 0           $size = [8,2];
1054             }
1055             }
1056              
1057 0 0         if ( $type =~ /^tiny(text|blob)$/i ) {
    0          
    0          
    0          
1058 0           $changed = $size != 255;
1059 0           $size = 255;
1060             }
1061             elsif ( $type =~ /^(blob|text)$/i ) {
1062 0           $changed = $size != 65_535;
1063 0           $size = 65_535;
1064             }
1065             elsif ( $type =~ /^medium(blob|text)$/i ) {
1066 0           $changed = $size != 16_777_215;
1067 0           $size = 16_777_215;
1068             }
1069             elsif ( $type =~ /^long(blob|text)$/i ) {
1070 0           $changed = $size != 4_294_967_295;
1071 0           $size = 4_294_967_295;
1072             }
1073              
1074 0 0 0       if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
1075 0           my %extra = $field->extra;
1076 0           my $longest = 0;
1077 0 0         for my $len ( map { length } @{ $extra{'list'} || [] } ) {
  0            
  0            
1078 0 0         $longest = $len if $len > $longest;
1079             }
1080 0           $changed = 1;
1081 0 0         $size = $longest if $longest;
1082             }
1083              
1084              
1085 0 0         if ( $changed ) {
1086             # We only want to clone the field, not *everything*
1087             {
1088 0           local $field->{table} = undef;
  0            
1089 0           $field->parsed_field( dclone( $field ) );
1090 0           $field->parsed_field->{table} = $field->table;
1091             }
1092 0           $field->size( $size );
1093 0           $field->data_type( $type );
1094             $field->sql_data_type( $type_mapping{ lc $type } )
1095 0 0         if exists $type_mapping{ lc $type };
1096 0 0         $field->extra->{list} = $list if @$list;
1097             }
1098             }
1099              
1100             1;
1101              
1102             # -------------------------------------------------------------------
1103             # Where man is not nature is barren.
1104             # William Blake
1105             # -------------------------------------------------------------------
1106              
1107             __END__