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