Mercurial > pub > Impl
comparison Lib/Schema/DataSource/CDBIBuilder.pm @ 0:03e58a454b20
Создан репозитарий
author | Sergey |
---|---|
date | Tue, 14 Jul 2009 12:54:37 +0400 |
parents | |
children | 16ada169ca75 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:03e58a454b20 |
---|---|
1 use strict; | |
2 package Schema::DataSource::CDBIBuilder; | |
3 use Schema::DataSource::TypeMapping; | |
4 use Common; | |
5 our @ISA = qw(Object); | |
6 | |
7 BEGIN { | |
8 DeclareProperty ClassMappings => ACCESS_NONE; | |
9 DeclareProperty TypeMapping => ACCESS_READ; | |
10 DeclareProperty ValueTypeReflections => ACCESS_READ; | |
11 } | |
12 | |
13 sub CTOR { | |
14 my ($this,%args) = @_; | |
15 | |
16 $this->{$TypeMapping} = $args{'TypeMapping'} || Schema::DataSource::TypeMapping::Std->new; | |
17 $this->{$ValueTypeReflections} = { DateTime => 'DateTime'}; | |
18 } | |
19 | |
20 sub ReflectValueType { | |
21 my ($this,$Type) = @_; | |
22 return $this->{$ValueTypeReflections}{$Type->Name->Simple}; | |
23 } | |
24 | |
25 sub GetClassMapping { | |
26 my ($this,$type) = @_; | |
27 | |
28 if (my $mapping = $this->{$ClassMappings}->{$type->Name->Canonical}) { | |
29 return $mapping; | |
30 } else { | |
31 $mapping = new Schema::DataSource::CDBIBuilder::ClassMapping(Class => $type,Parent => $this); | |
32 $this->{$ClassMappings}{$type->Name->Canonical} = $mapping; | |
33 return $mapping | |
34 } | |
35 } | |
36 | |
37 sub EnumClassMappings { | |
38 my ($this) = @_; | |
39 return $this->{$ClassMappings} ? values %{$this->{$ClassMappings}} : (); | |
40 } | |
41 | |
42 sub AddType { | |
43 my ($this,$type) = @_; | |
44 $this->GetClassMapping($type); | |
45 } | |
46 | |
47 sub BuildDBSchema { | |
48 my ($this) = @_; | |
49 | |
50 my $schemaDB = new Schema::DB(Name => 'auto', Version => time); | |
51 | |
52 if ($this->{$ClassMappings}) { | |
53 $_->CreateTable($schemaDB) foreach values %{ $this->{$ClassMappings} }; | |
54 $_->CreateConstraints($schemaDB) foreach values %{ $this->{$ClassMappings} }; | |
55 } | |
56 | |
57 return $schemaDB; | |
58 } | |
59 | |
60 sub WriteModules { | |
61 my ($this,$fileName,$prefix) = @_; | |
62 | |
63 my $text; | |
64 $text = <<ModuleHeader; | |
65 #autogenerated script don't edit | |
66 package ${prefix}DBI; | |
67 use base 'Class::DBI'; | |
68 | |
69 require DateTime; | |
70 | |
71 our (\$DSN,\$User,\$Password,\$Init); | |
72 \$DSN ||= 'DBI:null'; # avoid warning | |
73 | |
74 __PACKAGE__->connection(\$DSN,\$User,\$Password); | |
75 | |
76 # initialize | |
77 foreach my \$action (ref \$Init eq 'ARRAY' ? \@{\$Init} : \$Init) { | |
78 next unless \$action; | |
79 | |
80 if (ref \$action eq 'CODE') { | |
81 \$action->(__PACKAGE__->db_Main); | |
82 } elsif (not ref \$action) { | |
83 __PACKAGE__->db_Main->do(\$action); | |
84 } | |
85 } | |
86 | |
87 ModuleHeader | |
88 | |
89 if ($this->{$ClassMappings}) { | |
90 $text .= join ("\n\n", map $_->GenerateText($prefix.'DBI',$prefix), sort {$a->Class->Name->Canonical cmp $b->Class->Name->Canonical } values %{ $this->{$ClassMappings} } ); | |
91 } | |
92 | |
93 $text .= "\n1;"; | |
94 | |
95 open my $out, ">$fileName" or die new Exception("Failed to open file",$fileName,$!); | |
96 print $out $text; | |
97 } | |
98 | |
99 sub Dispose { | |
100 my ($this) = @_; | |
101 | |
102 delete @$this{$ClassMappings,$TypeMapping,$ValueTypeReflections}; | |
103 | |
104 $this->SUPER::Dispose; | |
105 } | |
106 | |
107 package Schema::DataSource::CDBIBuilder::ClassMapping; | |
108 use Common; | |
109 use Schema; | |
110 our @ISA = qw(Object); | |
111 | |
112 BEGIN { | |
113 DeclareProperty Table => ACCESS_READ; | |
114 DeclareProperty PropertyTables => ACCESS_READ; | |
115 DeclareProperty PropertyMappings => ACCESS_READ; | |
116 | |
117 DeclareProperty Class => ACCESS_READ; | |
118 DeclareProperty Parent => ACCESS_NONE; | |
119 } | |
120 | |
121 sub CTOR { | |
122 my ($this,%args) = @_; | |
123 | |
124 $this->{$Class} = $args{'Class'} or die new Exception('The class must be specified'); | |
125 $this->{$Parent} = $args{'Parent'} or die new Exception('The parent must be specified'); | |
126 | |
127 } | |
128 | |
129 sub PropertyMapping { | |
130 my ($this,%args) = @_; | |
131 $this->{$PropertyMappings}{$args{'name'}} = { Column => $args{'Column'},DBType => $args{'DBType'} }; | |
132 } | |
133 | |
134 sub CreateTable { | |
135 my ($this,$schemaDB) = @_; | |
136 | |
137 return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set'; | |
138 | |
139 # CreateTable | |
140 my $table = $schemaDB->AddTable({Name => $this->{$Class}->Name->Canonical}); | |
141 $table->InsertColumn({ | |
142 Name => '_id', | |
143 Type => $this->{$Parent}->TypeMapping->DBIdentifierType, | |
144 Tag => ['AUTO_INCREMENT'] | |
145 }); | |
146 $table->SetPrimaryKey('_id'); | |
147 foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) { | |
148 if ($prop->Type->Name->Name eq 'Set') { | |
149 # special case for multiple values | |
150 my $propTable = $this->CreatePropertyTable($schemaDB,$prop); | |
151 $propTable->LinkTo($table,'parent'); | |
152 } else { | |
153 $table->InsertColumn({ | |
154 Name => $prop->Name, | |
155 Type => $this->{$Parent}->TypeMapping->MapType($prop->Type), | |
156 CanBeNull => 1 | |
157 }); | |
158 } | |
159 } | |
160 $this->{$Table} = $table; | |
161 return $table; | |
162 } | |
163 | |
164 sub CreatePropertyTable { | |
165 my ($this,$schemaDB,$property) = @_; | |
166 | |
167 my $table = $schemaDB->AddTable({Name => $this->{$Class}->Name->Canonical.'_'.$property->Name}); | |
168 $table->InsertColumn({ | |
169 Name => '_id', | |
170 Type => $this->{$Parent}->TypeMapping->DBIdentifierType, | |
171 Tag => ['AUTO_INCREMENT'] | |
172 }); | |
173 $table->SetPrimaryKey('_id'); | |
174 | |
175 $table->InsertColumn({ | |
176 Name => 'parent', | |
177 Type => $this->{$Parent}->TypeMapping->DBIdentifierType | |
178 }); | |
179 | |
180 $table->InsertColumn({ | |
181 Name => 'value', | |
182 Type => $this->{$Parent}->TypeMapping->MapType($property->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}), | |
183 CanBeNull => 1 | |
184 }); | |
185 | |
186 $this->{$PropertyTables}->{$property->Name} = $table; | |
187 | |
188 return $table; | |
189 } | |
190 | |
191 sub CreateConstraints { | |
192 my ($this,$schemaDB) = @_; | |
193 return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set'; | |
194 | |
195 foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) { | |
196 if ($prop->Type->Name->Name eq 'Set' ) { | |
197 # special case for multiple values | |
198 if (not $prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}->GetAttribute('ValueType')) { | |
199 $this->{$PropertyTables}->{$prop->Name}->LinkTo( | |
200 $this->{$Parent}->GetClassMapping($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'})->Table, | |
201 'value' | |
202 ); | |
203 } | |
204 } elsif (not $prop->Type->GetAttribute('ValueType')) { | |
205 $this->{$Table}->LinkTo( | |
206 scalar($this->{$Parent}->GetClassMapping($prop->Type)->Table), | |
207 $prop->Name | |
208 ); | |
209 } | |
210 } | |
211 } | |
212 | |
213 sub GeneratePropertyTableText { | |
214 my ($this,$prop,$baseModule,$prefix) = @_; | |
215 | |
216 my $packageName = $this->GeneratePropertyClassName($prop,$prefix); | |
217 my $tableName = $this->{$PropertyTables}->{$prop->Name}->Name; | |
218 my $parentName = $this->GenerateClassName($prefix); | |
219 my $text .= "package $packageName;\n"; | |
220 $text .= "use base '$baseModule';\n\n"; | |
221 $text .= "__PACKAGE__->table('`$tableName`');\n"; | |
222 $text .= "__PACKAGE__->columns(Essential => qw/_id parent value/);\n"; | |
223 $text .= "__PACKAGE__->has_a( parent => '$parentName');\n"; | |
224 | |
225 my $typeValue; | |
226 if ($prop->Type->Name->Simple eq 'Set') { | |
227 $typeValue = $prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}; | |
228 } else { | |
229 $typeValue = $prop->Type; | |
230 } | |
231 if ($typeValue->GetAttribute('ValueType')) { | |
232 if (my $reflectedClass = $this->{$Parent}->ReflectValueType($typeValue)) { | |
233 $text .= "__PACKAGE__->has_a( value => '$reflectedClass');\n"; | |
234 } | |
235 } else { | |
236 my $foreignName = $this->{$Parent}->GetClassMapping($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'})->GenerateClassName($prefix); | |
237 $text .= "__PACKAGE__->has_a( value => '$foreignName');\n"; | |
238 } | |
239 | |
240 return $text; | |
241 } | |
242 | |
243 sub GeneratePropertyClassName { | |
244 my ($this,$prop,$prefix) = @_; | |
245 | |
246 my $packageName = $this->{$Class}->Name->Canonical; | |
247 $packageName =~ s/\W//g; | |
248 return $prefix.$packageName.$prop->Name.'Ref'; | |
249 } | |
250 | |
251 sub GenerateClassName { | |
252 my ($this,$prefix) = @_; | |
253 my $packageName = $this->{$Class}->Name->Canonical; | |
254 $packageName =~ s/\W//g; | |
255 return $prefix. $packageName; | |
256 } | |
257 | |
258 sub GenerateText { | |
259 my ($this,$baseModule,$prefix) = @_; | |
260 | |
261 return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set'; | |
262 | |
263 my @PropertyModules; | |
264 my $text; | |
265 my $packageName = $this->GenerateClassName($prefix); | |
266 | |
267 my $tableName = $this->{$Table}->Name; | |
268 my $listColumns = join ',', map { '\''. $_->Name . '\''} $this->{$Table}->Columns; | |
269 | |
270 $text .= "package $packageName;\n"; | |
271 $text .= "use base '$baseModule'". ($this->{$Class}->Name->Name eq 'Map' ? ',\'CDBI::Map\'' : '' ).";\n\n"; | |
272 | |
273 $text .= "__PACKAGE__->table('`$tableName`');\n"; | |
274 $text .= "__PACKAGE__->columns(Essential => $listColumns);\n"; | |
275 | |
276 foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) { | |
277 my $propName = $prop->Name; | |
278 if ($prop->Type->Name->Name eq 'Set') { | |
279 # has_many | |
280 push @PropertyModules, $this->GeneratePropertyTableText($prop,$baseModule,$prefix); | |
281 my $propClass = $this->GeneratePropertyClassName($prop,$prefix); | |
282 $text .= <<ACCESSORS; | |
283 __PACKAGE__->has_many( ${propName}_ref => '$propClass'); | |
284 sub $propName { | |
285 return map { \$_->value } ${propName}_ref(\@_); | |
286 } | |
287 sub add_to_$propName { | |
288 return add_to_${propName}_ref(\@_); | |
289 } | |
290 ACCESSORS | |
291 | |
292 } elsif (not $prop->Type->GetAttribute('ValueType')) { | |
293 # has_a | |
294 my $ForeignClass = $this->{$Parent}->GetClassMapping($prop->Type)->GenerateClassName($prefix); | |
295 $text .= "__PACKAGE__->has_a( $propName => '$ForeignClass');\n"; | |
296 } else { | |
297 if (my $reflectedClass = $this->{$Parent}->ReflectValueType($prop->Type)) { | |
298 $text .= "__PACKAGE__->has_a( $propName => '$reflectedClass');\n"; | |
299 } | |
300 } | |
301 } | |
302 | |
303 # ñîçäàåì ñïèñîê äî÷åðíèõ êëàññîâ | |
304 foreach my $descedantMapping (grep {$_->{$Class}->isType($this->{$Class},1)} $this->{$Parent}->EnumClassMappings) { | |
305 next if $descedantMapping == $this; | |
306 $text .= "__PACKAGE__->might_have('m".$descedantMapping->GenerateClassName('')."' => '".$descedantMapping->GenerateClassName($prefix)."');\n"; | |
307 } | |
308 | |
309 # ñîçäàåì ññûëêè íà âñå êëàññû, êîòîðûå ìîãóò ññûëàòüñÿ íà íàø | |
310 # âèä ñâîéñòâà ññûëêè: refererClassProp | |
311 foreach my $referer (grep {not $_->Class->isTemplate} $this->{$Parent}->EnumClassMappings) { | |
312 next if $referer == $this; | |
313 foreach my $prop ( grep { $_->isa('Schema::Property') } $referer->{$Class}->ListMembers ) { | |
314 if($prop->Type->Equals($this->{$Class})) { | |
315 $text .= "__PACKAGE__->has_many('referer".$referer->GenerateClassName('').$prop->Name."' => '".$referer->GenerateClassName($prefix)."','".$prop->Name."');\n"; | |
316 } elsif ($prop->Type->Name->Name eq 'Set' and $this->{$Class}->Equals($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}) ) { | |
317 # åñëè êëàññ áûë ïàðàìåòðîì ìíîæåñòâà è $prop->Type è åñòü ýòî ìíîæåñòâî | |
318 $text .= "__PACKAGE__->has_many('referer".$referer->GeneratePropertyClassName($prop,'')."value' => '".$referer->GeneratePropertyClassName($prop,$prefix)."','value');\n"; | |
319 } | |
320 } | |
321 } | |
322 | |
323 return (@PropertyModules,$text); | |
324 } | |
325 | |
326 1; |