Mercurial > pub > Impl
diff Lib/Schema/DataSource/CDBIBuilder.pm @ 0:03e58a454b20
Создан репозитарий
author | Sergey |
---|---|
date | Tue, 14 Jul 2009 12:54:37 +0400 |
parents | |
children | 16ada169ca75 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/Schema/DataSource/CDBIBuilder.pm Tue Jul 14 12:54:37 2009 +0400 @@ -0,0 +1,326 @@ +use strict; +package Schema::DataSource::CDBIBuilder; +use Schema::DataSource::TypeMapping; +use Common; +our @ISA = qw(Object); + +BEGIN { + DeclareProperty ClassMappings => ACCESS_NONE; + DeclareProperty TypeMapping => ACCESS_READ; + DeclareProperty ValueTypeReflections => ACCESS_READ; +} + +sub CTOR { + my ($this,%args) = @_; + + $this->{$TypeMapping} = $args{'TypeMapping'} || Schema::DataSource::TypeMapping::Std->new; + $this->{$ValueTypeReflections} = { DateTime => 'DateTime'}; +} + +sub ReflectValueType { + my ($this,$Type) = @_; + return $this->{$ValueTypeReflections}{$Type->Name->Simple}; +} + +sub GetClassMapping { + my ($this,$type) = @_; + + if (my $mapping = $this->{$ClassMappings}->{$type->Name->Canonical}) { + return $mapping; + } else { + $mapping = new Schema::DataSource::CDBIBuilder::ClassMapping(Class => $type,Parent => $this); + $this->{$ClassMappings}{$type->Name->Canonical} = $mapping; + return $mapping + } +} + +sub EnumClassMappings { + my ($this) = @_; + return $this->{$ClassMappings} ? values %{$this->{$ClassMappings}} : (); +} + +sub AddType { + my ($this,$type) = @_; + $this->GetClassMapping($type); +} + +sub BuildDBSchema { + my ($this) = @_; + + my $schemaDB = new Schema::DB(Name => 'auto', Version => time); + + if ($this->{$ClassMappings}) { + $_->CreateTable($schemaDB) foreach values %{ $this->{$ClassMappings} }; + $_->CreateConstraints($schemaDB) foreach values %{ $this->{$ClassMappings} }; + } + + return $schemaDB; +} + +sub WriteModules { + my ($this,$fileName,$prefix) = @_; + + my $text; + $text = <<ModuleHeader; +#autogenerated script don't edit +package ${prefix}DBI; +use base 'Class::DBI'; + +require DateTime; + +our (\$DSN,\$User,\$Password,\$Init); +\$DSN ||= 'DBI:null'; # avoid warning + +__PACKAGE__->connection(\$DSN,\$User,\$Password); + +# initialize +foreach my \$action (ref \$Init eq 'ARRAY' ? \@{\$Init} : \$Init) { + next unless \$action; + + if (ref \$action eq 'CODE') { + \$action->(__PACKAGE__->db_Main); + } elsif (not ref \$action) { + __PACKAGE__->db_Main->do(\$action); + } +} + +ModuleHeader + + if ($this->{$ClassMappings}) { + $text .= join ("\n\n", map $_->GenerateText($prefix.'DBI',$prefix), sort {$a->Class->Name->Canonical cmp $b->Class->Name->Canonical } values %{ $this->{$ClassMappings} } ); + } + + $text .= "\n1;"; + + open my $out, ">$fileName" or die new Exception("Failed to open file",$fileName,$!); + print $out $text; +} + +sub Dispose { + my ($this) = @_; + + delete @$this{$ClassMappings,$TypeMapping,$ValueTypeReflections}; + + $this->SUPER::Dispose; +} + +package Schema::DataSource::CDBIBuilder::ClassMapping; +use Common; +use Schema; +our @ISA = qw(Object); + +BEGIN { + DeclareProperty Table => ACCESS_READ; + DeclareProperty PropertyTables => ACCESS_READ; + DeclareProperty PropertyMappings => ACCESS_READ; + + DeclareProperty Class => ACCESS_READ; + DeclareProperty Parent => ACCESS_NONE; +} + +sub CTOR { + my ($this,%args) = @_; + + $this->{$Class} = $args{'Class'} or die new Exception('The class must be specified'); + $this->{$Parent} = $args{'Parent'} or die new Exception('The parent must be specified'); + +} + +sub PropertyMapping { + my ($this,%args) = @_; + $this->{$PropertyMappings}{$args{'name'}} = { Column => $args{'Column'},DBType => $args{'DBType'} }; +} + +sub CreateTable { + my ($this,$schemaDB) = @_; + + return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set'; + + # CreateTable + my $table = $schemaDB->AddTable({Name => $this->{$Class}->Name->Canonical}); + $table->InsertColumn({ + Name => '_id', + Type => $this->{$Parent}->TypeMapping->DBIdentifierType, + Tag => ['AUTO_INCREMENT'] + }); + $table->SetPrimaryKey('_id'); + foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) { + if ($prop->Type->Name->Name eq 'Set') { + # special case for multiple values + my $propTable = $this->CreatePropertyTable($schemaDB,$prop); + $propTable->LinkTo($table,'parent'); + } else { + $table->InsertColumn({ + Name => $prop->Name, + Type => $this->{$Parent}->TypeMapping->MapType($prop->Type), + CanBeNull => 1 + }); + } + } + $this->{$Table} = $table; + return $table; +} + +sub CreatePropertyTable { + my ($this,$schemaDB,$property) = @_; + + my $table = $schemaDB->AddTable({Name => $this->{$Class}->Name->Canonical.'_'.$property->Name}); + $table->InsertColumn({ + Name => '_id', + Type => $this->{$Parent}->TypeMapping->DBIdentifierType, + Tag => ['AUTO_INCREMENT'] + }); + $table->SetPrimaryKey('_id'); + + $table->InsertColumn({ + Name => 'parent', + Type => $this->{$Parent}->TypeMapping->DBIdentifierType + }); + + $table->InsertColumn({ + Name => 'value', + Type => $this->{$Parent}->TypeMapping->MapType($property->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}), + CanBeNull => 1 + }); + + $this->{$PropertyTables}->{$property->Name} = $table; + + return $table; +} + +sub CreateConstraints { + my ($this,$schemaDB) = @_; + return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set'; + + foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) { + if ($prop->Type->Name->Name eq 'Set' ) { + # special case for multiple values + if (not $prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}->GetAttribute('ValueType')) { + $this->{$PropertyTables}->{$prop->Name}->LinkTo( + $this->{$Parent}->GetClassMapping($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'})->Table, + 'value' + ); + } + } elsif (not $prop->Type->GetAttribute('ValueType')) { + $this->{$Table}->LinkTo( + scalar($this->{$Parent}->GetClassMapping($prop->Type)->Table), + $prop->Name + ); + } + } +} + +sub GeneratePropertyTableText { + my ($this,$prop,$baseModule,$prefix) = @_; + + my $packageName = $this->GeneratePropertyClassName($prop,$prefix); + my $tableName = $this->{$PropertyTables}->{$prop->Name}->Name; + my $parentName = $this->GenerateClassName($prefix); + my $text .= "package $packageName;\n"; + $text .= "use base '$baseModule';\n\n"; + $text .= "__PACKAGE__->table('`$tableName`');\n"; + $text .= "__PACKAGE__->columns(Essential => qw/_id parent value/);\n"; + $text .= "__PACKAGE__->has_a( parent => '$parentName');\n"; + + my $typeValue; + if ($prop->Type->Name->Simple eq 'Set') { + $typeValue = $prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}; + } else { + $typeValue = $prop->Type; + } + if ($typeValue->GetAttribute('ValueType')) { + if (my $reflectedClass = $this->{$Parent}->ReflectValueType($typeValue)) { + $text .= "__PACKAGE__->has_a( value => '$reflectedClass');\n"; + } + } else { + my $foreignName = $this->{$Parent}->GetClassMapping($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'})->GenerateClassName($prefix); + $text .= "__PACKAGE__->has_a( value => '$foreignName');\n"; + } + + return $text; +} + +sub GeneratePropertyClassName { + my ($this,$prop,$prefix) = @_; + + my $packageName = $this->{$Class}->Name->Canonical; + $packageName =~ s/\W//g; + return $prefix.$packageName.$prop->Name.'Ref'; +} + +sub GenerateClassName { + my ($this,$prefix) = @_; + my $packageName = $this->{$Class}->Name->Canonical; + $packageName =~ s/\W//g; + return $prefix. $packageName; +} + +sub GenerateText { + my ($this,$baseModule,$prefix) = @_; + + return if $this->{$Class}->isTemplate or $this->{$Class}->GetAttribute('ValueType') or $this->{$Class}->Name->Simple eq 'Set'; + + my @PropertyModules; + my $text; + my $packageName = $this->GenerateClassName($prefix); + + my $tableName = $this->{$Table}->Name; + my $listColumns = join ',', map { '\''. $_->Name . '\''} $this->{$Table}->Columns; + + $text .= "package $packageName;\n"; + $text .= "use base '$baseModule'". ($this->{$Class}->Name->Name eq 'Map' ? ',\'CDBI::Map\'' : '' ).";\n\n"; + + $text .= "__PACKAGE__->table('`$tableName`');\n"; + $text .= "__PACKAGE__->columns(Essential => $listColumns);\n"; + + foreach my $prop ( grep { UNIVERSAL::isa($_,'Schema::Property') } $this->{$Class}->ListMembers ) { + my $propName = $prop->Name; + if ($prop->Type->Name->Name eq 'Set') { + # has_many + push @PropertyModules, $this->GeneratePropertyTableText($prop,$baseModule,$prefix); + my $propClass = $this->GeneratePropertyClassName($prop,$prefix); + $text .= <<ACCESSORS; +__PACKAGE__->has_many( ${propName}_ref => '$propClass'); +sub $propName { + return map { \$_->value } ${propName}_ref(\@_); +} +sub add_to_$propName { + return add_to_${propName}_ref(\@_); +} +ACCESSORS + + } elsif (not $prop->Type->GetAttribute('ValueType')) { + # has_a + my $ForeignClass = $this->{$Parent}->GetClassMapping($prop->Type)->GenerateClassName($prefix); + $text .= "__PACKAGE__->has_a( $propName => '$ForeignClass');\n"; + } else { + if (my $reflectedClass = $this->{$Parent}->ReflectValueType($prop->Type)) { + $text .= "__PACKAGE__->has_a( $propName => '$reflectedClass');\n"; + } + } + } + + # ñîçäàåì ñïèñîê äî÷åðíèõ êëàññîâ + foreach my $descedantMapping (grep {$_->{$Class}->isType($this->{$Class},1)} $this->{$Parent}->EnumClassMappings) { + next if $descedantMapping == $this; + $text .= "__PACKAGE__->might_have('m".$descedantMapping->GenerateClassName('')."' => '".$descedantMapping->GenerateClassName($prefix)."');\n"; + } + + # ñîçäàåì ññûëêè íà âñå êëàññû, êîòîðûå ìîãóò ññûëàòüñÿ íà íàø + # âèä ñâîéñòâà ññûëêè: refererClassProp + foreach my $referer (grep {not $_->Class->isTemplate} $this->{$Parent}->EnumClassMappings) { + next if $referer == $this; + foreach my $prop ( grep { $_->isa('Schema::Property') } $referer->{$Class}->ListMembers ) { + if($prop->Type->Equals($this->{$Class})) { + $text .= "__PACKAGE__->has_many('referer".$referer->GenerateClassName('').$prop->Name."' => '".$referer->GenerateClassName($prefix)."','".$prop->Name."');\n"; + } elsif ($prop->Type->Name->Name eq 'Set' and $this->{$Class}->Equals($prop->Type->GetAttribute('TemplateInstance')->{'Parameters'}{'T'}) ) { + # åñëè êëàññ áûë ïàðàìåòðîì ìíîæåñòâà è $prop->Type è åñòü ýòî ìíîæåñòâî + $text .= "__PACKAGE__->has_many('referer".$referer->GeneratePropertyClassName($prop,'')."value' => '".$referer->GeneratePropertyClassName($prop,$prefix)."','value');\n"; + } + } + } + + return (@PropertyModules,$text); +} + +1;