Export.php 46.3 KB
Newer Older
zYne's avatar
zYne committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
<?php
/*
 *  $Id$
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.phpdoctrine.com>.
 */
zYne's avatar
zYne committed
21
Doctrine::autoload('Doctrine_Connection_Module');
zYne's avatar
zYne committed
22 23 24
/**
 * Doctrine_Export
 *
25 26
 * @package     Doctrine
 * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
zYne's avatar
zYne committed
27
 * @author      Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
28 29 30 31 32 33
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @category    Object Relational Mapping
 * @link        www.phpdoctrine.com
 * @since       1.0
 * @version     $Revision$
 */
lsmith's avatar
lsmith committed
34 35
class Doctrine_Export extends Doctrine_Connection_Module
{
36 37
    /**
     * drop an existing database
38
     * (this method is implemented by the drivers)
39 40 41 42
     *
     * @param string $name name of the database that should be dropped
     * @return void
     */
lsmith's avatar
lsmith committed
43
    public function dropDatabase($database)
zYne's avatar
zYne committed
44 45 46 47 48 49 50 51 52 53 54
    {
        $this->conn->execute($this->dropDatabaseSql($database));
    }
    /**
     * drop an existing database
     * (this method is implemented by the drivers)
     *
     * @param string $name name of the database that should be dropped
     * @return void
     */
    public function dropDatabaseSql($database)
lsmith's avatar
lsmith committed
55
    {
56 57
        throw new Doctrine_Export_Exception('Drop database not supported by this driver.');
    }
zYne's avatar
zYne committed
58 59 60 61 62 63 64 65 66 67 68
    /**
     * dropTableSql
     * drop an existing table
     *
     * @param string $table           name of table that should be dropped from the database
     * @return string
     */
    public function dropTableSql($table)
    {
        return 'DROP TABLE ' . $this->conn->quoteIdentifier($table);
    }
69 70
    /**
     * dropTable
zYne's avatar
zYne committed
71
     * drop an existing table
72
     *
zYne's avatar
zYne committed
73
     * @param string $table           name of table that should be dropped from the database
74 75
     * @return void
     */
lsmith's avatar
lsmith committed
76 77
    public function dropTable($table)
    {
zYne's avatar
zYne committed
78
        $this->conn->execute($this->dropTableSql($table));
79
    }
80 81 82 83

    /**
     * drop existing index
     *
zYne's avatar
zYne committed
84
     * @param string    $table        name of table that should be used in method
85 86 87
     * @param string    $name         name of the index to be dropped
     * @return void
     */
lsmith's avatar
lsmith committed
88
    public function dropIndex($table, $name)
zYne's avatar
zYne committed
89 90 91 92 93 94 95 96 97 98 99 100
    {
        return $this->conn->exec($this->dropIndexSql($table, $name));
    }
    
