MDB2
[ class tree: MDB2 ] [ index: MDB2 ] [ all elements ]

Source for file Manager.php

Documentation is available at Manager.php

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP Version 4                                                        |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox,                 |
  6. // | Stig. S. Bakken, Lukas Smith                                         |
  7. // | All rights reserved.                                                 |
  8. // +----------------------------------------------------------------------+
  9. // | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB  |
  10. // | API as well as database abstraction for PHP applications.            |
  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 nor the names of his contributors may be used to endorse |
  26. // | or promote products derived from this software without specific prior|
  27. // | 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@backendmedia.com>                         |
  43. // +----------------------------------------------------------------------+
  44. //
  45. // $Id: Manager.php,v 1.10 2004/04/09 10:39:08 lsmith Exp $
  46. //
  47.  
  48. require_once 'MDB2/Tools/Parser.php';
  49.  
  50. define('MDB2_MANAGER_DUMP_ALL',          0);
  51. define('MDB2_MANAGER_DUMP_STRUCTURE',    1);
  52. define('MDB2_MANAGER_DUMP_CONTENT',      2);
  53.  
  54. /**
  55.  * The database manager is a class that provides a set of database
  56.  * management services like installing, altering and dumping the data
  57.  * structures of databases.
  58.  *
  59.  * @package MDB2
  60.  * @category Database
  61.  * @author  Lukas Smith <smith@backendmedia.com>
  62.  */
  63. class MDB2_Tools_Manager extends PEAR
  64. {
  65.     // {{{ properties
  66.  
  67.     var $db;
  68.  
  69.     var $warnings = array();
  70.  
  71.     var $options = array(
  72.         'fail_on_invalid_names' => 1,
  73.     );
  74.  
  75.     var $invalid_names = array(
  76.         'user' => array(),
  77.         'is' => array(),
  78.         'file' => array(
  79.             'oci' => array(),
  80.             'oracle' => array()
  81.         ),
  82.         'notify' => array(
  83.             'pgsql' => array()
  84.         ),
  85.         'restrict' => array(
  86.             'mysql' => array()
  87.         ),
  88.         'password' => array(
  89.             'ibase' => array()
  90.         )
  91.     );
  92.  
  93.     var $default_values = array(
  94.         'integer' => 0,
  95.         'float' => 0,
  96.         'decimal' => 0,
  97.         'text' => '',
  98.         'timestamp' => '0001-01-01 00:00:00',
  99.         'date' => '0001-01-01',
  100.         'time' => '00:00:00'
  101.     );
  102.  
  103.     var $database_definition = array(
  104.         'name' => '',
  105.         'create' => 0,
  106.         'tables' => array()
  107.     );
  108.  
  109.     // }}}
  110.     // {{{ raiseError()
  111.  
  112.     /**
  113.      * This method is used to communicate an error and invoke error
  114.      * callbacks etc.  Basically a wrapper for PEAR::raiseError
  115.      * without the message string.
  116.      *
  117.      * @param mixed $code integer error code, or a PEAR error object (all
  118.      *       other parameters are ignored if this parameter is an object
  119.      * @param int $mode error mode, see PEAR_Error docs
  120.      * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the
  121.      *       error level (E_USER_NOTICE etc).  If error mode is
  122.      *       PEAR_ERROR_CALLBACK, this is the callback function, either as a
  123.      *       function name, or as an array of an object and method name. For
  124.      *       other error modes this parameter is ignored.
  125.      * @param string $userinfo Extra debug information.  Defaults to the last
  126.      *       query and native error code.
  127.      * @param mixed $nativecode Native error code, integer or string depending
  128.      *       the backend.
  129.      * @return object a PEAR error object
  130.      * @access public
  131.      * @see PEAR_Error
  132.      */
  133.     function &raiseError($code = null, $mode = null, $options = null,
  134.                          $userinfo = null)
  135.     {
  136.         $error =& MDB2_Driver_Common::raiseError($code, $mode, $options, $userinfo);
  137.         return $error;
  138.     }
  139.  
  140.     // }}}
  141.     // {{{ debugOutput()
  142.  
  143.     /**
  144.      * output debug info
  145.      *
  146.      * @return string content of the debug_output class variable
  147.      * @access public
  148.      */
  149.     function debugOutput()
  150.     {
  151.         return $this->db->debugOutput();
  152.     }
  153.  
  154.     // }}}
  155.     // {{{ resetWarnings()
  156.  
  157.     /**
  158.      * reset the warning array
  159.      *
  160.      * @access public
  161.      */
  162.     function resetWarnings()
  163.     {
  164.         $this->warnings = array();
  165.     }
  166.  
  167.     // }}}
  168.     // {{{ getWarnings()
  169.  
  170.     /**
  171.      * get all warnings in reverse order.
  172.      * This means that the last warning is the first element in the array
  173.      *
  174.      * @return array with warnings
  175.      * @access public
  176.      * @see resetWarnings()
  177.      */
  178.     function getWarnings()
  179.     {
  180.         return array_reverse($this->warnings);
  181.     }
  182.  
  183.     // }}}
  184.     // {{{ setOption()
  185.  
  186.     /**
  187.      * set the option for the db class
  188.      *
  189.      * @param string $option option name
  190.      * @param mixed $value value for the option
  191.      * @return mixed MDB2_OK or MDB2 Error Object
  192.      * @access public
  193.      */
  194.     function setOption($option, $value)
  195.     {
  196.         if (isset($this->options[$option])) {
  197.             if (is_null($value)) {
  198.                 return $this->raiseError(MDB2_ERROR, null, null,
  199.                     'may not set an option to value null');
  200.             }
  201.             $this->options[$option] = $value;
  202.             return MDB2_OK;
  203.         }
  204.         return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  205.             "unknown option $option");
  206.     }
  207.  
  208.     // }}}
  209.     // {{{ getOption()
  210.  
  211.     /**
  212.      * returns the value of an option
  213.      *
  214.      * @param string $option option name
  215.      * @return mixed the option value or error object
  216.      * @access public
  217.      */
  218.     function getOption($option)
  219.     {
  220.         if (isset($this->options[$option])) {
  221.             return $this->options[$option];
  222.         }
  223.         return $this->raiseError(MDB2_ERROR_UNSUPPORTED,
  224.             null, null, "unknown option $option");
  225.     }
  226.  
  227.     // }}}
  228.     // {{{ connect()
  229.  
  230.     /**
  231.      * Create a new MDB2 connection object and connect to the specified
  232.      * database
  233.      *
  234.      * @param   mixed   $dbinfo   'data source name', see the MDB2::parseDSN
  235.      *                             method for a description of the dsn format.
  236.      *                             Can also be specified as an array of the
  237.      *                             format returned by MDB2::parseDSN.
  238.      *                             Finally you can also pass an existing db
  239.      *                             object to be used.
  240.      * @param   mixed   $options  An associative array of option names and
  241.      *                             their values.
  242.      * @return  mixed MDB2_OK on success, or a MDB2 error object
  243.      * @access  public
  244.      * @see     MDB2::parseDSN
  245.      */
  246.     function connect(&$dbinfo, $options = false)
  247.     {
  248.         if (is_object($this->db) && !MDB2::isError($this->db)) {
  249.             $this->disconnect();
  250.         }
  251.         if (is_object($dbinfo)) {
  252.              $this->db =& $dbinfo;
  253.         } else {
  254.             $this->db =& MDB2::connect($dbinfo, $options);
  255.             if (MDB2::isError($this->db)) {
  256.                 return $this->db;
  257.             }
  258.         }
  259.         if (is_array($options)) {
  260.             $this->options = array_merge($options, $this->options);
  261.         }
  262.         $this->db->loadModule('manager');
  263.         return MDB2_OK;
  264.     }
  265.  
  266.     // }}}
  267.     // {{{ disconnect()
  268.  
  269.     /**
  270.      * Log out and disconnect from the database.
  271.      *
  272.      * @access public
  273.      */
  274.     function disconnect()
  275.     {
  276.         if (MDB2::isConnection($this->db) && !MDB2::isError($this->db)) {
  277.             $this->db->disconnect();
  278.             unset($this->db);
  279.         }
  280.     }
  281.  
  282.     // }}}
  283.     // {{{ setDatabase()
  284.  
  285.     /**
  286.      * Select a different database
  287.      *
  288.      * @param string $name name of the database that should be selected
  289.      * @return string name of the database previously connected to
  290.      * @access public
  291.      */
  292.     function setDatabase($name)
  293.     {
  294.         return $this->db->setDatabase($name);
  295.     }
  296.  
  297.     // }}}
  298.     // {{{ _createTable()
  299.  
  300.     /**
  301.      * create a table and inititialize the table if data is available
  302.      *
  303.      * @param string $table_name  name of the table to be created
  304.      * @param array  $table       multi dimensional array that containts the
  305.      *                             structure and optional data of the table
  306.      * @param boolean $overwrite  determine if the table/index should be
  307.                                   overwritten if it already exists
  308.      * @return mixed MDB2_OK on success, or a MDB2 error object
  309.      * @access private
  310.      */
  311.     function _createTable($table_name, $table, $overwrite = false)
  312.     {
  313.         $this->expectError(MDB2_ERROR_ALREADY_EXISTS);
  314.         $result = $this->db->manager->createTable($table_name, $table['fields']);
  315.         $this->popExpect();
  316.         if (MDB2::isError($result)) {
  317.             if ($result->getCode() === MDB2_ERROR_ALREADY_EXISTS) {
  318.                 $this->warnings[] = 'Table already exists: '.$table_name;
  319.                 if ($overwrite) {
  320.                     $this->db->debug('Overwritting Table');
  321.                     $result = $this->db->manager->dropTable($table_name);
  322.                     if (MDB2::isError($result)) {
  323.                         return $result;
  324.                     }
  325.                     $result = $this->db->manager->createTable($table_name, $table['fields']);
  326.                     if (MDB2::isError($result)) {
  327.                         return $result;
  328.                     }
  329.                 } else {
  330.                     $result = MDB2_OK;
  331.                 }
  332.             } else {
  333.                 $this->db->debug('Create table error: '.$table_name);
  334.                 return $result;
  335.             }
  336.         }
  337.         if (isset($table['initialization']) && is_array($table['initialization'])) {
  338.             foreach ($table['initialization'] as $instruction) {
  339.                 switch ($instruction['type']) {
  340.                     case 'insert':
  341.                         $query_fields = $query_values = array();
  342.                         if (isset($instruction['fields']) && is_array($instruction['fields'])) {
  343.                             foreach ($instruction['fields'] as $field_name => $field) {
  344.                                 $query_fields[] = $field_name;
  345.                                 $query_values[] = '?';
  346.                                 $query_types[] = $table['fields'][$field_name]['type'];
  347.                             }
  348.                             $query_fields = implode(',',$query_fields);
  349.                             $query_values = implode(',',$query_values);
  350.                             $result = $prepared_query = $this->db->prepare(
  351.                                 "INSERT INTO $table_name ($query_fields) VALUES ($query_values)", $query_types);
  352.                         }
  353.                         if (!MDB2::isError($prepared_query)) {
  354.                             if (isset($instruction['fields']) && is_array($instruction['fields'])) {
  355.                                 $lobs = array();
  356.                                 $field_number = 0;
  357.                                 foreach ($instruction['fields'] as $field_name => $field) {
  358.                                     $field_number++;
  359.                                     $query = $field_name;
  360.                                     switch ($table['fields'][$field_name]['type']) {
  361.                                         case 'integer':
  362.                                         case 'text':
  363.                                         case 'boolean':
  364.                                         case 'date':
  365.                                         case 'timestamp':
  366.                                         case 'time':
  367.                                         case 'float':
  368.                                         case 'decimal':
  369.                                             $result = $this->db->setParam($prepared_query,
  370.                                                 $field_number, $field);
  371.                                             break;
  372.                                         case 'clob':
  373.                                             $lob_definition = array(
  374.                                                 'database' => $this->db,
  375.                                                 'error' => '',
  376.                                                 'data' => $field,
  377.                                                 'field_name' => $field_name
  378.                                             );
  379.                                             $result = $this->db->setParam($prepared_query,
  380.                                                 $field_number, $lob_definition, 'clob');
  381.                                             break;
  382.                                         case 'blob':
  383.                                             $lob_definition = array(
  384.                                                 'database' => $this->db,
  385.                                                 'error' => '',
  386.                                                 'data' => $field,
  387.                                                 'field_name' => $field_name
  388.                                             );
  389.                                             $result = $this->db->setParam($prepared_query,
  390.                                                 $field_number, $lob_definition, 'blob');
  391.                                             break;
  392.                                         default:
  393.                                             $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  394.                                                 'type "'.$field['type'].'" is not yet supported');
  395.                                             break;
  396.                                     }
  397.                                     if (MDB2::isError($result)) {
  398.                                         break;
  399.                                     }
  400.                                 }
  401.                             }
  402.                             if (!MDB2::isError($result)) {
  403.                                 $result = $this->db->execute($prepared_query);
  404.                             }
  405.                             for ($lob = 0; $lob < count($lobs); $lob++) {
  406.                                 $this->db->destroyLOB($lobs[$lob]);
  407.                             }
  408.                             $this->db->freePrepared($prepared_query);
  409.                         }
  410.                         break;
  411.                 }
  412.             }
  413.         };
  414.         if (!MDB2::isError($result) && isset($table['indexes']) && is_array($table['indexes'])) {
  415.             if (!$this->db->supports('indexes')) {
  416.                 return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  417.                     'indexes are not supported');
  418.             }
  419.             foreach ($table['indexes'] as $index_name => $index) {
  420.                 $this->expectError(MDB2_ERROR_ALREADY_EXISTS);
  421.                 $result = $this->db->manager->createIndex($table_name, $index_name, $index);
  422.                 $this->popExpect();
  423.                 if (MDB2::isError($result)) {
  424.                     if ($result->getCode() === MDB2_ERROR_ALREADY_EXISTS) {
  425.                         $this->warnings[] = 'Index already exists: '.$index_name;
  426.                         if ($overwrite) {
  427.                             $this->db->debug('Overwritting Index');
  428.                             $result = $this->db->manager->dropIndex($table_name, $index_name);
  429.                             if (MDB2::isError($result)) {
  430.                                 break;
  431.                             }
  432.                             $result = $this->db->manager->createIndex($table_name, $index_name, $index);
  433.                             if (MDB2::isError($result)) {
  434.                                 break;
  435.                             }
  436.                         } else {
  437.                             $result = MDB2_OK;
  438.                         }
  439.                     } else {
  440.                         $this->db->debug('Create index error: '.$table_name);
  441.                         break;
  442.                     }
  443.                 }
  444.             }
  445.         }
  446.         if (MDB2::isError($result)) {
  447.             $result = $this->db->manager->dropTable($table_name);
  448.             if (MDB2::isError($result)) {
  449.                 $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  450.                     'could not drop the table ('.$result->getMessage().
  451.                     ' ('.$result->getUserinfo().'))');
  452.             }
  453.             return $result;
  454.         }
  455.         return MDB2_OK;
  456.     }
  457.  
  458.     // }}}
  459.     // {{{ _dropTable()
  460.  
  461.     /**
  462.      * drop a table
  463.      *
  464.      * @param string $table_name    name of the table to be dropped
  465.      * @return mixed MDB2_OK on success, or a MDB2 error object
  466.      * @access private
  467.      */
  468.     function _dropTable($table_name)
  469.     {
  470.         return $this->db->manager->dropTable($table_name);
  471.     }
  472.  
  473.     // }}}
  474.     // {{{ _createSequence()
  475.  
  476.     /**
  477.      * create a sequence
  478.      *
  479.      * @param string $sequence_name  name of the sequence to be created
  480.      * @param array  $sequence       multi dimensional array that containts the
  481.      *                                structure and optional data of the table
  482.      * @param string $created_on_table 
  483.      * @param boolean $overwrite    determine if the sequence should be overwritten
  484.                                     if it already exists
  485.      * @return mixed MDB2_OK on success, or a MDB2 error object
  486.      * @access private
  487.      */
  488.     function _createSequence($sequence_name, $sequence, $created_on_table, $overwrite = false)
  489.     {
  490.         if (!$this->db->supports('sequences')) {
  491.             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  492.                 'sequences are not supported');
  493.         }
  494.         if (!isset($sequence_name) || !strcmp($sequence_name, '')) {
  495.             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  496.                 'no valid sequence name specified');
  497.         }
  498.         $this->db->debug('Create sequence: '.$sequence_name);
  499.         if (isset($sequence['start']) && is_int($sequence['start'])) {
  500.             $start = $sequence['start'];
  501.         } else if (isset($sequence['on']) && !$created_on_table) {
  502.             $table = $sequence['on']['table'];
  503.             $field = $sequence['on']['field'];
  504.             if ($this->db->supports('summary_functions')) {
  505.                 $field = "MAX($field)";
  506.             }
  507.             $start = $this->db->queryOne("SELECT $field FROM $table", 'integer');
  508.             if (MDB2::isError($start)) {
  509.                 return $start;
  510.             }
  511.             if (!is_int($start)) {
  512.                 $start = 1;
  513.             }
  514.         } else {
  515.             $start = 1;
  516.         }
  517.  
  518.         $this->expectError(MDB2_ERROR_ALREADY_EXISTS);
  519.         $result = $this->db->manager->createSequence($sequence_name, $start);
  520.         $this->popExpect();
  521.         if (MDB2::isError($result)) {
  522.             if ($result->getCode() === MDB2_ERROR_ALREADY_EXISTS) {
  523.                 $this->warnings[] = 'Sequence already exists: '.$sequence_name;
  524.                 if ($overwrite) {
  525.                     $this->db->debug('Overwritting Sequence');
  526.                     $result = $this->db->manager->dropSequence($sequence_name);
  527.                     if (MDB2::isError($result)) {
  528.                         return $result;
  529.                     }
  530.                     $result = $this->db->manager->createSequence($sequence_name, $start);
  531.                     if (MDB2::isError($result)) {
  532.                         return $result;
  533.                     }
  534.                 } else {
  535.                     return MDB2_OK;
  536.                 }
  537.             } else {
  538.                 $this->db->debug('Create sequence error: '.$sequence_name);
  539.                 return $result;
  540.             }
  541.         }
  542.     }
  543.  
  544.     // }}}
  545.     // {{{ _dropSequence()
  546.  
  547.     /**
  548.      * drop a table
  549.      *
  550.      * @param string $sequence_name    name of the sequence to be dropped
  551.      * @return mixed MDB2_OK on success, or a MDB2 error object
  552.      * @access private
  553.      */
  554.     function _dropSequence($sequence_name)
  555.     {
  556.         if (!$this->db->supports('sequences')) {
  557.             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  558.                 'sequences are not supported');
  559.         }
  560.         $this->db->debug('Dropping sequence: '.$sequence_name);
  561.         if (!isset($sequence_name) || !strcmp($sequence_name, '')) {
  562.             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  563.                 'no valid sequence name specified');
  564.         }
  565.         return $this->db->manager->dropSequence($sequence_name);
  566.     }
  567.  
  568.     // }}}
  569.     // {{{ _createDatabase()
  570.  
  571.     /**
  572.      * Create a database space within which may be created database objects
  573.      * like tables, indexes and sequences. The implementation of this function
  574.      * is highly DBMS specific and may require special permissions to run
  575.      * successfully. Consult the documentation or the DBMS drivers that you
  576.      * use to be aware of eventual configuration requirements.
  577.      *
  578.      * @return mixed MDB2_OK on success, or a MDB2 error object
  579.      * @access private
  580.      */
  581.     function _createDatabase()
  582.     {
  583.         if (!isset($this->database_definition['name'])
  584.             || !strcmp($this->database_definition['name'], '')
  585.         ) {
  586.             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  587.                 'no valid database name specified');
  588.         }
  589.         $create = (isset($this->database_definition['create']) && $this->database_definition['create']);
  590.         $overwrite = (isset($this->database_definition['overwrite']) && $this->database_definition['overwrite']);
  591.         if ($create) {
  592.             $this->db->debug('Create database: '.$this->database_definition['name']);
  593.             $this->expectError(MDB2_ERROR_ALREADY_EXISTS);
  594.             $result = $this->db->manager->createDatabase($this->database_definition['name']);
  595.             $this->popExpect();
  596.             if (MDB2::isError($result)) {
  597.                 if ($result->getCode() === MDB2_ERROR_ALREADY_EXISTS) {
  598.                     $this->warnings[] = 'Database already exists: '.$this->database_definition['name'];
  599.                     if ($overwrite) {
  600.                         $this->db->debug('Overwritting Database');
  601.                         $result = $this->db->manager->dropDatabase($this->database_definition['name']);
  602.                         if (MDB2::isError($result)) {
  603.                             return $result;
  604.                         }
  605.                         $result = $this->db->manager->createDatabase($this->database_definition['name']);
  606.                         if (MDB2::isError($result)) {
  607.                             return $result;
  608.                         }
  609.                     } else {
  610.                         $result = MDB2_OK;
  611.                     }
  612.                 } else {
  613.                     $this->db->debug('Create database error.');
  614.                     return $result;
  615.                 }
  616.             }
  617.         }
  618.         $previous_database_name = $this->db->setDatabase($this->database_definition['name']);
  619.         if (($support_transactions = $this->db->supports('transactions'))
  620.             && MDB2::isError($result = $this->db->autoCommit(false))
  621.         ) {
  622.             return $result;
  623.         }
  624.  
  625.         $created_objects = 0;
  626.         if (isset($this->database_definition['tables'])
  627.             && is_array($this->database_definition['tables'])
  628.         ) {
  629.             foreach ($this->database_definition['tables'] as $table_name => $table) {
  630.                 $result = $this->_createTable($table_name, $table, $overwrite);
  631.                 if (MDB2::isError($result)) {
  632.                     break;
  633.                 }
  634.                 $created_objects++;
  635.             }
  636.         }
  637.         if (!MDB2::isError($result)
  638.             && isset($this->database_definition['sequences'])
  639.             && is_array($this->database_definition['sequences'])
  640.         ) {
  641.             foreach ($this->database_definition['sequences'] as $sequence_name => $sequence) {
  642.                 $result = $this->_createSequence($sequence_name, $sequence, 0, $overwrite);
  643.  
  644.                 if (MDB2::isError($result)) {
  645.                     break;
  646.                 }
  647.                 $created_objects++;
  648.             }
  649.         }
  650.  
  651.         if (MDB2::isError($result)) {
  652.             if ($created_objects) {
  653.                 if ($support_transactions) {
  654.                     $res = $this->db->rollback();
  655.                     if (MDB2::isError($res))
  656.                         $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  657.                             'Could not rollback the partially created database alterations ('.
  658.                             $result->getMessage().' ('.$result->getUserinfo().'))');
  659.                 } else {
  660.                     $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  661.                         'the database was only partially created ('.
  662.                         $result->getMessage().' ('.$result->getUserinfo().'))');
  663.                 }
  664.             }
  665.         } else {
  666.             if ($support_transactions) {
  667.                 $res = $this->db->autoCommit(true);
  668.                 if (MDB2::isError($res))
  669.                     $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  670.                         'Could not end transaction after successfully created the database ('.
  671.                         $res->getMessage().' ('.$res->getUserinfo().'))');
  672.             }
  673.         }
  674.  
  675.         $this->db->setDatabase($previous_database_name);
  676.  
  677.         if (MDB2::isError($result) && $create
  678.             && MDB2::isError($res = $this->db->manager->dropDatabase($this->database_definition['name']))
  679.         ) {
  680.             return $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  681.                 'Could not drop the created database after unsuccessful creation attempt ('.
  682.                 $res->getMessage().' ('.$res->getUserinfo().'))');
  683.         }
  684.  
  685.         if (MDB2::isError($result)) {
  686.             return $result;
  687.         }
  688.  
  689.         return MDB2_OK;
  690.     }
  691.  
  692.     // }}}
  693.     // {{{ _addDefinitionChange()
  694.  
  695.     /**
  696.      * add change to an array of multiple changes
  697.      *
  698.      * @param array  &$changes 
  699.      * @param string $definition 
  700.      * @param string $item 
  701.      * @param array  $change 
  702.      * @return mixed MDB2_OK on success, or a MDB2 error object
  703.      * @access private
  704.      */
  705.     function _addDefinitionChange(&$changes, $definition, $item, $change)
  706.     {
  707.         if (!isset($changes[$definition][$item])) {
  708.             $changes[$definition][$item] = array();
  709.         }
  710.         foreach ($change as $change_data_name => $change_data) {
  711.             if (isset($change_data) && is_array($change_data)) {
  712.                 if (!isset($changes[$definition][$item][$change_data_name])) {
  713.                     $changes[$definition][$item][$change_data_name] = array();
  714.                 }
  715.                 foreach ($change_data as $change_part_name => $change_part) {
  716.                     $changes[$definition][$item][$change_data_name][$change_part_name] = $change_part;
  717.                 }
  718.             } else {
  719.                 $changes[$definition][$item][$change_data_name] = $change_data;
  720.             }
  721.         }
  722.         return MDB2_OK;
  723.     }
  724.  
  725.     // }}}
  726.     // {{{ _compareDefinitions()
  727.  
  728.     /**
  729.      * compare a previous definition with the currenlty parsed definition
  730.      *
  731.      * @param array multi dimensional array that contains the previous definition
  732.      * @return mixed array of changes on success, or a MDB2 error object
  733.      * @access private
  734.      */
  735.     function _compareDefinitions($previous_definition)
  736.     {
  737.         $defined_tables = $changes = array();
  738.         if (isset($this->database_definition['tables']) && is_array($this->database_definition['tables'])) {
  739.             foreach ($this->database_definition['tables'] as $table_name => $table) {
  740.                 $was_table_name = $table['was'];
  741.                 if (isset($previous_definition['tables'][$table_name])
  742.                     && isset($previous_definition['tables'][$table_name]['was'])
  743.                     && !strcmp($previous_definition['tables'][$table_name]['was'], $was_table_name)
  744.                 ) {
  745.                     $was_table_name = $table_name;
  746.                 }
  747.                 if (isset($previous_definition['tables'][$was_table_name])) {
  748.                     if (strcmp($was_table_name, $table_name)) {
  749.                         $this->_addDefinitionChange($changes, 'tables', $was_table_name, array('name' => $table_name));
  750.                         $this->db->debug("Renamed table '$was_table_name' to '$table_name'");
  751.                     }
  752.                     if (isset($defined_tables[$was_table_name])) {
  753.                         return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  754.                             'the table "'.$was_table_name.
  755.                             '" was specified as base of more than of table of the database');
  756.                     }
  757.                     $defined_tables[$was_table_name] = true;
  758.  
  759.                     $previous_fields = $previous_definition['tables'][$was_table_name]['fields'];
  760.                     $defined_fields = array();
  761.                     if (isset($table['fields']) && is_array($table['fields'])) {
  762.                         foreach ($table['fields'] as $field_name => $field) {
  763.                             $was_field_name = $field['was'];
  764.                             if (isset($previous_fields[$field_name])
  765.                                 && isset($previous_fields[$field_name]['was'])
  766.                                 && !strcmp($previous_fields[$field_name]['was'], $was_field_name)
  767.                             ) {
  768.                                 $was_field_name = $field_name;
  769.                             }
  770.                             if (isset($previous_fields[$was_field_name])) {
  771.                                 if (strcmp($was_field_name, $field_name)) {
  772.                                     $query = $this->db->getDeclaration($field['type'], $field_name, $field);
  773.                                     if (MDB2::isError($query)) {
  774.                                         return $query;
  775.                                     }
  776.                                     $this->_addDefinitionChange($changes, 'tables',
  777.                                         $was_table_name,
  778.                                         array(
  779.                                             'renamed_fields' => array(
  780.                                                 $was_field_name => array(
  781.                                                     'name' => $field_name,
  782.                                                     'declaration' => $query
  783.                                                 )
  784.                                             )
  785.                                         )
  786.                                     );
  787.                                     $this->db->debug("Renamed field '$was_field_name' to '$field_name' in table '$table_name'");
  788.                                 }
  789.                                 if (isset($defined_fields[$was_field_name])) {
  790.                                     return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  791.                                         'the field "'.$was_table_name.
  792.                                         '" was specified as base of more than one field of table');
  793.                                 }
  794.                                 $defined_fields[$was_field_name] = true;
  795.                                 $change = array();
  796.                                 if ($field['type'] == $previous_fields[$was_field_name]['type']) {
  797.                                     switch ($field['type']) {
  798.                                         case 'integer':
  799.                                             $previous_unsigned = isset($previous_fields[$was_field_name]['unsigned']);
  800.                                             $unsigned = isset($fields[$field_name]['unsigned']);
  801.                                             if (strcmp($previous_unsigned, $unsigned)) {
  802.                                                 $change['unsigned'] = $unsigned;
  803.                                                 $this->db->debug("Changed field '$field_name' type from '".($previous_unsigned ? 'unsigned ' : '').$previous_fields[$was_field_name]['type']."' to '".($unsigned ? 'unsigned ' : '').$field['type']."' in table '$table_name'");
  804.                                             }
  805.                                             break;
  806.                                         case 'text':
  807.                                         case 'clob':
  808.                                         case 'blob':
  809.                                             $previous_length = (isset($previous_fields[$was_field_name]['length']) ? $previous_fields[$was_field_name]['length'] : 0);
  810.                                             $length = (isset($field['length']) ? $field['length'] : 0);
  811.                                             if (strcmp($previous_length, $length)) {
  812.                                                 $change['length'] = $length;
  813.                                                 $this->db->debug("Changed field '$field_name' length from '".$previous_fields[$was_field_name]['type'].($previous_length == 0 ? ' no length' : "($previous_length)")."' to '".$field['type'].($length == 0 ? ' no length' : "($length)")."' in table '$table_name'");
  814.                                             }
  815.                                             break;
  816.                                         case 'date':
  817.                                         case 'timestamp':
  818.                                         case 'time':
  819.                                         case 'boolean':
  820.                                         case 'float':
  821.                                         case 'decimal':
  822.                                             break;
  823.                                         default:
  824.                                             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  825.                                                 'type "'.$field['type'].'" is not yet supported');
  826.                                     }
  827.  
  828.                                     $previous_notnull = isset($previous_fields[$was_field_name]['notnull']);
  829.                                     $notnull = isset($field['notnull']);
  830.                                     if ($previous_notnull != $notnull) {
  831.                                         $change['changed_not_null'] = true;
  832.                                         if ($notnull) {
  833.                                             $change['notnull'] = isset($field['notnull']);
  834.                                         }
  835.                                         $this->db->debug("Changed field '$field_name' notnull from $previous_notnull to $notnull in table '$table_name'");
  836.                                     }
  837.  
  838.                                     $previous_default = isset($previous_fields[$was_field_name]['default']);
  839.                                     $default = isset($field['default']);
  840.                                     if (strcmp($previous_default, $default)) {
  841.                                         $change['changed_default'] = true;
  842.                                         if ($default) {
  843.                                             $change['default'] = $field['default'];
  844.                                         }
  845.                                         $this->db->debug("Changed field '$field_name' default from ".($previous_default ? "'".$previous_fields[$was_field_name]['default']."'" : 'NULL').' TO '.($default ? "'".$fields[$field_name]['default']."'" : 'NULL')." IN TABLE '$table_name'");
  846.                                     } else {
  847.                                         if ($default
  848.                                             && strcmp($previous_fields[$was_field_name]['default'], $field['default'])
  849.                                         ) {
  850.                                             $change['changed_default'] = true;
  851.                                             $change['default'] = $field['default'];
  852.                                             $this->db->debug("Changed field '$field_name' default from '".$previous_fields[$was_field_name]['default']."' to '".$fields[$field_name]['default']."' in table '$table_name'");
  853.                                         }
  854.                                     }
  855.                                 } else {
  856.                                     $change['type'] = $field['type'];
  857.                                     $this->db->debug("Changed field '$field_name' type from '".$previous_fields[$was_field_name]['type']."' to '".$fields[$field_name]['type']."' in table '$table_name'");
  858.                                 }
  859.                                 if (count($change)) {
  860.                                     $query = $this->db->getDeclaration($field['type'], $field_name, $field);
  861.                                     if (MDB2::isError($query)) {
  862.                                         return $query;
  863.                                     }
  864.                                     $change['declaration'] = $query;
  865.                                     $change['definition'] = $field;
  866.                                     $this->_addDefinitionChange($changes, 'tables', $was_table_name, array('changed_fields' => array($field_name => $change)));
  867.                                 }
  868.                             } else {
  869.                                 if (strcmp($field_name, $was_field_name)) {
  870.                                     return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  871.                                         'it was specified a previous field name ("'.
  872.                                         $was_field_name.'") for field "'.$field_name.'" of table "'.
  873.                                         $table_name.'" that does not exist');
  874.                                 }
  875.                                 $query = $this->db->getDeclaration($field['type'], $field_name, $field);
  876.                                 if (MDB2::isError($query)) {
  877.                                     return $query;
  878.                                 }
  879.                                 $change['declaration'] = $query;
  880.                                 $this->_addDefinitionChange($changes, 'tables', $table_name, array('added_fields' => array($field_name => $change)));
  881.                                 $this->db->debug("Added field '$field_name' to table '$table_name'");
  882.                             }
  883.                         }
  884.                     }
  885.                     if (isset($previous_fields) && is_array($previous_fields)) {
  886.                         foreach ($previous_fields as $field_previous_name => $field_previous) {
  887.                             if (!isset($defined_fields[$field_previous_name])) {
  888.                                 $this->_addDefinitionChange($changes, 'tables', $table_name, array('removed_fields' => array($field_previous_name => array())));
  889.                                 $this->db->debug("Removed field '$field_name' from table '$table_name'");
  890.                             }
  891.                         }
  892.                     }
  893.                     $indexes = array();
  894.                     if (isset($this->database_definition['tables'][$table_name]['indexes'])
  895.                         && is_array($this->database_definition['tables'][$table_name]['indexes'])
  896.                     ) {
  897.                         $indexes = $this->database_definition['tables'][$table_name]['indexes'];
  898.                     }
  899.                     $previous_indexes = array();
  900.                     if (isset($previous_definition['tables'][$was_table_name]['indexes'])
  901.                         && is_array($previous_definition['tables'][$was_table_name]['indexes'])
  902.                     ) {
  903.                         $previous_indexes = $previous_definition['tables'][$was_table_name]['indexes'];
  904.                     }
  905.                     $defined_indexes = array();
  906.                     foreach ($indexes as $index_name => $index) {
  907.                         $was_index_name = $index['was'];
  908.                         if (isset($previous_indexes[$index_name])
  909.                             && isset($previous_indexes[$index_name]['was'])
  910.                             && !strcmp($previous_indexes[$index_name]['was'], $was_index_name)
  911.                         ) {
  912.                             $was_index_name = $index_name;
  913.                         }
  914.                         if (isset($previous_indexes[$was_index_name])) {
  915.                             $change = array();
  916.  
  917.                             if (strcmp($was_index_name, $index_name)) {
  918.                                 $change['name'] = $was_index_name;
  919.                                 $this->db->debug("Changed index '$was_index_name' name to '$index_name' in table '$table_name'");
  920.                             }
  921.                             if (isset($defined_indexes[$was_index_name])) {
  922.                                 return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  923.                                     'the index "'.$was_index_name.'" was specified as base of'.
  924.                                     ' more than one index of table "'.$table_name.'"');
  925.                             }
  926.                             $defined_indexes[$was_index_name] = true;
  927.  
  928.                             $previous_unique = isset($previous_indexes[$was_index_name]['unique']);
  929.                             $unique = isset($index['unique']);
  930.                             if ($previous_unique != $unique) {
  931.                                 $change['changed_unique'] = true;
  932.                                 if ($unique) {
  933.                                     $change['unique'] = $unique;
  934.                                 }
  935.                                 $this->db->debug("Changed index '$index_name' unique from $previous_unique to $unique in table '$table_name'");
  936.                             }
  937.                             $defined_fields = array();
  938.                             $previous_fields = $previous_indexes[$was_index_name]['fields'];
  939.                             if (isset($index['fields']) && is_array($index['fields'])) {
  940.                                 foreach ($index['fields'] as $field_name => $field) {
  941.                                     if (isset($previous_fields[$field_name])) {
  942.                                         $defined_fields[$field_name] = true;
  943.                                         $sorting = (isset($field['sorting']) ? $field['sorting'] : '');
  944.                                         $previous_sorting = (isset($previous_fields[$field_name]['sorting']) ? $previous_fields[$field_name]['sorting'] : '');
  945.                                         if (strcmp($sorting, $previous_sorting)) {
  946.                                             $this->db->debug("Changed index field '$field_name' sorting default from '$previous_sorting' to '$sorting' in table '$table_name'");
  947.                                             $change['changed_fields'] = true;
  948.                                         }
  949.                                     } else {
  950.                                         $change['changed_fields'] = true;
  951.                                         $this->db->debug("Added field '$field_name' to index '$index_name' of table '$table_name'");
  952.                                     }
  953.                                 }
  954.                             }
  955.                             if (isset($previous_fields) && is_array($previous_fields)) {
  956.                                 foreach ($previous_fields as $field_name => $field) {
  957.                                     if (!isset($defined_fields[$field_name])) {
  958.                                         $change['changed_fields'] = true;
  959.                                         $this->db->debug("Removed field '$field_name' from index '$index_name' of table '$table_name'");
  960.                                     }
  961.                                 }
  962.                             }
  963.  
  964.                             if (count($change)) {
  965.                                 $this->_addDefinitionChange($changes, 'indexes', $table_name,array('changed_indexes' => array($index_name => $change)));
  966.                             }
  967.                         } else {
  968.                             if (strcmp($index_name, $was_index_name)) {
  969.                                 return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  970.                                     'it was specified a previous index name ("'.$was_index_name.
  971.                                     ') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist');
  972.                             }
  973.                             $this->_addDefinitionChange($changes, 'indexes', $table_name,array('added_indexes' => array($index_name => $indexes[$index_name])));
  974.                             $this->db->debug("Added index '$index_name' to table '$table_name'");
  975.                         }
  976.                     }
  977.                     foreach ($previous_indexes as $index_previous_name => $index_previous) {
  978.                         if (!isset($defined_indexes[$index_previous_name])) {
  979.                             $this->_addDefinitionChange($changes, 'indexes', $table_name, array('removed_indexes' => array($index_previous_name => $was_table_name)));
  980.                             $this->db->debug("Removed index '$index_name' from table '$table_name'");
  981.                         }
  982.                     }
  983.                 } else {
  984.                     if (strcmp($table_name, $was_table_name)) {
  985.                         return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  986.                             'it was specified a previous table name ("'.
  987.                             $was_table_name.'") for table "'.$table_name.
  988.                             '" that does not exist');
  989.                     }
  990.                     $this->_addDefinitionChange($changes, 'tables', $table_name,array('add' => 1));
  991.                     $this->db->debug("Added table '$table_name'");
  992.                 }
  993.             }
  994.             if (isset($previous_definition['tables']) && is_array($previous_definition['tables'])) {
  995.                 foreach ($previous_definition['tables'] as $table_name => $table) {
  996.                     if (!isset($defined_tables[$table_name])) {
  997.                         $this->_addDefinitionChange($changes, 'tables', $table_name, array('remove' => 1));
  998.                         $this->db->debug("Removed table '$table_name'");
  999.                     }
  1000.                 }
  1001.             }
  1002.             if (isset($this->database_definition['sequences']) && is_array($this->database_definition['sequences'])) {
  1003.                 foreach ($this->database_definition['sequences'] as $sequence_name => $sequence) {
  1004.                     $was_sequence_name = $sequence['was'];
  1005.                     if (isset($previous_definition['sequences'][$sequence_name])
  1006.                         && isset($previous_definition['sequences'][$sequence_name]['was'])
  1007.                         && !strcmp($previous_definition['sequences'][$sequence_name]['was'], $was_sequence_name)
  1008.                     ) {
  1009.                         $was_sequence_name = $sequence_name;
  1010.                     }
  1011.                     if (isset($previous_definition['sequences'][$was_sequence_name])) {
  1012.                         if (strcmp($was_sequence_name, $sequence_name)) {
  1013.                             $this->_addDefinitionChange($changes, 'sequences', $was_sequence_name,array('name' => $sequence_name));
  1014.                             $this->db->debug("Renamed sequence '$was_sequence_name' to '$sequence_name'");
  1015.                         }
  1016.                         if (isset($defined_sequences[$was_sequence_name])) {
  1017.                             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  1018.                                 'the sequence "'.$was_sequence_name.'" was specified as base'.
  1019.                                 ' of more than of sequence of the database');
  1020.                         }
  1021.                         $defined_sequences[$was_sequence_name] = true;
  1022.                         $change = array();
  1023.                         if (strcmp($sequence['start'], $previous_definition['sequences'][$was_sequence_name]['start'])) {
  1024.                             $change['start'] = $this->database_definition['sequences'][$sequence_name]['start'];
  1025.                             $this->db->debug("Changed sequence '$sequence_name' start from '".$previous_definition['sequences'][$was_sequence_name]['start']."' to '".$this->database_definition['sequences'][$sequence_name]['start']."'");
  1026.                         }
  1027.                         if (strcmp($sequence['on']['table'], $previous_definition['sequences'][$was_sequence_name]['on']['table'])
  1028.                             || strcmp($sequence['on']['field'], $previous_definition['sequences'][$was_sequence_name]['on']['field'])
  1029.                         ) {
  1030.                             $change['on'] = $sequence['on'];
  1031.                             $this->db->debug("Changed sequence '$sequence_name' on table field from '".$previous_definition['sequences'][$was_sequence_name]['on']['table'].'.'.$previous_definition['sequences'][$was_sequence_name]['on']['field']."' to '".$this->database_definition['sequences'][$sequence_name]['on']['table'].'.'.$this->database_definition['sequences'][$sequence_name]['on']['field']."'");
  1032.                         }
  1033.                         if (count($change)) {
  1034.                             $this->_addDefinitionChange($changes, 'sequences', $was_sequence_name,array('change' => array($sequence_name => array($change))));
  1035.                         }
  1036.                     } else {
  1037.                         if (strcmp($sequence_name, $was_sequence_name)) {
  1038.                             return $this->raiseError(MDB2_ERROR_INVALID, null, null,
  1039.                                 'it was specified a previous sequence name ("'.$was_sequence_name.
  1040.                                 '") for sequence "'.$sequence_name.'" that does not exist');
  1041.                         }
  1042.                         $this->_addDefinitionChange($changes, 'sequences', $sequence_name, array('add' => 1));
  1043.                         $this->db->debug("Added sequence '$sequence_name'");
  1044.                     }
  1045.                 }
  1046.             }
  1047.             if (isset($previous_definition['sequences']) && is_array($previous_definition['sequences'])) {
  1048.                 foreach ($previous_definition['sequences'] as $sequence_name => $sequence) {
  1049.                     if (!isset($defined_sequences[$sequence_name])) {
  1050.                         $this->_addDefinitionChange($changes, 'sequences', $sequence_name, array('remove' => 1));
  1051.                         $this->db->debug("Removed sequence '$sequence_name'");
  1052.                     }
  1053.                 }
  1054.             }
  1055.         }
  1056.         return $changes;
  1057.     }
  1058.  
  1059.     // }}}
  1060.     // {{{ _alterDatabase()
  1061.  
  1062.     /**
  1063.      * Execute the necessary actions to implement the requested changes
  1064.      * in a database structure.
  1065.      *
  1066.      * @param array $previous_definition an associative array that contains
  1067.      *  the definition of the database structure before applying the requested
  1068.      *  changes. The definition of this array may be built separately, but
  1069.      *  usually it is built by the Parse method the Metabase parser class.
  1070.      * @param array $changes an associative array that contains the definition of
  1071.      *  the changes that are meant to be applied to the database structure.
  1072.      * @return mixed MDB2_OK on success, or a MDB2 error object
  1073.      * @access private
  1074.      */
  1075.     function _alterDatabase($previous_definition, $changes)
  1076.     {
  1077.         $result = '';
  1078.         if (isset($changes['tables']) && is_array($changes['tables'])) {
  1079.             foreach ($changes['tables'] as $table_name => $table) {
  1080.                 if (isset($table['add']) || isset($table['remove'])) {
  1081.                     continue;
  1082.                 }
  1083.                 $result = $this->db->manager->alterTable($table_name, $table, 1);
  1084.                 if (MDB2::isError($result)) {
  1085.                     return $result;
  1086.                 }
  1087.             }
  1088.         }
  1089.         if (isset($changes['sequences']) && is_array($changes['sequences'])) {
  1090.             if (!$this->db->supports('sequences')) {
  1091.                 return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  1092.                     'sequences are not supported');
  1093.             }
  1094.             foreach ($changes['sequences'] as $sequence) {
  1095.                 if (isset($sequence['add'])
  1096.                     || isset($sequence['remove'])
  1097.                     || isset($sequence['change'])
  1098.                 ) {
  1099.                     continue;
  1100.                 }
  1101.                 return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  1102.                     'some sequences changes are not yet supported');
  1103.             }
  1104.         }
  1105.         if (isset($changes['indexes']) && is_array($changes['indexes'])) {
  1106.             if (!$this->db->supports('indexes')) {
  1107.                 return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  1108.                     'indexes are not supported');
  1109.             }
  1110.             foreach ($changes['indexes'] as $index) {
  1111.                 $table_changes = count($index);
  1112.                 if (isset($index['added_indexes'])) {
  1113.                     $table_changes--;
  1114.                 }
  1115.                 if (isset($index['removed_indexes'])) {
  1116.                     $table_changes--;
  1117.                 }
  1118.                 if (isset($index['changed_indexes'])) {
  1119.                     $table_changes--;
  1120.                 }
  1121.                 if ($table_changes) {
  1122.                     return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  1123.                         'index alteration not yet supported');
  1124.                 }
  1125.             }
  1126.         }
  1127.  
  1128.         $previous_database_name = $this->db->setDatabase($this->database_definition['name']);
  1129.         if (($support_transactions = $this->db->supports('transactions'))
  1130.             && MDB2::isError($result = $this->db->autoCommit(false))
  1131.         ) {
  1132.             return $result;
  1133.         }
  1134.         $error = '';
  1135.         $alterations = 0;
  1136.         if (isset($changes['indexes']) && is_array($changes['indexes'])) {
  1137.             foreach ($changes['indexes'] as $index_name => $index) {
  1138.                 if (isset($index['removed_indexes']) && is_array($index['removed_indexes'])) {
  1139.                     foreach ($index['removed_indexes'] as $index_remove_name => $index_remove) {
  1140.                         $result = $this->db->manager->dropIndex($index_name,$index_remove_name);
  1141.                         if (MDB2::isError($result)) {
  1142.                             break;
  1143.                         }
  1144.                         $alterations++;
  1145.                     }
  1146.                 }
  1147.                 if (!MDB2::isError($result)
  1148.                     && is_array($index['changed_indexes'])
  1149.                 ) {
  1150.                     foreach ($index['changed_indexes'] as $index_changed_name => $index_changed) {
  1151.                         $was_name = (isset($indexes[$name]['name']) ? $indexes[$index_changed_name]['name'] : $index_changed_name);
  1152.                         $result = $this->db->manager->dropIndex($index_name, $was_name);
  1153.                         if (MDB2::isError($result)) {
  1154.                             break;
  1155.                         }
  1156.                         $alterations++;
  1157.                     }
  1158.                 }
  1159.                 if (MDB2::isError($result)) {
  1160.                     break;
  1161.                 }
  1162.             }
  1163.         }
  1164.         if (!MDB2::isError($result) && isset($changes['tables'])
  1165.             && is_array($changes['tables'])
  1166.         ) {
  1167.             foreach ($changes['tables'] as $table_name => $table) {
  1168.                 if (isset($table['remove'])) {
  1169.                     $result = $this->_dropTable($table_name);
  1170.                     if (!MDB2::isError($result)) {
  1171.                         $alterations++;
  1172.                     }
  1173.                 } else {
  1174.                     if (!isset($table['add'])) {
  1175.                         $result = $this->db->manager->alterTable($table_name, $changes['tables'][$table_name], 0);
  1176.                         if (!MDB2::isError($result)) {
  1177.                             $alterations++;
  1178.                         }
  1179.                     }
  1180.                 }
  1181.                 if (MDB2::isError($result)) {
  1182.                     break;
  1183.                 }
  1184.             }
  1185.             foreach ($changes['tables'] as $table_name => $table) {
  1186.                 if (isset($table['add'])) {
  1187.                     $result = $this->_createTable($table_name, $this->database_definition['tables'][$table_name]);
  1188.                     if (!MDB2::isError($result)) {
  1189.                         $alterations++;
  1190.                     }
  1191.                 }
  1192.                 if (MDB2::isError($result)) {
  1193.                     break;
  1194.                 }
  1195.             }
  1196.         }
  1197.         if (!MDB2::isError($result) && isset($changes['sequences'])
  1198.             && is_array($changes['sequences'])
  1199.         ) {
  1200.             foreach ($changes['sequences'] as $sequence_name => $sequence) {
  1201.                 if (isset($sequence['add'])) {
  1202.                     $created_on_table = false;
  1203.                     if (isset($this->database_definition['sequences'][$sequence_name]['on'])) {
  1204.                         $table = $this->database_definition['sequences'][$sequence_name]['on']['table'];
  1205.                         if (isset($changes['tables'])
  1206.                             && isset($changes['tables'][$table_name])
  1207.                             && isset($changes['tables'][$table_name]['add'])
  1208.                         ) {
  1209.                             $created_on_table = true;
  1210.                         }
  1211.                     }
  1212.  
  1213.                     $result = $this->_createSequence($sequence_name,
  1214.                         $this->database_definition['sequences'][$sequence_name], $created_on_table);
  1215.                     if (!MDB2::isError($result)) {
  1216.                         $alterations++;
  1217.                     }
  1218.                 } else {
  1219.                     if (isset($sequence['remove'])) {
  1220.                         if (!strcmp($error = $this->_dropSequence($sequence_name), '')) {
  1221.                             $alterations++;
  1222.                         }
  1223.                     } else {
  1224.                         if (isset($sequence['change'])) {
  1225.                             $created_on_table = false;
  1226.                             if (isset($this->database_definition['sequences'][$sequence_name]['on'])) {
  1227.                                 $table = $this->database_definition['sequences'][$sequence_name]['on']['table'];
  1228.                                 if (isset($changes['tables'])
  1229.                                     && isset($changes['tables'][$table_name])
  1230.                                     && isset($changes['tables'][$table_name]['add'])
  1231.                                 ) {
  1232.                                     $created_on_table = true;
  1233.                                 }
  1234.                             }
  1235.                             if (!MDB2::isError($result = $this->_dropSequence(
  1236.                                     $this->database_definition['sequences'][$sequence_name]['was']), '')
  1237.                                 && !MDB2::isError($result = $this->_createSequence(
  1238.                                     $sequence_name, $this->database_definition['sequences'][$sequence_name], $created_on_table), '')
  1239.                             ) {
  1240.                                 $alterations++;
  1241.                             }
  1242.                         } else {
  1243.                             return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null,
  1244.                                 'changing sequences is not yet supported');
  1245.                         }
  1246.                     }
  1247.                 }
  1248.                 if (MDB2::isError($result)) {
  1249.                     break;
  1250.                 }
  1251.             }
  1252.         }
  1253.         if (!MDB2::isError($result) && isset($changes['indexes'])
  1254.             && is_array($changes['indexes'])
  1255.         ) {
  1256.             foreach ($changes['indexes'] as $table_name => $indexes) {
  1257.                 if (isset($indexes['changed_indexes'])) {
  1258.                     $changedindexes = $indexes['changed_indexes'];
  1259.                     foreach ($changedindexes as $index_name => $index) {
  1260.                         $result = $this->db->manager->createIndex($table_name, $index_name,
  1261.                             $this->database_definition['tables'][$table_name]['indexes'][$index_name]);
  1262.                         if (MDB2::isError($result)) {
  1263.                             break;
  1264.                         }
  1265.                         $alterations++;
  1266.                     }
  1267.                 }
  1268.                 if (!MDB2::isError($result)
  1269.                     && isset($indexes['added_indexes'])
  1270.                 ) {
  1271.                     $addedindexes = $indexes['added_indexes'];
  1272.                     foreach ($addedindexes as $index_name => $index) {
  1273.                         $result = $this->db->manager->createIndex($table_name, $index_name,
  1274.                             $this->database_definition['tables'][$table_name]['indexes'][$index_name]);
  1275.                         if (MDB2::isError($result)) {
  1276.                             break;
  1277.                         }
  1278.                         $alterations++;
  1279.                     }
  1280.                 }
  1281.                 if (MDB2::isError($result)) {
  1282.                     break;
  1283.                 }
  1284.             }
  1285.         }
  1286.         if ($alterations && MDB2::isError($result)) {
  1287.             if ($support_transactions) {
  1288.                 $res = $this->db->rollback();
  1289.                 if (MDB2::isError($res))
  1290.                     $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  1291.                         'Could not rollback the partially created database alterations ('.
  1292.                         $result->getMessage().' ('.$result->getUserinfo().'))');
  1293.             } else {
  1294.                 $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  1295.                     'the requested database alterations were only partially implemented ('.
  1296.                     $result->getMessage().' ('.$result->getUserinfo().'))');
  1297.             }
  1298.         }
  1299.         if ($support_transactions) {
  1300.             $result = $this->db->autoCommit(true);
  1301.             if (MDB2::isError($result)) {
  1302.                 $result = $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  1303.                     'Could not end transaction after successfully implemented the requested database alterations ('.
  1304.                     $result->getMessage().' ('.$result->getUserinfo().'))');
  1305.             }
  1306.         }
  1307.         $this->db->setDatabase($previous_database_name);
  1308.         return $result;
  1309.     }
  1310.  
  1311.     // }}}
  1312.     // {{{ _escapeSpecialCharacters()
  1313.  
  1314.     /**
  1315.      * add escapecharacters to all special characters in a string
  1316.      *
  1317.      * @param string $string string that should be escaped
  1318.      * @return string escaped string
  1319.      * @access private
  1320.      */
  1321.     function _escapeSpecialCharacters($string)
  1322.     {
  1323.         if (is_string($string)) {
  1324.             $string = strval($string);
  1325.         }
  1326.         for ($escaped = '', $character = 0;
  1327.             $character < strlen($string);
  1328.             $character++)
  1329.         {
  1330.             switch ($string[$character]) {
  1331.                 case '\"':
  1332.                 case '>':
  1333.                 case '<':
  1334.                 case '&':
  1335.                     $code = ord($string[$character]);
  1336.                     break;
  1337.                 default:
  1338.                     $code = ord($string[$character]);
  1339.                     if ($code < 32 || $code>127) {
  1340.                         break;
  1341.                     }
  1342.                     $escaped .= $string[$character];
  1343.                     continue 2;
  1344.             }
  1345.             $escaped .= "&#$code;";
  1346.         }
  1347.         return $escaped;
  1348.     }
  1349.  
  1350.     // }}}
  1351.     // {{{ _dumpSequence()
  1352.  
  1353.     /**
  1354.      * dump the structure of a sequence
  1355.      *
  1356.      * @param string  $sequence_name 
  1357.      * @param string  $eol 
  1358.      * @return mixed string with xml seqeunce definition on success, or a MDB2 error object
  1359.      * @access private
  1360.      */
  1361.     function _dumpSequence($sequence_name, $eol, $dump = MDB2_MANAGER_DUMP_ALL)
  1362.     {
  1363.         $sequence_definition = $this->database_definition['sequences'][$sequence_name];
  1364.         $buffer = "$eol <sequence>$eol  <name>$sequence_name</name>$eol";
  1365.         if ($dump == MDB2_MANAGER_DUMP_ALL || $dump == MDB2_MANAGER_DUMP_CONTENT) {
  1366.             if (isset($sequence_definition['start'])) {
  1367.                 $start = $sequence_definition['start'];
  1368.                 $buffer .= "  <start>$start</start>$eol";
  1369.             }
  1370.         }
  1371.         if (isset($sequence_definition['on'])) {
  1372.             $buffer .= "  <on>$eol   <table>".$sequence_definition['on']['table'].
  1373.                 "</table>$eol   <field>".$sequence_definition['on']['field'].
  1374.                 "</field>$eol  </on>$eol";
  1375.         }
  1376.         $buffer .= " </sequence>$eol";
  1377.         return $buffer;
  1378.     }
  1379.  
  1380.     // }}}
  1381.     // {{{ parseDatabaseDefinitionFile()
  1382.  
  1383.     /**
  1384.      * Parse a database definition file by creating a Metabase schema format
  1385.      * parser object and passing the file contents as parser input data stream.
  1386.      *
  1387.      * @param string $input_file the path of the database schema file.
  1388.      * @param array $variables an associative array that the defines the text
  1389.      *  string values that are meant to be used to replace the variables that are
  1390.      *  used in the schema description.
  1391.      * @param bool $fail_on_invalid_names (optional) make function fail on invalid
  1392.      *  names
  1393.      * @return mixed MDB2_OK on success, or a MDB2 error object
  1394.      * @access public
  1395.      */
  1396.     function parseDatabaseDefinitionFile($input_file, $variables, $fail_on_invalid_names = true)
  1397.     {
  1398.         $parser =& new MDB2_Tools_Parser($variables, $fail_on_invalid_names);
  1399.         $result = $parser->setInputFile($input_file);
  1400.         if (MDB2::isError($result)) {
  1401.             return $result;
  1402.         };
  1403.         $result = $parser->parse();
  1404.         if (MDB2::isError($result)) {
  1405.             return $result;
  1406.         };
  1407.         if (MDB2::isError($parser->error)) {
  1408.             return $parser->error;
  1409.         }
  1410.         return $parser->database_definition;
  1411.     }
  1412.  
  1413.     // }}}
  1414.     // {{{ _debugDatabaseChanges()
  1415.  
  1416.     /**
  1417.      * Dump the changes between two database definitions.
  1418.      *
  1419.      * @param array $changes an associative array that specifies the list
  1420.      *  of database definitions changes as returned by the _compareDefinitions
  1421.      *  manager class function.
  1422.      * @return mixed MDB2_OK on success, or a MDB2 error object
  1423.      * @access private
  1424.      */
  1425.     function _debugDatabaseChanges($changes)
  1426.     {
  1427.         if (isset($changes['tables'])) {
  1428.             foreach ($changes['tables'] as $table_name => $table)
  1429.             {
  1430.                 $this->db->debug("$table_name:");
  1431.                 if (isset($table['add'])) {
  1432.                     $this->db->debug("\tAdded table '$table_name'");
  1433.                 } elseif (isset($table['remove'])) {
  1434.                     $this->db->debug("\tRemoved table '$table_name'");
  1435.                 } else {
  1436.                     if (isset($table['name'])) {
  1437.                         $this->db->debug("\tRenamed table '$table_name' to '".
  1438.                             $table['name']."'");
  1439.                     }
  1440.                     if (isset($table['added_fields'])) {
  1441.                         foreach ($table['added_fields'] as $field_name => $field) {
  1442.                             $this->db->debug("\tAdded field '".$field_name."'");
  1443.                         }
  1444.                     }
  1445.                     if (isset($table['removed_fields'])) {
  1446.                         foreach ($table['removed_fields'] as $field_name => $field) {
  1447.                             $this->db->debug("\tRemoved field '".$field_name."'");
  1448.                         }
  1449.                     }
  1450.                     if (isset($table['renamed_fields'])) {
  1451.                         foreach ($table['renamed_fields'] as $field_name => $field) {
  1452.                             $this->db->debug("\tRenamed field '".$field_name."' to '".
  1453.                                 $field['name']."'");
  1454.                         }
  1455.                     }
  1456.                     if (isset($table['changed_fields'])) {
  1457.                         foreach ($table['changed_fields'] as $field_name => $field) {
  1458.                             if (isset($field['type'])) {
  1459.                                 $this->db->debug(
  1460.                                     "\tChanged field '$field_name' type to '".
  1461.                                         $field['type']."'");
  1462.                             }
  1463.                             if (isset($field['unsigned'])) {
  1464.                                 $this->db->debug(
  1465.                                     "\tChanged field '$field_name' type to '".
  1466.                                     ($field['unsigned'] ? '' : 'not ')."unsigned'");
  1467.                             }
  1468.                             if (isset($field['length'])) {
  1469.                                 $this->db->debug(
  1470.                                     "\tChanged field '$field_name' length to '".
  1471.                                     ($field['length'] == 0 ? 'no length' : $field['length'])."'");
  1472.                             }
  1473.                             if (isset($field['changed_default'])) {
  1474.                                 $this->db->debug(
  1475.                                     "\tChanged field '$field_name' default to ".
  1476.                                     (isset($field['default']) ? "'".$field['default']."'" : 'NULL'));
  1477.                             }
  1478.                             if (isset($field['changed_not_null'])) {
  1479.                                 $this->db->debug(
  1480.                                    "\tChanged field '$field_name' notnull to ".(isset($field['notnull']) ? "'1'" : '0'));
  1481.                             }
  1482.                         }
  1483.                     }
  1484.                 }
  1485.             }
  1486.         }
  1487.         if (isset($changes['sequences'])) {
  1488.             foreach ($changes['sequences'] as $sequence_name => $sequence)
  1489.             {
  1490.                 $this->db->debug("$sequence_name:");
  1491.                 if (isset($sequence['add'])) {
  1492.                     $this->db->debug("\tAdded sequence '$sequence_name'");
  1493.                 } elseif (isset($sequence['remove'])) {
  1494.                     $this->db->debug("\tRemoved sequence '$sequence_name'");
  1495.                 } else {
  1496.                     if (isset($sequence['name'])) {
  1497.                         $this->db->debug(
  1498.                             "\tRenamed sequence '$sequence_name' to '".
  1499.                             $sequence['name']."'");
  1500.                     }
  1501.                     if (isset($sequence['change'])) {
  1502.                         foreach ($sequence['change'] as $sequence_name => $sequence) {
  1503.                             if (isset($sequence['start'])) {
  1504.                                 $this->db->debug(
  1505.                                     "\tChanged sequence '$sequence_name' start to '".
  1506.                                     $sequence['start']."'");
  1507.                             }
  1508.                         }
  1509.                     }
  1510.                 }
  1511.             }
  1512.         }
  1513.         if (isset($changes['indexes'])) {
  1514.             foreach ($changes['indexes'] as $table_name => $table)
  1515.             {
  1516.                 $this->db->debug("$table_name:");
  1517.                 if (isset($table['added_indexes'])) {
  1518.                     foreach ($table['added_indexes'] as $index_name => $index) {
  1519.                         $this->db->debug("\tAdded index '".$index_name.
  1520.                             "' of table '$table_name'");
  1521.                     }
  1522.                 }
  1523.                 if (isset($table['removed_indexes'])) {
  1524.                     foreach ($table['removed_indexes'] as $index_name => $index) {
  1525.                         $this->db->debug("\tRemoved index '".$index_name.
  1526.                             "' of table '$table_name'");
  1527.                     }
  1528.                 }
  1529.                 if (isset($table['changed_indexes'])) {
  1530.                     foreach ($table['changed_indexes'] as $index_name => $index) {
  1531.                         if (isset($index['name'])) {
  1532.                             $this->db->debug(
  1533.                                 "\tRenamed index '".$index_name."' to '".$index['name'].
  1534.                                 "' on table '$table_name'");
  1535.                         }
  1536.                         if (isset($index['changed_unique'])) {
  1537.                             $this->db->debug(
  1538.                                 "\tChanged index '".$index_name."' unique to '".
  1539.                                 isset($index['unique'])."' on table '$table_name'");
  1540.                         }
  1541.                         if (isset($index['changed_fields'])) {
  1542.                             $this->db->debug("\tChanged index '".$index_name.
  1543.                                 "' on table '$table_name'");
  1544.                         }
  1545.                     }
  1546.                 }
  1547.             }
  1548.         }
  1549.         return MDB2_OK;
  1550.     }
  1551.  
  1552.     // }}}
  1553.     // {{{ _dumpDatabaseContents()
  1554.  
  1555.     /**
  1556.      * Parse a database schema definition file and dump the respective structure
  1557.      * and contents.
  1558.      *
  1559.      * @param string $schema_file path of the database schema file.
  1560.      * @param mixed $setup_arguments an associative array that takes pairs of tag names and values
  1561.      *  that define the setup arguments that are passed to the
  1562.      *  MDB2_Tools_Manager::connect function.
  1563.      * @param array $dump_arguments an associative array that takes pairs of tag names and values
  1564.      *  that define dump options as defined for the MDB2_Tools_Manager::DumpDatabase
  1565.      *  function.
  1566.      * @param array $variables an associative array that the defines the text string values
  1567.      *  that are meant to be used to replace the variables that are used in the
  1568.      *  schema description as defined for the
  1569.      *  MDB2_Tools_Manager::parseDatabaseDefinitionFile function.
  1570.      * @return mixed MDB2_OK on success, or a MDB2 error object
  1571.      * @access private
  1572.      */
  1573.     function _dumpDatabaseContents($schema_file, $setup_arguments, $dump_arguments, $variables)
  1574.     {
  1575.         $database_definition = $this->parseDatabaseDefinitionFile($schema_file,
  1576.             $variables, $this->options['fail_on_invalid_names']);
  1577.         if (MDB2::isError($database_definition)) {
  1578.             return $database_definition;
  1579.         }
  1580.  
  1581.         $this->database_definition = $database_definition;
  1582.  
  1583.         $result = $this->connect($setup_arguments);
  1584.         if (MDB2::isError($result)) {
  1585.             return $result;
  1586.         }
  1587.  
  1588.         return $this->dumpDatabase($dump_arguments);
  1589.     }
  1590.  
  1591.     // }}}
  1592.     // {{{ getDefinitionFromDatabase()
  1593.  
  1594.     /**
  1595.      * Attempt to reverse engineer a schema structure from an existing MDB2
  1596.      * This method can be used if no xml schema file exists yet.
  1597.      * The resulting xml schema file may need some manual adjustments.
  1598.      *
  1599.      * @return mixed MDB2_OK or array with all ambiguities on success, or a MDB2 error object
  1600.      * @access public
  1601.      */
  1602.     function getDefinitionFromDatabase()
  1603.     {
  1604.         $this->db->loadModule('reverse');
  1605.         $database = $this->db->database_name;
  1606.         if (strlen($database) == 0) {
  1607.             return $this->raiseError('it was not specified a valid database name');
  1608.         }
  1609.         $this->database_definition = array(
  1610.             'name' => $database,
  1611.             'create' => 1,
  1612.             'tables' => array()
  1613.         );
  1614.         $tables = $this->db->manager->listTables();
  1615.         if (MDB2::isError($tables)) {
  1616.             return $tables;
  1617.         }
  1618.  
  1619.         for ($table = 0; $table < count($tables); $table++) {
  1620.             $table_name = $tables[$table];
  1621.             $fields = $this->db->manager->listTableFields($table_name);
  1622.             if (MDB2::isError($fields)) {
  1623.                 return $fields;
  1624.             }
  1625.             $this->database_definition['tables'][$table_name] = array('fields' => array());
  1626.             for ($field = 0; $field < count($fields); $field++) {
  1627.                 $field_name = $fields[$field];
  1628.                 $definition = $this->db->reverse->getTableFieldDefinition($table_name, $field_name);
  1629.                 if (MDB2::isError($definition)) {
  1630.                     return $definition;
  1631.                 }
  1632.                 $this->database_definition['tables'][$table_name]['fields'][$field_name] = $definition[0][0];
  1633.                 $field_choices = count($definition[0]);
  1634.                 if ($field_choices > 1) {
  1635.                     $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
  1636.                     $field_choice_cnt = 1;
  1637.                     $this->database_definition['tables'][$table_name]['fields'][$field_name]['choices'] = array();
  1638.                     foreach ($definition[0] as $field_choice) {
  1639.                         $this->database_definition['tables'][$table_name]['fields'][$field_name]['choices'][] = $field_choice;
  1640.                         $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
  1641.                         $field_choice_cnt++;
  1642.                     }
  1643.                     $this->warnings[] = $warning;
  1644.                 }
  1645.                 if (isset($definition[1])) {
  1646.                     $sequence = $definition[1]['definition'];
  1647.                     $sequence_name = $definition[1]['name'];
  1648.                     $this->db->debug('Implicitly defining sequence: '.$sequence_name);
  1649.                     if (!isset($this->database_definition['sequences'])) {
  1650.                         $this->database_definition['sequences'] = array();
  1651.                     }
  1652.                     $this->database_definition['sequences'][$sequence_name] = $sequence;
  1653.                 }
  1654.                 if (isset($definition[2])) {
  1655.                     $index = $definition[2]['definition'];
  1656.                     $index_name = $definition[2]['name'];
  1657.                     $this->db->debug('Implicitly defining index: '.$index_name);
  1658.                     if (!isset($this->database_definition['tables'][$table_name]['indexes'])) {
  1659.                         $this->database_definition['tables'][$table_name]['indexes'] = array();
  1660.                     }
  1661.                     $this->database_definition['tables'][$table_name]['indexes'][$index_name] = $index;
  1662.                 }
  1663.             }
  1664.             $indexes = $this->db->manager->listTableIndexes($table_name);
  1665.             if (MDB2::isError($indexes)) {
  1666.                 return $indexes;
  1667.             }
  1668.             if (is_array($indexes) && count($indexes) > 0 && !isset($this->database_definition['tables'][$table_name]['indexes'])) {
  1669.                 $this->database_definition['tables'][$table_name]['indexes'] = array();
  1670.             }
  1671.             for ($index = 0, $index_cnt = count($indexes); $index < $index_cnt; $index++)
  1672.             {
  1673.                 $index_name = $indexes[$index];
  1674.                 $definition = $this->db->reverse->getTableIndexDefinition($table_name, $index_name);
  1675.                 if (MDB2::isError($definition)) {
  1676.                     return $definition;
  1677.                 }
  1678.                $this->database_definition['tables'][$table_name]['indexes'][$index_name] = $definition;
  1679.             }
  1680.             // ensure that all fields that have an index on them are set to NOT NULL
  1681.             if (isset($this->database_definition['tables'][$table_name]['indexes'])
  1682.                 && is_array($this->database_definition['tables'][$table_name]['indexes'])
  1683.                 && count($this->database_definition['tables'][$table_name]['indexes']) > 0
  1684.             ) {
  1685.                 foreach ($this->database_definition['tables'][$table_name]['indexes'] as $index_check_null) {
  1686.                     foreach ($index_check_null['fields'] as $field_name_check_null => $field_check_null) {
  1687.                         $this->database_definition['tables'][$table_name]['fields'][$field_name_check_null]['notnull'] = true;
  1688.                     }
  1689.                 }
  1690.             }
  1691.             // ensure that all fields that are set to NOT NULL also have a default value
  1692.             if (is_array($this->database_definition['tables'][$table_name]['fields'])
  1693.                 && count($this->database_definition['tables'][$table_name]['fields']) > 0
  1694.             ) {
  1695.                 foreach ($this->database_definition['tables'][$table_name]['fields'] as $field_set_default_name => $field_set_default) {
  1696.                     if (isset($field_set_default['notnull']) && $field_set_default['notnull']
  1697.                         && !isset($field_set_default['default'])
  1698.                     ) {
  1699.                         if (isset($this->default_values[$field_set_default['type']])) {
  1700.                             $this->database_definition['tables'][$table_name]['fields'][$field_set_default_name]['default'] = $this->default_values[$field_set_default['type']];
  1701.                         } else {
  1702.                             $this->database_definition['tables'][$table_name]['fields'][$field_set_default_name]['default'] = 0;
  1703.                         }
  1704.                     }
  1705.                     if (isset($field_set_default['choices']) && is_array($field_set_default['choices'])) {
  1706.                         foreach ($field_set_default['choices'] as $field_choices_set_default_name => $field_choices_set_default) {
  1707.                             if (isset($field_choices_set_default['notnull'])
  1708.                                 && $field_choices_set_default['notnull']
  1709.                                 && !isset($field_choices_set_default['default'])
  1710.                             ) {
  1711.                                 if (isset($this->default_values[$field_choices_set_default['type']])) {
  1712.                                     $this->database_definition['tables'][$table_name]['fields'][$field_set_default_name]['choices'][$field_choices_set_default_name]['default'] = $this->default_values[$field_choices_set_default['type']];
  1713.                                 } else {
  1714.                                     $this->database_definition['tables'][$table_name]['fields'][$field_set_default_name]['choices'][$field_choices_set_default_name]['default'] = 0;
  1715.                                 }
  1716.                             }
  1717.                         }
  1718.                     }
  1719.                 }
  1720.             }
  1721.         }
  1722.  
  1723.         $sequences = $this->db->manager->listSequences();
  1724.         if (MDB2::isError($sequences)) {
  1725.             return $sequences;
  1726.         }
  1727.         if (is_array($sequences) && count($sequences) > 0 && !isset($this->database_definition['sequences'])) {
  1728.             $this->database_definition['sequences'] = array();
  1729.         }
  1730.         for ($sequence = 0; $sequence < count($sequences); $sequence++) {
  1731.             $sequence_name = $sequences[$sequence];
  1732.             $definition = $this->db->reverse->getSequenceDefinition($sequence_name);
  1733.             if (MDB2::isError($definition)) {
  1734.                 return $definition;
  1735.             }
  1736.             $this->database_definition['sequences'][$sequence_name] = $definition;
  1737.         }
  1738.         return MDB2_OK;
  1739.     }
  1740.  
  1741.     // }}}
  1742.     // {{{ dumpDatabase()
  1743.  
  1744.     /**
  1745.      * Dump a previously parsed database structure in the Metabase schema
  1746.      * XML based format suitable for the Metabase parser. This function
  1747.      * may optionally dump the database definition with initialization
  1748.      * commands that specify the data that is currently present in the tables.
  1749.      *
  1750.      * @param array $arguments an associative array that takes pairs of tag
  1751.      *  names and values that define dump options.
  1752.      *                  array (
  1753.      *                      'definition'    =>    Boolean
  1754.      *                          true   :  dump currently parsed definition
  1755.      *                          default:  dump currently connected database
  1756.      *                      'Output_Mode'    =>    String
  1757.      *                          'file' :   dump into a file
  1758.      *                          default:   dump using a function
  1759.      *                      'output'        =>    String
  1760.      *                          depending on the 'Output_Mode'
  1761.      *                                   name of the file
  1762.      *                                   name of the function
  1763.      *                      'EndOfLine'        =>    String
  1764.      *                          end of line delimiter that should be used
  1765.      *                          default: "\n"
  1766.      *                  );
  1767.      * @param integer $dump constant that determines what data to dump
  1768.      *                       MDB2_MANAGER_DUMP_ALL       : the entire db
  1769.      *                       MDB2_MANAGER_DUMP_STRUCTURE : only the structure of the db
  1770.      *                       MDB2_MANAGER_DUMP_CONTENT   : only the content of the db
  1771.      * @return mixed MDB2_OK on success, or a MDB2 error object
  1772.      * @access public
  1773.      */
  1774.     function dumpDatabase($arguments, $dump = MDB2_MANAGER_DUMP_ALL)
  1775.     {
  1776.         if (isset($arguments['definition']) && $arguments['definition']) {
  1777.             $dump_definition = true;
  1778.         } else {
  1779.             if (!$this->db) {
  1780.                 return $this->raiseError(MDB2_ERROR_NODBSELECTED,
  1781.                     null, null, 'please connect to a RDBMS first');
  1782.             }
  1783.             $error = $this->getDefinitionFromDatabase();
  1784.             if (MDB2::isError($error)) {
  1785.                 return $error;
  1786.             }
  1787.             $dump_definition = false;
  1788.         }
  1789.  
  1790.         if (isset($arguments['output'])) {
  1791.             if (isset($arguments['output_mode']) && $arguments['output_mode'] == 'file') {
  1792.                 $fp = fopen($arguments['output'], 'w');
  1793.                 $output = false;
  1794.             } elseif (function_exists($arguments['output'])) {
  1795.                 $output = $arguments['output'];
  1796.             } else {
  1797.                 return $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  1798.                         'no valid output function specified');
  1799.             }
  1800.         } else {
  1801.             return $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  1802.                 'no output method specified');
  1803.         }
  1804.         if (isset($arguments['end_of_line'])) {
  1805.             $eol = $arguments['end_of_line'];
  1806.         } else {
  1807.             $eol = "\n";
  1808.         }
  1809.  
  1810.         $sequences = array();
  1811.         if (isset($this->database_definition['sequences'])
  1812.             && is_array($this->database_definition['sequences'])
  1813.         ) {
  1814.             foreach ($this->database_definition['sequences'] as $sequence_name => $sequence) {
  1815.                 if (isset($sequence['on'])) {
  1816.                     $table = $sequence['on']['table'];
  1817.                 } else {
  1818.                     $table = '';
  1819.                 }
  1820.                 $sequences[$table][] = $sequence_name;
  1821.             }
  1822.         }
  1823.         $previous_database_name = (strcmp($this->database_definition['name'], '') ? $this->db->setDatabase($this->database_definition['name']) : '');
  1824.         $buffer = ('<?xml version="1.0" encoding="ISO-8859-1" ?>'.$eol);
  1825.         $buffer .= ("<database>$eol$eol <name>".$this->database_definition['name']."</name>$eol <create>".$this->database_definition['create']."</create>$eol");
  1826.  
  1827.         if ($output) {
  1828.             $output($buffer);
  1829.         } else {
  1830.             fwrite($fp, $buffer);
  1831.         }
  1832.         $buffer = '';
  1833.         if (isset($this->database_definition['tables']) && is_array($this->database_definition['tables'])) {
  1834.             foreach ($this->database_definition['tables'] as $table_name => $table) {
  1835.                 $buffer = ("$eol <table>$eol$eol  <name>$table_name</name>$eol");
  1836.                 if ($dump == MDB2_MANAGER_DUMP_ALL || $dump == MDB2_MANAGER_DUMP_STRUCTURE) {
  1837.                     $buffer .= ("$eol  <declaration>$eol");
  1838.                     if (isset($table['fields']) && is_array($table['fields'])) {
  1839.                         foreach ($table['fields'] as $field_name => $field) {
  1840.                             if (!isset($field['type'])) {
  1841.                                 return $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  1842.                                     'it was not specified the type of the field "'.
  1843.                                     $field_name.'" of the table "'.$table_name);
  1844.                             }
  1845.                             $buffer .=("$eol   <field>$eol    <name>$field_name</name>$eol    <type>".$field['type']."</type>$eol");
  1846.                             if (in_array($field_name, array_keys($this->invalid_names))) {
  1847.                                 $this->warnings[] = "invalid field name: $field_name. You will need to set the class var \$fail_on_invalid_names to false or change the field name.";
  1848.                             }
  1849.                             switch ($field['type']) {
  1850.                                 case 'integer':
  1851.                                     if (isset($field['unsigned'])) {
  1852.                                         $buffer .=("    <unsigned>1</unsigned>$eol");
  1853.                                     }
  1854.                                     break;
  1855.                                 case 'text':
  1856.                                 case 'clob':
  1857.                                 case 'blob':
  1858.                                     if (isset($field['length'])) {
  1859.                                         $buffer .=('    <length>'.$field['length'].
  1860.                                             "</length>$eol");
  1861.                                     }
  1862.                                     break;
  1863.                                 case 'boolean':
  1864.                                 case 'date':
  1865.                                 case 'timestamp':
  1866.                                 case 'time':
  1867.                                 case 'float':
  1868.                                 case 'decimal':
  1869.                                     break;
  1870.                                 default:
  1871.                                     return $this->raiseError('type "'.$field['type'].
  1872.                                         '" is not yet supported');
  1873.                             }
  1874.                             if (isset($field['notnull'])) {
  1875.                                 $buffer .=("    <notnull>1</notnull>$eol");
  1876.                             }
  1877.                             if (isset($field['default'])) {
  1878.                                 $buffer .=('    <default>'.$this->_escapeSpecialCharacters($field['default'])."</default>$eol");
  1879.                             }
  1880.                             $buffer .=("   </field>$eol");
  1881.                         }
  1882.                     }
  1883.                     if (isset($table['indexes']) && is_array($table['indexes'])) {
  1884.                         foreach ($table['indexes'] as $index_name => $index) {
  1885.                             $buffer .=("$eol   <index>$eol    <name>$index_name</name>$eol");
  1886.                             if (isset($index['unique'])) {
  1887.                                 $buffer .=("    <unique>1</unique>$eol");
  1888.                             }
  1889.                             foreach ($index['fields'] as $field_name => $field) {
  1890.                                 $buffer .=("    <field>$eol     <name>$field_name</name>$eol");
  1891.                                 if (is_array($field) && isset($field['sorting'])) {
  1892.                                     $buffer .=('     <sorting>'.$field['sorting']."</sorting>$eol");
  1893.                                 }
  1894.                                 $buffer .=("    </field>$eol");
  1895.                             }
  1896.                             $buffer .=("   </index>$eol");
  1897.                         }
  1898.                     }
  1899.                     $buffer .= ("$eol  </declaration>$eol");
  1900.                 }
  1901.                 if ($output) {
  1902.                     $output($buffer);
  1903.                 } else {
  1904.                     fwrite($fp, $buffer);
  1905.                 }
  1906.                 $buffer = '';
  1907.                 if ($dump == MDB2_MANAGER_DUMP_ALL || $dump == MDB2_MANAGER_DUMP_CONTENT) {
  1908.                     if ($dump_definition) {
  1909.                         if (isset($table['initialization']) && is_array($table['initialization'])) {
  1910.                             $buffer = ("$eol  <initialization>$eol");
  1911.                             foreach ($table['initialization'] as $instruction_name => $instruction) {
  1912.                                 switch ($instruction['type']) {
  1913.                                     case 'insert':
  1914.                                         $buffer .= ("$eol   <insert>$eol");
  1915.                                         foreach ($instruction['fields'] as $field_name => $field) {
  1916.                                             $buffer .= ("$eol    <field>$eol     <name>$field_name</name>$eol     <value>".$this->_escapeSpecialCharacters($field)."</value>$eol   </field>$eol");
  1917.                                         }
  1918.                                         $buffer .= ("$eol   </insert>$eol");
  1919.                                         break;
  1920.                                 }
  1921.                             }
  1922.                             $buffer .= ("$eol  </initialization>$eol");
  1923.                         }
  1924.                     } else {
  1925.                         $types = array();
  1926.                         foreach ($table['fields'] as $field) {
  1927.                             $types[] = $field['type'];
  1928.                         }
  1929.                         $query = 'SELECT '.implode(',',array_keys($table['fields']))." FROM $table_name";
  1930.                         $data = $this->db->queryAll($query, $types, MDB2_FETCHMODE_ASSOC);
  1931.                         if (MDB2::isError($data)) {
  1932.                             return $data;
  1933.                         }
  1934.                         $rows = count($data);
  1935.                         if ($rows > 0) {
  1936.                             $buffer = ("$eol  <initialization>$eol");
  1937.                             if ($output) {
  1938.                                 $output($buffer);
  1939.                             } else {
  1940.                                 fwrite($fp, $buffer);
  1941.                             }
  1942.  
  1943.                             for ($row = 0; $row < $rows; $row++) {
  1944.                                 $buffer = ("$eol   <insert>$eol");
  1945.                                 $values = $data[$row];
  1946.                                 if (!is_array($values)) {
  1947.                                     break;
  1948.                                 } else {
  1949.                                     foreach ($values as $field_name => $field) {
  1950.                                             $buffer .= ("$eol   <field>$eol     <name>$field_name</name>$eol     <value>");
  1951.                                             $buffer .= $this->_escapeSpecialCharacters($values[$field_name]);
  1952.                                             $buffer .= ("</value>$eol   </field>$eol");
  1953.                                     }
  1954.                                 }
  1955.                                 $buffer .= ("$eol   </insert>$eol");
  1956.                                 if ($output) {
  1957.                                     $output($buffer);
  1958.                                 } else {
  1959.                                     fwrite($fp, $buffer);
  1960.                                 }
  1961.                                 $buffer = '';
  1962.                             }
  1963.                             $buffer = ("$eol  </initialization>$eol");
  1964.                             if ($output) {
  1965.                                 $output($buffer);
  1966.                             } else {
  1967.                                 fwrite($fp, $buffer);
  1968.                             }
  1969.                             $buffer = '';
  1970.                         }
  1971.                     }
  1972.                 }
  1973.                 $buffer .= ("$eol </table>$eol");
  1974.                 if ($output) {
  1975.                     $output($buffer);
  1976.                 } else {
  1977.                     fwrite($fp, $buffer);
  1978.                 }
  1979.                 if (isset($sequences[$table_name])) {
  1980.                     for ($sequence = 0, $j = count($sequences[$table_name]);
  1981.                         $sequence < $j;
  1982.                         $sequence++)
  1983.                     {
  1984.                         $result = $this->_dumpSequence($sequences[$table_name][$sequence], $eol, $dump);
  1985.                         if (MDB2::isError($result)) {
  1986.                             return $result;
  1987.                         }
  1988.                         if ($output) {
  1989.                             $output($result);
  1990.                         } else {
  1991.                             fwrite($fp, $result);
  1992.                         }
  1993.                     }
  1994.                 }
  1995.             }
  1996.         }
  1997.         if (isset($sequences[''])) {
  1998.             for ($sequence = 0;
  1999.                 $sequence < count($sequences['']);
  2000.                 $sequence++)
  2001.             {
  2002.                 $result = $this->_dumpSequence($sequences[''][$sequence], $eol, $dump);
  2003.                 if (MDB2::isError($result)) {
  2004.                     return $result;
  2005.                 }
  2006.                 if ($output) {
  2007.                        $output($result);
  2008.                    } else {
  2009.                        fwrite($fp, $result);
  2010.                 }
  2011.             }
  2012.         }
  2013.  
  2014.         $buffer = ("$eol</database>$eol");
  2015.         if ($output) {
  2016.             $output($buffer);
  2017.         } else {
  2018.             fwrite($fp, $buffer);
  2019.             fclose($fp);
  2020.         }
  2021.  
  2022.         if (strcmp($previous_database_name, '')) {
  2023.             $this->db->setDatabase($previous_database_name);
  2024.         }
  2025.         return MDB2_OK;
  2026.     }
  2027.  
  2028.     // }}}
  2029.     // {{{ updateDatabase()
  2030.  
  2031.     /**
  2032.      * Compare the correspondent files of two versions of a database schema
  2033.      * definition: the previously installed and the one that defines the schema
  2034.      * that is meant to update the database.
  2035.      * If the specified previous definition file does not exist, this function
  2036.      * will create the database from the definition specified in the current
  2037.      * schema file.
  2038.      * If both files exist, the function assumes that the database was previously
  2039.      * installed based on the previous schema file and will update it by just
  2040.      * applying the changes.
  2041.      * If this function succeeds, the contents of the current schema file are
  2042.      * copied to replace the previous schema file contents. Any subsequent schema
  2043.      * changes should only be done on the file specified by the $current_schema_file
  2044.      * to let this function make a consistent evaluation of the exact changes that
  2045.      * need to be applied.
  2046.      *
  2047.      * @param string $current_schema_file name of the updated database schema
  2048.      *  definition file.
  2049.      * @param string $previous_schema_file name the previously installed database
  2050.      *  schema definition file.
  2051.      * @param array $variables an associative array that is passed to the argument
  2052.      *  of the same name to the parseDatabaseDefinitionFile function. (there third
  2053.      *  param)
  2054.      * @return mixed MDB2_OK on success, or a MDB2 error object
  2055.      * @access public
  2056.      */
  2057.     function updateDatabase($current_schema_file, $previous_schema_file = false, $variables = array())
  2058.     {
  2059.         $database_definition = $this->parseDatabaseDefinitionFile($current_schema_file,
  2060.             $variables, $this->options['fail_on_invalid_names']);
  2061.         if (MDB2::isError($database_definition)) {
  2062.             return $database_definition;
  2063.         }
  2064.         $this->database_definition = $database_definition;
  2065.         $copy = false;
  2066. /*
  2067.         $this->expectError(MDB2_ERROR_UNSUPPORTED);
  2068.         $databases = $this->db->manager->listDatabases();
  2069.         $this->popExpect();
  2070.         if ((MDB2::isError($databases) || (is_array($databases) && in_array($this->database_definition['name'], $databases)))
  2071.             && $previous_schema_file && file_exists($previous_schema_file)
  2072.         ) {
  2073. */
  2074.         if ($previous_schema_file && file_exists($previous_schema_file)) {
  2075.             $previous_definition = $this->parseDatabaseDefinitionFile($previous_schema_file, $variables, 0);
  2076.             if (MDB2::isError($previous_definition)) {
  2077.                 return $previous_definition;
  2078.             }
  2079.             $changes = $this->_compareDefinitions($previous_definition);
  2080.             if (MDB2::isError($changes)) {
  2081.                 return $changes;
  2082.             }
  2083.             if (isset($changes) && is_array($changes)) {
  2084.                 $result = $this->_alterDatabase($previous_definition, $changes);
  2085.                 if (MDB2::isError($result)) {
  2086.                     return $result;
  2087.                 }
  2088.                 $copy = true;
  2089.                 if ($this->db->options['debug']) {
  2090.                     $result = $this->_debugDatabaseChanges($changes);
  2091.                     if (MDB2::isError($result)) {
  2092.                         return $result;
  2093.                     }
  2094.                 }
  2095.             }
  2096.         } else {
  2097.             $result = $this->_createDatabase();
  2098.             if (MDB2::isError($result)) {
  2099.                 return $result;
  2100.             }
  2101.             $copy = true;
  2102.         }
  2103.         if ($copy && $previous_schema_file && !copy($current_schema_file, $previous_schema_file)) {
  2104.             return $this->raiseError(MDB2_ERROR_MANAGER, null, null,
  2105.                 'Could not copy the new database definition file to the current file');
  2106.         }
  2107.         return MDB2_OK;
  2108.     }
  2109.  
  2110.     // }}}
  2111. }
  2112. ?>

Documentation generated on Mon, 11 Mar 2019 10:15:48 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.