diff lib/IMPL/SQL/Schema/Traits.pm @ 407:c6e90e02dd17 ref20150831

renamed Lib->lib
author cin
date Fri, 04 Sep 2015 19:40:23 +0300
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/IMPL/SQL/Schema/Traits.pm	Fri Sep 04 19:40:23 2015 +0300
@@ -0,0 +1,742 @@
+package IMPL::SQL::Schema::Traits;
+use strict;
+use IMPL::_core::version;
+use IMPL::Exception();
+
+use parent qw(IMPL::Object);
+
+# required for use with typeof operator
+use IMPL::SQL::Schema::Constraint::PrimaryKey();
+use IMPL::SQL::Schema::Constraint::Index();
+use IMPL::SQL::Schema::Constraint::Unique();
+use IMPL::SQL::Schema::Constraint::ForeignKey();
+
+###################################################
+
+package IMPL::SQL::Schema::Traits::Table;
+use base qw(IMPL::Object::Fields);
+
+use fields qw(
+    name
+    columns
+    constraints
+    options
+);
+
+sub CTOR {
+    my ($this,$table,$columns,$constraints,$options) = @_;
+    
+    $this->{name} = $table or die new IMPL::InvalidArgumentException(table => "A table name is required");
+    $this->{columns} = $columns if defined $columns;
+    $this->{constraints} = $constraints if defined $constraints;
+    $this->{options} = $options if defined $options;
+}
+
+###################################################
+
+package IMPL::SQL::Schema::Traits::Column;
+use base qw(IMPL::Object::Fields);
+
+use fields qw(
+    name
+    type
+    isNullable
+    defaultValue
+    tag
+);
+
+sub CTOR {
+    my ($this, $name, $type, %args) = @_;
+    
+    $this->{name} = $name or die new IMPL::InvalidArgumentException("name");
+    $this->{type} = $type or die new IMPL::InvalidArgumentException("type");
+    $this->{isNullable} = $args{isNullable} if exists $args{isNullable};
+    $this->{defaultValue} = $args{defaultValue} if exists $args{defaultValue};
+    $this->{tag} = $args{tag} if exists $args{tag};
+}
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::Constraint;
+use base qw(IMPL::Object::Fields);
+ 
+use fields qw(
+    name
+    columns
+);
+
+sub CTOR {
+    my ($this, $name, $columns) = @_;
+    
+    $this->{name} = $name;
+    $this->{columns} = $columns; # list of columnNames
+}
+
+sub constraintClass  {
+    die new IMPL::NotImplementedException();
+}
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::PrimaryKey;
+
+use base qw(IMPL::SQL::Schema::Traits::Constraint);
+
+__PACKAGE__->PassThroughArgs;
+
+use constant { constraintClass => typeof IMPL::SQL::Schema::Constraint::PrimaryKey };
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::Index;
+
+use base qw(IMPL::SQL::Schema::Traits::Constraint);
+
+__PACKAGE__->PassThroughArgs;
+
+use constant { constraintClass => typeof IMPL::SQL::Schema::Constraint::Index };
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::Unique;
+
+use base qw(IMPL::SQL::Schema::Traits::Constraint);
+
+__PACKAGE__->PassThroughArgs;
+
+use constant { constraintClass => typeof IMPL::SQL::Schema::Constraint::Unique };
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::ForeignKey;
+
+use base qw(IMPL::SQL::Schema::Traits::Constraint);
+use fields qw(
+    foreignTable
+    foreignColumns
+    onUpdate
+    onDelete
+);
+
+use constant { constraintClass => typeof IMPL::SQL::Schema::Constraint::ForeignKey };
+
+our %CTOR = (
+    'IMPL::SQL::Schema::Traits::Constraint' => sub { @_[0..1] }
+);
+
+sub CTOR {
+    my ($this,$foreignTable,$foreignColumns,%args) = @_[0,3..$#_];
+    
+    $this->{foreignTable} = $foreignTable;
+    $this->{foreignColumns} = $foreignColumns;
+    
+    $this->{onDelete} = $args{onDelete} if $args{onDelete};
+    $this->{onUpdate} = $args{onUpdate} if $args{onUpdate};
+}
+ 
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::CreateTable;
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        Table => '-IMPL::SQL::Schema::Traits::Table',
+        ArgException => '-IMPL::InvalidArgumentException',
+        OpException => '-IMPL::InvalidOperationException'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        table => PROP_RO,
+    ]
+};
+use IMPL::lang;
+
+sub CTOR {
+    my ($this,$table) = @_;
+    
+    die ArgException->new("table", "An object of IMPL::SQL::Schema::Traits::Table type is required")
+        unless is($table, Table);
+    
+    $this->table($table);
+}
+
+sub CanApply {
+    my ($this,$schema) = @_;
+    
+    return( $schema->GetTable( $this->table->{name} ) ? 0 : 1 );
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    my $args = {%{$this->table}};
+    
+    my $constraints = delete $args->{constraints} || [];
+    
+    my $table = $schema->AddTable($args);
+    
+    $table->AddConstraint($_->constraintClass, $_) foreach @{$constraints};
+}
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::DropTable;
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        ArgException => '-IMPL::InvalidArgumentException'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+    ]
+};
+
+sub CTOR {
+    my ($this,$tableName) = @_;
+    
+    $this->tableName($tableName) or die ArgException->new("tableName is required");
+}
+
+sub CanApply {
+    my ($this,$schema) = @_;
+    
+    return $schema->GetTable( $this->tableName ) ? 1 : 0;
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    $schema->RemoveTable($this->tableName);
+}
+
+##################################################
+
+package IMPL::SQL::Schema::Traits::RenameTable;
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        ArgException => '-IMPL::InvalidArgumentException'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+        tableNewName => PROP_RO,
+    ]
+};
+
+sub CTOR {
+    my ($this, $oldName, $newName) = @_;
+    
+    $this->tableName($oldName) or die ArgException->new("A table name is required");
+    $this->tableNewName($newName) or die ArgException->new("A new table name is required");
+}
+
+sub CanApply {
+    my ($this, $schema) = @_;
+    
+    return ($schema->GetTable($this->tableName) and not $schema->GetTable($this->tableNewName) ? 1 : 0 );
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    $schema->RenameTable($this->tableName, $this->tableNewName);
+    
+}
+
+#################################################
+
+package IMPL::SQL::Schema::Traits::AlterTableAddColumn;
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        Column => '-IMPL::SQL::Schema::Traits::Column',
+        ArgException => '-IMPL::InvalidArgumentException',
+        OpException => '-IMPL::InvalidOperationException'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+        column => PROP_RO,
+        position => PROP_RO
+    ]
+};
+use IMPL::lang;
+
+
+sub CTOR {
+    my ($this,$tableName,$column) = @_;
+    
+    $this->tableName($tableName) or die ArgException->new("A table name is required");
+    
+    die ArgException->new("A column should be a IMPL::SQL::Schema::Traits::Column object")
+        unless is($column, Column);
+        
+    $this->column($column);
+}
+
+sub CanApply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or return 0;
+    
+    return $table->GetColumn( $this->column->{name} ) ? 0 : 1;
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or die OpException->new("The specified table doesn't exists", $this->tableName);
+    
+    if ($this->position) {
+        $table->AddColumn($this->column);
+    } else {
+        $table->InsertColumn($this->column,$this->position);
+    }
+}
+
+#################################################
+
+package IMPL::SQL::Schema::Traits::AlterTableDropColumn;
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        FK => '-IMPL::SQL::Schema::Constraint::ForeignKey',
+        ArgException => '-IMPL::InvalidArgumentException',
+        OpException => '-IMPL::InvalidOperationException'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+        columnName => PROP_RO,
+    ]
+};
+use IMPL::lang;
+
+
+sub CTOR { 
+    my ($this,$table,$column) = @_;
+    
+    $this->tableName($table) or die ArgException->new(tableName => "A table name should be specified");
+    $this->columnName($column) or die ArgException->new(columnName => "A column name should be specified");
+}
+
+sub CanApply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or return 0;
+    
+    $table->GetColumn($this->columnName) or
+        return 0;
+    
+    # столбец 
+    return $table->GetColumnConstraints($this->columnName)
+        ? 0
+        : 1
+    ;
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or die OpException->new("The specified table doesn't exists", $this->tableName);
+    
+    $table->RemoveColumn($this->columnName);        
+}
+
+#################################################
+
+package IMPL::SQL::Schema::Traits::AlterTableChangeColumn;
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        Constraint => '-IMPL::SQL::Schema::Traits::Constraint',
+        ArgException => '-IMPL::InvalidArgumentException',
+        OpException => '-IMPL::InvalidOperationException'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+        columnName => PROP_RO,
+        columnType => PROP_RW,
+        defaultValue => PROP_RW,
+        isNullable => PROP_RW,
+        position => PROP_RW,
+        options => PROP_RW # hash diff format, (keys have a prefix '+' - add or update value, '-' remove value)
+    ]
+};
+use IMPL::lang;
+
+sub CTOR {
+    my ($this, $table,$column,%args) = @_;
+    
+    $this->tableName($table) or die ArgException->new(tableName => "A table name is required");
+    $this->columnName($column) or die ArgException->new(columnName => "A column name is required");
+    
+    $this->$_($args{$_})
+        for (grep exists $args{$_}, qw(columnType defaultValue isNullable options));
+}
+
+sub CanApply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or return 0;
+    
+    return $table->GetColumn($this->columnName) ? 1 : 0;
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or die OpException->new("The specified table doesn't exists", $this->tableName);
+    
+    my $column = $table->GetColumn($this->columnName)
+        or die OpException->new("The specified column doesn't exists", $this->tableName, $this->columnName);
+    
+    $column->SetType($this->columnType) if defined $this->columnType;
+    $column->SetNullable($this->isNullable) if defined $this->isNullable;
+    $column->SetDefaultValue($this->defaultValue) if defined $this->defaultValue;
+    $column->SetOptions($this->options) if defined $this->options;
+    
+    $table->SetColumnPosition($this->position)
+        if ($this->position);
+
+}
+
+#################################################
+
+package IMPL::SQL::Schema::Traits::AlterTableAddConstraint;
+
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        Constraint => '-IMPL::SQL::Schema::Traits::Constraint',
+        ArgException => '-IMPL::InvalidArgumentException',
+        FK => '-IMPL::SQL::Schema::Traits::ForeignKey'
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+        constraint => PROP_RO
+    ]
+};
+use IMPL::lang;
+
+sub CTOR {
+    my ($this,$table,$constraint) = @_;
+    
+    $this->tableName($table) or die ArgException->new( tableName => "A table name is required");
+    
+    die ArgException->new(constaraint => "A valid " . Constraint . " is required")
+        unless is($constraint, Constraint);
+        
+    $this->constraint($constraint);
+}
+
+sub CanApply {
+    my ($this, $schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or return 0;
+        
+    my $constraint = $this->constraint;
+    
+    my @columns = map $table->GetColumn($_), @{$constraint->{columns} || []};
+
+    # проверяем, что в таблице есть все столбцы для создания ограничения
+    return 0 if grep not($_), @columns;
+    
+    if (is($constraint,FK)) {
+        my $foreignTable = $schema->GetTable($constraint->{foreignTable})
+            or return 0;
+
+        my @foreignColumns = map $foreignTable->GetColumn($_), @{$constraint->{foreignColumns}||[]};
+        
+        # внешняя таблица имеет нужные столбцы
+        return 0
+            if grep not($_), @foreignColumns;
+
+        # типы столбцов во внешней таблице совпадают с типами столбцов ограничения
+        return 0
+            if grep not($columns[$_]->type->SameValue($foreignColumns[$_]->type)), (0 .. $#columns);
+    }
+    
+    return 1;
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or die IMPL::InvalidOperationException->new("The specified table doesn't exists", $this->tableName);
+        
+    my $constraint = $this->constraint;
+    
+    if (is($constraint,FK)) {
+        my $args = { %$constraint };
+        $args->{referencedTable} = $schema->GetTable(delete $args->{foreignTable});
+        $args->{referencedColumns} = delete $args->{foreignColumns};
+        $table->AddConstraint($constraint->constraintClass, $args);
+    } else {
+        $table->AddConstraint($constraint->constraintClass, $constraint);
+    }
+    
+}
+
+#################################################
+
+package IMPL::SQL::Schema::Traits::AlterTableDropConstraint;
+use IMPL::Const qw(:prop);
+use IMPL::declare {
+    require => {
+        PK => '-IMPL::SQL::Schema::Constraint::PrimaryKey'        
+    },
+    base => [
+        '-IMPL::SQL::Schema::Traits' => undef
+    ],
+    props => [
+        tableName => PROP_RO,
+        constraintName => PROP_RO
+    ]
+};
+use IMPL::lang qw(is);
+
+sub CTOR {
+    my ($this,$table,$constraint) = @_;
+    
+    die new IMPL::InvalidArgumentException( tableName => "A table name is required" ) unless $table;
+    die new IMPL::InvalidArgumentException( constraintName => "A constraint name is required" ) unless $constraint;
+    
+    $this->tableName($table);
+    $this->constraintName($constraint);
+}
+
+sub CanApply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or return 0;
+    
+    my $constraint = $table->GetConstraint($this->constraintName)
+        or return 0;
+    
+    # есть ли внешние ключи на данную таблицу    
+    return (
+        is($constraint,PK)
+        && values( %{$constraint->connectedFK || {}} )
+        ? 0
+        : 1
+    );
+}
+
+sub Apply {
+    my ($this,$schema) = @_;
+    
+    my $table = $schema->GetTable($this->tableName)
+        or die IMPL::InvalidOperationException->new("The specified table doesn't exists", $this->tableName);
+    
+    $table->RemoveConstraint($this->constraintName);
+}
+
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+C<IMPL::SQL::Traits> - Операции над объектками SQL схемы.
+
+=head1 DESCRIPTION
+
+Изменения схемы могу быть представлены в виде последовательности примитивных операций.
+Правила выполнения последовательности примитывных действий могут варьироваться
+в зависимости от процессора, который их выполняет. Например C<IMPL::SQL::Schema::Traits::Processor>.
+
+Данные, которые содержаться в примитивных операциях не могут существовать независимо от схемы.
+
+=head1 OPERATIONS
+
+=head2 General
+
+Методы обще для всех примитивных операций.
+
+=head3 C<CanApply($schema)>
+
+Определяет возможность применения операции к указанной схеме.
+
+Возвращаемое значение:
+
+=over
+
+=item C<true>
+
+Операция приминима к схеме.
+
+=item C<false>
+
+Операция не может быть применена к схеме.
+
+=back
+
+=head3 C<Apply($schema)>
+
+Применяет операцию к указанной схеме.
+
+=head2 Primitive operations
+
+=head3 C<IMPL::SQL::Schema::Traits::CreateTable>
+
+Создает таблицу
+
+=head4 C<CTOR($table)>
+
+=head4 C<[get]table>
+
+C<IMPL::SQL::Schema::Traits::Table> - описание создаваемой таблицы
+
+=head3 C<IMPL::SQL::Schema::Traits::DropTable>
+
+Удалает таблицу по имени
+
+=head4 C<CTOR($tableName)>
+
+=head4 C<[get]tableName>
+
+Имя удаляемой таблицы
+
+=head3 C<IMPL::SQL::Schema::Traits::RenameTable>
+
+=head4 C<CTOR($tableName,$tableNewName)>
+
+=head4 C<[get]tableName>
+
+Имя таблицы, которую требуется переименовать
+
+=head4 C<[get]tableNewName>
+
+Новое имя таблицы
+
+=head3 C<IMPL::SQL::Schema::Traits::AlterTableAddColumn>
+
+Добавляет столбец в таблицу
+
+=head4 C<CTOR($tableName,$column,$position)>
+
+=head4 C<[get]tableName>
+
+Имя таблицы в которую нужно добавить столбец
+
+=head4 C<[get]column>
+
+C<IMPL::SQL::Schema::Traits::Column> - описание столбца который нужно добавить
+
+=head4 C<[get]position>
+
+Позиция на которую нужно вставить столбец
+
+=head3 C<IMPL::SQL::Schema::Traits::AlterTableDropColumn>
+
+Удаляет столбец из таблицы
+
+=head4 C<CTOR($tableName,$columnName)>
+
+=head4 C<[get]tableName>
+
+Имя таблицы в которой нужно удалить столбец
+
+=head4 C<[get]columnName>
+
+Имя столбца для удаления
+
+=head3 C<IMPL::SQL::Schema::Traits::AlterTableChangeColumn>
+
+Меняет описание столбца
+
+=head4 C<CTOR($tableName,$columnName,%args)>
+
+C<%args> - хеш, ключами которого являются оставшиеся свойства создаваемого объекта.
+
+=head4 C<[get]tableName>
+
+Имя таблицы в которой находится столбец.
+
+=head4 C<[get]columnName>
+
+Имя столбца для изменения
+
+=head4 C<[get]columnType>
+
+Новый тип столбца. Не задан, если тип не меняется
+
+=head4 C<[get]defaultValue>
+
+Значение по умолчанию. Не задано, если не меняется
+
+=head4 C<[get]isNullable>
+
+Может ли столбец содержать C<NULL>. Не задано, если не меняется.
+
+=head4 C<[get]options>
+
+Хеш опций, не задан, если опции не меняются. Данный хеш содержит разничу между
+старыми и новыми значениями свойства C<tag> столбца.
+
+
+=head3 C<IMPL::SQL::Schema::Traits::AlterTableAddConstraint>
+
+Базовый класс для операций по добавлению ограничений
+
+=head4 C<CTOR($tableName,$constraint)>
+
+=head4 C<[get]tableName>
+
+Имя таблицы в которую добавляется ограничение.
+
+=head4 C<[get]constraint>
+
+C<IMPL::SQL::Schema::Traits::Constraint> - описние ограничения, которое нужно добавить.
+
+=head3 C<IMPL::SQL::Schema::Traits::AlterTableDropConstraint>
+
+Удаляет ограничение на таблицу
+
+=head4 C<CTOR($tableName,$constraintName)>
+
+=head4 C<[get]tableName>
+
+Имя таблицы в которой требуется удалить ограничение.
+
+=head4 C<[get]constraintName>
+
+Имя ограничения для удаления.
+
+=cut