1 package SQL::Translator::Parser::Access;
5 SQL::Translator::Parser::Access - parser for Access as produced by mdbtools
10 use SQL::Translator::Parser::Access;
12 my $translator = SQL::Translator->new;
13 $translator->parser("SQL::Translator::Parser::Access");
17 The grammar derived from the MySQL grammar. The input is expected to be
18 something similar to the output of mdbtools (http://mdbtools.sourceforge.net/).
25 our $VERSION = '1.59';
28 $DEBUG = 0 unless defined $DEBUG;
31 use SQL::Translator::Utils qw/ddl_parser_instance/;
33 use base qw(Exporter);
34 our @EXPORT_OK = qw(parse);
36 our $GRAMMAR = <<'END_OF_GRAMMAR';
39 my ( %tables, $table_order, @table_comments );
43 # The "eofile" rule makes the parser fail if any "statement" rule
44 # fails. Otherwise, the first successful match by a "statement"
45 # won't cause the failure needed to know that the parse, as a whole,
48 startrule : statement(s) eofile { \%tables }
60 { @table_comments = () }
62 set : /set/i /[^;]+/ ';'
63 { @table_comments = () }
65 drop : /drop/i TABLE /[^;]+/ ';'
67 drop : /drop/i WORD(s) ';'
68 { @table_comments = () }
70 create : CREATE /database/i WORD ';'
71 { @table_comments = () }
73 create : CREATE TABLE table_name '(' create_definition(s /,/) ')' ';'
75 my $table_name = $item{'table_name'};
76 $tables{ $table_name }{'order'} = ++$table_order;
77 $tables{ $table_name }{'table_name'} = $table_name;
79 if ( @table_comments ) {
80 $tables{ $table_name }{'comments'} = [ @table_comments ];
85 for my $definition ( @{ $item[5] } ) {
86 if ( $definition->{'supertype'} eq 'field' ) {
87 my $field_name = $definition->{'name'};
88 $tables{ $table_name }{'fields'}{ $field_name } =
89 { %$definition, order => $i };
92 if ( $definition->{'is_primary_key'} ) {
93 push @{ $tables{ $table_name }{'constraints'} },
95 type => 'primary_key',
96 fields => [ $field_name ],
101 elsif ( $definition->{'supertype'} eq 'constraint' ) {
102 push @{ $tables{ $table_name }{'constraints'} }, $definition;
104 elsif ( $definition->{'supertype'} eq 'index' ) {
105 push @{ $tables{ $table_name }{'indices'} }, $definition;
112 create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
114 @table_comments = ();
115 push @{ $tables{ $item{'table_name'} }{'indices'} },
118 type => $item[2] ? 'unique' : 'normal',
124 create_definition : constraint
130 comment : /^\s*--(.*)\n/
134 push @table_comments, $comment;
137 field : field_name data_type field_qualifier(s?) reference_definition(?)
140 supertype => 'field',
141 name => $item{'field_name'},
142 data_type => $item{'data_type'}{'type'},
143 size => $item{'data_type'}{'size'},
144 constraints => $item{'reference_definition(?)'},
149 field_qualifier : not_null
152 null => $item{'not_null'},
156 field_qualifier : default_val
159 default => $item{'default_val'},
163 field_qualifier : auto_inc
166 is_auto_inc => $item{'auto_inc'},
170 field_qualifier : primary_key
173 is_primary_key => $item{'primary_key'},
177 field_qualifier : unsigned
180 is_unsigned => $item{'unsigned'},
184 field_qualifier : /character set/i WORD
187 character_set => $item[2],
191 reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete(?) on_update(?)
194 type => 'foreign_key',
195 reference_table => $item[2],
196 reference_fields => $item[3][0],
197 match_type => $item[4][0],
198 on_delete => $item[5][0],
199 on_update => $item[6][0],
203 match_type : /match full/i { 'full' }
205 /match partial/i { 'partial' }
207 on_delete : /on delete/i reference_option
210 on_update : /on update/i reference_option
213 reference_option: /restrict/i |
230 data_type : access_data_type parens_value_list(s?) type_qualifier(s?)
235 qualifiers => $item[3],
239 access_data_type : /long integer/i { $return = 'Long Integer' }
240 | /text/i { $return = 'Text' }
241 | /datetime (\(short\))?/i { $return = 'DateTime' }
242 | /boolean/i { $return = 'Boolean' }
245 parens_field_list : '(' field_name(s /,/) ')'
248 parens_value_list : '(' VALUE(s /,/) ')'
251 type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
256 create_index : /create/i /index/i
258 not_null : /not/i /null/i { $return = 0 }
260 unsigned : /unsigned/i { $return = 0 }
262 default_val : /default/i /'(?:.*?\')*.*?'|(?:')?[\w\d:.-]*(?:')?/
264 $item[2] =~ s/^\s*'|'\s*$//g;
268 auto_inc : /auto_increment/i { 1 }
270 primary_key : /primary/i /key/i { 1 }
272 constraint : primary_key_def
277 foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
280 supertype => 'constraint',
281 type => 'foreign_key',
284 %{ $item{'reference_definition'} },
288 foreign_key_def_begin : /constraint/i /foreign key/i
291 /constraint/i WORD /foreign key/i
292 { $return = $item[2] }
297 primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
300 supertype => 'constraint',
301 name => $item{'index_name(?)'}[0],
302 type => 'primary_key',
307 unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
310 supertype => 'constraint',
311 name => $item{'index_name(?)'}[0],
317 normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
320 supertype => 'index',
322 name => $item{'index_name(?)'}[0],
327 fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
330 supertype => 'index',
332 name => $item{'index_name(?)'}[0],
337 name_with_opt_paren : NAME parens_value_list(s?)
338 { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
340 UNIQUE : /unique/i { 1 }
342 KEY : /key/i | /index/i
344 table_option : WORD /\s*=\s*/ WORD
346 $return = { $item[1] => $item[3] };
351 TEMPORARY : /temporary/i
366 VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
370 # remove leading/trailing quotes
372 $val =~ s/^['"]|['"]$//g;
381 my ( $translator, $data ) = @_;
383 # Enable warnings within the Parse::RecDescent module.
384 local $::RD_ERRORS = 1 unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
385 local $::RD_WARN = 1 unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
386 local $::RD_HINT = 1 unless defined $::RD_HINT; # Give out hints to help fix problems.
388 local $::RD_TRACE = $translator->trace ? 1 : undef;
389 local $DEBUG = $translator->debug;
391 my $parser = ddl_parser_instance('Access');
393 my $result = $parser->startrule($data);
394 return $translator->error( "Parse failed." ) unless defined $result;
395 warn Dumper( $result ) if $DEBUG;
397 my $schema = $translator->schema;
399 $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
402 for my $table_name ( @tables ) {
403 my $tdata = $result->{ $table_name };
404 my $table = $schema->add_table(
405 name => $tdata->{'table_name'},
406 ) or die $schema->error;
408 $table->comments( $tdata->{'comments'} );
411 $tdata->{'fields'}->{$a}->{'order'}
413 $tdata->{'fields'}->{$b}->{'order'}
414 } keys %{ $tdata->{'fields'} };
416 for my $fname ( @fields ) {
417 my $fdata = $tdata->{'fields'}{ $fname };
418 my $field = $table->add_field(
419 name => $fdata->{'name'},
420 data_type => $fdata->{'data_type'},
421 size => $fdata->{'size'},
422 default_value => $fdata->{'default'},
423 is_auto_increment => $fdata->{'is_auto_inc'},
424 is_nullable => $fdata->{'null'},
425 comments => $fdata->{'comments'},
426 ) or die $table->error;
428 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
431 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
432 my $index = $table->add_index(
433 name => $idata->{'name'},
434 type => uc $idata->{'type'},
435 fields => $idata->{'fields'},
436 ) or die $table->error;
445 # -------------------------------------------------------------------
446 # Where man is not nature is barren.
448 # -------------------------------------------------------------------
454 Ken Y. Clark E<lt>kclark@cpan.orgE<gt>.
458 perl(1), Parse::RecDescent, SQL::Translator::Schema.