changeset 32:56cef8e3cda6

+1
author Sergey
date Mon, 09 Nov 2009 01:39:31 +0300
parents d59526f6310e
children 0004faa276dc
files Lib/IMPL/SQL/Schema.pm Lib/IMPL/SQL/Schema/Column.pm Lib/IMPL/SQL/Schema/Constraint.pm Lib/IMPL/SQL/Schema/Constraint/ForeignKey.pm Lib/IMPL/SQL/Schema/Constraint/Index.pm Lib/IMPL/SQL/Schema/Constraint/PrimaryKey.pm Lib/IMPL/SQL/Schema/Constraint/Unique.pm Lib/IMPL/SQL/Schema/Table.pm Lib/IMPL/SQL/Schema/Traits.pm Lib/IMPL/SQL/Schema/Traits/mysql.pm Lib/IMPL/SQL/Schema/Type.pm _test/Test/SQL/Schema.pm
diffstat 12 files changed, 1369 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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<<IMPL::SQL::Traits>>
+
+=cut
--- /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; 
--- /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
--- /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
--- /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; 
--- /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
--- /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
--- /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;
--- /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;
--- /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;
--- /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;
--- /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;