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;