Coverage for Doctrine_Export_Oracle

Back to coverage report

1 <?php
2 /*
3  *  $Id: Oracle.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.org>.
20  */
21 Doctrine::autoload('Doctrine_Export');
22 /**
23  * Doctrine_Export_Oracle
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  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30  * @link        www.phpdoctrine.org
31  * @since       1.0
32  * @version     $Revision: 2963 $
33  */
34 class Doctrine_Export_Oracle extends Doctrine_Export
35 {
36     /**
37      * create a new database
38      *
39      * @param object $db database object that is extended by this class
40      * @param string $name name of the database that should be created
41      * @return mixed MDB2_OK on success, a MDB2 error on failure
42      * @access public
43      */
44     public function createDatabase($name)
45     {
46         if ( ! $this->conn->getAttribute(Doctrine::ATTR_EMULATE_DATABASE))
47             throw new Doctrine_Export_Exception('database creation is only supported if the "emulate_database" attribute is enabled');
48
49         $username   = sprintf($this->conn->getAttribute(Doctrine::ATTR_DB_NAME_FORMAT), $name);
50         $password   = $this->conn->dsn['password'] ? $this->conn->dsn['password'] : $name;
51
52         $tablespace = $this->conn->getAttribute(Doctrine::ATTR_DB_NAME_FORMAT)
53                     ? ' DEFAULT TABLESPACE '.$this->conn->options['default_tablespace'] : '';
54
55         $query  = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password . $tablespace;
56         $result = $this->conn->exec($query);
57
58         try {
59             $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO ' . $username;
60             $result = $this->conn->exec($query);
61         } catch (Exception $e) {
62             $query = 'DROP USER '.$username.' CASCADE';
63             $result2 = $this->conn->exec($query);
64         }
65         return true;
66     }
67
68     /**
69      * drop an existing database
70      *
71      * @param object $this->conn database object that is extended by this class
72      * @param string $name name of the database that should be dropped
73      * @return mixed MDB2_OK on success, a MDB2 error on failure
74      * @access public
75      */
76     public function dropDatabase($name)
77     {
78         if ( ! $this->conn->getAttribute(Doctrine::ATTR_EMULATE_DATABASE))
79             throw new Doctrine_Export_Exception('database dropping is only supported if the
80                                                        "emulate_database" option is enabled');
81
82         $username = sprintf($this->conn->getAttribute(Doctrine::ATTR_DB_NAME_FORMAT), $name);
83
84         return $this->conn->exec('DROP USER ' . $username . ' CASCADE');
85     }
86
87     /**
88      * add an autoincrement sequence + trigger
89      *
90      * @param string $name  name of the PK field
91      * @param string $table name of the table
92      * @param string $start start value for the sequence
93      * @return mixed        MDB2_OK on success, a MDB2 error on failure
94      * @access private
95      */
96     public function _makeAutoincrement($name, $table, $start = 1)
97     {
98         $sql   = array();
99         $table = strtoupper($table);
100         $indexName  = $table . '_AI_PK';
101         $definition = array(
102             'primary' => true,
103             'fields' => array($name => true),
104         );
105
106         $sql[] = $this->createConstraintSql($table, $indexName, $definition);
107
108         if (is_null($start)) {
109             $query = 'SELECT MAX(' . $this->conn->quoteIdentifier($name, true) . ') FROM ' . $this->conn->quoteIdentifier($table, true);
110             $start = $this->conn->fetchOne($query);
111
112             ++$start;
113         }
114
115         $sql[] = $this->createSequenceSql($table, $start);
116
117         $sequenceName = $this->conn->formatter->getSequenceName($table);
118         $triggerName  = $this->conn->quoteIdentifier($table . '_AI_PK', true);
119         $table = $this->conn->quoteIdentifier($table, true);
120         $name  = $this->conn->quoteIdentifier($name, true);
121         $sql[] = 'CREATE TRIGGER ' . $triggerName . '
122    BEFORE INSERT
123    ON '.$table.'
124    FOR EACH ROW
125 DECLARE
126    last_Sequence NUMBER;
127    last_InsertID NUMBER;
128 BEGIN
129    SELECT '.$sequenceName.'.NEXTVAL INTO :NEW.'.$name.' FROM DUAL;
130    IF (:NEW.'.$name.' IS NULL OR :NEW.'.$name.' = 0) THEN
131       SELECT '.$sequenceName.'.NEXTVAL INTO :NEW.'.$name.' FROM DUAL;
132    ELSE
133       SELECT NVL(Last_Number, 0) INTO last_Sequence
134         FROM User_Sequences
135        WHERE UPPER(Sequence_Name) = UPPER(\''.$sequenceName.'\');
136       SELECT :NEW.id INTO last_InsertID FROM DUAL;
137       WHILE (last_InsertID > last_Sequence) LOOP
138          SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
139       END LOOP;
140    END IF;
141 END;
142 ';
143         return $sql;
144     }
145
146     /**
147      * drop an existing autoincrement sequence + trigger
148      *
149      * @param string $table name of the table
150      * @return void
151      */
152     public function dropAutoincrement($table)
153     {
154         $table = strtoupper($table);
155         $triggerName = $table . '_AI_PK';
156         $trigger_name_quoted = $this->conn->quote($triggerName);
157         $query = 'SELECT trigger_name FROM user_triggers';
158         $query.= ' WHERE trigger_name='.$trigger_name_quoted.' OR trigger_name='.strtoupper($trigger_name_quoted);
159         $trigger = $this->conn->fetchOne($query);
160
161         if ($trigger) {
162             $trigger_name  = $this->conn->quoteIdentifier($table . '_AI_PK', true);
163             $trigger_sql = 'DROP TRIGGER ' . $trigger_name;
164
165             // if throws exception, trigger for autoincrement PK could not be dropped
166             $this->conn->exec($trigger_sql);
167
168             // if throws exception, sequence for autoincrement PK could not be dropped
169             $this->dropSequence($table);
170
171             $indexName = $table . '_AI_PK';
172
173             // if throws exception, primary key for autoincrement PK could not be dropped
174             $this->dropConstraint($table, $indexName);
175         }
176     }
177    /**
178      * A method to return the required SQL string that fits between CREATE ... TABLE
179      * to create the table as a temporary table.
180      *
181      * @return string The string required to be placed between "CREATE" and "TABLE"
182      *                to generate a temporary table, if possible.
183      */
184     public function getTemporaryTableQuery()
185     {
186         return 'GLOBAL TEMPORARY';
187     }
188
189     /**
190      * getAdvancedForeignKeyOptions
191      * Return the FOREIGN KEY query section dealing with non-standard options
192      * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
193      *
194      * @param array $definition         foreign key definition
195      * @return string
196      * @access protected
197      */
198     public function getAdvancedForeignKeyOptions(array $definition)
199     {
200         $query = '';
201         if (isset($definition['onDelete'])) {
202             $query .= ' ON DELETE ' . $definition['onDelete'];
203         }
204         if (isset($definition['deferrable'])) {
205             $query .= ' DEFERRABLE';
206         } else {
207             $query .= ' NOT DEFERRABLE';
208         }
209         if (isset($definition['feferred'])) {
210             $query .= ' INITIALLY DEFERRED';
211         } else {
212             $query .= ' INITIALLY IMMEDIATE';
213         }
214         return $query;
215     }
216
217     /**
218      * create a new table
219      *
220      * @param string $name     Name of the database that should be created
221      * @param array $fields Associative array that contains the definition of each field of the new table
222      *                        The indexes of the array entries are the names of the fields of the table an
223      *                        the array entry values are associative arrays like those that are meant to be
224      *                         passed with the field definitions to get[Type]Declaration() functions.
225      *
226      *                        Example
227      *                        array(
228      *
229      *                            'id' => array(
230      *                                'type' => 'integer',
231      *                                'unsigned' => 1
232      *                                'notnull' => 1
233      *                                'default' => 0
234      *                            ),
235      *                            'name' => array(
236      *                                'type' => 'text',
237      *                                'length' => 12
238      *                            ),
239      *                            'password' => array(
240      *                                'type' => 'text',
241      *                                'length' => 12
242      *                            )
243      *                        );
244      * @param array $options  An associative array of table options:
245      *
246      * @return void
247      */
248     public function createTable($name, array $fields, array $options = array())
249     {
250         $this->conn->beginTransaction();
251
252         foreach ($this->createTableSql($name, $fields, $options) as $sql) {
253             $this->conn->exec($sql);
254         }
255
256         $this->conn->commit();
257     }
258
259     /**
260      * create a new table
261      *
262      * @param string $name     Name of the database that should be created
263      * @param array $fields Associative array that contains the definition of each field of the new table
264      *                        The indexes of the array entries are the names of the fields of the table an
265      *                        the array entry values are associative arrays like those that are meant to be
266      *                         passed with the field definitions to get[Type]Declaration() functions.
267      *
268      *                        Example
269      *                        array(
270      *
271      *                            'id' => array(
272      *                                'type' => 'integer',
273      *                                'unsigned' => 1
274      *                                'notnull' => 1
275      *                                'default' => 0
276      *                            ),
277      *                            'name' => array(
278      *                                'type' => 'text',
279      *                                'length' => 12
280      *                            ),
281      *                            'password' => array(
282      *                                'type' => 'text',
283      *                                'length' => 12
284      *                            )
285      *                        );
286      * @param array $options  An associative array of table options:
287      *
288      * @return void
289      */
290     public function createTableSql($name, array $fields, array $options = array())
291     {
292         $sql = parent::createTableSql($name, $fields, $options);
293
294         foreach ($fields as $fieldName => $field) {
295             if (isset($field['autoincrement']) && $field['autoincrement'] ||
296                (isset($field['autoinc']) && $fields['autoinc'])) {           
297                 $sql = array_merge($sql, $this->_makeAutoincrement($fieldName, $name));
298             }
299         }
300
301         return $sql;
302     }
303
304     /**
305      * drop an existing table
306      *
307      * @param string $name name of the table that should be dropped
308      * @return void
309      */
310     public function dropTable($name)
311     {
312         //$this->conn->beginNestedTransaction();
313         $result = $this->dropAutoincrement($name);
314         $result = parent::dropTable($name);
315         //$this->conn->completeNestedTransaction();
316         return $result;
317     }
318
319     /**
320      * alter an existing table
321      *
322      * @param string $name         name of the table that is intended to be changed.
323      * @param array $changes     associative array that contains the details of each type
324      *                             of change that is intended to be performed. The types of
325      *                             changes that are currently supported are defined as follows:
326      *
327      *                             name
328      *
329      *                                New name for the table.
330      *
331      *                            add
332      *
333      *                                Associative array with the names of fields to be added as
334      *                                 indexes of the array. The value of each entry of the array
335      *                                 should be set to another associative array with the properties
336      *                                 of the fields to be added. The properties of the fields should
337      *                                 be the same as defined by the MDB2 parser.
338      *
339      *
340      *                            remove
341      *
342      *                                Associative array with the names of fields to be removed as indexes
343      *                                 of the array. Currently the values assigned to each entry are ignored.
344      *                                 An empty array should be used for future compatibility.
345      *
346      *                            rename
347      *
348      *                                Associative array with the names of fields to be renamed as indexes
349      *                                 of the array. The value of each entry of the array should be set to
350      *                                 another associative array with the entry named name with the new
351      *                                 field name and the entry named Declaration that is expected to contain
352      *                                 the portion of the field declaration already in DBMS specific SQL code
353      *                                 as it is used in the CREATE TABLE statement.
354      *
355      *                            change
356      *
357      *                                Associative array with the names of the fields to be changed as indexes
358      *                                 of the array. Keep in mind that if it is intended to change either the
359      *                                 name of a field and any other properties, the change array entries
360      *                                 should have the new names of the fields as array indexes.
361      *
362      *                                The value of each entry of the array should be set to another associative
363      *                                 array with the properties of the fields to that are meant to be changed as
364      *                                 array entries. These entries should be assigned to the new values of the
365      *                                 respective properties. The properties of the fields should be the same
366      *                                 as defined by the MDB2 parser.
367      *
368      *                            Example
369      *                                array(
370      *                                    'name' => 'userlist',
371      *                                    'add' => array(
372      *                                        'quota' => array(
373      *                                            'type' => 'integer',
374      *                                            'unsigned' => 1
375      *                                        )
376      *                                    ),
377      *                                    'remove' => array(
378      *                                        'file_limit' => array(),
379      *                                        'time_limit' => array()
380      *                                    ),
381      *                                    'change' => array(
382      *                                        'name' => array(
383      *                                            'length' => '20',
384      *                                            'definition' => array(
385      *                                                'type' => 'text',
386      *                                                'length' => 20,
387      *                                            ),
388      *                                        )
389      *                                    ),
390      *                                    'rename' => array(
391      *                                        'sex' => array(
392      *                                            'name' => 'gender',
393      *                                            'definition' => array(
394      *                                                'type' => 'text',
395      *                                                'length' => 1,
396      *                                                'default' => 'M',
397      *                                            ),
398      *                                        )
399      *                                    )
400      *                                )
401      *
402      * @param boolean $check     indicates whether the function should just check if the DBMS driver
403      *                             can perform the requested table alterations if the value is true or
404      *                             actually perform them otherwise.
405      * @return void
406      */
407     public function alterTable($name, array $changes, $check = false)
408     {
409
410         foreach ($changes as $changeName => $change) {
411             switch ($changeName) {
412                 case 'add':
413                 case 'remove':
414                 case 'change':
415                 case 'name':
416                 case 'rename':
417                     break;
418                 default:
419                     throw new Doctrine_Export_Exception('change type "' . $changeName . '" not yet supported');
420             }
421         }
422
423         if ($check) {
424             return false;
425         }
426
427         $name = $this->conn->quoteIdentifier($name, true);
428
429         if ( ! empty($changes['add']) && is_array($changes['add'])) {
430             $fields = array();
431             foreach ($changes['add'] as $fieldName => $field) {
432                 $fields[] = $this->conn->getDeclaration($fieldName, $field);
433             }
434             $result = $this->conn->exec('ALTER TABLE ' . $name . ' ADD (' . implode(', ', $fields) . ')');
435         }
436
437         if ( ! empty($changes['change']) && is_array($changes['change'])) {
438             $fields = array();
439             foreach ($changes['change'] as $fieldName => $field) {
440                 $fields[] = $fieldName. ' ' . $this->conn->getDeclaration('', $field['definition']);
441             }
442             $result = $this->conn->exec('ALTER TABLE ' . $name . ' MODIFY (' . implode(', ', $fields) . ')');
443         }
444
445         if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
446             foreach ($changes['rename'] as $fieldName => $field) {
447                 $query = 'ALTER TABLE ' . $name . ' RENAME COLUMN ' . $this->conn->quoteIdentifier($fieldName, true)
448                        . ' TO ' . $this->conn->quoteIdentifier($field['name']);
449
450                 $result = $this->conn->exec($query);
451             }
452         }
453
454         if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
455             $fields = array();
456             foreach ($changes['remove'] as $fieldName => $field) {
457                 $fields[] = $this->conn->quoteIdentifier($fieldName, true);
458             }
459             $result = $this->conn->exec('ALTER TABLE ' . $name . ' DROP COLUMN ' . implode(', ', $fields));
460         }
461
462         if ( ! empty($changes['name'])) {
463             $changeName = $this->conn->quoteIdentifier($changes['name'], true);
464             $result = $this->conn->exec('ALTER TABLE ' . $name . ' RENAME TO ' . $changeName);
465         }
466     }
467
468     /**
469      * create sequence
470      *
471      * @param string $seqName name of the sequence to be created
472      * @param string $start start value of the sequence; default is 1
473      * @param array     $options  An associative array of table options:
474      *                          array(
475      *                              'comment' => 'Foo',
476      *                              'charset' => 'utf8',
477      *                              'collate' => 'utf8_unicode_ci',
478      *                          );
479      * @return string
480      */
481     public function createSequenceSql($seqName, $start = 1, array $options = array())
482     {
483         $sequenceName = $this->conn->quoteIdentifier($this->conn->formatter->getSequenceName($seqName), true);
484         $query  = 'CREATE SEQUENCE ' . $sequenceName . ' START WITH ' . $start . ' INCREMENT BY 1 NOCACHE';
485         $query .= ($start < 1 ? ' MINVALUE ' . $start : '');
486         return $query;
487     }
488
489     /**
490      * drop existing sequence
491      *
492      * @param object $this->conn database object that is extended by this class
493      * @param string $seqName name of the sequence to be dropped
494      * @return string
495      */
496     public function dropSequenceSql($seqName)
497     {
498         $sequenceName = $this->conn->quoteIdentifier($this->conn->formatter->getSequenceName($seqName), true);
499         return 'DROP SEQUENCE ' . $sequenceName;
500     }
501 }