Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: Oracle.php 7490 2010-03-29 19:53:27Z jwage $
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.doctrine-project.org>.
20
 */
21
 
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.doctrine-project.org
31
 * @since       1.0
32
 * @version     $Revision: 7490 $
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 boolean      success of operation
42
     */
43
    public function createDatabase($name)
44
    {
45
        if ($this->conn->getAttribute(Doctrine_Core::ATTR_EMULATE_DATABASE)) {
46
            $username   = $name;
47
            $password   = $this->conn->dsn['password'] ? $this->conn->dsn['password'] : $name;
48
 
49
            $tablespace = $this->conn->options['default_tablespace']
50
                        ? ' DEFAULT TABLESPACE '.$this->conn->options['default_tablespace'] : '';
51
 
52
            $query  = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password . $tablespace;
53
            $result = $this->conn->exec($query);
54
 
55
            try {
56
                $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO ' . $username;
57
                $result = $this->conn->exec($query);
58
            } catch (Exception $e) {
59
                $this->dropDatabase($username);
60
            }
61
        }
62
        return true;
63
    }
64
 
65
    /**
66
     * drop an existing database
67
     *
68
     * @param object $this->conn database object that is extended by this class
69
     * @param string $name name of the database that should be dropped
70
     * @return boolean      success of operation
71
     * @access public
72
     */
73
    public function dropDatabase($name)
74
    {
75
        $sql = <<<SQL
76
BEGIN
77
  -- user_tables contains also materialized views
78
  FOR I IN (SELECT table_name FROM user_tables WHERE table_name NOT IN (SELECT mview_name FROM user_mviews))
79
  LOOP
80
    EXECUTE IMMEDIATE 'DROP TABLE "'||I.table_name||'" CASCADE CONSTRAINTS';
81
  END LOOP;
82
 
83
  FOR I IN (SELECT SEQUENCE_NAME FROM USER_SEQUENCES)
84
  LOOP
85
    EXECUTE IMMEDIATE 'DROP SEQUENCE "'||I.SEQUENCE_NAME||'"';
86
  END LOOP;
87
END;
88
 
89
SQL;
90
 
91
        $this->conn->exec($sql);
92
 
93
        if ($this->conn->getAttribute(Doctrine_Core::ATTR_EMULATE_DATABASE)) {
94
            $username = $name;
95
            $this->conn->exec('DROP USER ' . $username . ' CASCADE');
96
        }
97
    }
98
 
99
    /**
100
     * add an autoincrement sequence + trigger
101
     *
102
     * @param string $name  name of the PK field
103
     * @param string $table name of the table
104
     * @param string $start start value for the sequence
105
     * @return string        Sql code
106
     * @access private
107
     */
108
    public function _makeAutoincrement($name, $table, $start = 1)
109
    {
110
        $sql   = array();
111
 
112
        if ( ! $this->conn->getAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER)) {
113
        	$table = strtoupper($table);
114
        }
115
        $indexName  = $table . '_AI_PK';
116
        $definition = array(
117
            'primary' => true,
118
            'fields' => array($name => true),
119
        );
120
 
121
        $sql[] = 'DECLARE
122
  constraints_Count NUMBER;
123
BEGIN
124
  SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \''.$table.'\' AND CONSTRAINT_TYPE = \'P\';
125
  IF constraints_Count = 0 THEN
126
    EXECUTE IMMEDIATE \''.$this->createConstraintSql($table, $indexName, $definition).'\';
127
  END IF;
128
END;';
129
 
130
        if (is_null($start)) {
131
            $query = 'SELECT MAX(' . $this->conn->quoteIdentifier($name, true) . ') FROM ' . $this->conn->quoteIdentifier($table, true);
132
            $start = $this->conn->fetchOne($query);
133
 
134
            ++$start;
135
        }
136
 
137
        $sql[] = $this->createSequenceSql($table, $start);
138
 
139
        $sequenceName = $this->conn->formatter->getSequenceName($table);
140
        $triggerName  = $this->conn->quoteIdentifier($table . '_AI_PK', true);
141
        $table = $this->conn->quoteIdentifier($table, true);
142
        $name  = $this->conn->quoteIdentifier($name, true);
143
        $sql[] = 'CREATE TRIGGER ' . $triggerName . '
144
   BEFORE INSERT
145
   ON ' . $table . '
146
   FOR EACH ROW
147
DECLARE
148
   last_Sequence NUMBER;
