File Coverage

blib/lib/SQL/Translator/Parser/XML/SQLFairy.pm
Criterion Covered Total %
statement 100 104 96.1
branch 33 48 68.7
condition 10 17 58.8
subroutine 12 12 100.0
pod 0 2 0.0
total 155 183 84.7


line stmt bran cond sub pod time code
1             package SQL::Translator::Parser::XML::SQLFairy;
2              
3             =head1 NAME
4              
5             SQL::Translator::Parser::XML::SQLFairy - parser for SQL::Translator's XML.
6              
7             =head1 SYNOPSIS
8              
9             use SQL::Translator;
10              
11             my $translator = SQL::Translator->new( show_warnings => 1 );
12              
13             my $out = $obj->translate(
14             from => 'XML-SQLFairy',
15             to => 'MySQL',
16             filename => 'schema.xml',
17             ) or die $translator->error;
18              
19             print $out;
20              
21             =head1 DESCRIPTION
22              
23             This parser handles the flavor of XML used natively by the SQLFairy
24             project (L). The XML must be in the XML namespace
25             C.
26             See L for details of this format.
27              
28             You do not need to specify every attribute of the Schema objects as any missing
29             from the XML will be set to their default values. e.g. A field could be written
30             using only;
31              
32            
33              
34             Instead of the full;
35              
36            
37             is_auto_increment="0" is_primary_key="0" is_foreign_key="0" order="4">
38            
39            
40              
41             If you do not explicitly set the order of items using order attributes on the
42             tags then the order the tags appear in the XML will be used.
43              
44             =head2 default_value
45              
46             Leave the attribute out all together to use the default in
47             L. Use empty quotes or 'EMPTY_STRING'
48             for a zero length string. 'NULL' for an explicit null (currently sets
49             default_value to undef in the field object).
50              
51            
52            
53            
54              
55             =head2 ARGS
56              
57             Doesn't take any extra parser args at the moment.
58              
59             =head1 LEGACY FORMAT
60              
61             The previous version of the SQLFairy XML allowed the attributes of the
62             schema objects to be written as either xml attributes or as data elements, in
63             any combination. While this allows for lots of flexibility in writing the XML
64             the result is a great many possible XML formats, not so good for DTD writing,
65             XPathing etc! So we have moved to a fixed version described in
66             L.
67              
68             This version of the parser will still parse the old formats and emit warnings
69             when it sees them being used but they should be considered B
70             depreciated>.
71              
72             To convert your old format files simply pass them through the translator :)
73              
74             $ sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
75              
76             =cut
77              
78 14     14   97 use strict;
  14         29  
  14         363  
79 14     14   61 use warnings;
  14         35  
  14         940  
80              
81             our ( $DEBUG, @EXPORT_OK );
82             our $VERSION = '1.6_3';
83             $DEBUG = 0 unless defined $DEBUG;
84              
85 14     14   610 use Data::Dumper;
  14         6651  
  14         742  
86 14     14   1677 use Carp::Clan qw/^SQL::Translator/;
  14         11390  
  14         207  
87 14     14   1551 use Exporter;
  14         30  
  14         503  
88 14     14   82 use base qw(Exporter);
  14         30  
  14         1671  
89             @EXPORT_OK = qw(parse);
90              
91 14     14   77 use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
  14         29  
  14         5429  
92 14     14   1549 use SQL::Translator::Utils 'debug';
  14         31  
  14         653  
93 14     14   7093 use XML::LibXML;
  14         475749  
  14         88  
94 14     14   1987 use XML::LibXML::XPathContext;
  14         29  
  14         15381  
