1 package SQL::Translator::Parser::XML::XMI::SQLFairy;
3 # -------------------------------------------------------------------
4 # $Id: SQLFairy.pm,v 1.1 2003-10-10 20:03:24 grommit Exp $
5 # -------------------------------------------------------------------
6 # Copyright (C) 2003 Mark Addison <mark.addison@itn.co.uk>,
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.
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.
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
21 # -------------------------------------------------------------------
25 SQL::Translator::Parser::XML::XMI::SQLFairy - Create Schema from UML Models.
31 use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
32 $VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
33 $DEBUG = 0 unless defined $DEBUG;
35 use base qw(Exporter);
36 @EXPORT_OK = qw(parse);
39 use SQL::Translator::Parser::XML::XMI;
40 use SQL::Translator::Utils 'debug';
42 # Set the parg for the conversion sub then use the XMI parser
44 my ( $translator ) = @_;
45 local $DEBUG = $translator->debug;
46 my $pargs = $translator->parser_args;
47 $pargs->{classes2schema} = \&classes2schema;
48 return SQL::Translator::Parser::XML::XMI::parse(@_);
53 # TODO We could make the tag names a parser arg so people can use their own.
55 $TAGS{data_type} = "sqlfDataType";
56 $TAGS{size} = "sqlfSize";
57 $TAGS{is_nullable} = "sqlfIsNullable";
58 $TAGS{required} = "sqlfRequired";
59 $TAGS{is_auto_increment} = "sqlfIsAutoIncrement";
60 $TAGS{default_value} = "sqlfDefaultValue";
64 return grep {$_->{kind} ne "return"} @$params;
68 my ($tag, $things) = @_;
70 return $_->{_map_taggedValues}{$tag}{dataValue}
71 if exists $_->{_map_taggedValues}{$tag}{dataValue};
78 my ($schema, $classes) = @_;
81 foreach my $class (@$classes) {
83 debug "Adding class: $class->{name}";
84 my $table = $schema->add_table( name => $class->{name} )
85 or die "Schema Error: ".$schema->error;
87 # Only collect the associations for classes that are tables. Use a hash
88 # so we only get them once
89 $associations{$_->{"xmi.id"}} = $_
90 foreach map $_->{association}, @{$class->{associationEnds}};
93 # Fields from Class attributes
96 push @flds, attr2field($_) for @{$class->{attributes}};
97 # TODO Filter this e.g no abstract attr or stereotype check
99 my $extra = delete $_->{extra};
100 my $field = $table->add_field( %$_ ) or die $schema->error;
101 $field->extra(%$extra) if $extra;
108 @pkeys = map $_->{name},
109 grep($_->{stereotype} eq "PK", @{$class->{attributes}});
110 # if none set with steretype, use first attrib
111 @pkeys = $class->{attributes}[0]{name} unless @pkeys;
112 $table->add_constraint(
113 type => "PRIMARY KEY",
115 ) or die $schema->error;
119 # Relationships from Associations
121 foreach my $assoc (values %associations) {
122 my @end = @{$assoc->{associationEnds}};
124 $end[0]->{multiplicity}{rangeUpper} == 1
125 && $end[1]->{multiplicity}{rangeUpper} == 1
128 warn "Sorry, 1:1 associations not yet implimented for xmi.id".$assoc->{"xmi.id"}."\n";
131 $end[0]->{multiplicity}{rangeUpper} == 1
132 || $end[1]->{multiplicity}{rangeUpper} == 1
135 one2many($schema,$assoc);
140 warn "Sorry, n:m associations not yet implimented for xmi.id".$assoc->{"xmi.id"}."\n";
149 my $dataType = $attr->{dataType};
151 my %data = ( name => $attr->{name} );
154 = _resolve_tag($TAGS{data_type},[$attr,$dataType])
155 || $dataType->{name};
157 $data{size} = _resolve_tag($TAGS{size},[$attr,$dataType]);
160 = $attr->{initialValue}
161 || _resolve_tag($TAGS{default_value},[$attr,$dataType]);
163 my $is_nullable = _resolve_tag($TAGS{is_nullable},[$attr,$dataType]);
164 my $required = _resolve_tag($TAGS{required},[$attr,$dataType]);
166 = defined $is_nullable ? $is_nullable
167 : ( defined $required ? ($required ? 0 : 1) : undef);
169 $data{is_auto_increment}
170 = $attr->{_map_taggedValues}{$TAGS{is_auto_increment}}{dataValue}
171 || $dataType->{_map_taggedValues}{$TAGS{is_auto_increment}}{dataValue}
178 foreach ( keys %{$attr->{_map_taggedValues}} ) {$tagnames{$_}++; }
179 delete @tagnames{@TAGS{qw/data_type size default_value is_nullable required is_auto_increment/}}; # Remove the tags we have already done
181 my $val = $attr->{_map_taggedValues}{$_}{dataValue};
185 $data{extra} = \%extra;
190 # Maps a 1:M association into the schema
192 my ($scma,$assoc) = @_;
193 my @ends = @{$assoc->{associationEnds}};
194 my ($end1) = grep $_->{multiplicity}{rangeUpper} == 1, @ends;
195 my $endm = $end1->{otherEnd};
196 my $table1 = $scma->get_table($end1->{participant}{name});
197 my $tablem = $scma->get_table($endm->{participant}{name});
200 # Export 1end pkey to many end
202 my $con = $table1->primary_key;
203 my @flds = $con->fields;
205 my $fld = $table1->get_field($_);
207 $data{$_} = $fld->$_()
208 foreach (qw/name size data_type default_value is_nullable/);
209 $data{extra} = { $fld->extra }; # Copy extra hash
210 $data{is_unique} = 0; # FKey on many join so not unique
211 $data{is_nullable} = $end1->{multiplicity}{rangeLower} == 0 ? 1 : 0;
212 # 0:m - allow nulluable on fkey
213 # 1:m - dont allow nullable
215 $tablem->add_field(%data) or die $scma->error;
216 # Export the pkey if full composite (ie identity) relationship
217 $tablem->primary_key($_) if $end1->{aggregation} eq "composite";
221 # Add fkey constraint to many end
223 $tablem->add_constraint(
224 type => "FOREIGN_KEY",
226 reference_table => $table1->{name},
227 reference_fields => [@flds],
228 ) or die $scma->error;
231 1; #---------------------------------------------------------------------------
240 use SQL::Translator::Parser::XML::XMI;
242 my $translator = SQL::Translator->new(
243 from => 'XML-XMI-SQLFairy',
245 filename => 'schema.xmi',
248 print $obj->translate;
252 Converts Class diagrams to Schema trying to use standard UML features as much
253 as possible, with the minimum use of extension mechanisms (tagged values and
254 stereotypes) for the database details. The idea is to treat the object model
255 like a logical database model and map that to a physical model (the sql). Also
256 tries to make this mapping as configurable as possible and support all the
257 schema features that SQLFairy does.
261 Classes, all of them! (TODO More control over which tables to do.)
267 Database datatypes are modeled using tagged values; sqlfDataType,
268 sqlfSize, sqlfIsNullable and sqlfIsAutoIncrement. These can be added either
269 to the UML datatype or directly on the attribute where they override the value
270 from the datatype. If no sqlfDataType is given then the name of the UMLDataType
271 is used. If no default value is found then the UML initialValue is used (even
272 if a tag is set on the UMLDataType - do we want to do it this way?.
276 Primary keys are attributes marked with <<PK>>. Add to multiple attribs to make
277 multi column keys. If none are marked will use the 1st attribute.
281 Modeled using UML associations. Currently only handles 0:m and 1:m joins. That
282 is associations where one ends multiplicty is '1' or '0..1' and the other end's
283 multplicity is '0..*' or '1..*' or >1 (e.g '0..3' '1..23' '4..42') etc.
285 The pkey from the 1 end is added to the table for the class at the many end as
286 a foreign key. is_unique is forced to false for the new field.
288 If the 1 end is multiplicity '0..1' (ie a 0:m join) then the the fkey is made
289 nullable, if its multiplicity '1' (1:m) then its made not nullable.
291 If the association is a composition then the created fkey is made part of the
292 many ends pkey. ie It exports the pkey to create an identity join.
302 Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
306 perl(1), SQL::Translator::Parser::XML::XMI