X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FTranslator%2FParser%2FXML%2FSQLFairy.pm;h=26c1de91b5f8935c36554d9e08027fffe672d6e0;hb=935800450f88b0500c4fa7c3b174cd22b5f9eb56;hp=20a0879e8c2e45a7468a1d92ceabe1da988d07f1;hpb=0a1ec87af6bf62f801f03d9ad58d41a4b3a2a4ac;p=dbsrgits%2FSQL-Translator.git diff --git a/lib/SQL/Translator/Parser/XML/SQLFairy.pm b/lib/SQL/Translator/Parser/XML/SQLFairy.pm index 20a0879..26c1de9 100644 --- a/lib/SQL/Translator/Parser/XML/SQLFairy.pm +++ b/lib/SQL/Translator/Parser/XML/SQLFairy.pm @@ -1,82 +1,78 @@ package SQL::Translator::Parser::XML::SQLFairy; -# ------------------------------------------------------------------- -# $Id: SQLFairy.pm,v 1.1 2003-08-22 18:01:50 kycl4rk Exp $ -# ------------------------------------------------------------------- -# Copyright (C) 2003 Mark Addison , -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; version 2. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA -# 02111-1307 USA -# ------------------------------------------------------------------- - =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). The XML must be in the namespace "http://sqlfairy.sourceforge.net/sqlfairy.xml." +See L 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 + -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'. + + + - - foo - +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. +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). - - EMPTY_STRING - 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. + +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. + +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 # ------------------------------------------------------------------- @@ -84,110 +80,228 @@ Doesn't take any extra parser args at the moment. use strict; use vars qw[ $DEBUG $VERSION @EXPORT_OK ]; -$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/; +$VERSION = '1.59'; $DEBUG = 0 unless defined $DEBUG; use Data::Dumper; +use Carp::Clan qw/^SQL::Translator/; use Exporter; use base qw(Exporter); @EXPORT_OK = qw(parse); use base qw/SQL::Translator::Parser/; # Doesnt do anything at the mo! -use XML::XPath; -use XML::XPath::XMLParser; - -sub debug { - warn @_,"\n" if $DEBUG; -} +use SQL::Translator::Utils 'debug'; +use XML::LibXML 1.69; +use XML::LibXML::XPathContext; 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 $doc = XML::LibXML->new->parse_string($data); + my $xp = XML::LibXML::XPathContext->new($doc); - my $xp = XML::XPath->new(xml => $data); - $xp->set_namespace("sqlf", "http://sqlfairy.sourceforge.net/sqlfairy.xml"); + $xp->registerNs("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 database_events fields + on_table action order extra + /); + + # back compat + if (my $evt = $data{database_event} and $translator->{show_warnings}) { + carp 'The database_event tag is deprecated - please use ' . + 'database_events (which can take one or more comma separated ' . + 'event names)'; + $data{database_events} = join (', ', + $data{database_events} || (), + $evt, + ); + } + + # split into arrayref + if (my $evts = $data{database_events}) { + $data{database_events} = [split (/\s*,\s*/, $evts) ]; + } + + $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 = "\@$_"; + my $tag_path = "$thisns$_"; + if ( my $found = $xp->find($attrib_path,$node) ) { + $data{$_} = "".$found->to_literal; + 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 ( $found = $xp->find($tag_path,$node) ) { + if ($_ eq "extra") { + my %extra; + foreach ( $found->pop->getAttributes ) { + $extra{$_->getName} = $_->getData; + } + $data{$_} = \%extra; + } + else { + $data{$_} = "".$found->to_literal; + } + 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; } @@ -199,34 +313,36 @@ sub get_tagfields { =head1 BUGS -B e.g. Will be parsed as "" and -hence also false. This is a bit counter intuative for some tags as -seeing 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, L, L, +L. =head1 TODO =over 4 -=item * Support sqf:options. +=item * -=item * Test forign keys are parsed ok. +Support options attribute. -=item * Sort out sane handling of empty tags vs tags with no content - vs it no tag being there. +=item * -=item * Control over defaulting of non-existant tags. +Test foreign keys are parsed ok. -=back +=item * -=head1 AUTHOR +Control over defaulting. -Mark D. Addison Emark.addison@itn.co.ukE. +=back -=head1 SEE ALSO +=head1 AUTHOR -perl(1), SQL::Translator, SQL::Translator::Producer::XML::SQLFairy, -SQL::Translator::Schema. +Mark D. Addison Emark.addison@itn.co.ukE, +Jonathan Yu Efrequency@cpan.orgE =cut