comparison Lib/Schema/DB/Traits/mysql.pm @ 0:03e58a454b20

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