Initial code for SQLFairy UML profile for the XMI parser. The name may need
[dbsrgits/SQL-Translator.git] / lib / SQL / Translator / Parser / XML / XMI / SQLFairy.pm
CommitLineData
55e462b4 1package SQL::Translator::Parser::XML::XMI::SQLFairy;
2
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>,
7#
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.
11#
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.
16#
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
20# 02111-1307 USA
21# -------------------------------------------------------------------
22
23=head1 NAME
24
25SQL::Translator::Parser::XML::XMI::SQLFairy - Create Schema from UML Models.
26
27=cut
28
29use strict;
30
31use vars qw[ $DEBUG $VERSION @EXPORT_OK ];
32$VERSION = sprintf "%d.%02d", q$Revision: 1.1 $ =~ /(\d+)\.(\d+)/;
33$DEBUG = 0 unless defined $DEBUG;
34use Exporter;
35use base qw(Exporter);
36@EXPORT_OK = qw(parse);
37
38use Data::Dumper;
39use SQL::Translator::Parser::XML::XMI;
40use SQL::Translator::Utils 'debug';
41
42# Set the parg for the conversion sub then use the XMI parser
43sub parse {
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(@_);
49}
50
51
52
53# TODO We could make the tag names a parser arg so people can use their own.
54my %TAGS;
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";
61
62sub _parameters_in {
63 my $params = shift;
64 return grep {$_->{kind} ne "return"} @$params;
65}
66
67sub _resolve_tag {
68 my ($tag, $things) = @_;
69 foreach (@$things) {
70 return $_->{_map_taggedValues}{$tag}{dataValue}
71 if exists $_->{_map_taggedValues}{$tag}{dataValue};
72 }
73 return;
74}
75
76
77sub classes2schema {
78 my ($schema, $classes) = @_;
79
80 my %associations;
81 foreach my $class (@$classes) {
82 # Add the table
83 debug "Adding class: $class->{name}";
84 my $table = $schema->add_table( name => $class->{name} )
85 or die "Schema Error: ".$schema->error;
86
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}};
91
92 #
93 # Fields from Class attributes
94 #
95 my @flds;
96 push @flds, attr2field($_) for @{$class->{attributes}};
97 # TODO Filter this e.g no abstract attr or stereotype check
98 foreach (@flds) {
99 my $extra = delete $_->{extra};
100 my $field = $table->add_field( %$_ ) or die $schema->error;
101 $field->extra(%$extra) if $extra;
102 }
103
104 #
105 # Primary key
106 #
107 my @pkeys;
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",
114 fields => [@pkeys],
115 ) or die $schema->error;
116 }
117
118 #
119 # Relationships from Associations
120 #
121 foreach my $assoc (values %associations) {
122 my @end = @{$assoc->{associationEnds}};
123 if (
124 $end[0]->{multiplicity}{rangeUpper} == 1
125 && $end[1]->{multiplicity}{rangeUpper} == 1
126 ) {
127 # 1:1 or 0:1
128 warn "Sorry, 1:1 associations not yet implimented for xmi.id".$assoc->{"xmi.id"}."\n";
129 }
130 elsif (
131 $end[0]->{multiplicity}{rangeUpper} == 1
132 || $end[1]->{multiplicity}{rangeUpper} == 1
133 ) {
134 # 1:m or 0:m
135 one2many($schema,$assoc);
136 }
137 else
138 {
139 # m:n
140 warn "Sorry, n:m associations not yet implimented for xmi.id".$assoc->{"xmi.id"}."\n";
141 }
142
143 }
144
145}
146
147sub attr2field {
148 my $attr = shift;
149 my $dataType = $attr->{dataType};
150
151 my %data = ( name => $attr->{name} );
152
153 $data{data_type}
154 = _resolve_tag($TAGS{data_type},[$attr,$dataType])
155 || $dataType->{name};
156
157 $data{size} = _resolve_tag($TAGS{size},[$attr,$dataType]);
158
159 $data{default_value}
160 = $attr->{initialValue}
161 || _resolve_tag($TAGS{default_value},[$attr,$dataType]);
162
163 my $is_nullable = _resolve_tag($TAGS{is_nullable},[$attr,$dataType]);
164 my $required = _resolve_tag($TAGS{required},[$attr,$dataType]);
165 $data{is_nullable}
166 = defined $is_nullable ? $is_nullable
167 : ( defined $required ? ($required ? 0 : 1) : undef);
168
169 $data{is_auto_increment}
170 = $attr->{_map_taggedValues}{$TAGS{is_auto_increment}}{dataValue}
171 || $dataType->{_map_taggedValues}{$TAGS{is_auto_increment}}{dataValue}
172 || undef;
173
174 #
175 # Extras
176 #
177 my %tagnames;
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
180 my %extra = map {
181 my $val = $attr->{_map_taggedValues}{$_}{dataValue};
182 s/^sqlf//;
183 ($_,$val);
184 } keys %tagnames;
185 $data{extra} = \%extra;
186
187 return \%data;
188}
189
190# Maps a 1:M association into the schema
191sub one2many {
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});
198
199 #
200 # Export 1end pkey to many end
201 #
202 my $con = $table1->primary_key;
203 my @flds = $con->fields;
204 foreach (@flds) {
205 my $fld = $table1->get_field($_);
206 my %data;
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
214
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";
218 }
219
220 #
221 # Add fkey constraint to many end
222 #
223 $tablem->add_constraint(
224 type => "FOREIGN_KEY",
225 fields => [@flds],
226 reference_table => $table1->{name},
227 reference_fields => [@flds],
228 ) or die $scma->error;
229}
230
2311; #---------------------------------------------------------------------------
232
233__END__
234
235=pod
236
237=head1 SYNOPSIS
238
239 use SQL::Translator;
240 use SQL::Translator::Parser::XML::XMI;
241
242 my $translator = SQL::Translator->new(
243 from => 'XML-XMI-SQLFairy',
244 to => 'MySQL',
245 filename => 'schema.xmi',
246 );
247
248 print $obj->translate;
249
250=head1 DESCRIPTION
251
252Converts Class diagrams to Schema trying to use standard UML features as much
253as possible, with the minimum use of extension mechanisms (tagged values and
254stereotypes) for the database details. The idea is to treat the object model
255like a logical database model and map that to a physical model (the sql). Also
256tries to make this mapping as configurable as possible and support all the
257schema features that SQLFairy does.
258
259=head2 Tables
260
261Classes, all of them! (TODO More control over which tables to do.)
262
263=head2 Fields
264
265=head3 Datatypes
266
267Database datatypes are modeled using tagged values; sqlfDataType,
268sqlfSize, sqlfIsNullable and sqlfIsAutoIncrement. These can be added either
269to the UML datatype or directly on the attribute where they override the value
270from the datatype. If no sqlfDataType is given then the name of the UMLDataType
271is used. If no default value is found then the UML initialValue is used (even
272if a tag is set on the UMLDataType - do we want to do it this way?.
273
274=head3 Primary Keys
275
276Primary keys are attributes marked with <<PK>>. Add to multiple attribs to make
277multi column keys. If none are marked will use the 1st attribute.
278
279=head2 Relationships
280
281Modeled using UML associations. Currently only handles 0:m and 1:m joins. That
282is associations where one ends multiplicty is '1' or '0..1' and the other end's
283multplicity is '0..*' or '1..*' or >1 (e.g '0..3' '1..23' '4..42') etc.
284
285The pkey from the 1 end is added to the table for the class at the many end as
286a foreign key. is_unique is forced to false for the new field.
287
288If the 1 end is multiplicity '0..1' (ie a 0:m join) then the the fkey is made
289nullable, if its multiplicity '1' (1:m) then its made not nullable.
290
291If the association is a composition then the created fkey is made part of the
292many ends pkey. ie It exports the pkey to create an identity join.
293
294=head1 ARGS
295
296=head1 BUGS
297
298=head1 TODO
299
300=head1 AUTHOR
301
302Mark D. Addison E<lt>mark.addison@itn.co.ukE<gt>.
303
304=head1 SEE ALSO
305
306perl(1), SQL::Translator::Parser::XML::XMI
307
308=cut