149
   last_InsertID NUMBER;
150
BEGIN
151
   IF (:NEW.' . $name . ' IS NULL OR :NEW.'.$name.' = 0) THEN
152
      SELECT ' . $this->conn->quoteIdentifier($sequenceName) . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL;
153
   ELSE
154
      SELECT NVL(Last_Number, 0) INTO last_Sequence
155
        FROM User_Sequences
156
       WHERE UPPER(Sequence_Name) = UPPER(\'' . $sequenceName . '\');
157
      SELECT :NEW.' . $name . ' INTO last_InsertID FROM DUAL;
158
      WHILE (last_InsertID > last_Sequence) LOOP
159
         SELECT ' . $this->conn->quoteIdentifier($sequenceName) . '.NEXTVAL INTO last_Sequence FROM DUAL;
160
      END LOOP;
161
   END IF;
162
END;';
163
        return $sql;
164
    }
165
 
166
    /**
167
     * drop an existing autoincrement sequence + trigger
168
     *
169
     * @param string $table name of the table
170
     * @return void
171
     */
172
    public function dropAutoincrement($table)
173
    {
174
        $table = strtoupper($table);
175
        $triggerName = $table . '_AI_PK';
176
        $trigger_name_quoted = $this->conn->quote($triggerName);
177
        $query = 'SELECT trigger_name FROM user_triggers';
178
        $query.= ' WHERE trigger_name='.$trigger_name_quoted.' OR trigger_name='.strtoupper($trigger_name_quoted);
179
        $trigger = $this->conn->fetchOne($query);
180
 
181
        if ($trigger) {
182
            $trigger_name  = $this->conn->quoteIdentifier($table . '_AI_PK', true);
183
            $trigger_sql = 'DROP TRIGGER ' . $trigger_name;
184
 
185
            // if throws exception, trigger for autoincrement PK could not be dropped
186
            $this->conn->exec($trigger_sql);
187
 
188
            // if throws exception, sequence for autoincrement PK could not be dropped
189
            $this->dropSequence($table);
190
 
191
            $indexName = $table . '_AI_PK';
192
 
193
            // if throws exception, primary key for autoincrement PK could not be dropped
194
            $this->dropConstraint($table, $indexName);
195
        }
196
    }
197
   /**
198
     * A method to return the required SQL string that fits between CREATE ... TABLE
199
     * to create the table as a temporary table.
200
     *
201
     * @return string The string required to be placed between "CREATE" and "TABLE"
202
     *                to generate a temporary table, if possible.
203
     */
204
    public function getTemporaryTableQuery()
205
    {
206
        return 'GLOBAL TEMPORARY';
207
    }
208
 
209
    /**
210
     * getAdvancedForeignKeyOptions
211
     * Return the FOREIGN KEY query section dealing with non-standard options
212
     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
213
     *
214
     * @param array $definition         foreign key definition
215
     * @return string
216
     * @access protected
217
     */
218
    public function getAdvancedForeignKeyOptions(array $definition)
219
    {
220
        $query = '';
221
        if (isset($definition['onDelete']) && strtoupper(trim($definition['onDelete'])) != 'NO ACTION') {
222
            $query .= ' ON DELETE ' . $definition['onDelete'];
223
        }
224
        if (isset($definition['deferrable'])) {
225
            $query .= ' DEFERRABLE';
226
        } else {
227
            $query .= ' NOT DEFERRABLE';
228
        }
229
        if (isset($definition['feferred'])) {
230
            $query .= ' INITIALLY DEFERRED';
231
        } else {
232
            $query .= ' INITIALLY IMMEDIATE';
233
        }
234
        return $query;
235
    }
236
 
237
    /**
238
     * create a new table
239
     *
240
     * @param string $name     Name of the database that should be created
241
     * @param array $fields Associative array that contains the definition of each field of the new table
242
     *                        The indexes of the array entries are the names of the fields of the table an
243
     *                        the array entry values are associative arrays like those that are meant to be
244
     *                         passed with the field definitions to get[Type]Declaration() functions.
245
     *
246
     *                        Example
247
     *                        array(
248
     *
249
     *                            'id' => array(
250
     *                                'type' => 'integer',
251
     *                                'unsigned' => 1
252
     *                                'notnull' => 1
253
     *                                'default' => 0
254
     *                            ),
255
     *                            'name' => array(
256
     *                                'type' => 'text',
257
     *                                'length' => 12
258
     *                            ),
259
     *                            'password' => array(
260
     *                                'type' => 'text',
261
     *                                'length' => 12
262
     *                            )
263
     *                        );
264
     * @param array $options  An associative array of table options:
265
     *
266
     * @return void
267
     */
