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   102 use strict;
  14         34  
  14         738  
79 14     14   116 use warnings;
  14         33  
  14         1644  
80              
81             our ($DEBUG, @EXPORT_OK);
82             our $VERSION = '1.66';
83             $DEBUG = 0 unless defined $DEBUG;
84              
85 14     14   817 use Data::Dumper;
  14         12261  
  14         1074  
86 14     14   2366 use Carp::Clan qw/^SQL::Translator/;
  14         20724  
  14         149  
87 14     14   2050 use Exporter;
  14         34  
  14         891  
88 14     14   88 use base qw(Exporter);
  14         32  
  14         2614  
89             @EXPORT_OK = qw(parse);
90              
91 14     14   115 use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
  14         55  
  14         8244  
92 14     14   2260 use SQL::Translator::Utils 'debug';
  14         45  
  14         984  
93 14     14   13721 use XML::LibXML;
  14         926433  
  14         151  
94 14     14   3154 use XML::LibXML::XPathContext;
  14         41  
  14         27422  
95              
96             sub parse {
97 15     15 0 48 my ($translator, $data) = @_;
98 15         384 my $schema = $translator->schema;
99 15         1129 local $DEBUG = $translator->debug;
100 15         293 my $doc = XML::LibXML->new->parse_string($data);
101 15         9699 my $xp = XML::LibXML::XPathContext->new($doc);
102              
103 15         183 $xp->registerNs("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
104              
105             #
106             # Work our way through the tables
107             #
108 15         101 my @nodes = $xp->findnodes('/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table');
109 15         1713 for my $tblnode (
110             sort {
111 21   50     994 ("" . $xp->findvalue('sqlf:order|@order', $a) || 0) <=> ("" . $xp->findvalue('sqlf:order|@order', $b) || 0)
      50        
112             } @nodes
113             ) {
114 34         5685 debug "Adding table:" . $xp->findvalue('sqlf:name', $tblnode);
115              
116 34 50       1397 my $table = $schema->add_table(get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/))
117             or die $schema->error;
118              
119             #
120             # Fields
121             #
122 34         885 my @nodes = $xp->findnodes('sqlf:fields/sqlf:field', $tblnode);
123 34   50     2254 foreach (sort { ("" . $xp->findvalue('sqlf:order', $a) || 0) <=> ("" . $xp->findvalue('sqlf:order', $b) || 0) }
  274   50     24976  
124             @nodes) {
125 162         14382 my %fdata = get_tagfields(
126             $xp, $_, "sqlf:",
127             qw/name data_type size default_value is_nullable extra
128             is_auto_increment is_primary_key is_foreign_key comments/
129             );
130              
131 162 100 66     961 if (exists $fdata{'default_value'}
132             and defined $fdata{'default_value'}) {
133 76 50       408 if ($fdata{'default_value'} =~ /^\s*NULL\s*$/) {
    50          
134 0         0 $fdata{'default_value'} = undef;
135             } elsif ($fdata{'default_value'} =~ /^\s*EMPTY_STRING\s*$/) {
136 0         0 $fdata{'default_value'} = "";
137             }
138             }
139              
140 162 50       964 my $field = $table->add_field(%fdata) or die $table->error;
141              
142 162 100       4924 $table->primary_key($field->name) if $fdata{'is_primary_key'};
143              
144             #
145             # TODO:
146             # - We should be able to make the table obj spot this when
147             # we use add_field.
148             #
149             }
150              
151             #
152             # Constraints
153             #
154 34         258 @nodes = $xp->findnodes('sqlf:constraints/sqlf:constraint', $tblnode);
155 34         3751 foreach (@nodes) {
156 69         289 my %data = get_tagfields(
157             $xp, $_, "sqlf:",
158             qw/name type table fields reference_fields reference_table
159             match_type on_delete on_update extra/
160             );
161 69 50       476 $table->add_constraint(%data) or die $table->error;
162             }
163              
164             #
165             # Indexes
166             #
167 34         194 @nodes = $xp->findnodes('sqlf:indices/sqlf:index', $tblnode);
168 34         2637 foreach (@nodes) {
169 14         117 my %data = get_tagfields($xp, $_, "sqlf:", qw/name type fields options extra/);
170 14 50       114 $table->add_index(%data) or die $table->error;
171             }
172              
173             #
174             # Comments
175             #
176 34         171 @nodes = $xp->findnodes('sqlf:comments/sqlf:comment', $tblnode);
177 34         1651 foreach (@nodes) {
178 0         0 my $data = $_->string_value;
179 0         0 $table->comments($data);
180             }
181              
182             } # tables loop
183              
184             #
185             # Views
186             #
187 15         145 @nodes = $xp->findnodes('/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view');
188 15         934 foreach (@nodes) {
189 14         71 my %data = get_tagfields($xp, $_, "sqlf:", qw/name sql fields order extra/);
190 14 50       141 $schema->add_view(%data) or die $schema->error;
191             }
192              
193             #
194             # Triggers
195             #
196 15         81 @nodes = $xp->findnodes('/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger');
197 15         2483 foreach (@nodes) {
198 30         153 my %data = get_tagfields(
199             $xp, $_, "sqlf:", qw/
200             name perform_action_when database_event database_events fields
201             on_table action order extra scope
202             /
203             );
204              
205             # back compat
206 30 100 100     265 if (my $evt = $data{database_event} and $translator->{show_warnings}) {
207 1         9 carp 'The database_event tag is deprecated - please use '
208             . 'database_events (which can take one or more comma separated '
209             . 'event names)';
210 1   33     233 $data{database_events} = join(', ', $data{database_events} || (), $evt,);
211             }
212              
213             # split into arrayref
214 30 100       124 if (my $evts = $data{database_events}) {
215 18         204 $data{database_events} = [ split(/\s*,\s*/, $evts) ];
216             }
217              
218 30 50       232 $schema->add_trigger(%data) or die $schema->error;
219             }
220              
221             #
222             # Procedures
223             #
224 15         84 @nodes = $xp->findnodes('/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure');
225 15         1194 foreach (@nodes) {
226 14         66 my %data = get_tagfields($xp, $_, "sqlf:", qw/name sql parameters owner comments order extra/);
227 14 50       119 $schema->add_procedure(%data) or die $schema->error;
228             }
229              
230 15         96 return 1;
231             }
232              
233             sub get_tagfields {
234             #
235             # get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
236             # get_tagfields $node, "sqlf:" => qw/name type fields reference/;
237             #
238             # Returns hash of data.
239             # TODO - Add handling of an explicit NULL value.
240             #
241              
242 337     337 0 2478 my ($xp, $node, @names) = @_;
243 337         615 my (%data, $ns);
244 337         741 foreach (@names) {
245 3287 100       119478 if (m/:$/) { $ns = $_; next; } # Set def namespace
  337         571  
  337         705  
246 2950 50       7146 my $thisns = (s/(^.*?:)// ? $1 : $ns);
247              
248 2950 100       10062 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
249              
250 2950         4857 my $attrib_path = "\@$_";
251 2950         6229 my $tag_path = "$thisns$_";
252 2950 100       7783 if (my $found = $xp->find($attrib_path, $node)) {
    100          
253 1569         108884 $data{$_} = "" . $found->to_literal;
254 1569 50       41705 warn "Use of '$_' as an attribute is depricated."
255             . " Use a child tag instead."
256             . " To convert your file to the new version see the Docs.\n"
257             unless $is_attrib;
258 1569 50       12407 debug "Got $_=" . (defined $data{$_} ? $data{$_} : 'UNDEF');
259             } elsif ($found = $xp->find($tag_path, $node)) {
260 258 100       32690 if ($_ eq "extra") {
261 159         318 my %extra;
262 159         536 foreach ($found->pop->getAttributes) {
263 392         3608 $extra{ $_->getName } = $_->getData;
264             }
265 159         1041 $data{$_} = \%extra;
266             } else {
267 99         314 $data{$_} = "" . $found->to_literal;
268             }
269 258 50       6871 warn "Use of '$_' as a child tag is depricated."
270             . " Use an attribute instead."
271             . " To convert your file to the new version see the Docs.\n"
272             if $is_attrib;
273 258 50       1468 debug "Got $_=" . (defined $data{$_} ? $data{$_} : 'UNDEF');
274             }
275             }
276              
277 337 50       29002 return wantarray ? %data : \%data;
278             }
279              
280             1;
281              
282             =pod
283              
284             =head1 BUGS
285              
286             Ignores the order attribute for Constraints, Views, Indices, Views, Triggers
287             and Procedures, using the tag order instead. (This is the order output by the
288             SQLFairy XML producer).
289              
290             =head1 SEE ALSO
291              
292             L, L, L,
293             L.
294              
295             =head1 TODO
296              
297             =over 4
298              
299             =item *
300              
301             Support options attribute.
302              
303             =item *
304              
305             Test foreign keys are parsed ok.
306              
307             =item *
308              
309             Control over defaulting.
310              
311             =back
312              
313             =head1 AUTHOR
314              
315             Mark D. Addison Emark.addison@itn.co.ukE,
316             Jonathan Yu Efrequency@cpan.orgE
317              
318             =cut