comparison Lib/IMPL/SQL/Schema/Traits/mysql.pm @ 32:56cef8e3cda6

+1
author Sergey
date Mon, 09 Nov 2009 01:39:31 +0300
parents
children 32d2350fccf9
comparison
equal deleted inserted replaced
31:d59526f6310e 32:56cef8e3cda6
1 package IMPL::SQL::Schema::Traits::mysql::Handler;
2 use strict;
3 use base qw(IMPL::Object);
4 use IMPL::Class::Property;
5 use IMPL::Class::Property::Direct;
6
7 BEGIN {
8 public _direct property SqlBatch => prop_none;
9 }
10
11 sub formatTypeNameInteger {
12 my ($type) = @_;
13
14 return $type->Name.($type->MaxLength ? '('.$type->MaxLength.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : '');
15 }
16
17 sub formatTypeNameReal {
18 my ($type) = @_;
19
20 return $type->Name.($type->MaxLength ? '('.$type->MaxLength.', '.$type->Scale.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : '');
21 }
22
23 sub formatTypeNameNumeric {
24 my ($type) = @_;
25 $type->MaxLength or die new IMPL::InvalidArgumentException('The length and precission must be specified',$type->Name);
26 return $type->Name.($type->MaxLength ? '('.$type->MaxLength.', '.$type->Scale.')' : '').($type->Unsigned ? ' UNSIGNED': '').($type->Zerofill ? ' ZEROFILL' : '');
27 }
28
29 sub formatTypeName {
30 my ($type) = @_;
31 return $type->Name;
32 }
33
34 sub formatTypeNameChar {
35 my ($type) = @_;
36
37 return (
38 $type->Name.'('.$type->MaxLength.')'. (UNIVERSAL::isa($type,'IMPL::SQL::Schema::Type::mysql::CHAR') ? $type->Encoding : '')
39 );
40 }
41
42 sub formatTypeNameVarChar {
43 my ($type) = @_;
44
45 return (
46 $type->Name.'('.$type->MaxLength.')'. (UNIVERSAL::isa($type,'IMPL::SQL::Schema::Type::mysql::VARCHAR') ? $type->Encoding : '')
47 );
48 }
49
50 sub formatTypeNameEnum {
51 my ($type) = @_;
52 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'));
53 return (
54 $type->Name.'('.join(',',map {quote($_)} $type->Values).')'
55 );
56 }
57
58 sub quote{
59 if (wantarray) {
60 return map { my $str=$_; $str=~ s/'/''/g; "'$str'"; } @_;
61 } else {
62 return join '',map { my $str=$_; $str=~ s/'/''/g; "'$str'"; } @_;
63 }
64 }
65
66 sub quote_names {
67 if (wantarray) {
68 return map { my $str=$_; $str=~ s/`/``/g; "`$str`"; } @_;
69 } else {
70 return join '',map { my $str=$_; $str=~ s/`/``/g; "`$str`"; } @_;
71 }
72 }
73
74 sub formatStringValue {
75 my ($value) = @_;
76
77 if (ref $value) {
78 if (UNIVERSAL::isa($value,'IMPL::SQL::Schema::mysql::Expression')) {
79 return $value->as_string;
80 } else {
81 die new Exception('Can\'t format the object as a value',ref $value);
82 }
83 } else {
84 return quote($value);
85 }
86 }
87
88
89 sub formatNumberValue {
90 my ($value) = @_;
91
92 if (ref $value) {
93 if (UNIVERSAL::isa($value,'IMPL::SQL::Schema::mysql::Expression')) {
94 return $value->as_string;
95 } else {
96 die new Exception('Can\'t format the object as a value',ref $value);
97 }
98 } else {
99 $value =~ /^((\+|-)\s*)?\d+(\.\d+)?(e(\+|-)?\d+)?$/ or die new Exception('The specified value isn\'t a valid number',$value);
100 return $value;
101 }
102 }
103
104
105 my %TypesFormat = (
106 TINYINT => {
107 formatType => \&formatTypeNameInteger,
108 formatValue => \&formatNumberValue
109 },
110 SMALLINT => {
111 formatType => \&formatTypeNameInteger,
112 formatValue => \&formatNumberValue
113 },
114 MEDIUMINT => {
115 formatType => \&formatTypeNameInteger,
116 formatValue => \&formatNumberValue
117 },
118 INT => {
119 formatType => \&formatTypeNameInteger,
120 formatValue => \&formatNumberValue
121 },
122 INTEGER => {
123 formatType => \&formatTypeNameInteger,
124 formatValue => \&formatNumberValue
125 },
126 BIGINT => {
127 formatType => \&formatTypeNameInteger,
128 formatValue => \&formatNumberValue
129 },
130 REAL => {
131 formatType => \&formatTypeNameReal,
132 formatValue => \&formatNumberValue
133 },
134 DOUBLE => {
135 formatType => \&formatTypeNameReal,
136 formatValue => \&formatNumberValue
137 },
138 FLOAT => {
139 formatType => \&formatTypeNameReal,
140 formatValue => \&formatNumberValue
141 },
142 DECIMAL => {
143 formatType => \&formatTypeNameNumeric,
144 formatValue => \&formatNumberValue
145 },
146 NUMERIC => {
147 formatType => \&formatTypeNameNumeric,
148 formatValue => \&formatNumberValue
149 },
150 DATE => {
151 formatType => \&formatTypeName,
152 formatValue => \&formatStringValue
153 },
154 TIME => {
155 formatType => \&formatTypeName,
156 formatValue => \&formatStringValue
157 },
158 TIMESTAMP => {
159 formatType => \&formatTypeName,
160 formatValue => \&formatStringValue
161 },
162 DATETIME => {
163 formatType => \&formatTypeName,
164 formatValue => \&formatStringValue
165 },
166 CHAR => {
167 formatType => \&formatTypeNameChar,
168 formatValue => \&formatStringValue
169 },
170 VARCHAR => {
171 formatType => \&formatTypeNameVarChar,
172 formatValue => \&formatStringValue
173 },
174 TINYBLOB => {
175 formatType => \&formatTypeName,
176 formatValue => \&formatStringValue
177 },
178 BLOB => {
179 formatType => \&formatTypeName,
180 formatValue => \&formatStringValue
181 },
182 MEDIUMBLOB => {
183 formatType => \&formatTypeName,
184 formatValue => \&formatStringValue
185 },
186 LONGBLOB => {
187 formatType => \&formatTypeName,
188 formatValue => \&formatStringValue
189 },
190 TINYTEXT => {
191 formatType => \&formatTypeName,
192 formatValue => \&formatStringValue
193 },
194 TEXT => {
195 formatType => \&formatTypeName,
196 formatValue => \&formatStringValue
197 },
198 MEDIUMTEXT => {
199 formatType => \&formatTypeName,
200 formatValue => \&formatStringValue
201 },
202 LONGTEXT => {
203 formatType => \&formatTypeName,
204 formatValue => \&formatStringValue
205 },
206 ENUM => {
207 formatType => \&formatTypeNameEnum,
208 formatValue => \&formatStringValue
209 },
210 SET => {
211 formatType => \&formatTypeNameEnum,
212 formatValue => \&formatStringValue
213 }
214 );
215
216
217 =pod
218 CREATE TABLE 'test'.'New Table' (
219 'dd' INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
220 `ff` VARCHAR(45) NOT NULL,
221 `ffg` VARCHAR(45) NOT NULL DEFAULT 'aaa',
222 `ddf` INTEGER UNSIGNED NOT NULL,
223 PRIMARY KEY(`dd`),
224 UNIQUE `Index_2`(`ffg`),
225 CONSTRAINT `FK_New Table_1` FOREIGN KEY `FK_New Table_1` (`ddf`)
226 REFERENCES `user` (`id`)
227 ON DELETE RESTRICT
228 ON UPDATE RESTRICT
229 )
230 ENGINE = InnoDB;
231 =cut
232 sub formatCreateTable {
233 my ($table,$level,%options) = @_;
234
235 my @sql;
236
237 # table body
238 push @sql, map { formatColumn($_,$level+1) } $table->Columns ;
239 if ($options{'skip_foreign_keys'}) {
240 push @sql, map { formatConstraint($_,$level+1) } grep {not UNIVERSAL::isa($_,'IMPL::SQL::Schema::Constraint::ForeignKey')} values %{$table->Constraints};
241 } else {
242 push @sql, map { formatConstraint($_,$level+1) } values %{$table->Constraints};
243 }
244
245 for(my $i = 0 ; $i < @sql -1; $i++) {
246 $sql[$i] .= ',';
247 }
248
249 unshift @sql, "CREATE TABLE ".quote_names($table->Name)."(";
250
251 if ($table->Tag) {
252 push @sql, ")";
253 push @sql, formatTableTag($table->Tag,$level);
254 $sql[$#sql].=';';
255 } else {
256 push @sql, ');';
257 }
258
259 return map { ("\t" x $level) . $_ } @sql;
260 }
261
262 sub formatDropTable {
263 my ($tableName,$level) = @_;
264
265 return "\t"x$level."DROP TABLE ".quote_names($tableName).";";
266 }
267
268 sub formatTableTag {
269 my ($tag,$level) = @_;
270 return map { "\t"x$level . "$_ = ".$tag->{$_} } grep {/^(ENGINE)$/i} keys %{$tag};
271 }
272
273 sub formatColumn {
274 my ($column,$level) = @_;
275 $level ||= 0;
276 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) : '');
277 }
278
279 sub formatType {
280 my ($type) = @_;
281 my $format = $TypesFormat{uc $type->Name} or die new Exception('The unknown type name',$type->Name);
282 $format->{formatType}->($type);
283 }
284
285 sub formatValueToType {
286 my ($value,$type) = @_;
287
288 my $format = $TypesFormat{uc $type->Name} or die new Exception('The unknown type name',$type->Name);
289 $format->{formatValue}->($value);
290 }
291
292 sub formatConstraint {
293 my ($constraint,$level) = @_;
294
295 if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::ForeignKey')) {
296 return formatForeignKey($constraint,$level);
297 } else {
298 return formatIndex($constraint, $level);
299 }
300 }
301
302 sub formatIndex {
303 my ($constraint,$level) = @_;
304
305 my $name = quote_names($constraint->Name);
306 my $columns = join(',',map quote_names($_->Name),$constraint->Columns);
307
308 if (ref $constraint eq 'IMPL::SQL::Schema::Constraint::PrimaryKey') {
309 return "\t"x$level."PRIMARY KEY ($columns)";
310 } elsif ($constraint eq 'IMPL::SQL::Schema::Constraint::Unique') {
311 return "\t"x$level."UNIQUE $name ($columns)";
312 } elsif ($constraint eq 'IMPL::SQL::Schema::Constraint::Index') {
313 return "\t"x$level."INDEX $name ($columns)";
314 } else {
315 die new IMPL::InvalidArgumentException('The unknown constraint', ref $constraint);
316 }
317
318 }
319
320 sub formatForeignKey {
321 my ($constraint,$level) = @_;
322
323 my $name = quote_names($constraint->Name);
324 my $columns = join(',',map quote_names($_->Name),$constraint->Columns);
325
326 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);
327 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);
328
329 my $refname = quote_names($constraint->ReferencedPrimaryKey->Table->Name);
330 my $refcolumns = join(',',map quote_names($_->Name),$constraint->ReferencedPrimaryKey->Columns);
331 return (
332 "\t"x$level.
333 "CONSTRAINT $name FOREIGN KEY $name ($columns) REFERENCES $refname ($refcolumns)".
334 ($constraint->OnUpdate ? 'ON UPDATE'.$constraint->OnUpdate : '').
335 ($constraint->OnDelete ? 'ON DELETE'.$constraint->OnDelete : '')
336 );
337 }
338
339 sub formatAlterTableRename {
340 my ($oldName,$newName,$level) = @_;
341
342 return "\t"x$level."ALTER TABLE ".quote_names($oldName)." RENAME TO ".quote_names($newName).";";
343 }
344
345 sub formatAlterTableDropColumn {
346 my ($tableName, $columnName,$level) = @_;
347
348 return "\t"x$level."ALTER TABLE ".quote_names($tableName)." DROP COLUMN ".quote_names($columnName).";";
349 }
350
351 =pod
352 ALTER TABLE `test`.`user` ADD COLUMN `my_col` VARCHAR(45) NOT NULL AFTER `name2`
353 =cut
354 sub formatAlterTableAddColumn {
355 my ($tableName, $column, $table, $pos, $level) = @_;
356
357 my $posSpec = $pos == 0 ? 'FIRST' : 'AFTER '.quote_names($table->ColumnAt($pos-1)->Name);
358
359 return "\t"x$level."ALTER TABLE ".quote_names($tableName)." ADD COLUMN ".formatColumn($column) .' '. $posSpec.";";
360 }
361
362 =pod
363 ALTER TABLE `test`.`manager` MODIFY COLUMN `description` VARCHAR(256) NOT NULL DEFAULT NULL;
364 =cut
365 sub formatAlterTableChangeColumn {
366 my ($tableName,$column,$table,$pos,$level) = @_;
367 my $posSpec = $pos == 0 ? 'FIRST' : 'AFTER '.quote_names($table->ColumnAt($pos-1)->Name);
368 return "\t"x$level."ALTER TABLE ".quote_names($tableName)." MODIFY COLUMN ".formatColumn($column).' '. $posSpec.";";
369 }
370
371 =pod
372 ALTER TABLE `test`.`manager` DROP INDEX `Index_2`;
373 =cut
374 sub formatAlterTableDropConstraint {
375 my ($tableName,$constraint,$level) = @_;
376 my $constraintName;
377 if (ref $constraint eq 'IMPL::SQL::Schema::Constraint::PrimaryKey') {
378 $constraintName = 'PRIMARY KEY';
379 } elsif (ref $constraint eq 'IMPL::SQL::Schema::Constraint::ForeignKey') {
380 $constraintName = 'FOREIGN KEY '.quote_names($constraint->Name);
381 } elsif (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::Index')) {
382 $constraintName = 'INDEX '.quote_names($constraint->Name);
383 } else {
384 die new IMPL::Exception("The unknow type of the constraint",ref $constraint);
385 }
386 return "\t"x$level."ALTER TABLE ".quote_names($tableName)." DROP $constraintName;";
387 }
388
389 =pod
390 ALTER TABLE `test`.`session` ADD INDEX `Index_2`(`id`, `name`);
391 =cut
392 sub formatAlterTableAddConstraint {
393 my ($tableName,$constraint,$level) = @_;
394
395 return "\t"x$level."ALTER TABLE ".quote_names($tableName)." ADD ".formatConstraint($constraint,0).';';
396 }
397
398 sub CreateTable {
399 my ($this,$tbl,%option) = @_;
400
401 push @{$this->{$SqlBatch}},join("\n",formatCreateTable($tbl,0,%option));
402
403 return 1;
404 }
405
406 sub DropTable {
407 my ($this,$tbl) = @_;
408
409 push @{$this->{$SqlBatch}},join("\n",formatDropTable($tbl,0));
410
411 return 1;
412 }
413
414 sub RenameTable {
415 my ($this,$oldName,$newName) = @_;
416
417 push @{$this->{$SqlBatch}},join("\n",formatAlterTableRename($oldName,$newName,0));
418
419 return 1;
420 }
421
422 sub AlterTableAddColumn {
423 my ($this,$tblName,$column,$table,$pos) = @_;
424
425 push @{$this->{$SqlBatch}},join("\n",formatAlterTableAddColumn($tblName,$column,$table,$pos,0));
426
427 return 1;
428 }
429 sub AlterTableDropColumn {
430 my ($this,$tblName,$columnName) = @_;
431
432 push @{$this->{$SqlBatch}},join("\n",formatAlterTableDropColumn($tblName,$columnName,0));
433
434 return 1;
435 }
436
437 sub AlterTableChangeColumn {
438 my ($this,$tblName,$column,$table,$pos) = @_;
439
440 push @{$this->{$SqlBatch}},join("\n",formatAlterTableChangeColumn($tblName,$column,$table,$pos,0));
441
442 return 1;
443 }
444
445 sub AlterTableAddConstraint {
446 my ($this,$tblName,$constraint) = @_;
447
448 push @{$this->{$SqlBatch}},join("\n",formatAlterTableAddConstraint($tblName,$constraint,0));
449
450 return 1;
451 }
452
453 sub AlterTableDropConstraint {
454 my ($this,$tblName,$constraint) = @_;
455
456 push @{$this->{$SqlBatch}},join("\n",formatAlterTableDropConstraint($tblName,$constraint,0));
457
458 return 1;
459 }
460
461 sub Sql {
462 my ($this) = @_;
463 if (wantarray) {
464 $this->SqlBatch;
465 } else {
466 return join("\n",$this->SqlBatch);
467 }
468 }
469
470 package IMPL::SQL::Schema::Traits::mysql;
471 use Common;
472 use base qw(IMPL::SQL::Schema::Traits);
473 use IMPL::Class::Property;
474 use IMPL::Class::Property::Direct;
475
476 BEGIN {
477 public _direct property PendingConstraints => prop_none;
478 }
479
480 sub CTOR {
481 my ($this,%args) = @_;
482
483 $args{'Handler'} = new IMPL::SQL::Schema::Traits::mysql::Handler;
484 $this->SUPER::CTOR(%args);
485 }
486
487 sub DropConstraint {
488 my ($this,$constraint) = @_;
489
490 if (UNIVERSAL::isa($constraint,'IMPL::SQL::Schema::Constraint::Index')) {
491 return 1 if not grep { $this->TableInfo->{$this->MapTableName($constraint->Table->Name)}->{'Columns'}->{$_->Name} != IMPL::SQL::Schema::Traits::STATE_REMOVED} $constraint->Columns;
492 my @constraints = grep {$_ != $constraint } $constraint->Table->GetColumnConstraints($constraint->Columns);
493 if (scalar @constraints == 1 and UNIVERSAL::isa($constraints[0],'IMPL::SQL::Schema::Constraint::ForeignKey')) {
494 my $fk = shift @constraints;
495 if ($this->TableInfo->{$this->MapTableName($fk->Table->Name)}->{'Constraints'}->{$fk->Name} != IMPL::SQL::Schema::Traits::STATE_REMOVED) {
496 push @{$this->PendingActions}, {Action => \&DropConstraint, Args => [$constraint]};
497 $this->{$PendingConstraints}->{$constraint->UniqName}->{'attempts'} ++;
498
499 die new IMPL::Exception('Can\'t drop the primary key becouse of the foreing key',$fk->UniqName) if $this->{$PendingConstraints}->{$constraint->UniqName}->{'attempts'} > 2;
500 return 2;
501 }
502 }
503 }
504 $this->SUPER::DropConstraint($constraint);
505 }
506
507 sub GetMetaTable {
508 my ($class,$dbh) = @_;
509
510 return IMPL::SQL::Schema::Traits::mysql::MetaTable->new( DBHandle => $dbh);
511 }
512
513 package IMPL::SQL::Schema::Traits::mysql::MetaTable;
514 use Common;
515 use base qw(IMPL::Object);
516 use IMPL::Class::Property;
517 use IMPL::Class::Property::Direct;
518
519 BEGIN {
520 public _direct property DBHandle => prop_none;
521 }
522
523 sub ReadProperty {
524 my ($this,$name) = @_;
525
526 local $this->{$DBHandle}->{PrintError};
527 $this->{$DBHandle}->{PrintError} = 0;
528 my ($val) = $this->{$DBHandle}->selectrow_array("SELECT value FROM _Meta WHERE name like ?", undef, $name);
529 return $val;
530 }
531
532 sub SetProperty {
533 my ($this,$name,$val) = @_;
534
535 if ( $this->{$DBHandle}->selectrow_arrayref("SELECT TABLE_NAME FROM information_schema.`TABLES` T where TABLE_SCHEMA like DATABASE() and TABLE_NAME like '_Meta'")) {
536 if ($this->{$DBHandle}->selectrow_arrayref("SELECT name FROM _Meta WHERE name like ?", undef, $name)) {
537 $this->{$DBHandle}->do("UPDATE _Meta SET value = ? WHERE name like ?",undef,$val,$name);
538 } else {
539 $this->{$DBHandle}->do("INSERT INTO _Meta(name,value) VALUES ('$name',?)",undef,$val);
540 }
541 } else {
542 $this->{$DBHandle}->do(q{
543 CREATE TABLE `_Meta` (
544 `name` VARCHAR(255) NOT NULL,
545 `value` LONGTEXT NULL,
546 PRIMARY KEY(`name`)
547 );
548 }) or die new IMPL::Exception("Failed to create table","_Meta");
549
550 $this->{$DBHandle}->do("INSERT INTO _Meta(name,value) VALUES (?,?)",undef,$name,$val);
551 }
552 }
553
554 1;