270a8797182f0daa9b5c4ee045fbd300d3260fe8
[dbsrgits/SQL-Translator-2.0-ish.git] / lib / SQL / Translator / Grammar / MySQL.pm
1 use MooseX::Declare;
2 role SQL::Translator::Grammar::MySQL {
3
4 # -------------------------------------------------------------------
5 # Copyright (C) 2002-2009 SQLFairy Authors
6 #
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License as
9 # published by the Free Software Foundation; version 2.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 # 02111-1307  USA
20 # -------------------------------------------------------------------
21
22 method _build_grammar {
23 return q!
24
25     my ( $database_name, %tables, $table_order, @table_comments, %views,
26         $view_order, %procedures, $proc_order );
27     my $delimiter = ';';
28 }
29
30 #
31 # The "eofile" rule makes the parser fail if any "statement" rule
32 # fails.  Otherwise, the first successful match by a "statement" 
33 # won't cause the failure needed to know that the parse, as a whole,
34 # failed. -ky
35 #
36 startrule : statement(s) eofile { 
37     { 
38         database_name => $database_name, 
39         tables        => \%tables, 
40         views         => \%views, 
41         procedures    => \%procedures,
42     } 
43 }
44
45 eofile : /^\Z/
46
47 statement : comment
48     | use
49     | set
50     | drop
51     | create
52     | alter
53     | insert
54     | delimiter
55     | empty_statement
56     | <error>
57
58 use : /use/i WORD "$delimiter"
59     {
60         $database_name = $item[2];
61         @table_comments = ();
62     }
63
64 set : /set/i /[^;]+/ "$delimiter"
65     { @table_comments = () }
66
67 drop : /drop/i TABLE /[^;]+/ "$delimiter"
68
69 drop : /drop/i WORD(s) "$delimiter"
70     { @table_comments = () }
71
72 string :
73   # MySQL strings, unlike common SQL strings, can be double-quoted or 
74   # single-quoted, and you can escape the delmiters by doubling (but only the 
75   # delimiter) or by backslashing.
76
77    /'(\\.|''|[^\\\'])*'/ |
78    /"(\\.|""|[^\\\"])*"/
79   # For reference, std sql str: /(?:(?:\')(?:[^\']*(?:(?:\'\')[^\']*)*)(?:\'))//
80
81 nonstring : /[^;\'"]+/
82
83 statement_body : string | nonstring
84
85 insert : /insert/i  statement_body(s?) "$delimiter"
86
87 delimiter : /delimiter/i /[\S]+/
88     { $delimiter = $item[2] }
89
90 empty_statement : "$delimiter"
91
92 alter : ALTER TABLE table_name alter_specification(s /,/) "$delimiter"
93     {
94         my $table_name                       = $item{'table_name'};
95     die "Cannot ALTER table '$table_name'; it does not exist"
96         unless $tables{ $table_name };
97         for my $definition ( @{ $item[4] } ) { 
98         $definition->{'extra'}->{'alter'} = 1;
99         push @{ $tables{ $table_name }{'constraints'} }, $definition;
100     }
101     }
102
103 alter_specification : ADD foreign_key_def
104     { $return = $item[2] }
105
106 create : CREATE /database/i WORD "$delimiter"
107     { @table_comments = () }
108
109 create : CREATE TEMPORARY(?) TABLE opt_if_not_exists(?) table_name '(' create_definition(s /,/) /(,\s*)?\)/ table_option(s?) "$delimiter"
110     { 
111         my $table_name                       = $item{'table_name'};
112         $tables{ $table_name }{'order'}      = ++$table_order;
113         $tables{ $table_name }{'name'} = $table_name;
114
115         if ( @table_comments ) {
116             $tables{ $table_name }{'comments'} = [ @table_comments ];
117             @table_comments = ();
118         }
119
120         my $i = 1;
121         for my $definition ( @{ $item[7] } ) {
122             if ( $definition->{'supertype'} eq 'field' ) {
123                 my $field_name = $definition->{'name'};
124 #                $tables{ $table_name }{'columns'}{ $field_name } = { %$definition, order => $i };
125                 push @{$tables{$table_name}{'columns'}}, $definition;
126                 $i++;
127         
128                 if ( $definition->{'is_primary_key'} ) {
129                     push @{ $tables{ $table_name }{'constraints'} },
130                         {
131                             type   => 'primary_key',
132                             fields => [ $field_name ],
133                         }
134                     ;
135                 }
136             }
137             elsif ( $definition->{'supertype'} eq 'constraint' ) {
138                 push @{ $tables{ $table_name }{'constraints'} }, $definition;
139             }
140             elsif ( $definition->{'supertype'} eq 'index' ) {
141                 push @{ $tables{ $table_name }{'indices'} }, $definition;
142             }
143         }
144
145         if ( my @options = @{ $item{'table_option(s?)'} } ) {
146             for my $option ( @options ) {
147                 my ( $key, $value ) = each %$option;
148                 if ( $key eq 'comment' ) {
149                     push @{ $tables{ $table_name }{'comments'} }, $value;
150                 }
151                 else {
152                     push @{ $tables{ $table_name }{'table_options'} }, $option;
153                 }
154             }
155         }
156
157         1;
158     }
159
160 opt_if_not_exists : /if not exists/i
161
162 create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' "$delimiter"
163     {
164         @table_comments = ();
165         push @{ $tables{ $item{'table_name'} }{'indices'} },
166             {
167                 name   => $item[4],
168                 type   => $item[2][0] ? 'unique' : 'normal',
169                 fields => $item[8],
170             }
171         ;
172     }
173
174 create : CREATE /trigger/i NAME not_delimiter "$delimiter"
175     {
176         @table_comments = ();
177     }
178
179 create : CREATE PROCEDURE NAME not_delimiter "$delimiter"
180     {
181         @table_comments = ();
182         my $func_name = $item[3];
183         my $owner = '';
184         my $sql = "$item[1] $item[2] $item[3] $item[4]";
185         
186         $procedures{ $func_name }{'order'}  = ++$proc_order;
187         $procedures{ $func_name }{'name'}   = $func_name;
188         $procedures{ $func_name }{'owner'}  = $owner;
189         $procedures{ $func_name }{'sql'}    = $sql;
190     }
191
192 PROCEDURE : /procedure/i
193     | /function/i
194
195 create : CREATE replace(?) algorithm(?) /view/i NAME not_delimiter "$delimiter"
196     {
197         @table_comments = ();
198         my $view_name = $item[5];
199         my $sql = join(q{ }, grep { defined and length } $item[1], $item[2]->[0], $item[3]->[0])
200             . " $item[4] $item[5] $item[6]";
201         
202         # Hack to strip database from function calls in SQL
203         $sql =~ s#`\w+`\.(`\w+`\()##g;
204         
205         $views{ $view_name }{'order'}  = ++$view_order;
206         $views{ $view_name }{'name'}   = $view_name;
207         $views{ $view_name }{'sql'}    = $sql;
208     }
209
210 replace : /or replace/i
211
212 algorithm : /algorithm/i /=/ WORD
213     {
214         $return = "$item[1]=$item[3]";
215     }
216
217 not_delimiter : /.*?(?=$delimiter)/is
218
219 create_definition : constraint 
220     | index
221     | field
222     | comment
223     | <error>
224
225 comment : /^\s*(?:#|-{2}).*\n/ 
226     { 
227         my $comment =  $item[1];
228         $comment    =~ s/^\s*(#|--)\s*//;
229         $comment    =~ s/\s*$//;
230         $return     = $comment;
231     }
232
233 comment : /\/\*/ /.*?\*\//s
234     {
235         my $comment = $item[2];
236         $comment = substr($comment, 0, -2);
237         $comment    =~ s/^\s*|\s*$//g;
238         $return = $comment;
239     }
240     
241 field_comment : /^\s*(?:#|-{2}).*\n/ 
242     { 
243         my $comment =  $item[1];
244         $comment    =~ s/^\s*(#|--)\s*//;
245         $comment    =~ s/\s*$//;
246         $return     = $comment;
247     }
248
249
250 field_comment2 : /comment/i /'.*?'/
251     {
252         my $comment = $item[2];
253         $comment    =~ s/^'//;
254         $comment    =~ s/'$//;
255         $return     = $comment;
256     }
257
258 blank : /\s*/
259
260 field : field_comment(s?) field_name data_type field_qualifier(s?) field_comment2(?) reference_definition(?) on_update(?) field_comment(s?)
261     { 
262         my %qualifiers  = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
263         if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
264             $qualifiers{ $_ } = 1 for @type_quals;
265         }
266
267         my $null = defined $qualifiers{'not_null'} 
268                    ? $qualifiers{'not_null'} : 1;
269         delete $qualifiers{'not_null'};
270
271         my @comments = ( @{ $item[1] }, @{ $item[5] }, @{ $item[8] } );
272
273         $return = { 
274             supertype   => 'field',
275             name        => $item{'field_name'}, 
276             data_type   => $item{'data_type'}{'type'},
277             size        => $item{'data_type'}{'size'},
278             list        => $item{'data_type'}{'list'},
279             constraints => $item{'reference_definition(?)'},
280             comments    => [ @comments ],
281             %qualifiers,
282         } 
283     }
284     | <error>
285
286 field_qualifier : not_null
287     { 
288         $return = { 
289              is_nullable => $item{'not_null'},
290         } 
291     }
292
293 field_qualifier : default_val
294     { 
295         $return = { 
296              default => $item{'default_val'},
297         } 
298     }
299
300 field_qualifier : auto_inc
301     { 
302         $return = { 
303              is_auto_inc => $item{'auto_inc'},
304         } 
305     }
306
307 field_qualifier : primary_key
308     { 
309         $return = { 
310              is_primary_key => $item{'primary_key'},
311         } 
312     }
313
314 field_qualifier : unsigned
315     { 
316         $return = { 
317              is_unsigned => $item{'unsigned'},
318         } 
319     }
320
321 field_qualifier : /character set/i WORD 
322     {
323         $return = {
324             'CHARACTER SET' => $item[2],
325         }
326     }
327
328 field_qualifier : /collate/i WORD
329     {
330         $return = {
331             COLLATE => $item[2],
332         }
333     }
334
335 field_qualifier : /on update/i CURRENT_TIMESTAMP
336     {
337         $return = {
338             'ON UPDATE' => $item[2],
339         }
340     }
341
342 field_qualifier : /unique/i KEY(?)
343     {
344         $return = {
345             is_unique => 1,
346         }
347     }
348
349 field_qualifier : KEY
350     {
351         $return = {
352             has_index => 1,
353         }
354     }
355
356 reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete(?) on_update(?)
357     {
358         $return = {
359             type             => 'foreign_key',
360             reference_table  => $item[2],
361             reference_fields => $item[3][0],
362             match_type       => $item[4][0],
363             on_delete        => $item[5][0],
364             on_update        => $item[6][0],
365         }
366     }
367
368 match_type : /match full/i { 'full' }
369     |
370     /match partial/i { 'partial' }
371
372 on_delete : /on delete/i reference_option
373     { $item[2] }
374
375 on_update : 
376     /on update/i 'CURRENT_TIMESTAMP'
377     { $item[2] }
378     |
379     /on update/i reference_option
380     { $item[2] }
381
382 reference_option: /restrict/i | 
383     /cascade/i   | 
384     /set null/i  | 
385     /no action/i | 
386     /set default/i
387     { $item[1] }  
388
389 index : normal_index
390     | fulltext_index
391     | spatial_index
392     | <error>
393
394 table_name   : NAME
395
396 field_name   : NAME
397
398 index_name   : NAME
399
400 data_type    : WORD parens_value_list(s?) type_qualifier(s?)
401     { 
402         my $type = $item[1];
403         my $size; # field size, applicable only to non-set fields
404         my $list; # set list, applicable only to sets (duh)
405
406         if ( uc($type) =~ /^(SET|ENUM)$/ ) {
407             $size = undef;
408             $list = $item[2][0];
409         }
410         else {
411             $size = $item[2][0][0];
412             $list = [];
413         }
414
415
416         $return        = { 
417             type       => $type,
418             size       => $size,
419             list       => $list,
420             qualifiers => $item[3],
421         } 
422     }
423
424 parens_field_list : '(' field_name(s /,/) ')'
425     { $item[2] }
426
427 parens_value_list : '(' VALUE(s /,/) ')'
428     { $item[2] }
429
430 type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
431     { lc $item[1] }
432
433 field_type   : WORD
434
435 create_index : /create/i /index/i
436
437 not_null     : /not null/i 
438     { $return = 0 }
439     |
440     /null/i
441     { $return = 1 }
442
443 unsigned     : /unsigned/i { $return = 0 }
444
445 #default_val  : /default/i /(?:')?[\s\w\d:.-]*(?:')?/ 
446 #    { 
447 #        $item[2] =~ s/'//g; 
448 #        $return  =  $item[2];
449 #    }
450
451 default_val : 
452     /default/i 'CURRENT_TIMESTAMP'
453     {
454         $return =  \$item[2];
455     }
456     |
457     /default/i /'(?:.*?(?:\\'|''))*.*?'|(?:')?[\w\d:.-]*(?:')?/
458     {
459         $item[2] =~ s/^\s*'|'\s*$//g;
460         $return  =  $item[2];
461     }
462
463 auto_inc : /auto_increment/i { 1 }
464
465 primary_key : /primary/i /key/i { 1 }
466
467 constraint : primary_key_def
468     | unique_key_def
469     | foreign_key_def
470     | <error>
471
472 foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
473     {
474         $return              =  {
475             supertype        => 'constraint',
476             type             => 'foreign_key',
477             name             => $item[1],
478             fields           => $item[2],
479             %{ $item{'reference_definition'} },
480         }
481     }
482
483 foreign_key_def_begin : /constraint/i /foreign key/i WORD
484     { $return = $item[3] }
485     |
486     /constraint/i NAME /foreign key/i
487     { $return = $item[2] }
488     |
489     /constraint/i /foreign key/i
490     { $return = '' }
491     |
492     /foreign key/i WORD
493     { $return = $item[2] }
494     |
495     /foreign key/i
496     { $return = '' }
497
498 primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
499     { 
500         $return       = { 
501             supertype => 'constraint',
502             name      => $item{'index_name(?)'}[0],
503             type      => 'primary_key',
504             fields    => $item[4],
505         };
506     }
507
508 unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
509     { 
510         $return       = { 
511             supertype => 'constraint',
512             name      => $item{'index_name(?)'}[0],
513             type      => 'unique',
514             fields    => $item[5],
515         } 
516     }
517
518 normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
519     { 
520         $return       = { 
521             supertype => 'index',
522             type      => 'normal',
523             name      => $item{'index_name(?)'}[0],
524             fields    => $item[4],
525         } 
526     }
527
528 fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
529     { 
530         $return       = { 
531             supertype => 'index',
532             type      => 'fulltext',
533             name      => $item{'index_name(?)'}[0],
534             fields    => $item[5],
535         } 
536     }
537
538 spatial_index : /spatial/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
539     { 
540         $return       = { 
541             supertype => 'index',
542             type      => 'spatial',
543             name      => $item{'index_name(?)'}[0],
544             fields    => $item[5],
545         } 
546     }
547
548 name_with_opt_paren : NAME parens_value_list(s?)
549     { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
550
551 UNIQUE : /unique/i
552
553 KEY : /key/i | /index/i
554
555 table_option : /comment/i /=/ /'.*?'/
556     {
557         my $comment = $item[3];
558         $comment    =~ s/^'//;
559         $comment    =~ s/'$//;
560         $return     = { comment => $comment };
561     }
562     | /(default )?(charset|character set)/i /\s*=?\s*/ WORD
563     { 
564         $return = { 'CHARACTER SET' => $item[3] };
565     }
566     | /collate/i WORD
567     {
568         $return = { 'COLLATE' => $item[2] }
569     }
570     | /union/i /\s*=\s*/ '(' table_name(s /,/) ')'
571     { 
572         $return = { $item[1] => $item[4] };
573     }
574     | WORD /\s*=\s*/ MAYBE_QUOTED_WORD
575     {
576         $return = { $item[1] => $item[3] };
577     }
578
579 MAYBE_QUOTED_WORD: /\w+/
580                  | /'(\w+)'/
581                  { $return = $1 }
582                  | /"(\w+)"/
583                  { $return = $1 }
584
585 default : /default/i
586
587 ADD : /add/i
588
589 ALTER : /alter/i
590
591 CREATE : /create/i
592
593 TEMPORARY : /temporary/i
594
595 TABLE : /table/i
596
597 WORD : /\w+/
598
599 DIGITS : /\d+/
600
601 COMMA : ','
602
603 BACKTICK : '`'
604
605 DOUBLE_QUOTE: '"'
606
607 NAME    : BACKTICK /[^`]+/ BACKTICK
608     { $item[2] }
609     | DOUBLE_QUOTE /[^"]+/ DOUBLE_QUOTE
610     { $item[2] }
611     | /\w+/
612     { $item[1] }
613
614 VALUE   : /[-+]?\.?\d+(?:[eE]\d+)?/
615     { $item[1] }
616     | /'.*?'/   
617     { 
618         # remove leading/trailing quotes 
619         my $val = $item[1];
620         $val    =~ s/^['"]|['"]$//g;
621         $return = $val;
622     }
623     | /NULL/
624     { 'NULL' }
625
626 CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
627     | /now\(\)/i
628     { 'CURRENT_TIMESTAMP' }
629 !;
630 }
631 }