# Coverage for Doctrine_Export

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: Export.php 3149 2007-11-13 22:28:37Z zYne $
4 | *
5 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | *
17 | * This software consists of voluntary contributions made by many individuals
18 | * and is licensed under the LGPL. For more information, see
19 | * <http://www.phpdoctrine.org>.
20 | */
21 | Doctrine::autoload('Doctrine_Connection_Module');
22 | /**
23 | * Doctrine_Export
24 | *
25 | * @package Doctrine
26 | * @subpackage Export
27 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
28 | * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
29 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
30 | * @link www.phpdoctrine.org
31 | * @since 1.0
32 | * @version $Revision: 3149 $
33 | */
34 | class Doctrine_Export extends Doctrine_Connection_Module
35 | {
36 | protected $valid_default_values = array(
37 | 'text' => '',
38 | 'boolean' => true,
39 | 'integer' => 0,
40 | 'decimal' => 0.0,
41 | 'float' => 0.0,
42 | 'timestamp' => '1970-01-01 00:00:00',
43 | 'time' => '00:00:00',
44 | 'date' => '1970-01-01',
45 | 'clob' => '',
46 | 'blob' => '',
47 | 'string' => ''
48 | );
49 |
50 | /**
51 | * drop an existing database
52 | * (this method is implemented by the drivers)
53 | *
54 | * @param string $name name of the database that should be dropped
55 | * @return void
56 | */
57 | public function dropDatabase($database)
58 | {
59 | $this->conn->execute($this->dropDatabaseSql($database));
60 | }
61 |
62 | /**
63 | * drop an existing database
64 | * (this method is implemented by the drivers)
65 | *
66 | * @param string $name name of the database that should be dropped
67 | * @return void
68 | */
69 | public function dropDatabaseSql($database)
70 | {
71 | throw new Doctrine_Export_Exception('Drop database not supported by this driver.');
72 | }
73 |
74 | /**
75 | * dropTableSql
76 | * drop an existing table
77 | *
78 | * @param string $table name of table that should be dropped from the database
79 | * @return string
80 | */
81 | public function dropTableSql($table)
82 | {
83 | return 'DROP TABLE ' . $this->conn->quoteIdentifier($table);
84 | }
85 |
86 | /**
87 | * dropTable
88 | * drop an existing table
89 | *
90 | * @param string $table name of table that should be dropped from the database
91 | * @return void
92 | */
93 | public function dropTable($table)
94 | {
95 | $this->conn->execute($this->dropTableSql($table));
96 | }
97 |
98 | /**
99 | * drop existing index
100 | *
101 | * @param string $table name of table that should be used in method
102 | * @param string $name name of the index to be dropped
103 | * @return void
104 | */
105 | public function dropIndex($table, $name)
106 | {
107 | return $this->conn->exec($this->dropIndexSql($table, $name));
108 | }
109 |
110 | /**
111 | * dropIndexSql
112 | *
113 | * @param string $table name of table that should be used in method
114 | * @param string $name name of the index to be dropped
115 | * @return string SQL that is used for dropping an index
116 | */
117 | public function dropIndexSql($table, $name)
118 | {
119 | $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
120 |
121 | return 'DROP INDEX ' . $name;
122 | }
123 |
124 | /**
125 | * drop existing constraint
126 | *
127 | * @param string $table name of table that should be used in method
128 | * @param string $name name of the constraint to be dropped
129 | * @param string $primary hint if the constraint is primary
130 | * @return void
131 | */
132 | public function dropConstraint($table, $name, $primary = false)
133 | {
134 | $table = $this->conn->quoteIdentifier($table);
135 | $name = $this->conn->quoteIdentifier($name);
136 |
137 | return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
138 | }
139 |
140 | /**
141 | * drop existing foreign key
142 | *
143 | * @param string $table name of table that should be used in method
144 | * @param string $name name of the foreign key to be dropped
145 | * @return void
146 | */
147 | public function dropForeignKey($table, $name)
148 | {
149 | return $this->dropConstraint($table, $name);
150 | }
151 |
152 | /**
153 | * dropSequenceSql
154 | * drop existing sequence
155 | * (this method is implemented by the drivers)
156 | *
157 | * @throws Doctrine_Connection_Exception if something fails at database level
158 | * @param string $sequenceName name of the sequence to be dropped
159 | * @return void
160 | */
161 | public function dropSequence($sequenceName)
162 | {
163 | $this->conn->exec($this->dropSequenceSql($sequenceName));
164 | }
165 |
166 | /**
167 | * dropSequenceSql
168 | * drop existing sequence
169 | *
170 | * @throws Doctrine_Connection_Exception if something fails at database level
171 | * @param string $sequenceName name of the sequence to be dropped
172 | * @return void
173 | */
174 | public function dropSequenceSql($sequenceName)
175 | {
176 | throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.');
177 | }
178 |
179 | /**
180 | * create a new database
181 | * (this method is implemented by the drivers)
182 | *
183 | * @param string $name name of the database that should be created
184 | * @return void
185 | */
186 | public function createDatabase($database)
187 | {
188 | $this->conn->execute($this->createDatabaseSql($database));
189 | }
190 |
191 | /**
192 | * create a new database
193 | * (this method is implemented by the drivers)
194 | *
195 | * @param string $name name of the database that should be created
196 | * @return string
197 | */
198 | public function createDatabaseSql($database)
199 | {
200 | throw new Doctrine_Export_Exception('Create database not supported by this driver.');
201 | }
202 |
203 | /**
204 | * create a new table
205 | *
206 | * @param string $name Name of the database that should be created
207 | * @param array $fields Associative array that contains the definition of each field of the new table
208 | * The indexes of the array entries are the names of the fields of the table an
209 | * the array entry values are associative arrays like those that are meant to be
210 | * passed with the field definitions to get[Type]Declaration() functions.
211 | * array(
212 | * 'id' => array(
213 | * 'type' => 'integer',
214 | * 'unsigned' => 1
215 | * 'notnull' => 1
216 | * 'default' => 0
217 | * ),
218 | * 'name' => array(
219 | * 'type' => 'text',
220 | * 'length' => 12
221 | * ),
222 | * 'password' => array(
223 | * 'type' => 'text',
224 | * 'length' => 12
225 | * )
226 | * );
227 | * @param array $options An associative array of table options:
228 | *
229 | * @return string
230 | */
231 | public function createTableSql($name, array $fields, array $options = array())
232 | {
233 | if ( ! $name) {
234 | throw new Doctrine_Export_Exception('no valid table name specified');
235 | }
236 |
237 | if (empty($fields)) {
238 | throw new Doctrine_Export_Exception('no fields specified for table ' . $name);
239 | }
240 |
241 | $queryFields = $this->getFieldDeclarationList($fields);
242 |
243 |
244 | if (isset($options['primary']) && ! empty($options['primary'])) {
245 | $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')';
246 | }
247 |
248 | if (isset($options['indexes']) && ! empty($options['indexes'])) {
249 | foreach($options['indexes'] as $index => $definition) {
250 | $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
251 | }
252 | }
253 |
254 | $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields;
255 |
256 | $check = $this->getCheckDeclaration($fields);
257 |
258 | if ( ! empty($check)) {
259 | $query .= ', ' . $check;
260 | }
261 |
262 | $query .= ')';
263 |
264 |
265 |
266 | $sql[] = $query;
267 |
268 | if (isset($options['foreignKeys'])) {
269 |
270 | foreach ((array) $options['foreignKeys'] as $k => $definition) {
271 | if (is_array($definition)) {
272 | $sql[] = $this->createForeignKeySql($name, $definition);
273 | }
274 | }
275 | }
276 | return $sql;
277 | }
278 |
279 | /**
280 | * create a new table
281 | *
282 | * @param string $name Name of the database that should be created
283 | * @param array $fields Associative array that contains the definition of each field of the new table
284 | * @param array $options An associative array of table options:
285 | * @see Doctrine_Export::createTableSql()
286 | *
287 | * @return void
288 | */
289 | public function createTable($name, array $fields, array $options = array())
290 | {
291 | $sql = (array) $this->createTableSql($name, $fields, $options);
292 |
293 | foreach ($sql as $query) {
294 | $this->conn->execute($query);
295 | }
296 | }
297 |
298 | /**
299 | * create sequence
300 | *
301 | * @throws Doctrine_Connection_Exception if something fails at database level
302 | * @param string $seqName name of the sequence to be created
303 | * @param string $start start value of the sequence; default is 1
304 | * @param array $options An associative array of table options:
305 | * array(
306 | * 'comment' => 'Foo',
307 | * 'charset' => 'utf8',
308 | * 'collate' => 'utf8_unicode_ci',
309 | * );
310 | * @return void
311 | */
312 | public function createSequence($seqName, $start = 1, array $options = array())
313 | {
314 | return $this->conn->execute($this->createSequenceSql($seqName, $start = 1, $options));
315 | }
316 |
317 | /**
318 | * return RDBMS specific create sequence statement
319 | * (this method is implemented by the drivers)
320 | *
321 | * @throws Doctrine_Connection_Exception if something fails at database level
322 | * @param string $seqName name of the sequence to be created
323 | * @param string $start start value of the sequence; default is 1
324 | * @param array $options An associative array of table options:
325 | * array(
326 | * 'comment' => 'Foo',
327 | * 'charset' => 'utf8',
328 | * 'collate' => 'utf8_unicode_ci',
329 | * );
330 | * @return string
331 | */
332 | public function createSequenceSql($seqName, $start = 1, array $options = array())
333 | {
334 | throw new Doctrine_Export_Exception('Create sequence not supported by this driver.');
335 | }
336 |
337 | /**
338 | * create a constraint on a table
339 | *
340 | * @param string $table name of the table on which the constraint is to be created
341 | * @param string $name name of the constraint to be created
342 | * @param array $definition associative array that defines properties of the constraint to be created.
343 | * Currently, only one property named FIELDS is supported. This property
344 | * is also an associative with the names of the constraint fields as array
345 | * constraints. Each entry of this array is set to another type of associative
346 | * array that specifies properties of the constraint that are specific to
347 | * each field.
348 | *
349 | * Example
350 | * array(
351 | * 'fields' => array(
352 | * 'user_name' => array(),
353 | * 'last_login' => array()
354 | * )
355 | * )
356 | * @return void
357 | */
358 | public function createConstraint($table, $name, $definition)
359 | {
360 | $sql = $this->createConstraintSql($table, $name, $definition);
361 |
362 | return $this->conn->exec($sql);
363 | }
364 |
365 | /**
366 | * create a constraint on a table
367 | *
368 | * @param string $table name of the table on which the constraint is to be created
369 | * @param string $name name of the constraint to be created
370 | * @param array $definition associative array that defines properties of the constraint to be created.
371 | * Currently, only one property named FIELDS is supported. This property
372 | * is also an associative with the names of the constraint fields as array
373 | * constraints. Each entry of this array is set to another type of associative
374 | * array that specifies properties of the constraint that are specific to
375 | * each field.
376 | *
377 | * Example
378 | * array(
379 | * 'fields' => array(
380 | * 'user_name' => array(),
381 | * 'last_login' => array()
382 | * )
383 | * )
384 | * @return void
385 | */
386 | public function createConstraintSql($table, $name, $definition)
387 | {
388 | $table = $this->conn->quoteIdentifier($table);
389 | $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
390 | $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
391 |
392 | if (isset($definition['primary']) && $definition['primary']) {
393 | $query .= ' PRIMARY KEY';
394 | } elseif (isset($definition['unique']) && $definition['unique']) {
395 | $query .= ' UNIQUE';
396 | }
397 |
398 | $fields = array();
399 | foreach (array_keys($definition['fields']) as $field) {
400 | $fields[] = $this->conn->quoteIdentifier($field, true);
401 | }
402 | $query .= ' ('. implode(', ', $fields) . ')';
403 |
404 | return $query;
405 | }
406 |
407 | /**
408 | * Get the stucture of a field into an array
409 | *
410 | * @param string $table name of the table on which the index is to be created
411 | * @param string $name name of the index to be created
412 | * @param array $definition associative array that defines properties of the index to be created.
413 | * Currently, only one property named FIELDS is supported. This property
414 | * is also an associative with the names of the index fields as array
415 | * indexes. Each entry of this array is set to another type of associative
416 | * array that specifies properties of the index that are specific to
417 | * each field.
418 | *
419 | * Currently, only the sorting property is supported. It should be used
420 | * to define the sorting direction of the index. It may be set to either
421 | * ascending or descending.
422 | *
423 | * Not all DBMS support index sorting direction configuration. The DBMS
424 | * drivers of those that do not support it ignore this property. Use the
425 | * function supports() to determine whether the DBMS driver can manage indexes.
426 | *
427 | * Example
428 | * array(
429 | * 'fields' => array(
430 | * 'user_name' => array(
431 | * 'sorting' => 'ascending'
432 | * ),
433 | * 'last_login' => array()
434 | * )
435 | * )
436 | * @return void
437 | */
438 | public function createIndex($table, $name, array $definition)
439 | {
440 | return $this->conn->execute($this->createIndexSql($table, $name, $definition));
441 | }
442 |
443 | /**
444 | * Get the stucture of a field into an array
445 | *
446 | * @param string $table name of the table on which the index is to be created
447 | * @param string $name name of the index to be created
448 | * @param array $definition associative array that defines properties of the index to be created.
449 | * @see Doctrine_Export::createIndex()
450 | * @return string
451 | */
452 | public function createIndexSql($table, $name, array $definition)
453 | {
454 | $table = $this->conn->quoteIdentifier($table);
455 | $name = $this->conn->quoteIdentifier($name);
456 | $type = '';
457 |
458 | if (isset($definition['type'])) {
459 | switch (strtolower($definition['type'])) {
460 | case 'unique':
461 | $type = strtoupper($definition['type']) . ' ';
462 | break;
463 | default:
464 | throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
465 | }
466 | }
467 |
468 | $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
469 |
470 | $fields = array();
471 | foreach ($definition['fields'] as $field) {
472 | $fields[] = $this->conn->quoteIdentifier($field);
473 | }
474 | $query .= ' (' . implode(', ', $fields) . ')';
475 |
476 | return $query;
477 | }
478 | /**
479 | * createForeignKeySql
480 | *
481 | * @param string $table name of the table on which the foreign key is to be created
482 | * @param array $definition associative array that defines properties of the foreign key to be created.
483 | * @return string
484 | */
485 | public function createForeignKeySql($table, array $definition)
486 | {
487 | $table = $this->conn->quoteIdentifier($table);
488 |
489 | $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclaration($definition);
490 |
491 | return $query;
492 | }
493 |
494 | /**
495 | * createForeignKey
496 | *
497 | * @param string $table name of the table on which the foreign key is to be created
498 | * @param array $definition associative array that defines properties of the foreign key to be created.
499 | * @return string
500 | */
501 | public function createForeignKey($table, array $definition)
502 | {
503 | $sql = $this->createForeignKeySql($table, $definition);
504 |
505 | return $this->conn->execute($sql);
506 | }
507 |
508 | /**
509 | * alter an existing table
510 | * (this method is implemented by the drivers)
511 | *
512 | * @param string $name name of the table that is intended to be changed.
513 | * @param array $changes associative array that contains the details of each type
514 | * of change that is intended to be performed. The types of
515 | * changes that are currently supported are defined as follows:
516 | *
517 | * name
518 | *
519 | * New name for the table.
520 | *
521 | * add
522 | *
523 | * Associative array with the names of fields to be added as
524 | * indexes of the array. The value of each entry of the array
525 | * should be set to another associative array with the properties
526 | * of the fields to be added. The properties of the fields should
527 | * be the same as defined by the MDB2 parser.
528 | *
529 | *
530 | * remove
531 | *
532 | * Associative array with the names of fields to be removed as indexes
533 | * of the array. Currently the values assigned to each entry are ignored.
534 | * An empty array should be used for future compatibility.
535 | *
536 | * rename
537 | *
538 | * Associative array with the names of fields to be renamed as indexes
539 | * of the array. The value of each entry of the array should be set to
540 | * another associative array with the entry named name with the new
541 | * field name and the entry named Declaration that is expected to contain
542 | * the portion of the field declaration already in DBMS specific SQL code
543 | * as it is used in the CREATE TABLE statement.
544 | *
545 | * change
546 | *
547 | * Associative array with the names of the fields to be changed as indexes
548 | * of the array. Keep in mind that if it is intended to change either the
549 | * name of a field and any other properties, the change array entries
550 | * should have the new names of the fields as array indexes.
551 | *
552 | * The value of each entry of the array should be set to another associative
553 | * array with the properties of the fields to that are meant to be changed as
554 | * array entries. These entries should be assigned to the new values of the
555 | * respective properties. The properties of the fields should be the same
556 | * as defined by the MDB2 parser.
557 | *
558 | * Example
559 | * array(
560 | * 'name' => 'userlist',
561 | * 'add' => array(
562 | * 'quota' => array(
563 | * 'type' => 'integer',
564 | * 'unsigned' => 1
565 | * )
566 | * ),
567 | * 'remove' => array(
568 | * 'file_limit' => array(),
569 | * 'time_limit' => array()
570 | * ),
571 | * 'change' => array(
572 | * 'name' => array(
573 | * 'length' => '20',
574 | * 'definition' => array(
575 | * 'type' => 'text',
576 | * 'length' => 20,
577 | * ),
578 | * )
579 | * ),
580 | * 'rename' => array(
581 | * 'sex' => array(
582 | * 'name' => 'gender',
583 | * 'definition' => array(
584 | * 'type' => 'text',
585 | * 'length' => 1,
586 | * 'default' => 'M',
587 | * ),
588 | * )
589 | * )
590 | * )
591 | *
592 | * @param boolean $check indicates whether the function should just check if the DBMS driver
593 | * can perform the requested table alterations if the value is true or
594 | * actually perform them otherwise.
595 | * @return void
596 | */
597 | public function alterTable($name, array $changes, $check = false)
598 | {
599 | $sql = $this->alterTableSql($name, $changes, $check);
600 |
601 | if (is_string($sql) && $sql) {
602 | $this->conn->execute($sql);
603 | }
604 | }
605 |
606 | /**
607 | * generates the sql for altering an existing table
608 | * (this method is implemented by the drivers)
609 | *
610 | * @param string $name name of the table that is intended to be changed.
611 | * @param array $changes associative array that contains the details of each type *
612 | * @param boolean $check indicates whether the function should just check if the DBMS driver
613 | * can perform the requested table alterations if the value is true or
614 | * actually perform them otherwise.
615 | * @see Doctrine_Export::alterTable()
616 | * @return string
617 | */
618 | public function alterTableSql($name, array $changes, $check = false)
619 | {
620 | throw new Doctrine_Export_Exception('Alter table not supported by this driver.');
621 | }
622 |
623 | /**
624 | * Get declaration of a number of field in bulk
625 | *
626 | * @param array $fields a multidimensional associative array.
627 | * The first dimension determines the field name, while the second
628 | * dimension is keyed with the name of the properties
629 | * of the field being declared as array indexes. Currently, the types
630 | * of supported field properties are as follows:
631 | *
632 | * length
633 | * Integer value that determines the maximum length of the text
634 | * field. If this argument is missing the field should be
635 | * declared to have the longest length allowed by the DBMS.
636 | *
637 | * default
638 | * Text value to be used as default for this field.
639 | *
640 | * notnull
641 | * Boolean flag that indicates whether this field is constrained
642 | * to not be set to null.
643 | * charset
644 | * Text value with the default CHARACTER SET for this field.
645 | * collation
646 | * Text value with the default COLLATION for this field.
647 | * unique
648 | * unique constraint
649 | *
650 | * @return string
651 | */
652 | public function getFieldDeclarationList(array $fields)
653 | {
654 | foreach ($fields as $fieldName => $field) {
655 | $query = $this->getDeclaration($fieldName, $field);
656 |
657 | $queryFields[] = $query;
658 | }
659 | return implode(', ', $queryFields);
660 | }
661 |
662 | /**
663 | * Obtain DBMS specific SQL code portion needed to declare a generic type
664 | * field to be used in statements like CREATE TABLE.
665 | *
666 | * @param string $name name the field to be declared.
667 | * @param array $field associative array with the name of the properties
668 | * of the field being declared as array indexes. Currently, the types
669 | * of supported field properties are as follows:
670 | *
671 | * length
672 | * Integer value that determines the maximum length of the text
673 | * field. If this argument is missing the field should be
674 | * declared to have the longest length allowed by the DBMS.
675 | *
676 | * default
677 | * Text value to be used as default for this field.
678 | *
679 | * notnull
680 | * Boolean flag that indicates whether this field is constrained
681 | * to not be set to null.
682 | * charset
683 | * Text value with the default CHARACTER SET for this field.
684 | * collation
685 | * Text value with the default COLLATION for this field.
686 | * unique
687 | * unique constraint
688 | * check
689 | * column check constraint
690 | *
691 | * @return string DBMS specific SQL code portion that should be used to
692 | * declare the specified field.
693 | */
694 | public function getDeclaration($name, array $field)
695 | {
696 |
697 | $default = $this->getDefaultFieldDeclaration($field);
698 |
699 | $charset = (isset($field['charset']) && $field['charset']) ?
700 | ' ' . $this->getCharsetFieldDeclaration($field['charset']) : '';
701 |
702 | $collation = (isset($field['collation']) && $field['collation']) ?
703 | ' ' . $this->getCollationFieldDeclaration($field['collation']) : '';
704 |
705 | $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
706 |
707 | $unique = (isset($field['unique']) && $field['unique']) ?
708 | ' ' . $this->getUniqueFieldDeclaration() : '';
709 |
710 | $check = (isset($field['check']) && $field['check']) ?
711 | ' ' . $field['check'] : '';
712 |
713 | $method = 'get' . $field['type'] . 'Declaration';
714 |
715 | if (method_exists($this->conn->dataDict, $method)) {
716 | return $this->conn->dataDict->$method($name, $field);
717 | } else {
718 | $dec = $this->conn->dataDict->getNativeDeclaration($field);
719 | }
720 | return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $unique . $check . $collation;
721 | }
722 |
723 | /**
724 | * getDefaultDeclaration
725 | * Obtain DBMS specific SQL code portion needed to set a default value
726 | * declaration to be used in statements like CREATE TABLE.
727 | *
728 | * @param array $field field definition array
729 | * @return string DBMS specific SQL code portion needed to set a default value
730 | */
731 | public function getDefaultFieldDeclaration($field)
732 | {
733 | $default = '';
734 | if (isset($field['default'])) {
735 | if ($field['default'] === '') {
736 | $field['default'] = empty($field['notnull'])
737 | ? null : $this->valid_default_values[$field['type']];
738 |
739 | if ($field['default'] === '' &&
740 | ($this->conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)) {
741 | $field['default'] = null;
742 | }
743 | }
744 |
745 | if ($field['type'] === 'boolean') {
746 | $fields['default'] = $this->conn->convertBooleans($field['default']);
747 | }
748 | $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
749 | }
750 | return $default;
751 | }
752 |
753 | /**
754 | * Obtain DBMS specific SQL code portion needed to set a CHECK constraint
755 | * declaration to be used in statements like CREATE TABLE.
756 | *
757 | * @param array $definition check definition
758 | * @return string DBMS specific SQL code portion needed to set a CHECK constraint
759 | */
760 | public function getCheckDeclaration(array $definition)
761 | {
762 | $constraints = array();
763 | foreach ($definition as $field => $def) {
764 | if (is_string($def)) {
765 | $constraints[] = 'CHECK (' . $def . ')';
766 | } else {
767 | if (isset($def['min'])) {
768 | $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
769 | }
770 |
771 | if (isset($def['max'])) {
772 | $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
773 | }
774 | }
775 | }
776 |
777 | return implode(', ', $constraints);
778 | }
779 |
780 | /**
781 | * Obtain DBMS specific SQL code portion needed to set an index
782 | * declaration to be used in statements like CREATE TABLE.
783 | *
784 | * @param string $name name of the index
785 | * @param array $definition index definition
786 | * @return string DBMS specific SQL code portion needed to set an index
787 | */
788 | public function getIndexDeclaration($name, array $definition)
789 | {
790 | $name = $this->conn->quoteIdentifier($name);
791 | $type = '';
792 |
793 | if (isset($definition['type'])) {
794 | if (strtolower($definition['type']) == 'unique') {
795 | $type = strtoupper($definition['type']) . ' ';
796 | } else {
797 | throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
798 | }
799 | }
800 |
801 | if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) {
802 | throw new Doctrine_Export_Exception('No index columns given.');
803 | }
804 |
805 | $query = $type . 'INDEX ' . $name;
806 |
807 | $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
808 |
809 | return $query;
810 | }
811 |
812 | /**
813 | * getIndexFieldDeclarationList
814 | * Obtain DBMS specific SQL code portion needed to set an index
815 | * declaration to be used in statements like CREATE TABLE.
816 | *
817 | * @return string
818 | */
819 | public function getIndexFieldDeclarationList(array $fields)
820 | {
821 | $ret = array();
822 | foreach ($fields as $field => $definition) {
823 | if (is_array($definition)) {
824 | $ret[] = $this->conn->quoteIdentifier($field);
825 | } else {
826 | $ret[] = $this->conn->quoteIdentifier($definition);
827 | }
828 | }
829 | return implode(', ', $ret);
830 | }
831 |
832 | /**
833 | * A method to return the required SQL string that fits between CREATE ... TABLE
834 | * to create the table as a temporary table.
835 | *
836 | * Should be overridden in driver classes to return the correct string for the
837 | * specific database type.
838 | *
839 | * The default is to return the string "TEMPORARY" - this will result in a
840 | * SQL error for any database that does not support temporary tables, or that
841 | * requires a different SQL command from "CREATE TEMPORARY TABLE".
842 | *
843 | * @return string The string required to be placed between "CREATE" and "TABLE"
844 | * to generate a temporary table, if possible.
845 | */
846 | public function getTemporaryTableQuery()
847 | {
848 | return 'TEMPORARY';
849 | }
850 |
851 | /**
852 | * getForeignKeyDeclaration
853 | * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
854 | * of a field declaration to be used in statements like CREATE TABLE.
855 | *
856 | * @param array $definition an associative array with the following structure:
857 | * name optional constraint name
858 | *
859 | * local the local field(s)
860 | *
861 | * foreign the foreign reference field(s)
862 | *
863 | * foreignTable the name of the foreign table
864 | *
865 | * onDelete referential delete action
866 | *
867 | * onUpdate referential update action
868 | *
869 | * deferred deferred constraint checking
870 | *
871 | * The onDelete and onUpdate keys accept the following values:
872 | *
873 | * CASCADE: Delete or update the row from the parent table and automatically delete or
874 | * update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
875 | * Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column
876 | * in the parent table or in the child table.
877 | *
878 | * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the
879 | * child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier
880 | * specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
881 | *
882 | * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary
883 | * key value is not allowed to proceed if there is a related foreign key value in the referenced table.
884 | *
885 | * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as
886 | * omitting the ON DELETE or ON UPDATE clause.
887 | *
888 | * SET DEFAULT
889 | *
890 | * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
891 | * of a field declaration.
892 | */
893 | public function getForeignKeyDeclaration(array $definition)
894 | {
895 | $sql = $this->getForeignKeyBaseDeclaration($definition);
896 | $sql .= $this->getAdvancedForeignKeyOptions($definition);
897 |
898 | return $sql;
899 | }
900 |
901 | /**
902 | * getAdvancedForeignKeyOptions
903 | * Return the FOREIGN KEY query section dealing with non-standard options
904 | * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
905 | *
906 | * @param array $definition foreign key definition
907 | * @return string
908 | */
909 | public function getAdvancedForeignKeyOptions(array $definition)
910 | {
911 | $query = '';
912 | if ( ! empty($definition['onUpdate'])) {
913 | $query .= ' ON UPDATE ' . $this->getForeignKeyRefentialAction($definition['onUpdate']);
914 | }
915 | if ( ! empty($definition['onDelete'])) {
916 | $query .= ' ON DELETE ' . $this->getForeignKeyRefentialAction($definition['onDelete']);
917 | }
918 | return $query;
919 | }
920 |
921 | /**
922 | * getForeignKeyReferentialAction
923 | *
924 | * returns given referential action in uppercase if valid, otherwise throws
925 | * an exception
926 | *
927 | * @throws Doctrine_Exception_Exception if unknown referential action given
928 | * @param string $action foreign key referential action
929 | * @param string foreign key referential action in uppercase
930 | */
931 | public function getForeignKeyReferentialAction($action)
932 | {
933 | $upper = strtoupper($action);
934 | switch ($upper) {
935 | case 'CASCADE':
936 | case 'SET NULL':
937 | case 'NO ACTION':
938 | case 'RESTRICT':
939 | case 'SET DEFAULT':
940 | return $upper;
941 | break;
942 | default:
943 | throw new Doctrine_Export_Exception('Unknown foreign key referential action \'' . $upper . '\' given.');
944 | }
945 | }
946 |
947 | /**
948 | * getForeignKeyBaseDeclaration
949 | * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
950 | * of a field declaration to be used in statements like CREATE TABLE.
951 | *
952 | * @param array $definition
953 | * @return string
954 | */
955 | public function getForeignKeyBaseDeclaration(array $definition)
956 | {
957 | $sql = '';
958 | if (isset($definition['name'])) {
959 | $sql .= ' CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' ';
960 | }
961 | $sql .= 'FOREIGN KEY (';
962 |
963 | if ( ! isset($definition['local'])) {
964 | throw new Doctrine_Export_Exception('Local reference field missing from definition.');
965 | }
966 | if ( ! isset($definition['foreign'])) {
967 | throw new Doctrine_Export_Exception('Foreign reference field missing from definition.');
968 | }
969 | if ( ! isset($definition['foreignTable'])) {
970 | throw new Doctrine_Export_Exception('Foreign reference table missing from definition.');
971 | }
972 |
973 | if ( ! is_array($definition['local'])) {
974 | $definition['local'] = array($definition['local']);
975 | }
976 | if ( ! is_array($definition['foreign'])) {
977 | $definition['foreign'] = array($definition['foreign']);
978 | }
979 |
980 | $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local']))
981 | . ') REFERENCES '
982 | . $this->conn->quoteIdentifier($definition['foreignTable']) . '('
983 | . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')';
984 |
985 | return $sql;
986 | }
987 |
988 | /**
989 | * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint
990 | * of a field declaration to be used in statements like CREATE TABLE.
991 | *
992 | * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
993 | * of a field declaration.
994 | */
995 | public function getUniqueFieldDeclaration()
996 | {
997 | return 'UNIQUE';
998 | }
999 |
1000 | /**
1001 | * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
1002 | * of a field declaration to be used in statements like CREATE TABLE.
1003 | *
1004 | * @param string $charset name of the charset
1005 | * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
1006 | * of a field declaration.
1007 | */
1008 | public function getCharsetFieldDeclaration($charset)
1009 | {
1010 | return '';
1011 | }
1012 |
1013 | /**
1014 | * Obtain DBMS specific SQL code portion needed to set the COLLATION
1015 | * of a field declaration to be used in statements like CREATE TABLE.
1016 | *
1017 | * @param string $collation name of the collation
1018 | * @return string DBMS specific SQL code portion needed to set the COLLATION
1019 | * of a field declaration.
1020 | */
1021 | public function getCollationFieldDeclaration($collation)
1022 | {
1023 | return '';
1024 | }
1025 |
1026 | /**
1027 | * exportSchema
1028 | * method for exporting Doctrine_Record classes to a schema
1029 | *
1030 | * if the directory parameter is given this method first iterates
1031 | * recursively trhough the given directory in order to find any model classes
1032 | *
1033 | * Then it iterates through all declared classes and creates tables for the ones
1034 | * that extend Doctrine_Record and are not abstract classes
1035 | *
1036 | * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1037 | * occurred during the create table operation
1038 | * @param string $directory optional directory parameter
1039 | * @return void
1040 | */
1041 | public function exportSchema($directory = null)
1042 | {
1043 | if ($directory !== null) {
1044 | $models = Doctrine::loadModels($directory);
1045 | } else {
1046 | $models = Doctrine::getLoadedModels();
1047 | }
1048 |
1049 | $this->exportClasses($models);
1050 | }
1051 |
1052 | /**
1053 | * exportClasses
1054 | * method for exporting Doctrine_Record classes to a schema
1055 | *
1056 | * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1057 | * occurred during the create table operation
1058 | * @param array $classes
1059 | * @return void
1060 | */
1061 | public function exportClasses(array $classes)
1062 | {
1063 | $connections = array();
1064 | foreach ($classes as $class) {
1065 | $record = new $class();
1066 | $connection = $record->getTable()->getConnection();
1067 | $connectionName = Doctrine_Manager::getInstance()->getConnectionName($connection);
1068 |
1069 | if ( ! isset($connections[$connectionName])) {
1070 | $connections[$connectionName] = array();
1071 | $connections[$connectionName]['creates'] = array();
1072 | $connections[$connectionName]['alters'] = array();
1073 | }
1074 |
1075 | $sql = $this->exportClassesSql(array($class));
1076 | // Build array of all the creates
1077 | // We need these to happen first
1078 | foreach ($sql as $key => $query) {
1079 | if (strstr($query, 'CREATE')) {
1080 | $connections[$connectionName]['creates'][] = $query;
1081 | // Unset the create from sql so we can have an array of everything else but creates
1082 | unset($sql[$key]);
1083 | }
1084 | }
1085 |
1086 | $connections[$connectionName]['alters'] = array_merge($connections[$connectionName]['alters'], $sql);
1087 | }
1088 |
1089 | // Loop over all the sql again to merge the creates and alters in to the same array, but so that the alters are at the bottom
1090 | $build = array();
1091 | foreach ($connections as $connectionName => $sql) {
1092 | $build[$connectionName] = array_merge($sql['creates'], $sql['alters']);
1093 | }
1094 |
1095 | foreach ($build as $connectionName => $sql) {
1096 | $connection = Doctrine_Manager::getInstance()->getConnection($connectionName);
1097 |
1098 | $connection->beginTransaction();
1099 |
1100 | foreach ($sql as $query) {
1101 | try {
1102 | $connection->exec($query);
1103 | } catch (Doctrine_Connection_Exception $e) {
1104 | // we only want to silence table already exists errors
1105 | if ($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
1106 | $connection->rollback();
1107 | throw new Doctrine_Export_Exception($e->getMessage() . '. Failing Query: ' . $query);
1108 | }
1109 | }
1110 | }
1111 |
1112 | $connection->commit();
1113 | }
1114 | }
1115 |
1116 | /**
1117 | * exportClassesSql
1118 | * method for exporting Doctrine_Record classes to a schema
1119 | *
1120 | * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1121 | * occurred during the create table operation
1122 | * @param array $classes
1123 | * @return void
1124 | */
1125 | public function exportClassesSql(array $classes)
1126 | {
1127 | $models = Doctrine::getLoadedModels($classes);
1128 |
1129 | $sql = array();
1130 |
1131 | foreach ($models as $name) {
1132 | $record = new $name();
1133 | $table = $record->getTable();
1134 |
1135 | $parents = $table->getOption('joinedParents');
1136 |
1137 | foreach ($parents as $parent) {
1138 | $data = $table->getConnection()->getTable($parent)->getExportableFormat();
1139 |
1140 | $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
1141 |
1142 | $sql = array_merge($sql, (array) $query);
1143 | }
1144 |
1145 | $data = $table->getExportableFormat();
1146 |
1147 | $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
1148 |
1149 | if (is_array($query)) {
1150 | $sql = array_merge($sql, $query);
1151 | } else {
1152 | $sql[] = $query;
1153 | }
1154 |
1155 | if ($table->getAttribute(Doctrine::ATTR_EXPORT) & Doctrine::EXPORT_PLUGINS) {
1156 | $sql = array_merge($sql, $this->exportPluginsSql($table));
1157 | }
1158 | }
1159 |
1160 | $sql = array_unique($sql);
1161 |
1162 | rsort($sql);
1163 |
1164 | return $sql;
1165 | }
1166 |
1167 | /**
1168 | * fetches all plugins recursively for given table
1169 | *
1170 | * @param Doctrine_Table $table table object to retrieve the plugins from
1171 | * @return array an array of Doctrine_Plugin objects
1172 | */
1173 | public function getAllPlugins(Doctrine_Table $table)
1174 | {
1175 | $plugins = array();
1176 |
1177 | foreach ($table->getTemplates() as $name => $template) {
1178 | $plugin = $template->getPlugin();
1179 |
1180 | if ($plugin === null) {
1181 | continue;
1182 | }
1183 |
1184 | $plugins[] = $plugin;
1185 |
1186 | $pluginTable = $plugin->getOption('pluginTable');
1187 |
1188 | if ($pluginTable instanceof Doctrine_Table) {
1189 | $plugins = array_merge($plugins, $this->getAllPlugins($pluginTable));
1190 | }
1191 | }
1192 |
1193 | return $plugins;
1194 | }
1195 |
1196 | /**
1197 | * exportPluginsSql
1198 | * exports plugin tables for given table
1199 | *
1200 | * @param Doctrine_Table $table the table in which the plugins belong to
1201 | * @return array an array of sql strings
1202 | */
1203 | public function exportPluginsSql(Doctrine_Table $table)
1204 | {
1205 | $sql = array();
1206 |
1207 | foreach ($this->getAllPlugins($table) as $name => $plugin) {
1208 | $table = $plugin->getOption('pluginTable');
1209 |
1210 | // Make sure plugin has a valid table
1211 | if ($table instanceof Doctrine_Table) {
1212 | $data = $table->getExportableFormat();
1213 |
1214 | $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
1215 |
1216 | $sql = array_merge($sql, (array) $query);
1217 | }
1218 | }
1219 |
1220 | return $sql;
1221 | }
1222 |
1223 | /**
1224 | * exportSql
1225 | * returns the sql for exporting Doctrine_Record classes to a schema
1226 | *
1227 | * if the directory parameter is given this method first iterates
1228 | * recursively trhough the given directory in order to find any model classes
1229 | *
1230 | * Then it iterates through all declared classes and creates tables for the ones
1231 | * that extend Doctrine_Record and are not abstract classes
1232 | *
1233 | * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1234 | * occurred during the create table operation
1235 | * @param string $directory optional directory parameter
1236 | * @return void
1237 | */
1238 | public function exportSql($directory = null)
1239 | {
1240 | if ($directory !== null) {
1241 | $models = Doctrine::loadModels($directory);
1242 | } else {
1243 | $models = Doctrine::getLoadedModels();
1244 | }
1245 |
1246 | return $this->exportClassesSql($models);
1247 | }
1248 |
1249 | /**
1250 | * exportTable
1251 | * exports given table into database based on column and option definitions
1252 | *
1253 | * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1254 | * occurred during the create table operation
1255 | * @return boolean whether or not the export operation was successful
1256 | * false if table already existed in the database
1257 | */
1258 | public function exportTable(Doctrine_Table $table)
1259 | {
1260 | /**
1261 | TODO: maybe there should be portability option for the following check
1262 | if ( ! Doctrine::isValidClassname($table->getOption('declaringClass')->getName())) {
1263 | throw new Doctrine_Export_Exception('Class name not valid.');
1264 | }
1265 | */
1266 |
1267 | try {
1268 | $data = $table->getExportableFormat();
1269 |
1270 | $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']);
1271 | } catch(Doctrine_Connection_Exception $e) {
1272 | // we only want to silence table already exists errors
1273 | if ($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
1274 | throw $e;
1275 | }
1276 | }
1277 | }
1278 | }