Coverage for Doctrine_Export_Firebird

Back to coverage report

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