Updated to parse the new, single format sqlf xml and emit warnings when the old style...
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / Access.pm
CommitLineData
7c44825c 1package SQL::Translator::Parser::Access;
2
3# -------------------------------------------------------------------
4# $Id: Access.pm,v 1.1 2004-04-19 16:38:17 kycl4rk Exp $
5# -------------------------------------------------------------------
6# Copyright (C) 2002-4 SQLFairy Authors
7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License as
10# published by the Free Software Foundation; version 2.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20# 02111-1307 USA
21# -------------------------------------------------------------------
22
23=head1 NAME
24
25SQL::Translator::Parser::Access - parser for Access as produced by mdbtools
26
27=head1 SYNOPSIS
28
29 use SQL::Translator;
30 use SQL::Translator::Parser::Access;
31
32 my $translator = SQL::Translator->new;
33 $translator->parser("SQL::Translator::Parser::Access");
34
35=head1 DESCRIPTION
36
37The grammar derived from the MySQL grammar. The input is expected to be
38something similar to the output of mdbtools (http://mdbtools.sourceforge.net/).
39
40=cut
41
42use strict;
43use vars qw[ $DEBUG $VERSION $GRAMMAR @EXPORT_OK ];
44$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
45$DEBUG = 0 unless defined $DEBUG;
46
47use Data::Dumper;
48use Parse::RecDescent;
49use Exporter;
50use base qw(Exporter);
51
52@EXPORT_OK = qw(parse);
53
54# Enable warnings within the Parse::RecDescent module.
55$::RD_ERRORS = 1; # Make sure the parser dies when it encounters an error
56$::RD_WARN = 1; # Enable warnings. This will warn on unused rules &c.
57$::RD_HINT = 1; # Give out hints to help fix problems.
58
59$GRAMMAR = q!
60
61{
62 my ( %tables, $table_order, @table_comments );
63}
64
65#
66# The "eofile" rule makes the parser fail if any "statement" rule
67# fails. Otherwise, the first successful match by a "statement"
68# won't cause the failure needed to know that the parse, as a whole,
69# failed. -ky
70#
71startrule : statement(s) eofile { \%tables }
72
73eofile : /^\Z/
74
75statement : comment
76 | use
77 | set
78 | drop
79 | create
80 | <error>
81
82use : /use/i WORD ';'
83 { @table_comments = () }
84
85set : /set/i /[^;]+/ ';'
86 { @table_comments = () }
87
88drop : /drop/i TABLE /[^;]+/ ';'
89
90drop : /drop/i WORD(s) ';'
91 { @table_comments = () }
92
93create : CREATE /database/i WORD ';'
94 { @table_comments = () }
95
96create : CREATE TABLE table_name '(' create_definition(s /,/) ')' ';'
97 {
98 my $table_name = $item{'table_name'};
99 $tables{ $table_name }{'order'} = ++$table_order;
100 $tables{ $table_name }{'table_name'} = $table_name;
101
102 if ( @table_comments ) {
103 $tables{ $table_name }{'comments'} = [ @table_comments ];
104 @table_comments = ();
105 }
106
107 my $i = 1;
108 for my $definition ( @{ $item[5] } ) {
109 if ( $definition->{'supertype'} eq 'field' ) {
110 my $field_name = $definition->{'name'};
111 $tables{ $table_name }{'fields'}{ $field_name } =
112 { %$definition, order => $i };
113 $i++;
114
115 if ( $definition->{'is_primary_key'} ) {
116 push @{ $tables{ $table_name }{'constraints'} },
117 {
118 type => 'primary_key',
119 fields => [ $field_name ],
120 }
121 ;
122 }
123 }
124 elsif ( $definition->{'supertype'} eq 'constraint' ) {
125 push @{ $tables{ $table_name }{'constraints'} }, $definition;
126 }
127 elsif ( $definition->{'supertype'} eq 'index' ) {
128 push @{ $tables{ $table_name }{'indices'} }, $definition;
129 }
130 }
131
132 1;
133 }
134
135create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
136 {
137 @table_comments = ();
138 push @{ $tables{ $item{'table_name'} }{'indices'} },
139 {
140 name => $item[4],
141 type => $item[2] ? 'unique' : 'normal',
142 fields => $item[8],
143 }
144 ;
145 }
146
147create_definition : constraint
148 | index
149 | field
150 | comment
151 | <error>
152
153comment : /^\s*--(.*)\n/
154 {
155 my $comment = $1;
156 $return = $comment;
157 push @table_comments, $comment;
158 }
159
160field : field_name data_type field_qualifier(s?) reference_definition(?)
161 {
162# my %qualifiers = map { %$_ } @{ $item{'field_qualifier(s?)'} || [] };
163# if ( my @type_quals = @{ $item{'data_type'}{'qualifiers'} || [] } ) {
164# $qualifiers{ $_ } = 1 for @type_quals;
165# }
166#
167# my $null = defined $qualifiers{'not_null'}
168# ? $qualifiers{'not_null'} : 1;
169# delete $qualifiers{'not_null'};
170
171 $return = {
172 supertype => 'field',
173 name => $item{'field_name'},
174 data_type => $item{'data_type'}{'type'},
175 size => $item{'data_type'}{'size'},
176# null => $null,
177 constraints => $item{'reference_definition(?)'},
178# %qualifiers,
179 }
180 }
181 | <error>
182
183field_qualifier : not_null
184 {
185 $return = {
186 null => $item{'not_null'},
187 }
188 }
189
190field_qualifier : default_val
191 {
192 $return = {
193 default => $item{'default_val'},
194 }
195 }
196
197field_qualifier : auto_inc
198 {
199 $return = {
200 is_auto_inc => $item{'auto_inc'},
201 }
202 }
203
204field_qualifier : primary_key
205 {
206 $return = {
207 is_primary_key => $item{'primary_key'},
208 }
209 }
210
211field_qualifier : unsigned
212 {
213 $return = {
214 is_unsigned => $item{'unsigned'},
215 }
216 }
217
218field_qualifier : /character set/i WORD
219 {
220 $return = {
221 character_set => $item[2],
222 }
223 }
224
225reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete_do(?) on_update_do(?)
226 {
227 $return = {
228 type => 'foreign_key',
229 reference_table => $item[2],
230 reference_fields => $item[3][0],
231 match_type => $item[4][0],
232 on_delete_do => $item[5][0],
233 on_update_do => $item[6][0],
234 }
235 }
236
237match_type : /match full/i { 'full' }
238 |
239 /match partial/i { 'partial' }
240
241on_delete_do : /on delete/i reference_option
242 { $item[2] }
243
244on_update_do : /on update/i reference_option
245 { $item[2] }
246
247reference_option: /restrict/i |
248 /cascade/i |
249 /set null/i |
250 /no action/i |
251 /set default/i
252 { $item[1] }
253
254index : normal_index
255 | fulltext_index
256 | <error>
257
258table_name : NAME
259
260field_name : NAME
261
262index_name : NAME
263
264data_type : access_data_type parens_value_list(s?) type_qualifier(s?)
265 {
266 $return = {
267 type => $item[1],
268 size => $item[2][0],
269 qualifiers => $item[3],
270 }
271 }
272
273access_data_type : /long integer/i { $return = 'Long Integer' }
274 | /text/i { $return = 'Text' }
275 | /datetime (\(short\))?/i { $return = 'DateTime' }
276 | /boolean/i { $return = 'Boolean' }
277 | WORD
278
279parens_field_list : '(' field_name(s /,/) ')'
280 { $item[2] }
281
282parens_value_list : '(' VALUE(s /,/) ')'
283 { $item[2] }
284
285type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
286 { lc $item[1] }
287
288field_type : WORD
289
290create_index : /create/i /index/i
291
292not_null : /not/i /null/i { $return = 0 }
293
294unsigned : /unsigned/i { $return = 0 }
295
296default_val : /default/i /'(?:.*?\\')*.*?'|(?:')?[\w\d:.-]*(?:')?/
297 {
298 $item[2] =~ s/^\s*'|'\s*$//g;
299 $return = $item[2];
300 }
301
302auto_inc : /auto_increment/i { 1 }
303
304primary_key : /primary/i /key/i { 1 }
305
306constraint : primary_key_def
307 | unique_key_def
308 | foreign_key_def
309 | <error>
310
311foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
312 {
313 $return = {
314 supertype => 'constraint',
315 type => 'foreign_key',
316 name => $item[1],
317 fields => $item[2],
318 %{ $item{'reference_definition'} },
319 }
320 }
321
322foreign_key_def_begin : /constraint/i /foreign key/i
323 { $return = '' }
324 |
325 /constraint/i WORD /foreign key/i
326 { $return = $item[2] }
327 |
328 /foreign key/i
329 { $return = '' }
330
331primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
332 {
333 $return = {
334 supertype => 'constraint',
335 name => $item{'index_name(?)'}[0],
336 type => 'primary_key',
337 fields => $item[4],
338 };
339 }
340
341unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
342 {
343 $return = {
344 supertype => 'constraint',
345 name => $item{'index_name(?)'}[0],
346 type => 'unique',
347 fields => $item[5],
348 }
349 }
350
351normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
352 {
353 $return = {
354 supertype => 'index',
355 type => 'normal',
356 name => $item{'index_name(?)'}[0],
357 fields => $item[4],
358 }
359 }
360
361fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
362 {
363 $return = {
364 supertype => 'index',
365 type => 'fulltext',
366 name => $item{'index_name(?)'}[0],
367 fields => $item[5],
368 }
369 }
370
371name_with_opt_paren : NAME parens_value_list(s?)
372 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
373
374UNIQUE : /unique/i { 1 }
375
376KEY : /key/i | /index/i
377
378table_option : WORD /\s*=\s*/ WORD
379 {
380 $return = { $item[1] => $item[3] };
381 }
382
383CREATE : /create/i
384
385TEMPORARY : /temporary/i
386
387TABLE : /table/i
388
389WORD : /\w+/
390
391DIGITS : /\d+/
392
393COMMA : ','
394
395NAME : "`" /\w+/ "`"
396 { $item[2] }
397 | /\w+/
398 { $item[1] }
399
400VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
401 { $item[1] }
402 | /'.*?'/
403 {
404 # remove leading/trailing quotes
405 my $val = $item[1];
406 $val =~ s/^['"]|['"]$//g;
407 $return = $val;
408 }
409 | /NULL/
410 { 'NULL' }
411
412!;
413
414# -------------------------------------------------------------------
415sub parse {
416 my ( $translator, $data ) = @_;
417 my $parser = Parse::RecDescent->new($GRAMMAR);
418
419 local $::RD_TRACE = $translator->trace ? 1 : undef;
420 local $DEBUG = $translator->debug;
421
422 unless (defined $parser) {
423 return $translator->error("Error instantiating Parse::RecDescent ".
424 "instance: Bad grammer");
425 }
426
427 my $result = $parser->startrule($data);
428 return $translator->error( "Parse failed." ) unless defined $result;
429 warn Dumper( $result ) if $DEBUG;
430
431 my $schema = $translator->schema;
432 my @tables = sort {
433 $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
434 } keys %{ $result };
435
436 for my $table_name ( @tables ) {
437 my $tdata = $result->{ $table_name };
438 my $table = $schema->add_table(
439 name => $tdata->{'table_name'},
440 ) or die $schema->error;
441
442 $table->comments( $tdata->{'comments'} );
443
444 my @fields = sort {
445 $tdata->{'fields'}->{$a}->{'order'}
446 <=>
447 $tdata->{'fields'}->{$b}->{'order'}
448 } keys %{ $tdata->{'fields'} };
449
450 for my $fname ( @fields ) {
451 my $fdata = $tdata->{'fields'}{ $fname };
452 my $field = $table->add_field(
453 name => $fdata->{'name'},
454 data_type => $fdata->{'data_type'},
455 size => $fdata->{'size'},
456 default_value => $fdata->{'default'},
457 is_auto_increment => $fdata->{'is_auto_inc'},
458 is_nullable => $fdata->{'null'},
459 comments => $fdata->{'comments'},
460 ) or die $table->error;
461
462 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
463
464# for my $qual ( qw[ binary unsigned zerofill list ] ) {
465# if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
466# next if ref $val eq 'ARRAY' && !@$val;
467# $field->extra( $qual, $val );
468# }
469# }
470
471# for my $cdata ( @{ $fdata->{'constraints'} } ) {
472# next unless $cdata->{'type'} eq 'foreign_key';
473# $cdata->{'fields'} ||= [ $field->name ];
474# push @{ $tdata->{'constraints'} }, $cdata;
475# }
476 }
477
478 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
479 my $index = $table->add_index(
480 name => $idata->{'name'},
481 type => uc $idata->{'type'},
482 fields => $idata->{'fields'},
483 ) or die $table->error;
484 }
485
486# for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
487# my $constraint = $table->add_constraint(
488# name => $cdata->{'name'},
489# type => $cdata->{'type'},
490# fields => $cdata->{'fields'},
491# reference_table => $cdata->{'reference_table'},
492# reference_fields => $cdata->{'reference_fields'},
493# match_type => $cdata->{'match_type'} || '',
494# on_delete => $cdata->{'on_delete_do'},
495# on_update => $cdata->{'on_update_do'},
496# ) or die $table->error;
497# }
498 }
499
500 return 1;
501}
502
5031;
504
505# -------------------------------------------------------------------
506# Where man is not nature is barren.
507# William Blake
508# -------------------------------------------------------------------
509
510=pod
511
512=head1 AUTHOR
513
514Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
515
516=head1 SEE ALSO
517
518perl(1), Parse::RecDescent, SQL::Translator::Schema.
519
520=cut