| 407 | 1 package IMPL::SQL::Schema::MySQL::Formatter; | 
|  | 2 use strict; | 
|  | 3 | 
|  | 4 use IMPL::lang qw(is); | 
|  | 5 use IMPL::require { | 
|  | 6     Exception         => 'IMPL::Exception', | 
|  | 7     OpException       => '-IMPL::InvalidOperationException', | 
|  | 8     ArgException      => '-IMPL::InvalidArgumentException', | 
|  | 9     PrimaryKey        => '-IMPL::SQL::Schema::Constraint::PrimaryKey', | 
|  | 10     UniqueIndex       => '-IMPL::SQL::Schema::Constraint::Unique', | 
|  | 11     Index             => '-IMPL::SQL::Schema::Constraint::Index', | 
|  | 12     ForeignKey        => '-IMPL::SQL::Schema::Constraint::ForeignKey', | 
|  | 13     CharType          => '-IMPL::SQL::Schema::MySQL::CharType', | 
|  | 14     EnumType          => '-IMPL::SQL::Schema::MySQL::EnumType', | 
|  | 15     TraitsDropTable   => '-IMPL::SQL::Schema::Traits::DropTable', | 
|  | 16     TraitsCreateTable              => '-IMPL::SQL::Schema::Traits::CreateTable', | 
|  | 17     TraitsAlterTableDropConstraint => '-IMPL::SQL::Schema::Traits::AlterTableDropConstraint', | 
|  | 18     TraitsAlterTableAddConstraint  => '-IMPL::SQL::Schema::Traits::AlterTableAddConstraint', | 
|  | 19     TraitsAlterTableDropColumn     => '-IMPL::SQL::Schema::Traits::AlterTableDropColumn', | 
|  | 20     TraitsAlterTableAddColumn      => '-IMPL::SQL::Schema::Traits::AlterTableAddColumn', | 
|  | 21     TraitsAlterTableChangeColumn   => '-IMPL::SQL::Schema::Traits::AlterTableChangeColumn' | 
|  | 22 }; | 
|  | 23 | 
|  | 24 our %TRAITS_FORMATS = ( | 
|  | 25     TraitsDropTable,                'FormatDropTable', | 
|  | 26     TraitsCreateTable,              'FormatCreateTable', | 
|  | 27     TraitsAlterTableDropConstraint, 'FormatAlterTableDropConstraint', | 
|  | 28     TraitsAlterTableAddConstraint,  'FormatAlterTableAddConstraint', | 
|  | 29     TraitsAlterTableDropColumn,     'FormatAlterTableDropColumn', | 
|  | 30     TraitsAlterTableAddColumn,      'FormatAlterTableAddColumn', | 
|  | 31     TraitsAlterTableChangeColumn,   'FormatAlterTableChangeColumn' | 
|  | 32 ); | 
|  | 33 | 
|  | 34 sub quote { | 
|  | 35     my $self = shift; | 
|  | 36 | 
|  | 37     if (wantarray) { | 
|  | 38         return map { my $str = $_; $str =~ s/'/''/g; "'$str'"; } @_; | 
|  | 39     } | 
|  | 40     else { | 
|  | 41         return join '', map { my $str = $_; $str =~ s/'/''/g; "'$str'"; } @_; | 
|  | 42     } | 
|  | 43 } | 
|  | 44 | 
|  | 45 sub quote_names { | 
|  | 46     my $self = shift; | 
|  | 47 | 
|  | 48     if (wantarray) { | 
|  | 49         return map { my $str = $_; $str =~ s/`/``/g; "`$str`"; } @_; | 
|  | 50     } | 
|  | 51     else { | 
|  | 52         return join '', map { my $str = $_; $str =~ s/`/``/g; "`$str`"; } @_; | 
|  | 53     } | 
|  | 54 } | 
|  | 55 | 
|  | 56 sub formatTypeNameInteger { | 
|  | 57     my ( $self, $type ) = @_; | 
|  | 58 | 
|  | 59     return | 
|  | 60         $type->name | 
|  | 61       . ( $type->maxLength ? '(' . $type->maxLength . ')' : '' ) | 
|  | 62       . ( $type->unsigned  ? ' UNSIGNED'                  : '' ) | 
|  | 63       . ( $type->zerofill  ? ' ZEROFILL'                  : '' ); | 
|  | 64 } | 
|  | 65 | 
|  | 66 sub formatTypeNameReal { | 
|  | 67     my ( $self, $type ) = @_; | 
|  | 68 | 
|  | 69     return $type->name | 
|  | 70       . ( $type->maxLength | 
|  | 71         ? '(' . $type->maxLength . ', ' . $type->scale . ')' | 
|  | 72         : '' ) | 
|  | 73       . ( $type->unsigned ? ' UNSIGNED' : '' ) | 
|  | 74       . ( $type->zerofill ? ' ZEROFILL' : '' ); | 
|  | 75 } | 
|  | 76 | 
|  | 77 sub formatTypeNameNumeric { | 
|  | 78     my ( $self, $type ) = @_; | 
|  | 79     $type->maxLength | 
|  | 80       or die ArgException->new( | 
|  | 81         type => 'The length and precission must be specified', | 
|  | 82         $type->name | 
|  | 83       ); | 
|  | 84     return $type->name | 
|  | 85       . ( $type->maxLength | 
|  | 86         ? '(' . $type->maxLength . ', ' . $type->scale . ')' | 
|  | 87         : '' ) | 
|  | 88       . ( $type->unsigned ? ' UNSIGNED' : '' ) | 
|  | 89       . ( $type->zerofill ? ' ZEROFILL' : '' ); | 
|  | 90 } | 
|  | 91 | 
|  | 92 sub formatTypeName { | 
|  | 93     my ( $self, $type ) = @_; | 
|  | 94     return $type->name; | 
|  | 95 } | 
|  | 96 | 
|  | 97 sub formatTypeNameChar { | 
|  | 98     my ( $self, $type ) = @_; | 
|  | 99 | 
|  | 100     return ($type->name . '(' | 
|  | 101           . $type->MaxLength . ')' | 
|  | 102           . ( is( $type, CharType ) ? $type->encoding : '' ) ); | 
|  | 103 } | 
|  | 104 | 
|  | 105 sub formatTypeNameVarChar { | 
|  | 106     my ( $self, $type ) = @_; | 
|  | 107 | 
|  | 108     return ($type->name . '(' | 
|  | 109           . $type->maxLength . ')' | 
|  | 110           . ( is( $type, CharType ) ? $type->encoding : '' ) ); | 
|  | 111 } | 
|  | 112 | 
|  | 113 sub formatTypeNameEnum { | 
|  | 114     my ( $self, $type ) = @_; | 
|  | 115 | 
|  | 116     die ArgException->new( type => 'Invalid enum type' ) | 
|  | 117       unless is( $type, EnumType ); | 
|  | 118     return ($type->name . '(' | 
|  | 119           . join( ',', map { $self->quote($_) } $type->enumValues ) | 
|  | 120           . ')' ); | 
|  | 121 } | 
|  | 122 | 
|  | 123 sub formatStringValue { | 
|  | 124     my ( $self, $value ) = @_; | 
|  | 125 | 
|  | 126     if ( ref $value eq 'SCALAR' ) { | 
|  | 127         return $$value; | 
|  | 128     } | 
|  | 129     else { | 
|  | 130         return $self->quote($value); | 
|  | 131     } | 
|  | 132 } | 
|  | 133 | 
|  | 134 sub formatNumberValue { | 
|  | 135     my ( $self, $value ) = @_; | 
|  | 136 | 
|  | 137     if ( ref $value eq 'SCALAR' ) { | 
|  | 138         return $$value; | 
|  | 139     } | 
|  | 140     else { | 
|  | 141         $value =~ /^((\+|-)\s*)?\d+(\.\d+)?(e(\+|-)?\d+)?$/ | 
|  | 142           or die ArgException->new( | 
|  | 143             value => 'The specified value isn\'t a valid number', | 
|  | 144             $value | 
|  | 145           ); | 
|  | 146         return $value; | 
|  | 147     } | 
|  | 148 } | 
|  | 149 | 
|  | 150 our %TYPES_FORMATS = ( | 
|  | 151     TINYINT => { | 
|  | 152         formatType  => \&formatTypeNameInteger, | 
|  | 153         formatValue => \&formatNumberValue | 
|  | 154     }, | 
|  | 155     SMALLINT => { | 
|  | 156         formatType  => \&formatTypeNameInteger, | 
|  | 157         formatValue => \&formatNumberValue | 
|  | 158     }, | 
|  | 159     MEDIUMINT => { | 
|  | 160         formatType  => \&formatTypeNameInteger, | 
|  | 161         formatValue => \&formatNumberValue | 
|  | 162     }, | 
|  | 163     INT => { | 
|  | 164         formatType  => \&formatTypeNameInteger, | 
|  | 165         formatValue => \&formatNumberValue | 
|  | 166     }, | 
|  | 167     INTEGER => { | 
|  | 168         formatType  => \&formatTypeNameInteger, | 
|  | 169         formatValue => \&formatNumberValue | 
|  | 170     }, | 
|  | 171     BIGINT => { | 
|  | 172         formatType  => \&formatTypeNameInteger, | 
|  | 173         formatValue => \&formatNumberValue | 
|  | 174     }, | 
|  | 175     REAL => { | 
|  | 176         formatType  => \&formatTypeNameReal, | 
|  | 177         formatValue => \&formatNumberValue | 
|  | 178     }, | 
|  | 179     DOUBLE => { | 
|  | 180         formatType  => \&formatTypeNameReal, | 
|  | 181         formatValue => \&formatNumberValue | 
|  | 182     }, | 
|  | 183     FLOAT => { | 
|  | 184         formatType  => \&formatTypeNameReal, | 
|  | 185         formatValue => \&formatNumberValue | 
|  | 186     }, | 
|  | 187     DECIMAL => { | 
|  | 188         formatType  => \&formatTypeNameNumeric, | 
|  | 189         formatValue => \&formatNumberValue | 
|  | 190     }, | 
|  | 191     NUMERIC => { | 
|  | 192         formatType  => \&formatTypeNameNumeric, | 
|  | 193         formatValue => \&formatNumberValue | 
|  | 194     }, | 
|  | 195     DATE => { | 
|  | 196         formatType  => \&formatTypeName, | 
|  | 197         formatValue => \&formatStringValue | 
|  | 198     }, | 
|  | 199     TIME => { | 
|  | 200         formatType  => \&formatTypeName, | 
|  | 201         formatValue => \&formatStringValue | 
|  | 202     }, | 
|  | 203     TIMESTAMP => { | 
|  | 204         formatType  => \&formatTypeName, | 
|  | 205         formatValue => \&formatStringValue | 
|  | 206     }, | 
|  | 207     DATETIME => { | 
|  | 208         formatType  => \&formatTypeName, | 
|  | 209         formatValue => \&formatStringValue | 
|  | 210     }, | 
|  | 211     CHAR => { | 
|  | 212         formatType  => \&formatTypeNameChar, | 
|  | 213         formatValue => \&formatStringValue | 
|  | 214     }, | 
|  | 215     VARCHAR => { | 
|  | 216         formatType  => \&formatTypeNameVarChar, | 
|  | 217         formatValue => \&formatStringValue | 
|  | 218     }, | 
|  | 219     TINYBLOB => { | 
|  | 220         formatType  => \&formatTypeName, | 
|  | 221         formatValue => \&formatStringValue | 
|  | 222     }, | 
|  | 223     BLOB => { | 
|  | 224         formatType  => \&formatTypeName, | 
|  | 225         formatValue => \&formatStringValue | 
|  | 226     }, | 
|  | 227     MEDIUMBLOB => { | 
|  | 228         formatType  => \&formatTypeName, | 
|  | 229         formatValue => \&formatStringValue | 
|  | 230     }, | 
|  | 231     LONGBLOB => { | 
|  | 232         formatType  => \&formatTypeName, | 
|  | 233         formatValue => \&formatStringValue | 
|  | 234     }, | 
|  | 235     TINYTEXT => { | 
|  | 236         formatType  => \&formatTypeName, | 
|  | 237         formatValue => \&formatStringValue | 
|  | 238     }, | 
|  | 239     TEXT => { | 
|  | 240         formatType  => \&formatTypeName, | 
|  | 241         formatValue => \&formatStringValue | 
|  | 242     }, | 
|  | 243     MEDIUMTEXT => { | 
|  | 244         formatType  => \&formatTypeName, | 
|  | 245         formatValue => \&formatStringValue | 
|  | 246     }, | 
|  | 247     LONGTEXT => { | 
|  | 248         formatType  => \&formatTypeName, | 
|  | 249         formatValue => \&formatStringValue | 
|  | 250     }, | 
|  | 251     ENUM => { | 
|  | 252         formatType  => \&formatTypeNameEnum, | 
|  | 253         formatValue => \&formatStringValue | 
|  | 254     }, | 
|  | 255     SET => { | 
|  | 256         formatType  => \&formatTypeNameEnum, | 
|  | 257         formatValue => \&formatStringValue | 
|  | 258     } | 
|  | 259 ); | 
|  | 260 | 
|  | 261 sub FormatTypeName { | 
|  | 262     my ( $self, $type ) = @_; | 
|  | 263 | 
|  | 264     my $fn = $TYPES_FORMATS{ $type->name }{formatType} | 
|  | 265       or die ArgException->new( type => "The specified type is unknown", | 
|  | 266         $type->name ); | 
|  | 267 | 
|  | 268     return $self->$fn($type); | 
|  | 269 } | 
|  | 270 | 
|  | 271 sub FormatValue { | 
|  | 272     my ( $self, $value, $type ) = @_; | 
|  | 273 | 
|  | 274     my $fn = $TYPES_FORMATS{ $type->name }{formatValue} | 
|  | 275       or die ArgException->new( type => "The specified type is unknown", | 
|  | 276         $type->name ); | 
|  | 277 | 
|  | 278     return $self->$fn( $value, $type ); | 
|  | 279 } | 
|  | 280 | 
|  | 281 sub FormatColumn { | 
|  | 282     my ( $self, $column ) = @_; | 
|  | 283 | 
|  | 284     my @parts = ( | 
|  | 285         $self->quote_names( $column->{name} ), | 
|  | 286         $self->FormatTypeName( $column->{type} ), | 
|  | 287         $column->{isNullable} ? 'NULL' : 'NOT NULL' | 
|  | 288     ); | 
|  | 289 | 
|  | 290     push @parts, $self->FormatValue( $column->{defaultValue}, $column->{type} ) | 
|  | 291       if $column->{defaultValue}; | 
|  | 292 | 
|  | 293     push @parts, 'AUTO_INCREMENT' | 
|  | 294       if $column->{tag} and $column->{tag}->{auto_increment}; | 
|  | 295 | 
|  | 296     return join ' ', @parts; | 
|  | 297 } | 
|  | 298 | 
|  | 299 sub FormatCreateTable { | 
|  | 300     my ( $self, $op ) = @_; | 
|  | 301 | 
|  | 302     my $table = $op->table; | 
|  | 303 | 
|  | 304     my @lines; | 
|  | 305     my @body; | 
|  | 306 | 
|  | 307     push @lines, "CREATE TABLE " . $self->quote_names($table->{name}) . "("; | 
|  | 308 | 
|  | 309     push @body, map { "  " . $self->FormatColumn($_) } @{ $table->{columns} } | 
|  | 310         if $table->{columns}; | 
|  | 311 | 
|  | 312     push @body, map { "  " . $self->FormatConstraint($_) } @{ $table->{constraints} } | 
|  | 313         if $table->{constraints}; | 
|  | 314 | 
|  | 315     push @lines, join(",\n", @body); | 
|  | 316 | 
|  | 317     push @lines, ");"; | 
|  | 318 | 
|  | 319     return join "\n", @lines; | 
|  | 320 } | 
|  | 321 | 
|  | 322 sub FormatDropTable { | 
|  | 323     my ( $self, $op ) = @_; | 
|  | 324 | 
|  | 325     return join ' ', 'DROP TABLE', $self->quote_names( $op->tableName ), ';'; | 
|  | 326 } | 
|  | 327 | 
|  | 328 sub FormatRenameTable { | 
|  | 329     my ( $self, $op ) = @_; | 
|  | 330 | 
|  | 331     return join ' ', | 
|  | 332       'ALTER TABLE', | 
|  | 333       $self->quote_names( $op->tableName ), | 
|  | 334       'RENAME TO', | 
|  | 335       $self->quote_names( $op->tableNewName ), | 
|  | 336       ';'; | 
|  | 337 } | 
|  | 338 | 
|  | 339 sub FormatAlterTableAddColumn { | 
|  | 340     my ( $self, $op, $schema ) = @_; | 
|  | 341 | 
|  | 342     my @parts = ( | 
|  | 343         'ALTER TABLE',$self->quote_names($op->tableName), 'ADD COLUMN', | 
|  | 344         $self->FormatColumn( $op->column ) | 
|  | 345     ); | 
|  | 346 | 
|  | 347     if ( defined $op->position ) { | 
|  | 348 | 
|  | 349         # mysql supports column reordering | 
|  | 350         # the new location is specified relative to the previous column | 
|  | 351         # to determine the name of the previous column we need to ask the schema | 
|  | 352 | 
|  | 353         my $table = $schema->GetTable( $op->tableName ); | 
|  | 354 | 
|  | 355         if ( $op->position == 0 ) { | 
|  | 356             push @parts, 'FIRST'; | 
|  | 357         } | 
|  | 358         else { | 
|  | 359             push @parts, 'AFTER'; | 
|  | 360 | 
|  | 361             my $prevColumn = $table->GetColumnAt( $op->position - 1 ); | 
|  | 362             push @parts, $self->quote_names( $prevColumn->{name} ); | 
|  | 363         } | 
|  | 364     } | 
|  | 365 | 
|  | 366     push @parts, ';'; | 
|  | 367 | 
|  | 368     return join ' ', @parts; | 
|  | 369 } | 
|  | 370 | 
|  | 371 sub FormatAlterTableDropColumn { | 
|  | 372     my ( $self, $op ) = @_; | 
|  | 373 | 
|  | 374     return join ' ', | 
|  | 375       'ALTER TABLE', | 
|  | 376       $self->quote_names( $op->tableName ), | 
|  | 377       'DROP COLUMN', | 
|  | 378       $self->quote_names( $op->columnName ), | 
|  | 379       ';'; | 
|  | 380 } | 
|  | 381 | 
|  | 382 sub FormatAlterTableChangeColumn { | 
|  | 383     my ( $self, $op, $schema ) = @_; | 
|  | 384 | 
|  | 385     my $table  = $schema->GetTable( $op->tableName ); | 
|  | 386     my $column = $table->GetColumn( $op->columnName ); | 
|  | 387 | 
|  | 388     my @parts = ( | 
|  | 389         'ALTER TABLE', | 
|  | 390         $self->quote_names( $op->tableName ), | 
|  | 391         'MODIFY COLUMN', | 
|  | 392         $self->quote_names( $op->columnName ), | 
|  | 393         $self->FormatColumn( $self->_Column2Traits($column) ) | 
|  | 394     ); | 
|  | 395 | 
|  | 396     if ( defined $op->position ) { | 
|  | 397 | 
|  | 398         # mysql supports column reordering | 
|  | 399         # the new location is specified relative to the previous column | 
|  | 400         # to determine the name of the previous column we need to ask the schema | 
|  | 401 | 
|  | 402         if ( $op->position == 0 ) { | 
|  | 403             push @parts, 'FIRST'; | 
|  | 404         } | 
|  | 405         else { | 
|  | 406             push @parts, 'AFTER'; | 
|  | 407 | 
|  | 408             my $prevColumn = $table->GetColumnAt( $op->position - 1 ); | 
|  | 409             push @parts, $self->quote_names( $prevColumn->{name} ); | 
|  | 410         } | 
|  | 411     } | 
|  | 412 | 
|  | 413     push @parts, ';'; | 
|  | 414     return join ' ', @parts; | 
|  | 415 } | 
|  | 416 | 
|  | 417 sub FormatConstraint { | 
|  | 418     my ($self,$constraint) = @_; | 
|  | 419 | 
|  | 420     my @fkRules = | 
|  | 421       ( 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT', 'NO ACTION' ); | 
|  | 422 | 
|  | 423     my @parts; | 
|  | 424 | 
|  | 425     if ( $constraint->constraintClass eq ForeignKey ) { | 
|  | 426         push @parts, | 
|  | 427           'CONSTRAINT', | 
|  | 428           $self->quote_names( $constraint->{name} ), | 
|  | 429           'FOREIGN KEY', | 
|  | 430           $self->quote_names( $constraint->{name} ), | 
|  | 431           '(', | 
|  | 432           join( ', ', $self->quote_names( @{ $constraint->{columns} || [] } ) ), | 
|  | 433           ')', | 
|  | 434           'REFERENCES', $self->quote_names( $constraint->{foreignTable} ), '(', | 
|  | 435           join( ', ', | 
|  | 436             $self->quote_names( @{ $constraint->{foreignColumns} || [] } ) ), | 
|  | 437           ')'; | 
|  | 438 | 
|  | 439         if ( my $rule = $constraint->{onDelete} ) { | 
|  | 440         	$rule = uc($rule); | 
|  | 441             grep $_ eq $rule, @fkRules | 
|  | 442               or die Exception->new( "Invalid onDelete rule specified", | 
|  | 443                 $constraint->{name}, $rule ); | 
|  | 444 | 
|  | 445             push @parts, 'ON DELETE', $rule; | 
|  | 446         } | 
|  | 447 | 
|  | 448         if ( my $rule = $constraint->{onUpdate} ) { | 
|  | 449         	$rule = uc($rule); | 
|  | 450             grep $_ eq $rule, @fkRules | 
|  | 451               or die Exception->new( "Invalid onUpdate rule specified", | 
|  | 452                 $constraint->{name}, $rule ); | 
|  | 453 | 
|  | 454             push @parts, 'ON UPDATE', $rule; | 
|  | 455         } | 
|  | 456 | 
|  | 457     } | 
|  | 458     else { | 
|  | 459         if ( $constraint->constraintClass eq PrimaryKey ) { | 
|  | 460             push @parts, 'PRIMARY KEY'; | 
|  | 461 | 
|  | 462         } | 
|  | 463         elsif ( $constraint->constraintClass eq UniqueIndex ) { | 
|  | 464             push @parts, 'UNIQUE', $self->quote_names( $constraint->{name} ); | 
|  | 465         } | 
|  | 466         elsif ( $constraint->constraintClass eq Index ) { | 
|  | 467             push @parts, 'INDEX', $self->quote_names( $constraint->{name} ); | 
|  | 468         } | 
|  | 469         else { | 
|  | 470             die Exception->new( 'Invalid constraint type', | 
|  | 471                 $constraint->constraintClass ); | 
|  | 472         } | 
|  | 473 | 
|  | 474         push @parts, | 
|  | 475           '(', | 
|  | 476           join( ', ', $self->quote_names( @{ $constraint->{columns} || [] } ) ), | 
|  | 477           ')'; | 
|  | 478     } | 
|  | 479 | 
|  | 480 | 
|  | 481     return join ' ', @parts; | 
|  | 482 } | 
|  | 483 | 
|  | 484 sub FormatAlterTableAddConstraint { | 
|  | 485     my ( $self, $op ) = @_; | 
|  | 486 | 
|  | 487     return join(' ', | 
|  | 488         'ALTER TABLE', | 
|  | 489         $self->quote_names( $op->tableName ), | 
|  | 490         'ADD', | 
|  | 491         $self->FormatConstraint($op->constraint), | 
|  | 492         ';' | 
|  | 493     ); | 
|  | 494 } | 
|  | 495 | 
|  | 496 sub FormatAlterTableDropConstraint { | 
|  | 497     my ( $self, $op, $constraintType ) = @_; | 
|  | 498 | 
|  | 499     my @parts = ( 'ALTER TABLE', $self->quote_names( $op->tableName ), 'DROP' ); | 
|  | 500 | 
|  | 501     if ( $constraintType eq PrimaryKey ) { | 
|  | 502         push @parts, 'PRIMARY KEY'; | 
|  | 503     } | 
|  | 504     elsif ( $constraintType eq ForeignKey ) { | 
|  | 505         push @parts, 'FOREIGN KEY', $self->quote_names( $op->constraintName ); | 
|  | 506     } | 
|  | 507     elsif ( $constraintType eq UniqueIndex or $constraintType eq Index ) { | 
|  | 508         push @parts, 'INDEX', $self->quote_names( $op->constraintName ); | 
|  | 509     } | 
|  | 510     else { | 
|  | 511         die Exception->new( | 
|  | 512             'Invalid constraint type', $op->tableName, | 
|  | 513             $op->constraintName,       $constraintType | 
|  | 514         ); | 
|  | 515     } | 
|  | 516 | 
|  | 517     push @parts, ';'; | 
|  | 518 | 
|  | 519     return join ' ', @parts; | 
|  | 520 } | 
|  | 521 | 
|  | 522 sub Format { | 
|  | 523     my $self = shift; | 
|  | 524     my ($op) = @_; | 
|  | 525 | 
|  | 526     my $formatter = $TRAITS_FORMATS{ref $op} | 
|  | 527         or die OpException->new("Don't know how to format the specified operation", $op); | 
|  | 528 | 
|  | 529     $self->$formatter(@_); | 
|  | 530 } | 
|  | 531 | 
|  | 532 sub _Column2Traits { | 
|  | 533     my ( $self, $column, %options ) = @_; | 
|  | 534 | 
|  | 535     return new IMPL::SQL::Schema::Traits::Column( | 
|  | 536         $column->name, | 
|  | 537         $column->type, | 
|  | 538         isNullable   => $column->isNullable, | 
|  | 539         defaultValue => $column->defaultValue, | 
|  | 540         tag          => $column->tag, | 
|  | 541         %options | 
|  | 542     ); | 
|  | 543 } | 
|  | 544 | 
|  | 545 1; | 
|  | 546 | 
|  | 547 __END__ | 
|  | 548 | 
|  | 549 =pod | 
|  | 550 | 
|  | 551 =head1 NAME | 
|  | 552 | 
|  | 553 C<IMPL::SQL::Traits::MysqlFormatter> - преобразует операции над схемой в C<SQL> | 
|  | 554 выражения. | 
|  | 555 | 
|  | 556 =head1 DESCRIPTION | 
|  | 557 | 
|  | 558 Используется для форматирования операций изменения схемы БД. Осуществляет | 
|  | 559 правильное экранирование имен, форматирование значений, имен типов данных. | 
|  | 560 | 
|  | 561 =cut |