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