    /**
     * dropIndexSql
     *
     * @param string    $table        name of table that should be used in method
     * @param string    $name         name of the index to be dropped
     * @return string                 SQL that is used for dropping an index
     */
    public function dropIndexSql($table, $name) 
lsmith's avatar
lsmith committed
101
    {
zYne's avatar
zYne committed
102
        $name = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
zYne's avatar
zYne committed
103
        return 'DROP INDEX ' . $name;
104 105 106 107 108 109 110 111 112
    }
    /**
     * drop existing constraint
     *
     * @param string    $table        name of table that should be used in method
     * @param string    $name         name of the constraint to be dropped
     * @param string    $primary      hint if the constraint is primary
     * @return void
     */
lsmith's avatar
lsmith committed
113 114
    public function dropConstraint($table, $name, $primary = false)
    {
zYne's avatar
zYne committed
115 116
        $table = $this->conn->quoteIdentifier($table);
        $name  = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
zYne's avatar
zYne committed
117
        return $this->conn->exec('ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $name);
118 119
    }
    /**
zYne's avatar
zYne committed
120
     * dropSequenceSql
121
     * drop existing sequence
122
     * (this method is implemented by the drivers)
123
     *
zYne's avatar
zYne committed
124 125
     * @throws Doctrine_Connection_Exception     if something fails at database level
     * @param string $sequenceName      name of the sequence to be dropped
126 127
     * @return void
     */
zYne's avatar
zYne committed
128 129 130 131 132 133 134 135 136 137 138 139 140
    public function dropSequence($sequenceName)
    {
        $this->conn->exec($this->dropSequenceSql($sequenceName));
    }
    /**
     * dropSequenceSql
     * drop existing sequence
     *
     * @throws Doctrine_Connection_Exception     if something fails at database level
     * @param string $sequenceName name of the sequence to be dropped
     * @return void
     */
    public function dropSequenceSql($sequenceName)
lsmith's avatar
lsmith committed
141
    {
142 143 144 145
        throw new Doctrine_Export_Exception('Drop sequence not supported by this driver.');
    }
    /**
     * create a new database
146
     * (this method is implemented by the drivers)
147 148 149 150
     *
     * @param string $name name of the database that should be created
     * @return void
     */
lsmith's avatar
lsmith committed
151
    public function createDatabase($database)
zYne's avatar
zYne committed
152 153 154 155 156 157 158 159 160 161 162
    {
        $this->conn->execute($this->createDatabaseSql($database));
    }
    /**
     * create a new database
     * (this method is implemented by the drivers)
     *
     * @param string $name name of the database that should be created
     * @return string
     */
    public function createDatabaseSql($database)
lsmith's avatar
lsmith committed
163
    {
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
        throw new Doctrine_Export_Exception('Create database not supported by this driver.');
    }
    /**
     * create a new table
     *
     * @param string $name   Name of the database that should be created
     * @param array $fields  Associative array that contains the definition of each field of the new table
     *                       The indexes of the array entries are the names of the fields of the table an
     *                       the array entry values are associative arrays like those that are meant to be
     *                       passed with the field definitions to get[Type]Declaration() functions.
     *                          array(
     *                              'id' => array(
     *                                  'type' => 'integer',
     *                                  'unsigned' => 1
     *                                  'notnull' => 1
     *                                  'default' => 0
     *                              ),
     *                              'name' => array(
     *                                  'type' => 'text',
     *                                  'length' => 12
     *                              ),
     *                              'password' => array(
     *                                  'type' => 'text',
     *                                  'length' => 12
     *                              )
     *                          );
     * @param array $options  An associative array of table options:
     *
zYne's avatar
zYne committed
192
     * @return string
193
     */
zYne's avatar
zYne committed
194
    public function createTableSql($name, array $fields, array $options = array())
zYne's avatar
zYne committed
195 196
    {
        if ( ! $name) {
197
            throw new Doctrine_Export_Exception('no valid table name specified');
zYne's avatar
zYne committed
198 199
        }
        
lsmith's avatar
lsmith committed
200
        if (empty($fields)) {
zYne's avatar
zYne committed
201
            throw new Doctrine_Export_Exception('no fields specified for table ' . $name);
lsmith's avatar
lsmith committed
202
        }
zYne's avatar
zYne committed
203

204
        $queryFields = $this->getFieldDeclarationList($fields);
zYne's avatar
zYne committed
205

zYne's avatar
zYne committed
206

207
        if (isset($options['primary']) && ! empty($options['primary'])) {
zYne's avatar
zYne committed
208 209
            $queryFields .= ', PRIMARY KEY(' . implode(', ', array_values($options['primary'])) . ')';
        }
zYne's avatar
zYne committed
210

zYne's avatar
zYne committed
211 212 213 214
        if (isset($options['indexes']) && ! empty($options['indexes'])) {
            foreach($options['indexes'] as $index => $definition) {
                $queryFields .= ', ' . $this->getIndexDeclaration($index, $definition);
            }
215 216
        }

217 218
        $name  = $this->conn->quoteIdentifier($name, true);
        $query = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')';
zYne's avatar
zYne committed
219

zYne's avatar
zYne committed
220 221 222 223 224 225
        $sql[] = $query;

        if (isset($options['foreignKeys'])) {

            foreach ((array) $options['foreignKeys'] as $k => $definition) {
                if (is_array($definition)) {
zYne's avatar
zYne committed
226
                    $sql[] = $this->createForeignKeySql($name, $definition);
zYne's avatar
zYne committed
227 228 229 230
                }
            }
        }   
        return $sql;
zYne's avatar
zYne committed
231 232 233 234 235 236 237 238 239 240 241 242 243
    }
    /**
     * create a new table
     *
     * @param string $name   Name of the database that should be created
     * @param array $fields  Associative array that contains the definition of each field of the new table
     * @param array $options  An associative array of table options:
     * @see Doctrine_Export::createTableSql()
     *
     * @return void
     */
    public function createTable($name, array $fields, array $options = array())
    {
zYne's avatar
zYne committed
244 245 246 247 248
    	$sql = (array) $this->createTableSql($name, $fields, $options);

        foreach ($sql as $query) {
            $this->conn->execute($query);
        }
249 250 251 252
    }
    /**
     * create sequence
     *
zYne's avatar
zYne committed
253
     * @throws Doctrine_Connection_Exception     if something fails at database level
zYne's avatar
zYne committed
254 255
     * @param string    $seqName        name of the sequence to be created
     * @param string    $start          start value of the sequence; default is 1
zYne's avatar
zYne committed
256 257 258 259 260 261
     * @param array     $options  An associative array of table options:
     *                          array(
     *                              'comment' => 'Foo',
     *                              'charset' => 'utf8',
     *                              'collate' => 'utf8_unicode_ci',
     *                          );     
262 263
     * @return void
     */
zYne's avatar
zYne committed
264
    public function createSequence($seqName, $start = 1, array $options = array())
zYne's avatar
zYne committed
265
    {
zYne's avatar
zYne committed
266
        return $this->conn->execute($this->createSequenceSql($seqName, $start = 1, $options));
zYne's avatar
zYne committed
267 268 269 270 271
    }
    /**
     * return RDBMS specific create sequence statement
     * (this method is implemented by the drivers)
     *
zYne's avatar
zYne committed
272
     * @throws Doctrine_Connection_Exception     if something fails at database level
zYne's avatar
zYne committed
273 274
     * @param string    $seqName        name of the sequence to be created
     * @param string    $start          start value of the sequence; default is 1
zYne's avatar
zYne committed
275 276 277 278 279 280
     * @param array     $options  An associative array of table options:
     *                          array(
     *                              'comment' => 'Foo',
     *                              'charset' => 'utf8',
     *                              'collate' => 'utf8_unicode_ci',
     *                          );
zYne's avatar
zYne committed
281 282
     * @return string
     */
zYne's avatar
zYne committed
283
    public function createSequenceSql($seqName, $start = 1, array $options = array())
lsmith's avatar
lsmith committed
284
    {
285 286
        throw new Doctrine_Export_Exception('Create sequence not supported by this driver.');
    }
287
    /**
288 289 290 291 292 293 294 295 296 297
     * create a constraint on a table
     *
     * @param string    $table         name of the table on which the constraint is to be created
     * @param string    $name          name of the constraint to be created
     * @param array     $definition    associative array that defines properties of the constraint to be created.
     *                                 Currently, only one property named FIELDS is supported. This property
     *                                 is also an associative with the names of the constraint fields as array
     *                                 constraints. Each entry of this array is set to another type of associative
     *                                 array that specifies properties of the constraint that are specific to
     *                                 each field.
298
     *
299 300 301 302 303 304 305 306 307
     *                                 Example
     *                                    array(
     *                                        'fields' => array(
     *                                            'user_name' => array(),
     *                                            'last_login' => array()
     *                                        )
     *                                    )
     * @return void
     */
lsmith's avatar
lsmith committed
308
    public function createConstraint($table, $name, $definition)
zYne's avatar
zYne committed
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
    {
        return $this->conn->exec($this->createConstraintSql($table, $name, $definition));
    }
    /**
     * create a constraint on a table
     *
     * @param string    $table         name of the table on which the constraint is to be created
     * @param string    $name          name of the constraint to be created
     * @param array     $definition    associative array that defines properties of the constraint to be created.
     *                                 Currently, only one property named FIELDS is supported. This property
     *                                 is also an associative with the names of the constraint fields as array
     *                                 constraints. Each entry of this array is set to another type of associative
     *                                 array that specifies properties of the constraint that are specific to
     *                                 each field.
     *
     *                                 Example
     *                                    array(
     *                                        'fields' => array(
     *                                            'user_name' => array(),
     *                                            'last_login' => array()
     *                                        )
     *                                    )
     * @return void
     */
    public function createConstraintSql($table, $name, $definition)
lsmith's avatar
lsmith committed
334
    {
zYne's avatar
zYne committed
335 336
        $table = $this->conn->quoteIdentifier($table);
        $name  = $this->conn->quoteIdentifier($this->conn->formatter->getIndexName($name));
337
        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $name;
zYne's avatar
zYne committed
338

zYne's avatar
zYne committed
339
        if (isset($definition['primary']) && $definition['primary']) {
340
            $query .= ' PRIMARY KEY';
zYne's avatar
zYne committed
341
        } elseif (isset($definition['unique']) && $definition['unique']) {
342
            $query .= ' UNIQUE';
343
        }
zYne's avatar
zYne committed
344

345 346
        $fields = array();
        foreach (array_keys($definition['fields']) as $field) {
347
            $fields[] = $this->conn->quoteIdentifier($field, true);
348 349
        }
        $query .= ' ('. implode(', ', $fields) . ')';
zYne's avatar
zYne committed
350

zYne's avatar
zYne committed
351
        return $query;
352 353
    }
    /**
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
     * Get the stucture of a field into an array
     *
     * @param string    $table         name of the table on which the index is to be created
     * @param string    $name          name of the index to be created
     * @param array     $definition    associative array that defines properties of the index to be created.
     *                                 Currently, only one property named FIELDS is supported. This property
     *                                 is also an associative with the names of the index fields as array
     *                                 indexes. Each entry of this array is set to another type of associative
     *                                 array that specifies properties of the index that are specific to
     *                                 each field.
     *
     *                                 Currently, only the sorting property is supported. It should be used
     *                                 to define the sorting direction of the index. It may be set to either
     *                                 ascending or descending.
     *
     *                                 Not all DBMS support index sorting direction configuration. The DBMS
     *                                 drivers of those that do not support it ignore this property. Use the
     *                                 function supports() to determine whether the DBMS driver can manage indexes.
     *
     *                                 Example
     *                                    array(
     *                                        'fields' => array(
     *                                            'user_name' => array(
     *                                                'sorting' => 'ascending'
     *                                            ),
     *                                            'last_login' => array()
     *                                        )
     *                                    )
     * @return void
     */
lsmith's avatar
lsmith committed
384 385
    public function createIndex($table, $name, array $definition)
    {
zYne's avatar
zYne committed
386
        return $this->conn->execute($this->createIndexSql($table, $name, $definition));
387 388 389 390 391 392 393
    }
    /**
     * Get the stucture of a field into an array
     *
     * @param string    $table         name of the table on which the index is to be created
     * @param string    $name          name of the index to be created
     * @param array     $definition    associative array that defines properties of the index to be created.
394
     * @see Doctrine_Export::createIndex()
395 396
     * @return string
     */
lsmith's avatar
lsmith committed
397 398
    public function createIndexSql($table, $name, array $definition)
    {
399 400
        $table  = $this->conn->quoteIdentifier($table);
        $name   = $this->conn->quoteIdentifier($name);
zYne's avatar
zYne committed
401
        $type   = '';
zYne's avatar
zYne committed
402

zYne's avatar
zYne committed
403 404 405 406 407 408 409 410 411 412 413
        if(isset($definition['type'])) {
            switch (strtolower($definition['type'])) {
                case 'unique':
                    $type = strtoupper($definition['type']) . ' ';
                break;
                default:
                    throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
            }
        }

        $query = 'CREATE ' . $type . 'INDEX ' . $name . ' ON ' . $table;
414 415 416 417 418

        $fields = array();
        foreach (array_keys($definition['fields']) as $field) {
            $fields[] = $this->conn->quoteIdentifier($field);
        }
zYne's avatar
zYne committed
419
        $query .= ' (' . implode(', ', $fields) . ')';
zYne's avatar
zYne committed
420

421
        return $query;
422 423
    }
    /**
zYne's avatar
zYne committed
424
     * createForeignKeySql
425
     *
zYne's avatar
zYne committed
426
     * @param string    $table         name of the table on which the foreign key is to be created
427
     * @param array     $definition    associative array that defines properties of the foreign key to be created.
zYne's avatar
zYne committed
428
     * @return string
429
     */
zYne's avatar
zYne committed
430
    public function createForeignKeySql($table, array $definition)
431
    {
zYne's avatar
zYne committed
432 433 434
        $table = $this->conn->quoteIdentifier($table);

        $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $this->getForeignKeyDeclaration($definition);
435

zYne's avatar
zYne committed
436
        return $query;
437
    }
438 439
    /**
     * alter an existing table
440
     * (this method is implemented by the drivers)
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
     *
     * @param string $name         name of the table that is intended to be changed.
     * @param array $changes     associative array that contains the details of each type
     *                             of change that is intended to be performed. The types of
     *                             changes that are currently supported are defined as follows:
     *
     *                             name
     *
     *                                New name for the table.
     *
     *                            add
     *
     *                                Associative array with the names of fields to be added as
     *                                 indexes of the array. The value of each entry of the array
     *                                 should be set to another associative array with the properties
     *                                 of the fields to be added. The properties of the fields should
     *                                 be the same as defined by the MDB2 parser.
     *
     *
     *                            remove
     *
     *                                Associative array with the names of fields to be removed as indexes
     *                                 of the array. Currently the values assigned to each entry are ignored.
     *                                 An empty array should be used for future compatibility.
     *
     *                            rename
     *
     *                                Associative array with the names of fields to be renamed as indexes
     *                                 of the array. The value of each entry of the array should be set to
     *                                 another associative array with the entry named name with the new
     *                                 field name and the entry named Declaration that is expected to contain
     *                                 the portion of the field declaration already in DBMS specific SQL code
     *                                 as it is used in the CREATE TABLE statement.
     *
     *                            change
     *
     *                                Associative array with the names of the fields to be changed as indexes
     *                                 of the array. Keep in mind that if it is intended to change either the
     *                                 name of a field and any other properties, the change array entries
     *                                 should have the new names of the fields as array indexes.
     *
     *                                The value of each entry of the array should be set to another associative
     *                                 array with the properties of the fields to that are meant to be changed as
     *                                 array entries. These entries should be assigned to the new values of the
     *                                 respective properties. The properties of the fields should be the same
     *                                 as defined by the MDB2 parser.
     *
     *                            Example
     *                                array(
     *                                    'name' => 'userlist',
     *                                    'add' => array(
     *                                        'quota' => array(
     *                                            'type' => 'integer',
     *                                            'unsigned' => 1
     *                                        )
     *                                    ),
     *                                    'remove' => array(
     *                                        'file_limit' => array(),
     *                                        'time_limit' => array()
     *                                    ),
     *                                    'change' => array(
     *                                        'name' => array(
     *                                            'length' => '20',
     *                                            'definition' => array(
     *                                                'type' => 'text',
     *                                                'length' => 20,
     *                                            ),
     *                                        )
     *                                    ),
     *                                    'rename' => array(
     *                                        'sex' => array(
     *                                            'name' => 'gender',
     *                                            'definition' => array(
     *                                                'type' => 'text',
     *                                                'length' => 1,
     *                                                'default' => 'M',
     *                                            ),
     *                                        )
     *                                    )
     *                                )
     *
     * @param boolean $check     indicates whether the function should just check if the DBMS driver
     *                             can perform the requested table alterations if the value is true or
     *                             actually perform them otherwise.
     * @return void
     */
lsmith's avatar
lsmith committed
527 528
    public function alterTable($name, array $changes, $check)
    {
zYne's avatar
zYne committed
529
        $this->conn->execute($this->alterTableSql($name, $changes, $check));
530 531
    }
    /**
zYne's avatar
zYne committed
532
     * generates the sql for altering an existing table
533 534
     * (this method is implemented by the drivers)
     *
zYne's avatar
zYne committed
535 536 537 538 539 540
     * @param string $name          name of the table that is intended to be changed.
     * @param array $changes        associative array that contains the details of each type      *
     * @param boolean $check        indicates whether the function should just check if the DBMS driver
     *                              can perform the requested table alterations if the value is true or
     *                              actually perform them otherwise.
     * @see Doctrine_Export::alterTable()
541 542
     * @return string
     */
lsmith's avatar
lsmith committed
543 544
    public function alterTableSql($name, array $changes, $check)
    {
545 546 547 548 549
        throw new Doctrine_Export_Exception('Alter table not supported by this driver.');
    }
    /**
     * Get declaration of a number of field in bulk
     *
zYne's avatar
zYne committed
550
     * @param array $fields  a multidimensional associative array.
551 552 553 554 555
     *      The first dimension determines the field name, while the second
     *      dimension is keyed with the name of the properties
     *      of the field being declared as array indexes. Currently, the types
     *      of supported field properties are as follows:
     *
zYne's avatar
zYne committed
556 557 558 559 560
     *      length
     *          Integer value that determines the maximum length of the text
     *          field. If this argument is missing the field should be
     *          declared to have the longest length allowed by the DBMS.
     *
561
     *      default
zYne's avatar
zYne committed
562
     *          Text value to be used as default for this field.
563 564 565 566
     *
     *      notnull
     *          Boolean flag that indicates whether this field is constrained
     *          to not be set to null.
zYne's avatar
zYne committed
567 568 569 570
     *      charset
     *          Text value with the default CHARACTER SET for this field.
     *      collation
     *          Text value with the default COLLATION for this field.
zYne's avatar
zYne committed
571 572
     *      unique
     *          unique constraint
573 574 575
     *
     * @return string
     */
lsmith's avatar
lsmith committed
576 577
    public function getFieldDeclarationList(array $fields)
    {
578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
        foreach ($fields as $fieldName => $field) {
            $query = $this->getDeclaration($fieldName, $field);

            $queryFields[] = $query;
        }
        return implode(', ', $queryFields);
    }
    /**
     * Obtain DBMS specific SQL code portion needed to declare a generic type
     * field to be used in statements like CREATE TABLE.
     *
     * @param string $name   name the field to be declared.
     * @param array  $field  associative array with the name of the properties
     *      of the field being declared as array indexes. Currently, the types
     *      of supported field properties are as follows:
     *
     *      length
     *          Integer value that determines the maximum length of the text
     *          field. If this argument is missing the field should be
     *          declared to have the longest length allowed by the DBMS.
     *
     *      default
     *          Text value to be used as default for this field.
     *
     *      notnull
     *          Boolean flag that indicates whether this field is constrained
     *          to not be set to null.
     *      charset
     *          Text value with the default CHARACTER SET for this field.
     *      collation
     *          Text value with the default COLLATION for this field.
zYne's avatar
zYne committed
609 610
     *      unique
     *          unique constraint
zYne's avatar
zYne committed
611 612
     *      check   
     *          column check constraint
zYne's avatar
zYne committed
613
     *
614 615 616
     * @return string  DBMS specific SQL code portion that should be used to
     *      declare the specified field.
     */
lsmith's avatar
lsmith committed
617 618
    public function getDeclaration($name, array $field)
    {
619

zYne's avatar
zYne committed
620
        $default   = $this->getDefaultFieldDeclaration($field);
621

zYne's avatar
zYne committed
622 623 624 625 626
        $charset   = (isset($field['charset']) && $field['charset']) ?
                    ' ' . $this->getCharsetFieldDeclaration($field['charset']) : '';

        $collation = (isset($field['collation']) && $field['collation']) ?
                    ' ' . $this->getCollationFieldDeclaration($field['collation']) : '';
lsmith's avatar
lsmith committed
627

zYne's avatar
zYne committed
628
        $notnull   = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
lsmith's avatar
lsmith committed
629

zYne's avatar
zYne committed
630 631
        $unique    = (isset($field['unique']) && $field['unique']) ?
                    ' ' . $this->getUniqueFieldDeclaration() : '';
zYne's avatar
zYne committed
632 633 634
                    
        $check     = (isset($field['check']) && $field['check']) ?
                    ' ' . $field['check'] : '';
635 636 637

        $method = 'get' . $field['type'] . 'Declaration';

lsmith's avatar
lsmith committed
638
        if (method_exists($this->conn->dataDict, $method)) {
639
            return $this->conn->dataDict->$method($name, $field);
lsmith's avatar
lsmith committed
640
        } else {
641
            $dec = $this->conn->dataDict->getNativeDeclaration($field);
lsmith's avatar
lsmith committed
642
        }
zYne's avatar
zYne committed
643
        return $this->conn->quoteIdentifier($name, true) . ' ' . $dec . $charset . $default . $notnull . $unique . $check . $collation;
zYne's avatar
zYne committed
644
    }
zYne's avatar
zYne committed
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
    /**
     * getDefaultDeclaration
     * Obtain DBMS specific SQL code portion needed to set a default value
     * declaration to be used in statements like CREATE TABLE.
     *
     * @param array $field      field definition array
     * @return string           DBMS specific SQL code portion needed to set a default value
     */
    public function getDefaultFieldDeclaration($field)
    {
        $default = '';
        if (isset($field['default'])) {
            if ($field['default'] === '') {
                $field['default'] = empty($field['notnull'])
                    ? null : $this->valid_default_values[$field['type']];

                if ($field['default'] === ''
                    && ($conn->getAttribute(Doctrine::ATTR_PORTABILITY) & Doctrine::PORTABILITY_EMPTY_TO_NULL)
                ) {
                    $field['default'] = ' ';
                }
            }
    
zYne's avatar
zYne committed
668 669 670
            if ($field['type'] === 'boolean') {
                $fields['default'] = $this->conn->convertBooleans($field['default']);                                 	
            }
zYne's avatar
zYne committed
671 672 673 674
            $default = ' DEFAULT ' . $this->conn->quote($field['default'], $field['type']);
        }
        return $default;
    }
zYne's avatar
zYne committed
675 676 677 678 679 680
    /**
     * Obtain DBMS specific SQL code portion needed to set an index 
     * declaration to be used in statements like CREATE TABLE.
     *
     * @param string $charset       name of the index
     * @param array $definition     index definition
zYne's avatar
zYne committed
681
     * @return string               DBMS specific SQL code portion needed to set an index
zYne's avatar
zYne committed
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
     */
    public function getIndexDeclaration($name, array $definition)
    {
        $name   = $this->conn->quoteIdentifier($name);
        $type   = '';

        if (isset($definition['type'])) {
            if (strtolower($definition['type']) == 'unique') {
                $type = strtoupper($definition['type']) . ' ';
            } else {
                throw new Doctrine_Export_Exception('Unknown index type ' . $definition['type']);
            }
        }
        
        if ( ! isset($definition['fields']) || ! is_array($definition['fields'])) {
            throw new Doctrine_Export_Exception('No index columns given.');
        }

        $query = $type . 'INDEX ' . $name;

        $query .= ' (' . $this->getIndexFieldDeclarationList($definition['fields']) . ')';
        
        return $query;
    }
    /**
     * getIndexFieldDeclarationList
     * Obtain DBMS specific SQL code portion needed to set an index
     * declaration to be used in statements like CREATE TABLE.
     *
     * @return string
     */
    public function getIndexFieldDeclarationList(array $fields)
    {
zYne's avatar
zYne committed
715
    	$ret = array();
zYne's avatar
zYne committed
716 717
        foreach ($fields as $field => $definition) {
            if(is_array($definition)) {
zYne's avatar
zYne committed
718
                $ret[] = $this->conn->quoteIdentifier($field);
zYne's avatar
zYne committed
719
            } else {
zYne's avatar
zYne committed
720
                $ret[] = $this->conn->quoteIdentifier($definition);
zYne's avatar
zYne committed
721 722
            }
        }
zYne's avatar
zYne committed
723
        return implode(', ', $ret);
zYne's avatar
zYne committed
724
    }
zYne's avatar
zYne committed
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
    /**
     * A method to return the required SQL string that fits between CREATE ... TABLE
     * to create the table as a temporary table.
     *
     * Should be overridden in driver classes to return the correct string for the
     * specific database type.
     *
     * The default is to return the string "TEMPORARY" - this will result in a
     * SQL error for any database that does not support temporary tables, or that
     * requires a different SQL command from "CREATE TEMPORARY TABLE".
     *
     * @return string The string required to be placed between "CREATE" and "TABLE"
     *                to generate a temporary table, if possible.
     */
    public function getTemporaryTableQuery()
    {
        return 'TEMPORARY';
    }
zYne's avatar
zYne committed
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
    /**
     * getForeignKeyDeclaration
     * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @param array $definition         an associative array with the following structure:
     *          name                    optional constraint name
     * 
     *          local                   the local field(s)
     *
     *          foreign                 the foreign reference field(s)
     *
     *          foreignTable            the name of the foreign table
     *
     *          onDelete                referential delete action
     *  
     *          onUpdate                referential update action
     *
zYne's avatar
zYne committed
761 762
     *          deferred                deferred constraint checking
     *
zYne's avatar
zYne committed
763 764 765
     * The onDelete and onUpdate keys accept the following values:
     *
     * CASCADE: Delete or update the row from the parent table and automatically delete or 
766
     *          update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported.
zYne's avatar
zYne committed
767 768 769 770 771 772 773 774
     *          Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column
     *          in the parent table or in the child table.
     *
     * SET NULL: Delete or update the row from the parent table and set the foreign key column or columns in the
     *          child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier 
     *          specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
     *
     * NO ACTION: In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary 
775
     *           key value is not allowed to proceed if there is a related foreign key value in the referenced table.
zYne's avatar
zYne committed
776 777 778 779 780 781 782 783 784
     *
     * RESTRICT: Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as
     *           omitting the ON DELETE or ON UPDATE clause.
     *
     * SET DEFAULT
     *
     * @return string  DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
     *                 of a field declaration.
     */
785
    public function getForeignKeyDeclaration(array $definition)
zYne's avatar
zYne committed
786
    {
787
        $sql  = $this->getForeignKeyBaseDeclaration($definition);
zYne's avatar
zYne committed
788
        $sql .= $this->getAdvancedForeignKeyOptions($definition);
zYne's avatar
zYne committed
789 790 791

        return $sql;
    }
zYne's avatar
zYne committed
792 793 794 795
    /**
     * getAdvancedForeignKeyOptions
     * Return the FOREIGN KEY query section dealing with non-standard options
     * as MATCH, INITIALLY DEFERRED, ON UPDATE, ...
zYne's avatar
zYne committed
796
     *
zYne's avatar
zYne committed
797
     * @param array $definition     foreign key definition
zYne's avatar
zYne committed
798 799
     * @return string
     */
zYne's avatar
zYne committed
800
    public function getAdvancedForeignKeyOptions(array $definition)
zYne's avatar
zYne committed
801
    {
zYne's avatar
zYne committed
802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
        $query = '';
        if ( ! empty($definition['onUpdate'])) {
            $query .= ' ON UPDATE ' . $this->getForeignKeyRefentialAction($definition['onUpdate']);
        }
        if ( ! empty($definition['onDelete'])) {
            $query .= ' ON DELETE ' . $this->getForeignKeyRefentialAction($definition['onDelete']);
        }
        return $query;
    }
    /**
     * getForeignKeyReferentialAction
     *
     * returns given referential action in uppercase if valid, otherwise throws
     * an exception
     *
     * @throws Doctrine_Exception_Exception     if unknown referential action given
     * @param string $action    foreign key referential action
     * @param string            foreign key referential action in uppercase
     */
    public function getForeignKeyReferentialAction($action)
    {
    	$upper = strtoupper($action);
        switch ($upper) {
            case 'CASCADE':
            case 'SET NULL':
            case 'NO ACTION':
            case 'RESTRICT':
            case 'SET DEFAULT':
                return $upper;
            break;
            default:
                throw new Doctrine_Export_Exception('Unknown foreign key referential action \'' . $upper . '\' given.');
        }
zYne's avatar
zYne committed
835 836 837
    }
    /**
     * getForeignKeyBaseDeclaration
838 839
     * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint
     * of a field declaration to be used in statements like CREATE TABLE.
zYne's avatar
zYne committed
840
     *
841
     * @param array $definition
zYne's avatar
zYne committed
842 843
     * @return string
     */
844
    public function getForeignKeyBaseDeclaration(array $definition)
zYne's avatar
zYne committed
845 846 847
    {
    	$sql = '';
        if (isset($definition['name'])) {
zYne's avatar
zYne committed
848
            $sql .= 'CONSTRAINT ' . $this->conn->quoteIdentifier($definition['name']) . ' ';
zYne's avatar
zYne committed
849
        }
zYne's avatar
zYne committed
850
        $sql .= 'FOREIGN KEY (';
zYne's avatar
zYne committed
851

zYne's avatar
zYne committed
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
        if ( ! isset($definition['local'])) {
            throw new Doctrine_Export_Exception('Local reference field missing from definition.');
        }
        if ( ! isset($definition['foreign'])) {
            throw new Doctrine_Export_Exception('Foreign reference field missing from definition.');
        }
        if ( ! isset($definition['foreignTable'])) {
            throw new Doctrine_Export_Exception('Foreign reference table missing from definition.');
        }

        if ( ! is_array($definition['local'])) {
            $definition['local'] = array($definition['local']);
        }
        if ( ! is_array($definition['foreign'])) {
            $definition['foreign'] = array($definition['foreign']);
        }
zYne's avatar
zYne committed
868

zYne's avatar
zYne committed
869
        $sql .= implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['local']))
zYne's avatar
zYne committed
870
              . ') REFERENCES '
zYne's avatar
zYne committed
871 872
              . $definition['foreignTable'] . '('
              . implode(', ', array_map(array($this->conn, 'quoteIdentifier'), $definition['foreign'])) . ')';
zYne's avatar
zYne committed
873
        
zYne's avatar
zYne committed
874 875
        return $sql;
    }
