# Source for file Export.php

Documentation is available at Export.php

1. <?php
2. /*
3. * $Id: Export.php 2288 2007-08-29 21:51:49Z Jonathan.Wage $
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.com>.
20. */
21. Doctrine::autoload('Doctrine_Connection_Module');
22. /**
23. * Doctrine_Export
24. *
25. * @package Doctrine
26. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
27. * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
28. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
29. * @category Object Relational Mapping
30. * @link www.phpdoctrine.com
31. * @since 1.0
32. * @version $Revision: 2288 $
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. );
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. * drop an existing database
63. * (this method is implemented by the drivers)
64. *
65. * @param string $name name of the database that should be dropped
66. * @return void
67. */
68. public function dropDatabaseSql($database)
69. {
70. throw new Doctrine_Export_Exception('Drop database not supported by this driver.');
71. }
72. /**
73. * dropTableSql
74. * drop an existing table
75. *
76. * @param string $table name of table that should be dropped from the database
77. * @return string
78. */
79. public function dropTableSql($table)
80. {
81. return 'DROP TABLE ' . $this->conn->quoteIdentifier($table);
82. }
83. /**
84. * dropTable
85. * drop an existing table
86. *
87. * @param string $table name of table that should be dropped from the database
88. * @return void
89. */
90. public function dropTable($table)
91. {
92. $this->conn->execute($this->dropTableSql($table));
93. }
95. /**
96. * drop existing index
97. *
98. * @param string $table name of table that should be used in method
99. * @param string $name name of the index to be dropped
100. * @return void
101. */
102. public function dropIndex($table, $name)
103. {
104. return $this->conn->exec($this->dropIndexSql($table, $name));
105. }
107. /**
108. * dropIndexSql
109. *
110. * @param string $table name of table that should be used in method
111. * @param string $name name of the index to be dropped
112. * @return string SQL that is used for dropping an index
113. */
114. public function dropIndexSql($table, $name)
115. {
116. $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
117. return 'DROP INDEX ' . $name;
118. }
119. /**
120. * drop existing constraint
121. *
122. * @param string $table name of table that should be used in method
123. * @param string $name name of the constraint to be dropped
124. * @param string $primary hint if the constraint is primary
125. * @return void
126. */
127. public function dropConstraint($table, $name, $primary = false)
128. {
129. $table = $this->conn->quoteIdentifier($table);
130. $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
131. return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
132. }
133. /**
134. * dropSequenceSql
135. * drop existing sequence
136. * (this method is implemented by the drivers)
137. *
138. * @throws Doctrine_Connection_Exception if something fails at database level
139. * @param string $sequenceName name of the sequence to be dropped
140. * @return void
141. */
142. public function dropSequence($sequenceName)
143. {
144. $this->conn->exec($this->dropSequenceSql($sequenceName));
145. }
146. /**
147. * dropSequenceSql
148. * drop existing sequence
149. *
150. * @throws Doctrine_Connection_Exception if something fails at database level
151. * @param string $sequenceName name of the sequence to be dropped
152. * @return void
153. */
154. public function dropSequenceSql($sequenceName)
155. {
156. throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.');
157. }
158. /**
159. * create a new database
160. * (this method is implemented by the drivers)
161. *
162. * @param string $name name of the database that should be created
163. * @return void
164. */
165. public function createDatabase($database)
166. {
167. $this->conn->execute($this->createDatabaseSql($database));
168. }
169. /**
170. * create a new database
171. * (this method is implemented by the drivers)
172. *
173. * @param string $name name of the database that should be created
174. * @return string
175. */
176. public function createDatabaseSql($database)
177. {
178. throw new Doctrine_Export_Exception('Create database not supported by this driver.');
179. }
180. /**
181. * create a new table
182. *
183. * @param string $name Name of the database that should be created
184. * @param array $fields Associative array that contains the definition of each field of the new table
185. * The indexes of the array entries are the names of the fields of the table an
186. * the array entry values are associative arrays like those that are meant to be
187. * passed with the field definitions to get[Type]Declaration() functions.
188. * array(
189. * 'id' => array(
190. * 'type' => 'integer',
191. * 'unsigned' => 1
192. * 'notnull' => 1
193. * 'default' => 0
194. * ),
195. * 'name' => array(
196. * 'type' => 'text',
197. * 'length' => 12
198. * ),
199. * 'password' => array(
200. * 'type' => 'text',
201. * 'length' => 12
202. * )
203. * );
204. * @param array $options An associative array of table options:
205. *
206. * @return string
207. */
208. public function createTableSql($name, array $fields, array $options = array())
209. {
210. if ( ! $name) {
211. throw new Doctrine_Export_Exception('no valid table name specified');
212. }
214. if (empty($fields)) {
215. throw new Doctrine_Export_Exception('no fields specified for table ' . $name);
216. }
218. $queryFields = $this->getFieldDeclarationList($fields);
221. if (isset($options['primary']) && ! empty($options['primary'])) {
222. $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')';
223. }
225. if (isset($options['indexes']) && ! empty($options['indexes'])) {
226. foreach($options['indexes'] as $index => $definition) {
227. $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
228. }
229. }
231. $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields;
233. $check = $this->getCheckDeclaration($fields);
235. if ( ! empty($check)) {
236. $query .= ', ' . $check;
237. }
239. $query .= ')';
243. $sql[] = $query;
245. if (isset($options['foreignKeys'])) {
247. foreach ((array) $options['foreignKeys'] as $k => $definition) {
248. if (is_array($definition)) {
249. $sql[] = $this->createForeignKeySql($name, $definition);
250. }
251. }
252. }
253. return $sql;
254. }
255. /**
256. * create a new table
257. *
258. * @param string $name Name of the database that should be created
259. * @param array $fields Associative array that contains the definition of each field of the new table
260. * @param array $options An associative array of table options:
261. * @see Doctrine_Export::createTableSql()
262. *
263. * @return void
264. */
265. public function createTable($name, array $fields, array $options = array())
266. {
267. $sql = (array) $this->createTableSql($name, $fields, $options);
269. foreach ($sql as $query) {
270. $this->conn->execute($query);
271. }
272. }
273. /**
274. * create sequence
275. *
276. * @throws Doctrine_Connection_Exception if something fails at database level
277. * @param string $seqName name of the sequence to be created
278. * @param string $start start value of the sequence; default is 1
279. * @param array $options An associative array of table options:
280. * array(
281. * 'comment' => 'Foo',
282. * 'charset' => 'utf8',
283. * 'collate' => 'utf8_unicode_ci',
284. * );
285. * @return void
286. */
287. public function createSequence($seqName, $start = 1, array $options = array())
288. {
289. return $this->conn->execute($this->createSequenceSql($seqName, $start = 1, $options));
290. }
291. /**
292. * return RDBMS specific create sequence statement
293. * (this method is implemented by the drivers)
294. *
295. * @throws Doctrine_Connection_Exception if something fails at database level
296. * @param string $seqName name of the sequence to be created
297. * @param string $start start value of the sequence; default is 1
298. * @param array $options An associative array of table options:
299. * array(
300. * 'comment' => 'Foo',
301. * 'charset' => 'utf8',
302. * 'collate' => 'utf8_unicode_ci',
303. * );
304. * @return string
305. */
306. public function createSequenceSql($seqName, $start = 1, array $options = array())
307. {
308. throw new Doctrine_Export_Exception('Create sequence not supported by this driver.');
309. }
310. /**
311. * create a constraint on a table
312. *
313. * @param string $table name of the table on which the constraint is to be created
314. * @param string $name name of the constraint to be created
315. * @param array $definition associative array that defines properties of the constraint to be created.
316. * Currently, only one property named FIELDS is supported. This property
317. * is also an associative with the names of the constraint fields as array
318. * constraints. Each entry of this array is set to another type of associative
319. * array that specifies properties of the constraint that are specific to
320. * each field.
321. *
322. * Example
323. * array(
324. * 'fields' => array(
325. * 'user_name' => array(),
326. * 'last_login' => array()
327. * )
328. * )
329. * @return void
330. */
331. public function createConstraint($table, $name, $definition)
332. {
333. return $this->conn->exec($this->createConstraintSql($table, $name, $definition));
334. }
335. /**
336. * create a constraint on a table
337. *
338. * @param string $table name of the table on which the constraint is to be created
339. * @param string $name name of the constraint to be created
340. * @param array $definition associative array that defines properties of the constraint to be created.
341. * Currently, only one property named FIELDS is supported. This property
342. * is also an associative with the names of the constraint fields as array
343. * constraints. Each entry of this array is set to another type of associative
344. * array that specifies properties of the constraint that are specific to
345. * each field.
346. *
347. * Example
348. * array(
349. * 'fields' => array(
350. * 'user_name' => array(),
351. * 'last_login' => array()
352. * )
353. * )
354. * @return void
355. */
356. public function createConstraintSql($table, $name, $definition)
357. {
358. $table = $this->conn->quoteIdentifier($table);
359. $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
360. $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
362. if (isset($definition['primary']) && $definition['primary']) {
363. $query .= ' PRIMARY KEY';
364. } elseif (isset($definition['unique']) && $definition['unique']) {
365. $query .= ' UNIQUE';
366. }
368. $fields = array();
369. foreach (array_keys($definition['fields']) as $field) {
370. $fields[] = $this->conn->quoteIdentifier($field, true);
371. }
372. $query .= ' ('. implode(', ', $fields) . ')';
374. return $query;
375. }
376. /**
377. * Get the stucture of a field into an array
378. *
379. * @param string $table name of the table on which the index is to be created
380. * @param string $name name of the index to be created
381. * @param array $definition associative array that defines properties of the index to be created.
382. * Currently, only one property named FIELDS is supported. This property
383. * is also an associative with the names of the index fields as array
384. * indexes. Each entry of this array is set to another type of associative
385. * array that specifies properties of the index that are specific to
386. * each field.
387. *
388. * Currently, only the sorting property is supported. It should be used
389. * to define the sorting direction of the index. It may be set to either
390. * ascending or descending.
391. *
392. * Not all DBMS support index sorting direction configuration. The DBMS
393. * drivers of those that do not support it ignore this property. Use the
394. * function supports() to determine whether the DBMS driver can manage indexes.
395. *
396. * Example
397. * array(
398. * 'fields' => array(
399. * 'user_name' => array(
400. * 'sorting' => 'ascending'
401. * ),
402. * 'last_login' => array()
403. * )
404. * )
405. * @return void
406. */
407. public function createIndex($table, $name, array $definition)
408. {
409. return $this->conn->execute($this->createIndexSql($table, $name, $definition));
410. }
411. /**
412. * Get the stucture of a field into an array
413. *
414. * @param string $table name of the table on which the index is to be created
415. * @param string $name name of the index to be created
416. * @param array $definition associative array that defines properties of the index to be created.
417. * @see Doctrine_Export::createIndex()
418. * @return string
419. */
420. public function createIndexSql($table, $name, array $definition)
421. {
422. $table = $this->conn->quoteIdentifier($table);
423. $name = $this->conn->quoteIdentifier($name);
424. $type = '';
426. if(isset($definition['type'])) {
427. switch (strtolower($definition['type'])) {
428. case 'unique':
429. $type = strtoupper($definition['type']) . ' ';
430. break;
431. default:
432. throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
433. }
434. }
436. $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
438. $fields = array();
439. foreach ($definition['fields'] as $field) {
440. $fields[] = $this->conn->quoteIdentifier($field);
441. }
442. $query .= ' (' . implode(', ', $fields) . ')';
444. return $query;
445. }
446. /**
447. * createForeignKeySql
448. *
449. * @param string $table name of the table on which the foreign key is to be created
450. * @param array $definition associative array that defines properties of the foreign key to be created.
451. * @return string
452. */
453. public function createForeignKeySql($table, array $definition)
454. {
455. $table = $this->conn->quoteIdentifier($table);
457. $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $this->getForeignKeyDeclaration($definition);
459. return $query;
461. /**
462. * alter an existing table
463. * (this method is implemented by the drivers)
464. *
465. * @param string $name name of the table that is intended to be changed.
466. * @param array $changes associative array that contains the details of each type
467. * of change that is intended to be performed. The types of
468. * changes that are currently supported are defined as follows:
469. *
470. * name
471. *
472. * New name for the table.
473. *
474. * add
475. *
476. * Associative array with the names of fields to be added as
477. * indexes of the array. The value of each entry of the array
478. * should be set to another associative array with the properties
479. * of the fields to be added. The properties of the fields should
480. * be the same as defined by the MDB2 parser.
481. *
482. *
483. * remove
484. *
485. * Associative array with the names of fields to be removed as indexes
486. * of the array. Currently the values assigned to each entry are ignored.
487. * An empty array should be used for future compatibility.
488. *
489. * rename
490. *
491. * Associative array with the names of fields to be renamed as indexes
492. * of the array. The value of each entry of the array should be set to
493. * another associative array with the entry named name with the new
494. * field name and the entry named Declaration that is expected to contain
495. * the portion of the field declaration already in DBMS specific SQL code
496. * as it is used in the CREATE TABLE statement.
497. *
498. * change
499. *
500. * Associative array with the names of the fields to be changed as indexes
501. * of the array. Keep in mind that if it is intended to change either the
502. * name of a field and any other properties, the change array entries
503. * should have the new names of the fields as array indexes.
504. *
505. * The value of each entry of the array should be set to another associative
506. * array with the properties of the fields to that are meant to be changed as
507. * array entries. These entries should be assigned to the new values of the
508. * respective properties. The properties of the fields should be the same
509. * as defined by the MDB2 parser.
510. *
511. * Example
512. * array(
513. * 'name' => 'userlist',
514. * 'add' => array(
515. * 'quota' => array(
516. * 'type' => 'integer',
517. * 'unsigned' => 1
518. * )
519. * ),
520. * 'remove' => array(
521. * 'file_limit' => array(),
522. * 'time_limit' => array()
523. * ),
524. * 'change' => array(
525. * 'name' => array(
526. * 'length' => '20',
527. * 'definition' => array(
528. * 'type' => 'text',
529. * 'length' => 20,
530. * ),
531. * )
532. * ),
533. * 'rename' => array(
534. * 'sex' => array(
535. * 'name' => 'gender',
536. * 'definition' => array(
537. * 'type' => 'text',
538. * 'length' => 1,
539. * 'default' => 'M',
540. * ),
541. * )
542. * )
543. * )
544. *
545. * @param boolean $check indicates whether the function should just check if the DBMS driver
546. * can perform the requested table alterations if the value is true or
547. * actually perform them otherwise.
548. * @return void
549. */
550. public function alterTable($name, array $changes, $check)
551. {
552. $this->conn->execute($this->alterTableSql($name, $changes, $check));
554. /**
555. * generates the sql for altering an existing table
556. * (this method is implemented by the drivers)
557. *
558. * @param string $name name of the table that is intended to be changed.
559. * @param array $changes associative array that contains the details of each type *
560. * @param boolean $check indicates whether the function should just check if the DBMS driver
561. * can perform the requested table alterations if the value is true or
562. * actually perform them otherwise.
563. * @see Doctrine_Export::alterTable()
564. * @return string
565. */
566. public function alterTableSql($name, array $changes, $check)
567. {
568. throw new Doctrine_Export_Exception('Alter table not supported by this driver.');
570. /**
571. * Get declaration of a number of field in bulk
572. *
573. * @param array $fields a multidimensional associative array.
574. * The first dimension determines the field name, while the second
575. * dimension is keyed with the name of the properties
576. * of the field being declared as array indexes. Currently, the types
577. * of supported field properties are as follows:
578. *
579. * length
580. * Integer value that determines the maximum length of the text
581. * field. If this argument is missing the field should be
582. * declared to have the longest length allowed by the DBMS.
583. *
584. * default
585. * Text value to be used as default for this field.
586. *
587. * notnull
588. * Boolean flag that indicates whether this field is constrained
589. * to not be set to null.
590. * charset
591. * Text value with the default CHARACTER SET for this field.
592. * collation
593. * Text value with the default COLLATION for this field.
594. * unique
595. * unique constraint
596. *
597. * @return string
598. */
599. public function getFieldDeclarationList(array $fields)
600. {
601. foreach ($fields as $fieldName => $field) {
602. $query = $this->getDeclaration($fieldName, $field);
604. $queryFields[] = $query;
605. }
606. return implode(', ', $queryFields);
607. }
608. /**
609. * Obtain DBMS specific SQL code portion needed to declare a generic type
610. * field to be used in statements like CREATE TABLE.
611. *
612. * @param string $name name the field to be declared.
613. * @param array $field associative array with the name of the properties
614. * of the field being declared as array indexes. Currently, the types
615. * of supported field properties are as follows:
616. *
617. * length
618. * Integer value that determines the maximum length of the text
619. * field. If this argument is missing the field should be
620. * declared to have the longest length allowed by the DBMS.
621. *
622. * default
623. * Text value to be used as default for this field.
624. *
625. * notnull
626. * Boolean flag that indicates whether this field is constrained
627. * to not be set to null.
628. * charset
629. * Text value with the default CHARACTER SET for this field.
630. * collation
631. * Text value with the default COLLATION for this field.
632. * unique
633. * unique constraint
634. * check
635. * column check constraint
636. *
637. * @return string DBMS specific SQL code portion that should be used to
638. * declare the specified field.
639. */
640. public function getDeclaration($name, array $field)
641. {
643. $default = $this->getDefaultFieldDeclaration($field);
645. $charset = (isset($field['charset']) && $field['charset']) ?
646. ' ' . $this->getCharsetFieldDeclaration($field['charset']) : '';
648. $collation = (isset($field['collation']) && $field['collation']) ?
649. ' ' . $this->getCollationFieldDeclaration($field['collation']) : '';
651. $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
653. $unique = (isset($field['unique']) && $field['unique']) ?
654. ' ' . $this->getUniqueFieldDeclaration() : '';
656. $check = (isset($field['check']) && $field['check']) ?
657. ' ' . $field['check'] : '';
659. $method = 'get' . $field['type'] . 'Declaration';
661. if (method_exists($this->conn->dataDict, $method)) {
662. return $this->conn->dataDict->$method($name, $field);
663. } else {
664. $dec = $this->conn->dataDict->getNativeDeclaration($field);
665. }
666. return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $unique . $check . $collation;
667. }
668. /**
669. * getDefaultDeclaration
670. * Obtain DBMS specific SQL code portion needed to set a default value
671. * declaration to be used in statements like CREATE TABLE.
672. *
673. * @param array $field field definition array
674. * @return string DBMS specific SQL code portion needed to set a default value
675. */
676. public function getDefaultFieldDeclaration($field)
677. {
678. $default = '';
679. if (isset($field['default'])) {
680. if ($field['default'] === '') {
681. $field['default'] = empty($field['notnull'])
682. ? null : $this->valid_default_values[$field['type']];
684. if ($field['default'] === '' &&
685. ($this->conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)) {
686. $field['default'] = null;
687. }
688. }
690. if ($field['type'] === 'boolean') {
691. $fields['default'] = $this->conn->convertBooleans($field['default']);
692. }
693. $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
694. }
695. return $default;
696. }
697. /**
698. * Obtain DBMS specific SQL code portion needed to set a CHECK constraint
699. * declaration to be used in statements like CREATE TABLE.
700. *
701. * @param array $definition check definition
702. * @return string DBMS specific SQL code portion needed to set a CHECK constraint
703. */
704. public function getCheckDeclaration(array $definition)
705. {
706. $constraints = array();
707. foreach ($definition as $field => $def) {
708. if (is_string($def)) {
709. $constraints[] = 'CHECK (' . $def . ')';
710. } else {
711. if (isset($def['min'])) {
712. $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')';
713. }
715. if (isset($def['max'])) {
716. $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')';
717. }
718. }
719. }
721. return implode(', ', $constraints);
722. }
723. /**
724. * Obtain DBMS specific SQL code portion needed to set an index
725. * declaration to be used in statements like CREATE TABLE.
726. *
727. * @param string $name name of the index
728. * @param array $definition index definition
729. * @return string DBMS specific SQL code portion needed to set an index
730. */
731. public function getIndexDeclaration($name, array $definition)
732. {
733. $name = $this->conn->quoteIdentifier($name);
734. $type = '';
736. if (isset($definition['type'])) {
737. if (strtolower($definition['type']) == 'unique') {
738. $type = strtoupper($definition['type']) . ' ';
739. } else {
740. throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
741. }
742. }
744. if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) {
745. throw new Doctrine_Export_Exception('No index columns given.');
746. }
748. $query = $type . 'INDEX ' . $name;
750. $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
752. return $query;
753. }
754. /**
755. * getIndexFieldDeclarationList
756. * Obtain DBMS specific SQL code portion needed to set an index
757. * declaration to be used in statements like CREATE TABLE.
758. *
759. * @return string
760. */
761. public function getIndexFieldDeclarationList(array $fields)
762. {
763. $ret = array();
764. foreach ($fields as $field => $definition) {
765. if(is_array($definition)) {
766. $ret[] = $this->conn->quoteIdentifier($field);
767. } else {
768. $ret[] = $this->conn->quoteIdentifier($definition);
769. }
770. }
771. return implode(', ', $ret);
772. }
773. /**
774. * A method to return the required SQL string that fits between CREATE ... TABLE
775. * to create the table as a temporary table.
776. *
777. * Should be overridden in driver classes to return the correct string for the
778. * specific database type.
779. *
780. * The default is to return the string "TEMPORARY" - this will result in a
781. * SQL error for any database that does not support temporary tables, or that
782. * requires a different SQL command from "CREATE TEMPORARY TABLE".
783. *
784. * @return string The string required to be placed between "CREATE" and "TABLE"
785. * to generate a temporary table, if possible.
786. */
787. public function getTemporaryTableQuery()
788. {
789. return 'TEMPORARY';
790. }
791. /**
792. * getForeignKeyDeclaration
793. * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
794. * of a field declaration to be used in statements like CREATE TABLE.
795. *
796. * @param array $definition an associative array with the following structure:
797. * name optional constraint name
798. *
799. * local the local field(s)
800. *
801. * foreign the foreign reference field(s)
802. *
803. * foreignTable the name of the foreign table
804. *
805. * onDelete referential delete action
806. *
807. * onUpdate referential update action
808. *
809. * deferred deferred constraint checking
810. *
811. * The onDelete and onUpdate keys accept the following values:
812. *
813. * CASCADE: Delete or update the row from the parent table and automatically delete or
814. * update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
815. * Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column
816. * in the parent table or in the child table.
817. *
818. * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the
819. * child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier
820. * specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
821. *
822. * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary
823. * key value is not allowed to proceed if there is a related foreign key value in the referenced table.
824. *
825. * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as
826. * omitting the ON DELETE or ON UPDATE clause.
827. *
828. * SET DEFAULT
829. *
830. * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
831. * of a field declaration.
832. */
833. public function getForeignKeyDeclaration(array $definition)
834. {
835. $sql = $this->getForeignKeyBaseDeclaration($definition);
836. $sql .= $this->getAdvancedForeignKeyOptions($definition);
838. return $sql;
840. /**
841. * getAdvancedForeignKeyOptions
842. * Return the FOREIGN KEY query section dealing with non-standard options
843. * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
844. *
845. * @param array $definition foreign key definition
846. * @return string
847. */
848. public function getAdvancedForeignKeyOptions(array $definition)
849. {
850. $query = '';
851. if ( ! empty($definition['onUpdate'])) {
852. $query .= ' ON UPDATE ' . $this->getForeignKeyRefentialAction($definition['onUpdate']);
853. }
854. if ( ! empty($definition['onDelete'])) {
855. $query .= ' ON DELETE ' . $this->getForeignKeyRefentialAction($definition['onDelete']);
856. }
857. return $query;
858. }
859. /**
860. * getForeignKeyReferentialAction
861. *
862. * returns given referential action in uppercase if valid, otherwise throws
863. * an exception
864. *
865. * @throws Doctrine_Exception_Exception if unknown referential action given
866. * @param string $action foreign key referential action
867. * @param string foreign key referential action in uppercase
868. */
869. public function getForeignKeyReferentialAction($action)
870. {
871. $upper = strtoupper($action);
872. switch ($upper) {
873. case 'CASCADE':
874. case 'SET NULL':
875. case 'NO ACTION':
876. case 'RESTRICT':
877. case 'SET DEFAULT':
878. return $upper;
879. break;
880. default:
881. throw new Doctrine_Export_Exception('Unknown foreign key referential action \'' . $upper . '\' given.');
882. }
883. }
884. /**
885. * getForeignKeyBaseDeclaration
886. * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
887. * of a field declaration to be used in statements like CREATE TABLE.
888. *
889. * @param array $definition
890. * @return string
891. */
892. public function getForeignKeyBaseDeclaration(array $definition)
893. {
894. $sql = '';
895. if (isset($definition['name'])) {
896. $sql .= 'CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' ';
897. }
898. $sql .= 'FOREIGN KEY (';
900. if ( ! isset($definition['local'])) {
901. throw new Doctrine_Export_Exception('Local reference field missing from definition.');
902. }
903. if ( ! isset($definition['foreign'])) {
904. throw new Doctrine_Export_Exception('Foreign reference field missing from definition.');
905. }
906. if ( ! isset($definition['foreignTable'])) {
907. throw new Doctrine_Export_Exception('Foreign reference table missing from definition.');
908. }
910. if ( ! is_array($definition['local'])) {
911. $definition['local'] = array($definition['local']);
912. }
913. if ( ! is_array($definition['foreign'])) {
914. $definition['foreign'] = array($definition['foreign']);
915. }
917. $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local']))
918. . ') REFERENCES '
919. . $definition['foreignTable'] . '('
920. . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')';
922. return $sql;
923. }
924. /**
925. * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint
926. * of a field declaration to be used in statements like CREATE TABLE.
927. *
928. * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint
929. * of a field declaration.
930. */
931. public function getUniqueFieldDeclaration()
932. {
933. return 'UNIQUE';
934. }
935. /**
936. * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
937. * of a field declaration to be used in statements like CREATE TABLE.
938. *
939. * @param string $charset name of the charset
940. * @return string DBMS specific SQL code portion needed to set the CHARACTER SET
941. * of a field declaration.
942. */
943. public function getCharsetFieldDeclaration($charset)
944. {
945. return '';
946. }
947. /**
948. * Obtain DBMS specific SQL code portion needed to set the COLLATION
949. * of a field declaration to be used in statements like CREATE TABLE.
950. *
951. * @param string $collation name of the collation
952. * @return string DBMS specific SQL code portion needed to set the COLLATION
953. * of a field declaration.
954. */
955. public function getCollationFieldDeclaration($collation)
956. {
957. return '';
958. }
959. /**
960. * exportSchema
961. * method for exporting Doctrine_Record classes to a schema
962. *
963. * if the directory parameter is given this method first iterates
964. * recursively trhough the given directory in order to find any model classes
965. *
966. * Then it iterates through all declared classes and creates tables for the ones
967. * that extend Doctrine_Record and are not abstract classes
968. *
969. * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
970. * occurred during the create table operation
971. * @param string $directory optional directory parameter
972. * @return void
973. */
974. public function exportSchema($directory = null)
975. {
976. $sql = $this->exportSql($directory);
978. $this->conn->beginTransaction();
980. foreach ($sql as $query) {
981. try {
982. $this->conn->exec($query);
983. } catch (Doctrine_Connection_Exception $e) {
984. // we only want to silence table already exists errors
985. if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
986. $this->conn->rollback();
987. throw $e;
988. }
989. }
990. }
991. $this->conn->commit();
992. }
993. /**
994. * exportClasses
995. * method for exporting Doctrine_Record classes to a schema
996. *
997. * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
998. * occurred during the create table operation
999. * @param array $classes
1000. * @return void
1001. */
1002. public function exportClasses(array $classes)
1003. {
1004. $sql = $this->exportClassesSql($classes);
1006. $this->conn->beginTransaction();
1008. foreach ($sql as $query) {
1009. try {
1010. $this->conn->exec($query);
1011. } catch (Doctrine_Connection_Exception $e) {
1012. // we only want to silence table already exists errors
1013. if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
1014. $this->conn->rollback();
1015. throw $e;
1016. }
1017. }
1018. }
1019. $this->conn->commit();
1020. }
1021. /**
1022. * exportClassesSql
1023. * method for exporting Doctrine_Record classes to a schema
1024. *
1025. * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1026. * occurred during the create table operation
1027. * @param array $classes
1028. * @return void
1029. */
1030. public function exportClassesSql(array $classes)
1031. {
1032. $parent = new ReflectionClass('Doctrine_Record');
1034. $sql = array();
1035. $fks = array();
1037. // we iterate trhough the diff of previously declared classes
1038. // and currently declared classes
1039. foreach ($classes as $name) {
1040. $class = new ReflectionClass($name);
1041. $conn = Doctrine_Manager::getInstance()->getConnectionForComponent($name);
1043. // check if class is an instance of Doctrine_Record and not abstract
1044. // class must have method setTableDefinition (to avoid non-Record subclasses like symfony's sfDoctrineRecord)
1045. if ($class->isSubclassOf($parent) && ! $class->isAbstract() && $class->hasMethod('setTableDefinition')
1046. && $class->getMethod('setTableDefinition')->getDeclaringClass()->getName() == $class->getName()) {
1047. $record = new $name();
1048. $table = $record->getTable();
1049. $data = $table->getExportableFormat();
1051. $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
1053. if (is_array($query)) {
1054. $sql = array_merge($sql, $query);
1055. } else {
1056. $sql[] = $query;
1057. }
1058. }
1059. }
1060. $sql = array_unique($sql);
1061. rsort($sql);
1063. return $sql;
1064. }
1065. /**
1066. * exportSql
1067. * returns the sql for exporting Doctrine_Record classes to a schema
1068. *
1069. * if the directory parameter is given this method first iterates
1070. * recursively trhough the given directory in order to find any model classes
1071. *
1072. * Then it iterates through all declared classes and creates tables for the ones
1073. * that extend Doctrine_Record and are not abstract classes
1074. *
1075. * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1076. * occurred during the create table operation
1077. * @param string $directory optional directory parameter
1078. * @return void
1079. */
1080. public function exportSql($directory = null)
1081. {
1082. $declared = get_declared_classes();
1084. if ($directory !== null) {
1085. foreach ((array) $directory as $dir) {
1086. $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir),
1087. RecursiveIteratorIterator::LEAVES_ONLY);
1089. foreach ($it as $file) {
1090. $e = explode('.', $file->getFileName());
1091. if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) {
1092. require_once $file->getPathName();
1093. }
1094. }
1095. }
1096. $declared = array_diff(get_declared_classes(), $declared);
1097. }
1099. return $this->exportClassesSql($declared);
1100. }
1101. /**
1102. * exportTable
1103. * exports given table into database based on column and option definitions
1104. *
1105. * @throws Doctrine_Connection_Exception if some error other than Doctrine::ERR_ALREADY_EXISTS
1106. * occurred during the create table operation
1107. * @return boolean whether or not the export operation was successful
1108. * false if table already existed in the database
1109. */
1110. public function exportTable(Doctrine_Table $table)
1111. {
1112. /**
1113. TODO: maybe there should be portability option for the following check
1114. if ( ! Doctrine::isValidClassname($table->getOption('declaringClass')->getName())) {
1115. throw new Doctrine_Export_Exception('Class name not valid.');
1116. }
1117. */
1119. try {
1120. $data = $table->getExportableFormat();
1122. $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']);
1123. } catch(Doctrine_Connection_Exception $e) {
1124. // we only want to silence table already exists errors
1125. if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
1126. throw $e;
1127. }
1128. }
1129. }
1130. }