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   1510 use strict;
  2         6  
  2         68  
21 2     2   11 use warnings;
  2         6  
  2         188  
22              
23             our $VERSION = '1.63';
24              
25             our $DEBUG;
26             $DEBUG = 0 unless defined $DEBUG;
27              
28 2     2   14 use Data::Dumper;
  2         4  
  2         193  
29 2     2   16 use SQL::Translator::Utils qw/ddl_parser_instance/;
  2         4  
  2         136  
30              
31 2     2   37 use base qw(Exporter);
  2         6  
  2         2427  
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 43 my ( $translator, $data ) = @_;
292              
293             # Enable warnings within the Parse::RecDescent module.
294 2 100       18 local $::RD_ERRORS = 1 unless defined $::RD_ERRORS; # Make sure the parser dies when it encounters an error
295 2 100       10 local $::RD_WARN = 1 unless defined $::RD_WARN; # Enable warnings. This will warn on unused rules &c.
296 2 100       10 local $::RD_HINT = 1 unless defined $::RD_HINT; # Give out hints to help fix problems.
297              
298 2 50       42 local $::RD_TRACE = $translator->trace ? 1 : undef;
299 2         28 local $DEBUG = $translator->debug;
300              
301 2         33 my $parser = ddl_parser_instance('Sybase');
302              
303 2         503232 my $result = $parser->startrule($data);
304 2 100       843820 return $translator->error( "Parse failed." ) unless defined $result;
305 1 50       9 warn Dumper( $result ) if $DEBUG;
306              
307 1         55 my $schema = $translator->schema;
308             my @tables = sort {
309 21         54 $result->{ $a }->{'order'} <=> $result->{ $b }->{'order'}
310 1         102 } keys %{ $result };
  1         10  
311              
312 1         8 for my $table_name ( @tables ) {
313 9         23 my $tdata = $result->{ $table_name };
314 9 50       41 my $table = $schema->add_table( name => $tdata->{'name'} )
315             or die "Can't create table '$table_name': ", $schema->error;
316              
317 9         398 $table->comments( $tdata->{'comments'} );
318              
319             my @fields = sort {
320             $tdata->{'fields'}->{$a}->{'order'}
321             <=>
322 61         154 $tdata->{'fields'}->{$b}->{'order'}
323 9         15 } keys %{ $tdata->{'fields'} };
  9         83  
324              
325 9         23 for my $fname ( @fields ) {
326 40         88 my $fdata = $tdata->{'fields'}{ $fname };
327             my $field = $table->add_field(
328             name => $fdata->{'name'},
329             data_type => $fdata->{'data_type'},
330             size => $fdata->{'size'},
331             default_value => $fdata->{'default'},
332             is_auto_increment => $fdata->{'is_auto_inc'},
333             is_nullable => $fdata->{'nullable'},
334 40 50       267 comments => $fdata->{'comments'},
335             ) or die $table->error;
336              
337 40 50       861 $table->primary_key( $field->name ) if $fdata->{'is_primary_key'};
338              
339 40         86 for my $qual ( qw[ binary unsigned zerofill list ] ) {
340 160 50 33     624 if ( my $val = $fdata->{ $qual } || $fdata->{ uc $qual } ) {
341 0 0 0     0 next if ref $val eq 'ARRAY' && !@$val;
342 0         0 $field->extra( $qual, $val );
343             }
344             }
345              
346 40 50 33     224 if ( $field->data_type =~ /(set|enum)/i && !$field->size ) {
347 0         0 my %extra = $field->extra;
348 0         0 my $longest = 0;
349 0 0       0 for my $len ( map { length } @{ $extra{'list'} || [] } ) {
  0         0  
  0         0  
350 0 0       0 $longest = $len if $len > $longest;
351             }
352 0 0       0 $field->size( $longest ) if $longest;
353             }
354              
355 40         67 for my $cdata ( @{ $fdata->{'constraints'} } ) {
  40         134  
356 0 0       0 next unless $cdata->{'type'} eq 'foreign_key';
357 0   0     0 $cdata->{'fields'} ||= [ $field->name ];
358 0         0 push @{ $tdata->{'constraints'} }, $cdata;
  0         0  
359             }
360             }
361              
362 9 100       15 for my $idata ( @{ $tdata->{'indices'} || [] } ) {
  9         36  
363             my $index = $table->add_index(
364             name => $idata->{'name'},
365             type => uc $idata->{'type'},
366 1 50       13 fields => $idata->{'fields'},
367             ) or die $table->error;
368             }
369              
370 9 100       19 for my $cdata ( @{ $tdata->{'constraints'} || [] } ) {
  9         58  
371             my $constraint = $table->add_constraint(
372             name => $cdata->{'name'},
373             type => $cdata->{'type'},
374             fields => $cdata->{'fields'},
375             reference_table => $cdata->{'reference_table'},
376             reference_fields => $cdata->{'reference_fields'},
377             match_type => $cdata->{'match_type'} || '',
378             on_delete => $cdata->{'on_delete'} || $cdata->{'on_delete_do'},
379 2 50 50     40 on_update => $cdata->{'on_update'} || $cdata->{'on_update_do'},
      33        
      33        
380             ) or die $table->error;
381             }
382             }
383              
384 1         22 return 1;
385             }
386              
387             1;
388              
389             # -------------------------------------------------------------------
390             # Every hero becomes a bore at last.
391             # Ralph Waldo Emerson
392             # -------------------------------------------------------------------
393              
394             =pod
395              
396             =head1 AUTHOR
397              
398             Ken Y. Clark Ekclark@cpan.orgE.
399              
400             =head1 SEE ALSO
401              
402             SQL::Translator, SQL::Translator::Parser::DBI, L.
403              
404             =cut