1 package SQL::Translator::Parser::XML::XMI::SQLFairy;
3 # -------------------------------------------------------------------
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 @EXPORT_OK ];
32 $DEBUG = 0 unless defined $DEBUG;
34 use base qw(Exporter);
35 @EXPORT_OK = qw(parse);
38 use SQL::Translator::Parser::XML::XMI;
39 use SQL::Translator::Utils 'debug';
41 # Globals for the subs to use, set in parse() and classes2schema()
43 # TODO Should we be giving classes2schema the schema or should they use their
44 # parse() to get it. Obj parsers maybe?
45 #our ($schema,$pargs);
46 use vars qw[ $schema $pargs ];
48 # Set the parg for the conversion sub then use the XMI parser
50 my ( $translator ) = @_;
51 local $DEBUG = $translator->debug;
52 local $pargs = $translator->parser_args;
53 #local $schema = $translator->schema;
54 $pargs->{classes2schema} = \&classes2schema;
55 $pargs->{derive_pkey} ||= "stereotype,auto,first";
56 $pargs->{auto_pkey} ||= {
64 is_auto_increment => 1,
67 return SQL::Translator::Parser::XML::XMI::parse(@_);
72 # TODO We could make the tag names a parser arg so people can use their own.
74 $TAGS{data_type} = "sqlfDataType";
75 $TAGS{size} = "sqlfSize";
76 $TAGS{is_nullable} = "sqlfIsNullable";
77 $TAGS{required} = "sqlfRequired";
78 $TAGS{is_auto_increment} = "sqlfIsAutoIncrement";
79 $TAGS{default_value} = "sqlfDefaultValue";
83 return grep {$_->{kind} ne "return"} @$params;
87 my ($tag, $things) = @_;
89 return $_->{_map_taggedValues}{$tag}{dataValue}
90 if exists $_->{_map_taggedValues}{$tag}{dataValue};
97 local $schema = shift;
101 # Create tablles from Classes and collect their associations
104 foreach my $class (@$classes) {
106 debug "Adding class: $class->{name}";
107 my $table = $schema->add_table( name => $class->{name} )
108 or die "Schema Error: ".$schema->error;
110 # Only collect the associations for classes that are tables. Use a hash
111 # so we only get them once
112 $associations{$_->{"xmi.id"}} = $_
113 foreach map $_->{association}, @{$class->{associationEnds}};
116 # Fields from Class attributes
119 push @flds, attr2field($_) for @{$class->{attributes}};
120 # TODO Filter this e.g no abstract attr or stereotype check
122 my $extra = delete $_->{extra};
123 my $field = $table->add_field( %$_ ) or die $schema->error;
124 $field->extra(%$extra) if $extra;
128 add_pkey($class,$table);
132 # Relationships from Associations
134 foreach my $assoc (values %associations) {
135 my @end = @{$assoc->{associationEnds}};
137 $end[0]->{multiplicity}{rangeUpper} == 1
138 && $end[1]->{multiplicity}{rangeUpper} == 1
141 warn "Sorry, 1:1 associations not yet implimented for xmi.id=".$assoc->{"xmi.id"}."\n";
144 $end[0]->{multiplicity}{rangeUpper} == 1
145 || $end[1]->{multiplicity}{rangeUpper} == 1
158 # Take an attribute and return the field data for it
161 my $dataType = $attr->{dataType};
163 my %data = ( name => $attr->{name} );
166 = _resolve_tag($TAGS{data_type},[$attr,$dataType])
167 || $dataType->{name};
169 $data{size} = _resolve_tag($TAGS{size},[$attr,$dataType]);
172 = _resolve_tag($TAGS{default_value},[$attr,$dataType])
173 || $attr->{initialValue};
175 my $is_nullable = _resolve_tag($TAGS{is_nullable},[$attr,$dataType]);
176 my $required = _resolve_tag($TAGS{required},[$attr,$dataType]);
178 = defined $is_nullable ? $is_nullable
179 : ( defined $required ? ($required ? 0 : 1) : undef);
181 $data{is_auto_increment}
182 = $attr->{_map_taggedValues}{$TAGS{is_auto_increment}}{dataValue}
183 || $dataType->{_map_taggedValues}{$TAGS{is_auto_increment}}{dataValue}
190 foreach ( keys %{$attr->{_map_taggedValues}} ) {$tagnames{$_}++; }
191 delete @tagnames{@TAGS{qw/data_type size default_value is_nullable required is_auto_increment/}}; # Remove the tags we have already done
193 my $val = $attr->{_map_taggedValues}{$_}{dataValue};
197 $data{extra} = \%extra;
202 # Add a pkey to a table for the class
204 my ($class,$table) = @_;
207 foreach ( split(",", $pargs->{derive_pkey}) ) {
208 if ( $_ eq "stereotype" ) {
209 @pkeys = map $_->{name},
210 grep($_->{stereotype} eq "PK", @{$class->{attributes}});
212 elsif( $_ eq "first" ) {
213 @pkeys = $class->{attributes}[0]{name} unless @pkeys;
215 elsif( $_ eq "auto" ) {
216 if ( my %data = %{$pargs->{auto_pkey}} ) {
217 $data{name} = $data{name}->($class,$table);
218 my $field = $table->add_field(%data) or die $table->error;
219 @pkeys = $field->name;
225 $table->add_constraint(
226 type => "PRIMARY KEY",
228 ) or die $table->error;
231 # Maps a 1:M association into the schema
235 my @ends = @{$assoc->{associationEnds}};
236 my ($end1) = grep $_->{multiplicity}{rangeUpper} == 1, @ends;
237 my $endm = $end1->{otherEnd};
238 my $table1 = $schema->get_table($end1->{participant}{name});
239 my $tablem = $schema->get_table($endm->{participant}{name});
242 # Export 1end pkey to many end
244 my $con = $table1->primary_key;
245 my @flds = $con->fields;
247 my $fld = $table1->get_field($_);
249 $data{$_} = $fld->$_()
250 foreach (qw/name size data_type default_value is_nullable/);
251 $data{extra} = { $fld->extra }; # Copy extra hash
252 $data{is_unique} = 0; # FKey on many join so not unique
253 $data{is_nullable} = $end1->{multiplicity}{rangeLower} == 0 ? 1 : 0;
254 # 0:m - allow nulluable on fkey
255 # 1:m - dont allow nullable
257 $tablem->add_field(%data) or die $tablem->error;
258 # Export the pkey if full composite (ie identity) relationship
259 $tablem->primary_key($_) if $end1->{aggregation} eq "composite";
263 # Add fkey constraint to many end
265 $tablem->add_constraint(
266 type => "FOREIGN_KEY",
268 reference_table => $table1->{name},
269 reference_fields => [@flds],
270 ) or die $schema->error;
273 # Maps m:n into schema by building a link table.
277 my @end = @{$assoc->{associationEnds}};
279 # Create the link table
280 my $name = $end[0]->{participant}{name}."_".$end[1]->{participant}{name};
281 my $link_table = $schema->add_table( name => $name )
282 or die "Schema Error: ".$schema->error;
284 # Export the pkey(s) from the ends into the link table
287 my $table = $schema->get_table($_->{participant}{name});
288 my @fkeys = $table->primary_key->fields;
291 my $fld = $table->get_field($_);
293 $data{$_} = $fld->$_()
295 qw/name size data_type default_value is_nullable is_unique/);
296 $data{is_auto_increment} = 0;
297 $data{extra} = { $fld->extra }; # Copy
298 $link_table->add_field(%data) or die $table->error;
300 $link_table->add_constraint(
301 type => "FOREIGN_KEY",
303 reference_table => $table->{name},
304 reference_fields => [@fkeys],
305 ) or die $schema->error;
308 # Add pkey constraint
309 $link_table->add_constraint( type => "PRIMARY KEY", fields => [@pkeys] )
310 or die $link_table->error;
313 # Add fkeys to our participants
315 1; #---------------------------------------------------------------------------
324 use SQL::Translator::Parser::XML::XMI;
326 my $translator = SQL::Translator->new(
327 from => 'XML-XMI-SQLFairy',
329 filename => 'schema.xmi',
332 print $obj->translate;
336 Converts Class diagrams to Schema trying to use standard UML features as much
337 as possible, with the minimum use of extension mechanisms (tagged values and
338 stereotypes) for the database details. The idea is to treat the object model
339 like a logical database model and map that to a physical model (the sql). Also
340 tries to make this mapping as configurable as possible and support all the
341 schema features of SQLFairy.
345 Classes, all of them! (TODO More control over which tables to do.)
349 The attributes of the class will be converted to fields of the same name.
353 Database datatypes are modeled using tagged values; sqlfDataType,
354 sqlfSize, sqlfIsNullable and sqlfIsAutoIncrement added to the attribute.
355 The default value is the UML initial value of the attribute or can be overridden
356 using a sqlfDefaultValue tagged value if you want to have a different default
357 in the database then the object uses.
359 For more advanced datatype modeling you can use UML data types by adding the
360 tagged values to the UML data types in your model and then giving your
361 attributes those datatypes. Any tagged values set on attributes will override
362 any they get from their datatype. This allows you to use UML datatypes like
363 domains. If no sqlfDataType is given then the name of the UMLDataType is used.
367 If no attribute is marked explicity on the Class as a pkey then one is added.
368 The default is an INT(10) auto number named after the class with ID on the end.
369 For many cases this is enough as you don't normally need to model pkeys
370 explicitly in your object models as its a database thing.
372 The pkey created can be controlled by setting the C<auto_pkey> parser arg to a
373 hash ref describing the field. The name key is a sub that gets given a ref to
374 the class (from the xmi) and the table it has been mapped to, and should return the pkey name. e.g. the defualt looks like;
384 is_auto_increment => 1,
387 NB You need to return a unique name for the key if it will be used to build
388 relationships as it will be exported to other tables (see Relationships).
390 You can also set them explicitly by marking attributes with a <<PK>> stereotype.
391 Add to multiple attribs to make multi column keys. Usefull when your object
392 contains an attribute that makes a good candidate for a pkey, e.g. email.
398 Associations where one ends multiplicty is '1' or '0..1' and the other end's
399 multplicity is more than 1 e.g '*', '0..*', '1..*', '0..3', '4..42' etc.
401 The pkey field from the 1 end is added to the table for the class at the many
402 end as a foreign key with is_unique and auto number turned off.
404 If the 1 end is multiplicity '0..1' (ie a 0:m join) then the the fkey is made
405 nullable, if its multiplicity '1' (1:m) then its made not nullable.
407 If the association is a composition then the created fkey is made part of the
408 many ends pkey. ie It exports the pkey to create an identity join.
412 Model using a standard m:n association and the parser will automatically create
413 a link table for you in the Schema by exporting pkeys from the tables at
418 TODO An example to help make sense of the above! Probably based on the test.
428 Use Role names from associations as field names for exported keys when building
433 Support for the format_X_name subs in the Translator and format subs for
434 generating the link table name in m:n joins.
440 Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
444 perl(1), SQL::Translator::Parser::XML::XMI