PageRenderTime 60ms CodeModel.GetById 15ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 1ms

/php-pear-MDB2-Schema-0.8.5/MDB2_Schema-0.8.5/MDB2/Schema.php

#
PHP | 2767 lines | 1775 code | 283 blank | 709 comment | 494 complexity | bc5c139674e5780f76186b77308f126a MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * PHP version 4, 5
   4 *
   5 * Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox,
   6 * Stig. S. Bakken, Lukas Smith, Igor Feghali
   7 * All rights reserved.
   8 *
   9 * MDB2_Schema enables users to maintain RDBMS independant schema files
  10 * in XML that can be used to manipulate both data and database schemas
  11 * This LICENSE is in the BSD license style.
  12 *
  13 * Redistribution and use in source and binary forms, with or without
  14 * modification, are permitted provided that the following conditions
  15 * are met:
  16 *
  17 * Redistributions of source code must retain the above copyright
  18 * notice, this list of conditions and the following disclaimer.
  19 *
  20 * Redistributions in binary form must reproduce the above copyright
  21 * notice, this list of conditions and the following disclaimer in the
  22 * documentation and/or other materials provided with the distribution.
  23 *
  24 * Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,
  25 * Lukas Smith, Igor Feghali nor the names of his contributors may be
  26 * used to endorse or promote products derived from this software
  27 * without specific prior written permission.
  28 *
  29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  30 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  31 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  32 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
  33 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  34 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  35 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  36 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  37 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  39 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  40 * POSSIBILITY OF SUCH DAMAGE.
  41 *
  42 * Author: Lukas Smith <smith@pooteeweet.org>
  43 * Author: Igor Feghali <ifeghali@php.net>
  44 *
  45 * @category Database
  46 * @package  MDB2_Schema
  47 * @author   Lukas Smith <smith@pooteeweet.org>
  48 * @author   Igor Feghali <ifeghali@php.net>
  49 * @license  BSD http://www.opensource.org/licenses/bsd-license.php
  50 * @version  CVS: $Id: Schema.php,v 1.132 2009/02/22 21:43:22 ifeghali Exp $
  51 * @link     http://pear.php.net/packages/MDB2_Schema
  52 */
  53
  54require_once 'MDB2.php';
  55
  56define('MDB2_SCHEMA_DUMP_ALL',       0);
  57define('MDB2_SCHEMA_DUMP_STRUCTURE', 1);
  58define('MDB2_SCHEMA_DUMP_CONTENT',   2);
  59
  60/**
  61 * If you add an error code here, make sure you also add a textual
  62 * version of it in MDB2_Schema::errorMessage().
  63 */
  64
  65define('MDB2_SCHEMA_ERROR',             -1);
  66define('MDB2_SCHEMA_ERROR_PARSE',       -2);
  67define('MDB2_SCHEMA_ERROR_VALIDATE',    -3);
  68define('MDB2_SCHEMA_ERROR_UNSUPPORTED', -4);    // Driver does not support this function
  69define('MDB2_SCHEMA_ERROR_INVALID',     -5);    // Invalid attribute value
  70define('MDB2_SCHEMA_ERROR_WRITER',      -6);
  71
  72/**
  73 * The database manager is a class that provides a set of database
  74 * management services like installing, altering and dumping the data
  75 * structures of databases.
  76 *
  77 * @category Database
  78 * @package  MDB2_Schema
  79 * @author   Lukas Smith <smith@pooteeweet.org>
  80 * @license  BSD http://www.opensource.org/licenses/bsd-license.php
  81 * @link     http://pear.php.net/packages/MDB2_Schema
  82 */
  83class MDB2_Schema extends PEAR
  84{
  85    // {{{ properties
  86
  87    var $db;
  88
  89    var $warnings = array();
  90
  91    var $options = array(
  92        'fail_on_invalid_names' => true,
  93        'dtd_file'              => false,
  94        'valid_types'           => array(),
  95        'force_defaults'        => true,
  96        'parser'                => 'MDB2_Schema_Parser',
  97        'writer'                => 'MDB2_Schema_Writer',
  98        'validate'              => 'MDB2_Schema_Validate',
  99        'drop_missing_tables'   => false
 100    );
 101
 102    // }}}
 103    // {{{ apiVersion()
 104
 105    /**
 106     * Return the MDB2 API version
 107     *
 108     * @return string  the MDB2 API version number
 109     * @access public
 110     */
 111    function apiVersion()
 112    {
 113        return '0.4.3';
 114    }
 115
 116    // }}}
 117    // {{{ arrayMergeClobber()
 118
 119    /**
 120     * Clobbers two arrays together
 121     *
 122     * @param array $a1 array that should be clobbered
 123     * @param array $a2 array that should be clobbered
 124     *
 125     * @return array|false  array on success and false on error
 126     *
 127     * @access public
 128     * @author kc@hireability.com
 129     */
 130    function arrayMergeClobber($a1, $a2)
 131    {
 132        if (!is_array($a1) || !is_array($a2)) {
 133            return false;
 134        }
 135        foreach ($a2 as $key => $val) {
 136            if (is_array($val) && array_key_exists($key, $a1) && is_array($a1[$key])) {
 137                $a1[$key] = MDB2_Schema::arrayMergeClobber($a1[$key], $val);
 138            } else {
 139                $a1[$key] = $val;
 140            }
 141        }
 142        return $a1;
 143    }
 144
 145    // }}}
 146    // {{{ resetWarnings()
 147
 148    /**
 149     * reset the warning array
 150     *
 151     * @access public
 152     * @return void
 153     */
 154    function resetWarnings()
 155    {
 156        $this->warnings = array();
 157    }
 158
 159    // }}}
 160    // {{{ getWarnings()
 161
 162    /**
 163     * Get all warnings in reverse order
 164     *
 165     * This means that the last warning is the first element in the array
 166     *
 167     * @return array with warnings
 168     * @access public
 169     * @see resetWarnings()
 170     */
 171    function getWarnings()
 172    {
 173        return array_reverse($this->warnings);
 174    }
 175
 176    // }}}
 177    // {{{ setOption()
 178
 179    /**
 180     * Sets the option for the db class
 181     *
 182     * @param string $option option name
 183     * @param mixed  $value  value for the option
 184     *
 185     * @return bool|MDB2_Error MDB2_OK or error object
 186     * @access public
 187     */
 188    function setOption($option, $value)
 189    {
 190        if (isset($this->options[$option])) {
 191            if (is_null($value)) {
 192                return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
 193                    'may not set an option to value null');
 194            }
 195            $this->options[$option] = $value;
 196            return MDB2_OK;
 197        }
 198        return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
 199            "unknown option $option");
 200    }
 201
 202    // }}}
 203    // {{{ getOption()
 204
 205    /**
 206     * returns the value of an option
 207     *
 208     * @param string $option option name
 209     *
 210     * @return mixed the option value or error object
 211     * @access public
 212     */
 213    function getOption($option)
 214    {
 215        if (isset($this->options[$option])) {
 216            return $this->options[$option];
 217        }
 218        return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED,
 219            null, null, "unknown option $option");
 220    }
 221
 222    // }}}
 223    // {{{ factory()
 224
 225    /**
 226     * Create a new MDB2 object for the specified database type
 227     * type
 228     *
 229     * @param string|array|MDB2_Driver_Common &$db     'data source name', see the
 230     *                                                 MDB2::parseDSN method for a description of the dsn format.
 231     *                                                 Can also be specified as an array of the
 232     *                                                 format returned by @see MDB2::parseDSN.
 233     *                                                 Finally you can also pass an existing db object to be used.
 234     * @param array                           $options An associative array of option names and their values.
 235     *
 236     * @return bool|MDB2_Error MDB2_OK or error object
 237     * @access public
 238     * @see     MDB2::parseDSN
 239     */
 240    function &factory(&$db, $options = array())
 241    {
 242        $obj =& new MDB2_Schema();
 243
 244        $result = $obj->connect($db, $options);
 245        if (PEAR::isError($result)) {
 246            return $result;
 247        }
 248        return $obj;
 249    }
 250
 251    // }}}
 252    // {{{ connect()
 253
 254    /**
 255     * Create a new MDB2 connection object and connect to the specified
 256     * database
 257     *
 258     * @param string|array|MDB2_Driver_Common &$db     'data source name', see the
 259     *              MDB2::parseDSN method for a description of the dsn format.
 260     *              Can also be specified as an array of the
 261     *              format returned by MDB2::parseDSN.
 262     *              Finally you can also pass an existing db object to be used.
 263     * @param array                           $options An associative array of option names and their values.
 264     *
 265     * @return bool|MDB2_Error MDB2_OK or error object
 266     * @access public
 267     * @see    MDB2::parseDSN
 268     */
 269    function connect(&$db, $options = array())
 270    {
 271        $db_options = array();
 272        if (is_array($options)) {
 273            foreach ($options as $option => $value) {
 274                if (array_key_exists($option, $this->options)) {
 275                    $result = $this->setOption($option, $value);
 276                    if (PEAR::isError($result)) {
 277                        return $result;
 278                    }
 279                } else {
 280                    $db_options[$option] = $value;
 281                }
 282            }
 283        }
 284
 285        $this->disconnect();
 286        if (!MDB2::isConnection($db)) {
 287            $db =& MDB2::factory($db, $db_options);
 288        }
 289
 290        if (PEAR::isError($db)) {
 291            return $db;
 292        }
 293
 294        $this->db =& $db;
 295        $this->db->loadModule('Datatype');
 296        $this->db->loadModule('Manager');
 297        $this->db->loadModule('Reverse');
 298        $this->db->loadModule('Function');
 299        if (empty($this->options['valid_types'])) {
 300            $this->options['valid_types'] = $this->db->datatype->getValidTypes();
 301        }
 302
 303        return MDB2_OK;
 304    }
 305
 306    // }}}
 307    // {{{ disconnect()
 308
 309    /**
 310     * Log out and disconnect from the database.
 311     *
 312     * @access public
 313     * @return void
 314     */
 315    function disconnect()
 316    {
 317        if (MDB2::isConnection($this->db)) {
 318            $this->db->disconnect();
 319            unset($this->db);
 320        }
 321    }
 322
 323    // }}}
 324    // {{{ parseDatabaseDefinition()
 325
 326    /**
 327     * Parse a database definition from a file or an array
 328     *
 329     * @param string|array $schema                the database schema array or file name
 330     * @param bool         $skip_unreadable       if non readable files should be skipped
 331     * @param array        $variables             associative array that the defines the text string values
 332     *                                            that are meant to be used to replace the variables that are
 333     *                                            used in the schema description.
 334     * @param bool         $fail_on_invalid_names make function fail on invalid names
 335     * @param array        $structure             database structure definition
 336     *
 337     * @access public
 338     * @return array
 339     */
 340    function parseDatabaseDefinition($schema, $skip_unreadable = false, $variables = array(),
 341        $fail_on_invalid_names = true, $structure = false)
 342    {
 343        $database_definition = false;
 344        if (is_string($schema)) {
 345            // if $schema is not readable then we just skip it
 346            // and simply copy the $current_schema file to that file name
 347            if (is_readable($schema)) {
 348                $database_definition = $this->parseDatabaseDefinitionFile($schema, $variables, $fail_on_invalid_names, $structure);
 349            }
 350        } elseif (is_array($schema)) {
 351            $database_definition = $schema;
 352        }
 353        if (!$database_definition && !$skip_unreadable) {
 354            $database_definition = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
 355                'invalid data type of schema or unreadable data source');
 356        }
 357        return $database_definition;
 358    }
 359
 360    // }}}
 361    // {{{ parseDatabaseDefinitionFile()
 362
 363    /**
 364     * Parse a database definition file by creating a schema format
 365     * parser object and passing the file contents as parser input data stream.
 366     *
 367     * @param string $input_file            the database schema file.
 368     * @param array  $variables             associative array that the defines the text string values
 369     *                                      that are meant to be used to replace the variables that are
 370     *                                      used in the schema description.
 371     * @param bool   $fail_on_invalid_names make function fail on invalid names
 372     * @param array  $structure             database structure definition
 373     *
 374     * @access public
 375     * @return array
 376     */
 377    function parseDatabaseDefinitionFile($input_file, $variables = array(),
 378        $fail_on_invalid_names = true, $structure = false)
 379    {
 380        $dtd_file = $this->options['dtd_file'];
 381        if ($dtd_file) {
 382            include_once 'XML/DTD/XmlValidator.php';
 383            $dtd =& new XML_DTD_XmlValidator;
 384            if (!$dtd->isValid($dtd_file, $input_file)) {
 385                return $this->raiseError(MDB2_SCHEMA_ERROR_PARSE, null, null, $dtd->getMessage());
 386            }
 387        }
 388
 389        $class_name = $this->options['parser'];
 390
 391        $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
 392        if (PEAR::isError($result)) {
 393            return $result;
 394        }
 395
 396        $parser =& new $class_name($variables, $fail_on_invalid_names, $structure, $this->options['valid_types'], $this->options['force_defaults']);
 397        $result = $parser->setInputFile($input_file);
 398        if (PEAR::isError($result)) {
 399            return $result;
 400        }
 401
 402        $result = $parser->parse();
 403        if (PEAR::isError($result)) {
 404            return $result;
 405        }
 406        if (PEAR::isError($parser->error)) {
 407            return $parser->error;
 408        }
 409
 410        return $parser->database_definition;
 411    }
 412
 413    // }}}
 414    // {{{ getDefinitionFromDatabase()
 415
 416    /**
 417     * Attempt to reverse engineer a schema structure from an existing MDB2
 418     * This method can be used if no xml schema file exists yet.
 419     * The resulting xml schema file may need some manual adjustments.
 420     *
 421     * @return array|MDB2_Error array with definition or error object
 422     * @access public
 423     */
 424    function getDefinitionFromDatabase()
 425    {
 426        $database = $this->db->database_name;
 427        if (empty($database)) {
 428            return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
 429                'it was not specified a valid database name');
 430        }
 431
 432        $class_name = $this->options['validate'];
 433
 434        $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
 435        if (PEAR::isError($result)) {
 436            return $result;
 437        }
 438
 439        $val =& new $class_name($this->options['fail_on_invalid_names'], $this->options['valid_types'], $this->options['force_defaults']);
 440
 441        $database_definition = array(
 442            'name' => $database,
 443            'create' => true,
 444            'overwrite' => false,
 445            'charset' => 'utf8',
 446            'description' => '',
 447            'comments' => '',
 448            'tables' => array(),
 449            'sequences' => array(),
 450        );
 451
 452        $tables = $this->db->manager->listTables();
 453        if (PEAR::isError($tables)) {
 454            return $tables;
 455        }
 456
 457        foreach ($tables as $table_name) {
 458            $fields = $this->db->manager->listTableFields($table_name);
 459            if (PEAR::isError($fields)) {
 460                return $fields;
 461            }
 462
 463            $database_definition['tables'][$table_name] = array(
 464                'was' => '',
 465                'description' => '',
 466                'comments' => '',
 467                'fields' => array(),
 468                'indexes' => array(),
 469                'constraints' => array(),
 470                'initialization' => array()
 471            );
 472
 473            $table_definition =& $database_definition['tables'][$table_name];
 474            foreach ($fields as $field_name) {
 475                $definition = $this->db->reverse->getTableFieldDefinition($table_name, $field_name);
 476                if (PEAR::isError($definition)) {
 477                    return $definition;
 478                }
 479
 480                if (!empty($definition[0]['autoincrement'])) {
 481                    $definition[0]['default'] = '0';
 482                }
 483
 484                $table_definition['fields'][$field_name] = $definition[0];
 485
 486                $field_choices = count($definition);
 487                if ($field_choices > 1) {
 488                    $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
 489
 490                    $field_choice_cnt = 1;
 491
 492                    $table_definition['fields'][$field_name]['choices'] = array();
 493                    foreach ($definition as $field_choice) {
 494                        $table_definition['fields'][$field_name]['choices'][] = $field_choice;
 495
 496                        $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
 497                        $field_choice_cnt++;
 498                    }
 499                    $this->warnings[] = $warning;
 500                }
 501
 502                /**
 503                 * The first parameter is used to verify if there are duplicated
 504                 * fields which we can guarantee that won't happen when reverse engineering
 505                 */
 506                $result = $val->validateField(array(), $table_definition['fields'][$field_name], $field_name);
 507                if (PEAR::isError($result)) {
 508                    return $result;
 509                }
 510            }
 511
 512            $keys = array();
 513
 514            $indexes = $this->db->manager->listTableIndexes($table_name);
 515            if (PEAR::isError($indexes)) {
 516                return $indexes;
 517            }
 518
 519            if (is_array($indexes)) {
 520                foreach ($indexes as $index_name) {
 521                    $this->db->expectError(MDB2_ERROR_NOT_FOUND);
 522                    $definition = $this->db->reverse->getTableIndexDefinition($table_name, $index_name);
 523                    $this->db->popExpect();
 524                    if (PEAR::isError($definition)) {
 525                        if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
 526                            continue;
 527                        }
 528                        return $definition;
 529                    }
 530
 531                    $keys[$index_name] = $definition;
 532                }
 533            }
 534
 535            $constraints = $this->db->manager->listTableConstraints($table_name);
 536            if (PEAR::isError($constraints)) {
 537                return $constraints;
 538            }
 539
 540            if (is_array($constraints)) {
 541                foreach ($constraints as $constraint_name) {
 542                    $this->db->expectError(MDB2_ERROR_NOT_FOUND);
 543                    $definition = $this->db->reverse->getTableConstraintDefinition($table_name, $constraint_name);
 544                    $this->db->popExpect();
 545                    if (PEAR::isError($definition)) {
 546                        if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
 547                            continue;
 548                        }
 549                        return $definition;
 550                    }
 551
 552                    $keys[$constraint_name] = $definition;
 553                }
 554            }
 555
 556            foreach ($keys as $key_name => $definition) {
 557                if (array_key_exists('foreign', $definition)
 558                    && $definition['foreign']
 559                ) {
 560                    /**
 561                     * The first parameter is used to verify if there are duplicated
 562                     * foreign keys which we can guarantee that won't happen when reverse engineering
 563                     */
 564                    $result = $val->validateConstraint(array(), $definition, $key_name);
 565                    if (PEAR::isError($result)) {
 566                        return $result;
 567                    }
 568
 569                    foreach ($definition['fields'] as $field_name => $field) {
 570                        /**
 571                         * The first parameter is used to verify if there are duplicated
 572                         * referencing fields which we can guarantee that won't happen when reverse engineering
 573                         */
 574                        $result = $val->validateConstraintField(array(), $field_name);
 575                        if (PEAR::isError($result)) {
 576                            return $result;
 577                        }
 578
 579                        $definition['fields'][$field_name] = '';
 580                    }
 581
 582                    foreach ($definition['references']['fields'] as $field_name => $field) {
 583                        /**
 584                         * The first parameter is used to verify if there are duplicated
 585                         * referenced fields which we can guarantee that won't happen when reverse engineering
 586                         */
 587                        $result = $val->validateConstraintReferencedField(array(), $field_name);
 588                        if (PEAR::isError($result)) {
 589                            return $result;
 590                        }
 591
 592                        $definition['references']['fields'][$field_name] = '';
 593                    }
 594
 595                    $table_definition['constraints'][$key_name] = $definition;
 596                } else {
 597                    /**
 598                     * The first parameter is used to verify if there are duplicated
 599                     * indices which we can guarantee that won't happen when reverse engineering
 600                     */
 601                    $result = $val->validateIndex(array(), $definition, $key_name);
 602                    if (PEAR::isError($result)) {
 603                        return $result;
 604                    }
 605
 606                    foreach ($definition['fields'] as $field_name => $field) {
 607                        /**
 608                         * The first parameter is used to verify if there are duplicated
 609                         * index fields which we can guarantee that won't happen when reverse engineering
 610                         */
 611                        $result = $val->validateIndexField(array(), $field, $field_name);
 612                        if (PEAR::isError($result)) {
 613                            return $result;
 614                        }
 615
 616                        $definition['fields'][$field_name] = $field;
 617                    }
 618
 619                    $table_definition['indexes'][$key_name] = $definition;
 620                }
 621            }
 622
 623            /**
 624             * The first parameter is used to verify if there are duplicated
 625             * tables which we can guarantee that won't happen when reverse engineering
 626             */
 627            $result = $val->validateTable(array(), $table_definition, $table_name);
 628            if (PEAR::isError($result)) {
 629                return $result;
 630            }
 631
 632        }
 633
 634        $sequences = $this->db->manager->listSequences();
 635        if (PEAR::isError($sequences)) {
 636            return $sequences;
 637        }
 638
 639        if (is_array($sequences)) {
 640            foreach ($sequences as $sequence_name) {
 641                $definition = $this->db->reverse->getSequenceDefinition($sequence_name);
 642                if (PEAR::isError($definition)) {
 643                    return $definition;
 644                }
 645                if (isset($database_definition['tables'][$sequence_name])
 646                    && isset($database_definition['tables'][$sequence_name]['indexes'])
 647                ) {
 648                    foreach ($database_definition['tables'][$sequence_name]['indexes'] as $index) {
 649                        if (isset($index['primary']) && $index['primary']
 650                            && count($index['fields'] == 1)
 651                        ) {
 652                            $definition['on'] = array(
 653                                'table' => $sequence_name,
 654                                'field' => key($index['fields']),
 655                            );
 656                            break;
 657                        }
 658                    }
 659                }
 660
 661                /**
 662                 * The first parameter is used to verify if there are duplicated
 663                 * sequences which we can guarantee that won't happen when reverse engineering
 664                 */
 665                $result = $val->validateSequence(array(), $definition, $sequence_name);
 666                if (PEAR::isError($result)) {
 667                    return $result;
 668                }
 669
 670                $database_definition['sequences'][$sequence_name] = $definition;
 671            }
 672        }
 673
 674        $result = $val->validateDatabase($database_definition);
 675        if (PEAR::isError($result)) {
 676            return $result;
 677        }
 678
 679        return $database_definition;
 680    }
 681
 682    // }}}
 683    // {{{ createTableIndexes()
 684
 685    /**
 686     * A method to create indexes for an existing table
 687     *
 688     * @param string  $table_name Name of the table
 689     * @param array   $indexes    An array of indexes to be created
 690     * @param boolean $overwrite  If the table/index should be overwritten if it already exists
 691     *
 692     * @return mixed  MDB2_Error if there is an error creating an index, MDB2_OK otherwise
 693     * @access public
 694     */
 695    function createTableIndexes($table_name, $indexes, $overwrite = false)
 696    {
 697        if (!$this->db->supports('indexes')) {
 698            $this->db->debug('Indexes are not supported', __FUNCTION__);
 699            return MDB2_OK;
 700        }
 701
 702        $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
 703        foreach ($indexes as $index_name => $index) {
 704
 705            // Does the index already exist, and if so, should it be overwritten?
 706            $create_index = true;
 707            $this->db->expectError($errorcodes);
 708            if (!empty($index['primary']) || !empty($index['unique'])) {
 709                $current_indexes = $this->db->manager->listTableConstraints($table_name);
 710            } else {
 711                $current_indexes = $this->db->manager->listTableIndexes($table_name);
 712            }
 713
 714            $this->db->popExpect();
 715            if (PEAR::isError($current_indexes)) {
 716                if (!MDB2::isError($current_indexes, $errorcodes)) {
 717                    return $current_indexes;
 718                }
 719            } elseif (is_array($current_indexes) && in_array($index_name, $current_indexes)) {
 720                if (!$overwrite) {
 721                    $this->db->debug('Index already exists: '.$index_name, __FUNCTION__);
 722                    $create_index = false;
 723                } else {
 724                    $this->db->debug('Preparing to overwrite index: '.$index_name, __FUNCTION__);
 725
 726                    $this->db->expectError(MDB2_ERROR_NOT_FOUND);
 727                    if (!empty($index['primary']) || !empty($index['unique'])) {
 728                        $result = $this->db->manager->dropConstraint($table_name, $index_name);
 729                    } else {
 730                        $result = $this->db->manager->dropIndex($table_name, $index_name);
 731                    }
 732                    $this->db->popExpect();
 733                    if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) {
 734                        return $result;
 735                    }
 736                }
 737            }
 738
 739            // Check if primary is being used and if it's supported
 740            if (!empty($index['primary']) && !$this->db->supports('primary_key')) {
 741
 742                // Primary not supported so we fallback to UNIQUE and making the field NOT NULL
 743                $index['unique'] = true;
 744
 745                $changes = array();
 746
 747                foreach ($index['fields'] as $field => $empty) {
 748                    $field_info = $this->db->reverse->getTableFieldDefinition($table_name, $field);
 749                    if (PEAR::isError($field_info)) {
 750                        return $field_info;
 751                    }
 752                    if (!$field_info[0]['notnull']) {
 753                        $changes['change'][$field] = $field_info[0];
 754
 755                        $changes['change'][$field]['notnull'] = true;
 756                    }
 757                }
 758                if (!empty($changes)) {
 759                    $this->db->manager->alterTable($table_name, $changes, false);
 760                }
 761            }
 762
 763            // Should the index be created?
 764            if ($create_index) {
 765                if (!empty($index['primary']) || !empty($index['unique'])) {
 766                    $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
 767                } else {
 768                    $result = $this->db->manager->createIndex($table_name, $index_name, $index);
 769                }
 770                if (PEAR::isError($result)) {
 771                    return $result;
 772                }
 773            }
 774        }
 775        return MDB2_OK;
 776    }
 777
 778    // }}}
 779    // {{{ createTableConstraints()
 780
 781    /**
 782     * A method to create foreign keys for an existing table
 783     *
 784     * @param string  $table_name  Name of the table
 785     * @param array   $constraints An array of foreign keys to be created
 786     * @param boolean $overwrite   If the foreign key should be overwritten if it already exists
 787     *
 788     * @return mixed  MDB2_Error if there is an error creating a foreign key, MDB2_OK otherwise
 789     * @access public
 790     */
 791    function createTableConstraints($table_name, $constraints, $overwrite = false)
 792    {
 793        if (!$this->db->supports('indexes')) {
 794            $this->db->debug('Indexes are not supported', __FUNCTION__);
 795            return MDB2_OK;
 796        }
 797
 798        $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
 799        foreach ($constraints as $constraint_name => $constraint) {
 800
 801            // Does the foreign key already exist, and if so, should it be overwritten?
 802            $create_constraint = true;
 803            $this->db->expectError($errorcodes);
 804            $current_constraints = $this->db->manager->listTableConstraints($table_name);
 805            $this->db->popExpect();
 806            if (PEAR::isError($current_constraints)) {
 807                if (!MDB2::isError($current_constraints, $errorcodes)) {
 808                    return $current_constraints;
 809                }
 810            } elseif (is_array($current_constraints) && in_array($constraint_name, $current_constraints)) {
 811                if (!$overwrite) {
 812                    $this->db->debug('Foreign key already exists: '.$constraint_name, __FUNCTION__);
 813                    $create_constraint = false;
 814                } else {
 815                    $this->db->debug('Preparing to overwrite foreign key: '.$constraint_name, __FUNCTION__);
 816                    $result = $this->db->manager->dropConstraint($table_name, $constraint_name);
 817                    if (PEAR::isError($result)) {
 818                        return $result;
 819                    }
 820                }
 821            }
 822
 823            // Should the foreign key be created?
 824            if ($create_constraint) {
 825                $result = $this->db->manager->createConstraint($table_name, $constraint_name, $constraint);
 826                if (PEAR::isError($result)) {
 827                    return $result;
 828                }
 829            }
 830        }
 831        return MDB2_OK;
 832    }
 833
 834    // }}}
 835    // {{{ createTable()
 836
 837    /**
 838     * Create a table and inititialize the table if data is available
 839     *
 840     * @param string $table_name name of the table to be created
 841     * @param array  $table      multi dimensional array that contains the
 842     *                           structure and optional data of the table
 843     * @param bool   $overwrite  if the table/index should be overwritten if it already exists
 844     * @param array  $options    an array of options to be passed to the database specific driver
 845     *                           version of MDB2_Driver_Manager_Common::createTable().
 846     *
 847     * @return bool|MDB2_Error MDB2_OK or error object
 848     * @access public
 849     */
 850    function createTable($table_name, $table, $overwrite = false, $options = array())
 851    {
 852        $create = true;
 853
 854        $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
 855
 856        $this->db->expectError($errorcodes);
 857
 858        $tables = $this->db->manager->listTables();
 859
 860        $this->db->popExpect();
 861        if (PEAR::isError($tables)) {
 862            if (!MDB2::isError($tables, $errorcodes)) {
 863                return $tables;
 864            }
 865        } elseif (is_array($tables) && in_array($table_name, $tables)) {
 866            if (!$overwrite) {
 867                $create = false;
 868                $this->db->debug('Table already exists: '.$table_name, __FUNCTION__);
 869            } else {
 870                $result = $this->db->manager->dropTable($table_name);
 871                if (PEAR::isError($result)) {
 872                    return $result;
 873                }
 874                $this->db->debug('Overwritting table: '.$table_name, __FUNCTION__);
 875            }
 876        }
 877
 878        if ($create) {
 879            $result = $this->db->manager->createTable($table_name, $table['fields'], $options);
 880            if (PEAR::isError($result)) {
 881                return $result;
 882            }
 883        }
 884
 885        if (!empty($table['initialization']) && is_array($table['initialization'])) {
 886            $result = $this->initializeTable($table_name, $table);
 887            if (PEAR::isError($result)) {
 888                return $result;
 889            }
 890        }
 891
 892        if (!empty($table['indexes']) && is_array($table['indexes'])) {
 893            $result = $this->createTableIndexes($table_name, $table['indexes'], $overwrite);
 894            if (PEAR::isError($result)) {
 895                return $result;
 896            }
 897        }
 898
 899        if (!empty($table['constraints']) && is_array($table['constraints'])) {
 900            $result = $this->createTableConstraints($table_name, $table['constraints'], $overwrite);
 901            if (PEAR::isError($result)) {
 902                return $result;
 903            }
 904        }
 905
 906        return MDB2_OK;
 907    }
 908
 909    // }}}
 910    // {{{ initializeTable()
 911
 912    /**
 913     * Inititialize the table with data
 914     *
 915     * @param string $table_name name of the table
 916     * @param array  $table      multi dimensional array that contains the
 917     *                           structure and optional data of the table
 918     *
 919     * @return bool|MDB2_Error MDB2_OK or error object
 920     * @access public
 921     */
 922    function initializeTable($table_name, $table)
 923    {
 924        $query_insertselect = 'INSERT INTO %s (%s) (SELECT %s FROM %s %s)';
 925
 926        $query_insert = 'INSERT INTO %s (%s) VALUES (%s)';
 927        $query_update = 'UPDATE %s SET %s %s';
 928        $query_delete = 'DELETE FROM %s %s';
 929
 930        $table_name = $this->db->quoteIdentifier($table_name, true);
 931
 932        $result = MDB2_OK;
 933
 934        $support_transactions = $this->db->supports('transactions');
 935
 936        foreach ($table['initialization'] as $instruction) {
 937            $query = '';
 938            switch ($instruction['type']) {
 939            case 'insert':
 940                if (!isset($instruction['data']['select'])) {
 941                    $data = $this->getInstructionFields($instruction['data'], $table['fields']);
 942                    if (!empty($data)) {
 943                        $fields = implode(', ', array_keys($data));
 944                        $values = implode(', ', array_values($data));
 945
 946                        $query = sprintf($query_insert, $table_name, $fields, $values);
 947                    }
 948                } else {
 949                    $data  = $this->getInstructionFields($instruction['data']['select'], $table['fields']);
 950                    $where = $this->getInstructionWhere($instruction['data']['select'], $table['fields']);
 951
 952                    $select_table_name = $this->db->quoteIdentifier($instruction['data']['select']['table'], true);
 953                    if (!empty($data)) {
 954                        $fields = implode(', ', array_keys($data));
 955                        $values = implode(', ', array_values($data));
 956
 957                        $query = sprintf($query_insertselect, $table_name, $fields, $values, $select_table_name, $where);
 958                    }
 959                }
 960                break;
 961            case 'update':
 962                $data  = $this->getInstructionFields($instruction['data'], $table['fields']);
 963                $where = $this->getInstructionWhere($instruction['data'], $table['fields']);
 964                if (!empty($data)) {
 965                    array_walk($data, array($this, 'buildFieldValue'));
 966                    $fields_values = implode(', ', $data);
 967
 968                    $query = sprintf($query_update, $table_name, $fields_values, $where);
 969                }
 970                break;
 971            case 'delete':
 972                $where = $this->getInstructionWhere($instruction['data'], $table['fields']);
 973                $query = sprintf($query_delete, $table_name, $where);
 974                break;
 975            }
 976            if ($query) {
 977                if ($support_transactions && PEAR::isError($res = $this->db->beginNestedTransaction())) {
 978                    return $res;
 979                }
 980
 981                $result = $this->db->exec($query);
 982                if (PEAR::isError($result)) {
 983                    return $result;
 984                }
 985
 986                if ($support_transactions && PEAR::isError($res = $this->db->completeNestedTransaction())) {
 987                    return $res;
 988                }
 989            }
 990        }
 991        return $result;
 992    }
 993
 994    // }}}
 995    // {{{ buildFieldValue()
 996
 997    /**
 998     * Appends the contents of second argument + '=' to the beginning of first
 999     * argument.
1000     *
1001     * Used with array_walk() in initializeTable() for UPDATEs.
1002     *
1003     * @param string &$element value of array's element
1004     * @param string $key      key of array's element
1005     *
1006     * @return void
1007     *
1008     * @access public
1009     * @see MDB2_Schema::initializeTable()
1010     */
1011    function buildFieldValue(&$element, $key)
1012    {
1013        $element = $key."=$element";
1014    }
1015
1016    // }}}
1017    // {{{ getExpression()
1018
1019    /**
1020     * Generates a string that represents a value that would be associated
1021     * with a column in a DML instruction.
1022     *
1023     * @param array  $element           multi dimensional array that contains the
1024     *                                  structure of the current DML instruction.
1025     * @param array  $fields_definition multi dimensional array that contains the
1026     *                                  definition for current table's fields
1027     * @param string $type              type of given field
1028     *
1029     * @return string
1030     *
1031     * @access public
1032     * @see MDB2_Schema::getInstructionFields(), MDB2_Schema::getInstructionWhere()
1033     */
1034    function getExpression($element, $fields_definition = array(), $type = null)
1035    {
1036        $str = '';
1037        switch ($element['type']) {
1038        case 'null':
1039            $str .= 'NULL';
1040            break;
1041        case 'value':
1042            $str .= $this->db->quote($element['data'], $type);
1043            break;
1044        case 'column':
1045            $str .= $this->db->quoteIdentifier($element['data'], true);
1046            break;
1047        case 'function':
1048            $arguments = array();
1049            if (!empty($element['data']['arguments'])
1050                && is_array($element['data']['arguments'])
1051            ) {
1052                foreach ($element['data']['arguments'] as $v) {
1053                    $arguments[] = $this->getExpression($v, $fields_definition);
1054                }
1055            }
1056            if (method_exists($this->db->function, $element['data']['name'])) {
1057                $user_func = array(&$this->db->function, $element['data']['name']);
1058
1059                $str .= call_user_func_array($user_func, $arguments);
1060            } else {
1061                $str .= $element['data']['name'].'(';
1062                $str .= implode(', ', $arguments);
1063                $str .= ')';
1064            }
1065            break;
1066        case 'expression':
1067            $type0 = $type1 = null;
1068            if ($element['data']['operants'][0]['type'] == 'column'
1069                && array_key_exists($element['data']['operants'][0]['data'], $fields_definition)
1070            ) {
1071                $type0 = $fields_definition[$element['data']['operants'][0]['data']]['type'];
1072            }
1073
1074            if ($element['data']['operants'][1]['type'] == 'column'
1075                && array_key_exists($element['data']['operants'][1]['data'], $fields_definition)
1076            ) {
1077                $type1 = $fields_definition[$element['data']['operants'][1]['data']]['type'];
1078            }
1079
1080            $str .= '(';
1081            $str .= $this->getExpression($element['data']['operants'][0], $fields_definition, $type1);
1082            $str .= $this->getOperator($element['data']['operator']);
1083            $str .= $this->getExpression($element['data']['operants'][1], $fields_definition, $type0);
1084            $str .= ')';
1085            break;
1086        }
1087
1088        return $str;
1089    }
1090
1091    // }}}
1092    // {{{ getOperator()
1093
1094    /**
1095     * Returns the matching SQL operator
1096     *
1097     * @param string $op parsed descriptive operator
1098     *
1099     * @return string matching SQL operator
1100     *
1101     * @access public
1102     * @static
1103     * @see MDB2_Schema::getExpression()
1104     */
1105    function getOperator($op)
1106    {
1107        switch ($op) {
1108        case 'PLUS':
1109            return ' + ';
1110        case 'MINUS':
1111            return ' - ';
1112        case 'TIMES':
1113            return ' * ';
1114        case 'DIVIDED':
1115            return ' / ';
1116        case 'EQUAL':
1117            return ' = ';
1118        case 'NOT EQUAL':
1119            return ' != ';
1120        case 'LESS THAN':
1121            return ' < ';
1122        case 'GREATER THAN':
1123            return ' > ';
1124        case 'LESS THAN OR EQUAL':
1125            return ' <= ';
1126        case 'GREATER THAN OR EQUAL':
1127            return ' >= ';
1128        default:
1129            return ' '.$op.' ';
1130        }
1131    }
1132
1133    // }}}
1134    // {{{ getInstructionFields()
1135
1136    /**
1137     * Walks the parsed DML instruction array, field by field,
1138     * storing them and their processed values inside a new array.
1139     *
1140     * @param array $instruction       multi dimensional array that contains the
1141     *                                 structure of the current DML instruction.
1142     * @param array $fields_definition multi dimensional array that contains the
1143     *                                 definition for current table's fields
1144     *
1145     * @return array  array of strings in the form 'field_name' => 'value'
1146     *
1147     * @access public
1148     * @static
1149     * @see MDB2_Schema::initializeTable()
1150     */
1151    function getInstructionFields($instruction, $fields_definition = array())
1152    {
1153        $fields = array();
1154        if (!empty($instruction['field']) && is_array($instruction['field'])) {
1155            foreach ($instruction['field'] as $field) {
1156                $field_name = $this->db->quoteIdentifier($field['name'], true);
1157
1158                $fields[$field_name] = $this->getExpression($field['group'], $fields_definition);
1159            }
1160        }
1161        return $fields;
1162    }
1163
1164    // }}}
1165    // {{{ getInstructionWhere()
1166
1167    /**
1168     * Translates the parsed WHERE expression of a DML instruction
1169     * (array structure) to a SQL WHERE clause (string).
1170     *
1171     * @param array $instruction       multi dimensional array that contains the
1172     *                                 structure of the current DML instruction.
1173     * @param array $fields_definition multi dimensional array that contains the
1174     *                                 definition for current table's fields.
1175     *
1176     * @return string SQL WHERE clause
1177     *
1178     * @access public
1179     * @static
1180     * @see MDB2_Schema::initializeTable()
1181     */
1182    function getInstructionWhere($instruction, $fields_definition = array())
1183    {
1184        $where = '';
1185        if (!empty($instruction['where'])) {
1186            $where = 'WHERE '.$this->getExpression($instruction['where'], $fields_definition);
1187        }
1188        return $where;
1189    }
1190
1191    // }}}
1192    // {{{ createSequence()
1193
1194    /**
1195     * Create a sequence
1196     *
1197     * @param string $sequence_name name of the sequence to be created
1198     * @param array  $sequence      multi dimensional array that contains the
1199     *                              structure and optional data of the table
1200     * @param bool   $overwrite     if the sequence should be overwritten if it already exists
1201     *
1202     * @return bool|MDB2_Error MDB2_OK or error object
1203     * @access public
1204     */
1205    function createSequence($sequence_name, $sequence, $overwrite = false)
1206    {
1207        if (!$this->db->supports('sequences')) {
1208            $this->db->debug('Sequences are not supported', __FUNCTION__);
1209            return MDB2_OK;
1210        }
1211
1212        $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
1213        $this->db->expectError($errorcodes);
1214        $sequences = $this->db->manager->listSequences();
1215        $this->db->popExpect();
1216        if (PEAR::isError($sequences)) {
1217            if (!MDB2::isError($sequences, $errorcodes)) {
1218                return $sequences;
1219            }
1220        } elseif (is_array($sequence) && in_array($sequence_name, $sequences)) {
1221            if (!$overwrite) {
1222                $this->db->debug('Sequence already exists: '.$sequence_name, __FUNCTION__);
1223                return MDB2_OK;
1224            }
1225
1226            $result = $this->db->manager->dropSequence($sequence_name);
1227            if (PEAR::isError($result)) {
1228                return $result;
1229            }
1230            $this->db->debug('Overwritting sequence: '.$sequence_name, __FUNCTION__);
1231        }
1232
1233        $start = 1;
1234        $field = '';
1235        if (!empty($sequence['on'])) {
1236            $table = $sequence['on']['table'];
1237            $field = $sequence['on']['field'];
1238
1239            $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
1240            $this->db->expectError($errorcodes);
1241            $tables = $this->db->manager->listTables();
1242            $this->db->popExpect();
1243            if (PEAR::isError($tables) && !MDB2::isError($tables, $errorcodes)) {
1244                 return $tables;
1245            }
1246
1247            if (!PEAR::isError($tables) && is_array($tables) && in_array($table, $tables)) {
1248                if ($this->db->supports('summary_functions')) {
1249                    $query = "SELECT MAX($field) FROM ".$this->db->quoteIdentifier($table, true);
1250                } else {
1251                    $query = "SELECT $field FROM ".$this->db->quoteIdentifier($table, true)." ORDER BY $field DESC";
1252                }
1253                $start = $this->db->queryOne($query, 'integer');
1254                if (PEAR::isError($start)) {
1255                    return $start;
1256                }
1257                ++$start;
1258            } else {
1259                $this->warnings[] = 'Could not sync sequence: '.$sequence_name;
1260            }
1261        } elseif (!empty($sequence['start']) && is_numeric($sequence['start'])) {
1262            $start = $sequence['start'];
1263            $table = '';
1264        }
1265
1266        $result = $this->db->manager->createSequence($sequence_name, $start);
1267        if (PEAR::isError($result)) {
1268            return $result;
1269        }
1270
1271        return MDB2_OK;
1272    }
1273
1274    // }}}
1275    // {{{ createDatabase()
1276
1277    /**
1278     * Create a database space within which may be created database objects
1279     * like tables, indexes and sequences. The implementation of this function
1280     * is highly DBMS specific and may require special permissions to run
1281     * successfully. Consult the documentation or the DBMS drivers that you
1282     * use to be aware of eventual configuration requirements.
1283     *
1284     * @param array $database_definition multi dimensional array that contains the current definition
1285     * @param array $options             an array of options to be passed to the 
1286     *                                   database specific driver version of
1287     *                                   MDB2_Driver_Manager_Common::createTable().
1288     *
1289     * @return bool|MDB2_Error MDB2_OK or error object
1290     * @access public
1291     */
1292    function createDatabase($database_definition, $options = array())
1293    {
1294        if (!isset($database_definition['name']) || !$database_definition['name']) {
1295            return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
1296                'no valid database name specified');
1297        }
1298
1299        $create    = (isset($database_definition['create']) && $database_definition['create']);
1300        $overwrite = (isset($database_definition['overwrite']) && $database_definition['overwrite']);
1301
1302        /**
1303         *
1304         * We need to clean up database name before any query to prevent
1305         * database driver from using a inexistent database
1306         *
1307         */
1308        $previous_database_name = $this->db->setDatabase('');
1309
1310        // Lower / Upper case the db name if the portability deems so.
1311        if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
1312            $func = $this->db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper';
1313
1314            $db_name = $func($database_definition['name']);
1315        } else {
1316            $db_name = $database_definition['name'];
1317        }
1318
1319        if ($create) {
1320
1321            $dbExists = $this->db->databaseExists($db_name);
1322            if (PEAR::isError($dbExists)) {
1323                return $dbExists;
1324            }
1325
1326            if ($dbExists && $overwrite) {
1327                $this->db->expectError(MDB2_ERROR_CANNOT_DROP);
1328                $result = $this->db->manager->dropDatabase($db_name);
1329                $this->db->popExpect();
1330                if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_CANNOT_DROP)) {
1331                    return $result;
1332                }
1333                $dbExists = false;
1334                $this->db->debug('Overwritting database: ' . $db_name, __FUNCTION__);
1335            }
1336
1337            $dbOptions = array();
1338            if (array_key_exists('charset', $database_definition)
1339                && !empty($database_definition['charset'])) {
1340                $dbOptions['charset'] = $database_definition['charset'];
1341            }
1342
1343            if ($dbExists) {
1344                $this->db->debug('Database already exists: ' . $db_name, __…

Large files files are truncated, but you can click here to view the full file