Coverage for Doctrine_Export_Firebird

Back to coverage report

1 <?php
2 /*
3  *  $Id: Firebird.php 2702 2007-10-03 21:43:22Z 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: 2702 $
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');
75
76             ++$start;
77             $result = $this->createSequence($table, $start);
78             $this->conn->commit();
79         } else {
80             $result = $this->createSequence($table, $start);
81         }
82
83         $sequence_name = $this->conn->formatter->getSequenceName($table);
84         $trigger_name  = $this->conn->quoteIdentifier($table . '_AUTOINCREMENT_PK', true);
85
86         $table = $this->conn->quoteIdentifier($table, true);
87         $name  = $this->conn->quoteIdentifier($name,  true);
88
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);
97
98         // TODO ? $this->_silentCommit();
99
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     {
110
111         $result = $this->dropSequence($table);
112
113         //remove autoincrement trigger associated with the table
114         $table = $this->conn->quote(strtoupper($table));
115         $triggerName = $this->conn->quote(strtoupper($table) . '_AUTOINCREMENT_PK');
116
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);
152
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);
164
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);
210
211         //$this->_silentCommit();
212
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 = false)
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         }
332
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         }
342
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         }
352
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         }
365
366         if ( ! strlen($query)) {
367             return false;
368         }
369
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.
394
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';
409
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) . ')';
431
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);
458
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);
510
511         $this->conn->exec('CREATE GENERATOR ' . $sequenceName);
512
513         try {
514             $this->conn->exec('SET GENERATOR ' . $sequenceName . ' TO ' . ($start-1));
515             
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;
537         
538         return $query;
539     }
540 }