Minor POD fix
[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;
105use Exporter;
106use base qw(Exporter);
107@EXPORT_OK = qw(parse);
108
109use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
a5e624ac 110use SQL::Translator::Utils 'debug';
0a1ec87a 111use XML::XPath;
112use XML::XPath::XMLParser;
113
0a1ec87a 114sub parse {
115 my ( $translator, $data ) = @_;
a5e624ac 116 my $schema = $translator->schema;
117 local $DEBUG = $translator->debug;
118 my $xp = XML::XPath->new(xml => $data);
0a1ec87a 119
0a1ec87a 120 $xp->set_namespace("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
121
a5e624ac 122 #
0a1ec87a 123 # Work our way through the tables
124 #
87c5565e 125 my @nodes = $xp->findnodes(
126 '/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table'
127 );
0a1ec87a 128 for my $tblnode (
8571d198 129 sort {
53a533ef 130 ("".$xp->findvalue('sqlf:order|@order',$a) || 0)
8571d198 131 <=>
53a533ef 132 ("".$xp->findvalue('sqlf:order|@order',$b) || 0)
a5e624ac 133 } @nodes
0a1ec87a 134 ) {
135 debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
a5e624ac 136
0a1ec87a 137 my $table = $schema->add_table(
b1789409 138 get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/)
0a1ec87a 139 ) or die $schema->error;
140
a5e624ac 141 #
0a1ec87a 142 # Fields
143 #
144 my @nodes = $xp->findnodes('sqlf:fields/sqlf:field',$tblnode);
145 foreach (
8571d198 146 sort {
a5e624ac 147 ("".$xp->findvalue('sqlf:order',$a) || 0)
8571d198 148 <=>
149 ("".$xp->findvalue('sqlf:order',$b) || 0)
a5e624ac 150 } @nodes
0a1ec87a 151 ) {
152 my %fdata = get_tagfields($xp, $_, "sqlf:",
94ed484b 153 qw/name data_type size default_value is_nullable extra
a5e624ac 154 is_auto_increment is_primary_key is_foreign_key comments/
155 );
156
157 if (
8571d198 158 exists $fdata{'default_value'} and
a5e624ac 159 defined $fdata{'default_value'}
160 ) {
161 if ( $fdata{'default_value'} =~ /^\s*NULL\s*$/ ) {
162 $fdata{'default_value'}= undef;
0a1ec87a 163 }
a5e624ac 164 elsif ( $fdata{'default_value'} =~ /^\s*EMPTY_STRING\s*$/ ) {
165 $fdata{'default_value'} = "";
0a1ec87a 166 }
167 }
a5e624ac 168
19922fbc 169 my $field = $table->add_field( %fdata ) or die $table->error;
a5e624ac 170
171 $table->primary_key( $field->name ) if $fdata{'is_primary_key'};
172
173 #
174 # TODO:
8571d198 175 # - We should be able to make the table obj spot this when
a5e624ac 176 # we use add_field.
a5e624ac 177 #
0a1ec87a 178 }
179
a5e624ac 180 #
0a1ec87a 181 # Constraints
182 #
183 @nodes = $xp->findnodes('sqlf:constraints/sqlf:constraint',$tblnode);
184 foreach (@nodes) {
185 my %data = get_tagfields($xp, $_, "sqlf:",
a5e624ac 186 qw/name type table fields reference_fields reference_table
100684f3 187 match_type on_delete on_update extra/
a5e624ac 188 );
19922fbc 189 $table->add_constraint( %data ) or die $table->error;
0a1ec87a 190 }
191
a5e624ac 192 #
0a1ec87a 193 # Indexes
194 #
195 @nodes = $xp->findnodes('sqlf:indices/sqlf:index',$tblnode);
196 foreach (@nodes) {
197 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 198 qw/name type fields options extra/);
19922fbc 199 $table->add_index( %data ) or die $table->error;
0a1ec87a 200 }
201
7c71eaab 202
203 #
204 # Comments
205 #
206 @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
207 foreach (@nodes) {
208 my $data = $_->string_value;
209 $table->comments( $data );
210 }
211
0a1ec87a 212 } # tables loop
213
19922fbc 214 #
215 # Views
216 #
87c5565e 217 @nodes = $xp->findnodes(
218 '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
219 );
19922fbc 220 foreach (@nodes) {
221 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 222 qw/name sql fields order extra/
19922fbc 223 );
224 $schema->add_view( %data ) or die $schema->error;
225 }
8571d198 226
19922fbc 227 #
228 # Triggers
229 #
87c5565e 230 @nodes = $xp->findnodes(
231 '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
232 );
19922fbc 233 foreach (@nodes) {
b1789409 234 my %data = get_tagfields($xp, $_, "sqlf:", qw/
235 name perform_action_when database_event fields on_table action order
236 extra
237 /);
19922fbc 238 $schema->add_trigger( %data ) or die $schema->error;
239 }
8571d198 240
19922fbc 241 #
242 # Procedures
243 #
87c5565e 244 @nodes = $xp->findnodes(
245 '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
246 );
19922fbc 247 foreach (@nodes) {
248 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 249 qw/name sql parameters owner comments order extra/
19922fbc 250 );
251 $schema->add_procedure( %data ) or die $schema->error;
252 }
8571d198 253
0a1ec87a 254 return 1;
255}
256
a5e624ac 257# -------------------------------------------------------------------
258sub get_tagfields {
259#
8571d198 260# get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
0a1ec87a 261# get_tagfields $node, "sqlf:" => qw/name type fields reference/;
262#
8571d198 263# Returns hash of data.
264# TODO - Add handling of an explicit NULL value.
a5e624ac 265#
266
0a1ec87a 267 my ($xp, $node, @names) = @_;
268 my (%data, $ns);
269 foreach (@names) {
270 if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
271 my $thisns = (s/(^.*?:)// ? $1 : $ns);
a5e624ac 272
94ed484b 273 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
a5e624ac 274
8571d198 275 my $attrib_path = "\@$thisns$_";
276 my $tag_path = "$thisns$_";
277 if ( $xp->exists($attrib_path,$node) ) {
278 $data{$_} = "".$xp->findvalue($attrib_path,$node);
279 warn "Use of '$_' as an attribute is depricated."
280 ." Use a child tag instead."
281 ." To convert your file to the new version see the Docs.\n"
282 unless $is_attrib;
283 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
284 }
285 elsif ( $xp->exists($tag_path,$node) ) {
94ed484b 286 if ($_ eq "extra") {
287 my %extra;
288 my $extra_nodes = $xp->find($tag_path,$node);
289 foreach ( $extra_nodes->pop->getAttributes ) {
290 $extra{$_->getName} = $_->getData;
291 }
292 $data{$_} = \%extra;
293 }
294 else {
295 $data{$_} = "".$xp->findvalue($tag_path,$node);
296 }
8571d198 297 warn "Use of '$_' as a child tag is depricated."
298 ." Use an attribute instead."
299 ." To convert your file to the new version see the Docs.\n"
300 if $is_attrib;
a5e624ac 301 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
0a1ec87a 302 }
303 }
a5e624ac 304
0a1ec87a 305 return wantarray ? %data : \%data;
306}
307
3081;
309
310# -------------------------------------------------------------------
311
312=pod
313
314=head1 BUGS
315
8571d198 316Ignores the order attribute for Constraints, Views, Indices,
317Views, Triggers and Procedures, using the tag order instead. (This is the order
318output by the SQLFairy XML producer).
0a1ec87a 319
91f28468 320=head1 SEE ALSO
321
322L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
323L<SQL::Translator::Schema>.
324
0a1ec87a 325=head1 TODO
326
327=over 4
328
8571d198 329=item *
a5e624ac 330
94ed484b 331Support options attribute.
a5e624ac 332
8571d198 333=item *
0a1ec87a 334
8571d198 335Test foreign keys are parsed ok.
0a1ec87a 336
8571d198 337=item *
0a1ec87a 338
91f28468 339Control over defaulting.
0a1ec87a 340
341=back
342
343=head1 AUTHOR
344
345Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
346
0a1ec87a 347=cut