268
    public function createTable($name, array $fields, array $options = array())
269
    {
270
        $this->conn->beginTransaction();
271
 
272
        foreach ($this->createTableSql($name, $fields, $options) as $sql) {
273
            $this->conn->exec($sql);
274
        }
275
 
276
        $this->conn->commit();
277
    }
278
 
279
    /**
280
     * create a new table
281
     *
282
     * @param string $name     Name of the database that should be created
283
     * @param array $fields Associative array that contains the definition of each field of the new table
284
     *                        The indexes of the array entries are the names of the fields of the table an
285
     *                        the array entry values are associative arrays like those that are meant to be
286
     *                         passed with the field definitions to get[Type]Declaration() functions.
287
     *
288
     *                        Example
289
     *                        array(
290
     *
291
     *                            'id' => array(
292
     *                                'type' => 'integer',
293
     *                                'unsigned' => 1
294
     *                                'notnull' => 1
295
     *                                'default' => 0
296
     *                            ),
297
     *                            'name' => array(
298
     *                                'type' => 'text',
299
     *                                'length' => 12
300
     *                            ),
301
     *                            'password' => array(
302
     *                                'type' => 'text',
303
     *                                'length' => 12
304
     *                            )
305
     *                        );
306
     * @param array $options  An associative array of table options:
307
     *
308
     * @return void
309
     */
310
    public function createTableSql($name, array $fields, array $options = array())
311
    {
312
        $sql = parent::createTableSql($name, $fields, $options);
313
 
314
        if (isset($options['comment']) && ! empty($options['comment'])) {
315
     	    $sql[] = $this->_createTableCommentSql($name, $options['comment']);
316
     	}
317
 
318
        foreach ($fields as $fieldName => $field) {
319
            if (isset($field['sequence'])) {
320
              $sql[] = $this->createSequenceSql($field['sequence'], 1);
321
            }
322
 
323
            if (isset($field['autoincrement']) && $field['autoincrement'] ||
324
               (isset($field['autoinc']) && $fields['autoinc'])) {
325
                $sql = array_merge($sql, $this->_makeAutoincrement($fieldName, $name));
326
            }
327
 
328
            if (isset($field['comment']) && ! empty($field['comment'])){
329
                $sql[] = $this->_createColumnCommentSql($name,$fieldName,$field['comment']);
330
            }
331
        }
332
 
333
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
334
            foreach ($options['indexes'] as $indexName => $definition) {
335
                // create nonunique indexes, as they are a part od CREATE TABLE DDL
336
                if ( ! isset($definition['type']) ||
337
                    (isset($definition['type']) && strtolower($definition['type']) != 'unique')) {
338
                    $sql[] = $this->createIndexSql($name, $indexName, $definition);
339
                }
340
            }
341
        }
342
 
343
        return $sql;
344
    }
345
 
346
    /**
347
     * create a comment on a table
348
     *
349
     * @param string $table    Name of the table we are commenting
350
     * @param string $comment  The comment for the table
351
     *
352
     * @return string
353
     */
354
    public function _createTableCommentSql($table,$comment)
355
    {
356
        return 'COMMENT ON TABLE '. $this->conn->quoteIdentifier($table, true). ' IS '.$this->conn->quote($comment, 'text').'';
357
    }
358
 
359
    /**
360
     * create a comment on a column
361
     *
362
     * @param string $table    Name of the table
363
     * @param string $column   Name of the column we are commenting
364
     * @param string $comment  The comment for the table
365
     *
366
     * @return string
367
     */
368
    public function _createColumnCommentSql($table,$column, $comment)
369
    {
370
        return 'COMMENT ON COLUMN '. $this->conn->quoteIdentifier($table, true). '.'. $this->conn->quoteIdentifier($column, true). ' IS '.$this->conn->quote($comment, 'text').'';
371
    }
372
 
373
    /**
374
     * drop an existing table
375
     *
376
     * @param string $name name of the table that should be dropped
377
     * @return void
378
     */
379
    public function dropTable($name)
