49
|
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;
|