Made some changes suggested by Michael Slattery to fix table level comments. Also...
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / XML / SQLFairy.pm
CommitLineData
0a1ec87a 1package SQL::Translator::Parser::XML::SQLFairy;
2
3# -------------------------------------------------------------------
7c71eaab 4# $Id: SQLFairy.pm,v 1.15 2005-07-05 16:20:42 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 ];
7c71eaab 103$VERSION = sprintf "%d.%02d", q$Revision: 1.15 $ =~ /(\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
7c71eaab 204
205 #
206 # Comments
207 #
208 @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
209 foreach (@nodes) {
210 my $data = $_->string_value;
211 $table->comments( $data );
212 }
213
0a1ec87a 214 } # tables loop
215
19922fbc 216 #
217 # Views
218 #
87c5565e 219 @nodes = $xp->findnodes(
220 '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
221 );
19922fbc 222 foreach (@nodes) {
223 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 224 qw/name sql fields order extra/
19922fbc 225 );
226 $schema->add_view( %data ) or die $schema->error;
227 }
8571d198 228
19922fbc 229 #
230 # Triggers
231 #
87c5565e 232 @nodes = $xp->findnodes(
233 '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
234 );
19922fbc 235 foreach (@nodes) {
b1789409 236 my %data = get_tagfields($xp, $_, "sqlf:", qw/
237 name perform_action_when database_event fields on_table action order
238 extra
239 /);
19922fbc 240 $schema->add_trigger( %data ) or die $schema->error;
241 }
8571d198 242
19922fbc 243 #
244 # Procedures
245 #
87c5565e 246 @nodes = $xp->findnodes(
247 '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
248 );
19922fbc 249 foreach (@nodes) {
250 my %data = get_tagfields($xp, $_, "sqlf:",
b1789409 251 qw/name sql parameters owner comments order extra/
19922fbc 252 );
253 $schema->add_procedure( %data ) or die $schema->error;
254 }
8571d198 255
0a1ec87a 256 return 1;
257}
258
a5e624ac 259# -------------------------------------------------------------------
260sub get_tagfields {
261#
8571d198 262# get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
0a1ec87a 263# get_tagfields $node, "sqlf:" => qw/name type fields reference/;
264#
8571d198 265# Returns hash of data.
266# TODO - Add handling of an explicit NULL value.
a5e624ac 267#
268
0a1ec87a 269 my ($xp, $node, @names) = @_;
270 my (%data, $ns);
271 foreach (@names) {
272 if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
273 my $thisns = (s/(^.*?:)// ? $1 : $ns);
a5e624ac 274
94ed484b 275 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
a5e624ac 276
8571d198 277 my $attrib_path = "\@$thisns$_";
278 my $tag_path = "$thisns$_";
279 if ( $xp->exists($attrib_path,$node) ) {
280 $data{$_} = "".$xp->findvalue($attrib_path,$node);
281 warn "Use of '$_' as an attribute is depricated."
282 ." Use a child tag instead."
283 ." To convert your file to the new version see the Docs.\n"
284 unless $is_attrib;
285 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
286 }
287 elsif ( $xp->exists($tag_path,$node) ) {
94ed484b 288 if ($_ eq "extra") {
289 my %extra;
290 my $extra_nodes = $xp->find($tag_path,$node);
291 foreach ( $extra_nodes->pop->getAttributes ) {
292 $extra{$_->getName} = $_->getData;
293 }
294 $data{$_} = \%extra;
295 }
296 else {
297 $data{$_} = "".$xp->findvalue($tag_path,$node);
298 }
8571d198 299 warn "Use of '$_' as a child tag is depricated."
300 ." Use an attribute instead."
301 ." To convert your file to the new version see the Docs.\n"
302 if $is_attrib;
a5e624ac 303 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
0a1ec87a 304 }
305 }
a5e624ac 306
0a1ec87a 307 return wantarray ? %data : \%data;
308}
309
3101;
311
312# -------------------------------------------------------------------
313
314=pod
315
316=head1 BUGS
317
8571d198 318Ignores the order attribute for Constraints, Views, Indices,
319Views, Triggers and Procedures, using the tag order instead. (This is the order
320output by the SQLFairy XML producer).
0a1ec87a 321
91f28468 322=head1 SEE ALSO
323
324L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
325L<SQL::Translator::Schema>.
326
0a1ec87a 327=head1 TODO
328
329=over 4
330
8571d198 331=item *
a5e624ac 332
94ed484b 333Support options attribute.
a5e624ac 334
8571d198 335=item *
0a1ec87a 336
8571d198 337Test foreign keys are parsed ok.
0a1ec87a 338
8571d198 339=item *
0a1ec87a 340
91f28468 341Control over defaulting.
0a1ec87a 342
343=back
344
345=head1 AUTHOR
346
347Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
348
0a1ec87a 349=cut