zYne's avatar
zYne committed
876 877 878 879 880 881 882 883 884 885
    /**
     * Obtain DBMS specific SQL code portion needed to set the UNIQUE constraint
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @return string  DBMS specific SQL code portion needed to set the UNIQUE constraint
     *                 of a field declaration.
     */
    public function getUniqueFieldDeclaration()
    {
        return 'UNIQUE';
886 887 888 889 890 891 892 893 894
    }
    /**
     * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @param string $charset   name of the charset
     * @return string  DBMS specific SQL code portion needed to set the CHARACTER SET
     *                 of a field declaration.
     */
lsmith's avatar
lsmith committed
895 896
    public function getCharsetFieldDeclaration($charset)
    {
897 898 899 900 901 902 903 904 905 906
        return '';
    }
    /**
     * Obtain DBMS specific SQL code portion needed to set the COLLATION
     * of a field declaration to be used in statements like CREATE TABLE.
     *
     * @param string $collation   name of the collation
     * @return string  DBMS specific SQL code portion needed to set the COLLATION
     *                 of a field declaration.
     */
lsmith's avatar
lsmith committed
907 908
    public function getCollationFieldDeclaration($collation)
    {
909
        return '';
zYne's avatar
zYne committed
910
    } 
911 912
    /**
     * export
zYne's avatar
zYne committed
913 914
     * method for exporting Doctrine_Record classes to a schema
     *
zYne's avatar
zYne committed
915 916 917 918 919 920 921 922 923
     * if the directory parameter is given this method first iterates 
     * recursively trhough the given directory in order to find any model classes
     *
     * Then it iterates through all declared classes and creates tables for the ones
     * that extend Doctrine_Record and are not abstract classes
     *
     * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
     *                                          occurred during the create table operation
     * @param string $directory     optional directory parameter
zYne's avatar
zYne committed
924
     * @return void
925
     */
