File Coverage

blib/lib/SQL/Translator/Parser/Sybase.pm
Criterion Covered Total %
statement 52 65 80.0
branch 21 40 52.5
condition 5 19 26.3
subroutine 6 6 100.0
pod 0 1 0.0
total 84 131 64.1


line stmt bran cond sub pod time code
1             package SQL::Translator::Parser::Sybase;
2              
3             =head1 NAME
4              
5             SQL::Translator::Parser::Sybase - parser for Sybase
6              
7             =head1 SYNOPSIS
8              
9             use SQL::Translator::Parser::Sybase;
10              
11             =head1 DESCRIPTION
12              
13             Mostly parses the output of "dbschema.pl," a Perl script freely
14             available from http://www.midsomer.org. The parsing is not complete,
15             however, and you would probably have much better luck using the
16             DBI-Sybase parser included with SQL::Translator.
17              
18             =cut
19              
20 2     2   1560 use strict;
  2         8  
  2         117  
21 2     2   13 use warnings;
  2         6  
  2         235  
22              
23             our $VERSION = '1.66';
24              
25             our $DEBUG;
26             $DEBUG = 0 unless defined $DEBUG;
27              
28 2     2   17 use Data::Dumper;
  2         3  
  2         166  
29 2     2   15 use SQL::Translator::Utils qw/ddl_parser_instance/;
  2         5  
  2         132  
30              
31 2     2   33 use base qw(Exporter);
  2         8  
  2         2714  
