+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema extends PEAR
+{
+ // {{{ properties
+
+ var $db;
+
+ var $warnings = array();
+
+ var $options = array(
+ 'fail_on_invalid_names' => true,
+ 'dtd_file' => false,
+ 'valid_types' => array(),
+ 'force_defaults' => true,
+ 'parser' => 'MDB2_Schema_Parser',
+ 'writer' => 'MDB2_Schema_Writer',
+ 'validate' => 'MDB2_Schema_Validate',
+ 'drop_missing_tables' => false
+ );
+
+ // }}}
+ // {{{ apiVersion()
+
+ /**
+ * Return the MDB2 API version
+ *
+ * @return string the MDB2 API version number
+ * @access public
+ */
+ function apiVersion()
+ {
+ return '0.4.3';
+ }
+
+ // }}}
+ // {{{ arrayMergeClobber()
+
+ /**
+ * Clobbers two arrays together
+ *
+ * @param array $a1 array that should be clobbered
+ * @param array $a2 array that should be clobbered
+ *
+ * @return array|false array on success and false on error
+ *
+ * @access public
+ * @author kc@hireability.com
+ */
+ function arrayMergeClobber($a1, $a2)
+ {
+ if (!is_array($a1) || !is_array($a2)) {
+ return false;
+ }
+ foreach ($a2 as $key => $val) {
+ if (is_array($val) && array_key_exists($key, $a1) && is_array($a1[$key])) {
+ $a1[$key] = MDB2_Schema::arrayMergeClobber($a1[$key], $val);
+ } else {
+ $a1[$key] = $val;
+ }
+ }
+ return $a1;
+ }
+
+ // }}}
+ // {{{ resetWarnings()
+
+ /**
+ * reset the warning array
+ *
+ * @access public
+ * @return void
+ */
+ function resetWarnings()
+ {
+ $this->warnings = array();
+ }
+
+ // }}}
+ // {{{ getWarnings()
+
+ /**
+ * Get all warnings in reverse order
+ *
+ * This means that the last warning is the first element in the array
+ *
+ * @return array with warnings
+ * @access public
+ * @see resetWarnings()
+ */
+ function getWarnings()
+ {
+ return array_reverse($this->warnings);
+ }
+
+ // }}}
+ // {{{ setOption()
+
+ /**
+ * Sets the option for the db class
+ *
+ * @param string $option option name
+ * @param mixed $value value for the option
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function setOption($option, $value)
+ {
+ if (isset($this->options[$option])) {
+ if (is_null($value)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'may not set an option to value null');
+ }
+ $this->options[$option] = $value;
+ return MDB2_OK;
+ }
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
+ "unknown option $option");
+ }
+
+ // }}}
+ // {{{ getOption()
+
+ /**
+ * returns the value of an option
+ *
+ * @param string $option option name
+ *
+ * @return mixed the option value or error object
+ * @access public
+ */
+ function getOption($option)
+ {
+ if (isset($this->options[$option])) {
+ return $this->options[$option];
+ }
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED,
+ null, null, "unknown option $option");
+ }
+
+ // }}}
+ // {{{ factory()
+
+ /**
+ * Create a new MDB2 object for the specified database type
+ * type
+ *
+ * @param string|array|MDB2_Driver_Common &$db 'data source name', see the
+ * MDB2::parseDSN method for a description of the dsn format.
+ * Can also be specified as an array of the
+ * format returned by @see MDB2::parseDSN.
+ * Finally you can also pass an existing db object to be used.
+ * @param array $options An associative array of option names and their values.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ * @see MDB2::parseDSN
+ */
+ function &factory(&$db, $options = array())
+ {
+ $obj =new MDB2_Schema();
+ $result = $obj->connect($db, $options);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ return $obj;
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Create a new MDB2 connection object and connect to the specified
+ * database
+ *
+ * @param string|array|MDB2_Driver_Common &$db 'data source name', see the
+ * MDB2::parseDSN method for a description of the dsn format.
+ * Can also be specified as an array of the
+ * format returned by MDB2::parseDSN.
+ * Finally you can also pass an existing db object to be used.
+ * @param array $options An associative array of option names and their values.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ * @see MDB2::parseDSN
+ */
+ function connect(&$db, $options = array())
+ {
+ $db_options = array();
+ if (is_array($options)) {
+ foreach ($options as $option => $value) {
+ if (array_key_exists($option, $this->options)) {
+ $result = $this->setOption($option, $value);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ } else {
+ $db_options[$option] = $value;
+ }
+ }
+ }
+ $this->disconnect();
+ if (!MDB2::isConnection($db)) {
+ $db =& MDB2::factory($db, $db_options);
+ }
+
+ if (PEAR::isError($db)) {
+ return $db;
+ }
+ $this->db =& $db;
+ $this->db->loadModule('Datatype');
+ $this->db->loadModule('Manager');
+ $this->db->loadModule('Reverse');
+ $this->db->loadModule('Function');
+ if (empty($this->options['valid_types'])) {
+ $this->options['valid_types'] = $this->db->datatype->getValidTypes();
+ }
+
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Log out and disconnect from the database.
+ *
+ * @access public
+ * @return void
+ */
+ function disconnect()
+ {
+ if (MDB2::isConnection($this->db)) {
+ $this->db->disconnect();
+ unset($this->db);
+ }
+ }
+
+ // }}}
+ // {{{ parseDatabaseDefinition()
+
+ /**
+ * Parse a database definition from a file or an array
+ *
+ * @param string|array $schema the database schema array or file name
+ * @param bool $skip_unreadable if non readable files should be skipped
+ * @param array $variables 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 make function fail on invalid names
+ * @param array $structure database structure definition
+ *
+ * @access public
+ * @return array
+ */
+ function parseDatabaseDefinition($schema, $skip_unreadable = false, $variables = array(),
+ $fail_on_invalid_names = true, $structure = false)
+ {
+ $database_definition = false;
+ if (is_string($schema)) {
+ // if $schema is not readable then we just skip it
+ // and simply copy the $current_schema file to that file name
+ if (is_readable($schema)) {
+ $database_definition = $this->parseDatabaseDefinitionFile($schema, $variables, $fail_on_invalid_names, $structure);
+ }
+ } elseif (is_array($schema)) {
+ $database_definition = $schema;
+ }
+ if (!$database_definition && !$skip_unreadable) {
+ $database_definition = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'invalid data type of schema or unreadable data source');
+ }
+ return $database_definition;
+ }
+
+ // }}}
+ // {{{ parseDatabaseDefinitionFile()
+
+ /**
+ * Parse a database definition file by creating a schema format
+ * parser object and passing the file contents as parser input data stream.
+ *
+ * @param string $input_file the database schema file.
+ * @param array $variables 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 make function fail on invalid names
+ * @param array $structure database structure definition
+ *
+ * @access public
+ * @return array
+ */
+ function parseDatabaseDefinitionFile($input_file, $variables = array(),
+ $fail_on_invalid_names = true, $structure = false)
+ {
+ $dtd_file = $this->options['dtd_file'];
+ if ($dtd_file) {
+ include_once 'XML/DTD/XmlValidator.php';
+ $dtd =new XML_DTD_XmlValidator;
+ if (!$dtd->isValid($dtd_file, $input_file)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_PARSE, null, null, $dtd->getMessage());
+ }
+ }
+
+ $class_name = $this->options['parser'];
+
+ $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $parser =new $class_name($variables, $fail_on_invalid_names, $structure, $this->options['valid_types'], $this->options['force_defaults']);
+ $result = $parser->setInputFile($input_file);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $result = $parser->parse();
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ if (PEAR::isError($parser->error)) {
+ return $parser->error;
+ }
+
+ return $parser->database_definition;
+ }
+
+ // }}}
+ // {{{ getDefinitionFromDatabase()
+
+ /**
+ * Attempt to reverse engineer a schema structure from an existing MDB2
+ * This method can be used if no xml schema file exists yet.
+ * The resulting xml schema file may need some manual adjustments.
+ *
+ * @return array|MDB2_Error array with definition or error object
+ * @access public
+ */
+ function getDefinitionFromDatabase()
+ {
+ $database = $this->db->database_name;
+ if (empty($database)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'it was not specified a valid database name');
+ }
+ $class_name = $this->options['validate'];
+
+ $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $val =new $class_name($this->options['fail_on_invalid_names'], $this->options['valid_types'], $this->options['force_defaults']);
+
+ $database_definition = array(
+ 'name' => $database,
+ 'create' => true,
+ 'overwrite' => false,
+ 'charset' => 'utf8',
+ 'description' => '',
+ 'comments' => '',
+ 'tables' => array(),
+ 'sequences' => array(),
+ );
+
+ $tables = $this->db->manager->listTables();
+ if (PEAR::isError($tables)) {
+ return $tables;
+ }
+
+ foreach ($tables as $table_name) {
+ $fields = $this->db->manager->listTableFields($table_name);
+ if (PEAR::isError($fields)) {
+ return $fields;
+ }
+
+ $database_definition['tables'][$table_name] = array(
+ 'was' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'fields' => array(),
+ 'indexes' => array(),
+ 'constraints' => array(),
+ 'initialization' => array()
+ );
+
+ $table_definition =& $database_definition['tables'][$table_name];
+ foreach ($fields as $field_name) {
+ $definition = $this->db->reverse->getTableFieldDefinition($table_name, $field_name);
+ if (PEAR::isError($definition)) {
+ return $definition;
+ }
+
+ if (!empty($definition[0]['autoincrement'])) {
+ $definition[0]['default'] = '0';
+ }
+
+ $table_definition['fields'][$field_name] = $definition[0];
+
+ $field_choices = count($definition);
+ if ($field_choices > 1) {
+ $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
+
+ $field_choice_cnt = 1;
+
+ $table_definition['fields'][$field_name]['choices'] = array();
+ foreach ($definition as $field_choice) {
+ $table_definition['fields'][$field_name]['choices'][] = $field_choice;
+
+ $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
+ $field_choice_cnt++;
+ }
+ $this->warnings[] = $warning;
+ }
+
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * fields which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateField(array(), $table_definition['fields'][$field_name], $field_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+
+ $keys = array();
+
+ $indexes = $this->db->manager->listTableIndexes($table_name);
+ if (PEAR::isError($indexes)) {
+ return $indexes;
+ }
+
+ if (is_array($indexes)) {
+ foreach ($indexes as $index_name) {
+ $this->db->expectError(MDB2_ERROR_NOT_FOUND);
+ $definition = $this->db->reverse->getTableIndexDefinition($table_name, $index_name);
+ $this->db->popExpect();
+ if (PEAR::isError($definition)) {
+ if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
+ continue;
+ }
+ return $definition;
+ }
+
+ $keys[$index_name] = $definition;
+ }
+ }
+
+ $constraints = $this->db->manager->listTableConstraints($table_name);
+ if (PEAR::isError($constraints)) {
+ return $constraints;
+ }
+
+ if (is_array($constraints)) {
+ foreach ($constraints as $constraint_name) {
+ $this->db->expectError(MDB2_ERROR_NOT_FOUND);
+ $definition = $this->db->reverse->getTableConstraintDefinition($table_name, $constraint_name);
+ $this->db->popExpect();
+ if (PEAR::isError($definition)) {
+ if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) {
+ continue;
+ }
+ return $definition;
+ }
+
+ $keys[$constraint_name] = $definition;
+ }
+ }
+
+ foreach ($keys as $key_name => $definition) {
+ if (array_key_exists('foreign', $definition)
+ && $definition['foreign']
+ ) {
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * foreign keys which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateConstraint(array(), $definition, $key_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ foreach ($definition['fields'] as $field_name => $field) {
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * referencing fields which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateConstraintField(array(), $field_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $definition['fields'][$field_name] = '';
+ }
+
+ foreach ($definition['references']['fields'] as $field_name => $field) {
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * referenced fields which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateConstraintReferencedField(array(), $field_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $definition['references']['fields'][$field_name] = '';
+ }
+
+ $table_definition['constraints'][$key_name] = $definition;
+ } else {
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * indices which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateIndex(array(), $definition, $key_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ foreach ($definition['fields'] as $field_name => $field) {
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * index fields which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateIndexField(array(), $field, $field_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $definition['fields'][$field_name] = $field;
+ }
+
+ $table_definition['indexes'][$key_name] = $definition;
+ }
+ }
+
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * tables which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateTable(array(), $table_definition, $table_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ }
+
+ $sequences = $this->db->manager->listSequences();
+ if (PEAR::isError($sequences)) {
+ return $sequences;
+ }
+
+ if (is_array($sequences)) {
+ foreach ($sequences as $sequence_name) {
+ $definition = $this->db->reverse->getSequenceDefinition($sequence_name);
+ if (PEAR::isError($definition)) {
+ return $definition;
+ }
+ if (isset($database_definition['tables'][$sequence_name])
+ && isset($database_definition['tables'][$sequence_name]['indexes'])
+ ) {
+ foreach ($database_definition['tables'][$sequence_name]['indexes'] as $index) {
+ if (isset($index['primary']) && $index['primary']
+ && count($index['fields'] == 1)
+ ) {
+ $definition['on'] = array(
+ 'table' => $sequence_name,
+ 'field' => key($index['fields']),
+ );
+ break;
+ }
+ }
+ }
+
+ /**
+ * The first parameter is used to verify if there are duplicated
+ * sequences which we can guarantee that won't happen when reverse engineering
+ */
+ $result = $val->validateSequence(array(), $definition, $sequence_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ $database_definition['sequences'][$sequence_name] = $definition;
+ }
+ }
+
+ $result = $val->validateDatabase($database_definition);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ return $database_definition;
+ }
+
+ // }}}
+ // {{{ createTableIndexes()
+
+ /**
+ * A method to create indexes for an existing table
+ *
+ * @param string $table_name Name of the table
+ * @param array $indexes An array of indexes to be created
+ * @param boolean $overwrite If the table/index should be overwritten if it already exists
+ *
+ * @return mixed MDB2_Error if there is an error creating an index, MDB2_OK otherwise
+ * @access public
+ */
+ function createTableIndexes($table_name, $indexes, $overwrite = false)
+ {
+ if (!$this->db->supports('indexes')) {
+ $this->db->debug('Indexes are not supported', __FUNCTION__);
+ return MDB2_OK;
+ }
+
+ $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
+ foreach ($indexes as $index_name => $index) {
+
+ // Does the index already exist, and if so, should it be overwritten?
+ $create_index = true;
+ $this->db->expectError($errorcodes);
+ if (!empty($index['primary']) || !empty($index['unique'])) {
+ $current_indexes = $this->db->manager->listTableConstraints($table_name);
+ } else {
+ $current_indexes = $this->db->manager->listTableIndexes($table_name);
+ }
+
+ $this->db->popExpect();
+ if (PEAR::isError($current_indexes)) {
+ if (!MDB2::isError($current_indexes, $errorcodes)) {
+ return $current_indexes;
+ }
+ } elseif (is_array($current_indexes) && in_array($index_name, $current_indexes)) {
+ if (!$overwrite) {
+ $this->db->debug('Index already exists: '.$index_name, __FUNCTION__);
+ $create_index = false;
+ } else {
+ $this->db->debug('Preparing to overwrite index: '.$index_name, __FUNCTION__);
+
+ $this->db->expectError(MDB2_ERROR_NOT_FOUND);
+ if (!empty($index['primary']) || !empty($index['unique'])) {
+ $result = $this->db->manager->dropConstraint($table_name, $index_name);
+ } else {
+ $result = $this->db->manager->dropIndex($table_name, $index_name);
+ }
+ $this->db->popExpect();
+ if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) {
+ return $result;
+ }
+ }
+ }
+
+ // Check if primary is being used and if it's supported
+ if (!empty($index['primary']) && !$this->db->supports('primary_key')) {
+
+ // Primary not supported so we fallback to UNIQUE and making the field NOT NULL
+ $index['unique'] = true;
+
+ $changes = array();
+
+ foreach ($index['fields'] as $field => $empty) {
+ $field_info = $this->db->reverse->getTableFieldDefinition($table_name, $field);
+ if (PEAR::isError($field_info)) {
+ return $field_info;
+ }
+ if (!$field_info[0]['notnull']) {
+ $changes['change'][$field] = $field_info[0];
+
+ $changes['change'][$field]['notnull'] = true;
+ }
+ }
+ if (!empty($changes)) {
+ $this->db->manager->alterTable($table_name, $changes, false);
+ }
+ }
+
+ // Should the index be created?
+ if ($create_index) {
+ if (!empty($index['primary']) || !empty($index['unique'])) {
+ $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
+ } else {
+ $result = $this->db->manager->createIndex($table_name, $index_name, $index);
+ }
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ createTableConstraints()
+
+ /**
+ * A method to create foreign keys for an existing table
+ *
+ * @param string $table_name Name of the table
+ * @param array $constraints An array of foreign keys to be created
+ * @param boolean $overwrite If the foreign key should be overwritten if it already exists
+ *
+ * @return mixed MDB2_Error if there is an error creating a foreign key, MDB2_OK otherwise
+ * @access public
+ */
+ function createTableConstraints($table_name, $constraints, $overwrite = false)
+ {
+ if (!$this->db->supports('indexes')) {
+ $this->db->debug('Indexes are not supported', __FUNCTION__);
+ return MDB2_OK;
+ }
+
+ $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
+ foreach ($constraints as $constraint_name => $constraint) {
+
+ // Does the foreign key already exist, and if so, should it be overwritten?
+ $create_constraint = true;
+ $this->db->expectError($errorcodes);
+ $current_constraints = $this->db->manager->listTableConstraints($table_name);
+ $this->db->popExpect();
+ if (PEAR::isError($current_constraints)) {
+ if (!MDB2::isError($current_constraints, $errorcodes)) {
+ return $current_constraints;
+ }
+ } elseif (is_array($current_constraints) && in_array($constraint_name, $current_constraints)) {
+ if (!$overwrite) {
+ $this->db->debug('Foreign key already exists: '.$constraint_name, __FUNCTION__);
+ $create_constraint = false;
+ } else {
+ $this->db->debug('Preparing to overwrite foreign key: '.$constraint_name, __FUNCTION__);
+ $result = $this->db->manager->dropConstraint($table_name, $constraint_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+
+ // Should the foreign key be created?
+ if ($create_constraint) {
+ $result = $this->db->manager->createConstraint($table_name, $constraint_name, $constraint);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ createTable()
+
+ /**
+ * 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 contains the
+ * structure and optional data of the table
+ * @param bool $overwrite if the table/index should be overwritten if it already exists
+ * @param array $options an array of options to be passed to the database specific driver
+ * version of MDB2_Driver_Manager_Common::createTable().
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function createTable($table_name, $table, $overwrite = false, $options = array())
+ {
+ $create = true;
+
+ $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
+
+ $this->db->expectError($errorcodes);
+
+ $tables = $this->db->manager->listTables();
+
+ $this->db->popExpect();
+ if (PEAR::isError($tables)) {
+ if (!MDB2::isError($tables, $errorcodes)) {
+ return $tables;
+ }
+ } elseif (is_array($tables) && in_array($table_name, $tables)) {
+ if (!$overwrite) {
+ $create = false;
+ $this->db->debug('Table already exists: '.$table_name, __FUNCTION__);
+ } else {
+ $result = $this->db->manager->dropTable($table_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $this->db->debug('Overwritting table: '.$table_name, __FUNCTION__);
+ }
+ }
+
+ if ($create) {
+ $result = $this->db->manager->createTable($table_name, $table['fields'], $options);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+
+ if (!empty($table['initialization']) && is_array($table['initialization'])) {
+ $result = $this->initializeTable($table_name, $table);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+
+ if (!empty($table['indexes']) && is_array($table['indexes'])) {
+ $result = $this->createTableIndexes($table_name, $table['indexes'], $overwrite);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+
+ if (!empty($table['constraints']) && is_array($table['constraints'])) {
+ $result = $this->createTableConstraints($table_name, $table['constraints'], $overwrite);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ initializeTable()
+
+ /**
+ * Inititialize the table with data
+ *
+ * @param string $table_name name of the table
+ * @param array $table multi dimensional array that contains the
+ * structure and optional data of the table
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function initializeTable($table_name, $table)
+ {
+ $query_insertselect = 'INSERT INTO %s (%s) (SELECT %s FROM %s %s)';
+
+ $query_insert = 'INSERT INTO %s (%s) VALUES (%s)';
+ $query_update = 'UPDATE %s SET %s %s';
+ $query_delete = 'DELETE FROM %s %s';
+
+ $table_name = $this->db->quoteIdentifier($table_name, true);
+
+ $result = MDB2_OK;
+
+ $support_transactions = $this->db->supports('transactions');
+
+ foreach ($table['initialization'] as $instruction) {
+ $query = '';
+ switch ($instruction['type']) {
+ case 'insert':
+ if (!isset($instruction['data']['select'])) {
+ $data = $this->getInstructionFields($instruction['data'], $table['fields']);
+ if (!empty($data)) {
+ $fields = implode(', ', array_keys($data));
+ $values = implode(', ', array_values($data));
+
+ $query = sprintf($query_insert, $table_name, $fields, $values);
+ }
+ } else {
+ $data = $this->getInstructionFields($instruction['data']['select'], $table['fields']);
+ $where = $this->getInstructionWhere($instruction['data']['select'], $table['fields']);
+
+ $select_table_name = $this->db->quoteIdentifier($instruction['data']['select']['table'], true);
+ if (!empty($data)) {
+ $fields = implode(', ', array_keys($data));
+ $values = implode(', ', array_values($data));
+
+ $query = sprintf($query_insertselect, $table_name, $fields, $values, $select_table_name, $where);
+ }
+ }
+ break;
+ case 'update':
+ $data = $this->getInstructionFields($instruction['data'], $table['fields']);
+ $where = $this->getInstructionWhere($instruction['data'], $table['fields']);
+ if (!empty($data)) {
+ array_walk($data, array($this, 'buildFieldValue'));
+ $fields_values = implode(', ', $data);
+
+ $query = sprintf($query_update, $table_name, $fields_values, $where);
+ }
+ break;
+ case 'delete':
+ $where = $this->getInstructionWhere($instruction['data'], $table['fields']);
+ $query = sprintf($query_delete, $table_name, $where);
+ break;
+ }
+ if ($query) {
+ if ($support_transactions && PEAR::isError($res = $this->db->beginNestedTransaction())) {
+ return $res;
+ }
+
+ $result = $this->db->exec($query);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ if ($support_transactions && PEAR::isError($res = $this->db->completeNestedTransaction())) {
+ return $res;
+ }
+ }
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ buildFieldValue()
+
+ /**
+ * Appends the contents of second argument + '=' to the beginning of first
+ * argument.
+ *
+ * Used with array_walk() in initializeTable() for UPDATEs.
+ *
+ * @param string &$element value of array's element
+ * @param string $key key of array's element
+ *
+ * @return void
+ *
+ * @access public
+ * @see MDB2_Schema::initializeTable()
+ */
+ function buildFieldValue(&$element, $key)
+ {
+ $element = $key."=$element";
+ }
+
+ // }}}
+ // {{{ getExpression()
+
+ /**
+ * Generates a string that represents a value that would be associated
+ * with a column in a DML instruction.
+ *
+ * @param array $element multi dimensional array that contains the
+ * structure of the current DML instruction.
+ * @param array $fields_definition multi dimensional array that contains the
+ * definition for current table's fields
+ * @param string $type type of given field
+ *
+ * @return string
+ *
+ * @access public
+ * @see MDB2_Schema::getInstructionFields(), MDB2_Schema::getInstructionWhere()
+ */
+ function getExpression($element, $fields_definition = array(), $type = null)
+ {
+ $str = '';
+ switch ($element['type']) {
+ case 'null':
+ $str .= 'NULL';
+ break;
+ case 'value':
+ $str .= $this->db->quote($element['data'], $type);
+ break;
+ case 'column':
+ $str .= $this->db->quoteIdentifier($element['data'], true);
+ break;
+ case 'function':
+ $arguments = array();
+ if (!empty($element['data']['arguments'])
+ && is_array($element['data']['arguments'])
+ ) {
+ foreach ($element['data']['arguments'] as $v) {
+ $arguments[] = $this->getExpression($v, $fields_definition);
+ }
+ }
+ if (method_exists($this->db->function, $element['data']['name'])) {
+ $user_func = array(&$this->db->function, $element['data']['name']);
+
+ $str .= call_user_func_array($user_func, $arguments);
+ } else {
+ $str .= $element['data']['name'].'(';
+ $str .= implode(', ', $arguments);
+ $str .= ')';
+ }
+ break;
+ case 'expression':
+ $type0 = $type1 = null;
+ if ($element['data']['operants'][0]['type'] == 'column'
+ && array_key_exists($element['data']['operants'][0]['data'], $fields_definition)
+ ) {
+ $type0 = $fields_definition[$element['data']['operants'][0]['data']]['type'];
+ }
+
+ if ($element['data']['operants'][1]['type'] == 'column'
+ && array_key_exists($element['data']['operants'][1]['data'], $fields_definition)
+ ) {
+ $type1 = $fields_definition[$element['data']['operants'][1]['data']]['type'];
+ }
+
+ $str .= '(';
+ $str .= $this->getExpression($element['data']['operants'][0], $fields_definition, $type1);
+ $str .= $this->getOperator($element['data']['operator']);
+ $str .= $this->getExpression($element['data']['operants'][1], $fields_definition, $type0);
+ $str .= ')';
+ break;
+ }
+
+ return $str;
+ }
+
+ // }}}
+ // {{{ getOperator()
+
+ /**
+ * Returns the matching SQL operator
+ *
+ * @param string $op parsed descriptive operator
+ *
+ * @return string matching SQL operator
+ *
+ * @access public
+ * @static
+ * @see MDB2_Schema::getExpression()
+ */
+ function getOperator($op)
+ {
+ switch ($op) {
+ case 'PLUS':
+ return ' + ';
+ case 'MINUS':
+ return ' - ';
+ case 'TIMES':
+ return ' * ';
+ case 'DIVIDED':
+ return ' / ';
+ case 'EQUAL':
+ return ' = ';
+ case 'NOT EQUAL':
+ return ' != ';
+ case 'LESS THAN':
+ return ' < ';
+ case 'GREATER THAN':
+ return ' > ';
+ case 'LESS THAN OR EQUAL':
+ return ' <= ';
+ case 'GREATER THAN OR EQUAL':
+ return ' >= ';
+ default:
+ return ' '.$op.' ';
+ }
+ }
+
+ // }}}
+ // {{{ getInstructionFields()
+
+ /**
+ * Walks the parsed DML instruction array, field by field,
+ * storing them and their processed values inside a new array.
+ *
+ * @param array $instruction multi dimensional array that contains the
+ * structure of the current DML instruction.
+ * @param array $fields_definition multi dimensional array that contains the
+ * definition for current table's fields
+ *
+ * @return array array of strings in the form 'field_name' => 'value'
+ *
+ * @access public
+ * @static
+ * @see MDB2_Schema::initializeTable()
+ */
+ function getInstructionFields($instruction, $fields_definition = array())
+ {
+ $fields = array();
+ if (!empty($instruction['field']) && is_array($instruction['field'])) {
+ foreach ($instruction['field'] as $field) {
+ $field_name = $this->db->quoteIdentifier($field['name'], true);
+
+ $fields[$field_name] = $this->getExpression($field['group'], $fields_definition);
+ }
+ }
+ return $fields;
+ }
+
+ // }}}
+ // {{{ getInstructionWhere()
+
+ /**
+ * Translates the parsed WHERE expression of a DML instruction
+ * (array structure) to a SQL WHERE clause (string).
+ *
+ * @param array $instruction multi dimensional array that contains the
+ * structure of the current DML instruction.
+ * @param array $fields_definition multi dimensional array that contains the
+ * definition for current table's fields.
+ *
+ * @return string SQL WHERE clause
+ *
+ * @access public
+ * @static
+ * @see MDB2_Schema::initializeTable()
+ */
+ function getInstructionWhere($instruction, $fields_definition = array())
+ {
+ $where = '';
+ if (!empty($instruction['where'])) {
+ $where = 'WHERE '.$this->getExpression($instruction['where'], $fields_definition);
+ }
+ return $where;
+ }
+
+ // }}}
+ // {{{ createSequence()
+
+ /**
+ * Create a sequence
+ *
+ * @param string $sequence_name name of the sequence to be created
+ * @param array $sequence multi dimensional array that contains the
+ * structure and optional data of the table
+ * @param bool $overwrite if the sequence should be overwritten if it already exists
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function createSequence($sequence_name, $sequence, $overwrite = false)
+ {
+ if (!$this->db->supports('sequences')) {
+ $this->db->debug('Sequences are not supported', __FUNCTION__);
+ return MDB2_OK;
+ }
+
+ $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
+ $this->db->expectError($errorcodes);
+ $sequences = $this->db->manager->listSequences();
+ $this->db->popExpect();
+ if (PEAR::isError($sequences)) {
+ if (!MDB2::isError($sequences, $errorcodes)) {
+ return $sequences;
+ }
+ } elseif (is_array($sequence) && in_array($sequence_name, $sequences)) {
+ if (!$overwrite) {
+ $this->db->debug('Sequence already exists: '.$sequence_name, __FUNCTION__);
+ return MDB2_OK;
+ }
+
+ $result = $this->db->manager->dropSequence($sequence_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $this->db->debug('Overwritting sequence: '.$sequence_name, __FUNCTION__);
+ }
+
+ $start = 1;
+ $field = '';
+ if (!empty($sequence['on'])) {
+ $table = $sequence['on']['table'];
+ $field = $sequence['on']['field'];
+
+ $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE);
+ $this->db->expectError($errorcodes);
+ $tables = $this->db->manager->listTables();
+ $this->db->popExpect();
+ if (PEAR::isError($tables) && !MDB2::isError($tables, $errorcodes)) {
+ return $tables;
+ }
+
+ if (!PEAR::isError($tables) && is_array($tables) && in_array($table, $tables)) {
+ if ($this->db->supports('summary_functions')) {
+ $query = "SELECT MAX($field) FROM ".$this->db->quoteIdentifier($table, true);
+ } else {
+ $query = "SELECT $field FROM ".$this->db->quoteIdentifier($table, true)." ORDER BY $field DESC";
+ }
+ $start = $this->db->queryOne($query, 'integer');
+ if (PEAR::isError($start)) {
+ return $start;
+ }
+ ++$start;
+ } else {
+ $this->warnings[] = 'Could not sync sequence: '.$sequence_name;
+ }
+ } elseif (!empty($sequence['start']) && is_numeric($sequence['start'])) {
+ $start = $sequence['start'];
+ $table = '';
+ }
+
+ $result = $this->db->manager->createSequence($sequence_name, $start);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ createDatabase()
+
+ /**
+ * 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.
+ *
+ * @param array $database_definition multi dimensional array that contains the current definition
+ * @param array $options an array of options to be passed to the
+ * database specific driver version of
+ * MDB2_Driver_Manager_Common::createTable().
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function createDatabase($database_definition, $options = array())
+ {
+ if (!isset($database_definition['name']) || !$database_definition['name']) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'no valid database name specified');
+ }
+
+ $create = (isset($database_definition['create']) && $database_definition['create']);
+ $overwrite = (isset($database_definition['overwrite']) && $database_definition['overwrite']);
+
+ /**
+ *
+ * We need to clean up database name before any query to prevent
+ * database driver from using a inexistent database
+ *
+ */
+ $previous_database_name = $this->db->setDatabase('');
+
+ // Lower / Upper case the db name if the portability deems so.
+ if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) {
+ $func = $this->db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper';
+
+ $db_name = $func($database_definition['name']);
+ } else {
+ $db_name = $database_definition['name'];
+ }
+
+ if ($create) {
+
+ $dbExists = $this->db->databaseExists($db_name);
+ if (PEAR::isError($dbExists)) {
+ return $dbExists;
+ }
+
+ if ($dbExists && $overwrite) {
+ $this->db->expectError(MDB2_ERROR_CANNOT_DROP);
+ $result = $this->db->manager->dropDatabase($db_name);
+ $this->db->popExpect();
+ if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_CANNOT_DROP)) {
+ return $result;
+ }
+ $dbExists = false;
+ $this->db->debug('Overwritting database: ' . $db_name, __FUNCTION__);
+ }
+
+ $dbOptions = array();
+ if (array_key_exists('charset', $database_definition)
+ && !empty($database_definition['charset'])) {
+ $dbOptions['charset'] = $database_definition['charset'];
+ }
+
+ if ($dbExists) {
+ $this->db->debug('Database already exists: ' . $db_name, __FUNCTION__);
+ if (!empty($dbOptions)) {
+ $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NO_PERMISSION);
+ $this->db->expectError($errorcodes);
+ $result = $this->db->manager->alterDatabase($db_name, $dbOptions);
+ $this->db->popExpect();
+ if (PEAR::isError($result) && !MDB2::isError($result, $errorcodes)) {
+ return $result;
+ }
+ }
+ $create = false;
+ } else {
+ $this->db->expectError(MDB2_ERROR_UNSUPPORTED);
+ $result = $this->db->manager->createDatabase($db_name, $dbOptions);
+ $this->db->popExpect();
+ if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_UNSUPPORTED)) {
+ return $result;
+ }
+ $this->db->debug('Creating database: ' . $db_name, __FUNCTION__);
+ }
+ }
+
+ $this->db->setDatabase($db_name);
+ if (($support_transactions = $this->db->supports('transactions'))
+ && PEAR::isError($result = $this->db->beginNestedTransaction())
+ ) {
+ return $result;
+ }
+
+ $created_objects = 0;
+ if (isset($database_definition['tables'])
+ && is_array($database_definition['tables'])
+ ) {
+ foreach ($database_definition['tables'] as $table_name => $table) {
+ $result = $this->createTable($table_name, $table, $overwrite, $options);
+ if (PEAR::isError($result)) {
+ break;
+ }
+ $created_objects++;
+ }
+ }
+ if (!PEAR::isError($result)
+ && isset($database_definition['sequences'])
+ && is_array($database_definition['sequences'])
+ ) {
+ foreach ($database_definition['sequences'] as $sequence_name => $sequence) {
+ $result = $this->createSequence($sequence_name, $sequence, false, $overwrite);
+
+ if (PEAR::isError($result)) {
+ break;
+ }
+ $created_objects++;
+ }
+ }
+
+ if ($support_transactions) {
+ $res = $this->db->completeNestedTransaction();
+ if (PEAR::isError($res)) {
+ $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'Could not end transaction ('.
+ $res->getMessage().' ('.$res->getUserinfo().'))');
+ }
+ } elseif (PEAR::isError($result) && $created_objects) {
+ $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'the database was only partially created ('.
+ $result->getMessage().' ('.$result->getUserinfo().'))');
+ }
+
+ $this->db->setDatabase($previous_database_name);
+
+ if (PEAR::isError($result) && $create
+ && PEAR::isError($result2 = $this->db->manager->dropDatabase($db_name))
+ ) {
+ if (!MDB2::isError($result2, MDB2_ERROR_UNSUPPORTED)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'Could not drop the created database after unsuccessful creation attempt ('.
+ $result2->getMessage().' ('.$result2->getUserinfo().'))');
+ }
+ }
+
+ return $result;
+ }
+
+ // }}}
+ // {{{ compareDefinitions()
+
+ /**
+ * Compare a previous definition with the currently parsed definition
+ *
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ *
+ * @return array|MDB2_Error array of changes on success, or a error object
+ * @access public
+ */
+ function compareDefinitions($current_definition, $previous_definition)
+ {
+ $changes = array();
+
+ if (!empty($current_definition['tables']) && is_array($current_definition['tables'])) {
+ $changes['tables'] = $defined_tables = array();
+ foreach ($current_definition['tables'] as $table_name => $table) {
+ $previous_tables = array();
+ if (!empty($previous_definition) && is_array($previous_definition)) {
+ $previous_tables = $previous_definition['tables'];
+ }
+ $change = $this->compareTableDefinitions($table_name, $table, $previous_tables, $defined_tables);
+ if (PEAR::isError($change)) {
+ return $change;
+ }
+ if (!empty($change)) {
+ $changes['tables'] = MDB2_Schema::arrayMergeClobber($changes['tables'], $change);
+ }
+ }
+
+ if (!empty($previous_definition['tables'])
+ && is_array($previous_definition['tables'])) {
+ foreach ($previous_definition['tables'] as $table_name => $table) {
+ if (empty($defined_tables[$table_name])) {
+ $changes['tables']['remove'][$table_name] = true;
+ }
+ }
+ }
+ }
+ if (!empty($current_definition['sequences']) && is_array($current_definition['sequences'])) {
+ $changes['sequences'] = $defined_sequences = array();
+ foreach ($current_definition['sequences'] as $sequence_name => $sequence) {
+ $previous_sequences = array();
+ if (!empty($previous_definition) && is_array($previous_definition)) {
+ $previous_sequences = $previous_definition['sequences'];
+ }
+
+ $change = $this->compareSequenceDefinitions($sequence_name,
+ $sequence,
+ $previous_sequences,
+ $defined_sequences);
+ if (PEAR::isError($change)) {
+ return $change;
+ }
+ if (!empty($change)) {
+ $changes['sequences'] = MDB2_Schema::arrayMergeClobber($changes['sequences'], $change);
+ }
+ }
+ if (!empty($previous_definition['sequences']) && is_array($previous_definition['sequences'])) {
+ foreach ($previous_definition['sequences'] as $sequence_name => $sequence) {
+ if (empty($defined_sequences[$sequence_name])) {
+ $changes['sequences']['remove'][$sequence_name] = true;
+ }
+ }
+ }
+ }
+ return $changes;
+ }
+
+ // }}}
+ // {{{ compareTableFieldsDefinitions()
+
+ /**
+ * Compare a previous definition with the currently parsed definition
+ *
+ * @param string $table_name name of the table
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ *
+ * @return array|MDB2_Error array of changes on success, or a error object
+ * @access public
+ */
+ function compareTableFieldsDefinitions($table_name, $current_definition,
+ $previous_definition)
+ {
+ $changes = $defined_fields = array();
+
+ if (is_array($current_definition)) {
+ foreach ($current_definition as $field_name => $field) {
+ $was_field_name = $field['was'];
+ if (!empty($previous_definition[$field_name])
+ && (
+ (isset($previous_definition[$field_name]['was'])
+ && $previous_definition[$field_name]['was'] == $was_field_name)
+ || !isset($previous_definition[$was_field_name])
+ )) {
+ $was_field_name = $field_name;
+ }
+
+ if (!empty($previous_definition[$was_field_name])) {
+ if ($was_field_name != $field_name) {
+ $changes['rename'][$was_field_name] = array('name' => $field_name, 'definition' => $field);
+ }
+
+ if (!empty($defined_fields[$was_field_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'the field "'.$was_field_name.
+ '" was specified for more than one field of table');
+ }
+
+ $defined_fields[$was_field_name] = true;
+
+ $change = $this->db->compareDefinition($field, $previous_definition[$was_field_name]);
+ if (PEAR::isError($change)) {
+ return $change;
+ }
+
+ if (!empty($change)) {
+ if (array_key_exists('default', $change)
+ && $change['default']
+ && !array_key_exists('default', $field)) {
+ $field['default'] = null;
+ }
+
+ $change['definition'] = $field;
+
+ $changes['change'][$field_name] = $change;
+ }
+ } else {
+ if ($field_name != $was_field_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'it was specified a previous field name ("'.
+ $was_field_name.'") for field "'.$field_name.'" of table "'.
+ $table_name.'" that does not exist');
+ }
+
+ $changes['add'][$field_name] = $field;
+ }
+ }
+ }
+
+ if (isset($previous_definition) && is_array($previous_definition)) {
+ foreach ($previous_definition as $field_previous_name => $field_previous) {
+ if (empty($defined_fields[$field_previous_name])) {
+ $changes['remove'][$field_previous_name] = true;
+ }
+ }
+ }
+
+ return $changes;
+ }
+
+ // }}}
+ // {{{ compareTableIndexesDefinitions()
+
+ /**
+ * Compare a previous definition with the currently parsed definition
+ *
+ * @param string $table_name name of the table
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ *
+ * @return array|MDB2_Error array of changes on success, or a error object
+ * @access public
+ */
+ function compareTableIndexesDefinitions($table_name, $current_definition,
+ $previous_definition)
+ {
+ $changes = $defined_indexes = array();
+
+ if (is_array($current_definition)) {
+ foreach ($current_definition as $index_name => $index) {
+ $was_index_name = $index['was'];
+ if (!empty($previous_definition[$index_name])
+ && isset($previous_definition[$index_name]['was'])
+ && $previous_definition[$index_name]['was'] == $was_index_name
+ ) {
+ $was_index_name = $index_name;
+ }
+ if (!empty($previous_definition[$was_index_name])) {
+ $change = array();
+ if ($was_index_name != $index_name) {
+ $change['name'] = $was_index_name;
+ }
+
+ if (!empty($defined_indexes[$was_index_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'the index "'.$was_index_name.'" was specified for'.
+ ' more than one index of table "'.$table_name.'"');
+ }
+ $defined_indexes[$was_index_name] = true;
+
+ $previous_unique = array_key_exists('unique', $previous_definition[$was_index_name])
+ ? $previous_definition[$was_index_name]['unique'] : false;
+
+ $unique = array_key_exists('unique', $index) ? $index['unique'] : false;
+ if ($previous_unique != $unique) {
+ $change['unique'] = $unique;
+ }
+
+ $previous_primary = array_key_exists('primary', $previous_definition[$was_index_name])
+ ? $previous_definition[$was_index_name]['primary'] : false;
+
+ $primary = array_key_exists('primary', $index) ? $index['primary'] : false;
+ if ($previous_primary != $primary) {
+ $change['primary'] = $primary;
+ }
+
+ $defined_fields = array();
+ $previous_fields = $previous_definition[$was_index_name]['fields'];
+ if (!empty($index['fields']) && is_array($index['fields'])) {
+ foreach ($index['fields'] as $field_name => $field) {
+ if (!empty($previous_fields[$field_name])) {
+ $defined_fields[$field_name] = true;
+
+ $previous_sorting = array_key_exists('sorting', $previous_fields[$field_name])
+ ? $previous_fields[$field_name]['sorting'] : '';
+
+ $sorting = array_key_exists('sorting', $field) ? $field['sorting'] : '';
+ if ($previous_sorting != $sorting) {
+ $change['change'] = true;
+ }
+ } else {
+ $change['change'] = true;
+ }
+ }
+ }
+ if (isset($previous_fields) && is_array($previous_fields)) {
+ foreach ($previous_fields as $field_name => $field) {
+ if (empty($defined_fields[$field_name])) {
+ $change['change'] = true;
+ }
+ }
+ }
+ if (!empty($change)) {
+ $changes['change'][$index_name] = $current_definition[$index_name];
+ }
+ } else {
+ if ($index_name != $was_index_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'it was specified a previous index name ("'.$was_index_name.
+ ') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist');
+ }
+ $changes['add'][$index_name] = $current_definition[$index_name];
+ }
+ }
+ }
+ foreach ($previous_definition as $index_previous_name => $index_previous) {
+ if (empty($defined_indexes[$index_previous_name])) {
+ $changes['remove'][$index_previous_name] = $index_previous;
+ }
+ }
+ return $changes;
+ }
+
+ // }}}
+ // {{{ compareTableDefinitions()
+
+ /**
+ * Compare a previous definition with the currently parsed definition
+ *
+ * @param string $table_name name of the table
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ * @param array &$defined_tables table names in the schema
+ *
+ * @return array|MDB2_Error array of changes on success, or a error object
+ * @access public
+ */
+ function compareTableDefinitions($table_name, $current_definition,
+ $previous_definition, &$defined_tables)
+ {
+ $changes = array();
+
+ if (is_array($current_definition)) {
+ $was_table_name = $table_name;
+ if (!empty($current_definition['was'])) {
+ $was_table_name = $current_definition['was'];
+ }
+ if (!empty($previous_definition[$was_table_name])) {
+ $changes['change'][$was_table_name] = array();
+ if ($was_table_name != $table_name) {
+ $changes['change'][$was_table_name] = array('name' => $table_name);
+ }
+ if (!empty($defined_tables[$was_table_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'the table "'.$was_table_name.
+ '" was specified for more than one table of the database');
+ }
+ $defined_tables[$was_table_name] = true;
+ if (!empty($current_definition['fields']) && is_array($current_definition['fields'])) {
+ $previous_fields = array();
+ if (isset($previous_definition[$was_table_name]['fields'])
+ && is_array($previous_definition[$was_table_name]['fields'])) {
+ $previous_fields = $previous_definition[$was_table_name]['fields'];
+ }
+
+ $change = $this->compareTableFieldsDefinitions($table_name,
+ $current_definition['fields'],
+ $previous_fields);
+
+ if (PEAR::isError($change)) {
+ return $change;
+ }
+ if (!empty($change)) {
+ $changes['change'][$was_table_name] =
+ MDB2_Schema::arrayMergeClobber($changes['change'][$was_table_name], $change);
+ }
+ }
+ if (!empty($current_definition['indexes']) && is_array($current_definition['indexes'])) {
+ $previous_indexes = array();
+ if (isset($previous_definition[$was_table_name]['indexes'])
+ && is_array($previous_definition[$was_table_name]['indexes'])) {
+ $previous_indexes = $previous_definition[$was_table_name]['indexes'];
+ }
+ $change = $this->compareTableIndexesDefinitions($table_name,
+ $current_definition['indexes'],
+ $previous_indexes);
+
+ if (PEAR::isError($change)) {
+ return $change;
+ }
+ if (!empty($change)) {
+ $changes['change'][$was_table_name]['indexes'] = $change;
+ }
+ }
+ if (empty($changes['change'][$was_table_name])) {
+ unset($changes['change'][$was_table_name]);
+ }
+ if (empty($changes['change'])) {
+ unset($changes['change']);
+ }
+ } else {
+ if ($table_name != $was_table_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'it was specified a previous table name ("'.$was_table_name.
+ '") for table "'.$table_name.'" that does not exist');
+ }
+ $changes['add'][$table_name] = true;
+ }
+ }
+
+ return $changes;
+ }
+
+ // }}}
+ // {{{ compareSequenceDefinitions()
+
+ /**
+ * Compare a previous definition with the currently parsed definition
+ *
+ * @param string $sequence_name name of the sequence
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ * @param array &$defined_sequences names in the schema
+ *
+ * @return array|MDB2_Error array of changes on success, or a error object
+ * @access public
+ */
+ function compareSequenceDefinitions($sequence_name, $current_definition,
+ $previous_definition, &$defined_sequences)
+ {
+ $changes = array();
+
+ if (is_array($current_definition)) {
+ $was_sequence_name = $sequence_name;
+ if (!empty($previous_definition[$sequence_name])
+ && isset($previous_definition[$sequence_name]['was'])
+ && $previous_definition[$sequence_name]['was'] == $was_sequence_name
+ ) {
+ $was_sequence_name = $sequence_name;
+ } elseif (!empty($current_definition['was'])) {
+ $was_sequence_name = $current_definition['was'];
+ }
+ if (!empty($previous_definition[$was_sequence_name])) {
+ if ($was_sequence_name != $sequence_name) {
+ $changes['change'][$was_sequence_name]['name'] = $sequence_name;
+ }
+
+ if (!empty($defined_sequences[$was_sequence_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'the sequence "'.$was_sequence_name.'" was specified as base'.
+ ' of more than of sequence of the database');
+ }
+
+ $defined_sequences[$was_sequence_name] = true;
+
+ $change = array();
+ if (!empty($current_definition['start'])
+ && isset($previous_definition[$was_sequence_name]['start'])
+ && $current_definition['start'] != $previous_definition[$was_sequence_name]['start']
+ ) {
+ $change['start'] = $previous_definition[$sequence_name]['start'];
+ }
+ if (isset($current_definition['on']['table'])
+ && isset($previous_definition[$was_sequence_name]['on']['table'])
+ && $current_definition['on']['table'] != $previous_definition[$was_sequence_name]['on']['table']
+ && isset($current_definition['on']['field'])
+ && isset($previous_definition[$was_sequence_name]['on']['field'])
+ && $current_definition['on']['field'] != $previous_definition[$was_sequence_name]['on']['field']
+ ) {
+ $change['on'] = $current_definition['on'];
+ }
+ if (!empty($change)) {
+ $changes['change'][$was_sequence_name][$sequence_name] = $change;
+ }
+ } else {
+ if ($sequence_name != $was_sequence_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null,
+ 'it was specified a previous sequence name ("'.$was_sequence_name.
+ '") for sequence "'.$sequence_name.'" that does not exist');
+ }
+ $changes['add'][$sequence_name] = true;
+ }
+ }
+ return $changes;
+ }
+ // }}}
+ // {{{ verifyAlterDatabase()
+
+ /**
+ * Verify that the changes requested are supported
+ *
+ * @param array $changes associative array that contains the definition of the changes
+ * that are meant to be applied to the database structure.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function verifyAlterDatabase($changes)
+ {
+ if (!empty($changes['tables']['change']) && is_array($changes['tables']['change'])) {
+ foreach ($changes['tables']['change'] as $table_name => $table) {
+ if (!empty($table['indexes']) && is_array($table['indexes'])) {
+ if (!$this->db->supports('indexes')) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
+ 'indexes are not supported');
+ }
+ $table_changes = count($table['indexes']);
+ if (!empty($table['indexes']['add'])) {
+ $table_changes--;
+ }
+ if (!empty($table['indexes']['remove'])) {
+ $table_changes--;
+ }
+ if (!empty($table['indexes']['change'])) {
+ $table_changes--;
+ }
+ if ($table_changes) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
+ 'index alteration not yet supported: '.implode(', ', array_keys($table['indexes'])));
+ }
+ }
+ unset($table['indexes']);
+ $result = $this->db->manager->alterTable($table_name, $table, true);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+ if (!empty($changes['sequences']) && is_array($changes['sequences'])) {
+ if (!$this->db->supports('sequences')) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
+ 'sequences are not supported');
+ }
+ $sequence_changes = count($changes['sequences']);
+ if (!empty($changes['sequences']['add'])) {
+ $sequence_changes--;
+ }
+ if (!empty($changes['sequences']['remove'])) {
+ $sequence_changes--;
+ }
+ if (!empty($changes['sequences']['change'])) {
+ $sequence_changes--;
+ }
+ if ($sequence_changes) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
+ 'sequence alteration not yet supported: '.implode(', ', array_keys($changes['sequences'])));
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ alterDatabaseIndexes()
+
+ /**
+ * Execute the necessary actions to implement the requested changes
+ * in the indexes inside a database structure.
+ *
+ * @param string $table_name name of the table
+ * @param array $changes associative array that contains the definition of the changes
+ * that are meant to be applied to the database structure.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function alterDatabaseIndexes($table_name, $changes)
+ {
+ $alterations = 0;
+ if (empty($changes)) {
+ return $alterations;
+ }
+
+ if (!empty($changes['remove']) && is_array($changes['remove'])) {
+ foreach ($changes['remove'] as $index_name => $index) {
+ $this->db->expectError(MDB2_ERROR_NOT_FOUND);
+ if (!empty($index['primary']) || !empty($index['unique'])) {
+ $result = $this->db->manager->dropConstraint($table_name, $index_name, !empty($index['primary']));
+ } else {
+ $result = $this->db->manager->dropIndex($table_name, $index_name);
+ }
+ $this->db->popExpect();
+ if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+ if (!empty($changes['change']) && is_array($changes['change'])) {
+ foreach ($changes['change'] as $index_name => $index) {
+ /**
+ * Drop existing index/constraint first.
+ * Since $changes doesn't tell us whether it's an index or a constraint before the change,
+ * we have to find out and call the appropriate method.
+ */
+ if (in_array($index_name, $this->db->manager->listTableIndexes($table_name))) {
+ $result = $this->db->manager->dropIndex($table_name, $index_name);
+ } elseif (in_array($index_name, $this->db->manager->listTableConstraints($table_name))) {
+ $result = $this->db->manager->dropConstraint($table_name, $index_name);
+ }
+ if (!empty($result) && PEAR::isError($result)) {
+ return $result;
+ }
+
+ if (!empty($index['primary']) || !empty($index['unique'])) {
+ $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
+ } else {
+ $result = $this->db->manager->createIndex($table_name, $index_name, $index);
+ }
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+ if (!empty($changes['add']) && is_array($changes['add'])) {
+ foreach ($changes['add'] as $index_name => $index) {
+ if (!empty($index['primary']) || !empty($index['unique'])) {
+ $result = $this->db->manager->createConstraint($table_name, $index_name, $index);
+ } else {
+ $result = $this->db->manager->createIndex($table_name, $index_name, $index);
+ }
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+
+ return $alterations;
+ }
+
+ // }}}
+ // {{{ alterDatabaseTables()
+
+ /**
+ * Execute the necessary actions to implement the requested changes
+ * in the tables inside a database structure.
+ *
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ * @param array $changes associative array that contains the definition of the changes
+ * that are meant to be applied to the database structure.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function alterDatabaseTables($current_definition, $previous_definition, $changes)
+ {
+ /* FIXME: tables marked to be added are initialized by createTable(), others don't */
+ $alterations = 0;
+ if (empty($changes)) {
+ return $alterations;
+ }
+
+ if (!empty($changes['add']) && is_array($changes['add'])) {
+ foreach ($changes['add'] as $table_name => $table) {
+ $result = $this->createTable($table_name, $current_definition[$table_name]);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+
+ if ($this->options['drop_missing_tables']
+ && !empty($changes['remove'])
+ && is_array($changes['remove'])) {
+ foreach ($changes['remove'] as $table_name => $table) {
+ $result = $this->db->manager->dropTable($table_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+
+ if (!empty($changes['change']) && is_array($changes['change'])) {
+ foreach ($changes['change'] as $table_name => $table) {
+ $indexes = array();
+ if (!empty($table['indexes'])) {
+ $indexes = $table['indexes'];
+ unset($table['indexes']);
+ }
+ if (!empty($indexes['remove'])) {
+ $result = $this->alterDatabaseIndexes($table_name, array('remove' => $indexes['remove']));
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ unset($indexes['remove']);
+ $alterations += $result;
+ }
+ $result = $this->db->manager->alterTable($table_name, $table, false);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+
+ // table may be renamed at this point
+ if (!empty($table['name'])) {
+ $table_name = $table['name'];
+ }
+
+ if (!empty($indexes)) {
+ $result = $this->alterDatabaseIndexes($table_name, $indexes);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations += $result;
+ }
+ }
+ }
+
+ return $alterations;
+ }
+
+ // }}}
+ // {{{ alterDatabaseSequences()
+
+ /**
+ * Execute the necessary actions to implement the requested changes
+ * in the sequences inside a database structure.
+ *
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ * @param array $changes associative array that contains the definition of the changes
+ * that are meant to be applied to the database structure.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function alterDatabaseSequences($current_definition, $previous_definition, $changes)
+ {
+ $alterations = 0;
+ if (empty($changes)) {
+ return $alterations;
+ }
+
+ if (!empty($changes['add']) && is_array($changes['add'])) {
+ foreach ($changes['add'] as $sequence_name => $sequence) {
+ $result = $this->createSequence($sequence_name, $current_definition[$sequence_name]);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+
+ if (!empty($changes['remove']) && is_array($changes['remove'])) {
+ foreach ($changes['remove'] as $sequence_name => $sequence) {
+ $result = $this->db->manager->dropSequence($sequence_name);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+
+ if (!empty($changes['change']) && is_array($changes['change'])) {
+ foreach ($changes['change'] as $sequence_name => $sequence) {
+ $result = $this->db->manager->dropSequence($previous_definition[$sequence_name]['was']);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $result = $this->createSequence($sequence_name, $sequence);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $alterations++;
+ }
+ }
+
+ return $alterations;
+ }
+
+ // }}}
+ // {{{ alterDatabase()
+
+ /**
+ * Execute the necessary actions to implement the requested changes
+ * in a database structure.
+ *
+ * @param array $current_definition multi dimensional array that contains the current definition
+ * @param array $previous_definition multi dimensional array that contains the previous definition
+ * @param array $changes associative array that contains the definition of the changes
+ * that are meant to be applied to the database structure.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function alterDatabase($current_definition, $previous_definition, $changes)
+ {
+ $alterations = 0;
+ if (empty($changes)) {
+ return $alterations;
+ }
+
+ $result = $this->verifyAlterDatabase($changes);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ if (!empty($current_definition['name'])) {
+ $previous_database_name = $this->db->setDatabase($current_definition['name']);
+ }
+
+ if (($support_transactions = $this->db->supports('transactions'))
+ && PEAR::isError($result = $this->db->beginNestedTransaction())
+ ) {
+ return $result;
+ }
+
+ if (!empty($changes['tables']) && !empty($current_definition['tables'])) {
+ $current_tables = isset($current_definition['tables']) ? $current_definition['tables'] : array();
+ $previous_tables = isset($previous_definition['tables']) ? $previous_definition['tables'] : array();
+
+ $result = $this->alterDatabaseTables($current_tables, $previous_tables, $changes['tables']);
+ if (is_numeric($result)) {
+ $alterations += $result;
+ }
+ }
+
+ if (!PEAR::isError($result) && !empty($changes['sequences'])) {
+ $current_sequences = isset($current_definition['sequences']) ? $current_definition['sequences'] : array();
+ $previous_sequences = isset($previous_definition['sequences']) ? $previous_definition['sequences'] : array();
+
+ $result = $this->alterDatabaseSequences($current_sequences, $previous_sequences, $changes['sequences']);
+ if (is_numeric($result)) {
+ $alterations += $result;
+ }
+ }
+
+ if ($support_transactions) {
+ $res = $this->db->completeNestedTransaction();
+ if (PEAR::isError($res)) {
+ $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'Could not end transaction ('.
+ $res->getMessage().' ('.$res->getUserinfo().'))');
+ }
+ } elseif (PEAR::isError($result) && $alterations) {
+ $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'the requested database alterations were only partially implemented ('.
+ $result->getMessage().' ('.$result->getUserinfo().'))');
+ }
+
+ if (isset($previous_database_name)) {
+ $this->db->setDatabase($previous_database_name);
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ dumpDatabaseChanges()
+
+ /**
+ * Dump the changes between two database definitions.
+ *
+ * @param array $changes associative array that specifies the list of database
+ * definitions changes as returned by the _compareDefinitions
+ * manager class function.
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function dumpDatabaseChanges($changes)
+ {
+ if (!empty($changes['tables'])) {
+ if (!empty($changes['tables']['add']) && is_array($changes['tables']['add'])) {
+ foreach ($changes['tables']['add'] as $table_name => $table) {
+ $this->db->debug("$table_name:", __FUNCTION__);
+ $this->db->debug("\tAdded table '$table_name'", __FUNCTION__);
+ }
+ }
+
+ if (!empty($changes['tables']['remove']) && is_array($changes['tables']['remove'])) {
+ if ($this->options['drop_missing_tables']) {
+ foreach ($changes['tables']['remove'] as $table_name => $table) {
+ $this->db->debug("$table_name:", __FUNCTION__);
+ $this->db->debug("\tRemoved table '$table_name'", __FUNCTION__);
+ }
+ } else {
+ foreach ($changes['tables']['remove'] as $table_name => $table) {
+ $this->db->debug("\tObsolete table '$table_name' left as is", __FUNCTION__);
+ }
+ }
+ }
+
+ if (!empty($changes['tables']['change']) && is_array($changes['tables']['change'])) {
+ foreach ($changes['tables']['change'] as $table_name => $table) {
+ if (array_key_exists('name', $table)) {
+ $this->db->debug("\tRenamed table '$table_name' to '".$table['name']."'", __FUNCTION__);
+ }
+ if (!empty($table['add']) && is_array($table['add'])) {
+ foreach ($table['add'] as $field_name => $field) {
+ $this->db->debug("\tAdded field '".$field_name."'", __FUNCTION__);
+ }
+ }
+ if (!empty($table['remove']) && is_array($table['remove'])) {
+ foreach ($table['remove'] as $field_name => $field) {
+ $this->db->debug("\tRemoved field '".$field_name."'", __FUNCTION__);
+ }
+ }
+ if (!empty($table['rename']) && is_array($table['rename'])) {
+ foreach ($table['rename'] as $field_name => $field) {
+ $this->db->debug("\tRenamed field '".$field_name."' to '".$field['name']."'", __FUNCTION__);
+ }
+ }
+ if (!empty($table['change']) && is_array($table['change'])) {
+ foreach ($table['change'] as $field_name => $field) {
+ $field = $field['definition'];
+ if (array_key_exists('type', $field)) {
+ $this->db->debug("\tChanged field '$field_name' type to '".$field['type']."'", __FUNCTION__);
+ }
+
+ if (array_key_exists('unsigned', $field)) {
+ $this->db->debug("\tChanged field '$field_name' type to '".
+ (!empty($field['unsigned']) && $field['unsigned'] ? '' : 'not ')."unsigned'",
+ __FUNCTION__);
+ }
+
+ if (array_key_exists('length', $field)) {
+ $this->db->debug("\tChanged field '$field_name' length to '".
+ (!empty($field['length']) ? $field['length']: 'no length')."'", __FUNCTION__);
+ }
+ if (array_key_exists('default', $field)) {
+ $this->db->debug("\tChanged field '$field_name' default to ".
+ (isset($field['default']) ? "'".$field['default']."'" : 'NULL'), __FUNCTION__);
+ }
+
+ if (array_key_exists('notnull', $field)) {
+ $this->db->debug("\tChanged field '$field_name' notnull to ".
+ (!empty($field['notnull']) && $field['notnull'] ? 'true' : 'false'),
+ __FUNCTION__);
+ }
+ }
+ }
+ if (!empty($table['indexes']) && is_array($table['indexes'])) {
+ if (!empty($table['indexes']['add']) && is_array($table['indexes']['add'])) {
+ foreach ($table['indexes']['add'] as $index_name => $index) {
+ $this->db->debug("\tAdded index '".$index_name.
+ "' of table '$table_name'", __FUNCTION__);
+ }
+ }
+ if (!empty($table['indexes']['remove']) && is_array($table['indexes']['remove'])) {
+ foreach ($table['indexes']['remove'] as $index_name => $index) {
+ $this->db->debug("\tRemoved index '".$index_name.
+ "' of table '$table_name'", __FUNCTION__);
+ }
+ }
+ if (!empty($table['indexes']['change']) && is_array($table['indexes']['change'])) {
+ foreach ($table['indexes']['change'] as $index_name => $index) {
+ if (array_key_exists('name', $index)) {
+ $this->db->debug("\tRenamed index '".$index_name."' to '".$index['name'].
+ "' on table '$table_name'", __FUNCTION__);
+ }
+ if (array_key_exists('unique', $index)) {
+ $this->db->debug("\tChanged index '".$index_name."' unique to '".
+ !empty($index['unique'])."' on table '$table_name'", __FUNCTION__);
+ }
+ if (array_key_exists('primary', $index)) {
+ $this->db->debug("\tChanged index '".$index_name."' primary to '".
+ !empty($index['primary'])."' on table '$table_name'", __FUNCTION__);
+ }
+ if (array_key_exists('change', $index)) {
+ $this->db->debug("\tChanged index '".$index_name.
+ "' on table '$table_name'", __FUNCTION__);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!empty($changes['sequences'])) {
+ if (!empty($changes['sequences']['add']) && is_array($changes['sequences']['add'])) {
+ foreach ($changes['sequences']['add'] as $sequence_name => $sequence) {
+ $this->db->debug("$sequence_name:", __FUNCTION__);
+ $this->db->debug("\tAdded sequence '$sequence_name'", __FUNCTION__);
+ }
+ }
+ if (!empty($changes['sequences']['remove']) && is_array($changes['sequences']['remove'])) {
+ foreach ($changes['sequences']['remove'] as $sequence_name => $sequence) {
+ $this->db->debug("$sequence_name:", __FUNCTION__);
+ $this->db->debug("\tAdded sequence '$sequence_name'", __FUNCTION__);
+ }
+ }
+ if (!empty($changes['sequences']['change']) && is_array($changes['sequences']['change'])) {
+ foreach ($changes['sequences']['change'] as $sequence_name => $sequence) {
+ if (array_key_exists('name', $sequence)) {
+ $this->db->debug("\tRenamed sequence '$sequence_name' to '".
+ $sequence['name']."'", __FUNCTION__);
+ }
+ if (!empty($sequence['change']) && is_array($sequence['change'])) {
+ foreach ($sequence['change'] as $sequence_name => $sequence) {
+ if (array_key_exists('start', $sequence)) {
+ $this->db->debug("\tChanged sequence '$sequence_name' start to '".
+ $sequence['start']."'", __FUNCTION__);
+ }
+ }
+ }
+ }
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ dumpDatabase()
+
+ /**
+ * 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 $database_definition multi dimensional array that contains the current definition
+ * @param array $arguments associative array that takes pairs of tag
+ * names and values that define dump options.
+ * array (
+ * 'output_mode' => String
+ * 'file' : dump into a file
+ * default: dump using a function
+ * 'output' => String
+ * depending on the 'Output_Mode'
+ * name of the file
+ * name of the function
+ * 'end_of_line' => String
+ * end of line delimiter that should be used
+ * default: "\n"
+ * );
+ * @param int $dump Int that determines what data to dump
+ * + MDB2_SCHEMA_DUMP_ALL : the entire db
+ * + MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db
+ * + MDB2_SCHEMA_DUMP_CONTENT : only the content of the db
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function dumpDatabase($database_definition, $arguments, $dump = MDB2_SCHEMA_DUMP_ALL)
+ {
+ $class_name = $this->options['writer'];
+
+ $result = MDB2::loadClass($class_name, $this->db->getOption('debug'));
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ // get initialization data
+ if (isset($database_definition['tables']) && is_array($database_definition['tables'])
+ && $dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT
+ ) {
+ foreach ($database_definition['tables'] as $table_name => $table) {
+ $fields = array();
+ $fieldsq = array();
+ foreach ($table['fields'] as $field_name => $field) {
+ $fields[$field_name] = $field['type'];
+
+ $fieldsq[] = $this->db->quoteIdentifier($field_name, true);
+ }
+
+ $query = 'SELECT '.implode(', ', $fieldsq).' FROM ';
+ $query .= $this->db->quoteIdentifier($table_name, true);
+
+ $data = $this->db->queryAll($query, $fields, MDB2_FETCHMODE_ASSOC);
+
+ if (PEAR::isError($data)) {
+ return $data;
+ }
+
+ if (!empty($data)) {
+ $initialization = array();
+ $lob_buffer_length = $this->db->getOption('lob_buffer_length');
+ foreach ($data as $row) {
+ $rows = array();
+ foreach ($row as $key => $lob) {
+ if (is_resource($lob)) {
+ $value = '';
+ while (!feof($lob)) {
+ $value .= fread($lob, $lob_buffer_length);
+ }
+ $row[$key] = $value;
+ }
+ $rows[] = array('name' => $key, 'group' => array('type' => 'value', 'data' => $row[$key]));
+ }
+ $initialization[] = array('type' => 'insert', 'data' => array('field' => $rows));
+ }
+ $database_definition['tables'][$table_name]['initialization'] = $initialization;
+ }
+ }
+ }
+
+ $writer = new $class_name($this->options['valid_types']);
+ return $writer->dumpDatabase($database_definition, $arguments, $dump);
+ }
+
+ // }}}
+ // {{{ writeInitialization()
+
+ /**
+ * Write initialization and sequences
+ *
+ * @param string|array $data data file or data array
+ * @param string|array $structure structure file or array
+ * @param array $variables associative array that is passed to the argument
+ * of the same name to the parseDatabaseDefinitionFile function. (there third
+ * param)
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function writeInitialization($data, $structure = false, $variables = array())
+ {
+ if ($structure) {
+ $structure = $this->parseDatabaseDefinition($structure, false, $variables);
+ if (PEAR::isError($structure)) {
+ return $structure;
+ }
+ }
+
+ $data = $this->parseDatabaseDefinition($data, false, $variables, false, $structure);
+ if (PEAR::isError($data)) {
+ return $data;
+ }
+
+ $previous_database_name = null;
+ if (!empty($data['name'])) {
+ $previous_database_name = $this->db->setDatabase($data['name']);
+ } elseif (!empty($structure['name'])) {
+ $previous_database_name = $this->db->setDatabase($structure['name']);
+ }
+
+ if (!empty($data['tables']) && is_array($data['tables'])) {
+ foreach ($data['tables'] as $table_name => $table) {
+ if (empty($table['initialization'])) {
+ continue;
+ }
+ $result = $this->initializeTable($table_name, $table);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+
+ if (!empty($structure['sequences']) && is_array($structure['sequences'])) {
+ foreach ($structure['sequences'] as $sequence_name => $sequence) {
+ if (isset($data['sequences'][$sequence_name])
+ || !isset($sequence['on']['table'])
+ || !isset($data['tables'][$sequence['on']['table']])
+ ) {
+ continue;
+ }
+ $result = $this->createSequence($sequence_name, $sequence, true);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+ if (!empty($data['sequences']) && is_array($data['sequences'])) {
+ foreach ($data['sequences'] as $sequence_name => $sequence) {
+ $result = $this->createSequence($sequence_name, $sequence, true);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+
+ if (isset($previous_database_name)) {
+ $this->db->setDatabase($previous_database_name);
+ }
+
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ updateDatabase()
+
+ /**
+ * 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
+ * schema file.
+ * 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
+ * applying the changes.
+ * 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
+ * need to be applied.
+ *
+ * @param string|array $current_schema filename or array of the updated database schema definition.
+ * @param string|array $previous_schema filename or array of the previously installed database schema definition.
+ * @param array $variables associative array that is passed to the argument of the same
+ * name to the parseDatabaseDefinitionFile function. (there third param)
+ * @param bool $disable_query determines if the disable_query option should be set to true
+ * for the alterDatabase() or createDatabase() call
+ * @param bool $overwrite_old_schema_file Overwrite?
+ *
+ * @return bool|MDB2_Error MDB2_OK or error object
+ * @access public
+ */
+ function updateDatabase($current_schema, $previous_schema = false,
+ $variables = array(), $disable_query = false,
+ $overwrite_old_schema_file = false)
+ {
+ $current_definition = $this->parseDatabaseDefinition($current_schema, false, $variables,
+ $this->options['fail_on_invalid_names']);
+
+ if (PEAR::isError($current_definition)) {
+ return $current_definition;
+ }
+
+ $previous_definition = false;
+ if ($previous_schema) {
+ $previous_definition = $this->parseDatabaseDefinition($previous_schema, true, $variables,
+ $this->options['fail_on_invalid_names']);
+ if (PEAR::isError($previous_definition)) {
+ return $previous_definition;
+ }
+ }
+
+ if ($previous_definition) {
+ $dbExists = $this->db->databaseExists($current_definition['name']);
+ if (PEAR::isError($dbExists)) {
+ return $dbExists;
+ }
+
+ if (!$dbExists) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'database to update does not exist: '.$current_definition['name']);
+ }
+
+ $changes = $this->compareDefinitions($current_definition, $previous_definition);
+ if (PEAR::isError($changes)) {
+ return $changes;
+ }
+
+ if (is_array($changes)) {
+ $this->db->setOption('disable_query', $disable_query);
+ $result = $this->alterDatabase($current_definition, $previous_definition, $changes);
+ $this->db->setOption('disable_query', false);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ $copy = true;
+ if ($this->db->options['debug']) {
+ $result = $this->dumpDatabaseChanges($changes);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+ }
+ } else {
+ $this->db->setOption('disable_query', $disable_query);
+ $result = $this->createDatabase($current_definition);
+ $this->db->setOption('disable_query', false);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+ }
+
+ if ($overwrite_old_schema_file
+ && !$disable_query
+ && is_string($previous_schema) && is_string($current_schema)
+ && !copy($current_schema, $previous_schema)) {
+
+ return $this->raiseError(MDB2_SCHEMA_ERROR, null, null,
+ 'Could not copy the new database definition file to the current file');
+ }
+
+ return MDB2_OK;
+ }
+ // }}}
+ // {{{ errorMessage()
+
+ /**
+ * Return a textual error message for a MDB2 error code
+ *
+ * @param int|array $value integer error code, null
to get the
+ * current error code-message map,
+ * or an array with a new error code-message map
+ *
+ * @return string error message, or false if the error code was not recognized
+ * @access public
+ */
+ function errorMessage($value = null)
+ {
+ static $errorMessages;
+ if (is_array($value)) {
+ $errorMessages = $value;
+ return MDB2_OK;
+ } elseif (!isset($errorMessages)) {
+ $errorMessages = array(
+ MDB2_SCHEMA_ERROR => 'unknown error',
+ MDB2_SCHEMA_ERROR_PARSE => 'schema parse error',
+ MDB2_SCHEMA_ERROR_VALIDATE => 'schema validation error',
+ MDB2_SCHEMA_ERROR_INVALID => 'invalid',
+ MDB2_SCHEMA_ERROR_UNSUPPORTED => 'not supported',
+ MDB2_SCHEMA_ERROR_WRITER => 'schema writer error',
+ );
+ }
+
+ if (is_null($value)) {
+ return $errorMessages;
+ }
+
+ if (PEAR::isError($value)) {
+ $value = $value->getCode();
+ }
+
+ return !empty($errorMessages[$value]) ?
+ $errorMessages[$value] : $errorMessages[MDB2_SCHEMA_ERROR];
+ }
+
+ // }}}
+ // {{{ raiseError()
+
+ /**
+ * This method is used to communicate an error and invoke error
+ * callbacks etc. Basically a wrapper for PEAR::raiseError
+ * without the message string.
+ *
+ * @param int|PEAR_Error $code integer error code or and PEAR_Error instance
+ * @param int $mode error mode, see PEAR_Error docs
+ * 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 array $options Options, depending on the mode, @see PEAR::setErrorHandling
+ * @param string $userinfo Extra debug information. Defaults to the last
+ * query and native error code.
+ *
+ * @return object a PEAR error object
+ * @access public
+ * @see PEAR_Error
+ */
+ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
+ {
+ $err =& PEAR::raiseError(null, $code, $mode, $options,
+ $userinfo, 'MDB2_Schema_Error', true);
+ return $err;
+ }
+
+ // }}}
+ // {{{ isError()
+
+ /**
+ * Tell whether a value is an MDB2_Schema error.
+ *
+ * @param mixed $data the value to test
+ * @param int $code if $data is an error object, return true only if $code is
+ * a string and $db->getMessage() == $code or
+ * $code is an integer and $db->getCode() == $code
+ *
+ * @return bool true if parameter is an error
+ * @access public
+ */
+ function isError($data, $code = null)
+ {
+ if (is_a($data, 'MDB2_Schema_Error')) {
+ if (is_null($code)) {
+ return true;
+ } elseif (is_string($code)) {
+ return $data->getMessage() === $code;
+ } else {
+ $code = (array)$code;
+ return in_array($data->getCode(), $code);
+ }
+ }
+ return false;
+ }
+
+ // }}}
+}
+
+/**
+ * MDB2_Schema_Error implements a class for reporting portable database error
+ * messages.
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Stig Bakken
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema_Error extends PEAR_Error
+{
+ /**
+ * MDB2_Schema_Error constructor.
+ *
+ * @param mixed $code error code, or string with error message.
+ * @param int $mode what 'error mode' to operate in
+ * @param int $level what error level to use for $mode & PEAR_ERROR_TRIGGER
+ * @param mixed $debuginfo additional debug info, such as the last query
+ *
+ * @access public
+ */
+ function MDB2_Schema_Error($code = MDB2_SCHEMA_ERROR, $mode = PEAR_ERROR_RETURN,
+ $level = E_USER_NOTICE, $debuginfo = null)
+ {
+ $this->PEAR_Error('MDB2_Schema Error: ' . MDB2_Schema::errorMessage($code), $code,
+ $mode, $level, $debuginfo);
+ }
+}
+?>
diff --git a/inc/MDB2/Schema/Parser.php b/inc/MDB2/Schema/Parser.php
new file mode 100644
index 0000000000..ed31ba03bd
--- /dev/null
+++ b/inc/MDB2/Schema/Parser.php
@@ -0,0 +1,819 @@
+
+ * Author: Igor Feghali
+ *
+ * $Id: Parser.php,v 1.68 2008/11/30 03:34:00 clockwerx Exp $
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Christian Dickmann
+ * @author Igor Feghali
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @version CVS: $Id: Parser.php,v 1.68 2008/11/30 03:34:00 clockwerx Exp $
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+
+
+oc_require_once('XML/Parser.php');
+oc_require_once('MDB2/Schema/Validate.php');
+
+/**
+ * Parses an XML schema file
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Christian Dickmann
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema_Parser extends XML_Parser
+{
+ var $database_definition = array();
+
+ var $elements = array();
+
+ var $element = '';
+
+ var $count = 0;
+
+ var $table = array();
+
+ var $table_name = '';
+
+ var $field = array();
+
+ var $field_name = '';
+
+ var $init = array();
+
+ var $init_function = array();
+
+ var $init_expression = array();
+
+ var $init_field = array();
+
+ var $index = array();
+
+ var $index_name = '';
+
+ var $constraint = array();
+
+ var $constraint_name = '';
+
+ var $var_mode = false;
+
+ var $variables = array();
+
+ var $sequence = array();
+
+ var $sequence_name = '';
+
+ var $error;
+
+ var $structure = false;
+
+ var $val;
+
+ function __construct($variables, $fail_on_invalid_names = true,
+ $structure = false, $valid_types = array(),
+ $force_defaults = true)
+ {
+ // force ISO-8859-1 due to different defaults for PHP4 and PHP5
+ // todo: this probably needs to be investigated some more andcleaned up
+ parent::XML_Parser('ISO-8859-1');
+
+ $this->variables = $variables;
+ $this->structure = $structure;
+ $this->val =new MDB2_Schema_Validate($fail_on_invalid_names, $valid_types, $force_defaults);
+ }
+
+ function MDB2_Schema_Parser($variables, $fail_on_invalid_names = true,
+ $structure = false, $valid_types = array(),
+ $force_defaults = true)
+ {
+ $this->__construct($variables, $fail_on_invalid_names, $structure, $valid_types, $force_defaults);
+ }
+
+ function startHandler($xp, $element, $attribs)
+ {
+ if (strtolower($element) == 'variable') {
+ $this->var_mode = true;
+ return;
+ }
+
+ $this->elements[$this->count++] = strtolower($element);
+
+ $this->element = implode('-', $this->elements);
+
+ switch ($this->element) {
+ /* Initialization */
+ case 'database-table-initialization':
+ $this->table['initialization'] = array();
+ break;
+
+ /* Insert */
+ /* insert: field+ */
+ case 'database-table-initialization-insert':
+ $this->init = array('type' => 'insert', 'data' => array('field' => array()));
+ break;
+ /* insert-select: field+, table, where? */
+ case 'database-table-initialization-insert-select':
+ $this->init['data']['table'] = '';
+ break;
+
+ /* Update */
+ /* update: field+, where? */
+ case 'database-table-initialization-update':
+ $this->init = array('type' => 'update', 'data' => array('field' => array()));
+ break;
+
+ /* Delete */
+ /* delete: where */
+ case 'database-table-initialization-delete':
+ $this->init = array('type' => 'delete', 'data' => array('where' => array()));
+ break;
+
+ /* Insert and Update */
+ case 'database-table-initialization-insert-field':
+ case 'database-table-initialization-insert-select-field':
+ case 'database-table-initialization-update-field':
+ $this->init_field = array('name' => '', 'group' => array());
+ break;
+ case 'database-table-initialization-insert-field-value':
+ case 'database-table-initialization-insert-select-field-value':
+ case 'database-table-initialization-update-field-value':
+ /* if value tag is empty cdataHandler is not called so we must force value element creation here */
+ $this->init_field['group'] = array('type' => 'value', 'data' => '');
+ break;
+ case 'database-table-initialization-insert-field-null':
+ case 'database-table-initialization-insert-select-field-null':
+ case 'database-table-initialization-update-field-null':
+ $this->init_field['group'] = array('type' => 'null');
+ break;
+ case 'database-table-initialization-insert-field-function':
+ case 'database-table-initialization-insert-select-field-function':
+ case 'database-table-initialization-update-field-function':
+ $this->init_function = array('name' => '');
+ break;
+ case 'database-table-initialization-insert-field-expression':
+ case 'database-table-initialization-insert-select-field-expression':
+ case 'database-table-initialization-update-field-expression':
+ $this->init_expression = array();
+ break;
+
+ /* All */
+ case 'database-table-initialization-insert-select-where':
+ case 'database-table-initialization-update-where':
+ case 'database-table-initialization-delete-where':
+ $this->init['data']['where'] = array('type' => '', 'data' => array());
+ break;
+ case 'database-table-initialization-insert-select-where-expression':
+ case 'database-table-initialization-update-where-expression':
+ case 'database-table-initialization-delete-where-expression':
+ $this->init_expression = array();
+ break;
+
+ /* One level simulation of expression-function recursion */
+ case 'database-table-initialization-insert-field-expression-function':
+ case 'database-table-initialization-insert-select-field-expression-function':
+ case 'database-table-initialization-insert-select-where-expression-function':
+ case 'database-table-initialization-update-field-expression-function':
+ case 'database-table-initialization-update-where-expression-function':
+ case 'database-table-initialization-delete-where-expression-function':
+ $this->init_function = array('name' => '');
+ break;
+
+ /* One level simulation of function-expression recursion */
+ case 'database-table-initialization-insert-field-function-expression':
+ case 'database-table-initialization-insert-select-field-function-expression':
+ case 'database-table-initialization-insert-select-where-function-expression':
+ case 'database-table-initialization-update-field-function-expression':
+ case 'database-table-initialization-update-where-function-expression':
+ case 'database-table-initialization-delete-where-function-expression':
+ $this->init_expression = array();
+ break;
+
+ /* Definition */
+ case 'database':
+ $this->database_definition = array(
+ 'name' => '',
+ 'create' => '',
+ 'overwrite' => '',
+ 'charset' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'tables' => array(),
+ 'sequences' => array()
+ );
+ break;
+ case 'database-table':
+ $this->table_name = '';
+
+ $this->table = array(
+ 'was' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'fields' => array(),
+ 'indexes' => array(),
+ 'constraints' => array(),
+ 'initialization' => array()
+ );
+ break;
+ case 'database-table-declaration-field':
+ case 'database-table-declaration-foreign-field':
+ case 'database-table-declaration-foreign-references-field':
+ $this->field_name = '';
+
+ $this->field = array();
+ break;
+ case 'database-table-declaration-index-field':
+ $this->field_name = '';
+
+ $this->field = array('sorting' => '', 'length' => '');
+ break;
+ /* force field attributes to be initialized when the tag is empty in the XML */
+ case 'database-table-declaration-field-was':
+ $this->field['was'] = '';
+ break;
+ case 'database-table-declaration-field-type':
+ $this->field['type'] = '';
+ break;
+ case 'database-table-declaration-field-fixed':
+ $this->field['fixed'] = '';
+ break;
+ case 'database-table-declaration-field-default':
+ $this->field['default'] = '';
+ break;
+ case 'database-table-declaration-field-notnull':
+ $this->field['notnull'] = '';
+ break;
+ case 'database-table-declaration-field-autoincrement':
+ $this->field['autoincrement'] = '';
+ break;
+ case 'database-table-declaration-field-unsigned':
+ $this->field['unsigned'] = '';
+ break;
+ case 'database-table-declaration-field-length':
+ $this->field['length'] = '';
+ break;
+ case 'database-table-declaration-field-description':
+ $this->field['description'] = '';
+ break;
+ case 'database-table-declaration-field-comments':
+ $this->field['comments'] = '';
+ break;
+ case 'database-table-declaration-index':
+ $this->index_name = '';
+
+ $this->index = array(
+ 'was' => '',
+ 'unique' =>'',
+ 'primary' => '',
+ 'fields' => array()
+ );
+ break;
+ case 'database-table-declaration-foreign':
+ $this->constraint_name = '';
+
+ $this->constraint = array(
+ 'was' => '',
+ 'match' => '',
+ 'ondelete' => '',
+ 'onupdate' => '',
+ 'deferrable' => '',
+ 'initiallydeferred' => '',
+ 'foreign' => true,
+ 'fields' => array(),
+ 'references' => array('table' => '', 'fields' => array())
+ );
+ break;
+ case 'database-sequence':
+ $this->sequence_name = '';
+
+ $this->sequence = array(
+ 'was' => '',
+ 'start' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'on' => array('table' => '', 'field' => '')
+ );
+ break;
+ }
+ }
+
+ function endHandler($xp, $element)
+ {
+ if (strtolower($element) == 'variable') {
+ $this->var_mode = false;
+ return;
+ }
+
+ switch ($this->element) {
+ /* Initialization */
+
+ /* Insert */
+ case 'database-table-initialization-insert-select':
+ $this->init['data'] = array('select' => $this->init['data']);
+ break;
+
+ /* Insert and Delete */
+ case 'database-table-initialization-insert-field':
+ case 'database-table-initialization-insert-select-field':
+ case 'database-table-initialization-update-field':
+ $result = $this->val->validateDataField($this->table['fields'], $this->init['data']['field'], $this->init_field);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->init['data']['field'][] = $this->init_field;
+ }
+ break;
+ case 'database-table-initialization-insert-field-function':
+ case 'database-table-initialization-insert-select-field-function':
+ case 'database-table-initialization-update-field-function':
+ $this->init_field['group'] = array('type' => 'function', 'data' => $this->init_function);
+ break;
+ case 'database-table-initialization-insert-field-expression':
+ case 'database-table-initialization-insert-select-field-expression':
+ case 'database-table-initialization-update-field-expression':
+ $this->init_field['group'] = array('type' => 'expression', 'data' => $this->init_expression);
+ break;
+
+ /* All */
+ case 'database-table-initialization-insert-select-where-expression':
+ case 'database-table-initialization-update-where-expression':
+ case 'database-table-initialization-delete-where-expression':
+ $this->init['data']['where']['type'] = 'expression';
+ $this->init['data']['where']['data'] = $this->init_expression;
+ break;
+ case 'database-table-initialization-insert':
+ case 'database-table-initialization-delete':
+ case 'database-table-initialization-update':
+ $this->table['initialization'][] = $this->init;
+ break;
+
+ /* One level simulation of expression-function recursion */
+ case 'database-table-initialization-insert-field-expression-function':
+ case 'database-table-initialization-insert-select-field-expression-function':
+ case 'database-table-initialization-insert-select-where-expression-function':
+ case 'database-table-initialization-update-field-expression-function':
+ case 'database-table-initialization-update-where-expression-function':
+ case 'database-table-initialization-delete-where-expression-function':
+ $this->init_expression['operants'][] = array('type' => 'function', 'data' => $this->init_function);
+ break;
+
+ /* One level simulation of function-expression recursion */
+ case 'database-table-initialization-insert-field-function-expression':
+ case 'database-table-initialization-insert-select-field-function-expression':
+ case 'database-table-initialization-insert-select-where-function-expression':
+ case 'database-table-initialization-update-field-function-expression':
+ case 'database-table-initialization-update-where-function-expression':
+ case 'database-table-initialization-delete-where-function-expression':
+ $this->init_function['arguments'][] = array('type' => 'expression', 'data' => $this->init_expression);
+ break;
+
+ /* Table definition */
+ case 'database-table':
+ $result = $this->val->validateTable($this->database_definition['tables'], $this->table, $this->table_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->database_definition['tables'][$this->table_name] = $this->table;
+ }
+ break;
+ case 'database-table-name':
+ if (isset($this->structure['tables'][$this->table_name])) {
+ $this->table = $this->structure['tables'][$this->table_name];
+ }
+ break;
+
+ /* Field declaration */
+ case 'database-table-declaration-field':
+ $result = $this->val->validateField($this->table['fields'], $this->field, $this->field_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->table['fields'][$this->field_name] = $this->field;
+ }
+ break;
+
+ /* Index declaration */
+ case 'database-table-declaration-index':
+ $result = $this->val->validateIndex($this->table['indexes'], $this->index, $this->index_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->table['indexes'][$this->index_name] = $this->index;
+ }
+ break;
+ case 'database-table-declaration-index-field':
+ $result = $this->val->validateIndexField($this->index['fields'], $this->field, $this->field_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->index['fields'][$this->field_name] = $this->field;
+ }
+ break;
+
+ /* Foreign Key declaration */
+ case 'database-table-declaration-foreign':
+ $result = $this->val->validateConstraint($this->table['constraints'], $this->constraint, $this->constraint_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->table['constraints'][$this->constraint_name] = $this->constraint;
+ }
+ break;
+ case 'database-table-declaration-foreign-field':
+ $result = $this->val->validateConstraintField($this->constraint['fields'], $this->field_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->constraint['fields'][$this->field_name] = '';
+ }
+ break;
+ case 'database-table-declaration-foreign-references-field':
+ $result = $this->val->validateConstraintReferencedField($this->constraint['references']['fields'], $this->field_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->constraint['references']['fields'][$this->field_name] = '';
+ }
+ break;
+
+ /* Sequence declaration */
+ case 'database-sequence':
+ $result = $this->val->validateSequence($this->database_definition['sequences'], $this->sequence, $this->sequence_name);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ } else {
+ $this->database_definition['sequences'][$this->sequence_name] = $this->sequence;
+ }
+ break;
+
+ /* End of File */
+ case 'database':
+ $result = $this->val->validateDatabase($this->database_definition);
+ if (PEAR::isError($result)) {
+ $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode());
+ }
+ break;
+ }
+
+ unset($this->elements[--$this->count]);
+ $this->element = implode('-', $this->elements);
+ }
+
+ function &raiseError($msg = null, $xmlecode = 0, $xp = null, $ecode = MDB2_SCHEMA_ERROR_PARSE)
+ {
+ if (is_null($this->error)) {
+ $error = '';
+ if (is_resource($msg)) {
+ $error .= 'Parser error: '.xml_error_string(xml_get_error_code($msg));
+ $xp = $msg;
+ } else {
+ $error .= 'Parser error: '.$msg;
+ if (!is_resource($xp)) {
+ $xp = $this->parser;
+ }
+ }
+
+ if ($error_string = xml_error_string($xmlecode)) {
+ $error .= ' - '.$error_string;
+ }
+
+ if (is_resource($xp)) {
+ $byte = @xml_get_current_byte_index($xp);
+ $line = @xml_get_current_line_number($xp);
+ $column = @xml_get_current_column_number($xp);
+ $error .= " - Byte: $byte; Line: $line; Col: $column";
+ }
+
+ $error .= "\n";
+
+ $this->error =& MDB2_Schema::raiseError($ecode, null, null, $error);
+ }
+ return $this->error;
+ }
+
+ function cdataHandler($xp, $data)
+ {
+ if ($this->var_mode == true) {
+ if (!isset($this->variables[$data])) {
+ $this->raiseError('variable "'.$data.'" not found', null, $xp);
+ return;
+ }
+ $data = $this->variables[$data];
+ }
+
+ switch ($this->element) {
+ /* Initialization */
+
+ /* Insert */
+ case 'database-table-initialization-insert-select-table':
+ $this->init['data']['table'] = $data;
+ break;
+
+ /* Insert and Update */
+ case 'database-table-initialization-insert-field-name':
+ case 'database-table-initialization-insert-select-field-name':
+ case 'database-table-initialization-update-field-name':
+ $this->init_field['name'] .= $data;
+ break;
+ case 'database-table-initialization-insert-field-value':
+ case 'database-table-initialization-insert-select-field-value':
+ case 'database-table-initialization-update-field-value':
+ $this->init_field['group']['data'] .= $data;
+ break;
+ case 'database-table-initialization-insert-field-function-name':
+ case 'database-table-initialization-insert-select-field-function-name':
+ case 'database-table-initialization-update-field-function-name':
+ $this->init_function['name'] .= $data;
+ break;
+ case 'database-table-initialization-insert-field-function-value':
+ case 'database-table-initialization-insert-select-field-function-value':
+ case 'database-table-initialization-update-field-function-value':
+ $this->init_function['arguments'][] = array('type' => 'value', 'data' => $data);
+ break;
+ case 'database-table-initialization-insert-field-function-column':
+ case 'database-table-initialization-insert-select-field-function-column':
+ case 'database-table-initialization-update-field-function-column':
+ $this->init_function['arguments'][] = array('type' => 'column', 'data' => $data);
+ break;
+ case 'database-table-initialization-insert-field-column':
+ case 'database-table-initialization-insert-select-field-column':
+ case 'database-table-initialization-update-field-column':
+ $this->init_field['group'] = array('type' => 'column', 'data' => $data);
+ break;
+
+ /* All */
+ case 'database-table-initialization-insert-field-expression-operator':
+ case 'database-table-initialization-insert-select-field-expression-operator':
+ case 'database-table-initialization-insert-select-where-expression-operator':
+ case 'database-table-initialization-update-field-expression-operator':
+ case 'database-table-initialization-update-where-expression-operator':
+ case 'database-table-initialization-delete-where-expression-operator':
+ $this->init_expression['operator'] = $data;
+ break;
+ case 'database-table-initialization-insert-field-expression-value':
+ case 'database-table-initialization-insert-select-field-expression-value':
+ case 'database-table-initialization-insert-select-where-expression-value':
+ case 'database-table-initialization-update-field-expression-value':
+ case 'database-table-initialization-update-where-expression-value':
+ case 'database-table-initialization-delete-where-expression-value':
+ $this->init_expression['operants'][] = array('type' => 'value', 'data' => $data);
+ break;
+ case 'database-table-initialization-insert-field-expression-column':
+ case 'database-table-initialization-insert-select-field-expression-column':
+ case 'database-table-initialization-insert-select-where-expression-column':
+ case 'database-table-initialization-update-field-expression-column':
+ case 'database-table-initialization-update-where-expression-column':
+ case 'database-table-initialization-delete-where-expression-column':
+ $this->init_expression['operants'][] = array('type' => 'column', 'data' => $data);
+ break;
+
+ case 'database-table-initialization-insert-field-function-function':
+ case 'database-table-initialization-insert-field-function-expression':
+ case 'database-table-initialization-insert-field-expression-expression':
+ case 'database-table-initialization-update-field-function-function':
+ case 'database-table-initialization-update-field-function-expression':
+ case 'database-table-initialization-update-field-expression-expression':
+ case 'database-table-initialization-update-where-expression-expression':
+ case 'database-table-initialization-delete-where-expression-expression':
+ /* Recursion to be implemented yet */
+ break;
+
+ /* One level simulation of expression-function recursion */
+ case 'database-table-initialization-insert-field-expression-function-name':
+ case 'database-table-initialization-insert-select-field-expression-function-name':
+ case 'database-table-initialization-insert-select-where-expression-function-name':
+ case 'database-table-initialization-update-field-expression-function-name':
+ case 'database-table-initialization-update-where-expression-function-name':
+ case 'database-table-initialization-delete-where-expression-function-name':
+ $this->init_function['name'] .= $data;
+ break;
+ case 'database-table-initialization-insert-field-expression-function-value':
+ case 'database-table-initialization-insert-select-field-expression-function-value':
+ case 'database-table-initialization-insert-select-where-expression-function-value':
+ case 'database-table-initialization-update-field-expression-function-value':
+ case 'database-table-initialization-update-where-expression-function-value':
+ case 'database-table-initialization-delete-where-expression-function-value':
+ $this->init_function['arguments'][] = array('type' => 'value', 'data' => $data);
+ break;
+ case 'database-table-initialization-insert-field-expression-function-column':
+ case 'database-table-initialization-insert-select-field-expression-function-column':
+ case 'database-table-initialization-insert-select-where-expression-function-column':
+ case 'database-table-initialization-update-field-expression-function-column':
+ case 'database-table-initialization-update-where-expression-function-column':
+ case 'database-table-initialization-delete-where-expression-function-column':
+ $this->init_function['arguments'][] = array('type' => 'column', 'data' => $data);
+ break;
+
+ /* One level simulation of function-expression recursion */
+ case 'database-table-initialization-insert-field-function-expression-operator':
+ case 'database-table-initialization-insert-select-field-function-expression-operator':
+ case 'database-table-initialization-update-field-function-expression-operator':
+ $this->init_expression['operator'] = $data;
+ break;
+ case 'database-table-initialization-insert-field-function-expression-value':
+ case 'database-table-initialization-insert-select-field-function-expression-value':
+ case 'database-table-initialization-update-field-function-expression-value':
+ $this->init_expression['operants'][] = array('type' => 'value', 'data' => $data);
+ break;
+ case 'database-table-initialization-insert-field-function-expression-column':
+ case 'database-table-initialization-insert-select-field-function-expression-column':
+ case 'database-table-initialization-update-field-function-expression-column':
+ $this->init_expression['operants'][] = array('type' => 'column', 'data' => $data);
+ break;
+
+ /* Database */
+ case 'database-name':
+ $this->database_definition['name'] .= $data;
+ break;
+ case 'database-create':
+ $this->database_definition['create'] .= $data;
+ break;
+ case 'database-overwrite':
+ $this->database_definition['overwrite'] .= $data;
+ break;
+ case 'database-charset':
+ $this->database_definition['charset'] .= $data;
+ break;
+ case 'database-description':
+ $this->database_definition['description'] .= $data;
+ break;
+ case 'database-comments':
+ $this->database_definition['comments'] .= $data;
+ break;
+
+ /* Table declaration */
+ case 'database-table-name':
+ $this->table_name .= $data;
+ break;
+ case 'database-table-was':
+ $this->table['was'] .= $data;
+ break;
+ case 'database-table-description':
+ $this->table['description'] .= $data;
+ break;
+ case 'database-table-comments':
+ $this->table['comments'] .= $data;
+ break;
+
+ /* Field declaration */
+ case 'database-table-declaration-field-name':
+ $this->field_name .= $data;
+ break;
+ case 'database-table-declaration-field-was':
+ $this->field['was'] .= $data;
+ break;
+ case 'database-table-declaration-field-type':
+ $this->field['type'] .= $data;
+ break;
+ case 'database-table-declaration-field-fixed':
+ $this->field['fixed'] .= $data;
+ break;
+ case 'database-table-declaration-field-default':
+ $this->field['default'] .= $data;
+ break;
+ case 'database-table-declaration-field-notnull':
+ $this->field['notnull'] .= $data;
+ break;
+ case 'database-table-declaration-field-autoincrement':
+ $this->field['autoincrement'] .= $data;
+ break;
+ case 'database-table-declaration-field-unsigned':
+ $this->field['unsigned'] .= $data;
+ break;
+ case 'database-table-declaration-field-length':
+ $this->field['length'] .= $data;
+ break;
+ case 'database-table-declaration-field-description':
+ $this->field['description'] .= $data;
+ break;
+ case 'database-table-declaration-field-comments':
+ $this->field['comments'] .= $data;
+ break;
+
+ /* Index declaration */
+ case 'database-table-declaration-index-name':
+ $this->index_name .= $data;
+ break;
+ case 'database-table-declaration-index-was':
+ $this->index['was'] .= $data;
+ break;
+ case 'database-table-declaration-index-unique':
+ $this->index['unique'] .= $data;
+ break;
+ case 'database-table-declaration-index-primary':
+ $this->index['primary'] .= $data;
+ break;
+ case 'database-table-declaration-index-field-name':
+ $this->field_name .= $data;
+ break;
+ case 'database-table-declaration-index-field-sorting':
+ $this->field['sorting'] .= $data;
+ break;
+ /* Add by Leoncx */
+ case 'database-table-declaration-index-field-length':
+ $this->field['length'] .= $data;
+ break;
+
+ /* Foreign Key declaration */
+ case 'database-table-declaration-foreign-name':
+ $this->constraint_name .= $data;
+ break;
+ case 'database-table-declaration-foreign-was':
+ $this->constraint['was'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-match':
+ $this->constraint['match'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-ondelete':
+ $this->constraint['ondelete'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-onupdate':
+ $this->constraint['onupdate'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-deferrable':
+ $this->constraint['deferrable'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-initiallydeferred':
+ $this->constraint['initiallydeferred'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-field':
+ $this->field_name .= $data;
+ break;
+ case 'database-table-declaration-foreign-references-table':
+ $this->constraint['references']['table'] .= $data;
+ break;
+ case 'database-table-declaration-foreign-references-field':
+ $this->field_name .= $data;
+ break;
+
+ /* Sequence declaration */
+ case 'database-sequence-name':
+ $this->sequence_name .= $data;
+ break;
+ case 'database-sequence-was':
+ $this->sequence['was'] .= $data;
+ break;
+ case 'database-sequence-start':
+ $this->sequence['start'] .= $data;
+ break;
+ case 'database-sequence-description':
+ $this->sequence['description'] .= $data;
+ break;
+ case 'database-sequence-comments':
+ $this->sequence['comments'] .= $data;
+ break;
+ case 'database-sequence-on-table':
+ $this->sequence['on']['table'] .= $data;
+ break;
+ case 'database-sequence-on-field':
+ $this->sequence['on']['field'] .= $data;
+ break;
+ }
+ }
+}
+
+?>
diff --git a/inc/MDB2/Schema/Parser2.php b/inc/MDB2/Schema/Parser2.php
new file mode 100644
index 0000000000..01318473fd
--- /dev/null
+++ b/inc/MDB2/Schema/Parser2.php
@@ -0,0 +1,624 @@
+
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Igor Feghali
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @version CVS: $Id: Parser2.php,v 1.12 2008/11/30 03:34:00 clockwerx Exp $
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+
+require_once 'XML/Unserializer.php';
+require_once 'MDB2/Schema/Validate.php';
+
+/**
+ * Parses an XML schema file
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Lukas Smith
+ * @author Igor Feghali
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema_Parser2 extends XML_Unserializer
+{
+ var $database_definition = array();
+
+ var $database_loaded = array();
+
+ var $variables = array();
+
+ var $error;
+
+ var $structure = false;
+
+ var $val;
+
+ var $options = array();
+
+ var $table = array();
+
+ var $table_name = '';
+
+ var $field = array();
+
+ var $field_name = '';
+
+ var $index = array();
+
+ var $index_name = '';
+
+ var $constraint = array();
+
+ var $constraint_name = '';
+
+ var $sequence = array();
+
+ var $sequence_name = '';
+
+ var $init = array();
+
+ function __construct($variables, $fail_on_invalid_names = true, $structure = false, $valid_types = array(), $force_defaults = true)
+ {
+ // force ISO-8859-1 due to different defaults for PHP4 and PHP5
+ // todo: this probably needs to be investigated some more and cleaned up
+ $this->options['encoding'] = 'ISO-8859-1';
+
+ $this->options['XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE'] = true;
+ $this->options['XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY'] = false;
+
+ $this->options['forceEnum'] = array('table', 'field', 'index', 'foreign', 'insert', 'update', 'delete', 'sequence');
+
+ /*
+ * todo: find a way to force the following items not to be parsed as arrays
+ * as it cause problems in functions with multiple arguments
+ */
+ //$this->options['forceNEnum'] = array('value', 'column');
+ $this->variables = $variables;
+ $this->structure = $structure;
+
+ $this->val =& new MDB2_Schema_Validate($fail_on_invalid_names, $valid_types, $force_defaults);
+ parent::XML_Unserializer($this->options);
+ }
+
+ function MDB2_Schema_Parser2($variables, $fail_on_invalid_names = true, $structure = false, $valid_types = array(), $force_defaults = true)
+ {
+ $this->__construct($variables, $fail_on_invalid_names, $structure, $valid_types, $force_defaults);
+ }
+
+ function parse()
+ {
+ $result = $this->unserialize($this->filename, true);
+
+ if (PEAR::isError($result)) {
+ return $result;
+ } else {
+ $this->database_loaded = $this->getUnserializedData();
+ return $this->fixDatabaseKeys($this->database_loaded);
+ }
+ }
+
+ function setInputFile($filename)
+ {
+ $this->filename = $filename;
+ return MDB2_OK;
+ }
+
+ function renameKey(&$arr, $oKey, $nKey)
+ {
+ $arr[$nKey] = &$arr[$oKey];
+ unset($arr[$oKey]);
+ }
+
+ function fixDatabaseKeys($database)
+ {
+ $this->database_definition = array(
+ 'name' => '',
+ 'create' => '',
+ 'overwrite' => '',
+ 'charset' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'tables' => array(),
+ 'sequences' => array()
+ );
+
+ if (!empty($database['name'])) {
+ $this->database_definition['name'] = $database['name'];
+ }
+ if (!empty($database['create'])) {
+ $this->database_definition['create'] = $database['create'];
+ }
+ if (!empty($database['overwrite'])) {
+ $this->database_definition['overwrite'] = $database['overwrite'];
+ }
+ if (!empty($database['charset'])) {
+ $this->database_definition['charset'] = $database['charset'];
+ }
+ if (!empty($database['description'])) {
+ $this->database_definition['description'] = $database['description'];
+ }
+ if (!empty($database['comments'])) {
+ $this->database_definition['comments'] = $database['comments'];
+ }
+
+ if (!empty($database['table']) && is_array($database['table'])) {
+ foreach ($database['table'] as $table) {
+ $this->fixTableKeys($table);
+ }
+ }
+
+ if (!empty($database['sequence']) && is_array($database['sequence'])) {
+ foreach ($database['sequence'] as $sequence) {
+ $this->fixSequenceKeys($sequence);
+ }
+ }
+
+ $result = $this->val->validateDatabase($this->database_definition);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ }
+
+ return MDB2_OK;
+ }
+
+ function fixTableKeys($table)
+ {
+ $this->table = array(
+ 'was' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'fields' => array(),
+ 'indexes' => array(),
+ 'constraints' => array(),
+ 'initialization' => array()
+ );
+
+ if (!empty($table['name'])) {
+ $this->table_name = $table['name'];
+ } else {
+ $this->table_name = '';
+ }
+ if (!empty($table['was'])) {
+ $this->table['was'] = $table['was'];
+ }
+ if (!empty($table['description'])) {
+ $this->table['description'] = $table['description'];
+ }
+ if (!empty($table['comments'])) {
+ $this->table['comments'] = $table['comments'];
+ }
+
+ if (!empty($table['declaration']) && is_array($table['declaration'])) {
+ if (!empty($table['declaration']['field']) && is_array($table['declaration']['field'])) {
+ foreach ($table['declaration']['field'] as $field) {
+ $this->fixTableFieldKeys($field);
+ }
+ }
+
+ if (!empty($table['declaration']['index']) && is_array($table['declaration']['index'])) {
+ foreach ($table['declaration']['index'] as $index) {
+ $this->fixTableIndexKeys($index);
+ }
+ }
+
+ if (!empty($table['declaration']['foreign']) && is_array($table['declaration']['foreign'])) {
+ foreach ($table['declaration']['foreign'] as $constraint) {
+ $this->fixTableConstraintKeys($constraint);
+ }
+ }
+ }
+
+ if (!empty($table['initialization']) && is_array($table['initialization'])) {
+ if (!empty($table['initialization']['insert']) && is_array($table['initialization']['insert'])) {
+ foreach ($table['initialization']['insert'] as $init) {
+ $this->fixTableInitializationKeys($init, 'insert');
+ }
+ }
+ if (!empty($table['initialization']['update']) && is_array($table['initialization']['update'])) {
+ foreach ($table['initialization']['update'] as $init) {
+ $this->fixTableInitializationKeys($init, 'update');
+ }
+ }
+ if (!empty($table['initialization']['delete']) && is_array($table['initialization']['delete'])) {
+ foreach ($table['initialization']['delete'] as $init) {
+ $this->fixTableInitializationKeys($init, 'delete');
+ }
+ }
+ }
+
+ $result = $this->val->validateTable($this->database_definition['tables'], $this->table, $this->table_name);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ } else {
+ $this->database_definition['tables'][$this->table_name] = $this->table;
+ }
+
+ return MDB2_OK;
+ }
+
+ function fixTableFieldKeys($field)
+ {
+ $this->field = array();
+ if (!empty($field['name'])) {
+ $this->field_name = $field['name'];
+ } else {
+ $this->field_name = '';
+ }
+ if (!empty($field['was'])) {
+ $this->field['was'] = $field['was'];
+ }
+ if (!empty($field['type'])) {
+ $this->field['type'] = $field['type'];
+ }
+ if (!empty($field['fixed'])) {
+ $this->field['fixed'] = $field['fixed'];
+ }
+ if (isset($field['default'])) {
+ $this->field['default'] = $field['default'];
+ }
+ if (!empty($field['notnull'])) {
+ $this->field['notnull'] = $field['notnull'];
+ }
+ if (!empty($field['autoincrement'])) {
+ $this->field['autoincrement'] = $field['autoincrement'];
+ }
+ if (!empty($field['unsigned'])) {
+ $this->field['unsigned'] = $field['unsigned'];
+ }
+ if (!empty($field['length'])) {
+ $this->field['length'] = $field['length'];
+ }
+ if (!empty($field['description'])) {
+ $this->field['description'] = $field['description'];
+ }
+ if (!empty($field['comments'])) {
+ $this->field['comments'] = $field['comments'];
+ }
+
+ $result = $this->val->validateField($this->table['fields'], $this->field, $this->field_name);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ } else {
+ $this->table['fields'][$this->field_name] = $this->field;
+ }
+
+ return MDB2_OK;
+ }
+
+ function fixTableIndexKeys($index)
+ {
+ $this->index = array(
+ 'was' => '',
+ 'unique' =>'',
+ 'primary' => '',
+ 'fields' => array()
+ );
+
+ if (!empty($index['name'])) {
+ $this->index_name = $index['name'];
+ } else {
+ $this->index_name = '';
+ }
+ if (!empty($index['was'])) {
+ $this->index['was'] = $index['was'];
+ }
+ if (!empty($index['unique'])) {
+ $this->index['unique'] = $index['unique'];
+ }
+ if (!empty($index['primary'])) {
+ $this->index['primary'] = $index['primary'];
+ }
+ if (!empty($index['field'])) {
+ foreach ($index['field'] as $field) {
+ if (!empty($field['name'])) {
+ $this->field_name = $field['name'];
+ } else {
+ $this->field_name = '';
+ }
+ $this->field = array(
+ 'sorting' => '',
+ 'length' => ''
+ );
+
+ if (!empty($field['sorting'])) {
+ $this->field['sorting'] = $field['sorting'];
+ }
+ if (!empty($field['length'])) {
+ $this->field['length'] = $field['length'];
+ }
+
+ $result = $this->val->validateIndexField($this->index['fields'], $this->field, $this->field_name);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ }
+
+ $this->index['fields'][$this->field_name] = $this->field;
+ }
+ }
+
+ $result = $this->val->validateIndex($this->table['indexes'], $this->index, $this->index_name);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ } else {
+ $this->table['indexes'][$this->index_name] = $this->index;
+ }
+
+ return MDB2_OK;
+ }
+
+ function fixTableConstraintKeys($constraint)
+ {
+ $this->constraint = array(
+ 'was' => '',
+ 'match' => '',
+ 'ondelete' => '',
+ 'onupdate' => '',
+ 'deferrable' => '',
+ 'initiallydeferred' => '',
+ 'foreign' => true,
+ 'fields' => array(),
+ 'references' => array('table' => '', 'fields' => array())
+ );
+
+ if (!empty($constraint['name'])) {
+ $this->constraint_name = $constraint['name'];
+ } else {
+ $this->constraint_name = '';
+ }
+ if (!empty($constraint['was'])) {
+ $this->constraint['was'] = $constraint['was'];
+ }
+ if (!empty($constraint['match'])) {
+ $this->constraint['match'] = $constraint['match'];
+ }
+ if (!empty($constraint['ondelete'])) {
+ $this->constraint['ondelete'] = $constraint['ondelete'];
+ }
+ if (!empty($constraint['onupdate'])) {
+ $this->constraint['onupdate'] = $constraint['onupdate'];
+ }
+ if (!empty($constraint['deferrable'])) {
+ $this->constraint['deferrable'] = $constraint['deferrable'];
+ }
+ if (!empty($constraint['initiallydeferred'])) {
+ $this->constraint['initiallydeferred'] = $constraint['initiallydeferred'];
+ }
+ if (!empty($constraint['field']) && is_array($constraint['field'])) {
+ foreach ($constraint['field'] as $field) {
+ $result = $this->val->validateConstraintField($this->constraint['fields'], $field);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ }
+
+ $this->constraint['fields'][$field] = '';
+ }
+ }
+
+ if (!empty($constraint['references']) && is_array($constraint['references'])) {
+ /**
+ * As we forced 'table' to be enumerated
+ * we have to fix it on the foreign-references-table context
+ */
+ if (!empty($constraint['references']['table']) && is_array($constraint['references']['table'])) {
+ $this->constraint['references']['table'] = $constraint['references']['table'][0];
+ }
+
+ if (!empty($constraint['references']['field']) && is_array($constraint['references']['field'])) {
+ foreach ($constraint['references']['field'] as $field) {
+ $result = $this->val->validateConstraintReferencedField($this->constraint['references']['fields'], $field);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ }
+
+ $this->constraint['references']['fields'][$field] = '';
+ }
+ }
+ }
+
+ $result = $this->val->validateConstraint($this->table['constraints'], $this->constraint, $this->constraint_name);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ } else {
+ $this->table['constraints'][$this->constraint_name] = $this->constraint;
+ }
+
+ return MDB2_OK;
+ }
+
+ function fixTableInitializationKeys($element, $type = '')
+ {
+ if (!empty($element['select']) && is_array($element['select'])) {
+ $this->fixTableInitializationDataKeys($element['select']);
+ $this->init = array( 'select' => $this->init );
+ } else {
+ $this->fixTableInitializationDataKeys($element);
+ }
+
+ $this->table['initialization'][] = array( 'type' => $type, 'data' => $this->init );
+ }
+
+ function fixTableInitializationDataKeys($element)
+ {
+ $this->init = array();
+ if (!empty($element['field']) && is_array($element['field'])) {
+ foreach ($element['field'] as $field) {
+ $name = $field['name'];
+ unset($field['name']);
+
+ $this->setExpression($field);
+ $this->init['field'][] = array( 'name' => $name, 'group' => $field );
+ }
+ }
+ /**
+ * As we forced 'table' to be enumerated
+ * we have to fix it on the insert-select context
+ */
+ if (!empty($element['table']) && is_array($element['table'])) {
+ $this->init['table'] = $element['table'][0];
+ }
+ if (!empty($element['where']) && is_array($element['where'])) {
+ $this->init['where'] = $element['where'];
+ $this->setExpression($this->init['where']);
+ }
+ }
+
+ function setExpression(&$arr)
+ {
+ $element = each($arr);
+
+ $arr = array( 'type' => $element['key'] );
+
+ $element = $element['value'];
+
+ switch ($arr['type']) {
+ case 'null':
+ break;
+ case 'value':
+ case 'column':
+ $arr['data'] = $element;
+ break;
+ case 'function':
+ if (!empty($element)
+ && is_array($element)
+ ) {
+ $arr['data'] = array( 'name' => $element['name'] );
+ unset($element['name']);
+
+ foreach ($element as $type => $value) {
+ if (!empty($value)) {
+ if (is_array($value)) {
+ foreach ($value as $argument) {
+ $argument = array( $type => $argument );
+ $this->setExpression($argument);
+ $arr['data']['arguments'][] = $argument;
+ }
+ } else {
+ $arr['data']['arguments'][] = array( 'type' => $type, 'data' => $value );
+ }
+ }
+ }
+ }
+ break;
+ case 'expression':
+ $arr['data'] = array( 'operants' => array(), 'operator' => $element['operator'] );
+ unset($element['operator']);
+
+ foreach ($element as $k => $v) {
+ $argument = array( $k => $v );
+ $this->setExpression($argument);
+ $arr['data']['operants'][] = $argument;
+ }
+ break;
+ }
+ }
+
+ function fixSequenceKeys($sequence)
+ {
+ $this->sequence = array(
+ 'was' => '',
+ 'start' => '',
+ 'description' => '',
+ 'comments' => '',
+ 'on' => array('table' => '', 'field' => '')
+ );
+
+ if (!empty($sequence['name'])) {
+ $this->sequence_name = $sequence['name'];
+ } else {
+ $this->sequence_name = '';
+ }
+ if (!empty($sequence['was'])) {
+ $this->sequence['was'] = $sequence['was'];
+ }
+ if (!empty($sequence['start'])) {
+ $this->sequence['start'] = $sequence['start'];
+ }
+ if (!empty($sequence['description'])) {
+ $this->sequence['description'] = $sequence['description'];
+ }
+ if (!empty($sequence['comments'])) {
+ $this->sequence['comments'] = $sequence['comments'];
+ }
+ if (!empty($sequence['on']) && is_array($sequence['on'])) {
+ /**
+ * As we forced 'table' to be enumerated
+ * we have to fix it on the sequence-on-table context
+ */
+ if (!empty($sequence['on']['table']) && is_array($sequence['on']['table'])) {
+ $this->sequence['on']['table'] = $sequence['on']['table'][0];
+ }
+
+ /**
+ * As we forced 'field' to be enumerated
+ * we have to fix it on the sequence-on-field context
+ */
+ if (!empty($sequence['on']['field']) && is_array($sequence['on']['field'])) {
+ $this->sequence['on']['field'] = $sequence['on']['field'][0];
+ }
+ }
+
+ $result = $this->val->validateSequence($this->database_definition['sequences'], $this->sequence, $this->sequence_name);
+ if (PEAR::isError($result)) {
+ return $this->raiseError($result->getUserinfo());
+ } else {
+ $this->database_definition['sequences'][$this->sequence_name] = $this->sequence;
+ }
+
+ return MDB2_OK;
+ }
+
+ function &raiseError($msg = null, $ecode = MDB2_SCHEMA_ERROR_PARSE)
+ {
+ if (is_null($this->error)) {
+ $error = 'Parser error: '.$msg."\n";
+
+ $this->error =& MDB2_Schema::raiseError($ecode, null, null, $error);
+ }
+ return $this->error;
+ }
+}
+
+?>
diff --git a/inc/MDB2/Schema/Reserved/ibase.php b/inc/MDB2/Schema/Reserved/ibase.php
new file mode 100644
index 0000000000..b208abc83a
--- /dev/null
+++ b/inc/MDB2/Schema/Reserved/ibase.php
@@ -0,0 +1,436 @@
+ |
+// +----------------------------------------------------------------------+
+//
+// }}}
+// {{{ $GLOBALS['_MDB2_Schema_Reserved']['ibase']
+/**
+ * Has a list of reserved words of Interbase/Firebird
+ *
+ * @package MDB2_Schema
+ * @category Database
+ * @access protected
+ * @author Lorenzo Alberton
+ */
+$GLOBALS['_MDB2_Schema_Reserved']['ibase'] = array(
+ 'ABS',
+ 'ABSOLUTE',
+ 'ACTION',
+ 'ACTIVE',
+ 'ADD',
+ 'ADMIN',
+ 'AFTER',
+ 'ALL',
+ 'ALLOCATE',
+ 'ALTER',
+ 'AND',
+ 'ANY',
+ 'ARE',
+ 'AS',
+ 'ASC',
+ 'ASCENDING',
+ 'ASSERTION',
+ 'AT',
+ 'AUTHORIZATION',
+ 'AUTO',
+ 'AUTODDL',
+ 'AVG',
+ 'BACKUP',
+ 'BASE_NAME',
+ 'BASED',
+ 'BASENAME',
+ 'BEFORE',
+ 'BEGIN',
+ 'BETWEEN',
+ 'BIGINT',
+ 'BIT',
+ 'BIT_LENGTH',
+ 'BLOB',
+ 'BLOCK',
+ 'BLOBEDIT',
+ 'BOOLEAN',
+ 'BOTH',
+ 'BOTH',
+ 'BREAK',
+ 'BUFFER',
+ 'BY',
+ 'CACHE',
+ 'CASCADE',
+ 'CASCADED',
+ 'CASE',
+ 'CASE',
+ 'CAST',
+ 'CATALOG',
+ 'CHAR',
+ 'CHAR_LENGTH',
+ 'CHARACTER',
+ 'CHARACTER_LENGTH',
+ 'CHECK',
+ 'CHECK_POINT_LEN',
+ 'CHECK_POINT_LENGTH',
+ 'CLOSE',
+ 'COALESCE',
+ 'COLLATE',
+ 'COLLATION',
+ 'COLUMN',
+ 'COMMENT',
+ 'COMMIT',
+ 'COMMITTED',
+ 'COMPILETIME',
+ 'COMPUTED',
+ 'CONDITIONAL',
+ 'CONNECT',
+ 'CONNECTION',
+ 'CONSTRAINT',
+ 'CONSTRAINTS',
+ 'CONTAINING',
+ 'CONTINUE',
+ 'CONVERT',
+ 'CORRESPONDING',
+ 'COUNT',
+ 'CREATE',
+ 'CROSS',
+ 'CSTRING',
+ 'CURRENT',
+ 'CURRENT_CONNECTION',
+ 'CURRENT_DATE',
+ 'CURRENT_ROLE',
+ 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP',
+ 'CURRENT_TRANSACTION',
+ 'CURRENT_USER',
+ 'DATABASE',
+ 'DATE',
+ 'DAY',
+ 'DB_KEY',
+ 'DEALLOCATE',
+ 'DEBUG',
+ 'DEC',
+ 'DECIMAL',
+ 'DECLARE',
+ 'DEFAULT',
+ 'DEFERRABLE',
+ 'DEFERRED',
+ 'DELETE',
+ 'DELETING',
+ 'DESC',
+ 'DESCENDING',
+ 'DESCRIBE',
+ 'DESCRIPTOR',
+ 'DIAGNOSTICS',
+ 'DIFFERENCE',
+ 'DISCONNECT',
+ 'DISPLAY',
+ 'DISTINCT',
+ 'DO',
+ 'DOMAIN',
+ 'DOUBLE',
+ 'DROP',
+ 'ECHO',
+ 'EDIT',
+ 'ELSE',
+ 'END',
+ 'END-EXEC',
+ 'ENTRY_POINT',
+ 'ESCAPE',
+ 'EVENT',
+ 'EXCEPT',
+ 'EXCEPTION',
+ 'EXEC',
+ 'EXECUTE',
+ 'EXISTS',
+ 'EXIT',
+ 'EXTERN',
+ 'EXTERNAL',
+ 'EXTRACT',
+ 'FALSE',
+ 'FETCH',
+ 'FILE',
+ 'FILTER',
+ 'FIRST',
+ 'FLOAT',
+ 'FOR',
+ 'FOREIGN',
+ 'FOUND',
+ 'FREE_IT',
+ 'FROM',
+ 'FULL',
+ 'FUNCTION',
+ 'GDSCODE',
+ 'GEN_ID',
+ 'GENERATOR',
+ 'GET',
+ 'GLOBAL',
+ 'GO',
+ 'GOTO',
+ 'GRANT',
+ 'GROUP',
+ 'GROUP_COMMIT_WAIT',
+ 'GROUP_COMMIT_WAIT_TIME',
+ 'HAVING',
+ 'HELP',
+ 'HOUR',
+ 'IDENTITY',
+ 'IF',
+ 'IIF',
+ 'IMMEDIATE',
+ 'IN',
+ 'INACTIVE',
+ 'INDEX',
+ 'INDICATOR',
+ 'INIT',
+ 'INITIALLY',
+ 'INNER',
+ 'INPUT',
+ 'INPUT_TYPE',
+ 'INSENSITIVE',
+ 'INSERT',
+ 'INSERTING',
+ 'INT',
+ 'INTEGER',
+ 'INTERSECT',
+ 'INTERVAL',
+ 'INTO',
+ 'IS',
+ 'ISOLATION',
+ 'ISQL',
+ 'JOIN',
+ 'KEY',
+ 'LANGUAGE',
+ 'LAST',
+ 'LC_MESSAGES',
+ 'LC_TYPE',
+ 'LEADING',
+ 'LEADING',
+ 'LEADING',
+ 'LEAVE',
+ 'LEFT',
+ 'LENGTH',
+ 'LEV',
+ 'LEVEL',
+ 'LIKE',
+ 'LOCAL',
+ 'LOCK',
+ 'LOG_BUF_SIZE',
+ 'LOG_BUFFER_SIZE',
+ 'LOGFILE',
+ 'LONG',
+ 'LOWER',
+ 'MANUAL',
+ 'MATCH',
+ 'MAX',
+ 'MAX_SEGMENT',
+ 'MAXIMUM',
+ 'MAXIMUM_SEGMENT',
+ 'MERGE',
+ 'MESSAGE',
+ 'MIN',
+ 'MINIMUM',
+ 'MINUTE',
+ 'MODULE',
+ 'MODULE_NAME',
+ 'MONTH',
+ 'NAMES',
+ 'NATIONAL',
+ 'NATURAL',
+ 'NCHAR',
+ 'NEXT',
+ 'NO',
+ 'NOAUTO',
+ 'NOT',
+ 'NULL',
+ 'NULLIF',
+ 'NULLS',
+ 'NUM_LOG_BUFFERS',
+ 'NUM_LOG_BUFS',
+ 'NUMERIC',
+ 'OCTET_LENGTH',
+ 'OF',
+ 'ON',
+ 'ONLY',
+ 'OPEN',
+ 'OPTION',
+ 'OR',
+ 'ORDER',
+ 'OUTER',
+ 'OUTPUT',
+ 'OUTPUT_TYPE',
+ 'OVERFLOW',
+ 'OVERLAPS',
+ 'PAD',
+ 'PAGE',
+ 'PAGE_SIZE',
+ 'PAGELENGTH',
+ 'PAGES',
+ 'PARAMETER',
+ 'PARTIAL',
+ 'PASSWORD',
+ 'PERCENT',
+ 'PLAN',
+ 'POSITION',
+ 'POST_EVENT',
+ 'PRECISION',
+ 'PREPARE',
+ 'PRESERVE',
+ 'PRIMARY',
+ 'PRIOR',
+ 'PRIVILEGES',
+ 'PROCEDURE',
+ 'PUBLIC',
+ 'QUIT',
+ 'RAW_PARTITIONS',
+ 'RDB$DB_KEY',
+ 'READ',
+ 'REAL',
+ 'RECORD_VERSION',
+ 'RECREATE',
+ 'RECREATE ROW_COUNT',
+ 'REFERENCES',
+ 'RELATIVE',
+ 'RELEASE',
+ 'RESERV',
+ 'RESERVING',
+ 'RESTART',
+ 'RESTRICT',
+ 'RETAIN',
+ 'RETURN',
+ 'RETURNING',
+ 'RETURNING_VALUES',
+ 'RETURNS',
+ 'REVOKE',
+ 'RIGHT',
+ 'ROLE',
+ 'ROLLBACK',
+ 'ROW_COUNT',
+ 'ROWS',
+ 'RUNTIME',
+ 'SAVEPOINT',
+ 'SCALAR_ARRAY',
+ 'SCHEMA',
+ 'SCROLL',
+ 'SECOND',
+ 'SECTION',
+ 'SELECT',
+ 'SEQUENCE',
+ 'SESSION',
+ 'SESSION_USER',
+ 'SET',
+ 'SHADOW',
+ 'SHARED',
+ 'SHELL',
+ 'SHOW',
+ 'SINGULAR',
+ 'SIZE',
+ 'SKIP',
+ 'SMALLINT',
+ 'SNAPSHOT',
+ 'SOME',
+ 'SORT',
+ 'SPACE',
+ 'SQL',
+ 'SQLCODE',
+ 'SQLERROR',
+ 'SQLSTATE',
+ 'SQLWARNING',
+ 'STABILITY',
+ 'STARTING',
+ 'STARTS',
+ 'STATEMENT',
+ 'STATIC',
+ 'STATISTICS',
+ 'SUB_TYPE',
+ 'SUBSTRING',
+ 'SUM',
+ 'SUSPEND',
+ 'SYSTEM_USER',
+ 'TABLE',
+ 'TEMPORARY',
+ 'TERMINATOR',
+ 'THEN',
+ 'TIES',
+ 'TIME',
+ 'TIMESTAMP',
+ 'TIMEZONE_HOUR',
+ 'TIMEZONE_MINUTE',
+ 'TO',
+ 'TRAILING',
+ 'TRANSACTION',
+ 'TRANSLATE',
+ 'TRANSLATION',
+ 'TRIGGER',
+ 'TRIM',
+ 'TRUE',
+ 'TYPE',
+ 'UNCOMMITTED',
+ 'UNION',
+ 'UNIQUE',
+ 'UNKNOWN',
+ 'UPDATE',
+ 'UPDATING',
+ 'UPPER',
+ 'USAGE',
+ 'USER',
+ 'USING',
+ 'VALUE',
+ 'VALUES',
+ 'VARCHAR',
+ 'VARIABLE',
+ 'VARYING',
+ 'VERSION',
+ 'VIEW',
+ 'WAIT',
+ 'WEEKDAY',
+ 'WHEN',
+ 'WHENEVER',
+ 'WHERE',
+ 'WHILE',
+ 'WITH',
+ 'WORK',
+ 'WRITE',
+ 'YEAR',
+ 'YEARDAY',
+ 'ZONE',
+);
+// }}}
+?>
\ No newline at end of file
diff --git a/inc/MDB2/Schema/Reserved/mssql.php b/inc/MDB2/Schema/Reserved/mssql.php
new file mode 100644
index 0000000000..74ac688578
--- /dev/null
+++ b/inc/MDB2/Schema/Reserved/mssql.php
@@ -0,0 +1,258 @@
+ |
+// +----------------------------------------------------------------------+
+// }}}
+// {{{ $GLOBALS['_MDB2_Schema_Reserved']['mssql']
+/**
+ * Has a list of all the reserved words for mssql.
+ *
+ * @package MDB2_Schema
+ * @category Database
+ * @access protected
+ * @author David Coallier
+ */
+$GLOBALS['_MDB2_Schema_Reserved']['mssql'] = array(
+ 'ADD',
+ 'CURRENT_TIMESTAMP',
+ 'GROUP',
+ 'OPENQUERY',
+ 'SERIALIZABLE',
+ 'ALL',
+ 'CURRENT_USER',
+ 'HAVING',
+ 'OPENROWSET',
+ 'SESSION_USER',
+ 'ALTER',
+ 'CURSOR',
+ 'HOLDLOCK',
+ 'OPTION',
+ 'SET',
+ 'AND',
+ 'DATABASE',
+ 'IDENTITY',
+ 'OR',
+ 'SETUSER',
+ 'ANY',
+ 'DBCC',
+ 'IDENTITYCOL',
+ 'ORDER',
+ 'SHUTDOWN',
+ 'AS',
+ 'DEALLOCATE',
+ 'IDENTITY_INSERT',
+ 'OUTER',
+ 'SOME',
+ 'ASC',
+ 'DECLARE',
+ 'IF',
+ 'OVER',
+ 'STATISTICS',
+ 'AUTHORIZATION',
+ 'DEFAULT',
+ 'IN',
+ 'PERCENT',
+ 'SUM',
+ 'AVG',
+ 'DELETE',
+ 'INDEX',
+ 'PERM',
+ 'SYSTEM_USER',
+ 'BACKUP',
+ 'DENY',
+ 'INNER',
+ 'PERMANENT',
+ 'TABLE',
+ 'BEGIN',
+ 'DESC',
+ 'INSERT',
+ 'PIPE',
+ 'TAPE',
+ 'BETWEEN',
+ 'DISK',
+ 'INTERSECT',
+ 'PLAN',
+ 'TEMP',
+ 'BREAK',
+ 'DISTINCT',
+ 'INTO',
+ 'PRECISION',
+ 'TEMPORARY',
+ 'BROWSE',
+ 'DISTRIBUTED',
+ 'IS',
+ 'PREPARE',
+ 'TEXTSIZE',
+ 'BULK',
+ 'DOUBLE',
+ 'ISOLATION',
+ 'PRIMARY',
+ 'THEN',
+ 'BY',
+ 'DROP',
+ 'JOIN',
+ 'PRINT',
+ 'TO',
+ 'CASCADE',
+ 'DUMMY',
+ 'KEY',
+ 'PRIVILEGES',
+ 'TOP',
+ 'CASE',
+ 'DUMP',
+ 'KILL',
+ 'PROC',
+ 'TRAN',
+ 'CHECK',
+ 'ELSE',
+ 'LEFT',
+ 'PROCEDURE',
+ 'TRANSACTION',
+ 'CHECKPOINT',
+ 'END',
+ 'LEVEL',
+ 'PROCESSEXIT',
+ 'TRIGGER',
+ 'CLOSE',
+ 'ERRLVL',
+ 'LIKE',
+ 'PUBLIC',
+ 'TRUNCATE',
+ 'CLUSTERED',
+ 'ERROREXIT',
+ 'LINENO',
+ 'RAISERROR',
+ 'TSEQUAL',
+ 'COALESCE',
+ 'ESCAPE',
+ 'LOAD',
+ 'READ',
+ 'UNCOMMITTED',
+ 'COLUMN',
+ 'EXCEPT',
+ 'MAX',
+ 'READTEXT',
+ 'UNION',
+ 'COMMIT',
+ 'EXEC',
+ 'MIN',
+ 'RECONFIGURE',
+ 'UNIQUE',
+ 'COMMITTED',
+ 'EXECUTE',
+ 'MIRROREXIT',
+ 'REFERENCES',
+ 'UPDATE',
+ 'COMPUTE',
+ 'EXISTS',
+ 'NATIONAL',
+ 'REPEATABLE',
+ 'UPDATETEXT',
+ 'CONFIRM',
+ 'EXIT',
+ 'NOCHECK',
+ 'REPLICATION',
+ 'USE',
+ 'CONSTRAINT',
+ 'FETCH',
+ 'NONCLUSTERED',
+ 'RESTORE',
+ 'USER',
+ 'CONTAINS',
+ 'FILE',
+ 'NOT',
+ 'RESTRICT',
+ 'VALUES',
+ 'CONTAINSTABLE',
+ 'FILLFACTOR',
+ 'NULL',
+ 'RETURN',
+ 'VARYING',
+ 'CONTINUE',
+ 'FLOPPY',
+ 'NULLIF',
+ 'REVOKE',
+ 'VIEW',
+ 'CONTROLROW',
+ 'FOR',
+ 'OF',
+ 'RIGHT',
+ 'WAITFOR',
+ 'CONVERT',
+ 'FOREIGN',
+ 'OFF',
+ 'ROLLBACK',
+ 'WHEN',
+ 'COUNT',
+ 'FREETEXT',
+ 'OFFSETS',
+ 'ROWCOUNT',
+ 'WHERE',
+ 'CREATE',
+ 'FREETEXTTABLE',
+ 'ON',
+ 'ROWGUIDCOL',
+ 'WHILE',
+ 'CROSS',
+ 'FROM',
+ 'ONCE',
+ 'RULE',
+ 'WITH',
+ 'CURRENT',
+ 'FULL',
+ 'ONLY',
+ 'SAVE',
+ 'WORK',
+ 'CURRENT_DATE',
+ 'GOTO',
+ 'OPEN',
+ 'SCHEMA',
+ 'WRITETEXT',
+ 'CURRENT_TIME',
+ 'GRANT',
+ 'OPENDATASOURCE',
+ 'SELECT',
+);
+//}}}
+
+?>
diff --git a/inc/MDB2/Schema/Reserved/mysql.php b/inc/MDB2/Schema/Reserved/mysql.php
new file mode 100644
index 0000000000..4f0575e0bb
--- /dev/null
+++ b/inc/MDB2/Schema/Reserved/mysql.php
@@ -0,0 +1,284 @@
+ |
+// +----------------------------------------------------------------------+
+//
+// $Id: mysql.php,v 1.3 2006/03/01 12:16:40 lsmith Exp $
+// }}}
+// {{{ $GLOBALS['_MDB2_Schema_Reserved']['mysql']
+/**
+ * Has a list of reserved words of mysql
+ *
+ * @package MDB2_Schema
+ * @category Database
+ * @access protected
+ * @author David Coalier
+ */
+$GLOBALS['_MDB2_Schema_Reserved']['mysql'] = array(
+ 'ADD',
+ 'ALL',
+ 'ALTER',
+ 'ANALYZE',
+ 'AND',
+ 'AS',
+ 'ASC',
+ 'ASENSITIVE',
+ 'BEFORE',
+ 'BETWEEN',
+ 'BIGINT',
+ 'BINARY',
+ 'BLOB',
+ 'BOTH',
+ 'BY',
+ 'CALL',
+ 'CASCADE',
+ 'CASE',
+ 'CHANGE',
+ 'CHAR',
+ 'CHARACTER',
+ 'CHECK',
+ 'COLLATE',
+ 'COLUMN',
+ 'CONDITION',
+ 'CONNECTION',
+ 'CONSTRAINT',
+ 'CONTINUE',
+ 'CONVERT',
+ 'CREATE',
+ 'CROSS',
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP',
+ 'CURRENT_USER',
+ 'CURSOR',
+ 'DATABASE',
+ 'DATABASES',
+ 'DAY_HOUR',
+ 'DAY_MICROSECOND',
+ 'DAY_MINUTE',
+ 'DAY_SECOND',
+ 'DEC',
+ 'DECIMAL',
+ 'DECLARE',
+ 'DEFAULT',
+ 'DELAYED',
+ 'DELETE',
+ 'DESC',
+ 'DESCRIBE',
+ 'DETERMINISTIC',
+ 'DISTINCT',
+ 'DISTINCTROW',
+ 'DIV',
+ 'DOUBLE',
+ 'DROP',
+ 'DUAL',
+ 'EACH',
+ 'ELSE',
+ 'ELSEIF',
+ 'ENCLOSED',
+ 'ESCAPED',
+ 'EXISTS',
+ 'EXIT',
+ 'EXPLAIN',
+ 'FALSE',
+ 'FETCH',
+ 'FLOAT',
+ 'FLOAT4',
+ 'FLOAT8',
+ 'FOR',
+ 'FORCE',
+ 'FOREIGN',
+ 'FROM',
+ 'FULLTEXT',
+ 'GOTO',
+ 'GRANT',
+ 'GROUP',
+ 'HAVING',
+ 'HIGH_PRIORITY',
+ 'HOUR_MICROSECOND',
+ 'HOUR_MINUTE',
+ 'HOUR_SECOND',
+ 'IF',
+ 'IGNORE',
+ 'IN',
+ 'INDEX',
+ 'INFILE',
+ 'INNER',
+ 'INOUT',
+ 'INSENSITIVE',
+ 'INSERT',
+ 'INT',
+ 'INT1',
+ 'INT2',
+ 'INT3',
+ 'INT4',
+ 'INT8',
+ 'INTEGER',
+ 'INTERVAL',
+ 'INTO',
+ 'IS',
+ 'ITERATE',
+ 'JOIN',
+ 'KEY',
+ 'KEYS',
+ 'KILL',
+ 'LABEL',
+ 'LEADING',
+ 'LEAVE',
+ 'LEFT',
+ 'LIKE',
+ 'LIMIT',
+ 'LINES',
+ 'LOAD',
+ 'LOCALTIME',
+ 'LOCALTIMESTAMP',
+ 'LOCK',
+ 'LONG',
+ 'LONGBLOB',
+ 'LONGTEXT',
+ 'LOOP',
+ 'LOW_PRIORITY',
+ 'MATCH',
+ 'MEDIUMBLOB',
+ 'MEDIUMINT',
+ 'MEDIUMTEXT',
+ 'MIDDLEINT',
+ 'MINUTE_MICROSECOND',
+ 'MINUTE_SECOND',
+ 'MOD',
+ 'MODIFIES',
+ 'NATURAL',
+ 'NOT',
+ 'NO_WRITE_TO_BINLOG',
+ 'NULL',
+ 'NUMERIC',
+ 'ON',
+ 'OPTIMIZE',
+ 'OPTION',
+ 'OPTIONALLY',
+ 'OR',
+ 'ORDER',
+ 'OUT',
+ 'OUTER',
+ 'OUTFILE',
+ 'PRECISION',
+ 'PRIMARY',
+ 'PROCEDURE',
+ 'PURGE',
+ 'RAID0',
+ 'READ',
+ 'READS',
+ 'REAL',
+ 'REFERENCES',
+ 'REGEXP',
+ 'RELEASE',
+ 'RENAME',
+ 'REPEAT',
+ 'REPLACE',
+ 'REQUIRE',
+ 'RESTRICT',
+ 'RETURN',
+ 'REVOKE',
+ 'RIGHT',
+ 'RLIKE',
+ 'SCHEMA',
+ 'SCHEMAS',
+ 'SECOND_MICROSECOND',
+ 'SELECT',
+ 'SENSITIVE',
+ 'SEPARATOR',
+ 'SET',
+ 'SHOW',
+ 'SMALLINT',
+ 'SONAME',
+ 'SPATIAL',
+ 'SPECIFIC',
+ 'SQL',
+ 'SQLEXCEPTION',
+ 'SQLSTATE',
+ 'SQLWARNING',
+ 'SQL_BIG_RESULT',
+ 'SQL_CALC_FOUND_ROWS',
+ 'SQL_SMALL_RESULT',
+ 'SSL',
+ 'STARTING',
+ 'STRAIGHT_JOIN',
+ 'TABLE',
+ 'TERMINATED',
+ 'THEN',
+ 'TINYBLOB',
+ 'TINYINT',
+ 'TINYTEXT',
+ 'TO',
+ 'TRAILING',
+ 'TRIGGER',
+ 'TRUE',
+ 'UNDO',
+ 'UNION',
+ 'UNIQUE',
+ 'UNLOCK',
+ 'UNSIGNED',
+ 'UPDATE',
+ 'USAGE',
+ 'USE',
+ 'USING',
+ 'UTC_DATE',
+ 'UTC_TIME',
+ 'UTC_TIMESTAMP',
+ 'VALUES',
+ 'VARBINARY',
+ 'VARCHAR',
+ 'VARCHARACTER',
+ 'VARYING',
+ 'WHEN',
+ 'WHERE',
+ 'WHILE',
+ 'WITH',
+ 'WRITE',
+ 'X509',
+ 'XOR',
+ 'YEAR_MONTH',
+ 'ZEROFILL',
+ );
+ // }}}
+?>
diff --git a/inc/MDB2/Schema/Reserved/oci8.php b/inc/MDB2/Schema/Reserved/oci8.php
new file mode 100644
index 0000000000..57fe12ddca
--- /dev/null
+++ b/inc/MDB2/Schema/Reserved/oci8.php
@@ -0,0 +1,171 @@
+ |
+// +----------------------------------------------------------------------+
+// }}}
+// {{{ $GLOBALS['_MDB2_Schema_Reserved']['oci8']
+/**
+ * Has a list of all the reserved words for oracle.
+ *
+ * @package MDB2_Schema
+ * @category Database
+ * @access protected
+ * @author David Coallier
+ */
+$GLOBALS['_MDB2_Schema_Reserved']['oci8'] = array(
+ 'ACCESS',
+ 'ELSE',
+ 'MODIFY',
+ 'START',
+ 'ADD',
+ 'EXCLUSIVE',
+ 'NOAUDIT',
+ 'SELECT',
+ 'ALL',
+ 'EXISTS',
+ 'NOCOMPRESS',
+ 'SESSION',
+ 'ALTER',
+ 'FILE',
+ 'NOT',
+ 'SET',
+ 'AND',
+ 'FLOAT',
+ 'NOTFOUND ',
+ 'SHARE',
+ 'ANY',
+ 'FOR',
+ 'NOWAIT',
+ 'SIZE',
+ 'ARRAYLEN',
+ 'FROM',
+ 'NULL',
+ 'SMALLINT',
+ 'AS',
+ 'GRANT',
+ 'NUMBER',
+ 'SQLBUF',
+ 'ASC',
+ 'GROUP',
+ 'OF',
+ 'SUCCESSFUL',
+ 'AUDIT',
+ 'HAVING',
+ 'OFFLINE ',
+ 'SYNONYM',
+ 'BETWEEN',
+ 'IDENTIFIED',
+ 'ON',
+ 'SYSDATE',
+ 'BY',
+ 'IMMEDIATE',
+ 'ONLINE',
+ 'TABLE',
+ 'CHAR',
+ 'IN',
+ 'OPTION',
+ 'THEN',
+ 'CHECK',
+ 'INCREMENT',
+ 'OR',
+ 'TO',
+ 'CLUSTER',
+ 'INDEX',
+ 'ORDER',
+ 'TRIGGER',
+ 'COLUMN',
+ 'INITIAL',
+ 'PCTFREE',
+ 'UID',
+ 'COMMENT',
+ 'INSERT',
+ 'PRIOR',
+ 'UNION',
+ 'COMPRESS',
+ 'INTEGER',
+ 'PRIVILEGES',
+ 'UNIQUE',
+ 'CONNECT',
+ 'INTERSECT',
+ 'PUBLIC',
+ 'UPDATE',
+ 'CREATE',
+ 'INTO',
+ 'RAW',
+ 'USER',
+ 'CURRENT',
+ 'IS',
+ 'RENAME',
+ 'VALIDATE',
+ 'DATE',
+ 'LEVEL',
+ 'RESOURCE',
+ 'VALUES',
+ 'DECIMAL',
+ 'LIKE',
+ 'REVOKE',
+ 'VARCHAR',
+ 'DEFAULT',
+ 'LOCK',
+ 'ROW',
+ 'VARCHAR2',
+ 'DELETE',
+ 'LONG',
+ 'ROWID',
+ 'VIEW',
+ 'DESC',
+ 'MAXEXTENTS',
+ 'ROWLABEL',
+ 'WHENEVER',
+ 'DISTINCT',
+ 'MINUS',
+ 'ROWNUM',
+ 'WHERE',
+ 'DROP',
+ 'MODE',
+ 'ROWS',
+ 'WITH',
+);
+// }}}
+
+?>
diff --git a/inc/MDB2/Schema/Reserved/pgsql.php b/inc/MDB2/Schema/Reserved/pgsql.php
new file mode 100644
index 0000000000..d358e9c12f
--- /dev/null
+++ b/inc/MDB2/Schema/Reserved/pgsql.php
@@ -0,0 +1,147 @@
+ |
+// +----------------------------------------------------------------------+
+//
+// }}}
+// {{{ $GLOBALS['_MDB2_Schema_Reserved']['pgsql']
+/**
+ * Has a list of reserved words of pgsql
+ *
+ * @package MDB2_Schema
+ * @category Database
+ * @access protected
+ * @author Marcelo Santos Araujo
+ */
+$GLOBALS['_MDB2_Schema_Reserved']['pgsql'] = array(
+ 'ALL',
+ 'ANALYSE',
+ 'ANALYZE',
+ 'AND',
+ 'ANY',
+ 'AS',
+ 'ASC',
+ 'AUTHORIZATION',
+ 'BETWEEN',
+ 'BINARY',
+ 'BOTH',
+ 'CASE',
+ 'CAST',
+ 'CHECK',
+ 'COLLATE',
+ 'COLUMN',
+ 'CONSTRAINT',
+ 'CREATE',
+ 'CURRENT_DATE',
+ 'CURRENT_TIME',
+ 'CURRENT_TIMESTAMP',
+ 'CURRENT_USER',
+ 'DEFAULT',
+ 'DEFERRABLE',
+ 'DESC',
+ 'DISTINCT',
+ 'DO',
+ 'ELSE',
+ 'END',
+ 'EXCEPT',
+ 'FALSE',
+ 'FOR',
+ 'FOREIGN',
+ 'FREEZE',
+ 'FROM',
+ 'FULL',
+ 'GRANT',
+ 'GROUP',
+ 'HAVING',
+ 'ILIKE',
+ 'IN',
+ 'INITIALLY',
+ 'INNER',
+ 'INTERSECT',
+ 'INTO',
+ 'IS',
+ 'ISNULL',
+ 'JOIN',
+ 'LEADING',
+ 'LEFT',
+ 'LIKE',
+ 'LIMIT',
+ 'LOCALTIME',
+ 'LOCALTIMESTAMP',
+ 'NATURAL',
+ 'NEW',
+ 'NOT',
+ 'NOTNULL',
+ 'NULL',
+ 'OFF',
+ 'OFFSET',
+ 'OLD',
+ 'ON',
+ 'ONLY',
+ 'OR',
+ 'ORDER',
+ 'OUTER',
+ 'OVERLAPS',
+ 'PLACING',
+ 'PRIMARY',
+ 'REFERENCES',
+ 'SELECT',
+ 'SESSION_USER',
+ 'SIMILAR',
+ 'SOME',
+ 'TABLE',
+ 'THEN',
+ 'TO',
+ 'TRAILING',
+ 'TRUE',
+ 'UNION',
+ 'UNIQUE',
+ 'USER',
+ 'USING',
+ 'VERBOSE',
+ 'WHEN',
+ 'WHERE'
+);
+// }}}
+?>
+
diff --git a/inc/MDB2/Schema/Tool.php b/inc/MDB2/Schema/Tool.php
new file mode 100644
index 0000000000..9689a0f6d7
--- /dev/null
+++ b/inc/MDB2/Schema/Tool.php
@@ -0,0 +1,560 @@
+
+ * $Id: Tool.php,v 1.6 2008/12/13 00:26:07 clockwerx Exp $
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Christian Weiske
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @version CVS: $Id: Tool.php,v 1.6 2008/12/13 00:26:07 clockwerx Exp $
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+
+require_once 'MDB2/Schema.php';
+require_once 'MDB2/Schema/Tool/ParameterException.php';
+
+/**
+* Command line tool to work with database schemas
+*
+* Functionality:
+* - dump a database schema to stdout
+* - import schema into database
+* - create a diff between two schemas
+* - apply diff to database
+*
+ * @category Database
+ * @package MDB2_Schema
+ * @author Christian Weiske
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema_Tool
+{
+ /**
+ * Run the schema tool
+ *
+ * @param array $args Array of command line arguments
+ */
+ public function __construct($args)
+ {
+ $strAction = $this->getAction($args);
+ try {
+ $this->{'do' . ucfirst($strAction)}($args);
+ } catch (MDB2_Schema_Tool_ParameterException $e) {
+ $this->{'doHelp' . ucfirst($strAction)}($e->getMessage());
+ }
+ }//public function __construct($args)
+
+
+
+ /**
+ * Runs the tool with command line arguments
+ *
+ * @return void
+ */
+ public static function run()
+ {
+ $args = $GLOBALS['argv'];
+ array_shift($args);
+
+ try {
+ $tool = new MDB2_Schema_Tool($args);
+ } catch (Exception $e) {
+ self::toStdErr($e->getMessage() . "\n");
+ }
+ }//public static function run()
+
+
+
+ /**
+ * Reads the first parameter from the argument array and
+ * returns the action.
+ *
+ * @param array &$args Command line parameters
+ *
+ * @return string Action to execute
+ */
+ protected function getAction(&$args)
+ {
+ if (count($args) == 0) {
+ return 'help';
+ }
+ $arg = array_shift($args);
+ switch ($arg) {
+ case 'h':
+ case 'help':
+ case '-h':
+ case '--help':
+ return 'help';
+ case 'd':
+ case 'dump':
+ case '-d':
+ case '--dump':
+ return 'dump';
+ case 'l':
+ case 'load':
+ case '-l':
+ case '--load':
+ return 'load';
+ case 'i':
+ case 'diff':
+ case '-i':
+ case '--diff':
+ return 'diff';
+ case 'a':
+ case 'apply':
+ case '-a':
+ case '--apply':
+ return 'apply';
+ case 'n':
+ case 'init':
+ case '-i':
+ case '--init':
+ return 'init';
+ default:
+ throw new MDB2_Schema_Tool_ParameterException("Unknown mode \"$arg\"");
+ }
+ }//protected function getAction(&$args)
+
+
+
+ /**
+ * Writes the message to stderr
+ *
+ * @param string $msg Message to print
+ *
+ * @return void
+ */
+ protected static function toStdErr($msg)
+ {
+ file_put_contents('php://stderr', $msg);
+ }//protected static function toStdErr($msg)
+
+
+
+ /**
+ * Displays generic help to stdout
+ *
+ * @return void
+ */
+ protected function doHelp()
+ {
+ self::toStdErr(<< '
',
+ 'idxname_format' => '%s',
+ 'debug' => true,
+ 'quote_identifier' => true,
+ 'force_defaults' => false,
+ 'portability' => true,
+ 'use_transactions' => false,
+ );
+ return $options;
+ }//protected function getSchemaOptions()
+
+
+
+ /**
+ * Checks if the passed parameter is a PEAR_Error object
+ * and throws an exception in that case.
+ *
+ * @param mixed $object Some variable to check
+ * @param string $location Where the error occured
+ *
+ * @return void
+ */
+ protected function throwExceptionOnError($object, $location = '')
+ {
+ if (PEAR::isError($object)) {
+ //FIXME: exception class
+ //debug_print_backtrace();
+ throw new Exception('Error ' . $location
+ . "\n" . $object->getMessage()
+ . "\n" . $object->getUserInfo()
+ );
+ }
+ }//protected function throwExceptionOnError($object, $location = '')
+
+
+
+ /**
+ * Loads a file or a dsn from the arguments
+ *
+ * @param array &$args Array of arguments to the program
+ *
+ * @return array Array of ('file'|'dsn', $value)
+ */
+ protected function getFileOrDsn(&$args)
+ {
+ if (count($args) == 0) {
+ throw new MDB2_Schema_Tool_ParameterException('File or DSN expected');
+ }
+
+ $arg = array_shift($args);
+ if ($arg == '-p') {
+ $bAskPassword = true;
+ $arg = array_shift($args);
+ } else {
+ $bAskPassword = false;
+ }
+
+ if (strpos($arg, '://') === false) {
+ if (file_exists($arg)) {
+ //File
+ return array('file', $arg);
+ } else {
+ throw new Exception('Schema file does not exist');
+ }
+ }
+
+ //read password if necessary
+ if ($bAskPassword) {
+ $password = $this->readPasswordFromStdin($arg);
+ $arg = self::setPasswordIntoDsn($arg, $password);
+ self::toStdErr($arg);
+ }
+ return array('dsn', $arg);
+ }//protected function getFileOrDsn(&$args)
+
+
+
+ /**
+ * Takes a DSN data source name and integrates the given
+ * password into it.
+ *
+ * @param string $dsn Data source name
+ * @param string $password Password
+ *
+ * @return string DSN with password
+ */
+ protected function setPasswordIntoDsn($dsn, $password)
+ {
+ //simple try to integrate password
+ if (strpos($dsn, '@') === false) {
+ //no @ -> no user and no password
+ return str_replace('://', '://:' . $password . '@', $dsn);
+ } else if (preg_match('|://[^:]+@|', $dsn)) {
+ //user only, no password
+ return str_replace('@', ':' . $password . '@', $dsn);
+ } else if (strpos($dsn, ':@') !== false) {
+ //abstract version
+ return str_replace(':@', ':' . $password . '@', $dsn);
+ }
+
+ return $dsn;
+ }//protected function setPasswordIntoDsn($dsn, $password)
+
+
+
+ /**
+ * Reads a password from stdin
+ *
+ * @param string $dsn DSN name to put into the message
+ *
+ * @return string Password
+ */
+ protected function readPasswordFromStdin($dsn)
+ {
+ $stdin = fopen('php://stdin', 'r');
+ self::toStdErr('Please insert password for ' . $dsn . "\n");
+ $password = '';
+ $breakme = false;
+ while (false !== ($char = fgetc($stdin))) {
+ if (ord($char) == 10 || $char == "\n" || $char == "\r") {
+ break;
+ }
+ $password .= $char;
+ }
+ fclose($stdin);
+
+ return trim($password);
+ }//protected function readPasswordFromStdin()
+
+
+
+ /**
+ * Creates a database schema dump and sends it to stdout
+ *
+ * @param array $args Command line arguments
+ *
+ * @return void
+ */
+ protected function doDump($args)
+ {
+ $dump_what = MDB2_SCHEMA_DUMP_STRUCTURE;
+ $arg = '';
+ if (count($args)) {
+ $arg = $args[0];
+ }
+
+ switch (strtolower($arg)) {
+ case 'all':
+ $dump_what = MDB2_SCHEMA_DUMP_ALL;
+ array_shift($args);
+ break;
+ case 'data':
+ $dump_what = MDB2_SCHEMA_DUMP_CONTENT;
+ array_shift($args);
+ break;
+ case 'schema':
+ array_shift($args);
+ }
+
+ list($type, $dsn) = $this->getFileOrDsn($args);
+ if ($type == 'file') {
+ throw new MDB2_Schema_Tool_ParameterException(
+ 'Dumping a schema file as a schema file does not make much sense'
+ );
+ }
+
+ $schema = MDB2_Schema::factory($dsn, $this->getSchemaOptions());
+ $this->throwExceptionOnError($schema);
+
+ $definition = $schema->getDefinitionFromDatabase();
+ $this->throwExceptionOnError($definition);
+
+
+ $dump_options = array(
+ 'output_mode' => 'file',
+ 'output' => 'php://stdout',
+ 'end_of_line' => "\r\n"
+ );
+ $op = $schema->dumpDatabase(
+ $definition, $dump_options, $dump_what
+ );
+ $this->throwExceptionOnError($op);
+
+ $schema->disconnect();
+ }//protected function doDump($args)
+
+
+
+ /**
+ * Loads a database schema
+ *
+ * @param array $args Command line arguments
+ *
+ * @return void
+ */
+ protected function doLoad($args)
+ {
+ list($typeSource, $dsnSource) = $this->getFileOrDsn($args);
+ list($typeDest, $dsnDest) = $this->getFileOrDsn($args);
+
+ if ($typeDest == 'file') {
+ throw new MDB2_Schema_Tool_ParameterException(
+ 'A schema can only be loaded into a database, not a file'
+ );
+ }
+
+
+ $schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions());
+ $this->throwExceptionOnError($schemaDest);
+
+ //load definition
+ if ($typeSource == 'file') {
+ $definition = $schemaDest->parseDatabaseDefinitionFile($dsnSource);
+ $where = 'loading schema file';
+ } else {
+ $schemaSource = MDB2_Schema::factory($dsnSource, $this->getSchemaOptions());
+ $this->throwExceptionOnError($schemaSource, 'connecting to source database');
+
+ $definition = $schemaSource->getDefinitionFromDatabase();
+ $where = 'loading definition from database';
+ }
+ $this->throwExceptionOnError($definition, $where);
+
+
+ //create destination database from definition
+ $simulate = false;
+ $op = $schemaDest->createDatabase($definition, array(), $simulate);
+ $this->throwExceptionOnError($op, 'creating the database');
+ }//protected function doLoad($args)
+
+
+
+ /**
+ * Initializes a database with data
+ *
+ * @param array $args Command line arguments
+ *
+ * @return void
+ */
+ protected function doInit($args)
+ {
+ list($typeSource, $dsnSource) = $this->getFileOrDsn($args);
+ list($typeDest, $dsnDest) = $this->getFileOrDsn($args);
+
+ if ($typeSource != 'file') {
+ throw new MDB2_Schema_Tool_ParameterException(
+ 'Data must come from a source file'
+ );
+ }
+
+ if ($typeDest != 'dsn') {
+ throw new MDB2_Schema_Tool_ParameterException(
+ 'A schema can only be loaded into a database, not a file'
+ );
+ }
+
+ $schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions());
+ $this->throwExceptionOnError($schemaDest, 'connecting to destination database');
+
+ $definition = $schemaDest->getDefinitionFromDatabase();
+ $this->throwExceptionOnError($definition, 'loading definition from database');
+
+ $op = $schemaDest->writeInitialization($dsnSource, $definition);
+ $this->throwExceptionOnError($op, 'initializing database');
+ }//protected function doInit($args)
+
+
+}//class MDB2_Schema_Tool
+
+?>
diff --git a/inc/MDB2/Schema/Tool/ParameterException.php b/inc/MDB2/Schema/Tool/ParameterException.php
new file mode 100644
index 0000000000..fab1e03e25
--- /dev/null
+++ b/inc/MDB2/Schema/Tool/ParameterException.php
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/inc/MDB2/Schema/Validate.php b/inc/MDB2/Schema/Validate.php
new file mode 100644
index 0000000000..21be024ce9
--- /dev/null
+++ b/inc/MDB2/Schema/Validate.php
@@ -0,0 +1,922 @@
+
+ * Author: Igor Feghali
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Christian Dickmann
+ * @author Igor Feghali
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @version CVS: $Id: Validate.php,v 1.42 2008/11/30 03:34:00 clockwerx Exp $
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+
+/**
+ * Validates an XML schema file
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Igor Feghali
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema_Validate
+{
+ // {{{ properties
+
+ var $fail_on_invalid_names = true;
+
+ var $valid_types = array();
+
+ var $force_defaults = true;
+
+ // }}}
+ // {{{ constructor
+
+ function __construct($fail_on_invalid_names = true, $valid_types = array(), $force_defaults = true)
+ {
+ if (empty($GLOBALS['_MDB2_Schema_Reserved'])) {
+ $GLOBALS['_MDB2_Schema_Reserved'] = array();
+ }
+
+ if (is_array($fail_on_invalid_names)) {
+ $this->fail_on_invalid_names = array_intersect($fail_on_invalid_names,
+ array_keys($GLOBALS['_MDB2_Schema_Reserved']));
+ } elseif ($fail_on_invalid_names === true) {
+ $this->fail_on_invalid_names = array_keys($GLOBALS['_MDB2_Schema_Reserved']);
+ } else {
+ $this->fail_on_invalid_names = array();
+ }
+ $this->valid_types = $valid_types;
+ $this->force_defaults = $force_defaults;
+ }
+
+ function MDB2_Schema_Validate($fail_on_invalid_names = true, $valid_types = array(), $force_defaults = true)
+ {
+ $this->__construct($fail_on_invalid_names, $valid_types, $force_defaults);
+ }
+
+ // }}}
+ // {{{ raiseError()
+
+ function &raiseError($ecode, $msg = null)
+ {
+ $error =& MDB2_Schema::raiseError($ecode, null, null, $msg);
+ return $error;
+ }
+
+ // }}}
+ // {{{ isBoolean()
+
+ /**
+ * Verifies if a given value can be considered boolean. If yes, set value
+ * to true or false according to its actual contents and return true. If
+ * not, keep its contents untouched and return false.
+ *
+ * @param mixed &$value value to be checked
+ *
+ * @return bool
+ *
+ * @access public
+ * @static
+ */
+ function isBoolean(&$value)
+ {
+ if (is_bool($value)) {
+ return true;
+ }
+
+ if ($value === 0 || $value === 1 || $value === '') {
+ $value = (bool)$value;
+ return true;
+ }
+
+ if (!is_string($value)) {
+ return false;
+ }
+
+ switch ($value) {
+ case '0':
+ case 'N':
+ case 'n':
+ case 'no':
+ case 'false':
+ $value = false;
+ break;
+ case '1':
+ case 'Y':
+ case 'y':
+ case 'yes':
+ case 'true':
+ $value = true;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ validateTable()
+
+ /* Definition */
+ /**
+ * Checks whether the definition of a parsed table is valid. Modify table
+ * definition when necessary.
+ *
+ * @param array $tables multi dimensional array that contains the
+ * tables of current database.
+ * @param array &$table multi dimensional array that contains the
+ * structure and optional data of the table.
+ * @param string $table_name name of the parsed table
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateTable($tables, &$table, $table_name)
+ {
+ /* Have we got a name? */
+ if (!$table_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'a table has to have a name');
+ }
+
+ /* Table name duplicated? */
+ if (is_array($tables) && isset($tables[$table_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'table "'.$table_name.'" already exists');
+ }
+
+ /* Table name reserved? */
+ if (is_array($this->fail_on_invalid_names)) {
+ $name = strtoupper($table_name);
+ foreach ($this->fail_on_invalid_names as $rdbms) {
+ if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'table name "'.$table_name.'" is a reserved word in: '.$rdbms);
+ }
+ }
+ }
+
+ /* Was */
+ if (empty($table['was'])) {
+ $table['was'] = $table_name;
+ }
+
+ /* Have we got fields? */
+ if (empty($table['fields']) || !is_array($table['fields'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'tables need one or more fields');
+ }
+
+ /* Autoincrement */
+ $autoinc = $primary = false;
+ foreach ($table['fields'] as $field_name => $field) {
+ if (!empty($field['autoincrement'])) {
+ if ($autoinc) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'there was already an autoincrement field in "'.$table_name.'" before "'.$field_name.'"');
+ }
+ $autoinc = $field_name;
+ }
+ }
+
+ /*
+ * Checking Indexes
+ * this have to be done here otherwise we can't
+ * guarantee that all table fields were already
+ * defined in the moment we are parsing indexes
+ */
+ if (!empty($table['indexes']) && is_array($table['indexes'])) {
+ foreach ($table['indexes'] as $name => $index) {
+ $skip_index = false;
+ if (!empty($index['primary'])) {
+ /*
+ * Lets see if we should skip this index since there is
+ * already an auto increment on this field this implying
+ * a primary key index.
+ */
+ if (count($index['fields']) == '1'
+ && $autoinc
+ && array_key_exists($autoinc, $index['fields'])) {
+ $skip_index = true;
+ } elseif ($autoinc || $primary) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'there was already an primary index or autoincrement field in "'.$table_name.'" before "'.$name.'"');
+ } else {
+ $primary = true;
+ }
+ }
+
+ if (!$skip_index && is_array($index['fields'])) {
+ foreach ($index['fields'] as $field_name => $field) {
+ if (!isset($table['fields'][$field_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'index field "'.$field_name.'" does not exist');
+ }
+ if (!empty($index['primary'])
+ && !$table['fields'][$field_name]['notnull']
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'all primary key fields must be defined notnull in "'.$table_name.'"');
+ }
+ }
+ } else {
+ unset($table['indexes'][$name]);
+ }
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateField()
+
+ /**
+ * Checks whether the definition of a parsed field is valid. Modify field
+ * definition when necessary.
+ *
+ * @param array $fields multi dimensional array that contains the
+ * fields of current table.
+ * @param array &$field multi dimensional array that contains the
+ * structure of the parsed field.
+ * @param string $field_name name of the parsed field
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateField($fields, &$field, $field_name)
+ {
+ /* Have we got a name? */
+ if (!$field_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field name missing');
+ }
+
+ /* Field name duplicated? */
+ if (is_array($fields) && isset($fields[$field_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "'.$field_name.'" already exists');
+ }
+
+ /* Field name reserverd? */
+ if (is_array($this->fail_on_invalid_names)) {
+ $name = strtoupper($field_name);
+ foreach ($this->fail_on_invalid_names as $rdbms) {
+ if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field name "'.$field_name.'" is a reserved word in: '.$rdbms);
+ }
+ }
+ }
+
+ /* Type check */
+ if (empty($field['type'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'no field type specified');
+ }
+ if (!empty($this->valid_types) && !array_key_exists($field['type'], $this->valid_types)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'no valid field type ("'.$field['type'].'") specified');
+ }
+
+ /* Unsigned */
+ if (array_key_exists('unsigned', $field) && !$this->isBoolean($field['unsigned'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'unsigned has to be a boolean value');
+ }
+
+ /* Fixed */
+ if (array_key_exists('fixed', $field) && !$this->isBoolean($field['fixed'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'fixed has to be a boolean value');
+ }
+
+ /* Length */
+ if (array_key_exists('length', $field) && $field['length'] <= 0) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'length has to be an integer greater 0');
+ }
+
+ // if it's a DECIMAL datatype, check if a 'scale' value is provided:
+ // 8,4 should be translated to DECIMAL(8,4)
+ if (is_float($this->valid_types[$field['type']])
+ && !empty($field['length'])
+ && strpos($field['length'], ',') !== false
+ ) {
+ list($field['length'], $field['scale']) = explode(',', $field['length']);
+ }
+
+ /* Was */
+ if (empty($field['was'])) {
+ $field['was'] = $field_name;
+ }
+
+ /* Notnull */
+ if (empty($field['notnull'])) {
+ $field['notnull'] = false;
+ }
+ if (!$this->isBoolean($field['notnull'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "notnull" has to be a boolean value');
+ }
+
+ /* Default */
+ if ($this->force_defaults
+ && !array_key_exists('default', $field)
+ && $field['type'] != 'clob' && $field['type'] != 'blob'
+ ) {
+ $field['default'] = $this->valid_types[$field['type']];
+ }
+ if (array_key_exists('default', $field)) {
+ if ($field['type'] == 'clob' || $field['type'] == 'blob') {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field['type'].'"-fields are not allowed to have a default value');
+ }
+ if ($field['default'] === '' && !$field['notnull']) {
+ $field['default'] = null;
+ }
+ }
+ if (isset($field['default'])
+ && PEAR::isError($result = $this->validateDataFieldValue($field, $field['default'], $field_name))
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'default value of "'.$field_name.'" is incorrect: '.$result->getUserinfo());
+ }
+
+ /* Autoincrement */
+ if (!empty($field['autoincrement'])) {
+ if (!$field['notnull']) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'all autoincrement fields must be defined notnull');
+ }
+
+ if (empty($field['default'])) {
+ $field['default'] = '0';
+ } elseif ($field['default'] !== '0' && $field['default'] !== 0) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'all autoincrement fields must be defined default "0"');
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateIndex()
+
+ /**
+ * Checks whether a parsed index is valid. Modify index definition when
+ * necessary.
+ *
+ * @param array $table_indexes multi dimensional array that contains the
+ * indexes of current table.
+ * @param array &$index multi dimensional array that contains the
+ * structure of the parsed index.
+ * @param string $index_name name of the parsed index
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateIndex($table_indexes, &$index, $index_name)
+ {
+ if (!$index_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'an index has to have a name');
+ }
+ if (is_array($table_indexes) && isset($table_indexes[$index_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'index "'.$index_name.'" already exists');
+ }
+ if (array_key_exists('unique', $index) && !$this->isBoolean($index['unique'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "unique" has to be a boolean value');
+ }
+ if (array_key_exists('primary', $index) && !$this->isBoolean($index['primary'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "primary" has to be a boolean value');
+ }
+
+ /* Have we got fields? */
+ if (empty($index['fields']) || !is_array($index['fields'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'indexes need one or more fields');
+ }
+
+ if (empty($index['was'])) {
+ $index['was'] = $index_name;
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateIndexField()
+
+ /**
+ * Checks whether a parsed index-field is valid. Modify its definition when
+ * necessary.
+ *
+ * @param array $index_fields multi dimensional array that contains the
+ * fields of current index.
+ * @param array &$field multi dimensional array that contains the
+ * structure of the parsed index-field.
+ * @param string $field_name name of the parsed index-field
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateIndexField($index_fields, &$field, $field_name)
+ {
+ if (is_array($index_fields) && isset($index_fields[$field_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'index field "'.$field_name.'" already exists');
+ }
+ if (!$field_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'the index-field-name is required');
+ }
+ if (empty($field['sorting'])) {
+ $field['sorting'] = 'ascending';
+ } elseif ($field['sorting'] !== 'ascending' && $field['sorting'] !== 'descending') {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'sorting type unknown');
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateConstraint()
+
+ /**
+ * Checks whether a parsed foreign key is valid. Modify its definition when
+ * necessary.
+ *
+ * @param array $table_constraints multi dimensional array that contains the
+ * constraints of current table.
+ * @param array &$constraint multi dimensional array that contains the
+ * structure of the parsed foreign key.
+ * @param string $constraint_name name of the parsed foreign key
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateConstraint($table_constraints, &$constraint, $constraint_name)
+ {
+ if (!$constraint_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'a foreign key has to have a name');
+ }
+ if (is_array($table_constraints) && isset($table_constraints[$constraint_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'foreign key "'.$constraint_name.'" already exists');
+ }
+
+ /* Have we got fields? */
+ if (empty($constraint['fields']) || !is_array($constraint['fields'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'foreign key "'.$constraint_name.'" need one or more fields');
+ }
+
+ /* Have we got referenced fields? */
+ if (empty($constraint['references']) || !is_array($constraint['references'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'foreign key "'.$constraint_name.'" need to reference one or more fields');
+ }
+
+ /* Have we got referenced table? */
+ if (empty($constraint['references']['table'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'foreign key "'.$constraint_name.'" need to reference a table');
+ }
+
+ if (empty($constraint['was'])) {
+ $constraint['was'] = $constraint_name;
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateConstraintField()
+
+ /**
+ * Checks whether a foreign-field is valid.
+ *
+ * @param array $constraint_fields multi dimensional array that contains the
+ * fields of current foreign key.
+ * @param string $field_name name of the parsed foreign-field
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateConstraintField($constraint_fields, $field_name)
+ {
+ if (!$field_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'empty value for foreign-field');
+ }
+ if (is_array($constraint_fields) && isset($constraint_fields[$field_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'foreign field "'.$field_name.'" already exists');
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateConstraintReferencedField()
+
+ /**
+ * Checks whether a foreign-referenced field is valid.
+ *
+ * @param array $referenced_fields multi dimensional array that contains the
+ * fields of current foreign key.
+ * @param string $field_name name of the parsed foreign-field
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateConstraintReferencedField($referenced_fields, $field_name)
+ {
+ if (!$field_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'empty value for referenced foreign-field');
+ }
+ if (is_array($referenced_fields) && isset($referenced_fields[$field_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'foreign field "'.$field_name.'" already referenced');
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateSequence()
+
+ /**
+ * Checks whether the definition of a parsed sequence is valid. Modify
+ * sequence definition when necessary.
+ *
+ * @param array $sequences multi dimensional array that contains the
+ * sequences of current database.
+ * @param array &$sequence multi dimensional array that contains the
+ * structure of the parsed sequence.
+ * @param string $sequence_name name of the parsed sequence
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateSequence($sequences, &$sequence, $sequence_name)
+ {
+ if (!$sequence_name) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'a sequence has to have a name');
+ }
+
+ if (is_array($sequences) && isset($sequences[$sequence_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'sequence "'.$sequence_name.'" already exists');
+ }
+
+ if (is_array($this->fail_on_invalid_names)) {
+ $name = strtoupper($sequence_name);
+ foreach ($this->fail_on_invalid_names as $rdbms) {
+ if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'sequence name "'.$sequence_name.'" is a reserved word in: '.$rdbms);
+ }
+ }
+ }
+
+ if (empty($sequence['was'])) {
+ $sequence['was'] = $sequence_name;
+ }
+
+ if (!empty($sequence['on'])
+ && (empty($sequence['on']['table']) || empty($sequence['on']['field']))
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'sequence "'.$sequence_name.'" on a table was not properly defined');
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateDatabase()
+
+ /**
+ * Checks whether a parsed database is valid. Modify its structure and
+ * data when necessary.
+ *
+ * @param array &$database multi dimensional array that contains the
+ * structure and optional data of the database.
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateDatabase(&$database)
+ {
+ /* Have we got a name? */
+ if (!is_array($database) || !isset($database['name']) || !$database['name']) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'a database has to have a name');
+ }
+
+ /* Database name reserved? */
+ if (is_array($this->fail_on_invalid_names)) {
+ $name = strtoupper($database['name']);
+ foreach ($this->fail_on_invalid_names as $rdbms) {
+ if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'database name "'.$database['name'].'" is a reserved word in: '.$rdbms);
+ }
+ }
+ }
+
+ /* Create */
+ if (isset($database['create'])
+ && !$this->isBoolean($database['create'])
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "create" has to be a boolean value');
+ }
+
+ /* Overwrite */
+ if (isset($database['overwrite'])
+ && $database['overwrite'] !== ''
+ && !$this->isBoolean($database['overwrite'])
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "overwrite" has to be a boolean value');
+ }
+
+ /*
+ * This have to be done here otherwise we can't guarantee that all
+ * tables were already defined in the moment we are parsing constraints
+ */
+ if (isset($database['tables'])) {
+ foreach ($database['tables'] as $table_name => $table) {
+ if (!empty($table['constraints'])) {
+ foreach ($table['constraints'] as $constraint_name => $constraint) {
+ $referenced_table_name = $constraint['references']['table'];
+
+ if (!isset($database['tables'][$referenced_table_name])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'referenced table "'.$referenced_table_name.'" of foreign key "'.$constraint_name.'" of table "'.$table_name.'" does not exist');
+ }
+
+ if (empty($constraint['references']['fields'])) {
+ $referenced_table = $database['tables'][$referenced_table_name];
+
+ $primary = false;
+
+ if (!empty($referenced_table['indexes'])) {
+ foreach ($referenced_table['indexes'] as $index_name => $index) {
+ if (array_key_exists('primary', $index)
+ && $index['primary']
+ ) {
+ $primary = array();
+ foreach ($index['fields'] as $field_name => $field) {
+ $primary[$field_name] = '';
+ }
+ break;
+ }
+ }
+ }
+
+ if (!$primary) {
+ foreach ($referenced_table['fields'] as $field_name => $field) {
+ if (array_key_exists('autoincrement', $field)
+ && $field['autoincrement']
+ ) {
+ $primary = array( $field_name => '' );
+ break;
+ }
+ }
+ }
+
+ if (!$primary) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'referenced table "'.$referenced_table_name.'" has no primary key and no referenced field was specified for foreign key "'.$constraint_name.'" of table "'.$table_name.'"');
+ }
+
+ $constraint['references']['fields'] = $primary;
+ }
+
+ /* the same number of referencing and referenced fields ? */
+ if (count($constraint['fields']) != count($constraint['references']['fields'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'The number of fields in the referenced key must match those of the foreign key "'.$constraint_name.'"');
+ }
+
+ $database['tables'][$table_name]['constraints'][$constraint_name]['references']['fields'] = $constraint['references']['fields'];
+ }
+ }
+ }
+ }
+
+ /*
+ * This have to be done here otherwise we can't guarantee that all
+ * tables were already defined in the moment we are parsing sequences
+ */
+ if (isset($database['sequences'])) {
+ foreach ($database['sequences'] as $seq_name => $seq) {
+ if (!empty($seq['on'])
+ && empty($database['tables'][$seq['on']['table']]['fields'][$seq['on']['field']])
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'sequence "'.$seq_name.'" was assigned on unexisting field/table');
+ }
+ }
+ }
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateDataField()
+
+ /* Data Manipulation */
+ /**
+ * Checks whether a parsed DML-field is valid. Modify its structure when
+ * necessary. This is called when validating INSERT and
+ * UPDATE.
+ *
+ * @param array $table_fields multi dimensional array that contains the
+ * definition for current table's fields.
+ * @param array $instruction_fields multi dimensional array that contains the
+ * parsed fields of the current DML instruction.
+ * @param string &$field array that contains the parsed instruction field
+ *
+ * @return bool|error object
+ *
+ * @access public
+ */
+ function validateDataField($table_fields, $instruction_fields, &$field)
+ {
+ if (!$field['name']) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field-name has to be specified');
+ }
+
+ if (is_array($instruction_fields) && isset($instruction_fields[$field['name']])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'field "'.$field['name'].'" already initialized');
+ }
+
+ if (is_array($table_fields) && !isset($table_fields[$field['name']])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field['name'].'" is not defined');
+ }
+
+ if (!isset($field['group']['type'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field['name'].'" has no initial value');
+ }
+
+ if (isset($field['group']['data'])
+ && $field['group']['type'] == 'value'
+ && $field['group']['data'] !== ''
+ && PEAR::isError($result = $this->validateDataFieldValue($table_fields[$field['name']], $field['group']['data'], $field['name']))
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ 'value of "'.$field['name'].'" is incorrect: '.$result->getUserinfo());
+ }
+
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ validateDataFieldValue()
+
+ /**
+ * Checks whether a given value is compatible with a table field. This is
+ * done when parsing a field for a INSERT or UPDATE instruction.
+ *
+ * @param array $field_def multi dimensional array that contains the
+ * definition for current table's fields.
+ * @param string &$field_value value to fill the parsed field
+ * @param string $field_name name of the parsed field
+ *
+ * @return bool|error object
+ *
+ * @access public
+ * @see MDB2_Schema_Validate::validateInsertField()
+ */
+ function validateDataFieldValue($field_def, &$field_value, $field_name)
+ {
+ switch ($field_def['type']) {
+ case 'text':
+ case 'clob':
+ if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is larger than "'.$field_def['length'].'"');
+ }
+ break;
+ case 'blob':
+ $field_value = pack('H*', $field_value);
+ if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is larger than "'.$field_def['type'].'"');
+ }
+ break;
+ case 'integer':
+ if ($field_value != ((int)$field_value)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
+ }
+ //$field_value = (int)$field_value;
+ if (!empty($field_def['unsigned']) && $field_def['unsigned'] && $field_value < 0) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" signed instead of unsigned');
+ }
+ break;
+ case 'boolean':
+ if (!$this->isBoolean($field_value)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
+ }
+ break;
+ case 'date':
+ if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})/', $field_value)
+ && $field_value !== 'CURRENT_DATE'
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
+ }
+ break;
+ case 'timestamp':
+ if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', $field_value)
+ && strcasecmp($field_value, 'now()') != 0
+ && $field_value !== 'CURRENT_TIMESTAMP'
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
+ }
+ break;
+ case 'time':
+ if (!preg_match("/([0-9]{2}):([0-9]{2}):([0-9]{2})/", $field_value)
+ && $field_value !== 'CURRENT_TIME'
+ ) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
+ }
+ break;
+ case 'float':
+ case 'double':
+ if ($field_value != (double)$field_value) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE,
+ '"'.$field_value.'" is not of type "'.$field_def['type'].'"');
+ }
+ //$field_value = (double)$field_value;
+ break;
+ }
+ return MDB2_OK;
+ }
+}
+
+?>
diff --git a/inc/MDB2/Schema/Writer.php b/inc/MDB2/Schema/Writer.php
new file mode 100644
index 0000000000..5ae4918dc1
--- /dev/null
+++ b/inc/MDB2/Schema/Writer.php
@@ -0,0 +1,581 @@
+
+ * Author: Igor Feghali
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Lukas Smith
+ * @author Igor Feghali
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @version CVS: $Id: Writer.php,v 1.40 2008/11/30 03:34:00 clockwerx Exp $
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+
+/**
+ * Writes an XML schema file
+ *
+ * @category Database
+ * @package MDB2_Schema
+ * @author Lukas Smith
+ * @license BSD http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/packages/MDB2_Schema
+ */
+class MDB2_Schema_Writer
+{
+ // {{{ properties
+
+ var $valid_types = array();
+
+ // }}}
+ // {{{ constructor
+
+ function __construct($valid_types = array())
+ {
+ $this->valid_types = $valid_types;
+ }
+
+ function MDB2_Schema_Writer($valid_types = array())
+ {
+ $this->__construct($valid_types);
+ }
+
+ // }}}
+ // {{{ raiseError()
+
+ /**
+ * This method is used to communicate an error and invoke error
+ * callbacks etc. Basically a wrapper for PEAR::raiseError
+ * without the message string.
+ *
+ * @param int|PEAR_Error $code integer error code or and PEAR_Error instance
+ * @param int $mode error mode, see PEAR_Error docs
+ * 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 $options Extra debug information. Defaults to the last
+ * query and native error code.
+ *
+ * @return object a PEAR error object
+ * @access public
+ * @see PEAR_Error
+ */
+ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null)
+ {
+ $error =& MDB2_Schema::raiseError($code, $mode, $options, $userinfo);
+ return $error;
+ }
+
+ // }}}
+ // {{{ _escapeSpecialChars()
+
+ /**
+ * add escapecharacters to all special characters in a string
+ *
+ * @param string $string string that should be escaped
+ *
+ * @return string escaped string
+ * @access protected
+ */
+ function _escapeSpecialChars($string)
+ {
+ if (!is_string($string)) {
+ $string = strval($string);
+ }
+
+ $escaped = '';
+ for ($char = 0, $count = strlen($string); $char < $count; $char++) {
+ switch ($string[$char]) {
+ case '&':
+ $escaped .= '&';
+ break;
+ case '>':
+ $escaped .= '>';
+ break;
+ case '<':
+ $escaped .= '<';
+ break;
+ case '"':
+ $escaped .= '"';
+ break;
+ case '\'':
+ $escaped .= ''';
+ break;
+ default:
+ $code = ord($string[$char]);
+ if ($code < 32 || $code > 127) {
+ $escaped .= "$code;";
+ } else {
+ $escaped .= $string[$char];
+ }
+ break;
+ }
+ }
+ return $escaped;
+ }
+
+ // }}}
+ // {{{ _dumpBoolean()
+
+ /**
+ * dump the structure of a sequence
+ *
+ * @param string $boolean boolean value or variable definition
+ *
+ * @return string with xml boolea definition
+ * @access private
+ */
+ function _dumpBoolean($boolean)
+ {
+ if (is_string($boolean)) {
+ if ($boolean !== 'true' || $boolean !== 'false'
+ || preg_match('/.*/', $boolean)
+ ) {
+ return $boolean;
+ }
+ }
+ return $boolean ? 'true' : 'false';
+ }
+
+ // }}}
+ // {{{ dumpSequence()
+
+ /**
+ * dump the structure of a sequence
+ *
+ * @param string $sequence_definition sequence definition
+ * @param string $sequence_name sequence name
+ * @param string $eol end of line characters
+ * @param integer $dump determines what data to dump
+ * MDB2_SCHEMA_DUMP_ALL : the entire db
+ * MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db
+ * MDB2_SCHEMA_DUMP_CONTENT : only the content of the db
+ *
+ * @return mixed string xml sequence definition on success, or a error object
+ * @access public
+ */
+ function dumpSequence($sequence_definition, $sequence_name, $eol, $dump = MDB2_SCHEMA_DUMP_ALL)
+ {
+ $buffer = "$eol $eol $sequence_name$eol";
+ if ($dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT) {
+ if (!empty($sequence_definition['start'])) {
+ $start = $sequence_definition['start'];
+ $buffer .= " $start$eol";
+ }
+ }
+
+ if (!empty($sequence_definition['on'])) {
+ $buffer .= " $eol";
+ $buffer .= " ".$sequence_definition['on']['table'];
+ $buffer .= "
$eol ".$sequence_definition['on']['field'];
+ $buffer .= "$eol $eol";
+ }
+ $buffer .= " $eol";
+
+ return $buffer;
+ }
+
+ // }}}
+ // {{{ dumpDatabase()
+
+ /**
+ * 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 $database_definition unknown
+ * @param array $arguments associative array that takes pairs of tag
+ * names and values that define dump options.
+ * array (
+ * 'output_mode' => String
+ * 'file' : dump into a file
+ * default: dump using a function
+ * 'output' => String
+ * depending on the 'Output_Mode'
+ * name of the file
+ * name of the function
+ * 'end_of_line' => String
+ * end of line delimiter that should be used
+ * default: "\n"
+ * );
+ * @param integer $dump determines what data to dump
+ * MDB2_SCHEMA_DUMP_ALL : the entire db
+ * MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db
+ * MDB2_SCHEMA_DUMP_CONTENT : only the content of the db
+ *
+ * @return mixed MDB2_OK on success, or a error object
+ * @access public
+ */
+ function dumpDatabase($database_definition, $arguments, $dump = MDB2_SCHEMA_DUMP_ALL)
+ {
+ if (!empty($arguments['output'])) {
+ if (!empty($arguments['output_mode']) && $arguments['output_mode'] == 'file') {
+ $fp = fopen($arguments['output'], 'w');
+ if ($fp === false) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_WRITER, null, null,
+ 'it was not possible to open output file');
+ }
+
+ $output = false;
+ } elseif (is_callable($arguments['output'])) {
+ $output = $arguments['output'];
+ } else {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_WRITER, null, null,
+ 'no valid output function specified');
+ }
+ } else {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_WRITER, null, null,
+ 'no output method specified');
+ }
+
+ $eol = isset($arguments['end_of_line']) ? $arguments['end_of_line'] : "\n";
+
+ $sequences = array();
+ if (!empty($database_definition['sequences'])
+ && is_array($database_definition['sequences'])
+ ) {
+ foreach ($database_definition['sequences'] as $sequence_name => $sequence) {
+ $table = !empty($sequence['on']) ? $sequence['on']['table'] :'';
+
+ $sequences[$table][] = $sequence_name;
+ }
+ }
+
+ $buffer = ''.$eol;
+ $buffer .= "$eol$eol ".$database_definition['name']."";
+ $buffer .= "$eol ".$this->_dumpBoolean($database_definition['create'])."";
+ $buffer .= "$eol ".$this->_dumpBoolean($database_definition['overwrite'])."$eol";
+ $buffer .= "$eol ".$database_definition['charset']."$eol";
+
+ if ($output) {
+ call_user_func($output, $buffer);
+ } else {
+ fwrite($fp, $buffer);
+ }
+
+ if (!empty($database_definition['tables']) && is_array($database_definition['tables'])) {
+ foreach ($database_definition['tables'] as $table_name => $table) {
+ $buffer = "$eol $eol$eol $table_name$eol";
+ if ($dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_STRUCTURE) {
+ $buffer .= "$eol $eol";
+ if (!empty($table['fields']) && is_array($table['fields'])) {
+ foreach ($table['fields'] as $field_name => $field) {
+ if (empty($field['type'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, null, null,
+ 'it was not specified the type of the field "'.
+ $field_name.'" of the table "'.$table_name.'"');
+ }
+ if (!empty($this->valid_types) && !array_key_exists($field['type'], $this->valid_types)) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null,
+ 'type "'.$field['type'].'" is not yet supported');
+ }
+ $buffer .= "$eol $eol $field_name$eol ";
+ $buffer .= $field['type']."$eol";
+ if (!empty($field['fixed']) && $field['type'] === 'text') {
+ $buffer .= " ".$this->_dumpBoolean($field['fixed'])."$eol";
+ }
+ if (array_key_exists('default', $field)
+ && $field['type'] !== 'clob' && $field['type'] !== 'blob'
+ ) {
+ $buffer .= ' '.$this->_escapeSpecialChars($field['default'])."$eol";
+ }
+ if (!empty($field['notnull'])) {
+ $buffer .= " ".$this->_dumpBoolean($field['notnull'])."$eol";
+ } else {
+ $buffer .= " false$eol";
+ }
+ if (!empty($field['autoincrement'])) {
+ $buffer .= " " . $field['autoincrement'] ."$eol";
+ }
+ if (!empty($field['unsigned'])) {
+ $buffer .= " ".$this->_dumpBoolean($field['unsigned'])."$eol";
+ }
+ if (!empty($field['length'])) {
+ $buffer .= ' '.$field['length']."$eol";
+ }
+ $buffer .= " $eol";
+ }
+ }
+
+ if (!empty($table['indexes']) && is_array($table['indexes'])) {
+ foreach ($table['indexes'] as $index_name => $index) {
+ if (strtolower($index_name) === 'primary') {
+ $index_name = $table_name . '_pKey';
+ }
+ $buffer .= "$eol $eol $index_name$eol";
+ if (!empty($index['unique'])) {
+ $buffer .= " ".$this->_dumpBoolean($index['unique'])."$eol";
+ }
+
+ if (!empty($index['primary'])) {
+ $buffer .= " ".$this->_dumpBoolean($index['primary'])."$eol";
+ }
+
+ foreach ($index['fields'] as $field_name => $field) {
+ $buffer .= " $eol $field_name$eol";
+ if (!empty($field) && is_array($field)) {
+ $buffer .= ' '.$field['sorting']."$eol";
+ }
+ $buffer .= " $eol";
+ }
+ $buffer .= " $eol";
+ }
+ }
+
+ if (!empty($table['constraints']) && is_array($table['constraints'])) {
+ foreach ($table['constraints'] as $constraint_name => $constraint) {
+ $buffer .= "$eol $eol $constraint_name$eol";
+ if (empty($constraint['fields']) || !is_array($constraint['fields'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, null, null,
+ 'it was not specified a field for the foreign key "'.
+ $constraint_name.'" of the table "'.$table_name.'"');
+ }
+ if (!is_array($constraint['references']) || empty($constraint['references']['table'])) {
+ return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, null, null,
+ 'it was not specified the referenced table of the foreign key "'.
+ $constraint_name.'" of the table "'.$table_name.'"');
+ }
+ if (!empty($constraint['match'])) {
+ $buffer .= " ".$constraint['match']."$eol";
+ }
+ if (!empty($constraint['ondelete'])) {
+ $buffer .= " ".$constraint['ondelete']."$eol";
+ }
+ if (!empty($constraint['onupdate'])) {
+ $buffer .= " ".$constraint['onupdate']."$eol";
+ }
+ if (!empty($constraint['deferrable'])) {
+ $buffer .= " ".$constraint['deferrable']."$eol";
+ }
+ if (!empty($constraint['initiallydeferred'])) {
+ $buffer .= " ".$constraint['initiallydeferred']."$eol";
+ }
+ foreach ($constraint['fields'] as $field_name => $field) {
+ $buffer .= " $field_name$eol";
+ }
+ $buffer .= " $eol ".$constraint['references']['table']."
$eol";
+ foreach ($constraint['references']['fields'] as $field_name => $field) {
+ $buffer .= " $field_name$eol";
+ }
+ $buffer .= " $eol";
+
+ $buffer .= " $eol";
+ }
+ }
+
+ $buffer .= "$eol $eol";
+ }
+
+ if ($output) {
+ call_user_func($output, $buffer);
+ } else {
+ fwrite($fp, $buffer);
+ }
+
+ $buffer = '';
+ if ($dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT) {
+ if (!empty($table['initialization']) && is_array($table['initialization'])) {
+ $buffer = "$eol $eol";
+ foreach ($table['initialization'] as $instruction) {
+ switch ($instruction['type']) {
+ case 'insert':
+ $buffer .= "$eol $eol";
+ foreach ($instruction['data']['field'] as $field) {
+ $field_name = $field['name'];
+
+ $buffer .= "$eol $eol $field_name$eol";
+ $buffer .= $this->writeExpression($field['group'], 5, $arguments);
+ $buffer .= " $eol";
+ }
+ $buffer .= "$eol $eol";
+ break;
+ case 'update':
+ $buffer .= "$eol $eol";
+ foreach ($instruction['data']['field'] as $field) {
+ $field_name = $field['name'];
+
+ $buffer .= "$eol $eol $field_name$eol";
+ $buffer .= $this->writeExpression($field['group'], 5, $arguments);
+ $buffer .= " $eol";
+ }
+
+ if (!empty($instruction['data']['where'])
+ && is_array($instruction['data']['where'])
+ ) {
+ $buffer .= " $eol";
+ $buffer .= $this->writeExpression($instruction['data']['where'], 5, $arguments);
+ $buffer .= " $eol";
+ }
+
+ $buffer .= "$eol $eol";
+ break;
+ case 'delete':
+ $buffer .= "$eol $eol$eol";
+ if (!empty($instruction['data']['where'])
+ && is_array($instruction['data']['where'])
+ ) {
+ $buffer .= " $eol";
+ $buffer .= $this->writeExpression($instruction['data']['where'], 5, $arguments);
+ $buffer .= " $eol";
+ }
+ $buffer .= "$eol $eol";
+ break;
+ }
+ }
+ $buffer .= "$eol $eol";
+ }
+ }
+ $buffer .= "$eol
$eol";
+ if ($output) {
+ call_user_func($output, $buffer);
+ } else {
+ fwrite($fp, $buffer);
+ }
+
+ if (isset($sequences[$table_name])) {
+ foreach ($sequences[$table_name] as $sequence) {
+ $result = $this->dumpSequence($database_definition['sequences'][$sequence],
+ $sequence, $eol, $dump);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ if ($output) {
+ call_user_func($output, $result);
+ } else {
+ fwrite($fp, $result);
+ }
+ }
+ }
+ }
+ }
+
+ if (isset($sequences[''])) {
+ foreach ($sequences[''] as $sequence) {
+ $result = $this->dumpSequence($database_definition['sequences'][$sequence],
+ $sequence, $eol, $dump);
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ if ($output) {
+ call_user_func($output, $result);
+ } else {
+ fwrite($fp, $result);
+ }
+ }
+ }
+
+ $buffer = "$eol$eol";
+ if ($output) {
+ call_user_func($output, $buffer);
+ } else {
+ fwrite($fp, $buffer);
+ fclose($fp);
+ }
+
+ return MDB2_OK;
+ }
+
+ // }}}
+ // {{{ writeExpression()
+
+ /**
+ * Dumps the structure of an element. Elements can be value, column,
+ * function or expression.
+ *
+ * @param array $element multi dimensional array that represents the parsed element
+ * of a DML instruction.
+ * @param integer $offset base indentation width
+ * @param array $arguments associative array that takes pairs of tag
+ * names and values that define dump options.
+ *
+ * @return string
+ *
+ * @access public
+ * @see MDB2_Schema_Writer::dumpDatabase()
+ */
+ function writeExpression($element, $offset = 0, $arguments = null)
+ {
+ $eol = isset($arguments['end_of_line']) ? $arguments['end_of_line'] : "\n";
+ $str = '';
+
+ $indent = str_repeat(' ', $offset);
+ $noffset = $offset + 1;
+
+ switch ($element['type']) {
+ case 'value':
+ $str .= "$indent".$this->_escapeSpecialChars($element['data'])."$eol";
+ break;
+ case 'column':
+ $str .= "$indent".$this->_escapeSpecialChars($element['data'])."$eol";
+ break;
+ case 'function':
+ $str .= "$indent$eol$indent ".$this->_escapeSpecialChars($element['data']['name'])."$eol";
+
+ if (!empty($element['data']['arguments'])
+ && is_array($element['data']['arguments'])
+ ) {
+ foreach ($element['data']['arguments'] as $v) {
+ $str .= $this->writeExpression($v, $noffset, $arguments);
+ }
+ }
+
+ $str .= "$indent$eol";
+ break;
+ case 'expression':
+ $str .= "$indent$eol";
+ $str .= $this->writeExpression($element['data']['operants'][0], $noffset, $arguments);
+ $str .= "$indent ".$element['data']['operator']."$eol";
+ $str .= $this->writeExpression($element['data']['operants'][1], $noffset, $arguments);
+ $str .= "$indent$eol";
+ break;
+ }
+ return $str;
+ }
+
+ // }}}
+}
+?>
diff --git a/inc/XML/Parser.php b/inc/XML/Parser.php
index c8e45947c1..6f77b5c66d 100755
--- a/inc/XML/Parser.php
+++ b/inc/XML/Parser.php
@@ -36,7 +36,7 @@
/**
* uses PEAR's error handling
*/
-require_once 'PEAR.php';
+oc_require_once('PEAR.php');
/**
* resource could not be created
@@ -376,13 +376,12 @@ class XML_Parser extends PEAR
/**
* check, if file is a remote file
*/
- if (eregi('^(http|ftp)://', substr($file, 0, 10))) {
+ if (preg_match('[^(http|ftp)://]', substr($file, 0, 10))) {
if (!ini_get('allow_url_fopen')) {
return $this->raiseError('Remote files cannot be parsed, as safe mode is enabled.', XML_PARSER_ERROR_REMOTE);
}
}
-
- $fp = @fopen($file, 'rb');
+ $fp = fopen($file, 'rb');
if (is_resource($fp)) {
$this->fp = $fp;
return $fp;
@@ -564,7 +563,7 @@ class XML_Parser extends PEAR
function raiseError($msg = null, $ecode = 0)
{
$msg = !is_null($msg) ? $msg : $this->parser;
- $err = &new XML_Parser_Error($msg, $ecode);
+ $err = new XML_Parser_Error($msg, $ecode);
return parent::raiseError($err);
}
diff --git a/inc/lib_base.php b/inc/lib_base.php
index 4fcba692b4..5a2a4c8266 100755
--- a/inc/lib_base.php
+++ b/inc/lib_base.php
@@ -81,7 +81,9 @@ oc_require_once('lib_config.php');
oc_require_once('lib_user.php');
oc_require_once('lib_ocs.php');
@oc_require_once('MDB2.php');
+@oc_require_once('MDB2/Schema.php');
oc_require_once('lib_connect.php');
+oc_require_once('lib_remotestorage.php');
if(!is_dir($CONFIG_DATADIRECTORY_ROOT)){
@@ -312,6 +314,7 @@ class OC_UTIL {
*/
class OC_DB {
static private $DBConnection=false;
+ static private $schema=false;
static private $affected=0;
static private $result=false;
/**
@@ -327,8 +330,11 @@ class OC_DB {
global $SERVERROOT;
if(!self::$DBConnection){
$options = array(
- 'debug' => 0,
'portability' => MDB2_PORTABILITY_ALL,
+ 'log_line_break' => '
',
+ 'idxname_format' => '%s',
+ 'debug' => true,
+ 'quote_identifier' => true,
);
if($CONFIG_DBTYPE=='sqlite'){
$dsn = array(
@@ -344,14 +350,22 @@ class OC_DB {
'hostspec' => $CONFIG_DBHOST,
'database' => $CONFIG_DBNAME,
);
+ }elseif($CONFIG_DBTYPE=='pgsql'){
+ $dsn = array(
+ 'phptype' => 'pgsql',
+ 'username' => $CONFIG_DBUSER,
+ 'password' => $CONFIG_DBPASSWORD,
+ 'hostspec' => $CONFIG_DBHOST,
+ 'database' => $CONFIG_DBNAME,
+ );
}
- self::$DBConnection=MDB2::connect($dsn,$options);
- if (@PEAR::isError(self::$DBConnection)) {
+ self::$DBConnection=&MDB2::factory($dsn,$options);
+ if (PEAR::isError(self::$DBConnection)) {
echo('can not connect to database, using '.$CONFIG_DBTYPE.'. ('.self::$DBConnection->getUserInfo().')');
die(self::$DBConnection->getMessage());
}
self::$DBConnection->setFetchMode(MDB2_FETCHMODE_ASSOC);
-// self::$DBConnection->loadModule('Manager');
+ self::$schema=&MDB2_Schema::factory(self::$DBConnection);
}
}
@@ -369,6 +383,8 @@ class OC_DB {
OC_DB::connect();
if($CONFIG_DBTYPE=='sqlite'){//fix differences between sql versions
$cmd=str_replace('`','',$cmd);
+ }elseif($CONFIG_DBTYPE=='pgsql'){
+ $cmd=str_replace('`','"',$cmd);
}
$result=self::$DBConnection->exec($cmd);
if (PEAR::isError($result)) {
@@ -390,7 +406,19 @@ class OC_DB {
*/
static function select($cmd){
OC_DB::connect();
- return self::$DBConnection->queryAll($cmd);
+ global $CONFIG_DBTYPE;
+ if($CONFIG_DBTYPE=='sqlite'){//fix differences between sql versions
+ $cmd=str_replace('`','',$cmd);
+ }elseif($CONFIG_DBTYPE=='pgsql'){
+ $cmd=str_replace('`','"',$cmd);
+ }
+ $result=self::$DBConnection->queryAll($cmd);
+ if (PEAR::isError($result)) {
+ $entry='DB Error: "'.$result->getMessage().'"
';
+ $entry.='Offending command was: '.$cmd.'
';
+ die($entry);
+ }
+ return $result;
}
/**
@@ -425,12 +453,8 @@ class OC_DB {
* @return primarykey
*/
static function insertid() {
- global $CONFIG_DBTYPE;
- if($CONFIG_DBTYPE=='sqlite'){
- return self::$DBConnection->lastInsertRowid();
- }elseif($CONFIG_DBTYPE=='mysql'){
- return(mysqli_insert_id(self::$DBConnection));
- }
+ $id=self::$DBConnection->lastInsertID();
+ return $id;
}
/**
@@ -505,7 +529,41 @@ class OC_DB {
OC_DB::connect();
return self::$DBConnection->escape($string);
}
-
+
+ static function getDBStructure($file){
+ OC_DB::connect();
+ $definition = self::$schema->getDefinitionFromDatabase();
+ $dump_options = array(
+ 'output_mode' => 'file',
+ 'output' => $file,
+ 'end_of_line' => "\n"
+ );
+ self::$schema->dumpDatabase($definition, $dump_options, MDB2_SCHEMA_DUMP_STRUCTURE);
+ }
+
+ static function createDBFromStructure($file){
+ OC_DB::connect();
+ global $CONFIG_DBNAME;
+ global $CONFIG_DBTABLEPREFIX;
+ $content=file_get_contents($file);
+ $file2=tempnam(sys_get_temp_dir(),'oc_db_scheme_');
+ echo $content;
+ $content=str_replace('*dbname*',$CONFIG_DBNAME,$content);
+ $content=str_replace('*dbprefix*',$CONFIG_DBTABLEPREFIX,$content);
+ echo $content;
+ file_put_contents($file2,$content);
+ $definition=@self::$schema->parseDatabaseDefinitionFile($file2);
+ unlink($file2);
+ if($definition instanceof MDB2_Schema_Error){
+ die($definition->getMessage() . ': ' . $definition->getUserInfo());
+ }
+ $ret=@self::$schema->createDatabase($definition);
+ if($ret instanceof MDB2_Error) {
+ die ($ret->getMessage() . ': ' . $ret->getUserInfo());
+ }else{
+ return true;
+ }
+ }
}
@@ -589,12 +647,12 @@ function oc_include_once($file){
global $CONFIG_HTTPFORCESSL;
global $CONFIG_DATEFORMAT;
global $CONFIG_INSTALLED;
- if(is_file($file)){
- return include_once($file);
- }elseif(is_file($SERVERROOT.'/'.$file)){
+ if(is_file($SERVERROOT.'/'.$file)){
return include_once($SERVERROOT.'/'.$file);
}elseif(is_file($SERVERROOT.'/inc/'.$file)){
return include_once($SERVERROOT.'/inc/'.$file);
+ }elseif(is_file($file)){
+ return include_once($file);
}
}
diff --git a/inc/lib_config.php b/inc/lib_config.php
index 986d12f4dc..cbf86b17aa 100755
--- a/inc/lib_config.php
+++ b/inc/lib_config.php
@@ -25,6 +25,7 @@ class OC_CONFIG{
global $CONFIG_HTTPFORCESSL;
global $CONFIG_DATEFORMAT;
global $CONFIG_DBNAME;
+ global $CONFIG_DBTABLEPREFIX;
global $CONFIG_INSTALLED;
$allow=false;
if(!$CONFIG_INSTALLED){
@@ -130,6 +131,7 @@ class OC_CONFIG{
global $WEBROOT;
global $CONFIG_DBHOST;
global $CONFIG_DBNAME;
+ global $CONFIG_DBTABLEPREFIX;
global $CONFIG_INSTALLED;
global $CONFIG_DBUSER;
global $CONFIG_DBPASSWORD;
@@ -184,13 +186,17 @@ class OC_CONFIG{
//create/fill database
$CONFIG_DBTYPE=$dbtype;
$CONFIG_DBNAME=$_POST['dbname'];
- if($dbtype=='mysql'){
+ if($dbtype!='sqlite'){
+ $CONFIG_DBTABLEPREFIX=$_POST['dbtableprefix'];
$CONFIG_DBHOST=$_POST['dbhost'];
$CONFIG_DBUSER=$_POST['dbuser'];
$CONFIG_DBPASSWORD=$_POST['dbpassword'];
+ }else{
+ $_POST['dbtableprefix']='';
+ $CONFIG_DBTABLEPREFIX='';
}
try{
- if(isset($_POST['createdatabase']) and $CONFIG_DBTYPE=='mysql'){
+ if(isset($_POST['createdatabase']) and $CONFIG_DBTYPE!='sqlite'){
self::createdatabase($_POST['dbadminuser'],$_POST['dbadminpwd']);
}
}catch(Exception $e){
@@ -209,7 +215,6 @@ class OC_CONFIG{
self::filldatabase();
}
}catch(Exception $e){
- echo 'testin';
$error.='error while trying to fill the database
';
}
if($CONFIG_DBTYPE=='sqlite'){
@@ -241,7 +246,8 @@ class OC_CONFIG{
$config.='$CONFIG_DATEFORMAT=\''.$_POST['dateformat']."';\n";
$config.='$CONFIG_DBTYPE=\''.$dbtype."';\n";
$config.='$CONFIG_DBNAME=\''.$_POST['dbname']."';\n";
- if($dbtype=='mysql'){
+ $config.='$CONFIG_DBTABLEPREFIX=\''.$_POST['dbtableprefix']."';\n";
+ if($dbtype!='sqlite'){
$config.='$CONFIG_DBHOST=\''.$_POST['dbhost']."';\n";
$config.='$CONFIG_DBUSER=\''.$_POST['dbuser']."';\n";
$config.='$CONFIG_DBPASSWORD=\''.$_POST['dbpassword']."';\n";
@@ -267,169 +273,73 @@ class OC_CONFIG{
}
}
}
-
- /**
- * Fills the database with the initial tables
- * Note: while the AUTO_INCREMENT function is not supported by SQLite
- * the same effect can be achieved by accessing the SQLite pseudo-column
- * "rowid"
- */
- private static function filldatabase(){
- global $CONFIG_DBTYPE;
- if($CONFIG_DBTYPE=='sqlite'){
- $query="CREATE TABLE 'locks' (
- 'token' VARCHAR(255) NOT NULL DEFAULT '',
- 'path' varchar(200) NOT NULL DEFAULT '',
- 'created' int(11) NOT NULL DEFAULT '0',
- 'modified' int(11) NOT NULL DEFAULT '0',
- 'expires' int(11) NOT NULL DEFAULT '0',
- 'owner' varchar(200) DEFAULT NULL,
- 'recursive' int(11) DEFAULT '0',
- 'writelock' int(11) DEFAULT '0',
- 'exclusivelock' int(11) NOT NULL DEFAULT '0',
- PRIMARY KEY ('token'),
- UNIQUE ('token')
- );
-
-CREATE TABLE 'log' (
- `id` INTEGER ASC DEFAULT '' NOT NULL,
- 'timestamp' int(11) NOT NULL,
- 'user' varchar(250) NOT NULL,
- 'type' int(11) NOT NULL,
- 'message' varchar(250) NOT NULL,
- PRIMARY KEY ('id')
-);
-
-
-CREATE TABLE 'properties' (
- 'path' varchar(255) NOT NULL DEFAULT '',
- 'name' varchar(120) NOT NULL DEFAULT '',
- 'ns' varchar(120) NOT NULL DEFAULT 'DAV:',
- 'value' text,
- PRIMARY KEY ('path','name','ns')
-);
-
-CREATE TABLE 'users' (
- 'user_id' INTEGER ASC DEFAULT '',
- 'user_name' varchar(64) NOT NULL DEFAULT '',
- 'user_name_clean' varchar(64) NOT NULL DEFAULT '',
- 'user_password' varchar(40) NOT NULL DEFAULT '',
- PRIMARY KEY ('user_id'),
- UNIQUE ('user_name' ,'user_name_clean')
-);
-
-CREATE TABLE 'groups' (
-'group_id' INTEGER ASC DEFAULT '',
-'group_name' VARCHAR( 64 ) NOT NULL DEFAULT '',
-PRIMARY KEY ('group_id'),
-UNIQUE ('group_name')
-);
-
-CREATE TABLE 'user_group' (
-'user_group_id' INTEGER ASC DEFAULT '',
-'user_id' VARCHAR( 64 ) NOT NULL DEFAULT '',
-'group_id' VARCHAR( 64 ) NOT NULL DEFAULT '',
-PRIMARY KEY ('user_group_id')
-)
-";
- }elseif($CONFIG_DBTYPE=='mysql'){
- $query="CREATE TABLE IF NOT EXISTS `locks` (
- `token` varchar(255) NOT NULL DEFAULT '',
- `path` varchar(200) NOT NULL DEFAULT '',
- `created` int(11) NOT NULL DEFAULT '0',
- `modified` int(11) NOT NULL DEFAULT '0',
- `expires` int(11) NOT NULL DEFAULT '0',
- `owner` varchar(200) DEFAULT NULL,
- `recursive` int(11) DEFAULT '0',
- `writelock` int(11) DEFAULT '0',
- `exclusivelock` int(11) NOT NULL DEFAULT '0',
- PRIMARY KEY (`token`),
- UNIQUE KEY `token` (`token`),
- KEY `path` (`path`),
- KEY `path_2` (`path`),
- KEY `path_3` (`path`,`token`),
- KEY `expires` (`expires`)
-);
-
-CREATE TABLE IF NOT EXISTS `log` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `timestamp` int(11) NOT NULL,
- `user` varchar(250) NOT NULL,
- `type` int(11) NOT NULL,
- `message` varchar(250) NOT NULL,
- PRIMARY KEY (`id`)
-);
-
-
-CREATE TABLE IF NOT EXISTS `properties` (
- `path` varchar(255) NOT NULL DEFAULT '',
- `name` varchar(120) NOT NULL DEFAULT '',
- `ns` varchar(120) NOT NULL DEFAULT 'DAV:',
- `value` text,
- PRIMARY KEY (`path`,`name`,`ns`),
- KEY `path` (`path`)
-);
-
-CREATE TABLE IF NOT EXISTS `users` (
-`user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
-`user_name` VARCHAR( 64 ) NOT NULL ,
-`user_name_clean` VARCHAR( 64 ) NOT NULL ,
-`user_password` VARCHAR( 340) NOT NULL ,
-UNIQUE (
-`user_name` ,
-`user_name_clean`
-)
-);
-
-CREATE TABLE IF NOT EXISTS `groups` (
-`group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
-`group_name` VARCHAR( 64 ) NOT NULL ,
-UNIQUE (
-`group_name`
-)
-);
-
-CREATE TABLE IF NOT EXISTS `user_group` (
-`user_group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
-`user_id` VARCHAR( 64 ) NOT NULL ,
-`group_id` VARCHAR( 64 ) NOT NULL
-)
-";
+
+ /**
+ * Fills the database with the initial tables
+ * Note: while the AUTO_INCREMENT function is not supported by SQLite
+ * the same effect can be achieved by accessing the SQLite pseudo-column
+ * "rowid"
+ */
+ private static function filldatabase(){
+ global $SERVERROOT;
+ OC_DB::createDBFromStructure($SERVERROOT.'/db_structure.xml');
+ }
+
+ /**
+ * Create the database and user
+ * @param string adminUser
+ * @param string adminPwd
+ *
+ */
+ private static function createdatabase($adminUser,$adminPwd){
+ global $CONFIG_DBHOST;
+ global $CONFIG_DBNAME;
+ global $CONFIG_DBUSER;
+ global $CONFIG_DBPWD;
+ global $CONFIG_DBTYPE;
+ //we cant user OC_BD functions here because we need to connect as the administrative user.
+ if($CONFIG_DBTYPE=='mysql'){
+ $connection = @new mysqli($CONFIG_DBHOST, $adminUser, $adminPwd);
+ if (mysqli_connect_errno()) {
+ @ob_end_clean();
+ echo('
can not connect to database as administrative user.');
+ exit();
+ }
+ $query="SELECT user FROM mysql.user WHERE user='{$_POST['dbuser']}';";
+ $result = @$connection->query($query);
+ if (!$result) {
+ $entry='DB Error: "'.$connection->error.'"
';
+ $entry.='Offending command was: '.$query.'
';
+ echo($entry);
+ }
+ if($result->num_rows==0){
+ $query="CREATE USER '{$_POST['dbuser']}' IDENTIFIED BY '{$_POST['dbpassword']}';";
+ }else{
+ $query='';
+ }
+ $query.="CREATE DATABASE IF NOT EXISTS `{$_POST['dbname']}`;";
+ $query.="GRANT ALL PRIVILEGES ON `{$_POST['dbname']}` . * TO '{$_POST['dbuser']}';";
+ $result = @$connection->multi_query($query);
+ if (!$result) {
+ $entry='DB Error: "'.$connection->error.'"
';
+ $entry.='Offending command was: '.$query.'
';
+ echo($entry);
+ }
+ $connection->close();
+ }elseif($CONFIG_DBTYPE=='pgsql'){
+ $connection = pg_connect("user='$adminUser' host='$CONFIG_DBHOST' password='$adminPwd'");
+ $query="CREATE USER {$_POST['dbuser']} WITH PASSWORD '{$_POST['dbpassword']}' CREATEDB;";
+ $result = pg_exec($connection, $query);
+ $query="select count(*) from pg_catalog.pg_database where datname = '{$_POST['dbname']}';";
+ $result = pg_exec($connection, $query);
+ if(pg_result($result,0,0)==0){
+ $query="CREATE DATABASE {$_POST['dbname']};";
+ $result = pg_exec($connection, $query);
+ $query="ALTER DATABASE {$_POST['dbname']} OWNER TO {$_POST['dbuser']};";
+ $result = pg_exec($connection, $query);
+ }
+ }
}
- OC_DB::multiquery($query);
- }
-
- /**
- * Create the database and user
- * @param string adminUser
- * @param string adminPwd
- *
- */
- private static function createdatabase($adminUser,$adminPwd){
- global $CONFIG_DBHOST;
- global $CONFIG_DBNAME;
- global $CONFIG_DBUSER;
- global $CONFIG_DBPWD;
- //we cant user OC_BD functions here because we need to connect as the administrative user.
- $connection = @new mysqli($CONFIG_DBHOST, $adminUser, $adminPwd);
- if (mysqli_connect_errno()) {
- @ob_end_clean();
- echo('
can not connect to database as administrative user.');
- exit();
- }
- $query="CREATE USER '{$_POST['dbuser']}' IDENTIFIED BY '{$_POST['dbpassword']}';
-
-CREATE DATABASE IF NOT EXISTS `{$_POST['dbname']}` ;
-
-GRANT ALL PRIVILEGES ON `{$_POST['dbname']}` . * TO '{$_POST['dbuser']}';";
- $result = @$connection->multi_query($query);
- if (!$result) {
- $entry='DB Error: "'.$connection->error.'"
';
- $entry.='Offending command was: '.$query.'
';
- echo($entry);
- }
- $connection->close();
- }
}
?>
diff --git a/inc/lib_connect.php b/inc/lib_connect.php
index ddf03eb6e7..9db867715e 100644
--- a/inc/lib_connect.php
+++ b/inc/lib_connect.php
@@ -75,9 +75,11 @@ class OC_REMOTE_CLOUD{
curl_setopt($ch, CURLOPT_COOKIEFILE,$this->cookiefile);
curl_setopt($ch, CURLOPT_COOKIEJAR,$this->cookiefile);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
- $result=trim(curl_exec($ch));
+ $result=curl_exec($ch);
+ $result=trim($result);
$info=curl_getinfo($ch);
$httpCode=$info['http_code'];
+ curl_close($ch);
if($httpCode==200 or $httpCode==0){
return json_decode($result,$assoc);
}else{
@@ -120,6 +122,70 @@ class OC_REMOTE_CLOUD{
$this->cookiefile=false;
}
+ /**
+ * create a new file or directory
+ * @param string $dir
+ * @param string $name
+ * @param string $type
+ */
+ public function newFile($dir,$name,$type){
+ if(!$this->connected){
+ return false;
+ }
+ return $this->apiCall('new',array('dir'=>$dir,'name'=>$name,'type'=>$type),true);
+ }
+
+ /**
+ * deletes a file or directory
+ * @param string $dir
+ * @param string $file
+ */
+ public function delete($dir,$name){
+ if(!$this->connected){
+ return false;
+ }
+ return $this->apiCall('delete',array('dir'=>$dir,'file'=>$name),true);
+ }
+
+ /**
+ * moves a file or directory
+ * @param string $sorceDir
+ * @param string $sorceFile
+ * @param string $targetDir
+ * @param string $targetFile
+ */
+ public function move($sourceDir,$sourceFile,$targetDir,$targetFile){
+ if(!$this->connected){
+ return false;
+ }
+ return $this->apiCall('move',array('sourcedir'=>$sourceDir,'source'=>$sourceFile,'targetdir'=>$targetDir,'target'=>$targetFile),true);
+ }
+
+ /**
+ * copies a file or directory
+ * @param string $sorceDir
+ * @param string $sorceFile
+ * @param string $targetDir
+ * @param string $targetFile
+ */
+ public function copy($sourceDir,$sourceFile,$targetDir,$targetFile){
+ if(!$this->connected){
+ return false;
+ }
+ return $this->apiCall('copy',array('sourcedir'=>$sourceDir,'source'=>$sourceFile,'targetdir'=>$targetDir,'target'=>$targetFile),true);
+ }
+
+ /**
+ * get a file tree
+ * @param string $dir
+ */
+ public function getTree($dir){
+ if(!$this->connected){
+ return false;
+ }
+ return $this->apiCall('gettree',array('dir'=>$dir),true);
+ }
+
/**
* get the files inside a directory of the remote cloud
* @param string $dir
@@ -130,6 +196,53 @@ class OC_REMOTE_CLOUD{
}
return $this->apiCall('getfiles',array('dir'=>$dir),true);
}
+
+ /**
+ * get a remove file and save it in a temporary file and return the path of the temporary file
+ * @param string $dir
+ * @param string $file
+ * @return string
+ */
+ public function getFile($dir, $file){
+ if(!$this->connected){
+ return false;
+ }
+ $ch=curl_init();
+ if(!$this->cookiefile){
+ $this->cookiefile=sys_get_temp_dir().'/remoteCloudCookie'.uniqid();
+ }
+ $tmpfile=tempnam(sys_get_temp_dir(),'remoteCloudFile');
+ $fp=fopen($tmpfile,'w+');
+ $url=$this->path.="/files/api.php?action=get&dir=$dir&file=$file";
+ curl_setopt($ch,CURLOPT_URL,$url);
+ curl_setopt($ch, CURLOPT_COOKIEFILE,$this->cookiefile);
+ curl_setopt($ch, CURLOPT_COOKIEJAR,$this->cookiefile);
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_exec($ch);
+ fclose($fp);
+ curl_close($ch);
+ return $tmpfile;
+ }
+
+ public function sendFile($sourceDir,$sourceFile,$targetDir,$targetFile){
+ global $WEBROOT;
+ $source=$sourceDir.'/'.$sourceFile;
+ $tmp=OC_FILESYSTEM::toTmpFile($source);
+ return $this->sendTmpFile($tmp,$targetDir,$targetFile);
+ }
+
+ public function sendTmpFile($tmp,$targetDir,$targetFile){
+ $token=sha1(uniqid().$tmp);
+ global $WEBROOT;
+ $file=sys_get_temp_dir().'/'.'remoteCloudFile'.$token;
+ rename($tmp,$file);
+ if((isset($CONFIG_HTTPFORCESSL) and $CONFIG_HTTPFORCESSL) or isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') {
+ $url = "https://". $_SERVER['SERVER_NAME'] . $WEBROOT;
+ }else{
+ $url = "http://". $_SERVER['SERVER_NAME'] . $WEBROOT;
+ }
+ return $this->apiCall('pull',array('dir'=>$targetDir,'file'=>$targetFile,'token'=>$token,'source'=>$url),true);
+ }
}
function OC_CONNECT_TEST($path,$user,$password){
@@ -146,6 +259,30 @@ function OC_CONNECT_TEST($path,$user,$password){
foreach($files as $file){
echo "{$file['type']} {$file['name']}: {$file['size']} bytes
";
}
+ echo 'getting file "'.$file['name'].'"...';
+ $size=$file['size'];
+ $file=$remote->getFile('',$file['name']);
+ if(file_exists($file)){
+ $newSize=filesize($file);
+ if($size!=$newSize){
+ echo "fail
Error: $newSize bytes received, $size expected.";
+ echo '
Recieved file:
';
+ readfile($file);
+ unlink($file);
+ return;
+ }
+ OC_FILESYSTEM::fromTmpFile($file,'/remoteFile');
+ echo 'done
';
+ echo 'sending file "burning_avatar.png"...';
+ $res=$remote->sendFile('','burning_avatar.png','','burning_avatar.png');
+ if($res){
+ echo 'done
';
+ }else{
+ echo 'fail
';
+ }
+ }else{
+ echo 'fail
';
+ }
}else{
echo 'fail
';
}
diff --git a/inc/lib_files.php b/inc/lib_files.php
index 1702ef20de..8f694536f7 100755
--- a/inc/lib_files.php
+++ b/inc/lib_files.php
@@ -54,7 +54,7 @@ class OC_FILES {
$dirs=array();
$file=array();
$files=array();
- if (OC_FILESYSTEM::is_dir($directory)) {
+ if(OC_FILESYSTEM::is_dir($directory)) {
if ($dh = OC_FILESYSTEM::opendir($directory)) {
while (($filename = readdir($dh)) !== false) {
if($filename<>'.' and $filename<>'..' and substr($filename,0,1)!='.'){
@@ -136,7 +136,11 @@ class OC_FILES {
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
- header('Content-Length: ' . filesize($filename));
+ if($zip){
+ header('Content-Length: ' . filesize($filename));
+ }else{
+ header('Content-Length: ' . OC_FILESYSTEM::filesize($filename));
+ }
}elseif($zip or !OC_FILESYSTEM::file_exists($filename)){
header("HTTP/1.0 404 Not Found");
die('404 Not Found');
@@ -242,6 +246,44 @@ class OC_FILES {
static function getMimeType($path){
return OC_FILESYSTEM::getMimeType($path);
}
+
+ /**
+ * get a file tree
+ *
+ * @param string path
+ * @return array
+ */
+ static function getTree($path){
+ return OC_FILESYSTEM::getTree($path);
+ }
+
+ /**
+ * pull a file from a remote server
+ * @param string source
+ * @param string token
+ * @param string dir
+ * @param string file
+ * @return string guessed mime type
+ */
+ static function pull($source,$token,$dir,$file){
+ $tmpfile=tempnam(sys_get_temp_dir(),'remoteCloudFile');
+ $fp=fopen($tmpfile,'w+');
+ $url=$source.="/files/pull.php?token=$token";
+ $ch=curl_init();
+ curl_setopt($ch,CURLOPT_URL,$url);
+ curl_setopt($ch, CURLOPT_FILE, $fp);
+ curl_exec($ch);
+ fclose($fp);
+ $info=curl_getinfo($ch);
+ $httpCode=$info['http_code'];
+ curl_close($ch);
+ if($httpCode==200 or $httpCode==0){
+ OC_FILESYSTEM::fromTmpFile($tmpfile,$dir.'/'.$file);
+ return true;
+ }else{
+ return false;
+ }
+ }
}
function zipAddDir($dir,$zip,$internalDir=''){
@@ -276,4 +318,46 @@ if(!function_exists('sys_get_temp_dir')) {
}
}
+global $FAKEDIRS;
+$FAKEDIRS=array();
+
+class fakeDirStream{
+ private $name;
+ private $data;
+ private $index;
+
+ public function dir_opendir($path,$options){
+ global $FAKEDIRS;
+ $url=parse_url($path);
+ $this->name=substr($path,strlen('fakedir://'));
+ $this->index=0;
+ if(isset($FAKEDIRS[$this->name])){
+ $this->data=$FAKEDIRS[$this->name];
+ }else{
+ $this->data=array();
+ }
+ return true;
+ }
+
+ public function dir_readdir(){
+ if($this->index>=count($this->data)){
+ return false;
+ }
+ $filename=$this->data[$this->index];
+ $this->index++;
+ return $filename;
+ }
+
+ public function dir_closedir() {
+ $this->data=false;
+ $this->name='';
+ return true;
+ }
+
+ public function dir_rewinddir() {
+ $this->index=0;
+ return true;
+ }
+}
+stream_wrapper_register("fakedir", "fakeDirStream");
?>
\ No newline at end of file
diff --git a/inc/lib_filestorage.php b/inc/lib_filestorage.php
index 85382a4444..10266d4eaf 100755
--- a/inc/lib_filestorage.php
+++ b/inc/lib_filestorage.php
@@ -62,7 +62,7 @@ class OC_FILESTORAGE{
public function filemtime($path){}
public function fileatime($path){}
public function file_get_contents($path){}
- public function file_put_contents($path){}
+ public function file_put_contents($path,$data){}
public function unlink($path){}
public function rename($path1,$path2){}
public function copy($path1,$path2){}
@@ -149,8 +149,8 @@ class OC_FILESTORAGE_LOCAL extends OC_FILESTORAGE{
}
return $return;
}
- public function file_put_contents($path){
- if($return=file_put_contents($this->datadir.$path)){
+ public function file_put_contents($path,$data){
+ if($return=file_put_contents($this->datadir.$path,$data)){
$this->notifyObservers($path,OC_FILEACTION_WRITE);
}
}
diff --git a/inc/lib_filesystem.php b/inc/lib_filesystem.php
index 6eb317f442..492e0c5d38 100755
--- a/inc/lib_filesystem.php
+++ b/inc/lib_filesystem.php
@@ -199,7 +199,7 @@ class OC_FILESYSTEM{
return $storage->file_get_contents(substr($path,strlen(self::getMountPoint($path))));
}
}
- static public function file_put_contents($path){
+ static public function file_put_contents($path,$data){
if(self::canWrite($path) and $storage=self::getStorage($path)){
$this->notifyObservers($path,OC_FILEACTION_WRITE | OC_FILEACTION_CREATE);
return $storage->file_put_contents(substr($path,strlen(self::getMountPoint($path))));
diff --git a/inc/lib_log.php b/inc/lib_log.php
index 1244a44a93..989308c82f 100755
--- a/inc/lib_log.php
+++ b/inc/lib_log.php
@@ -48,7 +48,8 @@ class OC_LOG {
* @param message $message
*/
public static function event($user,$type,$message){
- $result = OC_DB::query('insert into log (timestamp,user,type,message) values ("'.time().'","'.addslashes($user).'","'.addslashes($type).'","'.addslashes($message).'")');
+ global $CONFIG_DBTABLEPREFIX;
+ $result = OC_DB::query('INSERT INTO `' . $CONFIG_DBTABLEPREFIX . 'log` (`timestamp`,`user`,`type`,`message`) VALUES ('.time().',\''.addslashes($user).'\','.addslashes($type).',\''.addslashes($message).'\');');
}
@@ -57,14 +58,15 @@ class OC_LOG {
*
*/
public static function show(){
- global $CONFIG_DATEFORMAT;
+ global $CONFIG_DATEFORMAT;
+ global $CONFIG_DBTABLEPREFIX;
echo('');
-
+
if(OC_USER::ingroup($_SESSION['username_clean'],'admin')){
- $result = OC_DB::select('select timestamp,user,type,message from log order by timestamp desc limit 20');
+ $result = OC_DB::select('select `timestamp`,`user`,`type`,`message` from '.$CONFIG_DBTABLEPREFIX.'log order by timestamp desc limit 20');
}else{
$user=$_SESSION['username_clean'];
- $result = OC_DB::select('select timestamp,user,type,message from log where user=\''.$user.'\' order by timestamp desc limit 20');
+ $result = OC_DB::select('select `timestamp`,`user`,`type`,`message` from '.$CONFIG_DBTABLEPREFIX.'log where user=\''.$user.'\' order by timestamp desc limit 20');
}
foreach($result as $entry){
echo('');
diff --git a/inc/lib_ocs.php b/inc/lib_ocs.php
index 52dc95800a..e464d2ed9f 100755
--- a/inc/lib_ocs.php
+++ b/inc/lib_ocs.php
@@ -372,15 +372,16 @@ class OC_OCS {
* @return string xml/json
*/
private static function activityget($format,$page,$pagesize) {
+ global $CONFIG_DBTABLEPREFIX;
$user=OC_OCS::checkpassword();
- $result = OC_DB::query('select count(*) as co from log');
+ $result = OC_DB::query("select count(*) as co from {$CONFIG_DBTABLEPREFIX}log");
$entry=$result->fetchRow();
$totalcount=$entry['co'];
OC_DB::free_result($result);
- $result = OC_DB::select('select id,timestamp,user,type,message from log order by timestamp desc limit '.($page*$pagesize).','.$pagesize);
+ $result = OC_DB::select("select id,timestamp,user,type,message from {$CONFIG_DBTABLEPREFIX}log order by timestamp desc limit " . ($page*$pagesize) . ",$pagesize");
$itemscount=count($result);
$url='http://'.substr($_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'],0,-11).'';
diff --git a/inc/lib_remotestorage.php b/inc/lib_remotestorage.php
new file mode 100644
index 0000000000..f8835c1641
--- /dev/null
+++ b/inc/lib_remotestorage.php
@@ -0,0 +1,353 @@
+.
+*
+*/
+
+
+class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{
+ private $url;
+ private $username;
+ private $password;
+ private $remote=false;
+ private $statCache;
+ private $statCacheDir=false;
+ private $changed=array();
+
+ private function cacheDir($dir){
+ if($this->statCacheDir!=$dir or $this->statCacheDir===false){
+ $this->statCache=$this->remote->getFiles($dir);
+ $keys=array_keys($this->statCache);
+ $this->statCacheDir=$dir;
+ }
+ }
+
+ public function __construct($arguments){
+ $this->url=$arguments['url'];
+ $this->username=$arguments['username'];
+ $this->password=$arguments['password'];
+ }
+ private function connect(){
+ if($this->remote===false){
+ $this->remote=OC_CONNECT::connect($this->url,$this->username,$this->password);
+ }
+ }
+ public function mkdir($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $return=$this->remote->newFile($parent,$name,'dir');
+ if($return){
+ $this->notifyObservers($path,OC_FILEACTION_CREATE);
+ }
+ return $return;
+ }
+ public function rmdir($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $return=$this->remote->delete($parent,$name);
+ if($return){
+ $this->notifyObservers($path,OC_FILEACTION_DELETE);
+ }
+ return $return;
+ }
+ public function opendir($path){
+ $this->connect();
+ $this->cacheDir($path);
+ $dirs=array_keys($this->statCache);
+ $id=uniqid();
+ global $FAKEDIRS;
+ $FAKEDIRS[$id]=$dirs;
+ if($return=opendir("fakedir://$id")){
+ $this->notifyObservers($path,OC_FILEACTION_READ);
+ }
+ return $return;
+ }
+ public function is_dir($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($path);
+ if($path=='' or $path=='/'){
+ return true;
+ }
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return ($this->statCache[$name]['type'=='dir']);
+ }
+ public function is_file($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return ($this->statCache[$name]['type'!='dir']);
+ }
+ public function stat($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return $false;
+ }
+ return $this->statCache[$name];
+ }
+ public function filetype($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['type'];
+ }
+ public function filesize($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return $false;
+ }
+ return $this->statCache[$name]['size'];
+ }
+ public function is_readable($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['readable'];
+ }
+ public function is_writeable($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['writeable'];
+ }
+ public function file_exists($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ return isset($this->statCache[$name]);
+ }
+ public function readfile($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $file=$this->remote->getFile($parent,$name);
+ readfile($file);
+ unlink($file);
+ }
+ public function filectime($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['ctime'];
+ }
+ public function filemtime($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['mtime'];
+ }
+ public function fileatime($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['atime'];
+ }
+ public function file_get_contents($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $file=$this->remote->getFile($parent,$name);
+ file_get_contents($file);
+ unlink($file);
+ }
+ public function file_put_contents($path,$data){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $file=$this->remote->getFile($parent,$name);
+ $file=tempnam(sys_get_temp_dir(),'oc_');
+ file_put_contents($file,$data);
+ if($return=$this->remote->sendTmpFile($file,$parent,$name)){
+ $this->notifyObservers($path,OC_FILEACTION_WRITE);
+ }
+ }
+ public function unlink($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ if($return=$this->remote->delete($paren,$name)){
+ $this->notifyObservers($path,OC_FILEACTION_DELETE);
+ }
+ return $return;
+ }
+ public function rename($path1,$path2){
+ $this->connect();
+ $parent1=dirname($path1);
+ $name1=substr($path1,strlen($parent1)+1);
+ $parent2=dirname($path2);
+ $name2=substr($path2,strlen($parent2)+1);
+ if($return=$this->remote->move($parent1,$name1,$parent2,$name2)){
+ $this->notifyObservers($path1.'->'.$path2,OC_FILEACTION_RENAME);
+ }
+ return $return;
+ }
+ public function copy($path1,$path2){
+ $this->connect();
+ $parent1=dirname($path1);
+ $name1=substr($path1,strlen($parent1)+1);
+ $parent2=dirname($path2);
+ $name2=substr($path2,strlen($parent2)+1);
+ if($return=$this->copy->rename($parent1,$name1,$parent2,$name2)){
+ $this->notifyObservers($path1.'->'.$path2,OC_FILEACTION_RENAME);
+ }
+ return $return;
+ }
+ public function fopen($path,$mode){
+ $this->connect();
+ $changed=false;
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ $file=$this->remote->getFile($parent,$name);
+ if($return=fopen($file,$mode)){
+ switch($mode){
+ case 'r':
+ $this->notifyObservers($path,OC_FILEACTION_READ);
+ break;
+ case 'r+':
+ case 'w+':
+ case 'x+':
+ case 'a+':
+ $this->notifyObservers($path,OC_FILEACTION_READ | OC_FILEACTION_WRITE);
+ $this->changed[]=array('dir'=>$parent,'file'=>$name,'tmp'=>$file);
+ break;
+ case 'w':
+ case 'x':
+ case 'a':
+ $this->notifyObservers($path,OC_FILEACTION_WRITE);
+ $this->changed[]=array('dir'=>$parent,'file'=>$name,'tmp'=>$file);
+ break;
+ }
+ }
+ return $return;
+ }
+
+ public function getMimeType($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ if(substr($name,0,1)=='/'){
+ $name=substr($name,1);
+ }
+ $this->cacheDir($parent);
+ if(!isset($this->statCache[$name])){
+ return false;
+ }
+ return $this->statCache[$name]['mime'];
+ }
+
+ public function toTmpFile($path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ if(substr($name,0,1)=='/'){
+ $name=substr($name,1);
+ }
+ $filename=$this->remote->getFile($parent,$name);
+ if($filename){
+ $this->notifyObservers($path,OC_FILEACTION_READ);
+ return $filename;
+ }else{
+ return false;
+ }
+ }
+
+ public function fromTmpFile($tmpFile,$path){
+ $this->connect();
+ $parent=dirname($path);
+ $name=substr($path,strlen($parent)+1);
+ if($this->remote->sendTmpFile($tmpFile,$parent,$name)){
+ $this->notifyObservers($path,OC_FILEACTION_CREATE);
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ public function delTree($dir) {
+ $this->connect();
+ $parent=dirname($dir);
+ $name=substr($dir,strlen($parent)+1);
+ $return=$this->remote->delete($parent,$name);
+ if($return=rmdir($dir)){
+ $this->notifyObservers($dir,OC_FILEACTION_DELETE);
+ }
+ return $return;
+ }
+
+ public function find($path){
+ return $this->getTree($path);
+ }
+
+ public function getTree($dir) {
+ $this->connect();
+ if($return=$this->remote->getTree($dir)){
+ $this->notifyObservers($dir,OC_FILEACTION_READ);
+ }
+ return $return;
+ }
+
+ public function __destruct(){
+ foreach($this->changed as $changed){
+ $this->remote->sendTmpFile($changed['tmp'],$changed['dir'],$changed['file']);
+ }
+ }
+}
+
+?>
diff --git a/inc/lib_user.php b/inc/lib_user.php
index 7a75e02ece..896478bf28 100755
--- a/inc/lib_user.php
+++ b/inc/lib_user.php
@@ -27,6 +27,14 @@ if(!$CONFIG_INSTALLED){
$_SESSION['username_clean']='';
}
+//cache the userid's an groupid's
+if(!isset($_SESSION['user_id_cache'])){
+ $_SESSION['user_id_cache']=array();
+}
+if(!isset($_SESSION['group_id_cache'])){
+ $_SESSION['group_id_cache']=array();
+}
+
/**
* Class for usermanagement
*
@@ -63,14 +71,15 @@ class OC_USER {
*
*/
public static function createuser($username,$password){
- if(OC_USER::getuserid($username)!=0){
+ global $CONFIG_DBTABLEPREFIX;
+ if(OC_USER::getuserid($username,true)!=0){
return false;
}else{
- $password=sha1($password);
$usernameclean=strtolower($username);
+ $password=sha1($password);
$username=OC_DB::escape($username);
$usernameclean=OC_DB::escape($usernameclean);
- $query="INSERT INTO `users` (`user_id` ,`user_name` ,`user_name_clean` ,`user_password`) VALUES (NULL , '$username', '$usernameclean', '$password')";
+ $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}users` (`user_name` ,`user_name_clean` ,`user_password`) VALUES ('$username', '$usernameclean', '$password')";
$result=OC_DB::query($query);
return ($result)?true:false;
}
@@ -82,11 +91,13 @@ class OC_USER {
*
*/
public static function login($username,$password){
+ global $CONFIG_DBTABLEPREFIX;
+
$password=sha1($password);
$usernameclean=strtolower($username);
$username=OC_DB::escape($username);
$usernameclean=OC_DB::escape($usernameclean);
- $query="SELECT user_id FROM users WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1";
+ $query = "SELECT user_id FROM {$CONFIG_DBTABLEPREFIX}users WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1";
$result=OC_DB::select($query);
if(isset($result[0]) && isset($result[0]['user_id'])){
$_SESSION['user_id']=$result[0]['user_id'];
@@ -124,9 +135,10 @@ class OC_USER {
*
*/
public static function creategroup($groupname){
- if(OC_USER::getgroupid($groupname)==0){
+ global $CONFIG_DBTABLEPREFIX;
+ if(OC_USER::getgroupid($groupname,true)==0){
$groupname=OC_DB::escape($groupname);
- $query="INSERT INTO `groups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')";
+ $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}groups` (`group_name`) VALUES ('$groupname')";
$result=OC_DB::query($query);
return ($result)?true:false;
}else{
@@ -138,16 +150,20 @@ class OC_USER {
* get the id of a user
*
*/
- public static function getuserid($username){
+ public static function getuserid($username,$nocache=false){
+ global $CONFIG_DBTABLEPREFIX;
$usernameclean=strtolower($username);
- $username=OC_DB::escape($username);
+ if(!$nocache and isset($_SESSION['user_id_cache'][$usernameclean])){//try to use cached value to save an sql query
+ return $_SESSION['user_id_cache'][$usernameclean];
+ }
$usernameclean=OC_DB::escape($usernameclean);
- $query="SELECT user_id FROM users WHERE user_name_clean = '$usernameclean'";
+ $query="SELECT user_id FROM {$CONFIG_DBTABLEPREFIX}users WHERE user_name_clean = '$usernameclean'";
$result=OC_DB::select($query);
if(!is_array($result)){
return 0;
}
if(isset($result[0]) && isset($result[0]['user_id'])){
+ $_SESSION['user_id_cache'][$usernameclean]=$result[0]['user_id'];
return $result[0]['user_id'];
}else{
return 0;
@@ -158,14 +174,19 @@ class OC_USER {
* get the id of a group
*
*/
- public static function getgroupid($groupname){
+ public static function getgroupid($groupname,$nocache=false){
+ global $CONFIG_DBTABLEPREFIX;
+ if(!$nocache and isset($_SESSION['group_id_cache'][$groupname])){//try to use cached value to save an sql query
+ return $_SESSION['group_id_cache'][$groupname];
+ }
$groupname=OC_DB::escape($groupname);
- $query="SELECT group_id FROM groups WHERE group_name = '$groupname'";
+ $query="SELECT group_id FROM {$CONFIG_DBTABLEPREFIX}groups WHERE group_name = '$groupname'";
$result=OC_DB::select($query);
if(!is_array($result)){
return 0;
}
if(isset($result[0]) && isset($result[0]['group_id'])){
+ $_SESSION['group_id_cache'][$groupname]=$result[0]['group_id'];
return $result[0]['group_id'];
}else{
return 0;
@@ -176,9 +197,13 @@ class OC_USER {
* get the name of a group
*
*/
- public static function getgroupname($groupid){
+ public static function getgroupname($groupid,$nocache=false){
+ global $CONFIG_DBTABLEPREFIX;
+ if($nocache and $name=array_search($groupid,$_SESSION['group_id_cache'])){//try to use cached value to save an sql query
+ return $name;
+ }
$groupid=(integer)$groupid;
- $query="SELECT group_name FROM groups WHERE group_id = '$groupid' LIMIT 1";
+ $query="SELECT group_name FROM {$CONFIG_DBTABLEPREFIX}groups WHERE group_id = '$groupid' LIMIT 1";
$result=OC_DB::select($query);
if(isset($result[0]) && isset($result[0]['group_name'])){
return $result[0]['group_name'];
@@ -192,10 +217,12 @@ class OC_USER {
*
*/
public static function ingroup($username,$groupname){
+ global $CONFIG_DBTABLEPREFIX;
+
$userid=OC_USER::getuserid($username);
$groupid=OC_USER::getgroupid($groupname);
if($groupid>0 and $userid>0){
- $query="SELECT user_group_id FROM user_group WHERE group_id = $groupid AND user_id = $userid LIMIT 1";
+ $query="SELECT * FROM {$CONFIG_DBTABLEPREFIX}user_group WHERE group_id = '$groupid' AND user_id = '$userid';";
$result=OC_DB::select($query);
if(isset($result[0]) && isset($result[0]['user_group_id'])){
return true;
@@ -212,11 +239,13 @@ class OC_USER {
*
*/
public static function addtogroup($username,$groupname){
+ global $CONFIG_DBTABLEPREFIX;
+
if(!OC_USER::ingroup($username,$groupname)){
$userid=OC_USER::getuserid($username);
$groupid=OC_USER::getgroupid($groupname);
if($groupid!=0 and $userid!=0){
- $query="INSERT INTO `user_group` (`user_group_id` ,`user_id` ,`group_id`) VALUES (NULL , '$userid', '$groupid');";
+ $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}user_group` (`user_id` ,`group_id`) VALUES ('$userid', '$groupid');";
$result=OC_DB::query($query);
if($result){
return true;
@@ -240,8 +269,10 @@ class OC_USER {
*
*/
public static function getusergroups($username){
+ global $CONFIG_DBTABLEPREFIX;
+
$userid=OC_USER::getuserid($username);
- $query="SELECT group_id FROM user_group WHERE user_id = '$userid'";
+ $query = "SELECT group_id FROM {$CONFIG_DBTABLEPREFIX}user_group WHERE user_id = '$userid'";
$result=OC_DB::select($query);
$groups=array();
if(is_array($result)){
@@ -258,9 +289,11 @@ class OC_USER {
*
*/
public static function setpassword($username,$password){
+ global $CONFIG_DBTABLEPREFIX;
+
$password=sha1($password);
$userid=OC_USER::getuserid($username);
- $query="UPDATE users SET user_password = '$password' WHERE user_id ='$userid'";
+ $query = "UPDATE {$CONFIG_DBTABLEPREFIX}users SET user_password = '$password' WHERE user_id ='$userid'";
$result=OC_DB::query($query);
if($result){
return true;
@@ -274,11 +307,13 @@ class OC_USER {
*
*/
public static function checkpassword($username,$password){
+ global $CONFIG_DBTABLEPREFIX;
+
$password=sha1($password);
$usernameclean=strtolower($username);
$username=OC_DB::escape($username);
$usernameclean=OC_DB::escape($usernameclean);
- $query="SELECT user_id FROM 'users' WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1";
+ $query = "SELECT user_id FROM '{$CONFIG_DBTABLEPREFIX}users' WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1";
$result=OC_DB::select($query);
if(isset($result[0]) && isset($result[0]['user_id']) && $result[0]['user_id']>0){
return true;
diff --git a/inc/templates/adminform.php b/inc/templates/adminform.php
index 9846cfee6d..882c0dd377 100755
--- a/inc/templates/adminform.php
+++ b/inc/templates/adminform.php
@@ -10,6 +10,7 @@ if(!$f) die('Error: Config file (config/config.php) is not writable for the webs
if(!isset($fillDB)) $fillDB=true;
if(!isset($CONFIG_DBHOST)) $CONFIG_DBHOST='localhost';
if(!isset($CONFIG_DBUSER)) $CONFIG_DBUSER='owncloud';
+if(!isset($CONFIG_DBTABLEPREFIX)) $CONFIG_DBTABLEPREFIX='oc_';
$newuserpassword=OC_USER::generatepassword();
?>