turn data_type_mapping into an attribute
[dbsrgits/SQL-Translator-2.0-ish.git] / lib / SQL / Translator / Grammar / MySQL.pm
CommitLineData
247f09cd 1use MooseX::Declare;
2role 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
22method _build_grammar {
23return 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#
36startrule : statement(s) eofile {
37 {
38 database_name => $database_name,
39 tables => \%tables,
40 views => \%views,
41 procedures => \%procedures,
42 }
43}
44
45eofile : /^\Z/
46
47statement : comment
48 | use
49 | set
50 | drop
51 | create
52 | alter
53 | insert
54 | delimiter
55 | empty_statement
56 | <error>
57
58use : /use/i WORD "$delimiter"
59 {
60 $database_name = $item[2];
61 @table_comments = ();
62 }
63
64set : /set/i /[^;]+/ "$delimiter"
65 { @table_comments = () }
66
67drop : /drop/i TABLE /[^;]+/ "$delimiter"
68
69drop : /drop/i WORD(s) "$delimiter"
70 { @table_comments = () }
71
72string :
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
81nonstring : /[^;\'"]+/
82
83statement_body : string | nonstring
84
85insert : /insert/i statement_body(s?) "$delimiter"
86
87delimiter : /delimiter/i /[\S]+/
88 { $delimiter = $item[2] }
89
90empty_statement : "$delimiter"
91
92alter : 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
103alter_specification : ADD foreign_key_def
104 { $return = $item[2] }
105
106create : CREATE /database/i WORD "$delimiter"
107 { @table_comments = () }
108
109create : 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
160opt_if_not_exists : /if not exists/i
161
162create : 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
174create : CREATE /trigger/i NAME not_delimiter "$delimiter"
175 {
176 @table_comments = ();
177 }
178
179create : 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
192PROCEDURE : /procedure/i
193 | /function/i
194
195create : 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
210replace : /or replace/i
211
212algorithm : /algorithm/i /=/ WORD
213 {
214 $return = "$item[1]=$item[3]";
215 }
216
217not_delimiter : /.*?(?=$delimiter)/is
218
219create_definition : constraint
220 | index
221 | field
222 | comment
223 | <error>
224
225comment : /^\s*(?:#|-{2}).*\n/
226 {
227 my $comment = $item[1];
228 $comment =~ s/^\s*(#|--)\s*//;
229 $comment =~ s/\s*$//;
230 $return = $comment;
231 }
232
233comment : /\/\*/ /.*?\*\//s
234 {
235 my $comment = $item[2];
236 $comment = substr($comment, 0, -2);
237 $comment =~ s/^\s*|\s*$//g;
238 $return = $comment;
239 }
240
241field_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
250field_comment2 : /comment/i /'.*?'/
251 {
252 my $comment = $item[2];
253 $comment =~ s/^'//;
254 $comment =~ s/'$//;
255 $return = $comment;
256 }
257
258blank : /\s*/
259
260field : 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
286field_qualifier : not_null
287 {
288 $return = {
289 is_nullable => $item{'not_null'},
290 }
291 }
292
293field_qualifier : default_val
294 {
295 $return = {
296 default => $item{'default_val'},
297 }
298 }
299
300field_qualifier : auto_inc
301 {
302 $return = {
303 is_auto_inc => $item{'auto_inc'},
304 }
305 }
306
307field_qualifier : primary_key
308 {
309 $return = {
310 is_primary_key => $item{'primary_key'},
311 }
312 }
313
314field_qualifier : unsigned
315 {
316 $return = {
317 is_unsigned => $item{'unsigned'},
318 }
319 }
320
321field_qualifier : /character set/i WORD
322 {
323 $return = {
324 'CHARACTER SET' => $item[2],
325 }
326 }
327
328field_qualifier : /collate/i WORD
329 {
330 $return = {
331 COLLATE => $item[2],
332 }
333 }
334
335field_qualifier : /on update/i CURRENT_TIMESTAMP
336 {
337 $return = {
338 'ON UPDATE' => $item[2],
339 }
340 }
341
342field_qualifier : /unique/i KEY(?)
343 {
344 $return = {
345 is_unique => 1,
346 }
347 }
348
349field_qualifier : KEY
350 {
351 $return = {
352 has_index => 1,
353 }
354 }
355
356reference_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
368match_type : /match full/i { 'full' }
369 |
370 /match partial/i { 'partial' }
371
372on_delete : /on delete/i reference_option
373 { $item[2] }
374
375on_update :
376 /on update/i 'CURRENT_TIMESTAMP'
377 { $item[2] }
378 |
379 /on update/i reference_option
380 { $item[2] }
381
382reference_option: /restrict/i |
383 /cascade/i |
384 /set null/i |
385 /no action/i |
386 /set default/i
387 { $item[1] }
388
389index : normal_index
390 | fulltext_index
391 | spatial_index
392 | <error>
393
394table_name : NAME
395
396field_name : NAME
397
398index_name : NAME
399
400data_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
424parens_field_list : '(' field_name(s /,/) ')'
425 { $item[2] }
426
427parens_value_list : '(' VALUE(s /,/) ')'
428 { $item[2] }
429
430type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
431 { lc $item[1] }
432
433field_type : WORD
434
435create_index : /create/i /index/i
436
437not_null : /not null/i
438 { $return = 0 }
439 |
440 /null/i
441 { $return = 1 }
442
443unsigned : /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
451default_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
463auto_inc : /auto_increment/i { 1 }
464
465primary_key : /primary/i /key/i { 1 }
466
467constraint : primary_key_def
468 | unique_key_def
469 | foreign_key_def
470 | <error>
471
472foreign_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
483foreign_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
498primary_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
508unique_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
518normal_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
528fulltext_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
538spatial_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
548name_with_opt_paren : NAME parens_value_list(s?)
549 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
550
551UNIQUE : /unique/i
552
553KEY : /key/i | /index/i
554
555table_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
579MAYBE_QUOTED_WORD: /\w+/
580 | /'(\w+)'/
581 { $return = $1 }
582 | /"(\w+)"/
583 { $return = $1 }
584
585default : /default/i
586
587ADD : /add/i
588
589ALTER : /alter/i
590
591CREATE : /create/i
592
593TEMPORARY : /temporary/i
594
595TABLE : /table/i
596
597WORD : /\w+/
598
599DIGITS : /\d+/
600
601COMMA : ','
602
603BACKTICK : '`'
604
605DOUBLE_QUOTE: '"'
606
607NAME : BACKTICK /[^`]+/ BACKTICK
608 { $item[2] }
609 | DOUBLE_QUOTE /[^"]+/ DOUBLE_QUOTE
610 { $item[2] }
611 | /\w+/
612 { $item[1] }
613
614VALUE : /[-+]?\.?\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
626CURRENT_TIMESTAMP : /current_timestamp(\(\))?/i
627 | /now\(\)/i
628 { 'CURRENT_TIMESTAMP' }
629!;
630}
631}