zYne's avatar
zYne committed
926
    public function export($directory = null)
lsmith's avatar
lsmith committed
927
    {
zYne's avatar
zYne committed
928 929
        $sql = $this->exportSql($directory);

zYne's avatar
zYne committed
930 931
        $this->conn->beginTransaction();

zYne's avatar
zYne committed
932 933 934 935 936 937
        foreach ($sql as $query) {
            try { 
                $this->conn->exec($query);
            } catch (Doctrine_Connection_Exception $e) {
                // we only want to silence table already exists errors
                if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
zYne's avatar
zYne committed
938
                    $this->conn->rollback();
zYne's avatar
zYne committed
939 940
                    throw $e;
                }                                           
zYne's avatar
zYne committed
941
            }
zYne's avatar
zYne committed
942
        }
zYne's avatar
zYne committed
943
        $this->conn->commit();
zYne's avatar
zYne committed
944 945
    }
    /**
zYne's avatar
zYne committed
946
     * exportClasses
zYne's avatar
zYne committed
947 948 949 950 951 952 953 954 955
     * method for exporting Doctrine_Record classes to a schema
     *
     * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
     *                                          occurred during the create table operation
     * @param array $classes
     * @return void
     */
    public function exportClasses(array $classes)
    {
zYne's avatar
zYne committed
956 957
        $sql = $this->exportClassesSql($classes);

zYne's avatar
zYne committed
958 959
        $this->conn->beginTransaction();

zYne's avatar
zYne committed
960
        foreach ($sql as $query) {
zYne's avatar
zYne committed
961
            try {
zYne's avatar
zYne committed
962 963 964 965
                $this->conn->exec($query);
            } catch (Doctrine_Connection_Exception $e) {
                // we only want to silence table already exists errors
                if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
zYne's avatar
zYne committed
966
                    $this->conn->rollback();
zYne's avatar
zYne committed
967
                    throw $e;
zYne's avatar
zYne committed
968
                }
zYne's avatar
zYne committed
969 970
            }
        }
