Source for file Manager.php
Documentation is available at Manager.php
// +----------------------------------------------------------------------+
// +----------------------------------------------------------------------+
// | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox, |
// | Stig. S. Bakken, Lukas Smith |
// | All rights reserved. |
// +----------------------------------------------------------------------+
// | MDB is a merge of PEAR DB and Metabases that provides a unified DB |
// | API as well as database abstraction for PHP applications. |
// | This LICENSE is in the BSD license style. |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution. |
// | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, |
// | Lukas Smith nor the names of his contributors may be used to endorse |
// | or promote products derived from this software without specific prior|
// | written permission. |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
// | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
// | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
// | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
// | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
// | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
// | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
// | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
// | POSSIBILITY OF SUCH DAMAGE. |
// +----------------------------------------------------------------------+
// | Author: Lukas Smith <smith@backendmedia.com> |
// +----------------------------------------------------------------------+
// $Id: Manager.php,v 1.75.4.4 2004/03/10 14:42:59 lsmith Exp $
require_once('MDB/Parser.php');
define('MDB_MANAGER_DUMP_ALL', 0 );
define('MDB_MANAGER_DUMP_STRUCTURE', 1 );
define('MDB_MANAGER_DUMP_CONTENT', 2 );
* The database manager is a class that provides a set of database
* management services like installing, altering and dumping the data
* structures of databases.
* @author Lukas Smith <smith@backendmedia.com>
'fail_on_invalid_names' => 1 ,
'timestamp' => '0001-01-01 00:00:00',
* This method is used to communicate an error and invoke error
* callbacks etc. Basically a wrapper for PEAR::raiseError
* without the message string.
* @param mixed $code integer error code, or a PEAR error object (all
* other parameters are ignored if this parameter is an object
* @param int $mode error mode, see PEAR_Error docs
* @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the
* error level (E_USER_NOTICE etc). If error mode is
* PEAR_ERROR_CALLBACK, this is the callback function, either as a
* function name, or as an array of an object and method name. For
* other error modes this parameter is ignored.
* @param string $userinfo Extra debug information. Defaults to the last
* query and native error code.
* @param mixed $nativecode Native error code, integer or string depending
* @return object a PEAR error object
function &raiseError($code = MDB_MANAGER_ERROR , $mode = NULL , $options = NULL ,
$userinfo = NULL , $nativecode = NULL )
// The error is yet a MDB error object
$err = PEAR ::raiseError ($code, NULL , NULL , NULL , NULL , NULL , TRUE );
$err = PEAR ::raiseError (NULL , $code, $mode, $options, $userinfo,
// {{{ captureDebugOutput()
* @param string $capture name of the function that should be used in
$this->options['debug'] = $capture;
* @return string content of the debug_output class variable
* reset the warning array
* get all warnings in reverse order.
* This means that the last warning is the first element in the array
* @return array with warnings
* set the option for the db class
* @param string $option option name
* @param mixed $value value for the option
* @return mixed MDB_OK or MDB_Error
if(isset ($this->options[$option])) {
* returns the value of an option
* @param string $option option name
* @return mixed the option value or error object
if(isset ($this->options[$option])) {
* Create a new MDB connection object and connect to the specified
* @param mixed $dbinfo 'data source name', see the MDB::parseDSN
* method for a description of the dsn format.
* Can also be specified as an array of the
* format returned by MDB::parseDSN.
* Finally you can also pass an existing db
* @param mixed $options An associative array of option names and
* @return mixed MDB_OK on success, or a MDB error object
function &connect(&$dbinfo, $options = FALSE )
* Log out and disconnect from the database.
* Select a different database
* @param string $name name of the database that should be selected
* @return string name of the database previously connected to
return($this->database->setDatabase ($name));
* create a table and inititialize the table if data is available
* @param string $table_name name of the table to be created
* @param array $table multi dimensional array that containts the
* structure and optional data of the table
* @param boolean $overwrite determine if the table/index should be
overwritten if it already exists
* @return mixed MDB_OK on success, or a MDB error object
function _createTable ($table_name, $table, $overwrite = FALSE )
$result = $this->database->createTable ($table_name, $table['FIELDS']);
$this->warnings[] = 'Table already exists: '. $table_name;
$this->database->debug ('Overwritting Table');
$result = $this->database->dropTable ($table_name);
$result = $this->database->createTable ($table_name, $table['FIELDS']);
$this->database->debug ('Create table error: '. $table_name);
if(isset ($table['initialization']) && is_array($table['initialization'])) {
foreach($table['initialization'] as $instruction) {
switch($instruction['type']) {
$query_fields = $query_values = array ();
if(isset ($instruction['FIELDS']) && is_array($instruction['FIELDS'])) {
foreach($instruction['FIELDS'] as $field_name => $field) {
$query_fields[] = $field_name;
$query_fields = implode(',',$query_fields);
$query_values = implode(',',$query_values);
$result = $prepared_query = $this->database->prepareQuery (
" INSERT INTO $table_name ($query_fields) VALUES ($query_values)" );
if(isset ($instruction['FIELDS']) && is_array($instruction['FIELDS'])) {
foreach($instruction['FIELDS'] as $field_name => $field) {
switch($table['FIELDS'][$field_name]['type']) {
$result = $this->database->setParamInteger ($prepared_query,
$field_number, intval($field));
$result = $this->database->setParamText ($prepared_query,
$result = $this->database->setParamClob ($prepared_query,
$field_number, $lobs[$lob], $field_name);
$result = $this->database->setParamBlob ($prepared_query,
$field_number, $lobs[$lob], $field_name);
$result = $this->database->setParamBoolean ($prepared_query,
$field_number, intval($field));
$result = $this->database->setParamDate ($prepared_query,
$result = $this->database->setParamTimestamp ($prepared_query,
$result = $this->database->setParamTime ($prepared_query,
$result = $this->database->setParamFloat ($prepared_query,
$result = $this->database->setParamDecimal ($prepared_query,
'type "'. $field['type']. '" is not yet supported');
$result = $this->database->executeQuery ($prepared_query);
for($lob = 0; $lob < count($lobs); $lob++ ) {
$this->database->destroyLOB ($lobs[$lob]);
$this->database->freePreparedQuery ($prepared_query);
if(!$this->database->support ('Indexes')) {
'indexes are not supported'));
foreach($table['INDEXES'] as $index_name => $index) {
$result = $this->database->createIndex ($table_name, $index_name, $index);
$this->warnings[] = 'Index already exists: '. $index_name;
$this->database->debug ('Overwritting Index');
$result = $this->database->dropIndex ($table_name, $index_name);
$result = $this->database->createIndex ($table_name, $index_name, $index);
$this->database->debug ('Create index error: '. $table_name);
$result = $this->database->dropTable ($table_name);
'could not drop the table ('
. $result->getMessage (). ' ('. $result->getUserinfo (),'))',
* @param string $table_name name of the table to be dropped
* @return mixed MDB_OK on success, or a MDB error object
function _dropTable ($table_name)
return($this->database->dropTable ($table_name));
* @param string $sequence_name name of the sequence to be created
* @param array $sequence multi dimensional array that containts the
* structure and optional data of the table
* @param string $created_on_table
* @param boolean $overwrite determine if the sequence should be overwritten
* @return mixed MDB_OK on success, or a MDB error object
function _createSequence ($sequence_name, $sequence, $created_on_table, $overwrite = FALSE )
if(!$this->database->support ('Sequences')) {
'sequences are not supported'));
if(!isset ($sequence_name) || !strcmp($sequence_name, '')) {
'no valid sequence name specified'));
$this->database->debug ('Create sequence: '. $sequence_name);
if(isset ($sequence['start']) && $sequence['start'] != '') {
$start = $sequence['start'];
} else if(isset ($sequence['on']) && !$created_on_table) {
$table = $sequence['on']['table'];
$field = $sequence['on']['field'];
if($this->database->support ('Summaryfunctions')) {
$start = $this->database->queryOne (" SELECT $field FROM $table" );
$result = $this->database->createSequence ($sequence_name, $start);
$this->warnings[] = 'Sequence already exists: '. $sequence_name;
$this->database->debug ('Overwritting Sequence');
$result = $this->database->dropSequence ($sequence_name);
$result = $this->database->createSequence ($sequence_name, $start);
$this->database->debug ('Create sequence error: '. $sequence_name);
* @param string $sequence_name name of the sequence to be dropped
* @return mixed MDB_OK on success, or a MDB error object
function _dropSequence ($sequence_name)
if(!$this->database->support ('Sequences')) {
'sequences are not supported'));
$this->database->debug ('Dropping sequence: '. $sequence_name);
if(!isset ($sequence_name) || !strcmp($sequence_name, '')) {
'no valid sequence name specified'));
return($this->database->dropSequence ($sequence_name));
* Create a database space within which may be created database objects
* like tables, indexes and sequences. The implementation of this function
* is highly DBMS specific and may require special permissions to run
* successfully. Consult the documentation or the DBMS drivers that you
* use to be aware of eventual configuration requirements.
* @return mixed MDB_OK on success, or a MDB error object
function _createDatabase ()
'no valid database name specified'));
$this->database->debug ('Overwritting Database');
$this->database->debug ('Create database error.');
if(($support_transactions = $this->database->support ('Transactions'))
$result = $this->_createTable ($table_name, $table, $overwrite);
$result = $this->_createSequence ($sequence_name, $sequence, 0 , $overwrite);
if($support_transactions) {
'Could not rollback the partially created database alterations ('
. $result->getMessage (). ' ('. $result->getUserinfo (),'))',
'the database was only partially created ('
. $result->getMessage (). ' ('. $result->getUserinfo (),'))',
if($support_transactions) {
$res = $this->database->autoCommit (TRUE );
'Could not end transaction after successfully created the database ('
. $res->getMessage (). ' ('. $res->getUserinfo (),'))',
$this->database->setDatabase ($previous_database_name);
'Could not drop the created database after unsuccessful creation attempt ('
. $res->getMessage (). ' ('. $res->getUserinfo (),'))',
// {{{ _addDefinitionChange()
* add change to an array of multiple changes
* @param string $definition
* @return mixed MDB_OK on success, or a MDB error object
function _addDefinitionChange (&$changes, $definition, $item, $change)
if(!isset ($changes[$definition][$item])) {
$changes[$definition][$item] = array ();
foreach($change as $change_data_name => $change_data) {
if(isset ($change_data) && is_array($change_data)) {
if(!isset ($changes[$definition][$item][$change_data_name])) {
$changes[$definition][$item][$change_data_name] = array ();
foreach($change_data as $change_part_name => $change_part) {
$changes[$definition][$item][$change_data_name][$change_part_name] = $change_part;
$changes[$definition][$item][$change_data_name] = $change_data;
// {{{ _compareDefinitions()
* compare a previous definition with the currenlty parsed definition
* @param array multi dimensional array that contains the previous definition
* @return mixed array of changes on success, or a MDB error object
function _compareDefinitions ($previous_definition)
$defined_tables = $changes = array ();
$was_table_name = $table['was'];
if(isset ($previous_definition['TABLES'][$table_name])
&& isset ($previous_definition['TABLES'][$table_name]['was'])
&& !strcmp($previous_definition['TABLES'][$table_name]['was'], $was_table_name)
$was_table_name = $table_name;
if(isset ($previous_definition['TABLES'][$was_table_name])) {
if(strcmp($was_table_name, $table_name)) {
$this->_addDefinitionChange ($changes, 'TABLES', $was_table_name, array ('name' => $table_name));
$this->database->debug (" Renamed table '$was_table_name' to '$table_name'" );
if(isset ($defined_tables[$was_table_name])) {
'the table "'. $was_table_name. '" was specified as base of more than of table of the database',
$defined_tables[$was_table_name] = 1;
$previous_fields = $previous_definition['TABLES'][$was_table_name]['FIELDS'];
$defined_fields = array ();
if(isset ($table['FIELDS']) && is_array($table['FIELDS'])) {
foreach($table['FIELDS'] as $field_name => $field) {
$was_field_name = $field['was'];
if(isset ($previous_fields[$field_name])
&& isset ($previous_fields[$field_name]['was'])
&& !strcmp($previous_fields[$field_name]['was'], $was_field_name)
$was_field_name = $field_name;
if(isset ($previous_fields[$was_field_name])) {
if(strcmp($was_field_name, $field_name)) {
$query = $this->database->getFieldDeclaration ($field_name, $field);
$this->_addDefinitionChange ($changes, 'TABLES', $was_table_name,
'RenamedFields' => array (
$was_field_name => array (
$this->database->debug (" Renamed field '$was_field_name' to '$field_name' in table '$table_name'" );
if(isset ($defined_fields[$was_field_name])) {
'the field "'. $was_table_name. '" was specified as base of more than one field of table',
$defined_fields[$was_field_name] = 1;
if($field['type'] == $previous_fields[$was_field_name]['type']) {
$previous_unsigned = isset ($previous_fields[$was_field_name]['unsigned']);
$unsigned = isset ($fields[$field_name]['unsigned']);
if(strcmp($previous_unsigned, $unsigned)) {
$change['unsigned'] = $unsigned;
$this->database->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'" );
$previous_length = (isset ($previous_fields[$was_field_name]['length']) ? $previous_fields[$was_field_name]['length'] : 0 );
$length = (isset ($field['length']) ? $field['length'] : 0 );
if(strcmp($previous_length, $length)) {
$change['length'] = $length;
$this->database->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'" );
'type "'. $field['type']. '" is not yet supported',
$previous_notnull = isset ($previous_fields[$was_field_name]['notnull']);
$notnull = isset ($field['notnull']);
if($previous_notnull != $notnull) {
$change['ChangedNotNull'] = 1;
$change['notnull'] = isset ($field['notnull']);
$this->database->debug (" Changed field '$field_name' notnull from $previous_notnull to $notnull in table '$table_name'" );
$previous_default = isset ($previous_fields[$was_field_name]['default']);
$default = isset ($field['default']);
if(strcmp($previous_default, $default)) {
$change['ChangedDefault'] = 1;
$change['default'] = $field['default'];
$this->database->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'" );
&& strcmp($previous_fields[$was_field_name]['default'], $field['default'])
$change['ChangedDefault'] = 1;
$change['default'] = $field['default'];
$this->database->debug (" Changed field '$field_name' default from '". $previous_fields[$was_field_name]['default']. "' to '". $fields[$field_name]['default']." ' in table '$table_name'" );
$change['type'] = $field['type'];
$this->database->debug (" Changed field '$field_name' type from '". $previous_fields[$was_field_name]['type']. "' to '". $fields[$field_name]['type']." ' in table '$table_name'" );
$query = $this->database->getFieldDeclaration ($field_name, $field);
$change['Declaration'] = $query;
$change['Definition'] = $field;
$this->_addDefinitionChange ($changes, 'TABLES', $was_table_name, array ('ChangedFields' => array ($field_name => $change)));
if(strcmp($field_name, $was_field_name)) {
'it was specified a previous field name ("'
. $was_field_name. '") for field "'. $field_name. '" of table "'
. $table_name. '" that does not exist',
$query = $this->database->getFieldDeclaration ($field_name, $field);
$change['Declaration'] = $query;
$this->_addDefinitionChange ($changes, 'TABLES', $table_name, array ('AddedFields' => array ($field_name => $change)));
$this->database->debug (" Added field '$field_name' to table '$table_name'" );
if(isset ($previous_fields) && is_array($previous_fields)) {
foreach ($previous_fields as $field_previous_name => $field_previous) {
if(!isset ($defined_fields[$field_previous_name])) {
$this->_addDefinitionChange ($changes, 'TABLES', $table_name, array ('RemovedFields' => array ($field_previous_name => array ())));
$this->database->debug (" Removed field '$field_name' from table '$table_name'" );
$previous_indexes = array ();
if(isset ($previous_definition['TABLES'][$was_table_name]['INDEXES'])
&& is_array($previous_definition['TABLES'][$was_table_name]['INDEXES'])
$previous_indexes = $previous_definition['TABLES'][$was_table_name]['INDEXES'];
$defined_indexes = array ();
foreach($indexes as $index_name => $index) {
$was_index_name = $index['was'];
if(isset ($previous_indexes[$index_name])
&& isset ($previous_indexes[$index_name]['was'])
&& !strcmp($previous_indexes[$index_name]['was'], $was_index_name)
$was_index_name = $index_name;
if(isset ($previous_indexes[$was_index_name])) {
if(strcmp($was_index_name, $index_name)) {
$change['name'] = $was_index_name;
$this->database->debug (" Changed index '$was_index_name' name to '$index_name' in table '$table_name'" );
if(isset ($defined_indexes[$was_index_name])) {
'the index "'. $was_index_name. '" was specified as base of'
. ' more than one index of table "'. $table_name. '"',
$defined_indexes[$was_index_name] = 1;
$previous_unique = isset ($previous_indexes[$was_index_name]['unique']);
$unique = isset ($index['unique']);
if($previous_unique != $unique) {
$change['ChangedUnique'] = 1;
$change['unique'] = $unique;
$this->database->debug (" Changed index '$index_name' unique from $previous_unique to $unique in table '$table_name'" );
$defined_fields = array ();
$previous_fields = $previous_indexes[$was_index_name]['FIELDS'];
if(isset ($index['FIELDS']) && is_array($index['FIELDS'])) {
foreach($index['FIELDS'] as $field_name => $field) {
if(isset ($previous_fields[$field_name])) {
$defined_fields[$field_name] = 1;
$sorting = (isset ($field['sorting']) ? $field['sorting'] : '');
$previous_sorting = (isset ($previous_fields[$field_name]['sorting']) ? $previous_fields[$field_name]['sorting'] : '');
if(strcmp($sorting, $previous_sorting)) {
$this->database->debug (" Changed index field '$field_name' sorting default from '$previous_sorting' to '$sorting' in table '$table_name'" );
$change['ChangedFields'] = 1;
$change['ChangedFields'] = 1;
$this->database->debug (" Added field '$field_name' to index '$index_name' of table '$table_name'" );
if(isset ($previous_fields) && is_array($previous_fields)) {
foreach($previous_fields as $field_name => $field) {
if(!isset ($defined_fields[$field_name])) {
$change['ChangedFields'] = 1;
$this->database->debug (" Removed field '$field_name' from index '$index_name' of table '$table_name'" );
$this->_addDefinitionChange ($changes, 'INDEXES', $table_name,array ('ChangedIndexes' => array ($index_name => $change)));
if(strcmp($index_name, $was_index_name)) {
'it was specified a previous index name ("'. $was_index_name
. ') for index "'. $index_name. '" of table "'. $table_name. '" that does not exist',
$this->_addDefinitionChange ($changes, 'INDEXES', $table_name,array ('AddedIndexes' => array ($index_name => $indexes[$index_name])));
$this->database->debug (" Added index '$index_name' to table '$table_name'" );
foreach($previous_indexes as $index_previous_name => $index_previous) {
if(!isset ($defined_indexes[$index_previous_name])) {
$this->_addDefinitionChange ($changes, 'INDEXES', $table_name, array ('RemovedIndexes' => array ($index_previous_name => $was_table_name)));
$this->database->debug (" Removed index '$index_name' from table '$table_name'" );
if(strcmp($table_name, $was_table_name)) {
'it was specified a previous table name ("'
. $was_table_name. '") for table "'. $table_name. '" that does not exist',
$this->_addDefinitionChange ($changes, 'TABLES', $table_name,array ('Add' => 1 ));
$this->database->debug (" Added table '$table_name'" );
if(isset ($previous_definition['TABLES']) && is_array($previous_definition['TABLES'])) {
foreach ($previous_definition['TABLES'] as $table_name => $table) {
if(!isset ($defined_tables[$table_name])) {
$this->_addDefinitionChange ($changes, 'TABLES', $table_name, array ('Remove' => 1 ));
$this->database->debug (" Removed table '$table_name'" );
$was_sequence_name = $sequence['was'];
if(isset ($previous_definition['SEQUENCES'][$sequence_name])
&& isset ($previous_definition['SEQUENCES'][$sequence_name]['was'])
&& !strcmp($previous_definition['SEQUENCES'][$sequence_name]['was'], $was_sequence_name)
$was_sequence_name = $sequence_name;
if(isset ($previous_definition['SEQUENCES'][$was_sequence_name])) {
if(strcmp($was_sequence_name, $sequence_name)) {
$this->_addDefinitionChange ($changes, 'SEQUENCES', $was_sequence_name,array ('name' => $sequence_name));
$this->database->debug (" Renamed sequence '$was_sequence_name' to '$sequence_name'" );
if(isset ($defined_sequences[$was_sequence_name])) {
'the sequence "'. $was_sequence_name. '" was specified as base'
. ' of more than of sequence of the database',
$defined_sequences[$was_sequence_name] = 1;
if(strcmp($sequence['start'], $previous_definition['SEQUENCES'][$was_sequence_name]['start'])) {
$this->database->debug (" Changed sequence '$sequence_name' start from '". $previous_definition['SEQUENCES'][$was_sequence_name]['start']. "' to '". $this->database_definition['SEQUENCES'][$sequence_name]['start']. "'");
if(strcmp($sequence['on']['table'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['table'])
|| strcmp($sequence['on']['field'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['field'])
$change['on'] = $sequence['on'];
$this->database->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']. "'");
$this->_addDefinitionChange ($changes, 'SEQUENCES', $was_sequence_name,array ('Change' => array ($sequence_name => array ($change))));
if(strcmp($sequence_name, $was_sequence_name)) {
'it was specified a previous sequence name ("'. $was_sequence_name
. '") for sequence "'. $sequence_name. '" that does not exist',
$this->_addDefinitionChange ($changes, 'SEQUENCES', $sequence_name, array ('Add' => 1 ));
$this->database->debug (" Added sequence '$sequence_name'" );
if(isset ($previous_definition['SEQUENCES']) && is_array($previous_definition['SEQUENCES'])) {
foreach ($previous_definition['SEQUENCES'] as $sequence_name => $sequence) {
if(!isset ($defined_sequences[$sequence_name])) {
$this->_addDefinitionChange ($changes, 'SEQUENCES', $sequence_name, array ('Remove' => 1 ));
$this->database->debug (" Removed sequence '$sequence_name'" );
* Execute the necessary actions to implement the requested changes
* in a database structure.
* @param array $previous_definition an associative array that contains
* the definition of the database structure before applying the requested
* changes. The definition of this array may be built separately, but
* usually it is built by the Parse method the Metabase parser class.
* @param array $changes an associative array that contains the definition of
* the changes that are meant to be applied to the database structure.
* @return mixed MDB_OK on success, or a MDB error object
function _alterDatabase ($previous_definition, $changes)
if(isset ($changes['TABLES']) && is_array($changes['TABLES'])) {
foreach($changes['TABLES'] as $table_name => $table) {
if(isset ($table['Add']) || isset ($table['Remove'])) {
$result = $this->database->alterTable ($table_name, $table, 1 );
if(isset ($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) {
if(!$this->database->support ('Sequences')) {
'sequences are not supported'));
foreach($changes['SEQUENCES'] as $sequence) {
if(isset ($sequence['Add'])
|| isset ($sequence['Remove'])
|| isset ($sequence['Change'])
'some sequences changes are not yet supported'));
if(isset ($changes['INDEXES']) && is_array($changes['INDEXES'])) {
if(!$this->database->support ('Indexes')) {
'indexes are not supported'));
foreach($changes['INDEXES'] as $index) {
$table_changes = count($index);
if(isset ($index['AddedIndexes'])) {
if(isset ($index['RemovedIndexes'])) {
if(isset ($index['ChangedIndexes'])) {
'index alteration not yet supported'));
if(($support_transactions = $this->database->support ('Transactions'))
if(isset ($changes['INDEXES']) && is_array($changes['INDEXES'])) {
foreach($changes['INDEXES'] as $index_name => $index) {
if(isset ($index['RemovedIndexes']) && is_array($index['RemovedIndexes'])) {
foreach($index['RemovedIndexes'] as $index_remove_name => $index_remove) {
$result = $this->database->dropIndex ($index_name,$index_remove_name);
foreach($index['ChangedIndexes'] as $index_changed_name => $index_changed) {
$was_name = (isset ($indexes[$name]['name']) ? $indexes[$index_changed_name]['name'] : $index_changed_name);
$result = $this->database->dropIndex ($index_name, $was_name);
if(!MDB::isError($result) && isset ($changes['TABLES'])
foreach($changes['TABLES'] as $table_name => $table) {
if(isset ($table['Remove'])) {
$result = $this->_dropTable ($table_name);
if(!isset ($table['Add'])) {
$result = $this->database->alterTable ($table_name, $changes['TABLES'][$table_name], 0 );
foreach($changes['TABLES'] as $table_name => $table) {
if(isset ($table['Add'])) {
if(!MDB::isError($result) && isset ($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) {
foreach($changes['SEQUENCES'] as $sequence_name => $sequence) {
if(isset ($sequence['Add'])) {
if(isset ($changes['TABLES'])
&& isset ($changes['TABLES'][$table_name])
&& isset ($changes['TABLES'][$table_name]['Add'])
$result = $this->_createSequence ($sequence_name,
if(isset ($sequence['Remove'])) {
if(!strcmp($error = $this->_dropSequence ($sequence_name), '')) {
if(isset ($sequence['Change'])) {
if(isset ($changes['TABLES'])
&& isset ($changes['TABLES'][$table_name])
&& isset ($changes['TABLES'][$table_name]['Add'])
&& !MDB::isError($result = $this->_createSequence (
$sequence_name, $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table), '')
'changing sequences is not yet supported'));
if(!MDB::isError($result) && isset ($changes['INDEXES']) && is_array($changes['INDEXES'])) {
foreach($changes['INDEXES'] as $table_name => $indexes) {
if(isset ($indexes['ChangedIndexes'])) {
$changedindexes = $indexes['ChangedIndexes'];
foreach($changedindexes as $index_name => $index) {
$result = $this->database->createIndex ($table_name, $index_name,
&& isset ($indexes['AddedIndexes'])
$addedindexes = $indexes['AddedIndexes'];
foreach($addedindexes as $index_name => $index) {
$result = $this->database->createIndex ($table_name, $index_name,
if($support_transactions) {
'Could not rollback the partially created database alterations ('
. $result->getMessage (). ' ('. $result->getUserinfo (),'))',
'the requested database alterations were only partially implemented ('
. $result->getMessage (). ' ('. $result->getUserinfo (),'))',
if($support_transactions) {
$result = $this->database->autoCommit (TRUE );
'Could not end transaction after successfully implemented the requested database alterations ('
. $result->getMessage (). ' ('. $result->getUserinfo (),'))',
$this->database->setDatabase ($previous_database_name);
// {{{ _escapeSpecialCharacters()
* add escapecharacters to all special characters in a string
* @param string $string string that should be escaped
* @return string escaped string
function _escapeSpecialCharacters ($string)
for($escaped = '', $character = 0;
switch($string[$character]) {
$code = ord($string[$character]);
$code = ord($string[$character]);
if($code < 32 || $code>127 ) {
$escaped .= $string[$character];
* dump the structure of a sequence
* @param string $sequence_name
* @return mixed string with xml seqeunce definition on success, or a MDB error object
function _dumpSequence ($sequence_name, $eol, $dump = MDB_MANAGER_DUMP_ALL )
$buffer = " $eol <sequence>$eol <name>$sequence_name</name>$eol";
if(isset ($sequence_definition['start'])) {
$start = $sequence_definition['start'];
$buffer .= " <start>$start</start>$eol";
if(isset ($sequence_definition['on'])) {
$buffer .= " <on>$eol <table>". $sequence_definition['on']['table']." </table>$eol <field>". $sequence_definition['on']['field']." </field>$eol </on>$eol";
$buffer .= " </sequence>$eol";
// {{{ parseDatabaseDefinitionFile()
* Parse a database definition file by creating a Metabase schema format
* parser object and passing the file contents as parser input data stream.
* @param string $input_file the path of the database schema file.
* @param array $variables an associative array that the defines the text
* string values that are meant to be used to replace the variables that are
* used in the schema description.
* @param bool $fail_on_invalid_names (optional) make function fail on invalid
* @return mixed MDB_OK on success, or a MDB error object
$parser = & new MDB_Parser ($variables, $fail_on_invalid_names);
$result = $parser->setInputFile ($input_file);
$result = $parser->parse ();
return($parser->database_definition );
// {{{ _debugDatabaseChanges()
* Dump the changes between two database definitions.
* @param array $changes an associative array that specifies the list
* of database definitions changes as returned by the _compareDefinitions
* manager class function.
* @return mixed MDB_OK on success, or a MDB error object
function _debugDatabaseChanges ($changes)
if(isset ($changes['TABLES'])) {
foreach($changes['TABLES'] as $table_name => $table)
if(isset ($table['Add'])) {
$this->database->debug (" \tAdded table '$table_name'" );
} elseif (isset ($table['Remove'])) {
$this->database->debug (" \tRemoved table '$table_name'" );
if(isset ($table['name'])) {
$this->database->debug (" \tRenamed table '$table_name' to '". $table['name']. "'");
if(isset ($table['AddedFields'])) {
foreach($table['AddedFields'] as $field_name => $field) {
$this->database->debug ("\tAdded field '". $field_name. "'");
if(isset ($table['RemovedFields'])) {
foreach($table['RemovedFields'] as $field_name => $field) {
$this->database->debug ("\tRemoved field '". $field_name. "'");
if(isset ($table['RenamedFields'])) {
foreach($table['RenamedFields'] as $field_name => $field) {
$this->database->debug ("\tRenamed field '". $field_name. "' to '". $field['name']. "'");
if(isset ($table['ChangedFields'])) {
foreach($table['ChangedFields'] as $field_name => $field) {
if(isset ($field['type'])) {
" \tChanged field '$field_name' type to '". $field['type']. "'");
if(isset ($field['unsigned'])) {
" \tChanged field '$field_name' type to '".
($field['unsigned'] ? '' : 'not '). "unsigned'");
if(isset ($field['length'])) {
" \tChanged field '$field_name' length to '".
($field['length'] == 0 ? 'no length' : $field['length']). "'");
if(isset ($field['ChangedDefault'])) {
" \tChanged field '$field_name' default to ".
(isset ($field['default']) ? "'". $field['default']. "'" : 'NULL'));
if(isset ($field['ChangedNotNull'])) {
" \tChanged field '$field_name' notnull to ". (isset ($field['notnull']) ? "'1'" : '0'));
if(isset ($changes['SEQUENCES'])) {
foreach($changes['SEQUENCES'] as $sequence_name => $sequence)
$this->database->debug (" $sequence_name:" );
if(isset ($sequence['Add'])) {
$this->database->debug (" \tAdded sequence '$sequence_name'" );
} elseif (isset ($sequence['Remove'])) {
$this->database->debug (" \tRemoved sequence '$sequence_name'" );
if(isset ($sequence['name'])) {
$this->database->debug (" \tRenamed sequence '$sequence_name' to '". $sequence['name']. "'");
if(isset ($sequence['Change'])) {
foreach($sequence['Change'] as $sequence_name => $sequence) {
if(isset ($sequence['start'])) {
" \tChanged sequence '$sequence_name' start to '". $sequence['start']. "'");
if(isset ($changes['INDEXES'])) {
foreach($changes['INDEXES'] as $table_name => $table)
if(isset ($table['AddedIndexes'])) {
foreach($table['AddedIndexes'] as $index_name => $index) {
$this->database->debug ("\tAdded index '". $index_name." ' of table '$table_name'" );
if(isset ($table['RemovedIndexes'])) {
foreach($table['RemovedIndexes'] as $index_name => $index) {
$this->database->debug ("\tRemoved index '". $index_name." ' of table '$table_name'" );
if(isset ($table['ChangedIndexes'])) {
foreach($table['ChangedIndexes'] as $index_name => $index) {
if(isset ($index['name'])) {
"\tRenamed index '". $index_name. "' to '". $index['name']." ' on table '$table_name'" );
if(isset ($index['ChangedUnique'])) {
"\tChanged index '". $index_name. "' unique to '".
isset ($index['unique'])." ' on table '$table_name'" );
if(isset ($index['ChangedFields'])) {
$this->database->debug ("\tChanged index '". $index_name." ' on table '$table_name'" );
// {{{ _dumpDatabaseContents()
* Parse a database schema definition file and dump the respective structure
* @param string $schema_file path of the database schema file.
* @param mixed $setup_arguments an associative array that takes pairs of tag names and values
* that define the setup arguments that are passed to the
* MDB_Manager::connect function.
* @param array $dump_arguments an associative array that takes pairs of tag names and values
* that define dump options as defined for the MDB_Manager::DumpDatabase
* @param array $variables an associative array that the defines the text string values
* that are meant to be used to replace the variables that are used in the
* schema description as defined for the
* MDB_Manager::parseDatabaseDefinitionFile function.
* @return mixed MDB_OK on success, or a MDB error object
function _dumpDatabaseContents ($schema_file, $setup_arguments, $dump_arguments, $variables)
$variables, $this->options['fail_on_invalid_names']);
return($database_definition);
$result = $this->connect($setup_arguments);
// {{{ getDefinitionFromDatabase()
* Attempt to reverse engineer a schema structure from an existing MDB
* This method can be used if no xml schema file exists yet.
* The resulting xml schema file may need some manual adjustments.
* @return mixed MDB_OK or array with all ambiguities on success, or a MDB error object
$database = $this->database->database_name;
return('it was not specified a valid database name');
$tables = $this->database->listTables ();
for($table = 0; $table < count($tables); $table++ ) {
$table_name = $tables[$table];
$fields = $this->database->listTableFields ($table_name);
for($field = 0; $field < count($fields); $field++ )
$field_name = $fields[$field];
$definition = $this->database->getTableFieldDefinition ($table_name, $field_name);
$field_choices = count($definition[0 ]);
$warning = " There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
foreach($definition[0 ] as $field_choice) {
$this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'][] = $field_choice;
$warning .= 'choice #'. ($field_choice_cnt). ': '. serialize($field_choice);
if(isset ($definition[1 ])) {
$sequence = $definition[1 ]['definition'];
$sequence_name = $definition[1 ]['name'];
$this->database->debug ('Implicitly defining sequence: '. $sequence_name);
if(isset ($definition[2 ])) {
$index = $definition[2 ]['definition'];
$index_name = $definition[2 ]['name'];
$this->database->debug ('Implicitly defining index: '. $index_name);
$indexes = $this->database->listTableIndexes ($table_name);
for($index = 0 , $index_cnt = count($indexes); $index < $index_cnt; $index++ )
$index_name = $indexes[$index];
$definition = $this->database->getTableIndexDefinition ($table_name, $index_name);
// ensure that all fields that have an index on them are set to not null
foreach($index_check_null['FIELDS'] as $field_name_check_null => $field_check_null) {
$this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name_check_null]['notnull'] = 1;
// ensure that all fields that are set to not null also have a default value
foreach($this->database_definition['TABLES'][$table_name]['FIELDS'] as $field_set_default_name => $field_set_default) {
if(isset ($field_set_default['notnull']) && $field_set_default['notnull']
&& !isset ($field_set_default['default'])
$this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = 0;
if(isset ($field_set_default['CHOICES']) && is_array($field_set_default['CHOICES'])) {
foreach($field_set_default['CHOICES'] as $field_choices_set_default_name => $field_choices_set_default) {
if(isset ($field_choices_set_default['notnull'])
&& $field_choices_set_default['notnull']
&& !isset ($field_choices_set_default['default'])
if(isset ($this->default_values[$field_choices_set_default['type']])) {
$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']];
$this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES']
[$field_choices_set_default_name]['default'] = 0;
$sequences = $this->database->listSequences ();
for($sequence = 0; $sequence < count($sequences); $sequence++ ) {
$sequence_name = $sequences[$sequence];
$definition = $this->database->getSequenceDefinition ($sequence_name);
* Dump a previously parsed database structure in the Metabase schema
* XML based format suitable for the Metabase parser. This function
* may optionally dump the database definition with initialization
* commands that specify the data that is currently present in the tables.
* @param array $arguments an associative array that takes pairs of tag
* names and values that define dump options.
* 'Definition' => Boolean
* TRUE : dump currently parsed definition
* default: dump currently connected database
* 'Output_Mode' => String
* 'file' : dump into a file
* default: dump using a function
* depending on the 'Output_Mode'
* end of line delimiter that should be used
* @param integer $dump constant that determines what data to dump
* MDB_MANAGER_DUMP_ALL : the entire db
* MDB_MANAGER_DUMP_STRUCTURE : only the structure of the db
* MDB_MANAGER_DUMP_CONTENT : only the content of the db
* @return mixed MDB_OK on success, or a MDB error object
function dumpDatabase($arguments, $dump = MDB_MANAGER_DUMP_ALL )
if(isset ($arguments['Definition']) && $arguments['Definition']) {
NULL , NULL , 'please connect to a RDBMS first'));
$dump_definition = FALSE;
if(isset ($arguments['Output'])) {
if(isset ($arguments['Output_Mode']) && $arguments['Output_Mode'] == 'file') {
$fp = fopen($arguments['Output'], 'w');
$output = $arguments['Output'];
'no valid output function specified'));
'no output method specified'));
if(isset ($arguments['EndOfLine'])) {
$eol = $arguments['EndOfLine'];
if(isset ($sequence['on'])) {
$table = $sequence['on']['table'];
$sequences[$table][] = $sequence_name;
$buffer = ('<?xml version="1.0" encoding="ISO-8859-1" ?>'. $eol);
$buffer = (" $eol <table>$eol$eol <name>$table_name</name>$eol" );
$buffer .= (" $eol <declaration>$eol" );
if(isset ($table['FIELDS']) && is_array($table['FIELDS'])) {
foreach($table['FIELDS'] as $field_name => $field) {
if(!isset ($field['type'])) {
'it was not specified the type of the field "'. $field_name. '" of the table "'. $table_name));
$buffer .= (" $eol <field>$eol <name>$field_name</name>$eol <type>". $field['type']." </type>$eol" );
$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.";
if(isset ($field['unsigned'])) {
$buffer .= (" <unsigned>1</unsigned>$eol" );
if(isset ($field['length'])) {
$buffer .= (' <length>'. $field['length']." </length>$eol" );
return('type "'. $field['type']. '" is not yet supported');
if(isset ($field['notnull'])) {
$buffer .= (" <notnull>1</notnull>$eol" );
if(isset ($field['default'])) {
$buffer .= (' <default>'. $this->_escapeSpecialCharacters ($field['default'])." </default>$eol" );
$buffer .= (" </field>$eol" );
if(isset ($table['INDEXES']) && is_array($table['INDEXES'])) {
foreach($table['INDEXES'] as $index_name => $index) {
$buffer .= (" $eol <index>$eol <name>$index_name</name>$eol" );
if(isset ($index['unique'])) {
$buffer .= (" <unique>1</unique>$eol" );
foreach($index['FIELDS'] as $field_name => $field) {
$buffer .= (" <field>$eol <name>$field_name</name>$eol" );
if(is_array($field) && isset ($field['sorting'])) {
$buffer .= (' <sorting>'. $field['sorting']." </sorting>$eol" );
$buffer .= (" </field>$eol" );
$buffer .= (" </index>$eol" );
$buffer .= (" $eol </declaration>$eol" );
if(isset ($table['initialization']) && is_array($table['initialization'])) {
$buffer = (" $eol <initialization>$eol" );
foreach($table['initialization'] as $instruction_name => $instruction) {
switch($instruction['type']) {
$buffer .= (" $eol <insert>$eol" );
foreach($instruction['FIELDS'] as $field_name => $field) {
$buffer .= (" $eol <field>$eol <name>$field_name</name>$eol <value>". $this->_escapeSpecialCharacters ($field)." </value>$eol </field>$eol" );
$buffer .= (" $eol </insert>$eol" );
$buffer .= (" $eol </initialization>$eol" );
foreach($table['FIELDS'] as $field) {
$types[] = $field['type'];
$buffer = (" $eol <initialization>$eol" );
for($row = 0; $row < $rows; $row++ ) {
$buffer = (" $eol <insert>$eol" );
foreach($values as $field_name => $field) {
$buffer .= (" $eol <field>$eol <name>$field_name</name>$eol <value>" );
$buffer .= $this->_escapeSpecialCharacters ($values[$field_name]);
$buffer .= (" </value>$eol </field>$eol" );
$buffer .= (" $eol </insert>$eol" );
$buffer = (" $eol </initialization>$eol" );
$buffer .= (" $eol </table>$eol" );
if(isset ($sequences[$table_name])) {
for($sequence = 0 , $j = count($sequences[$table_name]);
$result = $this->_dumpSequence ($sequences[$table_name][$sequence], $eol, $dump);
if(isset ($sequences[''])) {
$sequence < count($sequences['']);
$result = $this->_dumpSequence ($sequences[''][$sequence], $eol, $dump);
$buffer = (" $eol</database>$eol" );
if(strcmp($previous_database_name, '')) {
$this->database->setDatabase ($previous_database_name);
* Compare the correspondent files of two versions of a database schema
* definition: the previously installed and the one that defines the schema
* that is meant to update the database.
* If the specified previous definition file does not exist, this function
* will create the database from the definition specified in the current
* If both files exist, the function assumes that the database was previously
* installed based on the previous schema file and will update it by just
* If this function succeeds, the contents of the current schema file are
* copied to replace the previous schema file contents. Any subsequent schema
* changes should only be done on the file specified by the $current_schema_file
* to let this function make a consistent evaluation of the exact changes that
* @param string $current_schema_file name of the updated database schema
* @param string $previous_schema_file name the previously installed database
* schema definition file.
* @param array $variables an associative array that is passed to the argument
* of the same name to the parseDatabaseDefinitionFile function. (there third
* @return mixed MDB_OK on success, or a MDB error object
function updateDatabase($current_schema_file, $previous_schema_file = FALSE , $variables = array ())
$variables, $this->options['fail_on_invalid_names']);
return($database_definition);
$this->expectError(MDB_ERROR_UNSUPPORTED);
$databases = $this->database->listDatabases();
if((MDB::isError($databases) || (is_array($databases) && in_array($this->database_definition['name'], $databases)))
&& $previous_schema_file && file_exists($previous_schema_file))
if($previous_schema_file && file_exists($previous_schema_file)) {
return($previous_definition);
$changes = $this->_compareDefinitions ($previous_definition);
if(isset ($changes) && is_array($changes)) {
$result = $this->_alterDatabase ($previous_definition, $changes);
$result = $this->_debugDatabaseChanges ($changes);
$result = $this->_createDatabase ();
if($copy && $previous_schema_file && !copy($current_schema_file, $previous_schema_file)) {
'Could not copy the new database definition file to the current file'));
Documentation generated on Mon, 11 Mar 2019 10:15:26 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|