Added _attributes class data to SQL::Translator::Schema::Object for sub classes
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / XML / SQLFairy.pm
CommitLineData
0a1ec87a 1package SQL::Translator::Parser::XML::SQLFairy;
2
3# -------------------------------------------------------------------
85633bfb 4# $Id: SQLFairy.pm,v 1.11 2004-08-20 11:01:48 grommit 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 ];
85633bfb 103$VERSION = sprintf "%d.%02d", q$Revision: 1.11 $ =~ /(\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 {
59b2ec83 132 "".$xp->findvalue('sqlf:order|@order',$a)
8571d198 133 <=>
134 "".$xp->findvalue('sqlf:order|@order',$b)
a5e624ac 135 } @nodes
0a1ec87a 136 ) {
137 debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
a5e624ac 138
0a1ec87a 139 my $table = $schema->add_table(
140 get_tagfields($xp, $tblnode, "sqlf:" => qw/name order/)
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
189 match_type on_delete_do on_update_do/
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:",
a5e624ac 200 qw/name type fields options/);
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:",
214 qw/name sql fields order/
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) {
226 my %data = get_tagfields($xp, $_, "sqlf:",
227 qw/name perform_action_when database_event fields on_table action order/
228 );
229 $schema->add_trigger( %data ) or die $schema->error;
230 }
8571d198 231
19922fbc 232 #
233 # Procedures
234 #
87c5565e 235 @nodes = $xp->findnodes(
236 '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
237 );
19922fbc 238 foreach (@nodes) {
239 my %data = get_tagfields($xp, $_, "sqlf:",
240 qw/name sql parameters owner comments order/
241 );
242 $schema->add_procedure( %data ) or die $schema->error;
243 }
8571d198 244
0a1ec87a 245 return 1;
246}
247
a5e624ac 248# -------------------------------------------------------------------
249sub get_tagfields {
250#
8571d198 251# get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
0a1ec87a 252# get_tagfields $node, "sqlf:" => qw/name type fields reference/;
253#
8571d198 254# Returns hash of data.
255# TODO - Add handling of an explicit NULL value.
a5e624ac 256#
257
0a1ec87a 258 my ($xp, $node, @names) = @_;
259 my (%data, $ns);
260 foreach (@names) {
261 if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
262 my $thisns = (s/(^.*?:)// ? $1 : $ns);
a5e624ac 263
94ed484b 264 my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
a5e624ac 265
8571d198 266 my $attrib_path = "\@$thisns$_";
267 my $tag_path = "$thisns$_";
268 if ( $xp->exists($attrib_path,$node) ) {
269 $data{$_} = "".$xp->findvalue($attrib_path,$node);
270 warn "Use of '$_' as an attribute is depricated."
271 ." Use a child tag instead."
272 ." To convert your file to the new version see the Docs.\n"
273 unless $is_attrib;
274 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
275 }
276 elsif ( $xp->exists($tag_path,$node) ) {
94ed484b 277 if ($_ eq "extra") {
278 my %extra;
279 my $extra_nodes = $xp->find($tag_path,$node);
280 foreach ( $extra_nodes->pop->getAttributes ) {
281 $extra{$_->getName} = $_->getData;
282 }
283 $data{$_} = \%extra;
284 }
285 else {
286 $data{$_} = "".$xp->findvalue($tag_path,$node);
287 }
8571d198 288 warn "Use of '$_' as a child tag is depricated."
289 ." Use an attribute instead."
290 ." To convert your file to the new version see the Docs.\n"
291 if $is_attrib;
a5e624ac 292 debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
0a1ec87a 293 }
294 }
a5e624ac 295
0a1ec87a 296 return wantarray ? %data : \%data;
297}
298
2991;
300
301# -------------------------------------------------------------------
302
303=pod
304
305=head1 BUGS
306
8571d198 307Ignores the order attribute for Constraints, Views, Indices,
308Views, Triggers and Procedures, using the tag order instead. (This is the order
309output by the SQLFairy XML producer).
0a1ec87a 310
91f28468 311=head1 SEE ALSO
312
313L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
314L<SQL::Translator::Schema>.
315
0a1ec87a 316=head1 TODO
317
318=over 4
319
8571d198 320=item *
a5e624ac 321
94ed484b 322Support options attribute.
a5e624ac 323
8571d198 324=item *
0a1ec87a 325
8571d198 326Test foreign keys are parsed ok.
0a1ec87a 327
8571d198 328=item *
0a1ec87a 329
91f28468 330Control over defaulting.
0a1ec87a 331
332=back
333
334=head1 AUTHOR
335
336Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
337
0a1ec87a 338=cut