File Coverage

blib/lib/SQL/Translator/Parser/MySQL.pm
Criterion Covered Total %
statement 139 154 90.2
branch 75 102 73.5
condition 27 37 72.9
subroutine 10 10 100.0
pod 0 2 0.0
total 251 305 82.3


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