95              
96             sub parse {
97 15     15 0 58 my ( $translator, $data ) = @_;
98 15         295 my $schema = $translator->schema;
99 15         982 local $DEBUG = $translator->debug;
100 15         234 my $doc = XML::LibXML->new->parse_string($data);
101 15         7592 my $xp = XML::LibXML::XPathContext->new($doc);
102              
103 15         164 $xp->registerNs("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
104              
105             #
106             # Work our way through the tables
107             #
108 15         82 my @nodes = $xp->findnodes(
109             '/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table'
110             );
111 15         1107 for my $tblnode (
112             sort {
113 21   50     843 ("".$xp->findvalue('sqlf:order|@order',$a) || 0)
      50        
114             <=>
115             ("".$xp->findvalue('sqlf:order|@order',$b) || 0)
116             } @nodes
117             ) {
118 34         2848 debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
119              
120 34 50       243 my $table = $schema->add_table(
121             get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/)
122             ) or die $schema->error;
123              
124             #
125             # Fields
126             #
127 34         662 my @nodes = $xp->findnodes('sqlf:fields/sqlf:field',$tblnode);
128 34         1669 foreach (
129             sort {
130 274   50     20654 ("".$xp->findvalue('sqlf:order',$a) || 0)
      50        
131             <=>
132             ("".$xp->findvalue('sqlf:order',$b) || 0)
133             } @nodes
134             ) {
135 162         2882 my %fdata = get_tagfields($xp, $_, "sqlf:",
136             qw/name data_type size default_value is_nullable extra
137             is_auto_increment is_primary_key is_foreign_key comments/
138             );
139              
140 162 100 66     778 if (
141             exists $fdata{'default_value'} and
142             defined $fdata{'default_value'}
143             ) {
144 76 50       365 if ( $fdata{'default_value'} =~ /^\s*NULL\s*$/ ) {
    50          
145 0         0 $fdata{'default_value'}= undef;
146             }
147             elsif ( $fdata{'default_value'} =~ /^\s*EMPTY_STRING\s*$/ ) {
148 0         0 $fdata{'default_value'} = "";
149             }
150             }
151              
152 162 50       754 my $field = $table->add_field( %fdata ) or die $table->error;
153              
154 162 100       3572 $table->primary_key( $field->name ) if $fdata{'is_primary_key'};
155              
156             #
157             # TODO:
158             # - We should be able to make the table obj spot this when
159             # we use add_field.
160             #
161             }
162              
163             #
164             # Constraints
165             #
166 34         411 @nodes = $xp->findnodes('sqlf:constraints/sqlf:constraint',$tblnode);
167 34         3038 foreach (@nodes) {
168 69         245 my %data = get_tagfields($xp, $_, "sqlf:",
169             qw/name type table fields reference_fields reference_table
170             match_type on_delete on_update extra/
171             );
172 69 50       411 $table->add_constraint( %data ) or die $table->error;
173             }
174              
175             #
176             # Indexes
177             #
178 34         150 @nodes = $xp->findnodes('sqlf:indices/sqlf:index',$tblnode);
179 34         2265 foreach (@nodes) {
180 14         57 my %data = get_tagfields($xp, $_, "sqlf:",
181             qw/name type fields options extra/);
182 14 50       107 $table->add_index( %data ) or die $table->error;
183             }
184              
185              
186             #
187             # Comments
188             #
189 34         162 @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
190 34         1298 foreach (@nodes) {
191 0         0 my $data = $_->string_value;
192 0         0 $table->comments( $data );
193             }
194              
195             } # tables loop
196              
197             #
198             # Views
199             #
200 15         143 @nodes = $xp->findnodes(
201             '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
202             );
203 15         837 foreach (@nodes) {
204 14         65 my %data = get_tagfields($xp, $_, "sqlf:",
205             qw/name sql fields order extra/
206             );
207 14 50       116 $schema->add_view( %data ) or die $schema->error;
208             }
209              
210             #
211             # Triggers
212             #
213 15         67 @nodes = $xp->findnodes(
214             '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
215             );
216 15         836 foreach (@nodes) {
217 30         99 my %data = get_tagfields($xp, $_, "sqlf:", qw/
218             name perform_action_when database_event database_events fields
219             on_table action order extra scope
220             /);
221              
222             # back compat
223 30 100 100     190 if (my $evt = $data{database_event} and $translator->{show_warnings}) {
224 1         9 carp 'The database_event tag is deprecated - please use ' .
225             'database_events (which can take one or more comma separated ' .
226             'event names)';
227             $data{database_events} = join (', ',
228 1   33     195 $data{database_events} || (),
229             $evt,
230             );
231             }
232              
233             # split into arrayref
234 30 100       102 if (my $evts = $data{database_events}) {
235 18         200 $data{database_events} = [split (/\s*,\s*/, $evts) ];
236             }
237              
238 30 50       193 $schema->add_trigger( %data ) or die $schema->error;
239             }
240              
241             #
242             # Procedures
243             #
244 15         80 @nodes = $xp->findnodes(
245             '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
246             );
247 15         986 foreach (@nodes) {
248 14         61 my %data = get_tagfields($xp, $_, "sqlf:",
249             qw/name sql parameters owner comments order extra/
250             );
251 14 50       104 $schema->add_procedure( %data ) or die $schema->error;
252             }
253              
254 15         80 return 1;
255             }
256              
257             sub get_tagfields {
258             #
259             # get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
260             # get_tagfields $node, "sqlf:" => qw/name type fields reference/;
261             #
262             # Returns hash of data.
263             # TODO - Add handling of an explicit NULL value.
264             #
265              
266 337     337 0 1311 my ($xp, $node, @names) = @_;
267 337         631 my (%data, $ns);
268 337         641 foreach (@names) {
269 3287 100       95042 if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
  337         574  
  337         636  
270 2950 50       5893 my $thisns = (s/(^.*?:)// ? $1 : $ns);
271              
272 2950 100       8070 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
273              
274 2950         4899 my $attrib_path = "\@$_";
275 2950         4035 my $tag_path = "$thisns$_";
276 2950 100       6143 if ( my $found = $xp->find($attrib_path,$node) ) {
    100          
277 1569         86723 $data{$_} = "".$found->to_literal;
278 1569 50       31192 warn "Use of '$_' as an attribute is depricated."
279             ." Use a child tag instead."
280             ." To convert your file to the new version see the Docs.\n"
281             unless $is_attrib;
282 1569 50       5686 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
283             }
284             elsif ( $found = $xp->find($tag_path,$node) ) {
285 258 100       25320 if ($_ eq "extra") {
286 159         247 my %extra;
287 159         427 foreach ( $found->pop->getAttributes ) {
288 392         2769 $extra{$_->getName} = $_->getData;
289             }
290 159         753 $data{$_} = \%extra;
291             }
292             else {
293 99         244 $data{$_} = "".$found->to_literal;
294             }
295 258 50       4763 warn "Use of '$_' as a child tag is depricated."
296             ." Use an attribute instead."
297             ." To convert your file to the new version see the Docs.\n"
298             if $is_attrib;
299 258 50       1154 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
300             }
301             }
302              
303 337 50       21093 return wantarray ? %data : \%data;
304             }
305              
306             1;
307              
308             =pod
309              
310             =head1 BUGS
311              
312             Ignores the order attribute for Constraints, Views, Indices, Views, Triggers
313             and Procedures, using the tag order instead. (This is the order output by the
314             SQLFairy XML producer).
315              
316             =head1 SEE ALSO
317              
318             L, L, L,
319             L.
320              
321             =head1 TODO
322              
323             =over 4
324              
325             =item *
326              
327             Support options attribute.
328              
329             =item *
330              
331             Test foreign keys are parsed ok.
332              
333             =item *
334              
335             Control over defaulting.
336              
337             =back
338              
339             =head1 AUTHOR
340              
341             Mark D. Addison Emark.addison@itn.co.ukE,
342             Jonathan Yu Efrequency@cpan.orgE
343              
344             =cut