# Source for file Firebird.php

Documentation is available at Firebird.php

1. <?php
2. /*
3. * $Id: Firebird.php 1753 2007-06-19 11:10:13Z 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.com>.
20. */
21. Doctrine::autoload('Doctrine_Export');
22. /**
23. * Doctrine_Export_Sqlite
24. *
25. * @package Doctrine
26. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
27. * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
28. * @author Lorenzo Alberton <l.alberton@quipo.it> (PEAR MDB2 Interbase driver)
29. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
30. * @category Object Relational Mapping
31. * @link www.phpdoctrine.com
32. * @since 1.0
33. * @version $Revision: 1753 $
34. */
35. class Doctrine_Export_Firebird extends Doctrine_Export
36. {
37. /**
38. * create a new database
39. *
40. * @param string $name name of the database that should be created
41. * @return void
42. */
43. public function createDatabase($name)
44. {
45. throw new Doctrine_Export_Exception(
46. 'PHP Interbase API does not support direct queries. You have to ' .
47. 'create the db manually by using isql command or a similar program');
48. }
49. /**
50. * drop an existing database
51. *
52. * @param string $name name of the database that should be dropped
53. * @return void
54. */
55. public function dropDatabase($name)
56. {
57. throw new Doctrine_Export_Exception(
58. 'PHP Interbase API does not support direct queries. You have ' .
59. 'to drop the db manually by using isql command or a similar program');
60. }
61. /**
62. * add an autoincrement sequence + trigger
63. *
64. * @param string $name name of the PK field
65. * @param string $table name of the table
66. * @param string $start start value for the sequence
67. * @return void
68. */
69. public function _makeAutoincrement($name, $table, $start = null)
70. {
71. if (is_null($start)) {
72. $this->conn->beginTransaction();
73. $query = 'SELECT MAX(' . $this->conn->quoteIdentifier($name, true) . ') FROM ' . $this->conn->quoteIdentifier($table, true);
74. $start = $this->conn->fetchOne($query, 'integer');
76. ++$start;
77. $result = $this->createSequence($table, $start);
78. $this->conn->commit();
79. } else {
80. $result = $this->createSequence($table, $start);
81. }
83. $sequence_name = $this->conn->formatter->getSequenceName($table);
84. $trigger_name = $this->conn->quoteIdentifier($table . '_AUTOINCREMENT_PK', true);
86. $table = $this->conn->quoteIdentifier($table, true);
87. $name = $this->conn->quoteIdentifier($name, true);
89. $triggerSql = 'CREATE TRIGGER ' . $trigger_name . ' FOR ' . $table . '
90. ACTIVE BEFORE INSERT POSITION 0
91. AS
92. BEGIN
93. IF (NEW.' . $name . ' IS NULL OR NEW.' . $name . ' = 0) THEN
94. NEW.' . $name . ' = GEN_ID('.$sequence_name.', 1);
95. END';
96. $result = $this->conn->exec($triggerSql);
98. // TODO ? $this->_silentCommit();
100. return $result;
101. }
102. /**
103. * drop an existing autoincrement sequence + trigger
104. *
105. * @param string $table name of the table
106. * @return void
107. */
108. public function _dropAutoincrement($table)
109. {
111. $result = $this->dropSequence($table);
113. //remove autoincrement trigger associated with the table
114. $table = $this->conn->quote(strtoupper($table));
115. $triggerName = $this->conn->quote(strtoupper($table) . '_AUTOINCREMENT_PK');
117. return $this->conn->exec("DELETE FROM RDB\$TRIGGERS WHERE UPPER(RDB\$RELATION_NAME)=" . $table . " AND UPPER(RDB\$TRIGGER_NAME)=" . $triggerName);
118. }
119. /**
120. * create a new table
121. *
122. * @param string $name Name of the database that should be created
123. * @param array $fields Associative array that contains the definition of each field of the new table
124. * The indexes of the array entries are the names of the fields of the table an
125. * the array entry values are associative arrays like those that are meant to be
126. * passed with the field definitions to get[Type]Declaration() functions.
127. *
128. * Example
129. * array(
130. *
131. * 'id' => array(
132. * 'type' => 'integer',
133. * 'unsigned' => 1,
134. * 'notnull' => 1,
135. * 'default' => 0,
136. * ),
137. * 'name' => array(
138. * 'type' => 'text',
139. * 'length' => 12,
140. * ),
141. * 'description' => array(
142. * 'type' => 'text',
143. * 'length' => 12,
144. * )
145. * );
146. * @param array $options An associative array of table options:
147. *
148. * @return void
149. */
150. public function createTable($name, array $fields, array $options = array()) {
151. parent::createTable($name, $fields, $options);
153. // TODO ? $this->_silentCommit();
154. foreach ($fields as $field_name => $field) {
155. if ( ! empty($field['autoincrement'])) {
156. //create PK constraint
157. $pk_definition = array(
158. 'fields' => array($field_name => array()),
159. 'primary' => true,
160. );
161. //$pk_name = $name.'_PK';
162. $pk_name = null;
163. $result = $this->createConstraint($name, $pk_name, $pk_definition);
165. //create autoincrement sequence + trigger
166. return $this->_makeAutoincrement($field_name, $name, 1);
167. }
168. }
169. }
170. /**
171. * Check if planned changes are supported
172. *
173. * @param string $name name of the database that should be dropped
174. * @return void
175. */
176. public function checkSupportedChanges(&$changes)
177. {
178. foreach ($changes as $change_name => $change) {
179. switch ($change_name) {
180. case 'notnull':
181. throw new Doctrine_DataDict_Exception('it is not supported changes to field not null constraint');
182. case 'default':
183. throw new Doctrine_DataDict_Exception('it is not supported changes to field default value');
184. case 'length':
185. /*
186. return throw new Doctrine_DataDict_Firebird_Exception('it is not supported changes to field default length');
187. */
188. case 'unsigned':
189. case 'type':
190. case 'declaration':
191. case 'definition':
192. break;
193. default:
194. throw new Doctrine_DataDict_Exception('it is not supported change of type' . $change_name);
195. }
196. }
197. return true;
198. }
199. /**
200. * drop an existing table
201. *
202. * @param string $name name of the table that should be dropped
203. * @return mixed MDB2_OK on success, a MDB2 error on failure
204. * @access public
205. */
206. public function dropTable($name)
207. {
208. $result = $this->_dropAutoincrement($name);
209. $result = parent::dropTable($name);
211. //$this->_silentCommit();
213. return $result;
214. }
215. /**
216. * alter an existing table
217. *
218. * @param string $name name of the table that is intended to be changed.
219. * @param array $changes associative array that contains the details of each type
220. * of change that is intended to be performed. The types of
221. * changes that are currently supported are defined as follows:
222. *
223. * name
224. *
225. * New name for the table.
226. *
227. * add
228. *
229. * Associative array with the names of fields to be added as
230. * indexes of the array. The value of each entry of the array
231. * should be set to another associative array with the properties
232. * of the fields to be added. The properties of the fields should
233. * be the same as defined by the Metabase parser.
234. *
235. *
236. * remove
237. *
238. * Associative array with the names of fields to be removed as indexes
239. * of the array. Currently the values assigned to each entry are ignored.
240. * An empty array should be used for future compatibility.
241. *
242. * rename
243. *
244. * Associative array with the names of fields to be renamed as indexes
245. * of the array. The value of each entry of the array should be set to
246. * another associative array with the entry named name with the new
247. * field name and the entry named Declaration that is expected to contain
248. * the portion of the field declaration already in DBMS specific SQL code
249. * as it is used in the CREATE TABLE statement.
250. *
251. * change
252. *
253. * Associative array with the names of the fields to be changed as indexes
254. * of the array. Keep in mind that if it is intended to change either the
255. * name of a field and any other properties, the change array entries
256. * should have the new names of the fields as array indexes.
257. *
258. * The value of each entry of the array should be set to another associative
259. * array with the properties of the fields to that are meant to be changed as
260. * array entries. These entries should be assigned to the new values of the
261. * respective properties. The properties of the fields should be the same
262. * as defined by the Metabase parser.
263. *
264. * Example
265. * array(
266. * 'name' => 'userlist',
267. * 'add' => array(
268. * 'quota' => array(
269. * 'type' => 'integer',
270. * 'unsigned' => 1
271. * )
272. * ),
273. * 'remove' => array(
274. * 'file_limit' => array(),
275. * 'time_limit' => array()
276. * ),
277. * 'change' => array(
278. * 'name' => array(
279. * 'length' => '20',
280. * 'definition' => array(
281. * 'type' => 'text',
282. * 'length' => 20,
283. * ),
284. * )
285. * ),
286. * 'rename' => array(
287. * 'sex' => array(
288. * 'name' => 'gender',
289. * 'definition' => array(
290. * 'type' => 'text',
291. * 'length' => 1,
292. * 'default' => 'M',
293. * ),
294. * )
295. * )
296. * )
297. *
298. * @param boolean $check indicates whether the function should just check if the DBMS driver
299. * can perform the requested table alterations if the value is true or
300. * actually perform them otherwise.
301. * @return void
302. */
303. public function alterTable($name, array $changes, $check)
304. {
305. foreach ($changes as $changeName => $change) {
306. switch ($changeName) {
307. case 'add':
308. case 'remove':
309. case 'rename':
310. break;
311. case 'change':
312. foreach ($changes['change'] as $field) {
313. $this->checkSupportedChanges($field);
314. }
315. break;
316. default:
317. throw new Doctrine_DataDict_Exception('change type ' . $changeName . ' not yet supported');
318. }
319. }
320. if ($check) {
321. return true;
322. }
323. $query = '';
324. if (!empty($changes['add']) && is_array($changes['add'])) {
325. foreach ($changes['add'] as $fieldName => $field) {
326. if ($query) {
327. $query.= ', ';
328. }
329. $query.= 'ADD ' . $this->getDeclaration($field['type'], $fieldName, $field, $name);
330. }
331. }
333. if (!empty($changes['remove']) && is_array($changes['remove'])) {
334. foreach ($changes['remove'] as $field_name => $field) {
335. if ($query) {
336. $query.= ', ';
337. }
338. $field_name = $this->conn->quoteIdentifier($field_name, true);
339. $query.= 'DROP ' . $field_name;
340. }
341. }
343. if (!empty($changes['rename']) && is_array($changes['rename'])) {
344. foreach ($changes['rename'] as $field_name => $field) {
345. if ($query) {
346. $query.= ', ';
347. }
348. $field_name = $this->conn->quoteIdentifier($field_name, true);
349. $query.= 'ALTER ' . $field_name . ' TO ' . $this->conn->quoteIdentifier($field['name'], true);
350. }
351. }
353. if (!empty($changes['change']) && is_array($changes['change'])) {
354. // missing support to change DEFAULT and NULLability
355. foreach ($changes['change'] as $fieldName => $field) {
356. $this->checkSupportedChanges($field);
357. if ($query) {
358. $query.= ', ';
359. }
360. $this->conn->loadModule('Datatype', null, true);
361. $field_name = $this->conn->quoteIdentifier($fieldName, true);
362. $query.= 'ALTER ' . $field_name.' TYPE ' . $this->getTypeDeclaration($field['definition']);
363. }
364. }
366. if (!strlen($query)) {
367. return false;
368. }
370. $name = $this->conn->quoteIdentifier($name, true);
371. $result = $this->conn->exec('ALTER TABLE ' . $name . ' ' . $query);
372. $this->_silentCommit();
373. return $result;
374. }
375. /**
376. * Get the stucture of a field into an array
377. *
378. * @param string $table name of the table on which the index is to be created
379. * @param string $name name of the index to be created
380. * @param array $definition associative array that defines properties of the index to be created.
381. * Currently, only one property named FIELDS is supported. This property
382. * is also an associative with the names of the index fields as array
383. * indexes. Each entry of this array is set to another type of associative
384. * array that specifies properties of the index that are specific to
385. * each field.
386. *
387. * Currently, only the sorting property is supported. It should be used
388. * to define the sorting direction of the index. It may be set to either
389. * ascending or descending.
390. *
391. * Not all DBMS support index sorting direction configuration. The DBMS
392. * drivers of those that do not support it ignore this property. Use the
393. * function support() to determine whether the DBMS driver can manage indexes.
395. * Example
396. * array(
397. * 'fields' => array(
398. * 'user_name' => array(
399. * 'sorting' => 'ascending'
400. * ),
401. * 'last_login' => array()
402. * )
403. * )
404. * @return void
405. */
406. public function createIndexSql($table, $name, array $definition)
407. {
408. $query = 'CREATE';
410. $query_sort = '';
411. foreach ($definition['fields'] as $field) {
412. if (!strcmp($query_sort, '') && isset($field['sorting'])) {
413. switch ($field['sorting']) {
414. case 'ascending':
415. $query_sort = ' ASC';
416. break;
417. case 'descending':
418. $query_sort = ' DESC';
419. break;
420. }
421. }
422. }
423. $table = $this->conn->quoteIdentifier($table, true);
424. $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name), true);
425. $query .= $query_sort. ' INDEX ' . $name . ' ON ' . $table;
426. $fields = array();
427. foreach (array_keys($definition['fields']) as $field) {
428. $fields[] = $this->conn->quoteIdentifier($field, true);
429. }
430. $query .= ' ('.implode(', ', $fields) . ')';
432. return $query;
433. }
434. /**
435. * create a constraint on a table
436. *
437. * @param string $table name of the table on which the constraint is to be created
438. * @param string $name name of the constraint to be created
439. * @param array $definition associative array that defines properties of the constraint to be created.
440. * Currently, only one property named FIELDS is supported. This property
441. * is also an associative with the names of the constraint fields as array
442. * constraints. Each entry of this array is set to another type of associative
443. * array that specifies properties of the constraint that are specific to
444. * each field.
445. *
446. * Example
447. * array(
448. * 'fields' => array(
449. * 'user_name' => array(),
450. * 'last_login' => array(),
451. * )
452. * )
453. * @return void
454. */
455. public function createConstraint($table, $name, $definition)
456. {
457. $table = $this->conn->quoteIdentifier($table, true);
459. if (!empty($name)) {
460. $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name), true);
461. }
462. $query = "ALTER TABLE $table ADD";
463. if (!empty($definition['primary'])) {
464. if (!empty($name)) {
465. $query.= ' CONSTRAINT '.$name;
466. }
467. $query.= ' PRIMARY KEY';
468. } else {
469. $query.= ' CONSTRAINT '. $name;
470. if (!empty($definition['unique'])) {
471. $query.= ' UNIQUE';
472. }
473. }
474. $fields = array();
475. foreach (array_keys($definition['fields']) as $field) {
476. $fields[] = $this->conn->quoteIdentifier($field, true);
477. }
478. $query .= ' ('. implode(', ', $fields) . ')';
479. $result = $this->conn->exec($query);
480. // TODO ? $this->_silentCommit();
481. return $result;
482. }
483. /**
484. * A method to return the required SQL string that fits between CREATE ... TABLE
485. * to create the table as a temporary table.
486. *
487. * @return string The string required to be placed between "CREATE" and "TABLE"
488. * to generate a temporary table, if possible.
489. */
490. public function getTemporaryTableQuery()
491. {
492. return 'GLOBAL TEMPORARY';
493. }
494. /**
495. * create sequence
496. *
497. * @param string $seqName name of the sequence to be created
498. * @param string $start start value of the sequence; default is 1
499. * @param array $options An associative array of table options:
500. * array(
501. * 'comment' => 'Foo',
502. * 'charset' => 'utf8',
503. * 'collate' => 'utf8_unicode_ci',
504. * );
505. * @return boolean
506. */
507. public function createSequence($seqName, $start = 1, array $options = array())
508. {
509. $sequenceName = $this->conn->formatter->getSequenceName($seqName);
511. $this->conn->exec('CREATE GENERATOR ' . $sequenceName);
513. try {
514. $this->conn->exec('SET GENERATOR ' . $sequenceName . ' TO ' . ($start-1));
516. return true;
517. } catch (Doctrine_Connection_Exception $e) {
518. try {
519. $this->dropSequence($seqName);
520. } catch(Doctrine_Connection_Exception $e) {
521. throw new Doctrine_Export_Exception('Could not drop inconsistent sequence table');
522. }
523. }
524. throw new Doctrine_Export_Exception('could not create sequence table');
525. }
526. /**
527. * drop existing sequence
528. *
529. * @param string $seqName name of the sequence to be dropped
530. * @return void
531. */
532. public function dropSequenceSql($seqName)
533. {
534. $sequenceName = $this->conn->formatter->getSequenceName($seqName);
535. $sequenceName = $this->conn->quote($sequenceName);
536. $query = "DELETE FROM RDB\$GENERATORS WHERE UPPER(RDB\$GENERATOR_NAME)=" . $sequenceName;
538. return $query;
539. }
540. }