380
    {
381
        //$this->conn->beginNestedTransaction();
382
        $result = $this->dropAutoincrement($name);
383
        $result = parent::dropTable($name);
384
        //$this->conn->completeNestedTransaction();
385
        return $result;
386
    }
387
 
388
    /**
389
     * alter an existing table
390
     *
391
     * @param string $name         name of the table that is intended to be changed.
392
     * @param array $changes     associative array that contains the details of each type
393
     *                             of change that is intended to be performed. The types of
394
     *                             changes that are currently supported are defined as follows:
395
     *
396
     *                             name
397
     *
398
     *                                New name for the table.
399
     *
400
     *                            add
401
     *
402
     *                                Associative array with the names of fields to be added as
403
     *                                 indexes of the array. The value of each entry of the array
404
     *                                 should be set to another associative array with the properties
405
     *                                 of the fields to be added. The properties of the fields should
406
     *                                 be the same as defined by the MDB2 parser.
407
     *
408
     *
409
     *                            remove
410
     *
411
     *                                Associative array with the names of fields to be removed as indexes
412
     *                                 of the array. Currently the values assigned to each entry are ignored.
413
     *                                 An empty array should be used for future compatibility.
414
     *
415
     *                            rename
416
     *
417
     *                                Associative array with the names of fields to be renamed as indexes
418
     *                                 of the array. The value of each entry of the array should be set to
419
     *                                 another associative array with the entry named name with the new
420
     *                                 field name and the entry named Declaration that is expected to contain
421
     *                                 the portion of the field declaration already in DBMS specific SQL code
422
     *                                 as it is used in the CREATE TABLE statement.
423
     *
424
     *                            change
425
     *
426
     *                                Associative array with the names of the fields to be changed as indexes
427
     *                                 of the array. Keep in mind that if it is intended to change either the
428
     *                                 name of a field and any other properties, the change array entries
429
     *                                 should have the new names of the fields as array indexes.
430
     *
431
     *                                The value of each entry of the array should be set to another associative
432
     *                                 array with the properties of the fields to that are meant to be changed as
433
     *                                 array entries. These entries should be assigned to the new values of the
434
     *                                 respective properties. The properties of the fields should be the same
435
     *                                 as defined by the MDB2 parser.
436
     *
437
     *                            Example
438
     *                                array(
439
     *                                    'name' => 'userlist',
440
     *                                    'add' => array(
441
     *                                        'quota' => array(
442
     *                                            'type' => 'integer',
443
     *                                            'unsigned' => 1
444
     *                                        )
445
     *                                    ),
446
     *                                    'remove' => array(
447
     *                                        'file_limit' => array(),
448
     *                                        'time_limit' => array()
449
     *                                    ),
450
     *                                    'change' => array(
451
     *                                        'name' => array(
452
     *                                            'length' => '20',
453
     *                                            'definition' => array(
454
     *                                                'type' => 'text',
455
     *                                                'length' => 20,
456
     *                                            ),
457
     *                                        )
458
     *                                    ),
459
     *                                    'rename' => array(
460
     *                                        'sex' => array(
461
     *                                            'name' => 'gender',
462
     *                                            'definition' => array(
463
     *                                                'type' => 'text',
464
     *                                                'length' => 1,
465
     *                                                'default' => 'M',
466
     *                                            ),
467
     *                                        )
468
     *                                    )
469
     *                                )
470
     *
471
     * @param boolean $check     indicates whether the function should just check if the DBMS driver
472
     *                             can perform the requested table alterations if the value is true or
473
     *                             actually perform them otherwise.
474
     * @return void
475
     */
476
    public function alterTable($name, array $changes, $check = false)
