File Coverage

blib/lib/SQL/Translator/Parser/Access.pm
Criterion Covered Total %
statement 43 44 97.7
branch 10 22 45.4
condition n/a
subroutine 6 6 100.0
pod 0 1 0.0
total 59 73 80.8


line stmt bran cond sub pod time code
1             package SQL::Translator::Parser::Access;
2              
3             =head1 NAME
4              
5             SQL::Translator::Parser::Access - parser for Access as produced by mdbtools
6              
7             =head1 SYNOPSIS
8              
9             use SQL::Translator;
10             use SQL::Translator::Parser::Access;
11              
12             my $translator = SQL::Translator->new;
13             $translator->parser("SQL::Translator::Parser::Access");
14              
15             =head1 DESCRIPTION
16              
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/).
19              
20             =cut
21              
22 1     1   3133 use strict;
  1         4  
  1         188  
23 1     1   8 use warnings;
  1         3  
  1         115  
24              
25             our $VERSION = '1.66';
26              
27             our $DEBUG;
28             $DEBUG = 0 unless defined $DEBUG;
29              
30 1     1   5 use Data::Dumper;
  1         2  
  1         150  
31 1     1   10 use SQL::Translator::Utils qw/ddl_parser_instance/;
  1         4  
  1         73  
32              
33 1     1   9 use base qw(Exporter);
  1         3  
  1         1083  