zYne's avatar
zYne committed
971
        $this->conn->commit();
zYne's avatar
zYne committed
972 973
    }
    /**
zYne's avatar
zYne committed
974 975
     * exportClassesSql
     * method for exporting Doctrine_Record classes to a schema
zYne's avatar
zYne committed
976 977 978
     *
     * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
     *                                          occurred during the create table operation
zYne's avatar
zYne committed
979
     * @param array $classes
zYne's avatar
zYne committed
980 981
     * @return void
     */
zYne's avatar
zYne committed
982
    public function exportClassesSql(array $classes)
zYne's avatar
zYne committed
983 984 985 986 987 988 989 990
    {
        $parent = new ReflectionClass('Doctrine_Record');

        $sql = array();
        $fks = array();

        // we iterate trhough the diff of previously declared classes 
        // and currently declared classes
zYne's avatar
zYne committed
991
        foreach ($classes as $name) {
zYne's avatar
zYne committed
992 993
            $class = new ReflectionClass($name);
            $conn  = Doctrine_Manager::getInstance()->getConnectionForComponent($name);
994

zYne's avatar
zYne committed
995
            // check if class is an instance of Doctrine_Record and not abstract
996 997
            // class must have method setTableDefinition (to avoid non-Record subclasses like symfony's sfDoctrineRecord)
            if ($class->isSubclassOf($parent) && ! $class->isAbstract() && method_exists($class->getName(), 'setTableDefinition')) {
zYne's avatar
zYne committed
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008
                $record = new $name();
                $table  = $record->getTable();
                $data = $table->getExportableFormat();

                $query = $this->conn->export->createTableSql($data['tableName'], $data['columns'], $data['options']);
                
                if (is_array($query)) {
                    $sql = array_merge($sql, $query);
                } else {
                    $sql[] = $query;
                }
lsmith's avatar
lsmith committed
1009
            }
1010
        }
1011
        rsort($sql);
zYne's avatar
zYne committed
1012

zYne's avatar
zYne committed
1013
        return $sql;
1014
    }
