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;