477
    {
478
 
479
        foreach ($changes as $changeName => $change) {
480
            switch ($changeName) {
481
                case 'add':
482
                case 'remove':
483
                case 'change':
484
                case 'name':
485
                case 'rename':
486
                    break;
487
                default:
488
                    throw new Doctrine_Export_Exception('change type "' . $changeName . '" not yet supported');
489
            }
490
        }
491
 
492
        if ($check) {
493
            return false;
494
        }
495
 
496
        $name = $this->conn->quoteIdentifier($name, true);
497
 
498
        if ( ! empty($changes['add']) && is_array($changes['add'])) {
499
            $fields = array();
500
            foreach ($changes['add'] as $fieldName => $field) {
501
                $fields[] = $this->getDeclaration($fieldName, $field);
502
            }
503
            $result = $this->conn->exec('ALTER TABLE ' . $name . ' ADD (' . implode(', ', $fields) . ')');
504
        }
505
 
506
        if ( ! empty($changes['change']) && is_array($changes['change'])) {
507
            $fields = array();
508
            foreach ($changes['change'] as $fieldName => $field) {
509
                $fields[] = $fieldName. ' ' . $this->getDeclaration('', $field['definition']);
510
            }
511
            $result = $this->conn->exec('ALTER TABLE ' . $name . ' MODIFY (' . implode(', ', $fields) . ')');
512
        }
513
 
514
        if ( ! empty($changes['rename']) && is_array($changes['rename'])) {
515
            foreach ($changes['rename'] as $fieldName => $field) {
516
                $query = 'ALTER TABLE ' . $name . ' RENAME COLUMN ' . $this->conn->quoteIdentifier($fieldName, true)
517
                       . ' TO ' . $this->conn->quoteIdentifier($field['name']);
518
 
519
                $result = $this->conn->exec($query);
520
            }
521
        }
522
 
523
        if ( ! empty($changes['remove']) && is_array($changes['remove'])) {
524
            $fields = array();
525
            foreach ($changes['remove'] as $fieldName => $field) {
526
                $fields[] = $this->conn->quoteIdentifier($fieldName, true);
527
            }
528
            $result = $this->conn->exec('ALTER TABLE ' . $name . ' DROP COLUMN ' . implode(', ', $fields));
529
        }
530
 
531
        if ( ! empty($changes['name'])) {
532
            $changeName = $this->conn->quoteIdentifier($changes['name'], true);
533
            $result = $this->conn->exec('ALTER TABLE ' . $name . ' RENAME TO ' . $changeName);
534
        }
535
    }
536
 
537
    /**
538
     * create sequence
539
     *
540
     * @param string $seqName name of the sequence to be created
541
     * @param string $start start value of the sequence; default is 1
542
     * @param array     $options  An associative array of table options:
543
     *                          array(
544
     *                              'comment' => 'Foo',
545
     *                              'charset' => 'utf8',
546
     *                              'collate' => 'utf8_unicode_ci',
547
     *                          );
548
     * @return string
549
     */
550
    public function createSequenceSql($seqName, $start = 1, array $options = array())
551
    {
552
        $sequenceName = $this->conn->quoteIdentifier($this->conn->formatter->getSequenceName($seqName), true);
553
        $query  = 'CREATE SEQUENCE ' . $sequenceName . ' START WITH ' . $start . ' INCREMENT BY 1 NOCACHE';
554
        $query .= ($start < 1 ? ' MINVALUE ' . $start : '');
555
        return $query;
556
    }
557
 
558
    /**
559
     * drop existing sequence
560
     *
561
     * @param object $this->conn database object that is extended by this class
562
     * @param string $seqName name of the sequence to be dropped
563
     * @return string
564
     */
565
    public function dropSequenceSql($seqName)
566
    {
567
        $sequenceName = $this->conn->quoteIdentifier($this->conn->formatter->getSequenceName($seqName), true);
568
        return 'DROP SEQUENCE ' . $sequenceName;
569
    }
570
 
571
    /**
572
     * return Oracle's SQL code portion needed to set an index
573
     * declaration to be unsed in statements like CREATE TABLE.
574
     *
575
     * @param string $name      name of the index
576
     * @param array $definition index definition
577
     * @return string           Oracle's SQL code portion needed to set an index
578
     */
579
    public function getIndexDeclaration($name, array $definition)
580
    {
581
        $name = $this->conn->quoteIdentifier($name);
582
        $type = '';
583
 
584
        if ( isset($definition['type']))
585
        {
586
            if (strtolower($definition['type']) == 'unique') {
587
                $type = strtoupper($definition['type']);
588
            } else {
589
                throw new Doctrine_Export_Exception(
590
                    'Unknown type '.$definition['type'] .' for index '.$name
591
                );
592
            }
593
        } else {
594
            // only unique indexes should be defined in create table statement
595
            return null;
596
        }
597
 
598
        if ( !isset($definition['fields']) || !is_array($definition['fields'])) {
599
            throw new Doctrine_Export_Exception('No columns given for index '.$name);
600
        }
601
 
602
        $query = 'CONSTRAINT '.$name.' '.$type.' ('.$this->getIndexFieldDeclarationList($definition['fields']).')';
603
 
604
        return $query;
605
    }
606
}