zYne's avatar
zYne committed
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
    /**
     * exportSql
     * returns the sql for exporting Doctrine_Record classes to a schema
     *
     * if the directory parameter is given this method first iterates 
     * recursively trhough the given directory in order to find any model classes
     *
     * Then it iterates through all declared classes and creates tables for the ones
     * that extend Doctrine_Record and are not abstract classes
     *
     * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
     *                                          occurred during the create table operation
     * @param string $directory     optional directory parameter
     * @return void
     */
    public function exportSql($directory = null)
    {
    	$declared = get_declared_classes();

       	if ($directory !== null) {
       	    foreach ((array) $directory as $dir) {
                $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir),
                                                        RecursiveIteratorIterator::LEAVES_ONLY);
                                                        
                foreach ($it as $file) {
                    $e = explode('.', $file->getFileName());
zYne's avatar
zYne committed
1041
                    if (end($e) === 'php' && strpos($file->getFileName(), '.inc') === false) {
zYne's avatar
zYne committed
1042 1043 1044 1045 1046 1047 1048 1049 1050
                        require_once $file->getPathName();
                    }
                }
            }
            $declared = array_diff(get_declared_classes(), $declared);
        }

        return $this->exportClassesSql($declared);
    }
zYne's avatar
zYne committed
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
    /**
     * exportTable
     * exports given table into database based on column and option definitions
     *
     * @throws Doctrine_Connection_Exception    if some error other than Doctrine::ERR_ALREADY_EXISTS
     *                                          occurred during the create table operation
     * @return boolean                          whether or not the export operation was successful
     *                                          false if table already existed in the database
     */
    public function exportTable(Doctrine_Table $table)
lsmith's avatar
lsmith committed
1061
    {
zYne's avatar
zYne committed
1062 1063
    	/**
    	TODO: maybe there should be portability option for the following check
zYne's avatar
zYne committed
1064 1065 1066
        if ( ! Doctrine::isValidClassname($table->getOption('declaringClass')->getName())) {
            throw new Doctrine_Export_Exception('Class name not valid.');
        }
zYne's avatar
zYne committed
1067
        */
zYne's avatar
zYne committed
1068 1069

        try {
zYne's avatar
zYne committed
1070
            $data = $table->getExportableFormat();
zYne's avatar
zYne committed
1071

zYne's avatar
zYne committed
1072
            $this->conn->export->createTable($data['tableName'], $data['columns'], $data['options']);
zYne's avatar
zYne committed
1073 1074 1075 1076 1077 1078
        } catch(Doctrine_Connection_Exception $e) {
            // we only want to silence table already exists errors
            if($e->getPortableCode() !== Doctrine::ERR_ALREADY_EXISTS) {
                throw $e;
            }
        }
zYne's avatar
zYne committed
1079
    }
zYne's avatar
zYne committed
1080
}