34             our @EXPORT_OK = qw(parse);
35              
36             our $GRAMMAR = <<'END_OF_GRAMMAR';
37              
38             {
39             my ( %tables, $table_order, @table_comments );
40             }
41              
42             #
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,
46             # failed. -ky
47             #
48             startrule : statement(s) eofile { \%tables }
49              
50             eofile : /^\Z/
51              
52             statement : comment
53             | use
54             | set
55             | drop
56             | create
57             |
58              
59             use : /use/i WORD ';'
60             { @table_comments = () }
61              
62             set : /set/i /[^;]+/ ';'
63             { @table_comments = () }
64              
65             drop : /drop/i TABLE /[^;]+/ ';'
66              
67             drop : /drop/i WORD(s) ';'
68             { @table_comments = () }
69              
70             create : CREATE /database/i WORD ';'
71             { @table_comments = () }
72              
73             create : CREATE TABLE table_name '(' create_definition(s /,/) ')' ';'
74             {
75             my $table_name = $item{'table_name'};
76             $tables{ $table_name }{'order'} = ++$table_order;
77             $tables{ $table_name }{'table_name'} = $table_name;
78              
79             if ( @table_comments ) {
80             $tables{ $table_name }{'comments'} = [ @table_comments ];
81             @table_comments = ();
82             }
83              
84             my $i = 1;
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 };
90             $i++;
91              
92             if ( $definition->{'is_primary_key'} ) {
93             push @{ $tables{ $table_name }{'constraints'} },
94             {
95             type => 'primary_key',
96             fields => [ $field_name ],
97             }
98             ;
99             }
100             }
101             elsif ( $definition->{'supertype'} eq 'constraint' ) {
102             push @{ $tables{ $table_name }{'constraints'} }, $definition;
103             }
104             elsif ( $definition->{'supertype'} eq 'index' ) {
105             push @{ $tables{ $table_name }{'indices'} }, $definition;
106             }
107             }
108              
109             1;
110             }
111              
112             create : CREATE UNIQUE(?) /(index|key)/i index_name /on/i table_name '(' field_name(s /,/) ')' ';'
113             {
114             @table_comments = ();
115             push @{ $tables{ $item{'table_name'} }{'indices'} },
116             {
117             name => $item[4],
118             type => $item[2] ? 'unique' : 'normal',
119             fields => $item[8],
120             }
121             ;
122             }
123              
124             create_definition : constraint
125             | index
126             | field
127             | comment
128             |
129              
130             comment : /^\s*--(.*)\n/
131             {
132             my $comment = $1;
133             $return = $comment;
134             push @table_comments, $comment;
135             }
136              
137             field : field_name data_type field_qualifier(s?) reference_definition(?)
138             {
139             $return = {
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(?)'},
145             }
146             }
147             |
148              
149             field_qualifier : not_null
150             {
151             $return = {
152             null => $item{'not_null'},
153             }
154             }
155              
156             field_qualifier : default_val
157             {
158             $return = {
159             default => $item{'default_val'},
160             }
161             }
162              
163             field_qualifier : auto_inc
164             {
165             $return = {
166             is_auto_inc => $item{'auto_inc'},
167             }
168             }
169              
170             field_qualifier : primary_key
171             {
172             $return = {
173             is_primary_key => $item{'primary_key'},
174             }
175             }
176              
177             field_qualifier : unsigned
178             {
179             $return = {
180             is_unsigned => $item{'unsigned'},
181             }
182             }
183              
184             field_qualifier : /character set/i WORD
185             {
186             $return = {
187             character_set => $item[2],
188             }
189             }
190              
191             reference_definition : /references/i table_name parens_field_list(?) match_type(?) on_delete(?) on_update(?)
192             {
193             $return = {
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],
200             }
201             }
202              
203             match_type : /match full/i { 'full' }
204             |
205             /match partial/i { 'partial' }
206              
207             on_delete : /on delete/i reference_option
208             { $item[2] }
209              
210             on_update : /on update/i reference_option
211             { $item[2] }
212              
213             reference_option: /restrict/i |
214             /cascade/i |
215             /set null/i |
216             /no action/i |
217             /set default/i
218             { $item[1] }
219              
220             index : normal_index
221             | fulltext_index
222             |
223              
224             table_name : NAME
225              
226             field_name : NAME
227              
228             index_name : NAME
229              
230             data_type : access_data_type parens_value_list(s?) type_qualifier(s?)
231             {
232             $return = {
233             type => $item[1],
234             size => $item[2][0],
235             qualifiers => $item[3],
236             }
237             }
238              
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' }
243             | WORD
244              
245             parens_field_list : '(' field_name(s /,/) ')'
246             { $item[2] }
247              
248             parens_value_list : '(' VALUE(s /,/) ')'
249             { $item[2] }
250              
251             type_qualifier : /(BINARY|UNSIGNED|ZEROFILL)/i
252             { lc $item[1] }
253              
254             field_type : WORD
255              
256             create_index : /create/i /index/i
257              
258             not_null : /not/i /null/i { $return = 0 }
259              
260             unsigned : /unsigned/i { $return = 0 }
261              
262             default_val : /default/i /'(?:.*?\')*.*?'|(?:')?[\w\d:.-]*(?:')?/
263             {
264             $item[2] =~ s/^\s*'|'\s*$//g;
265             $return = $item[2];
266             }
267              
268             auto_inc : /auto_increment/i { 1 }
269              
270             primary_key : /primary/i /key/i { 1 }
271              
272             constraint : primary_key_def
273             | unique_key_def
274             | foreign_key_def
275             |
276              
277             foreign_key_def : foreign_key_def_begin parens_field_list reference_definition
278             {
279             $return = {
280             supertype => 'constraint',
281             type => 'foreign_key',
282             name => $item[1],
283             fields => $item[2],
284             %{ $item{'reference_definition'} },
285             }
286             }
287              
288             foreign_key_def_begin : /constraint/i /foreign key/i
289             { $return = '' }
290             |
291             /constraint/i WORD /foreign key/i
292             { $return = $item[2] }
293             |
294             /foreign key/i
295             { $return = '' }
296              
297             primary_key_def : primary_key index_name(?) '(' name_with_opt_paren(s /,/) ')'
298             {
299             $return = {
300             supertype => 'constraint',
301             name => $item{'index_name(?)'}[0],
302             type => 'primary_key',
303             fields => $item[4],
304             };
305             }
306              
307             unique_key_def : UNIQUE KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
308             {
309             $return = {
310             supertype => 'constraint',
311             name => $item{'index_name(?)'}[0],
312             type => 'unique',
313             fields => $item[5],
314             }
315             }
316              
317             normal_index : KEY index_name(?) '(' name_with_opt_paren(s /,/) ')'
318             {
319             $return = {
320             supertype => 'index',
321             type => 'normal',
322             name => $item{'index_name(?)'}[0],
323             fields => $item[4],
324             }
325             }
326              
327             fulltext_index : /fulltext/i KEY(?) index_name(?) '(' name_with_opt_paren(s /,/) ')'
328             {
329             $return = {
330             supertype => 'index',
331             type => 'fulltext',
332             name => $item{'index_name(?)'}[0],
333             fields => $item[5],
334             }
335             }
336              
337             name_with_opt_paren : NAME parens_value_list(s?)
338             { $item[2][0] ? "$item[1]($item[2][0][0])" : $item[1] }
339              
340             UNIQUE : /unique/i { 1 }
341              
342             KEY : /key/i | /index/i
343              
344             table_option : WORD /\s*=\s*/ WORD
345             {
346             $return = { $item[1] => $item[3] };
347             }
348              
349             CREATE : /create/i
350              
351             TEMPORARY : /temporary/i
352              
353             TABLE : /table/i
354              
355             WORD : /\w+/
356              
357             DIGITS : /\d+/
358              
359             COMMA : ','
360              
361             NAME : "`" /\w+/ "`"
362             { $item[2] }
363             | /\w+/
364             { $item[1] }
365              
366             VALUE : /[-+]?\.?\d+(?:[eE]\d+)?/
367             { $item[1] }
368             | /'.*?'/
369             {
370             # remove leading/trailing quotes
371             my $val = $item[1];
372             $val =~ s/^['"]|['"]$//g;
373             $return = $val;
374             }
375             | /NULL/
376             { 'NULL' }
377              
378             END_OF_GRAMMAR
379              
380             sub parse {
381 1     1 0 1485 my ($translator, $data) = @_;
382              
383             # Enable warnings within the Parse::RecDescent module.
384 1 50       6 local $::RD_ERRORS = 1
385             unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
386 1 50       5 local $::RD_WARN = 1
387             unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
388 1 50       4 local $::RD_HINT = 1
389             unless defined $::RD_HINT; # Give out hints to help fix problems.
390              
391 1 50       42 local $::RD_TRACE = $translator->trace ? 1 : undef;
392 1         17 local $DEBUG = $translator->debug;
393              
394 1         16 my $parser = ddl_parser_instance('Access');
395              
396 1         412770 my $result = $parser->startrule($data);
397 1 50       1413292 return $translator->error("Parse failed.") unless defined $result;
398 1 50       8 warn Dumper($result) if $DEBUG;
399              
400 1         43 my $schema = $translator->schema;
401 86         232 my @tables = sort { $result->{$a}->{'order'} <=> $result->{$b}->{'order'} }
402 1         105 keys %{$result};
  1         18  
403              
404 1         7 for my $table_name (@tables) {
405 24         103 my $tdata = $result->{$table_name};
406 24 50       179 my $table = $schema->add_table(name => $tdata->{'table_name'},)
407             or die $schema->error;
408              
409 24         1432 $table->comments($tdata->{'comments'});
410              
411 319         743 my @fields = sort { $tdata->{'fields'}->{$a}->{'order'} <=> $tdata->{'fields'}->{$b}->{'order'} }
412 24         57 keys %{ $tdata->{'fields'} };
  24         227  
413              
414 24         69 for my $fname (@fields) {
415 145         444 my $fdata = $tdata->{'fields'}{$fname};
416             my $field = $table->add_field(
417             name => $fdata->{'name'},
418             data_type => $fdata->{'data_type'},
419             size => $fdata->{'size'},
420             default_value => $fdata->{'default'},
421             is_auto_increment => $fdata->{'is_auto_inc'},
422             is_nullable => $fdata->{'null'},
423 145 50       1181 comments => $fdata->{'comments'},
424             ) or die $table->error;
425              
426 145 50       3973 $table->primary_key($field->name) if $fdata->{'is_primary_key'};
427             }
428              
429 24 50       49 for my $idata (@{ $tdata->{'indices'} || [] }) {
  24         213  
430             my $index = $table->add_index(
431             name => $idata->{'name'},
432             type => uc $idata->{'type'},
433 0 0       0 fields => $idata->{'fields'},
434             ) or die $table->error;
435             }
436             }
437              
438 1         17 return 1;
439             }
440              
441             1;
442              
443             # -------------------------------------------------------------------
444             # Where man is not nature is barren.
445             # William Blake
446             # -------------------------------------------------------------------
447              
448             =pod
449              
450             =head1 AUTHOR
451              
452             Ken Y. Clark Ekclark@cpan.orgE.
453              
454             =head1 SEE ALSO
455              
456             perl(1), Parse::RecDescent, SQL::Translator::Schema.
457              
458             =cut