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