Got foreign key references basically working now.
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / MySQL.pm
CommitLineData
16dc9970 1package SQL::Translator::Parser::MySQL;
2
49e1eb70 3# -------------------------------------------------------------------
629b76f9 4# $Id: MySQL.pm,v 1.12 2003-02-25 14:55:35 kycl4rk Exp $
49e1eb70 5# -------------------------------------------------------------------
abfa405a 6# Copyright (C) 2003 Ken Y. Clark <kclark@cpan.org>,
7# darren chamberlain <darren@cpan.org>,
8# Chris Mungall <cjm@fruitfly.org>
077ebf34 9#
10# This program is free software; you can redistribute it and/or
11# modify it under the terms of the GNU General Public License as
12# published by the Free Software Foundation; version 2.
13#
14# This program is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17# General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
22# 02111-1307 USA
23# -------------------------------------------------------------------
16dc9970 24
d529894e 25=head1 NAME
26
27SQL::Translator::Parser::MySQL - parser for MySQL
28
29=head1 SYNOPSIS
30
31 use SQL::Translator;
32 use SQL::Translator::Parser::MySQL;
33
34 my $translator = SQL::Translator->new;
35 $translator->parser("SQL::Translator::Parser::MySQL");
36
37=head1 DESCRIPTION
38
39The grammar is influenced heavily by Tim Bunce's "mysql2ora" grammar.
40
629b76f9 41Here's the word from the MySQL site
42(http://www.mysql.com/doc/en/CREATE_TABLE.html):
43
44 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)]
45 [table_options] [select_statement]
46
47 or
48
49 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name LIKE old_table_name;
50
51 create_definition:
52 col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT]
53 [PRIMARY KEY] [reference_definition]
54 or PRIMARY KEY (index_col_name,...)
55 or KEY [index_name] (index_col_name,...)
56 or INDEX [index_name] (index_col_name,...)
57 or UNIQUE [INDEX] [index_name] (index_col_name,...)
58 or FULLTEXT [INDEX] [index_name] (index_col_name,...)
59 or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...)
60 [reference_definition]
61 or CHECK (expr)
62
63 type:
64 TINYINT[(length)] [UNSIGNED] [ZEROFILL]
65 or SMALLINT[(length)] [UNSIGNED] [ZEROFILL]
66 or MEDIUMINT[(length)] [UNSIGNED] [ZEROFILL]
67 or INT[(length)] [UNSIGNED] [ZEROFILL]
68 or INTEGER[(length)] [UNSIGNED] [ZEROFILL]
69 or BIGINT[(length)] [UNSIGNED] [ZEROFILL]
70 or REAL[(length,decimals)] [UNSIGNED] [ZEROFILL]
71 or DOUBLE[(length,decimals)] [UNSIGNED] [ZEROFILL]
72 or FLOAT[(length,decimals)] [UNSIGNED] [ZEROFILL]
73 or DECIMAL(length,decimals) [UNSIGNED] [ZEROFILL]
74 or NUMERIC(length,decimals) [UNSIGNED] [ZEROFILL]
75 or CHAR(length) [BINARY]
76 or VARCHAR(length) [BINARY]
77 or DATE
78 or TIME
79 or TIMESTAMP
80 or DATETIME
81 or TINYBLOB
82 or BLOB
83 or MEDIUMBLOB
84 or LONGBLOB
85 or TINYTEXT
86 or TEXT
87 or MEDIUMTEXT
88 or LONGTEXT
89 or ENUM(value1,value2,value3,...)
90 or SET(value1,value2,value3,...)
91
92 index_col_name:
93 col_name [(length)]
94
95 reference_definition:
96 REFERENCES tbl_name [(index_col_name,...)]
97 [MATCH FULL | MATCH PARTIAL]
98 [ON DELETE reference_option]
99 [ON UPDATE reference_option]
100
101 reference_option:
102 RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
103
104 table_options:
105 TYPE = {BDB | HEAP | ISAM | InnoDB | MERGE | MRG_MYISAM | MYISAM }
106 or AUTO_INCREMENT = #
107 or AVG_ROW_LENGTH = #
108 or CHECKSUM = {0 | 1}
109 or COMMENT = "string"
110 or MAX_ROWS = #
111 or MIN_ROWS = #
112 or PACK_KEYS = {0 | 1 | DEFAULT}
113 or PASSWORD = "string"
114 or DELAY_KEY_WRITE = {0 | 1}
115 or ROW_FORMAT= { default | dynamic | fixed | compressed }
116 or RAID_TYPE= {1 | STRIPED | RAID0 } RAID_CHUNKS=# RAID_CHUNKSIZE=#
117 or UNION = (table_name,[table_name...])
118 or INSERT_METHOD= {NO | FIRST | LAST }
119 or DATA DIRECTORY="absolute path to directory"
120 or INDEX DIRECTORY="absolute path to directory"
121
d529894e 122=cut
123
16dc9970 124use strict;
d529894e 125use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
629b76f9 126$VERSION = sprintf "%d.%02d", q$Revision: 1.12 $ =~ /(\d+)\.(\d+)/;
8d0f3086 127$DEBUG = 0 unless defined $DEBUG;
077ebf34 128
d529894e 129use Data::Dumper;
077ebf34 130use Parse::RecDescent;
131use Exporter;
132use base qw(Exporter);
133
134@EXPORT_OK = qw(parse);
135
d529894e 136# Enable warnings within the Parse::RecDescent module.
137$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
138$::RD_WARN = 1; # Enable warnings. This will warn on unused rules &c.
139$::RD_HINT = 1; # Give out hints to help fix problems.
140
077ebf34 141my $parser; # should we do this? There's no programmic way to
142 # change the grammar, so I think this is safe.
16dc9970 143
d529894e 144$GRAMMAR = q!
145
146{ our ( %tables, $table_order ) }
147
629b76f9 148#
149# The "eofile" rule makes the parser fail if any "statement" rule
150# fails. Otherwise, the first successful match by a "statement"
151# won't cause the failure needed to know that the parse, as a whole,
152# failed. -ky
153#
154startrule : statement(s) eofile { \%tables }
155
156eofile : /^\Z/
d529894e 157
158statement : comment
61745327 159 | drop
d529894e 160 | create
161 | <error>
162
61745327 163drop : /drop/i WORD(s) ';'
164
d529894e 165create : create_table table_name '(' create_definition(s /,/) ')' table_option(s?) ';'
166 {
167 my $table_name = $item{'table_name'};
168 $tables{ $table_name }{'order'} = ++$table_order;
169 $tables{ $table_name }{'table_name'} = $table_name;
170
61745327 171 my $i = 1;
d529894e 172 for my $definition ( @{ $item[4] } ) {
173 if ( $definition->{'type'} eq 'field' ) {
174 my $field_name = $definition->{'name'};
175 $tables{ $table_name }{'fields'}{ $field_name } =
176 { %$definition, order => $i };
177 $i++;
178
179 if ( $definition->{'is_primary_key'} ) {
180 push @{ $tables{ $table_name }{'indices'} },
181 {
182 type => 'primary_key',
183 fields => [ $field_name ],
16dc9970 184 }
d529894e 185 ;
186 }
dd2ef5ae 187 }
d529894e 188 else {
189 push @{ $tables{ $table_name }{'indices'} },
190 $definition;
dd2ef5ae 191 }
d529894e 192 }
dd2ef5ae 193
d529894e 194 for my $opt ( @{ $item{'table_option'} } ) {
195 if ( my ( $key, $val ) = each %$opt ) {
196 $tables{ $table_name }{'table_options'}{ $key } = $val;
dd2ef5ae 197 }
d529894e 198 }
199 }
dd2ef5ae 200
d529894e 201create : /CREATE/i unique(?) /(INDEX|KEY)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
202 {
203 push @{ $tables{ $item{'table_name'} }{'indices'} },
204 {
205 name => $item[4],
206 type => $item[2] ? 'unique' : 'normal',
207 fields => $item[8],
dd2ef5ae 208 }
d529894e 209 ;
210 }
dd2ef5ae 211
d529894e 212create_definition : index
213 | field
214 | <error>
215
216comment : /^\s*(?:#|-{2}).*\n/
217
218blank : /\s*/
219
220field : field_name data_type field_qualifier(s?)
221 {
222 my %qualifiers = map { %$_ } @{ $item{'field_qualifier'} || [] };
223 my $null = defined $item{'not_null'} ? $item{'not_null'} : 1;
224 delete $qualifiers{'not_null'};
225 if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
226 $qualifiers{ $_ } = 1 for @type_quals;
227 }
228
229 $return = {
230 type => 'field',
231 name => $item{'field_name'},
232 data_type => $item{'data_type'}{'type'},
233 size => $item{'data_type'}{'size'},
234 list => $item{'data_type'}{'list'},
235 null => $null,
236 %qualifiers,
237 }
238 }
239 | <error>
dd2ef5ae 240
d529894e 241field_qualifier : not_null
242 {
243 $return = {
244 null => $item{'not_null'},
245 }
246 }
16dc9970 247
d529894e 248field_qualifier : default_val
249 {
250 $return = {
251 default => $item{'default_val'},
252 }
253 }
16dc9970 254
d529894e 255field_qualifier : auto_inc
256 {
257 $return = {
258 is_auto_inc => $item{'auto_inc'},
259 }
260 }
16dc9970 261
d529894e 262field_qualifier : primary_key
263 {
264 $return = {
265 is_primary_key => $item{'primary_key'},
266 }
267 }
16dc9970 268
d529894e 269field_qualifier : unsigned
270 {
271 $return = {
272 is_unsigned => $item{'unsigned'},
273 }
274 }
16dc9970 275
d529894e 276index : primary_key_index
277 | unique_index
371f5f88 278 | fulltext_index
d529894e 279 | normal_index
280
281table_name : WORD
282
283field_name : WORD
284
285index_name : WORD
286
287data_type : WORD parens_value_list(s?) type_qualifier(s?)
288 {
289 my $type = $item[1];
290 my $size; # field size, applicable only to non-set fields
291 my $list; # set list, applicable only to sets (duh)
292
44fcd0b5 293 if ( uc($type) =~ /^(SET|ENUM)$/ ) {
d529894e 294 $size = undef;
295 $list = $item[2][0];
296 }
297 else {
298 $size = $item[2][0];
299 $list = [];
300 }
301
302 $return = {
303 type => $type,
304 size => $size,
305 list => $list,
306 qualifiers => $item[3],
307 }
308 }
16dc9970 309
d529894e 310parens_value_list : '(' VALUE(s /,/) ')'
311 { $item[2] }
16dc9970 312
d529894e 313type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
314 { lc $item[1] }
16dc9970 315
d529894e 316field_type : WORD
16dc9970 317
d529894e 318field_size : '(' num_range ')' { $item{'num_range'} }
16dc9970 319
d529894e 320num_range : DIGITS ',' DIGITS
321 { $return = $item[1].','.$item[3] }
322 | DIGITS
323 { $return = $item[1] }
dd2ef5ae 324
d529894e 325create_table : /create/i /table/i
16dc9970 326
d529894e 327create_index : /create/i /index/i
dd2ef5ae 328
d529894e 329not_null : /not/i /null/i { $return = 0 }
16dc9970 330
d529894e 331unsigned : /unsigned/i { $return = 0 }
16dc9970 332
d529894e 333default_val : /default/i /(?:')?[\w\d.-]*(?:')?/
334 {
335 $item[2] =~ s/'//g;
336 $return = $item[2];
337 }
16dc9970 338
d529894e 339auto_inc : /auto_increment/i { 1 }
16dc9970 340
d529894e 341primary_key : /primary/i /key/i { 1 }
16dc9970 342
d529894e 343primary_key_index : primary_key index_name(?) '(' field_name(s /,/) ')'
344 {
345 $return = {
346 name => $item{'index_name'}[0],
347 type => 'primary_key',
348 fields => $item[4],
349 }
350 }
16dc9970 351
d529894e 352normal_index : key index_name(?) '(' name_with_opt_paren(s /,/) ')'
353 {
354 $return = {
355 name => $item{'index_name'}[0],
356 type => 'normal',
357 fields => $item[4],
358 }
359 }
16dc9970 360
d529894e 361unique_index : unique key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
362 {
363 $return = {
364 name => $item{'index_name'}[0],
365 type => 'unique',
366 fields => $item[5],
367 }
368 }
16dc9970 369
371f5f88 370fulltext_index : fulltext key(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
371 {
372 $return = {
373 name => $item{'index_name'}[0],
374 type => 'fulltext',
375 fields => $item[5],
376 }
377 }
378
d529894e 379name_with_opt_paren : NAME parens_value_list(s?)
380 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
16dc9970 381
371f5f88 382fulltext : /fulltext/i { 1 }
383
d529894e 384unique : /unique/i { 1 }
16dc9970 385
d529894e 386key : /key/i | /index/i
16dc9970 387
44fcd0b5 388table_option : /[^\s;]*/
d529894e 389 {
390 $return = { split /=/, $item[1] }
391 }
16dc9970 392
d529894e 393WORD : /\w+/
16dc9970 394
d529894e 395DIGITS : /\d+/
16dc9970 396
d529894e 397COMMA : ','
16dc9970 398
d529894e 399NAME : "`" /\w+/ "`"
400 { $item[2] }
401 | /\w+/
402 { $item[1] }
16dc9970 403
d529894e 404VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
405 { $item[1] }
406 | /'.*?'/ # XXX doesn't handle embedded quotes
407 { $item[1] }
408 | /NULL/
409 { 'NULL' }
16dc9970 410
d529894e 411!;
16dc9970 412
d529894e 413# -------------------------------------------------------------------
414sub parse {
415 my ( $translator, $data ) = @_;
416 $parser ||= Parse::RecDescent->new($GRAMMAR);
077ebf34 417
d529894e 418 $::RD_TRACE = $translator->trace ? 1 : undef;
419 $DEBUG = $translator->debug;
420
421 unless (defined $parser) {
422 return $translator->error("Error instantiating Parse::RecDescent ".
423 "instance: Bad grammer");
424 }
425
426 my $result = $parser->startrule($data);
427 die "Parse failed.\n" unless defined $result;
428 warn Dumper($result) if $DEBUG;
429 return $result;
430}
431
4321;
433
434#-----------------------------------------------------
435# Where man is not nature is barren.
436# William Blake
437#-----------------------------------------------------
16dc9970 438
d529894e 439=pod
16dc9970 440
441=head1 AUTHOR
442
d529894e 443Ken Y. Clark E<lt>kclark@cpan.orgE<gt>,
444Chris Mungall
16dc9970 445
446=head1 SEE ALSO
447
d529894e 448perl(1), Parse::RecDescent.
16dc9970 449
450=cut