Remove duplicate req
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / XML / SQLFairy.pm
CommitLineData
0a1ec87a 1package SQL::Translator::Parser::XML::SQLFairy;
2
3# -------------------------------------------------------------------
0a1ec87a 4# Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13# General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18# 02111-1307 USA
19# -------------------------------------------------------------------
20
21=head1 NAME
22
8571d198 23SQL::Translator::Parser::XML::SQLFairy - parser for SQL::Translator's XML.
0a1ec87a 24
25=head1 SYNOPSIS
26
27 use SQL::Translator;
0a1ec87a 28
85633bfb 29 my $translator = SQL::Translator->new( show_warnings => 1 );
0a1ec87a 30
85633bfb 31 my $out = $obj->translate(
32 from => 'XML-SQLFairy',
33 to => 'MySQL',
34 filename => 'schema.xml',
35 ) or die $translator->error;
36
37 print $out;
0a1ec87a 38
39=head1 DESCRIPTION
40
41This parser handles the flavor of XML used natively by the SQLFairy
91f28468 42project (L<SQL::Translator>). The XML must be in the namespace
0a1ec87a 43"http://sqlfairy.sourceforge.net/sqlfairy.xml."
8571d198 44See L<SQL::Translator::Producer::XML::SQLFairy> for details of this format.
0a1ec87a 45
8571d198 46You do not need to specify every attribute of the Schema objects as any missing
47from the XML will be set to their default values. e.g. A field could be written
48using only;
0a1ec87a 49
8571d198 50 <sqlf:field name="email" data_type="varchar" size="255" />
0a1ec87a 51
8571d198 52Instead of the full;
0a1ec87a 53
8571d198 54 <sqlf:field name="email" data_type="varchar" size="255" is_nullable="1"
55 is_auto_increment="0" is_primary_key="0" is_foreign_key="0" order="4">
56 <sqlf:comments></sqlf:comments>
57 </sqlf:field>
0a1ec87a 58
8571d198 59If you do not explicitly set the order of items using order attributes on the
60tags then the order the tags appear in the XML will be used.
0a1ec87a 61
62=head2 default_value
63
91f28468 64Leave the attribute out all together to use the default in L<Schema::Field>.
65Use empty quotes or 'EMPTY_STRING' for a zero lenth string. 'NULL' for an
a5e624ac 66explicit null (currently sets default_value to undef in the
67Schema::Field obj).
0a1ec87a 68
91f28468 69 <sqlf:field default_value="" /> <!-- Empty string -->
70 <sqlf:field default_value="EMPTY_STRING" /> <!-- Empty string -->
71 <sqlf:field default_value="NULL" /> <!-- NULL -->
0a1ec87a 72
0a1ec87a 73=head2 ARGS
74
75Doesn't take any extra parser args at the moment.
76
4a268a6c 77=head1 LEGACY FORMAT
78
79The previous version of the SQLFairy XML allowed the attributes of the the
80schema objects to be written as either xml attributes or as data elements, in
81any combination. While this allows for lots of flexibility in writing the XML
82the result is a great many possible XML formats, not so good for DTD writing,
83XPathing etc! So we have moved to a fixed version described in
84L<SQL::Translator::Producer::XML::SQLFairy>.
85
86This version of the parser will still parse the old formats and emmit warnings
91f28468 87when it sees them being used but they should be considered B<heavily
88depreciated>.
4a268a6c 89
91f28468 90To convert your old format files simply pass them through the translator :)
4a268a6c 91
91f28468 92 $ sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
4a268a6c 93
0a1ec87a 94=cut
95
96# -------------------------------------------------------------------
97
98use strict;
99
da06ac74 100use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
4ab3763d 101$VERSION = '1.59';
0a1ec87a 102$DEBUG = 0 unless defined $DEBUG;
103
104use Data::Dumper;
9768b204 105use Carp::Clan qw/^SQL::Translator/;
0a1ec87a 106use Exporter;
107use base qw(Exporter);
108@EXPORT_OK = qw(parse);
109
110use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
a5e624ac 111use SQL::Translator::Utils 'debug';
0a1ec87a 112use XML::XPath;
113use XML::XPath::XMLParser;
114
0a1ec87a 115sub parse {
116 my ( $translator, $data ) = @_;
a5e624ac 117 my $schema = $translator->schema;
118 local $DEBUG = $translator->debug;
119 my $xp = XML::XPath->new(xml => $data);
0a1ec87a 120
0a1ec87a 121 $xp->set_namespace("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
122
a5e624ac 123 #
0a1ec87a 124 # Work our way through the tables
125 #
87c5565e 126 my @nodes = $xp->findnodes(
127 '/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table'
128 );
0a1ec87a 129 for my $tblnode (
8571d198 130 sort {
53a533ef 131 ("".$xp->findvalue('sqlf:order|@order',$a) || 0)
8571d198 132 <=>
53a533ef 133 ("".$xp->findvalue('sqlf:order|@order',$b) || 0)
a5e624ac 134 } @nodes
0a1ec87a 135 ) {
136 debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
a5e624ac 137
0a1ec87a 138 my $table = $schema->add_table(
b1789409 139 get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/)
0a1ec87a 140 ) or die $schema->error;
141
a5e624ac 142 #
0a1ec87a 143 # Fields
144 #
145 my @nodes = $xp->findnodes('sqlf:fields/sqlf:field',$tblnode);
146 foreach (
8571d198 147 sort {
a5e624ac 148 ("".$xp->findvalue('sqlf:order',$a) || 0)
8571d198 149 <=>
150 ("".$xp->findvalue('sqlf:order',$b) || 0)
a5e624ac 151 } @nodes
0a1ec87a 152 ) {
153 my %fdata = get_tagfields($xp, $_, "sqlf:",
94ed484b 154 qw/name data_type size default_value is_nullable extra
a5e624ac 155 is_auto_increment is_primary_key is_foreign_key comments/
156 );
157
158 if (
8571d198 159 exists $fdata{'default_value'} and
a5e624ac 160 defined $fdata{'default_value'}
161 ) {
162 if ( $fdata{'default_value'} =~ /^\s*NULL\s*$/ ) {
163 $fdata{'default_value'}= undef;
0a1ec87a 164 }
a5e624ac 165 elsif ( $fdata{'default_value'} =~ /^\s*EMPTY_STRING\s*$/ ) {
166 $fdata{'default_value'} = "";
0a1ec87a 167 }
168 }
a5e624ac 169
19922fbc 170 my $field = $table->add_field( %fdata ) or die $table->error;
a5e624ac 171
172 $table->primary_key( $field->name ) if $fdata{'is_primary_key'};
173
174 #
175 # TODO:
8571d198 176 # - We should be able to make the table obj spot this when
a5e624ac 177 # we use add_field.
a5e624ac 178 #
0a1ec87a 179 }
180
a5e624ac 181 #
0a1ec87a 182 # Constraints
183 #
184 @nodes = $xp->findnodes('sqlf:constraints/sqlf:constraint',$tblnode);
185 foreach (@nodes) {
186 my %data = get_tagfields($xp, $_, "sqlf:",
a5e624ac 187 qw/name type table fields reference_fields reference_table
100684f3 188 match_type on_delete on_update extra/
a5e624ac 189 );
19922fbc 190 $table->add_constraint( %data ) or die $table->error;
0a1ec87a 191 }
192
a5e624ac 193 #
0a1ec87a 194 # Indexes
195 #
196 @nodes = $xp->findnodes('sqlf:indices/sqlf:index',$tblnode);
197 foreach (@nodes) {
198 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 199 qw/name type fields options extra/);
19922fbc 200 $table->add_index( %data ) or die $table->error;
0a1ec87a 201 }
202
7c71eaab 203
204 #
205 # Comments
206 #
207 @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
208 foreach (@nodes) {
209 my $data = $_->string_value;
210 $table->comments( $data );
211 }
212
0a1ec87a 213 } # tables loop
214
19922fbc 215 #
216 # Views
217 #
87c5565e 218 @nodes = $xp->findnodes(
219 '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
220 );
19922fbc 221 foreach (@nodes) {
222 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 223 qw/name sql fields order extra/
19922fbc 224 );
225 $schema->add_view( %data ) or die $schema->error;
226 }
8571d198 227
19922fbc 228 #
229 # Triggers
230 #
87c5565e 231 @nodes = $xp->findnodes(
232 '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
233 );
19922fbc 234 foreach (@nodes) {
b1789409 235 my %data = get_tagfields($xp, $_, "sqlf:", qw/
af858b8a 236 name perform_action_when database_event database_events fields on_table action order
b1789409 237 extra
238 /);
af858b8a 239
240 # back compat
9768b204 241 if (my $evt = $data{database_event} and $translator->{show_warnings}) {
242 carp 'The database_event tag is deprecated - please use database_events (which can take one or more comma separated event names)';
af858b8a 243 $data{database_events} = join (', ',
244 $data{database_events} || (),
245 $evt,
246 );
247 }
248
249 # split into arrayref
250 if (my $evts = $data{database_events}) {
251 $data{database_events} = [split (/\s*,\s*/, $evts) ];
252 }
253
19922fbc 254 $schema->add_trigger( %data ) or die $schema->error;
255 }
8571d198 256
19922fbc 257 #
258 # Procedures
259 #
87c5565e 260 @nodes = $xp->findnodes(
261 '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
262 );
19922fbc 263 foreach (@nodes) {
264 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 265 qw/name sql parameters owner comments order extra/
19922fbc 266 );
267 $schema->add_procedure( %data ) or die $schema->error;
268 }
8571d198 269
0a1ec87a 270 return 1;
271}
272
a5e624ac 273# -------------------------------------------------------------------
274sub get_tagfields {
275#
8571d198 276# get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
0a1ec87a 277# get_tagfields $node, "sqlf:" => qw/name type fields reference/;
278#
8571d198 279# Returns hash of data.
280# TODO - Add handling of an explicit NULL value.
a5e624ac 281#
282
0a1ec87a 283 my ($xp, $node, @names) = @_;
284 my (%data, $ns);
285 foreach (@names) {
286 if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
287 my $thisns = (s/(^.*?:)// ? $1 : $ns);
a5e624ac 288
94ed484b 289 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
a5e624ac 290
8571d198 291 my $attrib_path = "\@$thisns$_";
292 my $tag_path = "$thisns$_";
293 if ( $xp->exists($attrib_path,$node) ) {
294 $data{$_} = "".$xp->findvalue($attrib_path,$node);
295 warn "Use of '$_' as an attribute is depricated."
296 ." Use a child tag instead."
297 ." To convert your file to the new version see the Docs.\n"
298 unless $is_attrib;
299 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
300 }
301 elsif ( $xp->exists($tag_path,$node) ) {
94ed484b 302 if ($_ eq "extra") {
303 my %extra;
304 my $extra_nodes = $xp->find($tag_path,$node);
305 foreach ( $extra_nodes->pop->getAttributes ) {
306 $extra{$_->getName} = $_->getData;
307 }
308 $data{$_} = \%extra;
309 }
310 else {
311 $data{$_} = "".$xp->findvalue($tag_path,$node);
312 }
8571d198 313 warn "Use of '$_' as a child tag is depricated."
314 ." Use an attribute instead."
315 ." To convert your file to the new version see the Docs.\n"
316 if $is_attrib;
a5e624ac 317 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
0a1ec87a 318 }
319 }
a5e624ac 320
0a1ec87a 321 return wantarray ? %data : \%data;
322}
323
3241;
325
326# -------------------------------------------------------------------
327
328=pod
329
330=head1 BUGS
331
8571d198 332Ignores the order attribute for Constraints, Views, Indices,
333Views, Triggers and Procedures, using the tag order instead. (This is the order
334output by the SQLFairy XML producer).
0a1ec87a 335
91f28468 336=head1 SEE ALSO
337
338L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
339L<SQL::Translator::Schema>.
340
0a1ec87a 341=head1 TODO
342
343=over 4
344
8571d198 345=item *
a5e624ac 346
94ed484b 347Support options attribute.
a5e624ac 348
8571d198 349=item *
0a1ec87a 350
8571d198 351Test foreign keys are parsed ok.
0a1ec87a 352
8571d198 353=item *
0a1ec87a 354
91f28468 355Control over defaulting.
0a1ec87a 356
357=back
358
359=head1 AUTHOR
360
361Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
362
0a1ec87a 363=cut