# Source for file Mysql.php

Documentation is available at Mysql.php

1. <?php
2. /*
3. * $Id: Mysql.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_Export');
22. /**
23. * Doctrine_Export_Mysql
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_Mysql extends Doctrine_Export
35. {
36. /**
37. * create a new database
38. *
39. * @param string $name name of the database that should be created
40. * @return string
41. */
42. public function createDatabaseSql($name)
43. {
44. return 'CREATE DATABASE ' . $this->conn->quoteIdentifier($name, true);
45. }
46. /**
47. * drop an existing database
48. *
49. * @param string $name name of the database that should be dropped
50. * @return string
51. */
52. public function dropDatabaseSql($name)
53. {
54. return 'DROP DATABASE ' . $this->conn->quoteIdentifier($name);
55. }
56. /**
57. * create a new table
58. *
59. * @param string $name Name of the database that should be created
60. * @param array $fields Associative array that contains the definition of each field of the new table
61. * The indexes of the array entries are the names of the fields of the table an
62. * the array entry values are associative arrays like those that are meant to be
63. * passed with the field definitions to get[Type]Declaration() functions.
64. * array(
65. * 'id' => array(
66. * 'type' => 'integer',
67. * 'unsigned' => 1
68. * 'notnull' => 1
69. * 'default' => 0
70. * ),
71. * 'name' => array(
72. * 'type' => 'text',
73. * 'length' => 12
74. * ),
75. * 'password' => array(
76. * 'type' => 'text',
77. * 'length' => 12
78. * )
79. * );
80. * @param array $options An associative array of table options:
81. * array(
82. * 'comment' => 'Foo',
83. * 'charset' => 'utf8',
84. * 'collate' => 'utf8_unicode_ci',
85. * 'type' => 'innodb',
86. * );
87. *
88. * @return void
89. */
90. public function createTableSql($name, array $fields, array $options = array())
91. {
92. if ( ! $name)
93. throw new Doctrine_Export_Exception('no valid table name specified');
95. if (empty($fields)) {
96. throw new Doctrine_Export_Exception('no fields specified for table "'.$name.'"');
97. }
98. $queryFields = $this->getFieldDeclarationList($fields);
100. // build indexes for all foreign key fields (needed in MySQL!!)
101. if (isset($options['foreignKeys'])) {
102. foreach ($options['foreignKeys'] as $fk) {
103. $local = $fk['local'];
104. $found = false;
105. if (isset($options['indexes'])) {
106. foreach ($options['indexes'] as $definition) {
107. if (is_string($definition['fields'])) {
108. // Check if index already exists on the column
109. $found = ($local == $definition['fields']);
110. } else if (in_array($local, $definition['fields']) && count($definition['fields']) === 1) {
111. // Index already exists on the column
112. $found = true;
113. }
114. }
115. }
116. if (isset($options['primary']) && !empty($options['primary']) &&
117. in_array($local, $options['primary'])) {
118. // field is part of the PK and therefore already indexed
119. $found = true;
120. }
122. if ( ! $found) {
123. $options['indexes'][$local] = array('fields' => array($local => array()));
124. }
125. }
126. }
128. // add all indexes
129. if (isset($options['indexes']) && ! empty($options['indexes'])) {
130. foreach($options['indexes'] as $index => $definition) {
131. $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
132. }
133. }
135. // attach all primary keys
136. if (isset($options['primary']) && ! empty($options['primary'])) {
137. $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')';
138. }
140. $query = 'CREATE TABLE ' . $this->conn->quoteIdentifier($name, true) . ' (' . $queryFields . ')';
142. $optionStrings = array();
144. if (isset($options['comment'])) {
145. $optionStrings['comment'] = 'COMMENT = ' . $this->dbh->quote($options['comment'], 'text');
146. }
147. if (isset($options['charset'])) {
148. $optionStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
149. if (isset($options['collate'])) {
150. $optionStrings['charset'] .= ' COLLATE ' . $options['collate'];
151. }
152. }
154. $type = false;
156. // get the type of the table
157. if (isset($options['type'])) {
158. $type = $options['type'];
159. } else {
160. $type = $this->conn->getAttribute(Doctrine::ATTR_DEFAULT_TABLE_TYPE);
161. }
163. if ($type) {
164. $optionStrings[] = 'ENGINE = ' . $type;
165. }
167. if (!empty($optionStrings)) {
168. $query.= ' '.implode(' ', $optionStrings);
169. }
170. $sql[] = $query;
172. if (isset($options['foreignKeys'])) {
174. foreach ((array) $options['foreignKeys'] as $k => $definition) {
175. if (is_array($definition)) {
176. $sql[] = $this->createForeignKeySql($name, $definition);
177. }
178. }
179. }
180. return $sql;
181. }
182. /**
183. * alter an existing table
184. *
185. * @param string $name name of the table that is intended to be changed.
186. * @param array $changes associative array that contains the details of each type
187. * of change that is intended to be performed. The types of
188. * changes that are currently supported are defined as follows:
189. *
190. * name
191. *
192. * New name for the table.
193. *
194. * add
195. *
196. * Associative array with the names of fields to be added as
197. * indexes of the array. The value of each entry of the array
198. * should be set to another associative array with the properties
199. * of the fields to be added. The properties of the fields should
200. * be the same as defined by the Metabase parser.
201. *
202. *
203. * remove
204. *
205. * Associative array with the names of fields to be removed as indexes
206. * of the array. Currently the values assigned to each entry are ignored.
207. * An empty array should be used for future compatibility.
208. *
209. * rename
210. *
211. * Associative array with the names of fields to be renamed as indexes
212. * of the array. The value of each entry of the array should be set to
213. * another associative array with the entry named name with the new
214. * field name and the entry named Declaration that is expected to contain
215. * the portion of the field declaration already in DBMS specific SQL code
216. * as it is used in the CREATE TABLE statement.
217. *
218. * change
219. *
220. * Associative array with the names of the fields to be changed as indexes
221. * of the array. Keep in mind that if it is intended to change either the
222. * name of a field and any other properties, the change array entries
223. * should have the new names of the fields as array indexes.
224. *
225. * The value of each entry of the array should be set to another associative
226. * array with the properties of the fields to that are meant to be changed as
227. * array entries. These entries should be assigned to the new values of the
228. * respective properties. The properties of the fields should be the same
229. * as defined by the Metabase parser.
230. *
231. * Example
232. * array(
233. * 'name' => 'userlist',
234. * 'add' => array(
235. * 'quota' => array(
236. * 'type' => 'integer',
237. * 'unsigned' => 1
238. * )
239. * ),
240. * 'remove' => array(
241. * 'file_limit' => array(),
242. * 'time_limit' => array()
243. * ),
244. * 'change' => array(
245. * 'name' => array(
246. * 'length' => '20',
247. * 'definition' => array(
248. * 'type' => 'text',
249. * 'length' => 20,
250. * ),
251. * )
252. * ),
253. * 'rename' => array(
254. * 'sex' => array(
255. * 'name' => 'gender',
256. * 'definition' => array(
257. * 'type' => 'text',
258. * 'length' => 1,
259. * 'default' => 'M',
260. * ),
261. * )
262. * )
263. * )
264. *
265. * @param boolean $check indicates whether the function should just check if the DBMS driver
266. * can perform the requested table alterations if the value is true or
267. * actually perform them otherwise.
268. * @return boolean
269. */
270. public function alterTableSql($name, array $changes, $check)
271. {
272. if ( ! $name) {
273. throw new Doctrine_Export_Exception('no valid table name specified');
274. }
275. foreach ($changes as $changeName => $change) {
276. switch ($changeName) {
277. case 'add':
278. case 'remove':
279. case 'change':
280. case 'rename':
281. case 'name':
282. break;
283. default:
284. throw new Doctrine_Export_Exception('change type "' . $changeName . '" not yet supported');
285. }
286. }
288. if ($check) {
289. return true;
290. }
292. $query = '';
293. if ( ! empty($changes['name'])) {
294. $change_name = $this->conn->quoteIdentifier($changes['name']);
295. $query .= 'RENAME TO ' . $change_name;
296. }
298. if ( ! empty($changes['add']) && is_array($changes['add'])) {
299. foreach ($changes['add'] as $fieldName => $field) {
300. if ($query) {
301. $query.= ', ';
302. }
303. $query.= 'ADD ' . $this->getDeclaration($field['type'], $fieldName, $field);
304. }
305. }
307. if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
308. foreach ($changes['remove'] as $fieldName => $field) {
309. if ($query) {
310. $query .= ', ';
311. }
312. $fieldName = $this->conn->quoteIdentifier($fieldName);
313. $query .= 'DROP ' . $fieldName;
314. }
315. }
317. $rename = array();
318. if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
319. foreach ($changes['rename'] as $fieldName => $field) {
320. $rename[$field['name']] = $fieldName;
321. }
322. }
324. if ( ! empty($changes['change']) && is_array($changes['change'])) {
325. foreach ($changes['change'] as $fieldName => $field) {
326. if ($query) {
327. $query.= ', ';
328. }
329. if (isset($rename[$fieldName])) {
330. $oldFieldName = $rename[$fieldName];
331. unset($rename[$fieldName]);
332. } else {
333. $oldFieldName = $fieldName;
334. }
335. $oldFieldName = $this->conn->quoteIdentifier($oldFieldName, true);
336. $query .= 'CHANGE ' . $oldFieldName . ' '
337. . $this->getDeclaration($field['definition']['type'], $fieldName, $field['definition']);
338. }
339. }
341. if ( ! empty($rename) && is_array($rename)) {
342. foreach ($rename as $renameName => $renamedField) {
343. if ($query) {
344. $query.= ', ';
345. }
346. $field = $changes['rename'][$renamedField];
347. $renamedField = $this->conn->quoteIdentifier($renamedField, true);
348. $query .= 'CHANGE ' . $renamedField . ' '
349. . $this->getDeclaration($field['definition']['type'], $field['name'], $field['definition']);
350. }
351. }
353. if ( ! $query) {
354. return false;
355. }
357. $name = $this->conn->quoteIdentifier($name, true);
358. return $this->conn->exec('ALTER TABLE ' . $name . ' ' . $query);
359. }
360. /**
361. * create sequence
362. *
363. * @param string $sequenceName name of the sequence to be created
364. * @param string $start start value of the sequence; default is 1
365. * @param array $options An associative array of table options:
366. * array(
367. * 'comment' => 'Foo',
368. * 'charset' => 'utf8',
369. * 'collate' => 'utf8_unicode_ci',
370. * 'type' => 'innodb',
371. * );
372. * @return boolean
373. */
374. public function createSequence($sequenceName, $start = 1, array $options = array())
375. {
376. $sequenceName = $this->conn->quoteIdentifier($this->conn->getSequenceName($sequenceName), true);
377. $seqcolName = $this->conn->quoteIdentifier($this->conn->getAttribute(Doctrine::ATTR_SEQCOL_NAME), true);
379. $optionsStrings = array();
381. if (isset($options['comment']) && ! empty($options['comment'])) {
382. $optionsStrings['comment'] = 'COMMENT = ' . $this->conn->quote($options['comment'], 'string');
383. }
385. if (isset($options['charset']) && ! empty($options['charset'])) {
386. $optionsStrings['charset'] = 'DEFAULT CHARACTER SET ' . $options['charset'];
388. if (isset($options['collate'])) {
389. $optionsStrings['collate'] .= ' COLLATE ' . $options['collate'];
390. }
391. }
393. $type = false;
395. if (isset($options['type'])) {
396. $type = $options['type'];
397. } else {
398. $type = $this->conn->default_table_type;
399. }
400. if ($type) {
401. $optionsStrings[] = 'ENGINE = ' . $type;
402. }
405. try {
406. $query = 'CREATE TABLE ' . $sequenceName
407. . ' (' . $seqcolName . ' INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ('
408. . $seqcolName . '))'
409. . strlen($this->conn->default_table_type) ? ' TYPE = '
410. . $this->conn->default_table_type : '';
412. $res = $this->conn->exec($query);
413. } catch(Doctrine_Connection_Exception $e) {
414. throw new Doctrine_Export_Exception('could not create sequence table');
415. }
417. if ($start == 1)
418. return true;
420. $query = 'INSERT INTO ' . $sequenceName
421. . ' (' . $seqcolName . ') VALUES (' . ($start - 1) . ')';
423. $res = $this->conn->exec($query);
425. // Handle error
426. try {
427. $result = $this->conn->exec('DROP TABLE ' . $sequenceName);
428. } catch(Doctrine_Connection_Exception $e) {
429. throw new Doctrine_Export_Exception('could not drop inconsistent sequence table');
430. }
433. }
434. /**
435. * Get the stucture of a field into an array
436. *
437. * @author Leoncx
438. * @param string $table name of the table on which the index is to be created
439. * @param string $name name of the index to be created
440. * @param array $definition associative array that defines properties of the index to be created.
441. * Currently, only one property named FIELDS is supported. This property
442. * is also an associative with the names of the index fields as array
443. * indexes. Each entry of this array is set to another type of associative
444. * array that specifies properties of the index that are specific to
445. * each field.
446. *
447. * Currently, only the sorting property is supported. It should be used
448. * to define the sorting direction of the index. It may be set to either
449. * ascending or descending.
450. *
451. * Not all DBMS support index sorting direction configuration. The DBMS
452. * drivers of those that do not support it ignore this property. Use the
453. * function supports() to determine whether the DBMS driver can manage indexes.
454. *
455. * Example
456. * array(
457. * 'fields' => array(
458. * 'user_name' => array(
459. * 'sorting' => 'ASC'
460. * 'length' => 10
461. * ),
462. * 'last_login' => array()
463. * )
464. * )
465. * @throws PDOException
466. * @return void
467. */
468. public function createIndexSql($table, $name, array $definition)
469. {
470. $table = $table;
471. $name = $this->conn->getIndexName($name);
472. $type = '';
473. if (isset($definition['type'])) {
474. switch (strtolower($definition['type'])) {
475. case 'fulltext':
476. case 'unique':
477. $type = strtoupper($definition['type']) . ' ';
478. break;
479. default:
480. throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
481. }
482. }
483. $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
484. $query .= ' (' . $this->getIndexFieldDeclarationList() . ')';
486. return $query;
487. }
488. /**
489. * getDefaultDeclaration
490. * Obtain DBMS specific SQL code portion needed to set a default value
491. * declaration to be used in statements like CREATE TABLE.
492. *
493. * @param array $field field definition array
494. * @return string DBMS specific SQL code portion needed to set a default value
495. */
496. public function getDefaultFieldDeclaration($field)
497. {
498. $default = '';
499. if (isset($field['default']) && $field['length'] <= 255) {
500. if ($field['default'] === '') {
501. $field['default'] = empty($field['notnull'])
502. ? null : $this->valid_default_values[$field['type']];
504. if ($field['default'] === ''
505. && ($conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)
506. ) {
507. $field['default'] = ' ';
508. }
509. }
511. $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
512. }
513. return $default;
514. }
515. /**
516. * Obtain DBMS specific SQL code portion needed to set an index
517. * declaration to be used in statements like CREATE TABLE.
518. *
519. * @param string $charset name of the index
520. * @param array $definition index definition
521. * @return string DBMS specific SQL code portion needed to set an index
522. */
523. public function getIndexDeclaration($name, array $definition)
524. {
525. $name = $this->conn->quoteIdentifier($name);
526. $type = '';
527. if(isset($definition['type'])) {
528. switch (strtolower($definition['type'])) {
529. case 'fulltext':
530. case 'unique':
531. $type = strtoupper($definition['type']) . ' ';
532. break;
533. default:
534. throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
535. }
536. }
538. if ( ! isset($definition['fields'])) {
539. throw new Doctrine_Export_Exception('No index columns given.');
540. }
541. if ( ! is_array($definition['fields'])) {
542. $definition['fields'] = array($definition['fields']);
543. }
545. $query = $type . 'INDEX ' . $this->conn->formatter->getIndexName($name);
547. $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
549. return $query;
550. }
551. /**
552. * getIndexFieldDeclarationList
553. * Obtain DBMS specific SQL code portion needed to set an index
554. * declaration to be used in statements like CREATE TABLE.
555. *
556. * @return string
557. */
558. public function getIndexFieldDeclarationList(array $fields)
559. {
560. $declFields = array();
562. foreach ($fields as $fieldName => $field) {
563. $fieldString = $fieldName;
565. if (is_array($field)) {
566. if (isset($field['length'])) {
567. $fieldString .= '(' . $field['length'] . ')';
568. }
570. if (isset($field['sorting'])) {
571. $sort = strtoupper($field['sorting']);
572. switch ($sort) {
573. case 'ASC':
574. case 'DESC':
575. $fieldString .= ' ' . $sort;
576. break;
577. default:
578. throw new Doctrine_Export_Exception('Unknown index sorting option given.');
579. }
580. }
581. } else {
582. $fieldString = $field;
583. }
584. $declFields[] = $fieldString;
585. }
586. return implode(', ', $declFields);
587. }
588. /**
589. * getAdvancedForeignKeyOptions
590. * Return the FOREIGN KEY query section dealing with non-standard options
591. * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
592. *
593. * @param array $definition
594. * @return string
595. */
596. public function getAdvancedForeignKeyOptions(array $definition)
597. {
598. $query = '';
599. if (!empty($definition['match'])) {
600. $query .= ' MATCH ' . $definition['match'];
601. }
602. if (!empty($definition['onUpdate'])) {
603. $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialAction($definition['onUpdate']);
604. }
605. if (!empty($definition['onDelete'])) {
606. $query .= ' ON DELETE ' . $this->getForeignKeyReferentialAction($definition['onDelete']);
607. }
608. return $query;
609. }
610. /**
611. * drop existing index
612. *
613. * @param string $table name of table that should be used in method
614. * @param string $name name of the index to be dropped
615. * @return void
616. */
617. public function dropIndexSql($table, $name)
618. {
619. $table = $this->conn->quoteIdentifier($table, true);
620. $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name), true);
621. return 'DROP INDEX ' . $name . ' ON ' . $table;
622. }
623. /**
624. * dropTable
625. *
626. * @param string $table name of table that should be dropped from the database
627. * @throws PDOException
628. * @return void
629. */
630. public function dropTableSql($table)
631. {
632. $table = $this->conn->quoteIdentifier($table, true);
633. return 'DROP TABLE ' . $table;
634. }
635. }