Commit | Line | Data |
247f09cd |
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 | } |