Coverage for Doctrine_Export_Firebird

Back to coverage report

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