package SQL::Translator::Parser::XML::SQLFairy;
# -------------------------------------------------------------------
-# $Id: SQLFairy.pm,v 1.1 2003-08-22 18:01:50 kycl4rk Exp $
+# $Id: SQLFairy.pm,v 1.15 2005-07-05 16:20:42 mwz444 Exp $
# -------------------------------------------------------------------
# Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
#
=head1 NAME
-SQL::Translator::Parser::XML::SQLFairy - parser for SQL::Translator's XML
+SQL::Translator::Parser::XML::SQLFairy - parser for SQL::Translator's XML.
=head1 SYNOPSIS
use SQL::Translator;
- use SQL::Translator::Parser::XML::SQLFairy;
- my $translator = SQL::Translator->new(
- from => 'XML-SQLFairy',
- to => 'MySQL',
- filename => 'schema.xml',
- show_warnings => 1,
- add_drop_table => 1,
- );
+ my $translator = SQL::Translator->new( show_warnings => 1 );
- print $obj->translate;
+ my $out = $obj->translate(
+ from => 'XML-SQLFairy',
+ to => 'MySQL',
+ filename => 'schema.xml',
+ ) or die $translator->error;
+
+ print $out;
=head1 DESCRIPTION
This parser handles the flavor of XML used natively by the SQLFairy
-project (SQL::Translator). The XML must be in the namespace
+project (L<SQL::Translator>). The XML must be in the namespace
"http://sqlfairy.sourceforge.net/sqlfairy.xml."
+See L<SQL::Translator::Producer::XML::SQLFairy> for details of this format.
-To see an example of the XML translate one of your schema :) e.g.
-
- $ sql_translator.pl -f MySQL -t XML-SQLFairy schema.sql
+You do not need to specify every attribute of the Schema objects as any missing
+from the XML will be set to their default values. e.g. A field could be written
+using only;
-=head2 attrib_values
+ <sqlf:field name="email" data_type="varchar" size="255" />
-The parser will happily parse XML produced with the attrib_values arg set. If
-it sees a value set as an attribute and a tag, the tag value will override
-that of the attribute.
+Instead of the full;
-e.g. For the xml below the table would get the name 'bar'.
+ <sqlf:field name="email" data_type="varchar" size="255" is_nullable="1"
+ is_auto_increment="0" is_primary_key="0" is_foreign_key="0" order="4">
+ <sqlf:comments></sqlf:comments>
+ </sqlf:field>
- <sqlf:table name="foo">
- <sqlf:name>foo</name>
- </sqlf:table>
+If you do not explicitly set the order of items using order attributes on the
+tags then the order the tags appear in the XML will be used.
=head2 default_value
-Leave the tag out all together to use the default in Schema::Field. Use empty
-tags or EMPTY_STRING for a zero lenth string. NULL for an explicit null
-(currently sets default_value to undef in the Schema::Field obj).
+Leave the attribute out all together to use the default in L<Schema::Field>.
+Use empty quotes or 'EMPTY_STRING' for a zero lenth string. 'NULL' for an
+explicit null (currently sets default_value to undef in the
+Schema::Field obj).
- <sqlf:default_value></sqlf:default_value> <!-- Empty string -->
- <sqlf:default_value>EMPTY_STRING</sqlf:default_value> <!-- Empty string -->
- <sqlf:default_value>NULL</sqlf:default_value> <!-- NULL -->
-
- <sqlf:default_value/> <!-- Empty string BUT DON'T USE! See BUGS -->
+ <sqlf:field default_value="" /> <!-- Empty string -->
+ <sqlf:field default_value="EMPTY_STRING" /> <!-- Empty string -->
+ <sqlf:field default_value="NULL" /> <!-- NULL -->
=head2 ARGS
Doesn't take any extra parser args at the moment.
+=head1 LEGACY FORMAT
+
+The previous version of the SQLFairy XML allowed the attributes of the the
+schema objects to be written as either xml attributes or as data elements, in
+any combination. While this allows for lots of flexibility in writing the XML
+the result is a great many possible XML formats, not so good for DTD writing,
+XPathing etc! So we have moved to a fixed version described in
+L<SQL::Translator::Producer::XML::SQLFairy>.
+
+This version of the parser will still parse the old formats and emmit warnings
+when it sees them being used but they should be considered B<heavily
+depreciated>.
+
+To convert your old format files simply pass them through the translator :)
+
+ $ sqlt -f XML-SQLFairy -t XML-SQLFairy schema-old.xml > schema-new.xml
+
=cut
# -------------------------------------------------------------------
use strict;
use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
-$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
+$VERSION = sprintf "%d.%02d", q$Revision: 1.15 $ =~ /(\d+)\.(\d+)/;
$DEBUG = 0 unless defined $DEBUG;
use Data::Dumper;
@EXPORT_OK = qw(parse);
use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo!
+use SQL::Translator::Utils 'debug';
use XML::XPath;
use XML::XPath::XMLParser;
-sub debug {
- warn @_,"\n" if $DEBUG;
-}
-
sub parse {
my ( $translator, $data ) = @_;
- my $schema = $translator->schema;
- local $DEBUG = $translator->debug;
- #local $TRACE = $translator->trace ? 1 : undef;
- # Nothing with trace option yet!
+ my $schema = $translator->schema;
+ local $DEBUG = $translator->debug;
+ my $xp = XML::XPath->new(xml => $data);
- my $xp = XML::XPath->new(xml => $data);
$xp->set_namespace("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml");
+ #
# Work our way through the tables
#
- my @nodes = $xp->findnodes('/sqlf:schema/sqlf:table');
+ my @nodes = $xp->findnodes(
+ '/sqlf:schema/sqlf:table|/sqlf:schema/sqlf:tables/sqlf:table'
+ );
for my $tblnode (
- sort { "".$xp->findvalue('sqlf:order',$a)
- <=> "".$xp->findvalue('sqlf:order',$b) } @nodes
+ sort {
+ ("".$xp->findvalue('sqlf:order|@order',$a) || 0)
+ <=>
+ ("".$xp->findvalue('sqlf:order|@order',$b) || 0)
+ } @nodes
) {
debug "Adding table:".$xp->findvalue('sqlf:name',$tblnode);
+
my $table = $schema->add_table(
- get_tagfields($xp, $tblnode, "sqlf:" => qw/name order/)
+ get_tagfields($xp, $tblnode, "sqlf:" => qw/name order extra/)
) or die $schema->error;
+ #
# Fields
#
my @nodes = $xp->findnodes('sqlf:fields/sqlf:field',$tblnode);
foreach (
- sort { ("".$xp->findvalue('sqlf:order',$a) || 0)
- <=> ("".$xp->findvalue('sqlf:order',$b) || 0) } @nodes
+ sort {
+ ("".$xp->findvalue('sqlf:order',$a) || 0)
+ <=>
+ ("".$xp->findvalue('sqlf:order',$b) || 0)
+ } @nodes
) {
my %fdata = get_tagfields($xp, $_, "sqlf:",
- qw/name data_type size default_value is_nullable is_auto_increment
- is_primary_key is_foreign_key comments/);
- if (exists $fdata{default_value} and defined $fdata{default_value}){
- if ( $fdata{default_value} =~ /^\s*NULL\s*$/ ) {
- $fdata{default_value}= undef;
+ qw/name data_type size default_value is_nullable extra
+ is_auto_increment is_primary_key is_foreign_key comments/
+ );
+
+ if (
+ exists $fdata{'default_value'} and
+ defined $fdata{'default_value'}
+ ) {
+ if ( $fdata{'default_value'} =~ /^\s*NULL\s*$/ ) {
+ $fdata{'default_value'}= undef;
}
- elsif ( $fdata{default_value} =~ /^\s*EMPTY_STRING\s*$/ ) {
- $fdata{default_value} = "";
+ elsif ( $fdata{'default_value'} =~ /^\s*EMPTY_STRING\s*$/ ) {
+ $fdata{'default_value'} = "";
}
}
- my $field = $table->add_field(%fdata) or die $schema->error;
- $table->primary_key($field->name) if $fdata{'is_primary_key'};
- # TODO We should be able to make the table obj spot this when we
- # use add_field.
- # TODO Deal with $field->extra
+
+ my $field = $table->add_field( %fdata ) or die $table->error;
+
+ $table->primary_key( $field->name ) if $fdata{'is_primary_key'};
+
+ #
+ # TODO:
+ # - We should be able to make the table obj spot this when
+ # we use add_field.
+ #
}
+ #
# Constraints
#
@nodes = $xp->findnodes('sqlf:constraints/sqlf:constraint',$tblnode);
foreach (@nodes) {
my %data = get_tagfields($xp, $_, "sqlf:",
- qw/name type table fields reference_fields reference_table
- match_type on_delete_do on_update_do/);
- $table->add_constraint(%data) or die $schema->error;
+ qw/name type table fields reference_fields reference_table
+ match_type on_delete on_update extra/
+ );
+ $table->add_constraint( %data ) or die $table->error;
}
+ #
# Indexes
#
@nodes = $xp->findnodes('sqlf:indices/sqlf:index',$tblnode);
foreach (@nodes) {
my %data = get_tagfields($xp, $_, "sqlf:",
- qw/name type fields options/);
- $table->add_index(%data) or die $schema->error;
+ qw/name type fields options extra/);
+ $table->add_index( %data ) or die $table->error;
+ }
+
+
+ #
+ # Comments
+ #
+ @nodes = $xp->findnodes('sqlf:comments/sqlf:comment',$tblnode);
+ foreach (@nodes) {
+ my $data = $_->string_value;
+ $table->comments( $data );
}
} # tables loop
+ #
+ # Views
+ #
+ @nodes = $xp->findnodes(
+ '/sqlf:schema/sqlf:view|/sqlf:schema/sqlf:views/sqlf:view'
+ );
+ foreach (@nodes) {
+ my %data = get_tagfields($xp, $_, "sqlf:",
+ qw/name sql fields order extra/
+ );
+ $schema->add_view( %data ) or die $schema->error;
+ }
+
+ #
+ # Triggers
+ #
+ @nodes = $xp->findnodes(
+ '/sqlf:schema/sqlf:trigger|/sqlf:schema/sqlf:triggers/sqlf:trigger'
+ );
+ foreach (@nodes) {
+ my %data = get_tagfields($xp, $_, "sqlf:", qw/
+ name perform_action_when database_event fields on_table action order
+ extra
+ /);
+ $schema->add_trigger( %data ) or die $schema->error;
+ }
+
+ #
+ # Procedures
+ #
+ @nodes = $xp->findnodes(
+ '/sqlf:schema/sqlf:procedure|/sqlf:schema/sqlf:procedures/sqlf:procedure'
+ );
+ foreach (@nodes) {
+ my %data = get_tagfields($xp, $_, "sqlf:",
+ qw/name sql parameters owner comments order extra/
+ );
+ $schema->add_procedure( %data ) or die $schema->error;
+ }
+
return 1;
}
-# get_tagfields XPNODE, NAMESPACE => qw/TAGNAMES/;
+# -------------------------------------------------------------------
+sub get_tagfields {
+#
+# get_tagfields XP, NODE, NAMESPACE => qw/TAGNAMES/;
# get_tagfields $node, "sqlf:" => qw/name type fields reference/;
#
-# Returns hash of data. If a tag isn't in the file it is not in this
-# hash.
-# TODO Add handling of and explicit NULL value.
-sub get_tagfields {
+# Returns hash of data.
+# TODO - Add handling of an explicit NULL value.
+#
+
my ($xp, $node, @names) = @_;
my (%data, $ns);
foreach (@names) {
if ( m/:$/ ) { $ns = $_; next; } # Set def namespace
my $thisns = (s/(^.*?:)// ? $1 : $ns);
- foreach my $path ( "\@$thisns$_","$thisns$_") {
- $data{$_} = $xp->findvalue($path,$node) if $xp->exists($path,$node);
- debug "Got $_=".(defined $data{$_} ? $data{$_} : "UNDEF");
+
+ my $is_attrib = m/^(sql|comments|action|extra)$/ ? 0 : 1;
+
+ my $attrib_path = "\@$thisns$_";
+ my $tag_path = "$thisns$_";
+ if ( $xp->exists($attrib_path,$node) ) {
+ $data{$_} = "".$xp->findvalue($attrib_path,$node);
+ warn "Use of '$_' as an attribute is depricated."
+ ." Use a child tag instead."
+ ." To convert your file to the new version see the Docs.\n"
+ unless $is_attrib;
+ debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
+ }
+ elsif ( $xp->exists($tag_path,$node) ) {
+ if ($_ eq "extra") {
+ my %extra;
+ my $extra_nodes = $xp->find($tag_path,$node);
+ foreach ( $extra_nodes->pop->getAttributes ) {
+ $extra{$_->getName} = $_->getData;
+ }
+ $data{$_} = \%extra;
+ }
+ else {
+ $data{$_} = "".$xp->findvalue($tag_path,$node);
+ }
+ warn "Use of '$_' as a child tag is depricated."
+ ." Use an attribute instead."
+ ." To convert your file to the new version see the Docs.\n"
+ if $is_attrib;
+ debug "Got $_=".( defined $data{ $_ } ? $data{ $_ } : 'UNDEF' );
}
}
+
return wantarray ? %data : \%data;
}
=head1 BUGS
-B<Empty Tags> e.g. <sqlf:default_value/> Will be parsed as "" and
-hence also false. This is a bit counter intuative for some tags as
-seeing <sqlf:is_nullable /> you might think that it was set when it
-fact it wouldn't be. So for now it is safest not to use them until
-their handling by the parser is defined.
+Ignores the order attribute for Constraints, Views, Indices,
+Views, Triggers and Procedures, using the tag order instead. (This is the order
+output by the SQLFairy XML producer).
+
+=head1 SEE ALSO
+
+L<perl>, L<SQL::Translator>, L<SQL::Translator::Producer::XML::SQLFairy>,
+L<SQL::Translator::Schema>.
=head1 TODO
=over 4
-=item * Support sqf:options.
+=item *
+
+Support options attribute.
+
+=item *
-=item * Test forign keys are parsed ok.
+Test foreign keys are parsed ok.
-=item * Sort out sane handling of empty tags <foo/> vs tags with no content
- <foo></foo> vs it no tag being there.
+=item *
-=item * Control over defaulting of non-existant tags.
+Control over defaulting.
=back
Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
-=head1 SEE ALSO
-
-perl(1), SQL::Translator, SQL::Translator::Producer::XML::SQLFairy,
-SQL::Translator::Schema.
-
=cut