# HG changeset patch # User Sergey # Date 1257719971 -10800 # Node ID 56cef8e3cda61d1612ff630f8a80a106136c1456 # Parent d59526f6310edbfdc2aa3eae33a67646028a248a +1 diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,94 @@ +use strict; +package IMPL::SQL::Schema; + +use base qw(IMPL::Object IMPL::Object::Disposable); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +require IMPL::SQL::Schema::Table; + +BEGIN { + public _direct property Version => prop_get; + public _direct property Name => prop_get; + public _direct property Tables => prop_get; +} + +sub AddTable { + my ($this,$table) = @_; + + if (UNIVERSAL::isa($table,'IMPL::SQL::Schema::Table')) { + $table->Schema == $this or die new IMPL::InvalidOperationException('The specified table must belong to the database'); + not exists $this->{$Tables}->{$table->Name} or die new IMPL::InvalidOperationException('a table with the same name already exists in the database'); + } elsif (UNIVERSAL::isa($table,'HASH')) { + not exists $this->{$Tables}->{$table->{'Name'}} or die new IMPL::InvalidOperationException('a table with the same name already exists in the database'); + $table->{'Schema'} = $this; + $table = new IMPL::SQL::Schema::Table(%{$table}); + } else { + die new IMPL::InvalidArgumentException('Either a table object or a hash with table parameters is required'); + } + + $this->{$Tables}{$table->Name} = $table; +} + +sub RemoveTable { + my ($this,$table) = @_; + + my $tn = UNIVERSAL::isa($table,'IMPL::SQL::Schema::Table') ? $table->Name : $table; + $table = delete $this->{$Tables}{$tn} or die new IMPL::InvalidArgumentException('The table doesn\'t exists',$tn); + + # drop foreign keys + map { $_->Table->RemoveConstraint($_) } values %{$table->PrimaryKey->ConnectedFK} if $table->PrimaryKey; + + # drop table contents + $table->Dispose(); + + return 1; +} + +sub Dispose { + my ($this) = @_; + + $_->Dispose foreach values %{$this->{$Tables}}; + + delete $this->{$Tables}; + + $this->SUPER::Dispose; +} + +1; + +__END__ +=pod + +=head1 SINOPSYS + +require IMPL::SQL::Schema; +use IMPL::SQL::Types qw(Varchar Integer); + +my $dbSchema = new IMPL::SQL::Schema; + +my $tbl = $dbSchema->AddTable({Name => 'Person' }); +$tbl->AddColumn({ + Name => 'FirstName', + CanBeNull => 1, + Type => Varchar(255) +}); +$tbl->AddColumn({ + Name => 'Age', + Type => Integer +}); + +# so on + +# and finally don't forget to + +$dbSchema->Dispoce(); + +=head1 DESCRIPTION + +Схема реляциоонной базы данных, орентированная на язык SQL, содержит описания таблиц +которые являются частью базы. Позволяет создавать и удалать таблицы. + +Имея две схемы можно создавать скрипты для примениения изменений схемы данных C<> + +=cut diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Column.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Column.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,60 @@ +use strict; +package IMPL::SQL::Schema::Column; +use base qw(IMPL::Object IMPL::Object::Autofill); + +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property Name => prop_get; + public _direct property Type => prop_get; + public _direct property CanBeNull => prop_get; + public _direct property DefaultValue => prop_get; + public _direct property Tag => prop_get; +} + +__PACKAGE__->PassThroughArgs; + +sub CTOR { + my $this = shift; + + $this->{$Name} or die new IMPL::InvalidArgumentException('a column name is required'); + $this->{$CanBeNull} = 0 if not exists $this->{$CanBeNull}; + UNIVERSAL::isa($this->{$Type},'IMPL::SQL::Schema::Type') or die new IMPL::InvalidArgumentException('a type is required for the column',$this->{$Name}); +} + +sub isEqualsStr { + my ($a,$b) = @_; + + if (defined $a and defined $b) { + return $a eq $b; + } else { + if (defined $a or defined $b) { + return 0; + } else { + return 1; + } + } +} + +sub isEquals { + my ($a,$b) = @_; + + if (defined $a and defined $b) { + return $a == $b; + } else { + if (defined $a or defined $b) { + return 0; + } else { + return 1; + } + } +} + +sub isSame { + my ($this,$other) = @_; + + return ($this->{$Name} eq $other->{$Name} and $this->{$CanBeNull} == $other->{$CanBeNull} and isEqualsStr($this->{$DefaultValue}, $other->{$DefaultValue}) and $this->{$Type}->isSame($other->{$Type})); +} + +1; diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Constraint.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Constraint.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,51 @@ +use strict; +package IMPL::SQL::Schema::Constraint; +use base qw(IMPL::Object IMPL::Object::Disposable); + +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property Name => prop_get; + public _direct property Table => prop_get; + public _direct property Columns => prop_get; +} + +sub CTOR { + my ($this,%args) = @_; + die new IMPL::InvalidArgumentException("The table argument must be an instance of a table object") if not UNIVERSAL::isa($args{'Table'},'IMPL::SQL::Schema::Table'); + $this->{$Name} = $args{'Name'}; + $this->{$Table} = $args{'Table'}; + $this->{$Columns} = [map { ResolveColumn($this->Table,$_) } @{$args{'Columns'}}]; +} + +sub ResolveColumn { + my ($Table,$Column) = @_; + + my $cn = UNIVERSAL::isa($Column,'IMPL::SQL::Schema::Column') ? $Column->Name : $Column; + + my $resolved = $Table->Column($cn); + die new IMPL::InvalidOperationException("The column is not found in the table", $cn, $Table->Name) if not $resolved; + return $resolved; +} + +sub HasColumn { + my ($this,@Columns) = @_; + + my %Columns = map { $_, 1} @Columns; + + return scalar(grep { $Columns{$_->Name} } $this->Columns) == scalar(@Columns); +} + +sub UniqName { + my ($this) = @_; + return $this->{$Table}->Name.'_'.$this->{$Name}; +} + +sub Dispose { + my ($this) = @_; + + delete @$this{$Table,$Columns}; + $this->SUPER::Dispose; +} +1; \ No newline at end of file diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Constraint/ForeignKey.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Constraint/ForeignKey.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,58 @@ +package IMPL::SQL::Schema::Constraint::ForeignKey; +use strict; +use base qw(IMPL::SQL::Schema::Constraint); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property ReferencedPrimaryKey => prop_get; + public _direct property OnDelete => prop_get; + public _direct property OnUpdate => prop_get; +} + +__PACKAGE__->PassThroughArgs; + +sub CTOR { + my ($this,%args) = @_; + + die new Eexception("Referenced table must be an instance of a table object") if not UNIVERSAL::isa($args{'ReferencedTable'},'IMPL::SQL::Schema::Table'); + + die new Exception("Referenced columns must be a not empty list of columns") if not UNIVERSAL::isa($args{'ReferencedColumns'},'ARRAY') or not scalar(@{$args{'ReferencedColumns'}}); + + my @ReferencedColumns = map {IMPL::SQL::Schema::Constraint::ResolveColumn($args{'ReferencedTable'},$_)} @{$args{'ReferencedColumns'}}; + my $ForeingPK = $args{'ReferencedTable'}->PrimaryKey or die new Exception('The referenced table doesn\'t have a primary key'); + + scalar (@ReferencedColumns) == scalar(@{$this->Columns}) or die new Exception('A foreing key columns doesn\'t match refenced columns'); + my @ColumnsCopy = @ReferencedColumns; + + die new Exception('A foreing key columns doesn\'t match refenced columns') if grep { not $_->Type->isSame((shift @ColumnsCopy)->Type)} $this->Columns; + + @ColumnsCopy = @ReferencedColumns; + die new Exception('The foreign key must match to the primary key of the referenced table',$this->Name) if grep { not $_->Type->isSame(shift(@ColumnsCopy)->Type)} $ForeingPK->Columns; + + $this->{$ReferencedPrimaryKey} = $ForeingPK; + + $ForeingPK->ConnectFK($this); +} + +sub Dispose { + my ($this) = @_; + + $this->{$ReferencedPrimaryKey}->DisconnectFK($this) if not $this->{$ReferencedPrimaryKey}->isa('Object::Disposed'); + delete $this->{$ReferencedPrimaryKey}; + + $this->SUPER::Dispose; +} + +sub isSame { + my ($this,$other) = @_; + + uc $this->OnDelete eq uc $other->OnDelete or return 0; + uc $this->OnUpdate eq uc $other->OnUpdate or return 0; + + return $this->SUPER::isSame($other); +} + + + +1; \ No newline at end of file diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Constraint/Index.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Constraint/Index.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,14 @@ +package IMPL::SQL::Schema::Constraint::Index; +use strict; +use base qw(IMPL::SQL::Schema::Constraint); + +__PACKAGE__->PassThroughArgs; + +sub CTOR { + my $this = shift; + + my %colnames; + not grep { $colnames{$_}++ } $this->Columns or die new Exception('Each column in the index can occur only once'); +} + +1; diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Constraint/PrimaryKey.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Constraint/PrimaryKey.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,43 @@ +package IMPL::SQL::Schema::Constraint::PrimaryKey; +use strict; +use base qw(IMPL::SQL::Schema::Constraint::Index); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +__PACKAGE__->PassThroughArgs; + +BEGIN { + public _direct property ConnectedFK => prop_get; +} + +sub CTOR { + my ($this,%args) = @_; + + $this->SUPER::CTOR(%args); + + $this->{$ConnectedFK} = {}; +} + +sub ConnectFK { + my ($this,$FK) = @_; + + UNIVERSAL::isa($FK,'IMPL::SQL::Schema::Constraint::ForeignKey') or die new Exception('Aprimary key could be connected only to a foreign key'); + not exists $this->{$ConnectedFK}->{$FK->UniqName} or die new Exception('This primary key already conneted with the specified foreing key',$FK->Name,$FK->Table->Name); + + $this->{$ConnectedFK}->{$FK->UniqName} = $FK; +} + +sub DisconnectFK { + my ($this,$FK) = @_; + + delete $this->{$ConnectedFK}->{$FK->UniqName}; +} + +sub Dispose { + my ($this) = @_; + + delete $this->{$ConnectedFK}; + $this->SUPER::Dispose; +} + +1; \ No newline at end of file diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Constraint/Unique.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Constraint/Unique.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,5 @@ +package IMPL::SQL::Schema::Constraint::PrimaryKey; +use strict; +use base qw(IMPL::SQL::Schema::Constraint::Index); + +1; \ No newline at end of file diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Table.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Table.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,168 @@ +use strict; +package IMPL::SQL::Schema::Table; + +use IMPL::SQL::Schema::Column; +use IMPL::SQL::Schema::Constraint; +use IMPL::SQL::Schema::Constraint::PrimaryKey; +use IMPL::SQL::Schema::Constraint::ForeignKey; + +use base qw(IMPL::Object IMPL::Object::Disposable); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +srand time; + +BEGIN { + public _direct property Name => prop_get; + public _direct property Schema => prop_get; + public _direct property Columns => prop_get; + public _direct property Constraints => prop_get; + public _direct property ColumnsByName => prop_none; + public _direct property PrimaryKey => prop_get; + public _direct property Tag => prop_all; +} + +sub CTOR { + my ($this,%args) = @_; + + $this->{$Name} = $args{'Name'} or die new IMPL::InvalidArgumentException('a table name is required'); + $this->{$Schema} = $args{'Schema'} or die new IMPL::InvalidArgumentException('a parent schema is required'); +} + +sub InsertColumn { + my ($this,$column,$index) = @_; + + $index = ($this->{$Columns} ? scalar(@{$this->{$Columns}}) : 0) if not defined $index; + + die new IMPL::InvalidArgumentException("The index is out of range") if ($index < 0 || $index > ($this->{$Columns} ? scalar(@{$this->{$Columns}}) : 0)); + + if (UNIVERSAL::isa($column,'IMPL::SQL::Schema::Column')) { + + } elsif (UNIVERSAL::isa($column,'HASH')) { + $column = new IMPL::SQL::Schema::Column(%{$column}); + } else { + die new IMPL::InvalidArgumentException("The invalid column parameter"); + } + + if (exists $this->{$ColumnsByName}->{$column->Name}) { + die new IMPL::InvalidOperationException("The column already exists",$column->name); + } else { + $this->{$ColumnsByName}->{$column->Name} = $column; + splice @{$this->{$Columns}},$index,0,$column; + } + + return $column; +} + +sub RemoveColumn { + my ($this,$NameOrColumn,$Force) = @_; + + my $ColName; + if (UNIVERSAL::isa($NameOrColumn,'IMPL::SQL::Schema::Column')) { + $ColName = $NameOrColumn->Name; + } elsif (not ref $NameOrColumn) { + $ColName = $NameOrColumn; + } + + if (exists $this->{$ColumnsByName}->{$ColName}) { + my $index = 0; + foreach my $column(@{$this->{$Columns}}) { + last if $column->Name eq $ColName; + $index++; + } + + my $column = $this->{$Columns}[$index]; + if (my @constraints = $this->GetColumnConstraints($column)){ + $Force or die new IMPL::InvalidOperationException('Can\'t remove column which is used in the constraints',@constraints); + $this->RemoveConstraint($_) foreach @constraints; + } + + my $removed = splice @{$this->{$Columns}},$index,1; + delete $this->{$ColumnsByName}->{$ColName}; + return $removed; + } else { + die new IMPL::InvalidOperationException("The column not found",$NameOrColumn->Name); + } +} + +sub Column { + my ($this,$name) = @_; + + return $this->{$ColumnsByName}->{$name}; +} + +sub ColumnAt { + my ($this,$index) = @_; + + die new IMPL::InvalidArgumentException("The index is out of range") if $index < 0 || $index >= ($this->{$Columns} ? scalar(@{$this->{$Columns}}) : 0); + + return $this->{$Columns}[$index]; +} + +sub AddConstraint { + my ($this,$Constraint) = @_; + + die new IMPL::InvalidArgumentException('The invalid parameter') if not UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint'); + + $Constraint->Table == $this or die new IMPL::InvalidOperationException('The constaint must belong to the target table'); + + if (exists $this->{$Constraints}->{$Constraint->Name}) { + die new IMPL::InvalidOperationException('The table already has the specified constraint',$Constraint->Name); + } else { + if (UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint::PrimaryKey')) { + not $this->{$PrimaryKey} or die new IMPL::InvalidOperationException('The table already has a primary key'); + $this->{$PrimaryKey} = $Constraint; + } + + $this->{$Constraints}->{$Constraint->Name} = $Constraint; + } +} + +sub RemoveConstraint { + my ($this,$Constraint,$Force) = @_; + + my $cn = UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint') ? $Constraint->Name : $Constraint; + $Constraint = $this->{$Constraints}->{$cn} or die new IMPL::InvalidOperationException('The specified constraint doesn\'t exists',$cn); + + if (UNIVERSAL::isa($Constraint,'IMPL::SQL::Schema::Constraint::PrimaryKey')) { + not scalar keys %{$this->{$PrimaryKey}->ConnectedFK} or die new IMPL::InvalidOperationException('Can\'t remove Primary Key unless some foreign keys referenses it'); + + delete $this->{$PrimaryKey}; + } + $Constraint->Dispose; + delete $this->{$Constraints}->{$cn}; + return $cn; +} + +sub GetColumnConstraints { + my ($this,@Columns) = @_; + + my @cn = map { UNIVERSAL::isa($_ ,'IMPL::SQL::Schema::Column') ? $_ ->Name : $_ } @Columns; + exists $this->{$ColumnsByName}->{$_} or die new IMPL::InvalidOperationException('The specified column isn\'t found',$_) foreach @cn; + + return grep {$_->HasColumn(@cn)} values %{$this->{$Constraints}}; +} + +sub SetPrimaryKey { + my ($this,@ColumnList) = @_; + + $this->AddConstraint(new IMPL::SQL::Schema::Constraint::PrimaryKey(Name => $this->{$Name}.'_PK', Table => $this,Columns => \@ColumnList)); +} + +sub LinkTo { + my ($this,$table,@ColumnList) = @_; + $table->PrimaryKey or die new IMPL::InvalidOperationException('The referenced table must have a primary key'); + my $constraintName = $this->{$Name}.'_'.$table->Name.'_FK_'.join('_',map {ref $_ ? $_->Name : $_} @ColumnList); + $this->AddConstraint(new IMPL::SQL::Schema::Constraint::ForeignKey(Name => $constraintName, Table => $this,Columns => \@ColumnList, ReferencedTable => $table, ReferencedColumns => scalar($table->PrimaryKey->Columns))); +} + +sub Dispose { + my ($this) = @_; + + $_->Dispose() foreach values %{$this->{$Constraints}}; + + undef %{$this}; + $this->SUPER::Dispose(); +} + +1; diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Traits.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Traits.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,270 @@ +package IMPL::SQL::Schema::Traits; +use strict; +use base qw(IMPL::Object IMPL::Object::Autofill); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +use constant { + STATE_NORMAL => 0, + STATE_UPDATED => 1, + STATE_CREATED => 2, + STATE_REMOVED => 3, + STATE_PENDING => 4 +} ; + +BEGIN { + public _direct property SrcSchema => prop_none; + public _direct property DstSchema => prop_none; + public _direct property PendingActions => prop_get; + public _direct property TableInfo => prop_get; + public _direct property Handler => prop_get; + public _direct property TableMap => prop_none; + public _direct property KeepTables => prop_all; +} + +__PACKAGE__->PassThroughArgs; + +sub CTOR { + my $this = shift; + + $this->{$SrcSchema} or die new IMPL::InvalidArgumentException('A source schema is required'); + $this->{$DstSchema} or die new IMPL::InvalidArgumentException('A destination schema is required'); + $this->{$Handler} or die new IMPL::InvalidArgumentException('A handler is required to produce the update batch'); + + $this->{$TableInfo} = {}; + $this->{$PendingActions} = []; + +} + +sub UpdateTable { + my ($this,$srcTable) = @_; + + return 1 if $this->{$TableInfo}->{$srcTable->Name}->{'processed'}; + + my $dstTableName = $this->{$TableMap}->{$srcTable->Name} ? $this->{$TableMap}->{$srcTable->Name} : $srcTable->Name; + my $dstTable = $this->{$DstSchema}->Tables->{$dstTableName}; + + $this->{$TableInfo}->{$srcTable->Name}->{'processed'} = 1; + + if (not $dstTable) { + $this->DropTable($srcTable) if not $this->{$KeepTables}; + return 1; + } + + if ( not grep {$srcTable->Column($_->Name)} $dstTable->Columns ) { + + $this->{$TableInfo}->{$srcTable->Name}->{'NewName'} = $dstTable->Name if $srcTable->Name ne $dstTable->Name; + + $this->DropTable($srcTable); + $this->CreateTable($dstTable); + + return 1; + } + + if ($srcTable->Name ne $dstTableName) { + $this->RenameTable($srcTable,$dstTableName); + } + + my %dstConstraints = %{$dstTable->Constraints}; + + foreach my $srcConstraint (values %{$srcTable->Constraints}) { + if (my $dstConstraint = delete $dstConstraints{$srcConstraint->Name}) { + $this->UpdateConstraint($srcConstraint,$dstConstraint); + } else { + $this->DropConstraint($srcConstraint); + } + } + + my $i = 0; + my %dstColumns = map { $_->Name, $i++} $dstTable->Columns ; + + # сначала удаляем столбцы + # потом добавляем недостающие и изменяем столбцы в нужном порядке + + my @columnsToUpdate; + + foreach my $srcColumn ($srcTable->Columns) { + if (defined (my $dstColumnIndex = delete $dstColumns{$srcColumn->Name})) { + push @columnsToUpdate, { Action => 'update', ColumnSrc => $srcColumn, ColumnDst => $dstTable->ColumnAt($dstColumnIndex), NewPosition => $dstColumnIndex}; + } else { + $this->DropColumn($srcTable,$srcColumn); + } + } + push @columnsToUpdate, map { {Action => 'add', ColumnDst => $dstTable->ColumnAt($_), NewPosition => $_} } values %dstColumns; + + foreach my $action (sort {$a->{'NewPosition'} <=> $b->{'NewPosition'}} @columnsToUpdate ) { + if ($action->{'Action'} eq 'update') { + $this->UpdateColumn($srcTable,@$action{'ColumnSrc','ColumnDst'},$dstTable,$action->{'NewPosition'}); # change type and position + }elsif ($action->{'Action'} eq 'add') { + $this->AddColumn($srcTable,$action->{'ColumnDst'},$dstTable,$action->{'NewPosition'}); # add at specified position + } + } + + foreach my $dstConstraint (values %dstConstraints) { + $this->AddConstraint($dstConstraint); + } + + $this->{$TableInfo}{$srcTable->Name}{'State'} = STATE_UPDATED; +} + +sub UpdateConstraint { + my ($this,$src,$dst) = @_; + + if (not ConstraintEquals($src,$dst)) { + if (UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::PrimaryKey')) { + $this->UpdateTable($_->Table) foreach values %{$src->ConnectedFK}; + } + $this->DropConstraint($src); + $this->AddConstraint($dst); + } else { + $this->{$TableInfo}->{$this->MapTableName($src->Table->Name)}->{'Constraints'}->{$src->Name} = STATE_UPDATED; + } +} + +sub ConstraintEquals { + my ($src,$dst) = @_; + + ref $src eq ref $dst or return 0; + + my @dstColumns = $dst->Columns; + scalar(@{$src->Columns}) == scalar(@{$dst->Columns}) and not grep { my $column = shift @dstColumns; not $column->isSame($_) } $src->Columns or return 0; + + not UNIVERSAL::isa($src,'IMPL::SQL::Schema::Constraint::ForeignKey') or ConstraintEquals($src->ReferencedPrimaryKey,$dst->ReferencedPrimaryKey) or return 0; + + 1; +} + +sub UpdateSchema { + my ($this) = @_; + + my %Updated = map { $this->UpdateTable($_); $this->MapTableName($_->Name) , 1; } values %{$this->{$SrcSchema}->Tables ? $this->{$SrcSchema}->Tables : {} }; + + $this->CreateTable($_) foreach grep {not $Updated{$_->Name}} values %{$this->{$DstSchema}->Tables}; + + $this->ProcessPendingActions(); +} + +sub RenameTable { + my ($this,$tblSrc,$tblDstName) = @_; + + $this->{$Handler}->AlterTableRename($tblSrc->Name,$tblDstName); + $this->{$TableInfo}->{$tblSrc->Name}->{'NewName'} = $tblDstName; +} + +sub MapTableName { + my ($this,$srcName) = @_; + + $this->{$TableInfo}->{$srcName}->{'NewName'} ? $this->{$TableInfo}->{$srcName}->{'NewName'} : $srcName; +} + +sub DropTable { + my ($this,$tbl) = @_; + + if ($tbl->PrimaryKey) { + $this->UpdateTable($_->Table) foreach values %{$tbl->PrimaryKey->ConnectedFK}; + } + + $this->{$Handler}->DropTable($this->MapTableName($tbl->Name)); + $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'State'} = STATE_REMOVED; + $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Constraints'} = {map {$_,STATE_REMOVED} keys %{$tbl->Constraints}}; + $this->{$TableInfo}{$this->MapTableName($tbl->Name)}{'Columns'} = {map { $_->Name, STATE_REMOVED} $tbl->Columns}; + + return 1; +} + +sub CreateTable { + my ($this,$tbl) = @_; + + # создаем таблицу, кроме внешних ключей + $this->{$Handler}->CreateTable($tbl,skip_foreign_keys => 1); + + $this->{$TableInfo}->{$tbl->Name}->{'State'} = STATE_CREATED; + + $this->{$TableInfo}->{$tbl->Name}->{'Columns'} = {map { $_->Name, STATE_CREATED } $tbl->Columns}; + $this->{$TableInfo}->{$tbl->Name}->{'Constraints'} = {map {$_->Name, STATE_CREATED} grep { not UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}}; + + $this->AddConstraint($_) foreach grep { UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey') } values %{$tbl->Constraints}; + + return 1; +} + +sub AddColumn { + my ($this,$tblSrc,$column,$tblDst,$pos) = @_; + + $this->{$Handler}->AlterTableAddColumn($this->MapTableName($tblSrc->Name),$column,$tblDst,$pos); + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_CREATED; + + return 1; +} + +sub DropColumn { + my ($this,$tblSrc,$column) = @_; + $this->{$Handler}->AlterTableDropColumn($this->MapTableName($tblSrc->Name),$column->Name); + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$column->Name} = STATE_REMOVED; + + return 1; +} + +sub UpdateColumn { + my ($this,$tblSrc,$srcColumn,$dstColumn,$tblDst,$pos) = @_; + + if ($srcColumn->isSame($dstColumn) and $pos < @{$tblSrc->Columns} and $tblSrc->ColumnAt($pos) == $srcColumn) { + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; + return 1; + } + + $this->{$Handler}->AlterTableChangeColumn($this->MapTableName($tblSrc->Name),$dstColumn,$tblDst,$pos); + $this->{$TableInfo}->{$this->MapTableName($tblSrc->Name)}->{'Columns'}->{$dstColumn->Name} = STATE_UPDATED; + + return 1; +} + +sub DropConstraint { + my ($this,$constraint) = @_; + + $this->{$Handler}->AlterTableDropConstraint($this->MapTableName($constraint->Table->Name),$constraint); + $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_REMOVED; + + return 1; +} + +sub IfUndef { + my ($value,$default) = @_; + + return defined $value ? $value : $default; +} + +sub AddConstraint { + my ($this,$constraint) = @_; + + # перед добавлением ограничения нужно убедиться в том, что созданы все необходимые столбцы и сопутствующие + # ограничения (например первичные ключи) + + my $pending; + + $pending = grep { my $column = $_; not grep { IfUndef($this->{$TableInfo}{$constraint->Table->Name}{'Columns'}{$column->Name}, STATE_NORMAL) == $_ } (STATE_UPDATED, STATE_CREATED) } $constraint->Columns; + + if ($pending) { + push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; + return 2; + } else { + if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::ForeignKey')) { + if (not grep { IfUndef($this->{$TableInfo}{$constraint->ReferencedPrimaryKey->Table->Name}{'Constraints'}{$constraint->ReferencedPrimaryKey->Name},STATE_NORMAL) == $_} (STATE_UPDATED, STATE_CREATED)) { + push @{$this->{$PendingActions}},{Action => \&AddConstraint, Args => [$constraint]}; + return 2; + } + } + $this->{$Handler}->AlterTableAddConstraint($constraint->Table->Name,$constraint); + $this->{$TableInfo}->{$constraint->Table->Name}->{'Constraints'}->{$constraint->Name} = STATE_CREATED; + } +} + +sub ProcessPendingActions { + my ($this) = @_; + + while (my $action = shift @{$this->{$PendingActions}}) { + $action->{'Action'}->($this,@{$action->{'Args'}}); + } +} + +1; diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Traits/mysql.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Traits/mysql.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,554 @@ +package IMPL::SQL::Schema::Traits::mysql::Handler; +use strict; +use base qw(IMPL::Object); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property SqlBatch => prop_none; +} + +sub formatTypeNameInteger { + my ($type) = @_; + + return $type->Name.($type->MaxLength ? '('.$type->MaxLength.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : ''); +} + +sub formatTypeNameReal { + my ($type) = @_; + + return $type->Name.($type->MaxLength ? '('.$type->MaxLength.', '.$type->Scale.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : ''); +} + +sub formatTypeNameNumeric { + my ($type) = @_; + $type->MaxLength or die new IMPL::InvalidArgumentException('The length and precission must be specified',$type->Name); + return $type->Name.($type->MaxLength ? '('.$type->MaxLength.', '.$type->Scale.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : ''); +} + +sub formatTypeName { + my ($type) = @_; + return $type->Name; +} + +sub formatTypeNameChar { + my ($type) = @_; + + return ( + $type->Name.'('.$type->MaxLength.')'. (UNIVERSAL::isa($type,'IMPL::SQL::Schema::Type::mysql::CHAR') ? $type->Encoding : '') + ); +} + +sub formatTypeNameVarChar { + my ($type) = @_; + + return ( + $type->Name.'('.$type->MaxLength.')'. (UNIVERSAL::isa($type,'IMPL::SQL::Schema::Type::mysql::VARCHAR') ? $type->Encoding : '') + ); +} + +sub formatTypeNameEnum { + my ($type) = @_; + die new Exception('Enum must be a type of either IMPL::SQL::Schema::Type::mysql::ENUM or IMPL::SQL::Schema::Type::mysql::SET') if not (UNIVERSAL::isa($type,'IMPL::SQL::Schema::Type::mysql::ENUM') or UNIVERSAL::isa($type,'IMPL::SQL::Schema::Type::mysql::SET')); + return ( + $type->Name.'('.join(',',map {quote($_)} $type->Values).')' + ); +} + +sub quote{ + if (wantarray) { + return map { my $str=$_; $str=~ s/'/''/g; "'$str'"; } @_; + } else { + return join '',map { my $str=$_; $str=~ s/'/''/g; "'$str'"; } @_; + } +} + +sub quote_names { + if (wantarray) { + return map { my $str=$_; $str=~ s/`/``/g; "`$str`"; } @_; + } else { + return join '',map { my $str=$_; $str=~ s/`/``/g; "`$str`"; } @_; + } +} + +sub formatStringValue { + my ($value) = @_; + + if (ref $value) { + if (UNIVERSAL::isa($value,'IMPL::SQL::Schema::mysql::Expression')) { + return $value->as_string; + } else { + die new Exception('Can\'t format the object as a value',ref $value); + } + } else { + return quote($value); + } +} + + +sub formatNumberValue { + my ($value) = @_; + + if (ref $value) { + if (UNIVERSAL::isa($value,'IMPL::SQL::Schema::mysql::Expression')) { + return $value->as_string; + } else { + die new Exception('Can\'t format the object as a value',ref $value); + } + } else { + $value =~ /^((\+|-)\s*)?\d+(\.\d+)?(e(\+|-)?\d+)?$/ or die new Exception('The specified value isn\'t a valid number',$value); + return $value; + } +} + + +my %TypesFormat = ( + TINYINT => { + formatType => \&formatTypeNameInteger, + formatValue => \&formatNumberValue + }, + SMALLINT => { + formatType => \&formatTypeNameInteger, + formatValue => \&formatNumberValue + }, + MEDIUMINT => { + formatType => \&formatTypeNameInteger, + formatValue => \&formatNumberValue + }, + INT => { + formatType => \&formatTypeNameInteger, + formatValue => \&formatNumberValue + }, + INTEGER => { + formatType => \&formatTypeNameInteger, + formatValue => \&formatNumberValue + }, + BIGINT => { + formatType => \&formatTypeNameInteger, + formatValue => \&formatNumberValue + }, + REAL => { + formatType => \&formatTypeNameReal, + formatValue => \&formatNumberValue + }, + DOUBLE => { + formatType => \&formatTypeNameReal, + formatValue => \&formatNumberValue + }, + FLOAT => { + formatType => \&formatTypeNameReal, + formatValue => \&formatNumberValue + }, + DECIMAL => { + formatType => \&formatTypeNameNumeric, + formatValue => \&formatNumberValue + }, + NUMERIC => { + formatType => \&formatTypeNameNumeric, + formatValue => \&formatNumberValue + }, + DATE => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + TIME => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + TIMESTAMP => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + DATETIME => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + CHAR => { + formatType => \&formatTypeNameChar, + formatValue => \&formatStringValue + }, + VARCHAR => { + formatType => \&formatTypeNameVarChar, + formatValue => \&formatStringValue + }, + TINYBLOB => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + BLOB => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + MEDIUMBLOB => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + LONGBLOB => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + TINYTEXT => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + TEXT => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + MEDIUMTEXT => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + LONGTEXT => { + formatType => \&formatTypeName, + formatValue => \&formatStringValue + }, + ENUM => { + formatType => \&formatTypeNameEnum, + formatValue => \&formatStringValue + }, + SET => { + formatType => \&formatTypeNameEnum, + formatValue => \&formatStringValue + } +); + + +=pod +CREATE TABLE 'test'.'New Table' ( + 'dd' INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + `ff` VARCHAR(45) NOT NULL, + `ffg` VARCHAR(45) NOT NULL DEFAULT 'aaa', + `ddf` INTEGER UNSIGNED NOT NULL, + PRIMARY KEY(`dd`), + UNIQUE `Index_2`(`ffg`), + CONSTRAINT `FK_New Table_1` FOREIGN KEY `FK_New Table_1` (`ddf`) + REFERENCES `user` (`id`) + ON DELETE RESTRICT + ON UPDATE RESTRICT +) +ENGINE = InnoDB; +=cut +sub formatCreateTable { + my ($table,$level,%options) = @_; + + my @sql; + + # table body + push @sql, map { formatColumn($_,$level+1) } $table->Columns ; + if ($options{'skip_foreign_keys'}) { + push @sql, map { formatConstraint($_,$level+1) } grep {not UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey')} values %{$table->Constraints}; + } else { + push @sql, map { formatConstraint($_,$level+1) } values %{$table->Constraints}; + } + + for(my $i = 0 ; $i < @sql -1; $i++) { + $sql[$i] .= ','; + } + + unshift @sql, "CREATE TABLE ".quote_names($table->Name)."("; + + if ($table->Tag) { + push @sql, ")"; + push @sql, formatTableTag($table->Tag,$level); + $sql[$#sql].=';'; + } else { + push @sql, ');'; + } + + return map { ("\t" x $level) . $_ } @sql; +} + +sub formatDropTable { + my ($tableName,$level) = @_; + + return "\t"x$level."DROP TABLE ".quote_names($tableName).";"; +} + +sub formatTableTag { + my ($tag,$level) = @_; + return map { "\t"x$level . "$_ = ".$tag->{$_} } grep {/^(ENGINE)$/i} keys %{$tag}; +} + +sub formatColumn { + my ($column,$level) = @_; + $level ||= 0; + return "\t"x$level.quote_names($column->Name)." ".formatType($column->Type)." ".($column->CanBeNull ? 'NULL' : 'NOT NULL').($column->DefaultValue ? formatValueToType($column->DefaultValue,$column->Type) : '' ).($column->Tag ? ' '.join(' ',$column->Tag) : ''); +} + +sub formatType { + my ($type) = @_; + my $format = $TypesFormat{uc $type->Name} or die new Exception('The unknown type name',$type->Name); + $format->{formatType}->($type); +} + +sub formatValueToType { + my ($value,$type) = @_; + + my $format = $TypesFormat{uc $type->Name} or die new Exception('The unknown type name',$type->Name); + $format->{formatValue}->($value); +} + +sub formatConstraint { + my ($constraint,$level) = @_; + + if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::ForeignKey')) { + return formatForeignKey($constraint,$level); + } else { + return formatIndex($constraint, $level); + } +} + +sub formatIndex { + my ($constraint,$level) = @_; + + my $name = quote_names($constraint->Name); + my $columns = join(',',map quote_names($_->Name),$constraint->Columns); + + if (ref $constraint eq 'IMPL::SQL::Schema::Constraint::PrimaryKey') { + return "\t"x$level."PRIMARY KEY ($columns)"; + } elsif ($constraint eq 'IMPL::SQL::Schema::Constraint::Unique') { + return "\t"x$level."UNIQUE $name ($columns)"; + } elsif ($constraint eq 'IMPL::SQL::Schema::Constraint::Index') { + return "\t"x$level."INDEX $name ($columns)"; + } else { + die new IMPL::InvalidArgumentException('The unknown constraint', ref $constraint); + } + +} + +sub formatForeignKey { + my ($constraint,$level) = @_; + + my $name = quote_names($constraint->Name); + my $columns = join(',',map quote_names($_->Name),$constraint->Columns); + + not $constraint->OnDelete or grep { uc $constraint->OnDelete eq $_ } ('RESTRICT','CASCADE','SET NULL','NO ACTION','SET DEFAULT') or die new IMPL::Exception('Invalid ON DELETE reference',$constraint->OnDelete); + not $constraint->OnUpdate or grep { uc $constraint->OnUpdate eq $_ } ('RESTRICT','CASCADE','SET NULL','NO ACTION','SET DEFAULT') or die new IMPL::Exception('Invalid ON UPDATE reference',$constraint->OnUpdate); + + my $refname = quote_names($constraint->ReferencedPrimaryKey->Table->Name); + my $refcolumns = join(',',map quote_names($_->Name),$constraint->ReferencedPrimaryKey->Columns); + return ( + "\t"x$level. + "CONSTRAINT $name FOREIGN KEY $name ($columns) REFERENCES $refname ($refcolumns)". + ($constraint->OnUpdate ? 'ON UPDATE'.$constraint->OnUpdate : ''). + ($constraint->OnDelete ? 'ON DELETE'.$constraint->OnDelete : '') + ); +} + +sub formatAlterTableRename { + my ($oldName,$newName,$level) = @_; + + return "\t"x$level."ALTER TABLE ".quote_names($oldName)." RENAME TO ".quote_names($newName).";"; +} + +sub formatAlterTableDropColumn { + my ($tableName, $columnName,$level) = @_; + + return "\t"x$level."ALTER TABLE ".quote_names($tableName)." DROP COLUMN ".quote_names($columnName).";"; +} + +=pod +ALTER TABLE `test`.`user` ADD COLUMN `my_col` VARCHAR(45) NOT NULL AFTER `name2` +=cut +sub formatAlterTableAddColumn { + my ($tableName, $column, $table, $pos, $level) = @_; + + my $posSpec = $pos == 0 ? 'FIRST' : 'AFTER '.quote_names($table->ColumnAt($pos-1)->Name); + + return "\t"x$level."ALTER TABLE ".quote_names($tableName)." ADD COLUMN ".formatColumn($column) .' '. $posSpec.";"; +} + +=pod +ALTER TABLE `test`.`manager` MODIFY COLUMN `description` VARCHAR(256) NOT NULL DEFAULT NULL; +=cut +sub formatAlterTableChangeColumn { + my ($tableName,$column,$table,$pos,$level) = @_; + my $posSpec = $pos == 0 ? 'FIRST' : 'AFTER '.quote_names($table->ColumnAt($pos-1)->Name); + return "\t"x$level."ALTER TABLE ".quote_names($tableName)." MODIFY COLUMN ".formatColumn($column).' '. $posSpec.";"; +} + +=pod +ALTER TABLE `test`.`manager` DROP INDEX `Index_2`; +=cut +sub formatAlterTableDropConstraint { + my ($tableName,$constraint,$level) = @_; + my $constraintName; + if (ref $constraint eq 'IMPL::SQL::Schema::Constraint::PrimaryKey') { + $constraintName = 'PRIMARY KEY'; + } elsif (ref $constraint eq 'IMPL::SQL::Schema::Constraint::ForeignKey') { + $constraintName = 'FOREIGN KEY '.quote_names($constraint->Name); + } elsif (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::Index')) { + $constraintName = 'INDEX '.quote_names($constraint->Name); + } else { + die new IMPL::Exception("The unknow type of the constraint",ref $constraint); + } + return "\t"x$level."ALTER TABLE ".quote_names($tableName)." DROP $constraintName;"; +} + +=pod +ALTER TABLE `test`.`session` ADD INDEX `Index_2`(`id`, `name`); +=cut +sub formatAlterTableAddConstraint { + my ($tableName,$constraint,$level) = @_; + + return "\t"x$level."ALTER TABLE ".quote_names($tableName)." ADD ".formatConstraint($constraint,0).';'; +} + +sub CreateTable { + my ($this,$tbl,%option) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatCreateTable($tbl,0,%option)); + + return 1; +} + +sub DropTable { + my ($this,$tbl) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatDropTable($tbl,0)); + + return 1; +} + +sub RenameTable { + my ($this,$oldName,$newName) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatAlterTableRename($oldName,$newName,0)); + + return 1; +} + +sub AlterTableAddColumn { + my ($this,$tblName,$column,$table,$pos) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatAlterTableAddColumn($tblName,$column,$table,$pos,0)); + + return 1; +} +sub AlterTableDropColumn { + my ($this,$tblName,$columnName) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatAlterTableDropColumn($tblName,$columnName,0)); + + return 1; +} + +sub AlterTableChangeColumn { + my ($this,$tblName,$column,$table,$pos) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatAlterTableChangeColumn($tblName,$column,$table,$pos,0)); + + return 1; +} + +sub AlterTableAddConstraint { + my ($this,$tblName,$constraint) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatAlterTableAddConstraint($tblName,$constraint,0)); + + return 1; +} + +sub AlterTableDropConstraint { + my ($this,$tblName,$constraint) = @_; + + push @{$this->{$SqlBatch}},join("\n",formatAlterTableDropConstraint($tblName,$constraint,0)); + + return 1; +} + +sub Sql { + my ($this) = @_; + if (wantarray) { + $this->SqlBatch; + } else { + return join("\n",$this->SqlBatch); + } +} + +package IMPL::SQL::Schema::Traits::mysql; +use Common; +use base qw(IMPL::SQL::Schema::Traits); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property PendingConstraints => prop_none; +} + +sub CTOR { + my ($this,%args) = @_; + + $args{'Handler'} = new IMPL::SQL::Schema::Traits::mysql::Handler; + $this->SUPER::CTOR(%args); +} + +sub DropConstraint { + my ($this,$constraint) = @_; + + if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::Index')) { + return 1 if not grep { $this->TableInfo->{$this->MapTableName($constraint->Table->Name)}->{'Columns'}->{$_->Name} != IMPL::SQL::Schema::Traits::STATE_REMOVED} $constraint->Columns; + my @constraints = grep {$_ != $constraint } $constraint->Table->GetColumnConstraints($constraint->Columns); + if (scalar @constraints == 1 and UNIVERSAL::isa($constraints[0],'IMPL::SQL::Schema::Constraint::ForeignKey')) { + my $fk = shift @constraints; + if ($this->TableInfo->{$this->MapTableName($fk->Table->Name)}->{'Constraints'}->{$fk->Name} != IMPL::SQL::Schema::Traits::STATE_REMOVED) { + push @{$this->PendingActions}, {Action => \&DropConstraint, Args => [$constraint]}; + $this->{$PendingConstraints}->{$constraint->UniqName}->{'attempts'} ++; + + die new IMPL::Exception('Can\'t drop the primary key becouse of the foreing key',$fk->UniqName) if $this->{$PendingConstraints}->{$constraint->UniqName}->{'attempts'} > 2; + return 2; + } + } + } + $this->SUPER::DropConstraint($constraint); +} + +sub GetMetaTable { + my ($class,$dbh) = @_; + + return IMPL::SQL::Schema::Traits::mysql::MetaTable->new( DBHandle => $dbh); +} + +package IMPL::SQL::Schema::Traits::mysql::MetaTable; +use Common; +use base qw(IMPL::Object); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property DBHandle => prop_none; +} + +sub ReadProperty { + my ($this,$name) = @_; + + local $this->{$DBHandle}->{PrintError}; + $this->{$DBHandle}->{PrintError} = 0; + my ($val) = $this->{$DBHandle}->selectrow_array("SELECT value FROM _Meta WHERE name like ?", undef, $name); + return $val; +} + +sub SetProperty { + my ($this,$name,$val) = @_; + + if ( $this->{$DBHandle}->selectrow_arrayref("SELECT TABLE_NAME FROM information_schema.`TABLES` T where TABLE_SCHEMA like DATABASE() and TABLE_NAME like '_Meta'")) { + if ($this->{$DBHandle}->selectrow_arrayref("SELECT name FROM _Meta WHERE name like ?", undef, $name)) { + $this->{$DBHandle}->do("UPDATE _Meta SET value = ? WHERE name like ?",undef,$val,$name); + } else { + $this->{$DBHandle}->do("INSERT INTO _Meta(name,value) VALUES ('$name',?)",undef,$val); + } + } else { + $this->{$DBHandle}->do(q{ + CREATE TABLE `_Meta` ( + `name` VARCHAR(255) NOT NULL, + `value` LONGTEXT NULL, + PRIMARY KEY(`name`) + ); + }) or die new IMPL::Exception("Failed to create table","_Meta"); + + $this->{$DBHandle}->do("INSERT INTO _Meta(name,value) VALUES (?,?)",undef,$name,$val); + } +} + +1; diff -r d59526f6310e -r 56cef8e3cda6 Lib/IMPL/SQL/Schema/Type.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/SQL/Schema/Type.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,44 @@ +use strict; +package IMPL::SQL::Schema::Type; +use base qw(IMPL::Object IMPL::Object::Autofill); +use IMPL::Class::Property; +use IMPL::Class::Property::Direct; + +BEGIN { + public _direct property Name => prop_get; + public _direct property MaxLength => prop_get; + public _direct property Scale => prop_get; + public _direct property Unsigned => prop_get; + public _direct property Zerofill => prop_get; + public _direct property Tag => prop_get; +} + +__PACKAGE__->PassThroughArgs; + +sub CTOR { + my $this = shift; + + $this->{$Scale} = 0 if not $this->{$Scale}; +} + +sub isEquals { + my ($a,$b) = @_; + + if (defined $a and defined $b) { + return $a == $b; + } else { + if (defined $a or defined $b) { + return 0; + } else { + return 1; + } + } +} + +sub isSame { + my ($this,$other) = @_; + + return ($this->{$Name} eq $other->{$Name} and isEquals($this->{$MaxLength},$other->{$MaxLength}) and isEquals($this->{$Scale},$other->{$Scale})); +} + +1; diff -r d59526f6310e -r 56cef8e3cda6 _test/Test/SQL/Schema.pm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/_test/Test/SQL/Schema.pm Mon Nov 09 01:39:31 2009 +0300 @@ -0,0 +1,8 @@ +package Test::SQL::Schema; +use strict; +use warnings; + +use base qw(IMPL::Test::Unit); +__PACKAGE__->PassThroughArgs; + +1;