32             our @EXPORT_OK = qw(parse);
33              
34             our $GRAMMAR = <<'END_OF_GRAMMAR';
35              
36             {
37             my ( %tables, @table_comments, $table_order );
38             }
39              
40             startrule : statement(s) eofile { \%tables }
41              
42             eofile : /^\Z/
43              
44             statement : create_table
45             | create_procedure
46             | create_index
47             | create_constraint
48             | comment
49             | use
50             | setuser
51             | if
52             | print
53             | grant
54             | exec
55             |
56              
57             use : /use/i WORD GO
58             { @table_comments = () }
59              
60             setuser : /setuser/i NAME GO
61              
62             if : /if/i object_not_null begin if_command end GO
63              
64             if_command : grant
65             | create_index
66             | create_constraint
67              
68             object_not_null : /object_id/i '(' ident ')' /is not null/i
69              
70             print : /\s*/ /print/i /.*/
71              
72             else : /else/i /.*/
73              
74             begin : /begin/i
75              
76             end : /end/i
77              
78             grant : /grant/i /[^\n]*/
79              
80             exec : exec_statement(s) GO
81              
82             exec_statement : /exec/i /[^\n]+/
83              
84             comment : comment_start comment_middle comment_end
85             {
86             my $comment = $item[2];
87             $comment =~ s/^\s*|\s*$//mg;
88             $comment =~ s/^\**\s*//mg;
89             push @table_comments, $comment;
90             }
91              
92             comment_start : /^\s*\/\*/
93              
94             comment_end : /\s*\*\//
95              
96             comment_middle : m{([^*]+|\*(?!/))*}
97              
98             #
99             # Create table.
100             #
101             create_table : /create/i /table/i ident '(' create_def(s /,/) ')' lock(?) on_system(?) GO
102             {
103             my $table_owner = $item[3]{'owner'};
104             my $table_name = $item[3]{'name'};
105              
106             if ( @table_comments ) {
107             $tables{ $table_name }{'comments'} = [ @table_comments ];
108             @table_comments = ();
109             }
110              
111             $tables{ $table_name }{'order'} = ++$table_order;
112             $tables{ $table_name }{'name'} = $table_name;
113             $tables{ $table_name }{'owner'} = $table_owner;
114             $tables{ $table_name }{'system'} = $item[7];
115              
116             my $i = 0;
117             for my $def ( @{ $item[5] } ) {
118             if ( $def->{'supertype'} eq 'field' ) {
119             my $field_name = $def->{'name'};
120             $tables{ $table_name }{'fields'}{ $field_name } =
121             { %$def, order => $i };
122             $i++;
123              
124             if ( $def->{'is_primary_key'} ) {
125             push @{ $tables{ $table_name }{'constraints'} }, {
126             type => 'primary_key',
127             fields => [ $field_name ],
128             };
129             }
130             }
131             elsif ( $def->{'supertype'} eq 'constraint' ) {
132             push @{ $tables{ $table_name }{'constraints'} }, $def;
133             }
134             else {
135             push @{ $tables{ $table_name }{'indices'} }, $def;
136             }
137             }
138             }
139              
140             create_constraint : /create/i constraint
141             {
142             @table_comments = ();
143             push @{ $tables{ $item[2]{'table'} }{'constraints'} }, $item[2];
144             }
145              
146             create_index : /create/i index
147             {
148             @table_comments = ();
149             push @{ $tables{ $item[2]{'table'} }{'indices'} }, $item[2];
150             }
151              
152             create_procedure : /create/i /procedure/i procedure_body GO
153             {
154             @table_comments = ();
155             }
156              
157             procedure_body : not_go(s)
158              
159             not_go : /((?!go).)*/
160              
161             create_def : field
162             | index
163             | constraint
164              
165             blank : /\s*/
166              
167             field : field_name data_type nullable(?)
168             {
169             $return = {
170             supertype => 'field',
171             name => $item{'field_name'},
172             data_type => $item{'data_type'}{'type'},
173             size => $item{'data_type'}{'size'},
174             nullable => $item[3][0],
175             # default => $item{'default_val'}[0],
176             # is_auto_inc => $item{'auto_inc'}[0],
177             # is_primary_key => $item{'primary_key'}[0],
178             }
179             }
180              
181             constraint : primary_key_constraint
182             | unique_constraint
183              
184             field_name : WORD
185              
186             index_name : WORD
187              
188             table_name : WORD
189              
190             data_type : WORD field_size(?)
191             {
192             $return = {
193             type => $item[1],
194             size => $item[2][0]
195             }
196             }
197              
198             lock : /lock/i /datarows/i
199              
200             field_type : WORD
201              
202             field_size : '(' num_range ')' { $item{'num_range'} }
203              
204             num_range : DIGITS ',' DIGITS
205             { $return = $item[1].','.$item[3] }
206             | DIGITS
207             { $return = $item[1] }
208              
209              
210             nullable : /not/i /null/i
211             { $return = 0 }
212             | /null/i
213             { $return = 1 }
214              
215             default_val : /default/i /(?:')?[\w\d.-]*(?:')?/
216             { $item[2]=~s/'//g; $return=$item[2] }
217              
218             auto_inc : /auto_increment/i { 1 }
219              
220             primary_key_constraint : /primary/i /key/i index_name(?) parens_field_list
221             {
222             $return = {
223             supertype => 'constraint',
224             name => $item{'index_name'}[0],
225             type => 'primary_key',
226             fields => $item[4],
227             }
228             }
229              
230             unique_constraint : /unique/i clustered(?) INDEX(?) index_name(?) on_table(?) parens_field_list
231             {
232             $return = {
233             supertype => 'constraint',
234             type => 'unique',
235             clustered => $item[2][0],
236             name => $item[4][0],
237             table => $item[5][0],
238             fields => $item[6],
239             }
240             }
241              
242             clustered : /clustered/i
243             { $return = 1 }
244             | /nonclustered/i
245             { $return = 0 }
246              
247             INDEX : /index/i
248              
249             on_table : /on/i table_name
250             { $return = $item[2] }
251              
252             on_system : /on/i /system/i
253             { $return = 1 }
254              
255             index : clustered(?) INDEX index_name(?) on_table(?) parens_field_list
256             {
257             $return = {
258             supertype => 'index',
259             type => 'normal',
260             clustered => $item[1][0],
261             name => $item[3][0],
262             table => $item[4][0],
263             fields => $item[5],
264             }
265             }
266              
267             parens_field_list : '(' field_name(s /,/) ')'
268             { $item[2] }
269              
270             ident : QUOTE(?) WORD '.' WORD QUOTE(?)
271             { $return = { owner => $item[2], name => $item[4] } }
272             | WORD
273             { $return = { name => $item[2] } }
274              
275             GO : /^go/i
276              
277             NAME : QUOTE(?) /\w+/ QUOTE(?)
278             { $item[2] }
279              
280             WORD : /[\w#]+/
281              
282             DIGITS : /\d+/
283              
284             COMMA : ','
285              
286             QUOTE : /'/
287              
288             END_OF_GRAMMAR
289              
290             sub parse {
291 2     2 0 50 my ($translator, $data) = @_;
292              
293             # Enable warnings within the Parse::RecDescent module.
294 2 100       11 local $::RD_ERRORS = 1
295             unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
296 2 100       9 local $::RD_WARN = 1
297             unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
298 2 100       12 local $::RD_HINT = 1
299             unless defined $::RD_HINT; # Give out hints to help fix problems.
300              
301 2 50       56 local $::RD_TRACE = $translator->trace ? 1 : undef;
302 2         58 local $DEBUG = $translator->debug;
303              
304 2         28 my $parser = ddl_parser_instance('Sybase');
305              
306 2         731633 my $result = $parser->startrule($data);
307 2 100       1170966 return $translator->error("Parse failed.") unless defined $result;
308 1 50       7 warn Dumper($result) if $DEBUG;
309              
310 1         50 my $schema = $translator->schema;
311 20         53 my @tables = sort { $result->{$a}->{'order'} <=> $result->{$b}->{'order'} }
312 1         104 keys %{$result};
  1         14  
313              
314 1         5 for my $table_name (@tables) {
315 9         35 my $tdata = $result->{$table_name};
316 9 50       64 my $table = $schema->add_table(name => $tdata->{'name'})
317             or die "Can't create table '$table_name': ", $schema->error;
318              
319 9         541 $table->comments($tdata->{'comments'});
320              
321 69         232 my @fields = sort { $tdata->{'fields'}->{$a}->{'order'} <=> $tdata->{'fields'}->{$b}->{'order'} }
322 9         20 keys %{ $tdata->{'fields'} };
  9         86  
323              
324 9         30 for my $fname (@fields) {
325 40         124 my $fdata = $tdata->{'fields'}{$fname};
326             my $field = $table->add_field(
327             name => $fdata->{'name'},
328             data_type => $fdata->{'data_type'},
329             size => $fdata->{'size'},
330             default_value => $fdata->{'default'},
331             is_auto_increment => $fdata->{'is_auto_inc'},
332             is_nullable => $fdata->{'nullable'},
333 40 50       418 comments => $fdata->{'comments'},
334             ) or die $table->error;
335              
336 40 50       1317 $table->primary_key($field->name) if $fdata->{'is_primary_key'};
337              
338 40         106 for my $qual (qw[ binary unsigned zerofill list ]) {
339 160 50 33     748 if (my $val = $fdata->{$qual} || $fdata->{ uc $qual }) {
340 0 0 0     0 next if ref $val eq 'ARRAY' && !@$val;
341 0         0 $field->extra($qual, $val);
342             }
343             }
344              
345 40 50 33     332 if ($field->data_type =~ /(set|enum)/i && !$field->size) {
346 0         0 my %extra = $field->extra;
347 0         0 my $longest = 0;
348 0 0       0 for my $len (map {length} @{ $extra{'list'} || [] }) {
  0         0  
  0         0  
349 0 0       0 $longest = $len if $len > $longest;
350             }
351 0 0       0 $field->size($longest) if $longest;
352             }
353              
354 40         83 for my $cdata (@{ $fdata->{'constraints'} }) {
  40         159  
355 0 0       0 next unless $cdata->{'type'} eq 'foreign_key';
356 0   0     0 $cdata->{'fields'} ||= [ $field->name ];
357 0         0 push @{ $tdata->{'constraints'} }, $cdata;
  0         0  
358             }
359             }
360              
361 9 100       22 for my $idata (@{ $tdata->{'indices'} || [] }) {
  9         65  
362             my $index = $table->add_index(
363             name => $idata->{'name'},
364             type => uc $idata->{'type'},
365 1 50       30 fields => $idata->{'fields'},
366             ) or die $table->error;
367             }
368              
369 9 100       20 for my $cdata (@{ $tdata->{'constraints'} || [] }) {
  9         61  
370             my $constraint = $table->add_constraint(
371             name => $cdata->{'name'},
372             type => $cdata->{'type'},
373             fields => $cdata->{'fields'},
374             reference_table => $cdata->{'reference_table'},
375             reference_fields => $cdata->{'reference_fields'},
376             match_type => $cdata->{'match_type'} || '',
377             on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
378 2 50 50     51 on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
      33        
      33        
379             ) or die $table->error;
380             }
381             }
382              
383 1         15 return 1;
384             }
385              
386             1;
387              
388             # -------------------------------------------------------------------
389             # Every hero becomes a bore at last.
390             # Ralph Waldo Emerson
391             # -------------------------------------------------------------------
392              
393             =pod
394              
395             =head1 AUTHOR
396              
397             Ken Y. Clark Ekclark@cpan.orgE.
398              
399             =head1 SEE ALSO
400              
401             SQL::Translator, SQL::Translator::Parser::DBI, L.
402              
403             =cut