From df149217b9edea53cb95c9f189531ff483acc044 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 11 Jun 2010 16:54:57 +0200 Subject: [PATCH 01/24] fix loading of plugins --- inc/lib_base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/lib_base.php b/inc/lib_base.php index cd622f1f5b..82f7119174 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -128,7 +128,7 @@ if(OC_USER::isLoggedIn()){ // load plugins $CONFIG_LOADPLUGINS=''; $plugins=explode(' ',$CONFIG_LOADPLUGINS); -if(isset($plugins[0]['url'])) foreach($plugins as $plugin) require_once('plugins/'.$plugin.'/lib_'.$plugin.'.php'); +if(isset($plugins[0]['url'])) foreach($plugins as $plugin) oc_require_once('plugins/'.$plugin.'/lib_'.$plugin.'.php'); // check if the server is correctly configured for ownCloud From 02ad83b04d79079eac4e0783a83db32ed0dba88b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 11 Jun 2010 17:58:23 +0200 Subject: [PATCH 02/24] fix insertid in OC_DB --- inc/lib_base.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/inc/lib_base.php b/inc/lib_base.php index 82f7119174..728b9acd01 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -426,12 +426,7 @@ 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)); - } + return self::$DBConnection->lastInsertID(); } /** From 7503ad139b53de41e3567d723419e49166de574f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 18 Jun 2010 20:08:24 +0200 Subject: [PATCH 03/24] use MDB2_Schema for the creation of the initial database --- db_structure.xml | 381 +++ inc/MDB2/Driver/Datatype/Common.php | 2 +- inc/MDB2/Driver/Datatype/mysql.php | 2 +- inc/MDB2/Driver/Datatype/pgsql.php | 2 +- inc/MDB2/Driver/Datatype/sqlite.php | 2 +- inc/MDB2/Driver/mysql.php | 2 +- inc/MDB2/LOB.php | 2 +- inc/MDB2/Schema.php | 2763 +++++++++++++++++++ inc/MDB2/Schema/Parser.php | 819 ++++++ inc/MDB2/Schema/Parser2.php | 624 +++++ inc/MDB2/Schema/Reserved/ibase.php | 436 +++ inc/MDB2/Schema/Reserved/mssql.php | 258 ++ inc/MDB2/Schema/Reserved/mysql.php | 284 ++ inc/MDB2/Schema/Reserved/oci8.php | 171 ++ inc/MDB2/Schema/Reserved/pgsql.php | 147 + inc/MDB2/Schema/Tool.php | 560 ++++ inc/MDB2/Schema/Tool/ParameterException.php | 6 + inc/MDB2/Schema/Validate.php | 922 +++++++ inc/MDB2/Schema/Writer.php | 581 ++++ inc/XML/Parser.php | 9 +- inc/lib_base.php | 40 +- inc/lib_config.php | 213 +- 22 files changed, 8048 insertions(+), 178 deletions(-) create mode 100755 db_structure.xml create mode 100644 inc/MDB2/Schema.php create mode 100644 inc/MDB2/Schema/Parser.php create mode 100644 inc/MDB2/Schema/Parser2.php create mode 100644 inc/MDB2/Schema/Reserved/ibase.php create mode 100644 inc/MDB2/Schema/Reserved/mssql.php create mode 100644 inc/MDB2/Schema/Reserved/mysql.php create mode 100644 inc/MDB2/Schema/Reserved/oci8.php create mode 100644 inc/MDB2/Schema/Reserved/pgsql.php create mode 100644 inc/MDB2/Schema/Tool.php create mode 100644 inc/MDB2/Schema/Tool/ParameterException.php create mode 100644 inc/MDB2/Schema/Validate.php create mode 100644 inc/MDB2/Schema/Writer.php diff --git a/db_structure.xml b/db_structure.xml new file mode 100755 index 0000000000..72c63761bc --- /dev/null +++ b/db_structure.xml @@ -0,0 +1,381 @@ + + + + owncloud + true + false + + latin1 + + + + groups + + + + + group_id + integer + 0 + true + 1 + 4 + + + + group_name + text + + true + 64 + + + + group_name + true + + group_name + ascending + + + + + +
+ + + + locks + + + + + token + text + + true + 255 + + + + path + text + + true + 200 + + + + created + integer + 0 + true + 4 + + + + modified + integer + 0 + true + 4 + + + + expires + integer + 0 + true + 4 + + + + owner + text + + false + 200 + + + + recursive + integer + 0 + false + 4 + + + + writelock + integer + 0 + false + 4 + + + + exclusivelock + integer + 0 + true + 4 + + + + path_2 + + path + ascending + + + + + path_3 + + path + ascending + + + token + ascending + + + + + expires + + expires + ascending + + + + + locks_pKey + true + + token + ascending + + + + + token + true + + token + ascending + + + + + +
+ + + + log + + + + + id + integer + 0 + true + 1 + 4 + + + + timestamp + integer + + true + 4 + + + + user + text + + true + 250 + + + + type + integer + + true + 4 + + + + message + text + + true + 250 + + + + +
+ + + + properties + + + + + path + text + + true + 255 + + + + name + text + + true + 120 + + + + ns + text + DAV: + true + 120 + + + + value + clob + false + + + + path + + path + ascending + + + + + properties_pKey + true + + path + ascending + + + name + ascending + + + ns + ascending + + + + + +
+ + + + user_group + + + + + user_group_id + integer + 0 + true + 1 + 4 + + + + user_id + text + + true + 64 + + + + group_id + text + + true + 64 + + + + +
+ + + + users + + + + + user_id + integer + 0 + true + 1 + 4 + + + + user_name + text + + true + 64 + + + + user_name_clean + text + + true + 64 + + + + user_password + text + + true + 340 + + + + user_name + true + + user_name + ascending + + + user_name_clean + ascending + + + + + +
+ +
diff --git a/inc/MDB2/Driver/Datatype/Common.php b/inc/MDB2/Driver/Datatype/Common.php index 329bd60768..750dbb2477 100755 --- a/inc/MDB2/Driver/Datatype/Common.php +++ b/inc/MDB2/Driver/Datatype/Common.php @@ -44,7 +44,7 @@ // // $Id: Common.php,v 1.139 2008/12/04 11:50:42 afz Exp $ -require_once 'MDB2/LOB.php'; +oc_require_once('MDB2/LOB.php'); /** * @package MDB2 diff --git a/inc/MDB2/Driver/Datatype/mysql.php b/inc/MDB2/Driver/Datatype/mysql.php index aeec86aefa..944248f57c 100755 --- a/inc/MDB2/Driver/Datatype/mysql.php +++ b/inc/MDB2/Driver/Datatype/mysql.php @@ -46,7 +46,7 @@ // $Id: mysql.php,v 1.65 2008/02/22 19:23:49 quipo Exp $ // -require_once 'MDB2/Driver/Datatype/Common.php'; +oc_require_once('MDB2/Driver/Datatype/Common.php'); /** * MDB2 MySQL driver diff --git a/inc/MDB2/Driver/Datatype/pgsql.php b/inc/MDB2/Driver/Datatype/pgsql.php index 2979680490..fe18729c84 100755 --- a/inc/MDB2/Driver/Datatype/pgsql.php +++ b/inc/MDB2/Driver/Datatype/pgsql.php @@ -44,7 +44,7 @@ // // $Id: pgsql.php,v 1.93 2008/08/28 20:32:57 afz Exp $ -require_once 'MDB2/Driver/Datatype/Common.php'; +oc_require_once('MDB2/Driver/Datatype/Common.php'); /** * MDB2 PostGreSQL driver diff --git a/inc/MDB2/Driver/Datatype/sqlite.php b/inc/MDB2/Driver/Datatype/sqlite.php index 270c139684..533d0e9510 100755 --- a/inc/MDB2/Driver/Datatype/sqlite.php +++ b/inc/MDB2/Driver/Datatype/sqlite.php @@ -46,7 +46,7 @@ // $Id: sqlite.php,v 1.67 2008/02/22 19:58:06 quipo Exp $ // -require_once 'MDB2/Driver/Datatype/Common.php'; +oc_require_once('MDB2/Driver/Datatype/Common.php'); /** * MDB2 SQLite driver diff --git a/inc/MDB2/Driver/mysql.php b/inc/MDB2/Driver/mysql.php index 5c8df3b6b6..d7b5719f68 100755 --- a/inc/MDB2/Driver/mysql.php +++ b/inc/MDB2/Driver/mysql.php @@ -1406,7 +1406,7 @@ class MDB2_Result_mysql extends MDB2_Result_Common if ($object_class == 'stdClass') { $row = (object) $row; } else { - $row = &new $object_class($row); + $row = new $object_class($row); } } ++$this->rownum; diff --git a/inc/MDB2/LOB.php b/inc/MDB2/LOB.php index 69db267d69..2cdf67afa9 100755 --- a/inc/MDB2/LOB.php +++ b/inc/MDB2/LOB.php @@ -50,7 +50,7 @@ * @author Lukas Smith */ -require_once 'MDB2.php'; +oc_require_once('MDB2.php'); /** * MDB2_LOB: user land stream wrapper implementation for LOB support diff --git a/inc/MDB2/Schema.php b/inc/MDB2/Schema.php new file mode 100644 index 0000000000..44518b3265 --- /dev/null +++ b/inc/MDB2/Schema.php @@ -0,0 +1,2763 @@ + + * 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: Schema.php,v 1.132 2009/02/22 21:43:22 ifeghali Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + +// require_once('MDB2.php'); + +define('MDB2_SCHEMA_DUMP_ALL', 0); +define('MDB2_SCHEMA_DUMP_STRUCTURE', 1); +define('MDB2_SCHEMA_DUMP_CONTENT', 2); + +/** + * If you add an error code here, make sure you also add a textual + * version of it in MDB2_Schema::errorMessage(). + */ + +define('MDB2_SCHEMA_ERROR', -1); +define('MDB2_SCHEMA_ERROR_PARSE', -2); +define('MDB2_SCHEMA_ERROR_VALIDATE', -3); +define('MDB2_SCHEMA_ERROR_UNSUPPORTED', -4); // Driver does not support this function +define('MDB2_SCHEMA_ERROR_INVALID', -5); // Invalid attribute value +define('MDB2_SCHEMA_ERROR_WRITER', -6); + +/** + * The database manager is a class that provides a set of database + * management services like installing, altering and dumping the data + * structures of databases. + * + * @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 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 728b9acd01..5fc95b66ae 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -98,6 +98,7 @@ 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'); @@ -318,6 +319,7 @@ class OC_UTIL { */ class OC_DB { static private $DBConnection=false; + static private $schema=false; /** * connect to the datbase if not already connected */ @@ -331,8 +333,11 @@ class OC_DB { global $SERVERROOT; if(!self::$DBConnection){ $options = array( - 'debug' => 0, 'portability' => MDB2_PORTABILITY_ALL, + 'log_line_break' => '
', + 'idxname_format' => '%s', + 'debug' => false, + 'quote_identifier' => true, ); if($CONFIG_DBTYPE=='sqlite'){ $dsn = array( @@ -349,13 +354,13 @@ class OC_DB { 'database' => $CONFIG_DBNAME, ); } - self::$DBConnection=MDB2::connect($dsn,$options); + 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($dsn,$options); } } @@ -426,7 +431,8 @@ class OC_DB { * @return primarykey */ static function insertid() { - return self::$DBConnection->lastInsertID(); + $id=self::$DBConnection->lastInsertID(); + return $id; } /** @@ -498,7 +504,31 @@ 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(); + $definition=@self::$schema->parseDatabaseDefinitionFile($file); + 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; + } + } } diff --git a/inc/lib_config.php b/inc/lib_config.php index 986d12f4dc..6ac83db951 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -267,169 +267,58 @@ 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; + //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="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']}`; + 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(); } - 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(); - } } ?> From a9fd0c67b44ea56f1941b49ec5453f6bf82d9f19 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sun, 20 Jun 2010 17:58:39 +0200 Subject: [PATCH 04/24] Initial PostgreSQL support --- db_structure.xml | 8 ++-- inc/lib_base.php | 24 +++++++++++- inc/lib_config.php | 73 ++++++++++++++++++++++--------------- inc/lib_log.php | 6 +-- inc/lib_user.php | 11 +++--- inc/templates/adminform.php | 32 +++++++++++----- 6 files changed, 101 insertions(+), 53 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 72c63761bc..f5d0f24505 100755 --- a/db_structure.xml +++ b/db_structure.xml @@ -304,18 +304,18 @@ user_id - text + integer true - 64 + 4 group_id - text + integer true - 64 + 4 diff --git a/inc/lib_base.php b/inc/lib_base.php index 5fc95b66ae..cfd9d4a567 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -353,6 +353,14 @@ 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::factory($dsn,$options); if (@PEAR::isError(self::$DBConnection)) { @@ -378,6 +386,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->query($cmd); if (PEAR::isError($result)) { @@ -396,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; } /** diff --git a/inc/lib_config.php b/inc/lib_config.php index 6ac83db951..4110e77cf4 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -184,13 +184,13 @@ class OC_CONFIG{ //create/fill database $CONFIG_DBTYPE=$dbtype; $CONFIG_DBNAME=$_POST['dbname']; - if($dbtype=='mysql'){ + if($dbtype!='sqlite'){ $CONFIG_DBHOST=$_POST['dbhost']; $CONFIG_DBUSER=$_POST['dbuser']; $CONFIG_DBPASSWORD=$_POST['dbpassword']; } 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){ @@ -241,7 +241,7 @@ class OC_CONFIG{ $config.='$CONFIG_DATEFORMAT=\''.$_POST['dateformat']."';\n"; $config.='$CONFIG_DBTYPE=\''.$dbtype."';\n"; $config.='$CONFIG_DBNAME=\''.$_POST['dbname']."';\n"; - if($dbtype=='mysql'){ + if($dbtype!='sqlite'){ $config.='$CONFIG_DBHOST=\''.$_POST['dbhost']."';\n"; $config.='$CONFIG_DBUSER=\''.$_POST['dbuser']."';\n"; $config.='$CONFIG_DBPASSWORD=\''.$_POST['dbpassword']."';\n"; @@ -290,34 +290,49 @@ class OC_CONFIG{ 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. - $connection = @new mysqli($CONFIG_DBHOST, $adminUser, $adminPwd); - if (mysqli_connect_errno()) { - @ob_end_clean(); - echo('

can not connect to database as administrative user.
'); - exit(); + 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); + } } - $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']}`; - 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_log.php b/inc/lib_log.php index 3e0d3d1609..f002733acd 100755 --- a/inc/lib_log.php +++ b/inc/lib_log.php @@ -48,7 +48,7 @@ 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).'")'); + $result = OC_DB::query('INSERT INTO `log` (`timestamp`,`user`,`type`,`message`) VALUES ('.time().',\''.addslashes($user).'\','.addslashes($type).',\''.addslashes($message).'\');'); OC_DB::free_result($result); } @@ -62,10 +62,10 @@ class OC_LOG { 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 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 log where user=\''.$user.'\' order by timestamp desc limit 20'); } foreach($result as $entry){ echo(''); diff --git a/inc/lib_user.php b/inc/lib_user.php index e5d994dd02..99909a3321 100755 --- a/inc/lib_user.php +++ b/inc/lib_user.php @@ -58,11 +58,11 @@ class OC_USER { if(OC_USER::getuserid($username)!=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 `users` (`user_name` ,`user_name_clean` ,`user_password`) VALUES ('$username', '$usernameclean', '$password')"; $result=OC_DB::query($query); return ($result)?true:false; } @@ -118,7 +118,7 @@ class OC_USER { public static function creategroup($groupname){ if(OC_USER::getgroupid($groupname)==0){ $groupname=OC_DB::escape($groupname); - $query="INSERT INTO `groups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')"; + $query="INSERT INTO `groups` (`group_name`) VALUES ('$groupname')"; $result=OC_DB::query($query); return ($result)?true:false; }else{ @@ -132,7 +132,6 @@ class OC_USER { */ public static function getuserid($username){ $usernameclean=strtolower($username); - $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); $query="SELECT user_id FROM users WHERE user_name_clean = '$usernameclean'"; $result=OC_DB::select($query); @@ -187,7 +186,7 @@ class OC_USER { $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 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; @@ -208,7 +207,7 @@ class OC_USER { $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 `user_group` (`user_id` ,`group_id`) VALUES ('$userid', '$groupid');"; $result=OC_DB::query($query); if($result){ return true; diff --git a/inc/templates/adminform.php b/inc/templates/adminform.php index c22c78768b..c94334466a 100755 --- a/inc/templates/adminform.php +++ b/inc/templates/adminform.php @@ -37,7 +37,7 @@ function dbtypechange(){ element.style.display='none'; } } - }else if(type=='mysql'){ + }else if(type=='mysql' || type=='pgsql'){ for(i in inputs){ id=inputs[i]; element=document.getElementById(id); @@ -80,24 +80,36 @@ if($FIRSTRUN){?> From e4a83b68e905347330f8c997c8f72daefadb031e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 22 Jun 2010 01:27:44 +0200 Subject: [PATCH 05/24] Cache user and group id's to save some queries --- inc/lib_user.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inc/lib_user.php b/inc/lib_user.php index 99909a3321..089d0cb54a 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 * @@ -132,6 +140,9 @@ class OC_USER { */ public static function getuserid($username){ $usernameclean=strtolower($username); + if(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'"; $result=OC_DB::select($query); @@ -139,6 +150,7 @@ class OC_USER { 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; @@ -150,6 +162,9 @@ class OC_USER { * */ public static function getgroupid($groupname){ + if(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'"; $result=OC_DB::select($query); @@ -157,6 +172,7 @@ class OC_USER { 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; @@ -168,6 +184,9 @@ class OC_USER { * */ public static function getgroupname($groupid){ + if($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"; $result=OC_DB::select($query); From 969c8053cecf16c912bc496d3796ee63a8933367 Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sat, 3 Jul 2010 18:59:56 +0200 Subject: [PATCH 06/24] Added variable $CONFIG_DBTABLEPREFIX in configuration file for table prefix Table prefix is useful where only one DB is possible (cheap hosting). this way is possible define something like a "namespace" default is 'oc_' --- config/config.sample.php | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.sample.php b/config/config.sample.php index dfaaa4284d..dc1a62f46e 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -8,4 +8,5 @@ $CONFIG_DBHOST='localhost'; $CONFIG_DBNAME='owncloud-db-name'; $CONFIG_DBUSER='user-name'; $CONFIG_DBPASSWORD='password'; +$CONFIG_DBTABLEPREFIX = 'oc_'; ?> From a87f4da3f3d176825faf3398a0e716f4577d385c Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sat, 3 Jul 2010 19:06:42 +0200 Subject: [PATCH 07/24] Using table prefix preference and some reformatting --- inc/lib_config.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/inc/lib_config.php b/inc/lib_config.php index 986d12f4dc..546c393b7e 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -134,6 +134,7 @@ class OC_CONFIG{ global $CONFIG_DBUSER; global $CONFIG_DBPASSWORD; global $CONFIG_DBTYPE; + global $CONFIG_DBTABLEPREFIX; global $CONFIG_ADMINLOGIN; global $CONFIG_ADMINPASSWORD; if(isset($_POST['set_config'])){ @@ -332,8 +333,15 @@ CREATE TABLE 'user_group' ( PRIMARY KEY ('user_group_id') ) "; - }elseif($CONFIG_DBTYPE=='mysql'){ - $query="CREATE TABLE IF NOT EXISTS `locks` ( + } elseif ( 'mysql' === $CONFIG_DBTYPE ) { + $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; + $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; + $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; + + $query = "CREATE TABLE IF NOT EXISTS `$dbTableLocks` ( `token` varchar(255) NOT NULL DEFAULT '', `path` varchar(200) NOT NULL DEFAULT '', `created` int(11) NOT NULL DEFAULT '0', @@ -351,7 +359,7 @@ PRIMARY KEY ('user_group_id') KEY `expires` (`expires`) ); -CREATE TABLE IF NOT EXISTS `log` ( +CREATE TABLE IF NOT EXISTS `$dbTableLog` ( `id` int(11) NOT NULL AUTO_INCREMENT, `timestamp` int(11) NOT NULL, `user` varchar(250) NOT NULL, @@ -361,7 +369,7 @@ CREATE TABLE IF NOT EXISTS `log` ( ); -CREATE TABLE IF NOT EXISTS `properties` ( +CREATE TABLE IF NOT EXISTS `$dbTableProperties` ( `path` varchar(255) NOT NULL DEFAULT '', `name` varchar(120) NOT NULL DEFAULT '', `ns` varchar(120) NOT NULL DEFAULT 'DAV:', @@ -370,7 +378,7 @@ CREATE TABLE IF NOT EXISTS `properties` ( KEY `path` (`path`) ); -CREATE TABLE IF NOT EXISTS `users` ( +CREATE TABLE IF NOT EXISTS `$dbTableUsers` ( `user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `user_name` VARCHAR( 64 ) NOT NULL , `user_name_clean` VARCHAR( 64 ) NOT NULL , @@ -381,7 +389,7 @@ UNIQUE ( ) ); -CREATE TABLE IF NOT EXISTS `groups` ( +CREATE TABLE IF NOT EXISTS `$dbTableGroups` ( `group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `group_name` VARCHAR( 64 ) NOT NULL , UNIQUE ( @@ -389,7 +397,7 @@ UNIQUE ( ) ); -CREATE TABLE IF NOT EXISTS `user_group` ( +CREATE TABLE IF NOT EXISTS `$dbTableUserGroup` ( `user_group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `user_id` VARCHAR( 64 ) NOT NULL , `group_id` VARCHAR( 64 ) NOT NULL From 31df117f1c24f1c61f306a79fd95c6dc371fd05b Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sat, 3 Jul 2010 19:08:31 +0200 Subject: [PATCH 08/24] Using table prefix preference in 'inc/lib_log.php' --- inc/lib_log.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/inc/lib_log.php b/inc/lib_log.php index 3e0d3d1609..8ece1a498a 100755 --- a/inc/lib_log.php +++ b/inc/lib_log.php @@ -47,8 +47,12 @@ class OC_LOG { * @param type $type * @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).'")'); + public static function event($user, $type, $message){ + global $CONFIG_DBTABLEPREFIX; + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'Log'; + $query = 'insert into ' . $dbTableLog . ' (timestamp,user,type,message) values + ("' . time() . '","' . addslashes($user) . '","' . addslashes($type) . '","' . addslashes($message) . '")'; + $result = OC_DB::query($query); OC_DB::free_result($result); } @@ -58,15 +62,18 @@ class OC_LOG { * */ public static function show(){ - global $CONFIG_DATEFORMAT; + global $CONFIG_DATEFORMAT; + global $CONFIG_DBTABLEPREFIX; echo('
'); - + + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'Log'; 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'); + $query = "select timestamp,user,type,message from $dbTableLog 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'); + $user = $_SESSION['username_clean']; + $query = "select timestamp,user,type,message from $dbTableLog where user='$user' order by timestamp desc limit 20"; } + $result = OC_DB::select($query); foreach($result as $entry){ echo(''); echo(''); From 3f4577189591d84dc92f10c5263d13778f5c3e98 Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sat, 3 Jul 2010 19:12:51 +0200 Subject: [PATCH 09/24] Using table prefix preference in 'inc/lib_ocs.php' --- inc/lib_ocs.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/inc/lib_ocs.php b/inc/lib_ocs.php index 52dc95800a..c2fe6dba33 100755 --- a/inc/lib_ocs.php +++ b/inc/lib_ocs.php @@ -372,15 +372,17 @@ class OC_OCS { * @return string xml/json */ private static function activityget($format,$page,$pagesize) { + global $CONFIG_DBTABLEPREFIX; + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'Log'; $user=OC_OCS::checkpassword(); - $result = OC_DB::query('select count(*) as co from log'); + $result = OC_DB::query("select count(*) as co from $dbTableLog"); $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 $dbTableLog order by timestamp desc limit " . ($page*$pagesize) . ",$pagesize"); $itemscount=count($result); $url='http://'.substr($_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'],0,-11).''; From 54c1264e2477a722ba8293a369a4dafe7755b65d Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sat, 3 Jul 2010 19:38:31 +0200 Subject: [PATCH 10/24] Using table prefix preference in 'inc/lib_user.php' --- inc/lib_user.php | 55 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/inc/lib_user.php b/inc/lib_user.php index e5d994dd02..d3b38bc2ea 100755 --- a/inc/lib_user.php +++ b/inc/lib_user.php @@ -55,6 +55,8 @@ class OC_USER { * */ public static function createuser($username,$password){ + global $CONFIG_DBTABLEPREFIX; + if(OC_USER::getuserid($username)!=0){ return false; }else{ @@ -62,7 +64,8 @@ class OC_USER { $usernameclean=strtolower($username); $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')"; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $query = "INSERT INTO `$dbTableUsers` (`user_id` ,`user_name` ,`user_name_clean` ,`user_password`) VALUES (NULL , '$username', '$usernameclean', '$password')"; $result=OC_DB::query($query); return ($result)?true:false; } @@ -74,11 +77,14 @@ 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"; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $query = "SELECT user_id FROM $dbTableUsers 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']; @@ -116,9 +122,12 @@ class OC_USER { * */ public static function creategroup($groupname){ + global $CONFIG_DBTABLEPREFIX; + if(OC_USER::getgroupid($groupname)==0){ $groupname=OC_DB::escape($groupname); - $query="INSERT INTO `groups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')"; + $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; + $query="INSERT INTO `$dbTableGroups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')"; $result=OC_DB::query($query); return ($result)?true:false; }else{ @@ -131,10 +140,13 @@ class OC_USER { * */ public static function getuserid($username){ + global $CONFIG_DBTABLEPREFIX; + $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $query="SELECT user_id FROM users WHERE user_name_clean = '$usernameclean'"; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $query="SELECT user_id FROM $dbTableUsers WHERE user_name_clean = '$usernameclean'"; $result=OC_DB::select($query); if(!is_array($result)){ return 0; @@ -151,8 +163,11 @@ class OC_USER { * */ public static function getgroupid($groupname){ + global $CONFIG_DBTABLEPREFIX; + $groupname=OC_DB::escape($groupname); - $query="SELECT group_id FROM groups WHERE group_name = '$groupname'"; + $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; + $query="SELECT group_id FROM $dbTableGroups WHERE group_name = '$groupname'"; $result=OC_DB::select($query); if(!is_array($result)){ return 0; @@ -169,8 +184,11 @@ class OC_USER { * */ public static function getgroupname($groupid){ + global $CONFIG_DBTABLEPREFIX; + $groupid=(integer)$groupid; - $query="SELECT group_name FROM groups WHERE group_id = '$groupid' LIMIT 1"; + $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; + $query="SELECT group_name FROM $dbTableGroups 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']; @@ -184,10 +202,13 @@ 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"; + $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; + $query="SELECT user_group_id FROM $dbTableUserGroup WHERE group_id = $groupid AND user_id = $userid LIMIT 1"; $result=OC_DB::select($query); if(isset($result[0]) && isset($result[0]['user_group_id'])){ return true; @@ -204,11 +225,14 @@ 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');"; + $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; + $query="INSERT INTO `$dbTableUserGroup` (`user_group_id` ,`user_id` ,`group_id`) VALUES (NULL , '$userid', '$groupid');"; $result=OC_DB::query($query); if($result){ return true; @@ -232,8 +256,11 @@ 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'"; + $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; + $query = "SELECT group_id FROM $dbTableUserGroup WHERE user_id = '$userid'"; $result=OC_DB::select($query); $groups=array(); if(is_array($result)){ @@ -250,9 +277,12 @@ 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'"; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $query = "UPDATE $dbTableUsers SET user_password = '$password' WHERE user_id ='$userid'"; $result=OC_DB::query($query); if($result){ return true; @@ -266,11 +296,14 @@ 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"; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $query = "SELECT user_id FROM '$dbTableUsers' 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; From f26c7d9bfc116ecf60d5036352c194a641f95c1b Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sun, 4 Jul 2010 06:47:08 +0200 Subject: [PATCH 11/24] Added a input for the DB table prefix in 'inc/templates/adminform.php' --- inc/templates/adminform.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/inc/templates/adminform.php b/inc/templates/adminform.php index c22c78768b..231e9c5283 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(); ?> -
+
'.date($CONFIG_DATEFORMAT,$entry['timestamp']).'
- + @@ -104,21 +105,22 @@ foreach($dbtypes as $dbtype){ + - + - +
force ssl:>
force ssl:>
enable automatic backup:>
backup directory:
date format:
database host:
database name:
database table prefix:
database user:
database password:(leave empty to keep current password)
retype database password:
create database and user: onchange='showDBAdmin()'>
create database and user: onchange='showDBAdmin()'>
database administrative user:
database administrative password:
automaticly fill initial database:>
automaticly fill initial database:>


-
- + +
From e2c621e9583608c02a761f3ad50223e7aa85bd19 Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sun, 4 Jul 2010 06:48:59 +0200 Subject: [PATCH 12/24] Write DB table prefix into the configuration file --- inc/lib_config.php | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/inc/lib_config.php b/inc/lib_config.php index 546c393b7e..a05aff389a 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -134,7 +134,6 @@ class OC_CONFIG{ global $CONFIG_DBUSER; global $CONFIG_DBPASSWORD; global $CONFIG_DBTYPE; - global $CONFIG_DBTABLEPREFIX; global $CONFIG_ADMINLOGIN; global $CONFIG_ADMINPASSWORD; if(isset($_POST['set_config'])){ @@ -242,6 +241,7 @@ class OC_CONFIG{ $config.='$CONFIG_DATEFORMAT=\''.$_POST['dateformat']."';\n"; $config.='$CONFIG_DBTYPE=\''.$dbtype."';\n"; $config.='$CONFIG_DBNAME=\''.$_POST['dbname']."';\n"; + $config.='$CONFIG_DBTABLEPREFIX=\''.$_POST['dbtableprefix']."';\n"; if($dbtype=='mysql'){ $config.='$CONFIG_DBHOST=\''.$_POST['dbhost']."';\n"; $config.='$CONFIG_DBUSER=\''.$_POST['dbuser']."';\n"; @@ -277,8 +277,16 @@ class OC_CONFIG{ */ private static function filldatabase(){ global $CONFIG_DBTYPE; + global $CONFIG_DBTABLEPREFIX; + + $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; + $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; + $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; + $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; + $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; if($CONFIG_DBTYPE=='sqlite'){ - $query="CREATE TABLE 'locks' ( + $query="CREATE TABLE '$dbTableLocks' ( 'token' VARCHAR(255) NOT NULL DEFAULT '', 'path' varchar(200) NOT NULL DEFAULT '', 'created' int(11) NOT NULL DEFAULT '0', @@ -292,7 +300,7 @@ class OC_CONFIG{ UNIQUE ('token') ); -CREATE TABLE 'log' ( +CREATE TABLE '$dbTableLog' ( `id` INTEGER ASC DEFAULT '' NOT NULL, 'timestamp' int(11) NOT NULL, 'user' varchar(250) NOT NULL, @@ -302,7 +310,7 @@ CREATE TABLE 'log' ( ); -CREATE TABLE 'properties' ( +CREATE TABLE '$dbTableProperties' ( 'path' varchar(255) NOT NULL DEFAULT '', 'name' varchar(120) NOT NULL DEFAULT '', 'ns' varchar(120) NOT NULL DEFAULT 'DAV:', @@ -310,7 +318,7 @@ CREATE TABLE 'properties' ( PRIMARY KEY ('path','name','ns') ); -CREATE TABLE 'users' ( +CREATE TABLE '$dbTableUsers' ( 'user_id' INTEGER ASC DEFAULT '', 'user_name' varchar(64) NOT NULL DEFAULT '', 'user_name_clean' varchar(64) NOT NULL DEFAULT '', @@ -319,29 +327,22 @@ CREATE TABLE 'users' ( UNIQUE ('user_name' ,'user_name_clean') ); -CREATE TABLE 'groups' ( +CREATE TABLE '$dbTableGroups' ( 'group_id' INTEGER ASC DEFAULT '', 'group_name' VARCHAR( 64 ) NOT NULL DEFAULT '', PRIMARY KEY ('group_id'), UNIQUE ('group_name') ); -CREATE TABLE 'user_group' ( +CREATE TABLE '$dbTableUserGroup' ( '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 ( 'mysql' === $CONFIG_DBTYPE ) { - $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; - $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; - $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; - - $query = "CREATE TABLE IF NOT EXISTS `$dbTableLocks` ( + }elseif($CONFIG_DBTYPE=='mysql'){ + $query="CREATE TABLE IF NOT EXISTS `$dbTableLocks` ( `token` varchar(255) NOT NULL DEFAULT '', `path` varchar(200) NOT NULL DEFAULT '', `created` int(11) NOT NULL DEFAULT '0', From 2fe595d10915afe27ba3a55eeb78bfe0be094f9b Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sun, 4 Jul 2010 07:35:05 +0200 Subject: [PATCH 13/24] Now DB tables are created with the right name (using the prefix) --- inc/lib_config.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inc/lib_config.php b/inc/lib_config.php index a05aff389a..9566b504b2 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,6 +186,7 @@ class OC_CONFIG{ //create/fill database $CONFIG_DBTYPE=$dbtype; $CONFIG_DBNAME=$_POST['dbname']; + $CONFIG_DBTABLEPREFIX=$_POST['dbtableprefix']; if($dbtype=='mysql'){ $CONFIG_DBHOST=$_POST['dbhost']; $CONFIG_DBUSER=$_POST['dbuser']; From 78cc98e4ea73daa9717297dab800c55edfc61ee5 Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sun, 4 Jul 2010 07:39:26 +0200 Subject: [PATCH 14/24] Changed table name from Log to log in 'inc/lib_log.php' --- inc/lib_log.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/lib_log.php b/inc/lib_log.php index 8ece1a498a..675f1c0b75 100755 --- a/inc/lib_log.php +++ b/inc/lib_log.php @@ -49,7 +49,7 @@ class OC_LOG { */ public static function event($user, $type, $message){ global $CONFIG_DBTABLEPREFIX; - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'Log'; + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; $query = 'insert into ' . $dbTableLog . ' (timestamp,user,type,message) values ("' . time() . '","' . addslashes($user) . '","' . addslashes($type) . '","' . addslashes($message) . '")'; $result = OC_DB::query($query); @@ -66,7 +66,7 @@ class OC_LOG { global $CONFIG_DBTABLEPREFIX; echo('
Create new user:
user name
password
'); - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'Log'; + $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; if(OC_USER::ingroup($_SESSION['username_clean'],'admin')){ $query = "select timestamp,user,type,message from $dbTableLog order by timestamp desc limit 20"; }else{ From f3627a756ea132cd61dc35ba94edd7d2d06315a3 Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sun, 4 Jul 2010 10:36:50 +0200 Subject: [PATCH 15/24] Using DB table prefix in 'inc/HTTP/WebDAV/Server/Filesyste.php' --- inc/HTTP/WebDAV/Server/Filesystem.php | 42 ++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/inc/HTTP/WebDAV/Server/Filesystem.php b/inc/HTTP/WebDAV/Server/Filesystem.php index 000831f6fe..218b286c38 100755 --- a/inc/HTTP/WebDAV/Server/Filesystem.php +++ b/inc/HTTP/WebDAV/Server/Filesystem.php @@ -150,6 +150,9 @@ */ function fileinfo($path) { + global $CONFIG_DBTABLEPREFIX; + $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; + // map URI path to filesystem path $fspath =$path; @@ -183,7 +186,7 @@ $info["props"][] = $this->mkprop("getcontentlength", OC_FILESYSTEM::filesize($fspath)); } // get additional properties from database - $query = "SELECT ns, name, value FROM properties WHERE path = '$path'"; + $query = "SELECT ns, name, value FROM $dbTableProperties WHERE path = '$path'"; $res = OC_DB::select($query); while ($row = $res[0]) { $info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]); @@ -392,6 +395,8 @@ */ function DELETE($options) { + global $CONFIG_DBTABLEPREFIX; + $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; $path =$options["path"]; if (!OC_FILESYSTEM::file_exists($path)) { @@ -399,13 +404,13 @@ } if (OC_FILESYSTEM::is_dir($path)) { - $query = "DELETE FROM properties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; + $query = "DELETE FROM $dbTableProperties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; OC_DB::query($query); OC_FILESYSTEM::delTree($path); } else { OC_FILESYSTEM::unlink($path); } - $query = "DELETE FROM properties WHERE path = '$options[path]'"; + $query = "DELETE FROM $dbTableProperties WHERE path = '$options[path]'"; OC_DB::query($query); return "204 No Content"; @@ -432,6 +437,8 @@ function COPY($options, $del=false) { // TODO Property updates still broken (Litmus should detect this?) + global $CONFIG_DBTABLEPREFIX; + $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet return "415 Unsupported media type"; @@ -507,13 +514,13 @@ } $destpath = $this->_unslashify($options["dest"]); if (is_dir($source)) { - $query = "UPDATE properties + $query = "UPDATE $dbTableProperties SET path = REPLACE(path, '".$options["path"]."', '".$destpath."') WHERE path LIKE '".$this->_slashify($options["path"])."%'"; OC_DB::query($query); } - $query = "UPDATE properties + $query = "UPDATE $dbTableProperties SET path = '".$destpath."' WHERE path = '".$options["path"]."'"; OC_DB::query($query); @@ -554,7 +561,7 @@ } } - $query = "INSERT INTO properties SELECT * FROM properties WHERE path = '".$options['path']."'"; + $query = "INSERT INTO $dbTableProperties SELECT * FROM properties WHERE path = '".$options['path']."'"; } return ($new && !$existing_col) ? "201 Created" : "204 No Content"; @@ -569,6 +576,9 @@ function PROPPATCH(&$options) { global $prefs, $tab; + global $CONFIG_DBTABLEPREFIX; + + $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; $msg = ""; $path = $options["path"]; @@ -580,10 +590,10 @@ $options["props"][$key]['status'] = "403 Forbidden"; } else { if (isset($prop["val"])) { - $query = "REPLACE INTO properties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'"; + $query = "REPLACE INTO $dbTableProperties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'"; error_log($query); } else { - $query = "DELETE FROM properties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'"; + $query = "DELETE FROM $dbTableProperties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'"; } OC_DB::query($query); } @@ -601,6 +611,8 @@ */ function LOCK(&$options) { + global $CONFIG_DBTABLEPREFIX; + $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; // get absolute fs path to requested resource $fspath = $options["path"]; @@ -615,13 +627,13 @@ if (isset($options["update"])) { // Lock Update $where = "WHERE path = '$options[path]' AND token = '$options[update]'"; - $query = "SELECT owner, exclusivelock FROM locks $where"; + $query = "SELECT owner, exclusivelock FROM $dbTableLocks $where"; $res = OC_DB::query($query); $row = OC_DB::fetch_assoc($res); OC_DB::free_result($res); if (is_array($row)) { - $query = "UPDATE `locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + $query = "UPDATE `$dbTableLocks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; OC_DB::query($query); $options['owner'] = $row['owner']; @@ -634,7 +646,7 @@ } } - $query = "INSERT INTO `locks` + $query = "INSERT INTO `$dbTableLocks` SET `token` = '$options[locktoken]' , `path` = '$options[path]' , `created` = ".time()." @@ -656,7 +668,9 @@ */ function UNLOCK(&$options) { - $query = "DELETE FROM locks + global $CONFIG_DBTABLEPREFIX; + $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; + $query = "DELETE FROM $dbTableLocks WHERE path = '$options[path]' AND token = '$options[token]'"; OC_DB::query($query); @@ -672,9 +686,11 @@ */ function checkLock($path) { + global $CONFIG_DBTABLEPREFIX; + $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; $result = false; $query = "SELECT * - FROM locks + FROM $dbTableLocks WHERE path = '$path' "; $res = OC_DB::select($query); From 76672fe0379acfd26ac5b85c4c99040c9cb2554e Mon Sep 17 00:00:00 2001 From: "Aldo \"xoen\" Giambelluca" Date: Sun, 4 Jul 2010 11:32:39 +0200 Subject: [PATCH 16/24] Avoid creation of variables just to put the table names with prefix --- inc/HTTP/WebDAV/Server/Filesystem.php | 38 +++++++++++---------------- inc/lib_config.php | 30 +++++++++------------ inc/lib_log.php | 9 +++---- inc/lib_ocs.php | 5 ++-- inc/lib_user.php | 33 ++++++++--------------- 5 files changed, 45 insertions(+), 70 deletions(-) diff --git a/inc/HTTP/WebDAV/Server/Filesystem.php b/inc/HTTP/WebDAV/Server/Filesystem.php index 218b286c38..db29b145b8 100755 --- a/inc/HTTP/WebDAV/Server/Filesystem.php +++ b/inc/HTTP/WebDAV/Server/Filesystem.php @@ -151,7 +151,6 @@ function fileinfo($path) { global $CONFIG_DBTABLEPREFIX; - $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; // map URI path to filesystem path $fspath =$path; @@ -186,7 +185,7 @@ $info["props"][] = $this->mkprop("getcontentlength", OC_FILESYSTEM::filesize($fspath)); } // get additional properties from database - $query = "SELECT ns, name, value FROM $dbTableProperties WHERE path = '$path'"; + $query = "SELECT ns, name, value FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$path'"; $res = OC_DB::select($query); while ($row = $res[0]) { $info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]); @@ -396,7 +395,6 @@ function DELETE($options) { global $CONFIG_DBTABLEPREFIX; - $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; $path =$options["path"]; if (!OC_FILESYSTEM::file_exists($path)) { @@ -404,13 +402,13 @@ } if (OC_FILESYSTEM::is_dir($path)) { - $query = "DELETE FROM $dbTableProperties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; OC_DB::query($query); OC_FILESYSTEM::delTree($path); } else { OC_FILESYSTEM::unlink($path); } - $query = "DELETE FROM $dbTableProperties WHERE path = '$options[path]'"; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$options[path]'"; OC_DB::query($query); return "204 No Content"; @@ -438,7 +436,6 @@ { // TODO Property updates still broken (Litmus should detect this?) global $CONFIG_DBTABLEPREFIX; - $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet return "415 Unsupported media type"; @@ -514,13 +511,13 @@ } $destpath = $this->_unslashify($options["dest"]); if (is_dir($source)) { - $query = "UPDATE $dbTableProperties + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}properties SET path = REPLACE(path, '".$options["path"]."', '".$destpath."') WHERE path LIKE '".$this->_slashify($options["path"])."%'"; OC_DB::query($query); } - $query = "UPDATE $dbTableProperties + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}properties SET path = '".$destpath."' WHERE path = '".$options["path"]."'"; OC_DB::query($query); @@ -561,7 +558,7 @@ } } - $query = "INSERT INTO $dbTableProperties SELECT * FROM properties WHERE path = '".$options['path']."'"; + $query = "INSERT INTO {$CONFIG_DBTABLEPREFIX}properties SELECT * FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '".$options['path']."'"; } return ($new && !$existing_col) ? "201 Created" : "204 No Content"; @@ -578,8 +575,6 @@ global $prefs, $tab; global $CONFIG_DBTABLEPREFIX; - $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; - $msg = ""; $path = $options["path"]; $dir = dirname($path)."/"; @@ -590,10 +585,10 @@ $options["props"][$key]['status'] = "403 Forbidden"; } else { if (isset($prop["val"])) { - $query = "REPLACE INTO $dbTableProperties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'"; + $query = "REPLACE INTO {$CONFIG_DBTABLEPREFIX}properties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'"; error_log($query); } else { - $query = "DELETE FROM $dbTableProperties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'"; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'"; } OC_DB::query($query); } @@ -612,7 +607,7 @@ function LOCK(&$options) { global $CONFIG_DBTABLEPREFIX; - $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; + // get absolute fs path to requested resource $fspath = $options["path"]; @@ -627,13 +622,13 @@ if (isset($options["update"])) { // Lock Update $where = "WHERE path = '$options[path]' AND token = '$options[update]'"; - $query = "SELECT owner, exclusivelock FROM $dbTableLocks $where"; + $query = "SELECT owner, exclusivelock FROM {$CONFIG_DBTABLEPREFIX}locks $where"; $res = OC_DB::query($query); $row = OC_DB::fetch_assoc($res); OC_DB::free_result($res); if (is_array($row)) { - $query = "UPDATE `$dbTableLocks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + $query = "UPDATE `{$CONFIG_DBTABLEPREFIX}locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; OC_DB::query($query); $options['owner'] = $row['owner']; @@ -646,7 +641,7 @@ } } - $query = "INSERT INTO `$dbTableLocks` + $query = "INSERT INTO `{$CONFIG_DBTABLEPREFIX}locks` SET `token` = '$options[locktoken]' , `path` = '$options[path]' , `created` = ".time()." @@ -669,8 +664,7 @@ function UNLOCK(&$options) { global $CONFIG_DBTABLEPREFIX; - $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; - $query = "DELETE FROM $dbTableLocks + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}locks WHERE path = '$options[path]' AND token = '$options[token]'"; OC_DB::query($query); @@ -687,10 +681,10 @@ function checkLock($path) { global $CONFIG_DBTABLEPREFIX; - $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; + $result = false; $query = "SELECT * - FROM $dbTableLocks + FROM {$CONFIG_DBTABLEPREFIX}locks WHERE path = '$path' "; $res = OC_DB::select($query); @@ -715,4 +709,4 @@ } } -?> +?> \ No newline at end of file diff --git a/inc/lib_config.php b/inc/lib_config.php index 9566b504b2..4d3cc340b8 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -282,14 +282,8 @@ class OC_CONFIG{ global $CONFIG_DBTYPE; global $CONFIG_DBTABLEPREFIX; - $dbTableLocks = $CONFIG_DBTABLEPREFIX . 'locks'; - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; - $dbTableProperties = $CONFIG_DBTABLEPREFIX . 'properties'; - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; - $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; if($CONFIG_DBTYPE=='sqlite'){ - $query="CREATE TABLE '$dbTableLocks' ( + $query="CREATE TABLE '{$CONFIG_DBTABLEPREFIX}locks' ( 'token' VARCHAR(255) NOT NULL DEFAULT '', 'path' varchar(200) NOT NULL DEFAULT '', 'created' int(11) NOT NULL DEFAULT '0', @@ -303,7 +297,7 @@ class OC_CONFIG{ UNIQUE ('token') ); -CREATE TABLE '$dbTableLog' ( +CREATE TABLE '{$CONFIG_DBTABLEPREFIX}log' ( `id` INTEGER ASC DEFAULT '' NOT NULL, 'timestamp' int(11) NOT NULL, 'user' varchar(250) NOT NULL, @@ -313,7 +307,7 @@ CREATE TABLE '$dbTableLog' ( ); -CREATE TABLE '$dbTableProperties' ( +CREATE TABLE '{$CONFIG_DBTABLEPREFIX}properties' ( 'path' varchar(255) NOT NULL DEFAULT '', 'name' varchar(120) NOT NULL DEFAULT '', 'ns' varchar(120) NOT NULL DEFAULT 'DAV:', @@ -321,7 +315,7 @@ CREATE TABLE '$dbTableProperties' ( PRIMARY KEY ('path','name','ns') ); -CREATE TABLE '$dbTableUsers' ( +CREATE TABLE '{$CONFIG_DBTABLEPREFIX}users' ( 'user_id' INTEGER ASC DEFAULT '', 'user_name' varchar(64) NOT NULL DEFAULT '', 'user_name_clean' varchar(64) NOT NULL DEFAULT '', @@ -330,14 +324,14 @@ CREATE TABLE '$dbTableUsers' ( UNIQUE ('user_name' ,'user_name_clean') ); -CREATE TABLE '$dbTableGroups' ( +CREATE TABLE '{$CONFIG_DBTABLEPREFIX}groups' ( 'group_id' INTEGER ASC DEFAULT '', 'group_name' VARCHAR( 64 ) NOT NULL DEFAULT '', PRIMARY KEY ('group_id'), UNIQUE ('group_name') ); -CREATE TABLE '$dbTableUserGroup' ( +CREATE TABLE '{$CONFIG_DBTABLEPREFIX}user_group' ( 'user_group_id' INTEGER ASC DEFAULT '', 'user_id' VARCHAR( 64 ) NOT NULL DEFAULT '', 'group_id' VARCHAR( 64 ) NOT NULL DEFAULT '', @@ -345,7 +339,7 @@ PRIMARY KEY ('user_group_id') ) "; }elseif($CONFIG_DBTYPE=='mysql'){ - $query="CREATE TABLE IF NOT EXISTS `$dbTableLocks` ( + $query="CREATE TABLE IF NOT EXISTS `{$CONFIG_DBTABLEPREFIX}locks` ( `token` varchar(255) NOT NULL DEFAULT '', `path` varchar(200) NOT NULL DEFAULT '', `created` int(11) NOT NULL DEFAULT '0', @@ -363,7 +357,7 @@ PRIMARY KEY ('user_group_id') KEY `expires` (`expires`) ); -CREATE TABLE IF NOT EXISTS `$dbTableLog` ( +CREATE TABLE IF NOT EXISTS `{$CONFIG_DBTABLEPREFIX}log` ( `id` int(11) NOT NULL AUTO_INCREMENT, `timestamp` int(11) NOT NULL, `user` varchar(250) NOT NULL, @@ -373,7 +367,7 @@ CREATE TABLE IF NOT EXISTS `$dbTableLog` ( ); -CREATE TABLE IF NOT EXISTS `$dbTableProperties` ( +CREATE TABLE IF NOT EXISTS `{$CONFIG_DBTABLEPREFIX}properties` ( `path` varchar(255) NOT NULL DEFAULT '', `name` varchar(120) NOT NULL DEFAULT '', `ns` varchar(120) NOT NULL DEFAULT 'DAV:', @@ -382,7 +376,7 @@ CREATE TABLE IF NOT EXISTS `$dbTableProperties` ( KEY `path` (`path`) ); -CREATE TABLE IF NOT EXISTS `$dbTableUsers` ( +CREATE TABLE IF NOT EXISTS `{$CONFIG_DBTABLEPREFIX}users` ( `user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `user_name` VARCHAR( 64 ) NOT NULL , `user_name_clean` VARCHAR( 64 ) NOT NULL , @@ -393,7 +387,7 @@ UNIQUE ( ) ); -CREATE TABLE IF NOT EXISTS `$dbTableGroups` ( +CREATE TABLE IF NOT EXISTS `{$CONFIG_DBTABLEPREFIX}groups` ( `group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `group_name` VARCHAR( 64 ) NOT NULL , UNIQUE ( @@ -401,7 +395,7 @@ UNIQUE ( ) ); -CREATE TABLE IF NOT EXISTS `$dbTableUserGroup` ( +CREATE TABLE IF NOT EXISTS `{$CONFIG_DBTABLEPREFIX}user_group` ( `user_group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `user_id` VARCHAR( 64 ) NOT NULL , `group_id` VARCHAR( 64 ) NOT NULL diff --git a/inc/lib_log.php b/inc/lib_log.php index 675f1c0b75..b6288eee04 100755 --- a/inc/lib_log.php +++ b/inc/lib_log.php @@ -49,8 +49,8 @@ class OC_LOG { */ public static function event($user, $type, $message){ global $CONFIG_DBTABLEPREFIX; - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; - $query = 'insert into ' . $dbTableLog . ' (timestamp,user,type,message) values + + $query = 'insert into ' . $CONFIG_DBTABLEPREFIX . 'log (timestamp,user,type,message) values ("' . time() . '","' . addslashes($user) . '","' . addslashes($type) . '","' . addslashes($message) . '")'; $result = OC_DB::query($query); OC_DB::free_result($result); @@ -66,12 +66,11 @@ class OC_LOG { global $CONFIG_DBTABLEPREFIX; echo('
'); - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'log'; if(OC_USER::ingroup($_SESSION['username_clean'],'admin')){ - $query = "select timestamp,user,type,message from $dbTableLog order by timestamp desc limit 20"; + $query = "select timestamp,user,type,message from {$CONFIG_DBTABLEPREFIX}log order by timestamp desc limit 20"; }else{ $user = $_SESSION['username_clean']; - $query = "select timestamp,user,type,message from $dbTableLog where user='$user' order by timestamp desc limit 20"; + $query = "select timestamp,user,type,message from {$CONFIG_DBTABLEPREFIX}log where user='$user' order by timestamp desc limit 20"; } $result = OC_DB::select($query); foreach($result as $entry){ diff --git a/inc/lib_ocs.php b/inc/lib_ocs.php index c2fe6dba33..e464d2ed9f 100755 --- a/inc/lib_ocs.php +++ b/inc/lib_ocs.php @@ -374,15 +374,14 @@ class OC_OCS { private static function activityget($format,$page,$pagesize) { global $CONFIG_DBTABLEPREFIX; - $dbTableLog = $CONFIG_DBTABLEPREFIX . 'Log'; $user=OC_OCS::checkpassword(); - $result = OC_DB::query("select count(*) as co from $dbTableLog"); + $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 $dbTableLog 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_user.php b/inc/lib_user.php index d3b38bc2ea..e8f6ce0357 100755 --- a/inc/lib_user.php +++ b/inc/lib_user.php @@ -64,8 +64,7 @@ class OC_USER { $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $query = "INSERT INTO `$dbTableUsers` (`user_id` ,`user_name` ,`user_name_clean` ,`user_password`) VALUES (NULL , '$username', '$usernameclean', '$password')"; + $query = "INSERT INTO `{$CONFIG_DBTABLEPREFIX}users` (`user_id` ,`user_name` ,`user_name_clean` ,`user_password`) VALUES (NULL , '$username', '$usernameclean', '$password')"; $result=OC_DB::query($query); return ($result)?true:false; } @@ -83,8 +82,7 @@ class OC_USER { $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $query = "SELECT user_id FROM $dbTableUsers 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']; @@ -126,8 +124,7 @@ class OC_USER { if(OC_USER::getgroupid($groupname)==0){ $groupname=OC_DB::escape($groupname); - $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; - $query="INSERT INTO `$dbTableGroups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')"; + $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}groups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')"; $result=OC_DB::query($query); return ($result)?true:false; }else{ @@ -145,8 +142,7 @@ class OC_USER { $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $query="SELECT user_id FROM $dbTableUsers 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; @@ -166,8 +162,7 @@ class OC_USER { global $CONFIG_DBTABLEPREFIX; $groupname=OC_DB::escape($groupname); - $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; - $query="SELECT group_id FROM $dbTableGroups 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; @@ -187,8 +182,7 @@ class OC_USER { global $CONFIG_DBTABLEPREFIX; $groupid=(integer)$groupid; - $dbTableGroups = $CONFIG_DBTABLEPREFIX . 'groups'; - $query="SELECT group_name FROM $dbTableGroups 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']; @@ -207,8 +201,7 @@ class OC_USER { $userid=OC_USER::getuserid($username); $groupid=OC_USER::getgroupid($groupname); if($groupid>0 and $userid>0){ - $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; - $query="SELECT user_group_id FROM $dbTableUserGroup WHERE group_id = $groupid AND user_id = $userid LIMIT 1"; + $query="SELECT user_group_id FROM {$CONFIG_DBTABLEPREFIX}user_group WHERE group_id = $groupid AND user_id = $userid LIMIT 1"; $result=OC_DB::select($query); if(isset($result[0]) && isset($result[0]['user_group_id'])){ return true; @@ -231,8 +224,7 @@ class OC_USER { $userid=OC_USER::getuserid($username); $groupid=OC_USER::getgroupid($groupname); if($groupid!=0 and $userid!=0){ - $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; - $query="INSERT INTO `$dbTableUserGroup` (`user_group_id` ,`user_id` ,`group_id`) VALUES (NULL , '$userid', '$groupid');"; + $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}user_group` (`user_group_id` ,`user_id` ,`group_id`) VALUES (NULL , '$userid', '$groupid');"; $result=OC_DB::query($query); if($result){ return true; @@ -259,8 +251,7 @@ class OC_USER { global $CONFIG_DBTABLEPREFIX; $userid=OC_USER::getuserid($username); - $dbTableUserGroup = $CONFIG_DBTABLEPREFIX . 'user_group'; - $query = "SELECT group_id FROM $dbTableUserGroup 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)){ @@ -281,8 +272,7 @@ class OC_USER { $password=sha1($password); $userid=OC_USER::getuserid($username); - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $query = "UPDATE $dbTableUsers 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; @@ -302,8 +292,7 @@ class OC_USER { $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $dbTableUsers = $CONFIG_DBTABLEPREFIX . 'users'; - $query = "SELECT user_id FROM '$dbTableUsers' 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; From d96793f14f810f9d6f1641922aad03cb84f4647f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 5 Jul 2010 11:37:50 +0200 Subject: [PATCH 17/24] Fix some database problems, do not use the cache when creating users --- inc/MDB2.php | 8 +- inc/MDB2/Driver/Function/mysql.php | 2 +- inc/MDB2/Driver/Function/pgsql.php | 228 +- inc/MDB2/Driver/Function/sqlite.php | 2 +- inc/MDB2/Driver/Manager/Common.php | 2026 ++++++++-------- inc/MDB2/Driver/Manager/mysql.php | 2862 +++++++++++----------- inc/MDB2/Driver/Manager/pgsql.php | 2 +- inc/MDB2/Driver/Manager/sqlite.php | 2 +- inc/MDB2/Driver/Reverse/Common.php | 1032 ++++---- inc/MDB2/Driver/Reverse/mysql.php | 2 +- inc/MDB2/Driver/Reverse/pgsql.php | 1144 ++++----- inc/MDB2/Driver/Reverse/sqlite.php | 2 +- inc/MDB2/Driver/mysql.php | 3398 +++++++++++++-------------- inc/lib_base.php | 12 +- inc/lib_config.php | 1 - inc/lib_user.php | 16 +- 16 files changed, 5370 insertions(+), 5369 deletions(-) diff --git a/inc/MDB2.php b/inc/MDB2.php index 94991bb0c4..aa4259b9db 100755 --- a/inc/MDB2.php +++ b/inc/MDB2.php @@ -329,11 +329,10 @@ class MDB2 { if (!MDB2::classExists($class_name)) { $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; -// echo $file_name; if ($debug) { $include = oc_include_once($file_name); } else { - $include = @oc_include_once($file_name); + $include = oc_include_once($file_name); } if (!$include) { if (!MDB2::fileExists($file_name)) { @@ -958,8 +957,12 @@ class MDB2 function fileExists($file) { // safe_mode does notwork with is_readable() + global $SERVERROOT; if (!@ini_get('safe_mode')) { $dirs = explode(PATH_SEPARATOR, ini_get('include_path')); + $dirs[]=$SERVERROOT; + $dirs[]=$SERVERROOT. DIRECTORY_SEPARATOR .'inc'; +// print_r($dirs);die(); foreach ($dirs as $dir) { if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) { return true; @@ -1920,7 +1923,6 @@ class MDB2_Driver_Common extends PEAR if (PEAR::isError($err)) { return $err; } - // load module in a specific version if ($version) { if (method_exists($class_name, 'getClassName')) { diff --git a/inc/MDB2/Driver/Function/mysql.php b/inc/MDB2/Driver/Function/mysql.php index b405c3ec02..aff531c9f3 100755 --- a/inc/MDB2/Driver/Function/mysql.php +++ b/inc/MDB2/Driver/Function/mysql.php @@ -45,7 +45,7 @@ // $Id: mysql.php,v 1.12 2008/02/17 18:54:08 quipo Exp $ // -require_once 'MDB2/Driver/Function/Common.php'; +oc_require_once('MDB2/Driver/Function/Common.php'); /** * MDB2 MySQL driver for the function modules diff --git a/inc/MDB2/Driver/Function/pgsql.php b/inc/MDB2/Driver/Function/pgsql.php index 99bb238082..cb47ea57d9 100755 --- a/inc/MDB2/Driver/Function/pgsql.php +++ b/inc/MDB2/Driver/Function/pgsql.php @@ -1,115 +1,115 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.11 2008/11/09 19:46:50 quipo Exp $ - -require_once 'MDB2/Driver/Function/Common.php'; - -/** - * MDB2 MySQL driver for the function modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Function_pgsql extends MDB2_Driver_Function_Common -{ - // {{{ executeStoredProc() - - /** - * Execute a stored procedure and return any results - * - * @param string $name string that identifies the function to execute - * @param mixed $params array that contains the paramaters to pass the stored proc - * @param mixed $types array that contains the types of the columns in - * the result set - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SELECT * FROM '.$name; - $query .= $params ? '('.implode(', ', $params).')' : '()'; - return $db->query($query, $types, $result_class, $result_wrap_class); - } - // }}} - // {{{ unixtimestamp() - - /** - * return string to call a function to get the unix timestamp from a iso timestamp - * - * @param string $expression - * - * @return string to call a variable with the timestamp - * @access public - */ - function unixtimestamp($expression) - { - return 'EXTRACT(EPOCH FROM DATE_TRUNC(\'seconds\', CAST ((' . $expression . ') AS TIMESTAMP)))'; - } - - // }}} - // {{{ random() - - /** - * return string to call a function to get random value inside an SQL statement - * - * @return return string to generate float between 0 and 1 - * @access public - */ - function random() - { - return 'RANDOM()'; - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.11 2008/11/09 19:46:50 quipo Exp $ + +oc_require_once('MDB2/Driver/Function/Common.php'); + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_pgsql extends MDB2_Driver_Function_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT * FROM '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + return 'EXTRACT(EPOCH FROM DATE_TRUNC(\'seconds\', CAST ((' . $expression . ') AS TIMESTAMP)))'; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'RANDOM()'; + } + + // }}} +} ?> \ No newline at end of file diff --git a/inc/MDB2/Driver/Function/sqlite.php b/inc/MDB2/Driver/Function/sqlite.php index 05fac65baf..f5499599dd 100755 --- a/inc/MDB2/Driver/Function/sqlite.php +++ b/inc/MDB2/Driver/Function/sqlite.php @@ -45,7 +45,7 @@ // $Id: sqlite.php,v 1.10 2008/02/17 18:54:08 quipo Exp $ // -require_once 'MDB2/Driver/Function/Common.php'; +oc_require_once('MDB2/Driver/Function/Common.php'); /** * MDB2 SQLite driver for the function modules diff --git a/inc/MDB2/Driver/Manager/Common.php b/inc/MDB2/Driver/Manager/Common.php index dd3d545e6c..d7154d4eac 100755 --- a/inc/MDB2/Driver/Manager/Common.php +++ b/inc/MDB2/Driver/Manager/Common.php @@ -1,1014 +1,1014 @@ - | -// | Lorenzo Alberton | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.72 2009/01/14 15:00:40 quipo Exp $ -// - -/** - * @package MDB2 - * @category Database - * @author Lukas Smith - * @author Lorenzo Alberton - */ - -/** - * Base class for the management modules that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Manager'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Manager_Common extends MDB2_Module_Common -{ - // {{{ splitTableSchema() - - /** - * Split the "[owner|schema].table" notation into an array - * - * @param string $table [schema and] table name - * - * @return array array(schema, table) - * @access private - */ - function splitTableSchema($table) - { - $ret = array(); - if (strpos($table, '.') !== false) { - return explode('.', $table); - } - return array(null, $table); - } - - // }}} - // {{{ getFieldDeclarationList() - - /** - * Get declaration of a number of field in bulk - * - * @param array $fields a multidimensional associative array. - * The first dimension determines the field name, while the second - * dimension is keyed with the name of the properties - * of the field being declared as array indexes. Currently, the types - * of supported field properties are as follows: - * - * default - * Boolean value to be used as default for this field. - * - * notnull - * Boolean flag that indicates whether this field is constrained - * to not be set to null. - * - * @return mixed string on success, a MDB2 error on failure - * @access public - */ - function getFieldDeclarationList($fields) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!is_array($fields) || empty($fields)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'missing any fields', __FUNCTION__); - } - foreach ($fields as $field_name => $field) { - $query = $db->getDeclaration($field['type'], $field_name, $field); - if (PEAR::isError($query)) { - return $query; - } - $query_fields[] = $query; - } - return implode(', ', $query_fields); - } - - // }}} - // {{{ _fixSequenceName() - - /** - * Removes any formatting in an sequence name using the 'seqname_format' option - * - * @param string $sqn string that containts name of a potential sequence - * @param bool $check if only formatted sequences should be returned - * @return string name of the sequence with possible formatting removed - * @access protected - */ - function _fixSequenceName($sqn, $check = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; - $seq_name = preg_replace($seq_pattern, '\\1', $sqn); - if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { - return $seq_name; - } - if ($check) { - return false; - } - return $sqn; - } - - // }}} - // {{{ _fixIndexName() - - /** - * Removes any formatting in an index name using the 'idxname_format' option - * - * @param string $idx string that containts name of anl index - * @return string name of the index with eventual formatting removed - * @access protected - */ - function _fixIndexName($idx) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; - $idx_name = preg_replace($idx_pattern, '\\1', $idx); - if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { - return $idx_name; - } - return $idx; - } - - // }}} - // {{{ createDatabase() - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createDatabase($database, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ alterDatabase() - - /** - * alter an existing database - * - * @param string $name name of the database that should be created - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function alterDatabase($database, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ dropDatabase() - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropDatabase($database) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ _getCreateTableQuery() - - /** - * Create a basic SQL query for a new table creation - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * @param array $options An associative array of table options - * - * @return mixed string (the SQL query) on success, a MDB2 error on failure - * @see createTable() - */ - function _getCreateTableQuery($name, $fields, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!$name) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - 'no valid table name specified', __FUNCTION__); - } - if (empty($fields)) { - return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, - 'no fields specified for table "'.$name.'"', __FUNCTION__); - } - $query_fields = $this->getFieldDeclarationList($fields); - if (PEAR::isError($query_fields)) { - return $query_fields; - } - if (!empty($options['primary'])) { - $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; - } - - $name = $db->quoteIdentifier($name, true); - $result = 'CREATE '; - if (!empty($options['temporary'])) { - $result .= $this->_getTemporaryTableQuery(); - } - $result .= " TABLE $name ($query_fields)"; - return $result; - } - - // }}} - // {{{ _getTemporaryTableQuery() - - /** - * A method to return the required SQL string that fits between CREATE ... TABLE - * to create the table as a temporary table. - * - * Should be overridden in driver classes to return the correct string for the - * specific database type. - * - * The default is to return the string "TEMPORARY" - this will result in a - * SQL error for any database that does not support temporary tables, or that - * requires a different SQL command from "CREATE TEMPORARY TABLE". - * - * @return string The string required to be placed between "CREATE" and "TABLE" - * to generate a temporary table, if possible. - */ - function _getTemporaryTableQuery() - { - return 'TEMPORARY'; - } - - // }}} - // {{{ createTable() - - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * The indexes of the array entries are the names of the fields of the table an - * the array entry values are associative arrays like those that are meant to be - * passed with the field definitions to get[Type]Declaration() functions. - * array( - * 'id' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * 'notnull' => 1 - * 'default' => 0 - * ), - * 'name' => array( - * 'type' => 'text', - * 'length' => 12 - * ), - * 'password' => array( - * 'type' => 'text', - * 'length' => 12 - * ) - * ); - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'temporary' => true|false, - * ); - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createTable($name, $fields, $options = array()) - { - $query = $this->_getCreateTableQuery($name, $fields, $options); - if (PEAR::isError($query)) { - return $query; - } - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $result = $db->exec($query); - if (PEAR::isError($result)) { - return $result; - } - return MDB2_OK; - } - - // }}} - // {{{ dropTable() - - /** - * drop an existing table - * - * @param string $name name of the table that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropTable($name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("DROP TABLE $name"); - } - - // }}} - // {{{ truncateTable() - - /** - * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, - * it falls back to a DELETE FROM TABLE query) - * - * @param string $name name of the table that should be truncated - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function truncateTable($name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("DELETE FROM $name"); - } - - // }}} - // {{{ vacuum() - - /** - * Optimize (vacuum) all the tables in the db (or only the specified table) - * and optionally run ANALYZE. - * - * @param string $table table name (all the tables if empty) - * @param array $options an array with driver-specific options: - * - timeout [int] (in seconds) [mssql-only] - * - analyze [boolean] [pgsql and mysql] - * - full [boolean] [pgsql-only] - * - freeze [boolean] [pgsql-only] - * - * @return mixed MDB2_OK success, a MDB2 error on failure - * @access public - */ - function vacuum($table = null, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ alterTable() - - /** - * alter an existing table - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @access public - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function alterTable($name, $changes, $check) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listDatabases() - - /** - * list all databases - * - * @return mixed array of database names on success, a MDB2 error on failure - * @access public - */ - function listDatabases() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implementedd', __FUNCTION__); - } - - // }}} - // {{{ listUsers() - - /** - * list all users - * - * @return mixed array of user names on success, a MDB2 error on failure - * @access public - */ - function listUsers() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listViews() - - /** - * list all views in the current database - * - * @param string database, the current is default - * NB: not all the drivers can get the view names from - * a database other than the current one - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listViews($database = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTableViews() - - /** - * list the views in the database that reference a given table - * - * @param string table for which all referenced views should be found - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listTableViews($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTableTriggers() - - /** - * list all triggers in the database that reference a given table - * - * @param string table for which all referenced triggers should be found - * @return mixed array of trigger names on success, a MDB2 error on failure - * @access public - */ - function listTableTriggers($table = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listFunctions() - - /** - * list all functions in the current database - * - * @return mixed array of function names on success, a MDB2 error on failure - * @access public - */ - function listFunctions() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTables() - - /** - * list all tables in the current database - * - * @param string database, the current is default. - * NB: not all the drivers can get the table names from - * a database other than the current one - * @return mixed array of table names on success, a MDB2 error on failure - * @access public - */ - function listTables($database = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listTableFields() - - /** - * list all fields in a table in the current database - * - * @param string $table name of table that should be used in method - * @return mixed array of field names on success, a MDB2 error on failure - * @access public - */ - function listTableFields($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ createIndex() - - /** - * Get the stucture of a field into an array - * - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the index fields as array - * indexes. Each entry of this array is set to another type of associative - * array that specifies properties of the index that are specific to - * each field. - * - * Currently, only the sorting property is supported. It should be used - * to define the sorting direction of the index. It may be set to either - * ascending or descending. - * - * Not all DBMS support index sorting direction configuration. The DBMS - * drivers of those that do not support it ignore this property. Use the - * function supports() to determine whether the DBMS driver can manage indexes. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array( - * 'sorting' => 'ascending' - * ), - * 'last_login' => array() - * ) - * ) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createIndex($table, $name, $definition) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "CREATE INDEX $name ON $table"; - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $fields) . ')'; - return $db->exec($query); - } - - // }}} - // {{{ dropIndex() - - /** - * drop existing index - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropIndex($table, $name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($db->getIndexName($name), true); - return $db->exec("DROP INDEX $name"); - } - - // }}} - // {{{ listTableIndexes() - - /** - * list all indexes in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of index names on success, a MDB2 error on failure - * @access public - */ - function listTableIndexes($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ _getAdvancedFKOptions() - - /** - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @access protected - */ - function _getAdvancedFKOptions($definition) - { - return ''; - } - - // }}} - // {{{ createConstraint() - - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * The full structure of the array looks like this: - *
-     *          array (
-     *              [primary] => 0
-     *              [unique]  => 0
-     *              [foreign] => 1
-     *              [check]   => 0
-     *              [fields] => array (
-     *                  [field1name] => array() // one entry per each field covered
-     *                  [field2name] => array() // by the index
-     *                  [field3name] => array(
-     *                      [sorting]  => ascending
-     *                      [position] => 3
-     *                  )
-     *              )
-     *              [references] => array(
-     *                  [table] => name
-     *                  [fields] => array(
-     *                      [field1name] => array(  //one entry per each referenced field
-     *                           [position] => 1
-     *                      )
-     *                  )
-     *              )
-     *              [deferrable] => 0
-     *              [initiallydeferred] => 0
-     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [match] => SIMPLE|PARTIAL|FULL
-     *          );
-     *          
- * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createConstraint($table, $name, $definition) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "ALTER TABLE $table ADD CONSTRAINT $name"; - if (!empty($definition['primary'])) { - $query.= ' PRIMARY KEY'; - } elseif (!empty($definition['unique'])) { - $query.= ' UNIQUE'; - } elseif (!empty($definition['foreign'])) { - $query.= ' FOREIGN KEY'; - } - $fields = array(); - foreach (array_keys($definition['fields']) as $field) { - $fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $fields) . ')'; - if (!empty($definition['foreign'])) { - $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); - $referenced_fields = array(); - foreach (array_keys($definition['references']['fields']) as $field) { - $referenced_fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $referenced_fields) . ')'; - $query .= $this->_getAdvancedFKOptions($definition); - } - return $db->exec($query); - } - - // }}} - // {{{ dropConstraint() - - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropConstraint($table, $name, $primary = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - return $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); - } - - // }}} - // {{{ listTableConstraints() - - /** - * list all constraints in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of constraint names on success, a MDB2 error on failure - * @access public - */ - function listTableConstraints($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ createSequence() - - /** - * create sequence - * - * @param string $seq_name name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createSequence($seq_name, $start = 1) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ dropSequence() - - /** - * drop existing sequence - * - * @param string $seq_name name of the sequence to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropSequence($name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ listSequences() - - /** - * list all sequences in the current database - * - * @param string database, the current is default - * NB: not all the drivers can get the sequence names from - * a database other than the current one - * @return mixed array of sequence names on success, a MDB2 error on failure - * @access public - */ - function listSequences($database = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} -} + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.72 2009/01/14 15:00:40 quipo Exp $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ + +/** + * Base class for the management modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Manager'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * + * @param string $table [schema and] table name + * + * @return array array(schema, table) + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getFieldDeclarationList() + + /** + * Get declaration of a number of field in bulk + * + * @param array $fields a multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return mixed string on success, a MDB2 error on failure + * @access public + */ + function getFieldDeclarationList($fields) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_array($fields) || empty($fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'missing any fields', __FUNCTION__); + } + foreach ($fields as $field_name => $field) { + $query = $db->getDeclaration($field['type'], $field_name, $field); + if (PEAR::isError($query)) { + return $query; + } + $query_fields[] = $query; + } + return implode(', ', $query_fields); + } + + // }}} + // {{{ _fixSequenceName() + + /** + * Removes any formatting in an sequence name using the 'seqname_format' option + * + * @param string $sqn string that containts name of a potential sequence + * @param bool $check if only formatted sequences should be returned + * @return string name of the sequence with possible formatting removed + * @access protected + */ + function _fixSequenceName($sqn, $check = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; + $seq_name = preg_replace($seq_pattern, '\\1', $sqn); + if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { + return $seq_name; + } + if ($check) { + return false; + } + return $sqn; + } + + // }}} + // {{{ _fixIndexName() + + /** + * Removes any formatting in an index name using the 'idxname_format' option + * + * @param string $idx string that containts name of anl index + * @return string name of the index with eventual formatting removed + * @access protected + */ + function _fixIndexName($idx) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; + $idx_name = preg_replace($idx_pattern, '\\1', $idx); + if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { + return $idx_name; + } + return $idx; + } + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($database, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($database, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($database) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'TEMPORARY'; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP TABLE $name"); + } + + // }}} + // {{{ truncateTable() + + /** + * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, + * it falls back to a DELETE FROM TABLE query) + * + * @param string $name name of the table that should be truncated + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function truncateTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DELETE FROM $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implementedd', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the view names from + * a database other than the current one + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default. + * NB: not all the drivers can get the table names from + * a database other than the current one + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + return ''; + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * The full structure of the array looks like this: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table ADD CONSTRAINT $name"; + if (!empty($definition['primary'])) { + $query.= ' PRIMARY KEY'; + } elseif (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $query.= ' FOREIGN KEY'; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the sequence names from + * a database other than the current one + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} +} ?> \ No newline at end of file diff --git a/inc/MDB2/Driver/Manager/mysql.php b/inc/MDB2/Driver/Manager/mysql.php index b37e346f40..7bd6a3623a 100755 --- a/inc/MDB2/Driver/Manager/mysql.php +++ b/inc/MDB2/Driver/Manager/mysql.php @@ -1,1432 +1,1432 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.113 2008/11/23 20:30:29 quipo Exp $ -// - -require_once 'MDB2/Driver/Manager/Common.php'; - -/** - * MDB2 MySQL driver for the management modules - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common -{ - - // }}} - // {{{ createDatabase() - - /** - * create a new database - * - * @param string $name name of the database that should be created - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createDatabase($name, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $query = 'CREATE DATABASE ' . $name; - if (!empty($options['charset'])) { - $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); - } - if (!empty($options['collation'])) { - $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); - } - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ alterDatabase() - - /** - * alter an existing database - * - * @param string $name name of the database that is intended to be changed - * @param array $options array with charset, collation info - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function alterDatabase($name, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true); - if (!empty($options['charset'])) { - $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); - } - if (!empty($options['collation'])) { - $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); - } - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ dropDatabase() - - /** - * drop an existing database - * - * @param string $name name of the database that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropDatabase($name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - $query = "DROP DATABASE $name"; - return $db->standaloneQuery($query, null, true); - } - - // }}} - // {{{ _getAdvancedFKOptions() - - /** - * Return the FOREIGN KEY query section dealing with non-standard options - * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... - * - * @param array $definition - * @return string - * @access protected - */ - function _getAdvancedFKOptions($definition) - { - $query = ''; - if (!empty($definition['match'])) { - $query .= ' MATCH '.$definition['match']; - } - if (!empty($definition['onupdate'])) { - $query .= ' ON UPDATE '.$definition['onupdate']; - } - if (!empty($definition['ondelete'])) { - $query .= ' ON DELETE '.$definition['ondelete']; - } - return $query; - } - - // }}} - // {{{ createTable() - - /** - * create a new table - * - * @param string $name Name of the database that should be created - * @param array $fields Associative array that contains the definition of each field of the new table - * The indexes of the array entries are the names of the fields of the table an - * the array entry values are associative arrays like those that are meant to be - * passed with the field definitions to get[Type]Declaration() functions. - * array( - * 'id' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * 'notnull' => 1 - * 'default' => 0 - * ), - * 'name' => array( - * 'type' => 'text', - * 'length' => 12 - * ), - * 'password' => array( - * 'type' => 'text', - * 'length' => 12 - * ) - * ); - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * 'type' => 'innodb', - * ); - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createTable($name, $fields, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - // if we have an AUTO_INCREMENT column and a PK on more than one field, - // we have to handle it differently... - $autoincrement = null; - if (empty($options['primary'])) { - $pk_fields = array(); - foreach ($fields as $fieldname => $def) { - if (!empty($def['primary'])) { - $pk_fields[$fieldname] = true; - } - if (!empty($def['autoincrement'])) { - $autoincrement = $fieldname; - } - } - if (!is_null($autoincrement) && count($pk_fields) > 1) { - $options['primary'] = $pk_fields; - } else { - // the PK constraint is on max one field => OK - $autoincrement = null; - } - } - - $query = $this->_getCreateTableQuery($name, $fields, $options); - if (PEAR::isError($query)) { - return $query; - } - - if (!is_null($autoincrement)) { - // we have to remove the PK clause added by _getIntegerDeclaration() - $query = str_replace('AUTO_INCREMENT PRIMARY KEY', 'AUTO_INCREMENT', $query); - } - - $options_strings = array(); - - if (!empty($options['comment'])) { - $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); - } - - if (!empty($options['charset'])) { - $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; - if (!empty($options['collate'])) { - $options_strings['charset'].= ' COLLATE '.$options['collate']; - } - } - - $type = false; - if (!empty($options['type'])) { - $type = $options['type']; - } elseif ($db->options['default_table_type']) { - $type = $db->options['default_table_type']; - } - if ($type) { - $options_strings[] = "ENGINE = $type"; - } - - if (!empty($options_strings)) { - $query .= ' '.implode(' ', $options_strings); - } - $result = $db->exec($query); - if (PEAR::isError($result)) { - return $result; - } - return MDB2_OK; - } - - // }}} - // {{{ dropTable() - - /** - * drop an existing table - * - * @param string $name name of the table that should be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropTable($name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - //delete the triggers associated to existing FK constraints - $constraints = $this->listTableConstraints($name); - if (!PEAR::isError($constraints) && !empty($constraints)) { - $db->loadModule('Reverse', null, true); - foreach ($constraints as $constraint) { - $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); - if (!PEAR::isError($definition) && !empty($definition['foreign'])) { - $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - - return parent::dropTable($name); - } - - // }}} - // {{{ truncateTable() - - /** - * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, - * it falls back to a DELETE FROM TABLE query) - * - * @param string $name name of the table that should be truncated - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function truncateTable($name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("TRUNCATE TABLE $name"); - } - - // }}} - // {{{ vacuum() - - /** - * Optimize (vacuum) all the tables in the db (or only the specified table) - * and optionally run ANALYZE. - * - * @param string $table table name (all the tables if empty) - * @param array $options an array with driver-specific options: - * - timeout [int] (in seconds) [mssql-only] - * - analyze [boolean] [pgsql and mysql] - * - full [boolean] [pgsql-only] - * - freeze [boolean] [pgsql-only] - * - * @return mixed MDB2_OK success, a MDB2 error on failure - * @access public - */ - function vacuum($table = null, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (empty($table)) { - $table = $this->listTables(); - if (PEAR::isError($table)) { - return $table; - } - } - if (is_array($table)) { - foreach (array_keys($table) as $k) { - $table[$k] = $db->quoteIdentifier($table[$k], true); - } - $table = implode(', ', $table); - } else { - $table = $db->quoteIdentifier($table, true); - } - - $result = $db->exec('OPTIMIZE TABLE '.$table); - if (PEAR::isError($result)) { - return $result; - } - if (!empty($options['analyze'])) { - return $db->exec('ANALYZE TABLE '.$table); - } - return MDB2_OK; - } - - // }}} - // {{{ alterTable() - - /** - * alter an existing table - * - * @param string $name name of the table that is intended to be changed. - * @param array $changes associative array that contains the details of each type - * of change that is intended to be performed. The types of - * changes that are currently supported are defined as follows: - * - * name - * - * New name for the table. - * - * add - * - * Associative array with the names of fields to be added as - * indexes of the array. The value of each entry of the array - * should be set to another associative array with the properties - * of the fields to be added. The properties of the fields should - * be the same as defined by the MDB2 parser. - * - * - * remove - * - * Associative array with the names of fields to be removed as indexes - * of the array. Currently the values assigned to each entry are ignored. - * An empty array should be used for future compatibility. - * - * rename - * - * Associative array with the names of fields to be renamed as indexes - * of the array. The value of each entry of the array should be set to - * another associative array with the entry named name with the new - * field name and the entry named Declaration that is expected to contain - * the portion of the field declaration already in DBMS specific SQL code - * as it is used in the CREATE TABLE statement. - * - * change - * - * Associative array with the names of the fields to be changed as indexes - * of the array. Keep in mind that if it is intended to change either the - * name of a field and any other properties, the change array entries - * should have the new names of the fields as array indexes. - * - * The value of each entry of the array should be set to another associative - * array with the properties of the fields to that are meant to be changed as - * array entries. These entries should be assigned to the new values of the - * respective properties. The properties of the fields should be the same - * as defined by the MDB2 parser. - * - * Example - * array( - * 'name' => 'userlist', - * 'add' => array( - * 'quota' => array( - * 'type' => 'integer', - * 'unsigned' => 1 - * ) - * ), - * 'remove' => array( - * 'file_limit' => array(), - * 'time_limit' => array() - * ), - * 'change' => array( - * 'name' => array( - * 'length' => '20', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 20, - * ), - * ) - * ), - * 'rename' => array( - * 'sex' => array( - * 'name' => 'gender', - * 'definition' => array( - * 'type' => 'text', - * 'length' => 1, - * 'default' => 'M', - * ), - * ) - * ) - * ) - * - * @param boolean $check indicates whether the function should just check if the DBMS driver - * can perform the requested table alterations if the value is true or - * actually perform them otherwise. - * @access public - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function alterTable($name, $changes, $check) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - foreach ($changes as $change_name => $change) { - switch ($change_name) { - case 'add': - case 'remove': - case 'change': - case 'rename': - case 'name': - break; - default: - return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, - 'change type "'.$change_name.'" not yet supported', __FUNCTION__); - } - } - - if ($check) { - return MDB2_OK; - } - - $query = ''; - if (!empty($changes['name'])) { - $change_name = $db->quoteIdentifier($changes['name'], true); - $query .= 'RENAME TO ' . $change_name; - } - - if (!empty($changes['add']) && is_array($changes['add'])) { - foreach ($changes['add'] as $field_name => $field) { - if ($query) { - $query.= ', '; - } - $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); - } - } - - if (!empty($changes['remove']) && is_array($changes['remove'])) { - foreach ($changes['remove'] as $field_name => $field) { - if ($query) { - $query.= ', '; - } - $field_name = $db->quoteIdentifier($field_name, true); - $query.= 'DROP ' . $field_name; - } - } - - $rename = array(); - if (!empty($changes['rename']) && is_array($changes['rename'])) { - foreach ($changes['rename'] as $field_name => $field) { - $rename[$field['name']] = $field_name; - } - } - - if (!empty($changes['change']) && is_array($changes['change'])) { - foreach ($changes['change'] as $field_name => $field) { - if ($query) { - $query.= ', '; - } - if (isset($rename[$field_name])) { - $old_field_name = $rename[$field_name]; - unset($rename[$field_name]); - } else { - $old_field_name = $field_name; - } - $old_field_name = $db->quoteIdentifier($old_field_name, true); - $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); - } - } - - if (!empty($rename) && is_array($rename)) { - foreach ($rename as $rename_name => $renamed_field) { - if ($query) { - $query.= ', '; - } - $field = $changes['rename'][$renamed_field]; - $renamed_field = $db->quoteIdentifier($renamed_field, true); - $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); - } - } - - if (!$query) { - return MDB2_OK; - } - - $name = $db->quoteIdentifier($name, true); - return $db->exec("ALTER TABLE $name $query"); - } - - // }}} - // {{{ listDatabases() - - /** - * list all databases - * - * @return mixed array of database names on success, a MDB2 error on failure - * @access public - */ - function listDatabases() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->queryCol('SHOW DATABASES'); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listUsers() - - /** - * list all users - * - * @return mixed array of user names on success, a MDB2 error on failure - * @access public - */ - function listUsers() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER'); - } - - // }}} - // {{{ listFunctions() - - /** - * list all functions in the current database - * - * @return mixed array of function names on success, a MDB2 error on failure - * @access public - */ - function listFunctions() - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT name FROM mysql.proc"; - /* - SELECT ROUTINE_NAME - FROM INFORMATION_SCHEMA.ROUTINES - WHERE ROUTINE_TYPE = 'FUNCTION' - */ - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableTriggers() - - /** - * list all triggers in the database that reference a given table - * - * @param string table for which all referenced triggers should be found - * @return mixed array of trigger names on success, a MDB2 error on failure - * @access public - */ - function listTableTriggers($table = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SHOW TRIGGERS'; - if (!is_null($table)) { - $table = $db->quote($table, 'text'); - $query .= " LIKE $table"; - } - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTables() - - /** - * list all tables in the current database - * - * @param string database, the current is default - * @return mixed array of table names on success, a MDB2 error on failure - * @access public - */ - function listTables($database = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SHOW /*!50002 FULL*/ TABLES"; - if (!is_null($database)) { - $query .= " FROM $database"; - } - $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; - - $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); - if (PEAR::isError($table_names)) { - return $table_names; - } - - $result = array(); - foreach ($table_names as $table) { - if (!$this->_fixSequenceName($table[0], true)) { - $result[] = $table[0]; - } - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listViews() - - /** - * list all views in the current database - * - * @param string database, the current is default - * @return mixed array of view names on success, a MDB2 error on failure - * @access public - */ - function listViews($database = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = 'SHOW FULL TABLES'; - if (!is_null($database)) { - $query.= " FROM $database"; - } - $query.= " WHERE Table_type = 'VIEW'"; - - $result = $db->queryCol($query); - if (PEAR::isError($result)) { - return $result; - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ listTableFields() - - /** - * list all fields in a table in the current database - * - * @param string $table name of table that should be used in method - * @return mixed array of field names on success, a MDB2 error on failure - * @access public - */ - function listTableFields($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $result = $db->queryCol("SHOW COLUMNS FROM $table"); - if (PEAR::isError($result)) { - return $result; - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} - // {{{ createIndex() - - /** - * Get the stucture of a field into an array - * - * @author Leoncx - * @param string $table name of the table on which the index is to be created - * @param string $name name of the index to be created - * @param array $definition associative array that defines properties of the index to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the index fields as array - * indexes. Each entry of this array is set to another type of associative - * array that specifies properties of the index that are specific to - * each field. - * - * Currently, only the sorting property is supported. It should be used - * to define the sorting direction of the index. It may be set to either - * ascending or descending. - * - * Not all DBMS support index sorting direction configuration. The DBMS - * drivers of those that do not support it ignore this property. Use the - * function supports() to determine whether the DBMS driver can manage indexes. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array( - * 'sorting' => 'ascending' - * 'length' => 10 - * ), - * 'last_login' => array() - * ) - * ) - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createIndex($table, $name, $definition) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "CREATE INDEX $name ON $table"; - $fields = array(); - foreach ($definition['fields'] as $field => $fieldinfo) { - if (!empty($fieldinfo['length'])) { - $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; - } else { - $fields[] = $db->quoteIdentifier($field, true); - } - } - $query .= ' ('. implode(', ', $fields) . ')'; - return $db->exec($query); - } - - // }}} - // {{{ dropIndex() - - /** - * drop existing index - * - * @param string $table name of table that should be used in method - * @param string $name name of the index to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropIndex($table, $name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - return $db->exec("DROP INDEX $name ON $table"); - } - - // }}} - // {{{ listTableIndexes() - - /** - * list all indexes in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of index names on success, a MDB2 error on failure - * @access public - */ - function listTableIndexes($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $key_name = 'Key_name'; - $non_unique = 'Non_unique'; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $key_name = strtolower($key_name); - $non_unique = strtolower($non_unique); - } else { - $key_name = strtoupper($key_name); - $non_unique = strtoupper($non_unique); - } - } - - $table = $db->quoteIdentifier($table, true); - $query = "SHOW INDEX FROM $table"; - $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $index_data) { - if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { - $result[$index] = true; - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createConstraint() - - /** - * create a constraint on a table - * - * @param string $table name of the table on which the constraint is to be created - * @param string $name name of the constraint to be created - * @param array $definition associative array that defines properties of the constraint to be created. - * Currently, only one property named FIELDS is supported. This property - * is also an associative with the names of the constraint fields as array - * constraints. Each entry of this array is set to another type of associative - * array that specifies properties of the constraint that are specific to - * each field. - * - * Example - * array( - * 'fields' => array( - * 'user_name' => array(), - * 'last_login' => array() - * ) - * ) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createConstraint($table, $name, $definition) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $type = ''; - $idx_name = $db->quoteIdentifier($db->getIndexName($name), true); - if (!empty($definition['primary'])) { - $type = 'PRIMARY'; - $idx_name = 'KEY'; - } elseif (!empty($definition['unique'])) { - $type = 'UNIQUE'; - } elseif (!empty($definition['foreign'])) { - $type = 'CONSTRAINT'; - } - if (empty($type)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'invalid definition, could not create constraint', __FUNCTION__); - } - - $table_quoted = $db->quoteIdentifier($table, true); - $query = "ALTER TABLE $table_quoted ADD $type $idx_name"; - if (!empty($definition['foreign'])) { - $query .= ' FOREIGN KEY'; - } - $fields = array(); - foreach ($definition['fields'] as $field => $fieldinfo) { - $quoted = $db->quoteIdentifier($field, true); - if (!empty($fieldinfo['length'])) { - $quoted .= '(' . $fieldinfo['length'] . ')'; - } - $fields[] = $quoted; - } - $query .= ' ('. implode(', ', $fields) . ')'; - if (!empty($definition['foreign'])) { - $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); - $referenced_fields = array(); - foreach (array_keys($definition['references']['fields']) as $field) { - $referenced_fields[] = $db->quoteIdentifier($field, true); - } - $query .= ' ('. implode(', ', $referenced_fields) . ')'; - $query .= $this->_getAdvancedFKOptions($definition); - - // add index on FK column(s) or we can't add a FK constraint - // @see http://forums.mysql.com/read.php?22,19755,226009 - $result = $this->createIndex($table, $name.'_fkidx', $definition); - if (PEAR::isError($result)) { - return $result; - } - } - $res = $db->exec($query); - if (PEAR::isError($res)) { - return $res; - } - if (!empty($definition['foreign'])) { - return $this->_createFKTriggers($table, array($name => $definition)); - } - return MDB2_OK; - } - - // }}} - // {{{ dropConstraint() - - /** - * drop existing constraint - * - * @param string $table name of table that should be used in method - * @param string $name name of the constraint to be dropped - * @param string $primary hint if the constraint is primary - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropConstraint($table, $name, $primary = false) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if ($primary || strtolower($name) == 'primary') { - $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY'; - return $db->exec($query); - } - - //is it a FK constraint? If so, also delete the associated triggers - $db->loadModule('Reverse', null, true); - $definition = $db->reverse->getTableConstraintDefinition($table, $name); - if (!PEAR::isError($definition) && !empty($definition['foreign'])) { - //first drop the FK enforcing triggers - $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); - if (PEAR::isError($result)) { - return $result; - } - //then drop the constraint itself - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "ALTER TABLE $table DROP FOREIGN KEY $name"; - return $db->exec($query); - } - - $table = $db->quoteIdentifier($table, true); - $name = $db->quoteIdentifier($db->getIndexName($name), true); - $query = "ALTER TABLE $table DROP INDEX $name"; - return $db->exec($query); - } - - // }}} - // {{{ _createFKTriggers() - - /** - * Create triggers to enforce the FOREIGN KEY constraint on the table - * - * NB: since there's no RAISE_APPLICATION_ERROR facility in mysql, - * we call a non-existent procedure to raise the FK violation message. - * @see http://forums.mysql.com/read.php?99,55108,71877#msg-71877 - * - * @param string $table table name - * @param array $foreign_keys FOREIGN KEY definitions - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access private - */ - function _createFKTriggers($table, $foreign_keys) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - // create triggers to enforce FOREIGN KEY constraints - if ($db->supports('triggers') && !empty($foreign_keys)) { - $table_quoted = $db->quoteIdentifier($table, true); - foreach ($foreign_keys as $fkname => $fkdef) { - if (empty($fkdef)) { - continue; - } - //set actions to default if not set - $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); - $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); - - $trigger_names = array( - 'insert' => $fkname.'_insert_trg', - 'update' => $fkname.'_update_trg', - 'pk_update' => $fkname.'_pk_update_trg', - 'pk_delete' => $fkname.'_pk_delete_trg', - ); - $table_fields = array_keys($fkdef['fields']); - $referenced_fields = array_keys($fkdef['references']['fields']); - - //create the ON [UPDATE|DELETE] triggers on the primary table - $restrict_action = ' IF (SELECT '; - $aliased_fields = array(); - foreach ($table_fields as $field) { - $aliased_fields[] = $table_quoted .'.'.$field .' AS '.$field; - } - $restrict_action .= implode(',', $aliased_fields) - .' FROM '.$table_quoted - .' WHERE '; - $conditions = array(); - $new_values = array(); - $null_values = array(); - for ($i=0; $i OLD.'.$referenced_fields[$i]; - } - $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' - .' AND (' .implode(' OR ', $conditions2) .')' - .' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();' - .' END IF;'; - - $cascade_action_update = 'UPDATE '.$table_quoted.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';'; - $cascade_action_delete = 'DELETE FROM '.$table_quoted.' WHERE '.implode(' AND ', $conditions). ';'; - $setnull_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';'; - - if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { - $db->loadModule('Reverse', null, true); - $default_values = array(); - foreach ($table_fields as $table_field) { - $field_definition = $db->reverse->getTableFieldDefinition($table, $field); - if (PEAR::isError($field_definition)) { - return $field_definition; - } - $default_values[] = $table_field .' = '. $field_definition[0]['default']; - } - $setdefault_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';'; - } - - $query = 'CREATE TRIGGER %s' - .' %s ON '.$fkdef['references']['table'] - .' FOR EACH ROW BEGIN ' - .' SET FOREIGN_KEY_CHECKS = 0; '; //only really needed for ON UPDATE CASCADE - - if ('CASCADE' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $cascade_action_update; - } elseif ('SET NULL' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action; - } elseif ('SET DEFAULT' == $fkdef['onupdate']) { - $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action; - } elseif ('NO ACTION' == $fkdef['onupdate']) { - $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update'); - } elseif ('RESTRICT' == $fkdef['onupdate']) { - $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update'); - } - if ('CASCADE' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $cascade_action_delete; - } elseif ('SET NULL' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action; - } elseif ('SET DEFAULT' == $fkdef['ondelete']) { - $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action; - } elseif ('NO ACTION' == $fkdef['ondelete']) { - $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete'); - } elseif ('RESTRICT' == $fkdef['ondelete']) { - $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete'); - } - $sql_update .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; - $sql_delete .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; - - $db->pushErrorHandling(PEAR_ERROR_RETURN); - $db->expectError(MDB2_ERROR_CANNOT_CREATE); - $result = $db->exec($sql_delete); - $expected_errmsg = 'This MySQL version doesn\'t support multiple triggers with the same action time and event for one table'; - $db->popExpect(); - $db->popErrorHandling(); - if (PEAR::isError($result)) { - if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { - return $result; - } - $db->warnings[] = $expected_errmsg; - } - $db->pushErrorHandling(PEAR_ERROR_RETURN); - $db->expectError(MDB2_ERROR_CANNOT_CREATE); - $result = $db->exec($sql_update); - $db->popExpect(); - $db->popErrorHandling(); - if (PEAR::isError($result) && $result->getCode() != MDB2_ERROR_CANNOT_CREATE) { - if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { - return $result; - } - $db->warnings[] = $expected_errmsg; - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ _dropFKTriggers() - - /** - * Drop the triggers created to enforce the FOREIGN KEY constraint on the table - * - * @param string $table table name - * @param string $fkname FOREIGN KEY constraint name - * @param string $referenced_table referenced table name - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access private - */ - function _dropFKTriggers($table, $fkname, $referenced_table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $triggers = $this->listTableTriggers($table); - $triggers2 = $this->listTableTriggers($referenced_table); - if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { - $triggers = array_merge($triggers, $triggers2); - $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; - foreach ($triggers as $trigger) { - if (preg_match($pattern, $trigger)) { - $result = $db->exec('DROP TRIGGER '.$trigger); - if (PEAR::isError($result)) { - return $result; - } - } - } - } - return MDB2_OK; - } - - // }}} - // {{{ listTableConstraints() - - /** - * list all constraints in a table - * - * @param string $table name of table that should be used in method - * @return mixed array of constraint names on success, a MDB2 error on failure - * @access public - */ - function listTableConstraints($table) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $key_name = 'Key_name'; - $non_unique = 'Non_unique'; - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $key_name = strtolower($key_name); - $non_unique = strtolower($non_unique); - } else { - $key_name = strtoupper($key_name); - $non_unique = strtoupper($non_unique); - } - } - - $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true); - $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($indexes)) { - return $indexes; - } - - $result = array(); - foreach ($indexes as $index_data) { - if (!$index_data[$non_unique]) { - if ($index_data[$key_name] !== 'PRIMARY') { - $index = $this->_fixIndexName($index_data[$key_name]); - } else { - $index = 'PRIMARY'; - } - if (!empty($index)) { - $result[$index] = true; - } - } - } - - //list FOREIGN KEY constraints... - $query = 'SHOW CREATE TABLE '. $db->escape($table); - $definition = $db->queryOne($query, 'text', 1); - if (!PEAR::isError($definition) && !empty($definition)) { - $pattern = '/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN KEY\b/Uims'; - if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 0) { - foreach ($matches[1] as $constraint) { - $result[$constraint] = true; - } - } - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_change_key_case($result, $db->options['field_case']); - } - return array_keys($result); - } - - // }}} - // {{{ createSequence() - - /** - * create sequence - * - * @param string $seq_name name of the sequence to be created - * @param string $start start value of the sequence; default is 1 - * @param array $options An associative array of table options: - * array( - * 'comment' => 'Foo', - * 'charset' => 'utf8', - * 'collate' => 'utf8_unicode_ci', - * 'type' => 'innodb', - * ); - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function createSequence($seq_name, $start = 1, $options = array()) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); - - $options_strings = array(); - - if (!empty($options['comment'])) { - $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); - } - - if (!empty($options['charset'])) { - $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; - if (!empty($options['collate'])) { - $options_strings['charset'].= ' COLLATE '.$options['collate']; - } - } - - $type = false; - if (!empty($options['type'])) { - $type = $options['type']; - } elseif ($db->options['default_table_type']) { - $type = $db->options['default_table_type']; - } - if ($type) { - $options_strings[] = "ENGINE = $type"; - } - - $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; - if (!empty($options_strings)) { - $query .= ' '.implode(' ', $options_strings); - } - $res = $db->exec($query); - if (PEAR::isError($res)) { - return $res; - } - - if ($start == 1) { - return MDB2_OK; - } - - $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; - $res = $db->exec($query); - if (!PEAR::isError($res)) { - return MDB2_OK; - } - - // Handle error - $result = $db->exec("DROP TABLE $sequence_name"); - if (PEAR::isError($result)) { - return $db->raiseError($result, null, null, - 'could not drop inconsistent sequence table', __FUNCTION__); - } - - return $db->raiseError($res, null, null, - 'could not create sequence table', __FUNCTION__); - } - - // }}} - // {{{ dropSequence() - - /** - * drop existing sequence - * - * @param string $seq_name name of the sequence to be dropped - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function dropSequence($seq_name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); - return $db->exec("DROP TABLE $sequence_name"); - } - - // }}} - // {{{ listSequences() - - /** - * list all sequences in the current database - * - * @param string database, the current is default - * @return mixed array of sequence names on success, a MDB2 error on failure - * @access public - */ - function listSequences($database = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SHOW TABLES"; - if (!is_null($database)) { - $query .= " FROM $database"; - } - $table_names = $db->queryCol($query); - if (PEAR::isError($table_names)) { - return $table_names; - } - - $result = array(); - foreach ($table_names as $table_name) { - if ($sqn = $this->_fixSequenceName($table_name, true)) { - $result[] = $sqn; - } - } - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); - } - return $result; - } - - // }}} -} + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.113 2008/11/23 20:30:29 quipo Exp $ +// + +oc_require_once('MDB2/Driver/Manager/Common.php'); + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common +{ + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = 'CREATE DATABASE ' . $name; + if (!empty($options['charset'])) { + $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); + } + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * @param string $name name of the database that is intended to be changed + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($name, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true); + if (!empty($options['charset'])) { + $query .= ' DEFAULT CHARACTER SET ' . $db->quote($options['charset'], 'text'); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $db->quote($options['collation'], 'text'); + } + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "DROP DATABASE $name"; + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + $query = ''; + if (!empty($definition['match'])) { + $query .= ' MATCH '.$definition['match']; + } + if (!empty($definition['onupdate'])) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete'])) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + return $query; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + // if we have an AUTO_INCREMENT column and a PK on more than one field, + // we have to handle it differently... + $autoincrement = null; + if (empty($options['primary'])) { + $pk_fields = array(); + foreach ($fields as $fieldname => $def) { + if (!empty($def['primary'])) { + $pk_fields[$fieldname] = true; + } + if (!empty($def['autoincrement'])) { + $autoincrement = $fieldname; + } + } + if (!is_null($autoincrement) && count($pk_fields) > 1) { + $options['primary'] = $pk_fields; + } else { + // the PK constraint is on max one field => OK + $autoincrement = null; + } + } + + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + + if (!is_null($autoincrement)) { + // we have to remove the PK clause added by _getIntegerDeclaration() + $query = str_replace('AUTO_INCREMENT PRIMARY KEY', 'AUTO_INCREMENT', $query); + } + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + //delete the triggers associated to existing FK constraints + $constraints = $this->listTableConstraints($name); + if (!PEAR::isError($constraints) && !empty($constraints)) { + $db->loadModule('Reverse', null, true); + foreach ($constraints as $constraint) { + $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + + return parent::dropTable($name); + } + + // }}} + // {{{ truncateTable() + + /** + * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, + * it falls back to a DELETE FROM TABLE query) + * + * @param string $name name of the table that should be truncated + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function truncateTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("TRUNCATE TABLE $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($table)) { + $table = $this->listTables(); + if (PEAR::isError($table)) { + return $table; + } + } + if (is_array($table)) { + foreach (array_keys($table) as $k) { + $table[$k] = $db->quoteIdentifier($table[$k], true); + } + $table = implode(', ', $table); + } else { + $table = $db->quoteIdentifier($table, true); + } + + $result = $db->exec('OPTIMIZE TABLE '.$table); + if (PEAR::isError($result)) { + return $result; + } + if (!empty($options['analyze'])) { + return $db->exec('ANALYZE TABLE '.$table); + } + return MDB2_OK; + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $query = ''; + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $query .= 'RENAME TO ' . $change_name; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'DROP ' . $field_name; + } + } + + $rename = array(); + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $rename[$field['name']] = $field_name; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$field_name])) { + $old_field_name = $rename[$field_name]; + unset($rename[$field_name]); + } else { + $old_field_name = $field_name; + } + $old_field_name = $db->quoteIdentifier($old_field_name, true); + $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + } + + if (!empty($rename) && is_array($rename)) { + foreach ($rename as $rename_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamed_field]; + $renamed_field = $db->quoteIdentifier($renamed_field, true); + $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); + } + } + + if (!$query) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("ALTER TABLE $name $query"); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SHOW DATABASES'); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER'); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM mysql.proc"; + /* + SELECT ROUTINE_NAME + FROM INFORMATION_SCHEMA.ROUTINES + WHERE ROUTINE_TYPE = 'FUNCTION' + */ + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW TRIGGERS'; + if (!is_null($table)) { + $table = $db->quote($table, 'text'); + $query .= " LIKE $table"; + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW /*!50002 FULL*/ TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; + + $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table) { + if (!$this->_fixSequenceName($table[0], true)) { + $result[] = $table[0]; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW FULL TABLES'; + if (!is_null($database)) { + $query.= " FROM $database"; + } + $query.= " WHERE Table_type = 'VIEW'"; + + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $result = $db->queryCol("SHOW COLUMNS FROM $table"); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @author Leoncx + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * 'length' => 10 + * ), + * 'last_login' => array() + * ) + * ) + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + if (!empty($fieldinfo['length'])) { + $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; + } else { + $fields[] = $db->quoteIdentifier($field, true); + } + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name ON $table"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { + $result[$index] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $type = ''; + $idx_name = $db->quoteIdentifier($db->getIndexName($name), true); + if (!empty($definition['primary'])) { + $type = 'PRIMARY'; + $idx_name = 'KEY'; + } elseif (!empty($definition['unique'])) { + $type = 'UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $type = 'CONSTRAINT'; + } + if (empty($type)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'invalid definition, could not create constraint', __FUNCTION__); + } + + $table_quoted = $db->quoteIdentifier($table, true); + $query = "ALTER TABLE $table_quoted ADD $type $idx_name"; + if (!empty($definition['foreign'])) { + $query .= ' FOREIGN KEY'; + } + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + $quoted = $db->quoteIdentifier($field, true); + if (!empty($fieldinfo['length'])) { + $quoted .= '(' . $fieldinfo['length'] . ')'; + } + $fields[] = $quoted; + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + + // add index on FK column(s) or we can't add a FK constraint + // @see http://forums.mysql.com/read.php?22,19755,226009 + $result = $this->createIndex($table, $name.'_fkidx', $definition); + if (PEAR::isError($result)) { + return $result; + } + } + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + if (!empty($definition['foreign'])) { + return $this->_createFKTriggers($table, array($name => $definition)); + } + return MDB2_OK; + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($primary || strtolower($name) == 'primary') { + $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY'; + return $db->exec($query); + } + + //is it a FK constraint? If so, also delete the associated triggers + $db->loadModule('Reverse', null, true); + $definition = $db->reverse->getTableConstraintDefinition($table, $name); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + //first drop the FK enforcing triggers + $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + //then drop the constraint itself + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP FOREIGN KEY $name"; + return $db->exec($query); + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP INDEX $name"; + return $db->exec($query); + } + + // }}} + // {{{ _createFKTriggers() + + /** + * Create triggers to enforce the FOREIGN KEY constraint on the table + * + * NB: since there's no RAISE_APPLICATION_ERROR facility in mysql, + * we call a non-existent procedure to raise the FK violation message. + * @see http://forums.mysql.com/read.php?99,55108,71877#msg-71877 + * + * @param string $table table name + * @param array $foreign_keys FOREIGN KEY definitions + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _createFKTriggers($table, $foreign_keys) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + // create triggers to enforce FOREIGN KEY constraints + if ($db->supports('triggers') && !empty($foreign_keys)) { + $table_quoted = $db->quoteIdentifier($table, true); + foreach ($foreign_keys as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + //set actions to default if not set + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); + $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); + + $trigger_names = array( + 'insert' => $fkname.'_insert_trg', + 'update' => $fkname.'_update_trg', + 'pk_update' => $fkname.'_pk_update_trg', + 'pk_delete' => $fkname.'_pk_delete_trg', + ); + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + + //create the ON [UPDATE|DELETE] triggers on the primary table + $restrict_action = ' IF (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $table_quoted .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$table_quoted + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $i OLD.'.$referenced_fields[$i]; + } + $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' + .' AND (' .implode(' OR ', $conditions2) .')' + .' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();' + .' END IF;'; + + $cascade_action_update = 'UPDATE '.$table_quoted.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';'; + $cascade_action_delete = 'DELETE FROM '.$table_quoted.' WHERE '.implode(' AND ', $conditions). ';'; + $setnull_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';'; + + if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { + $db->loadModule('Reverse', null, true); + $default_values = array(); + foreach ($table_fields as $table_field) { + $field_definition = $db->reverse->getTableFieldDefinition($table, $field); + if (PEAR::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$table_quoted.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';'; + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW BEGIN ' + .' SET FOREIGN_KEY_CHECKS = 0; '; //only really needed for ON UPDATE CASCADE + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $cascade_action_update; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action; + } elseif ('NO ACTION' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update'); + } elseif ('RESTRICT' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update'); + } + if ('CASCADE' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $cascade_action_delete; + } elseif ('SET NULL' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action; + } elseif ('SET DEFAULT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action; + } elseif ('NO ACTION' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete'); + } elseif ('RESTRICT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete'); + } + $sql_update .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; + $sql_delete .= ' SET FOREIGN_KEY_CHECKS = 1; END;'; + + $db->pushErrorHandling(PEAR_ERROR_RETURN); + $db->expectError(MDB2_ERROR_CANNOT_CREATE); + $result = $db->exec($sql_delete); + $expected_errmsg = 'This MySQL version doesn\'t support multiple triggers with the same action time and event for one table'; + $db->popExpect(); + $db->popErrorHandling(); + if (PEAR::isError($result)) { + if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { + return $result; + } + $db->warnings[] = $expected_errmsg; + } + $db->pushErrorHandling(PEAR_ERROR_RETURN); + $db->expectError(MDB2_ERROR_CANNOT_CREATE); + $result = $db->exec($sql_update); + $db->popExpect(); + $db->popErrorHandling(); + if (PEAR::isError($result) && $result->getCode() != MDB2_ERROR_CANNOT_CREATE) { + if ($result->getCode() != MDB2_ERROR_CANNOT_CREATE) { + return $result; + } + $db->warnings[] = $expected_errmsg; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ _dropFKTriggers() + + /** + * Drop the triggers created to enforce the FOREIGN KEY constraint on the table + * + * @param string $table table name + * @param string $fkname FOREIGN KEY constraint name + * @param string $referenced_table referenced table name + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropFKTriggers($table, $fkname, $referenced_table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $triggers = $this->listTableTriggers($table); + $triggers2 = $this->listTableTriggers($referenced_table); + if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { + $triggers = array_merge($triggers, $triggers2); + $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; + foreach ($triggers as $trigger) { + if (preg_match($pattern, $trigger)) { + $result = $db->exec('DROP TRIGGER '.$trigger); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true); + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if (!$index_data[$non_unique]) { + if ($index_data[$key_name] !== 'PRIMARY') { + $index = $this->_fixIndexName($index_data[$key_name]); + } else { + $index = 'PRIMARY'; + } + if (!empty($index)) { + $result[$index] = true; + } + } + } + + //list FOREIGN KEY constraints... + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $definition = $db->queryOne($query, 'text', 1); + if (!PEAR::isError($definition) && !empty($definition)) { + $pattern = '/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN KEY\b/Uims'; + if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 0) { + foreach ($matches[1] as $constraint) { + $result[$constraint] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + + if ($start == 1) { + return MDB2_OK; + } + + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; + $res = $db->exec($query); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} ?> \ No newline at end of file diff --git a/inc/MDB2/Driver/Manager/pgsql.php b/inc/MDB2/Driver/Manager/pgsql.php index 3f9c121a50..1a7e851897 100755 --- a/inc/MDB2/Driver/Manager/pgsql.php +++ b/inc/MDB2/Driver/Manager/pgsql.php @@ -44,7 +44,7 @@ // // $Id: pgsql.php,v 1.87 2008/11/29 14:09:59 afz Exp $ -require_once 'MDB2/Driver/Manager/Common.php'; +oc_require_once('MDB2/Driver/Manager/Common.php'); /** * MDB2 MySQL driver for the management modules diff --git a/inc/MDB2/Driver/Manager/sqlite.php b/inc/MDB2/Driver/Manager/sqlite.php index a0f899e788..85751d39a3 100755 --- a/inc/MDB2/Driver/Manager/sqlite.php +++ b/inc/MDB2/Driver/Manager/sqlite.php @@ -46,7 +46,7 @@ // $Id: sqlite.php,v 1.76 2008/05/31 11:48:48 quipo Exp $ // -require_once 'MDB2/Driver/Manager/Common.php'; +oc_require_once('MDB2/Driver/Manager/Common.php'); /** * MDB2 SQLite driver for the management modules diff --git a/inc/MDB2/Driver/Reverse/Common.php b/inc/MDB2/Driver/Reverse/Common.php index 7ce9c74426..4a411fea6d 100755 --- a/inc/MDB2/Driver/Reverse/Common.php +++ b/inc/MDB2/Driver/Reverse/Common.php @@ -1,517 +1,517 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: Common.php,v 1.43 2009/01/14 15:01:21 quipo Exp $ -// - -/** - * @package MDB2 - * @category Database - */ - -/** - * These are constants for the tableInfo-function - * they are bitwised or'ed. so if there are more constants to be defined - * in the future, adjust MDB2_TABLEINFO_FULL accordingly - */ - -define('MDB2_TABLEINFO_ORDER', 1); -define('MDB2_TABLEINFO_ORDERTABLE', 2); -define('MDB2_TABLEINFO_FULL', 3); - -/** - * Base class for the schema reverse engineering module that is extended by each MDB2 driver - * - * To load this module in the MDB2 object: - * $mdb->loadModule('Reverse'); - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_Reverse_Common extends MDB2_Module_Common -{ - // {{{ splitTableSchema() - - /** - * Split the "[owner|schema].table" notation into an array - * - * @param string $table [schema and] table name - * - * @return array array(schema, table) - * @access private - */ - function splitTableSchema($table) - { - $ret = array(); - if (strpos($table, '.') !== false) { - return explode('.', $table); - } - return array(null, $table); - } - - // }}} - // {{{ getTableFieldDefinition() - - /** - * Get the structure of a field into an array - * - * @param string $table name of table that should be used in method - * @param string $field name of field that should be used in method - * @return mixed data array on success, a MDB2 error on failure. - * The returned array contains an array for each field definition, - * with all or some of these indices, depending on the field data type: - * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] - * @access public - */ - function getTableFieldDefinition($table, $field) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ getTableIndexDefinition() - - /** - * Get the structure of an index into an array - * - * @param string $table name of table that should be used in method - * @param string $index name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - * - * array ( - * [fields] => array ( - * [field1name] => array() // one entry per each field covered - * [field2name] => array() // by the index - * [field3name] => array( - * [sorting] => ascending - * ) - * ) - * ); - * - * @access public - */ - function getTableIndexDefinition($table, $index) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ getTableConstraintDefinition() - - /** - * Get the structure of an constraints into an array - * - * @param string $table name of table that should be used in method - * @param string $index name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - *
-     *          array (
-     *              [primary] => 0
-     *              [unique]  => 0
-     *              [foreign] => 1
-     *              [check]   => 0
-     *              [fields] => array (
-     *                  [field1name] => array() // one entry per each field covered
-     *                  [field2name] => array() // by the index
-     *                  [field3name] => array(
-     *                      [sorting]  => ascending
-     *                      [position] => 3
-     *                  )
-     *              )
-     *              [references] => array(
-     *                  [table] => name
-     *                  [fields] => array(
-     *                      [field1name] => array(  //one entry per each referenced field
-     *                           [position] => 1
-     *                      )
-     *                  )
-     *              )
-     *              [deferrable] => 0
-     *              [initiallydeferred] => 0
-     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
-     *              [match] => SIMPLE|PARTIAL|FULL
-     *          );
-     *          
- * @access public - */ - function getTableConstraintDefinition($table, $index) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ getSequenceDefinition() - - /** - * Get the structure of a sequence into an array - * - * @param string $sequence name of sequence that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - *
-     *          array (
-     *              [start] => n
-     *          );
-     *          
- * @access public - */ - function getSequenceDefinition($sequence) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $start = $db->currId($sequence); - if (PEAR::isError($start)) { - return $start; - } - if ($db->supports('current_id')) { - $start++; - } else { - $db->warnings[] = 'database does not support getting current - sequence value, the sequence value was incremented'; - } - $definition = array(); - if ($start != 1) { - $definition = array('start' => $start); - } - return $definition; - } - - // }}} - // {{{ getTriggerDefinition() - - /** - * Get the structure of a trigger into an array - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change the returned value - * at any time until labelled as non-experimental - * - * @param string $trigger name of trigger that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * The returned array has this structure: - *
-     *          array (
-     *              [trigger_name]    => 'trigger name',
-     *              [table_name]      => 'table name',
-     *              [trigger_body]    => 'trigger body definition',
-     *              [trigger_type]    => 'BEFORE' | 'AFTER',
-     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
-     *                  //or comma separated list of multiple events, when supported
-     *              [trigger_enabled] => true|false
-     *              [trigger_comment] => 'trigger comment',
-     *          );
-     *          
- * The oci8 driver also returns a [when_clause] index. - * @access public - */ - function getTriggerDefinition($trigger) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * The format of the resulting array depends on which $mode - * you select. The sample output below is based on this query: - *
-     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
-     *    FROM tblFoo
-     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
-     * 
- * - *
    - *
  • - * - * null (default) - *
    -     *   [0] => Array (
    -     *       [table] => tblFoo
    -     *       [name] => fldId
    -     *       [type] => int
    -     *       [len] => 11
    -     *       [flags] => primary_key not_null
    -     *   )
    -     *   [1] => Array (
    -     *       [table] => tblFoo
    -     *       [name] => fldPhone
    -     *       [type] => string
    -     *       [len] => 20
    -     *       [flags] =>
    -     *   )
    -     *   [2] => Array (
    -     *       [table] => tblBar
    -     *       [name] => fldId
    -     *       [type] => int
    -     *       [len] => 11
    -     *       [flags] => primary_key not_null
    -     *   )
    -     *   
    - * - *
  • - * - * MDB2_TABLEINFO_ORDER - * - *

    In addition to the information found in the default output, - * a notation of the number of columns is provided by the - * num_fields element while the order - * element provides an array with the column names as the keys and - * their location index number (corresponding to the keys in the - * the default output) as the values.

    - * - *

    If a result set has identical field names, the last one is - * used.

    - * - *
    -     *   [num_fields] => 3
    -     *   [order] => Array (
    -     *       [fldId] => 2
    -     *       [fldTrans] => 1
    -     *   )
    -     *   
    - * - *
  • - * - * MDB2_TABLEINFO_ORDERTABLE - * - *

    Similar to MDB2_TABLEINFO_ORDER but adds more - * dimensions to the array in which the table names are keys and - * the field names are sub-keys. This is helpful for queries that - * join tables which have identical field names.

    - * - *
    -     *   [num_fields] => 3
    -     *   [ordertable] => Array (
    -     *       [tblFoo] => Array (
    -     *           [fldId] => 0
    -     *           [fldPhone] => 1
    -     *       )
    -     *       [tblBar] => Array (
    -     *           [fldId] => 2
    -     *       )
    -     *   )
    -     *   
    - * - *
  • - *
- * - * The flags element contains a space separated list - * of extra information about the field. This data is inconsistent - * between DBMS's due to the way each DBMS works. - * + primary_key - * + unique_key - * + multiple_key - * + not_null - * - * Most DBMS's only provide the table and flags - * elements if $result is a table name. The following DBMS's - * provide full information from queries: - * + fbsql - * + mysql - * - * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE - * turned on, the names of tables and fields will be lower or upper cased. - * - * @param object|string $result MDB2_result object from a query or a - * string containing the name of a table. - * While this also accepts a query result - * resource identifier, this behavior is - * deprecated. - * @param int $mode either unused or one of the tableInfo modes: - * MDB2_TABLEINFO_ORDERTABLE, - * MDB2_TABLEINFO_ORDER or - * MDB2_TABLEINFO_FULL (which does both). - * These are bitwise, so the first two can be - * combined using |. - * - * @return array an associative array with the information requested. - * A MDB2_Error object on failure. - * - * @see MDB2_Driver_Common::setOption() - */ - function tableInfo($result, $mode = null) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - if (!is_string($result)) { - return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'method not implemented', __FUNCTION__); - } - - $db->loadModule('Manager', null, true); - $fields = $db->manager->listTableFields($result); - if (PEAR::isError($fields)) { - return $fields; - } - - $flags = array(); - - $idxname_format = $db->getOption('idxname_format'); - $db->setOption('idxname_format', '%s'); - - $indexes = $db->manager->listTableIndexes($result); - if (PEAR::isError($indexes)) { - $db->setOption('idxname_format', $idxname_format); - return $indexes; - } - - foreach ($indexes as $index) { - $definition = $this->getTableIndexDefinition($result, $index); - if (PEAR::isError($definition)) { - $db->setOption('idxname_format', $idxname_format); - return $definition; - } - if (count($definition['fields']) > 1) { - foreach ($definition['fields'] as $field => $sort) { - $flags[$field] = 'multiple_key'; - } - } - } - - $constraints = $db->manager->listTableConstraints($result); - if (PEAR::isError($constraints)) { - return $constraints; - } - - foreach ($constraints as $constraint) { - $definition = $this->getTableConstraintDefinition($result, $constraint); - if (PEAR::isError($definition)) { - $db->setOption('idxname_format', $idxname_format); - return $definition; - } - $flag = !empty($definition['primary']) - ? 'primary_key' : (!empty($definition['unique']) - ? 'unique_key' : false); - if ($flag) { - foreach ($definition['fields'] as $field => $sort) { - if (empty($flags[$field]) || $flags[$field] != 'primary_key') { - $flags[$field] = $flag; - } - } - } - } - - $res = array(); - - if ($mode) { - $res['num_fields'] = count($fields); - } - - foreach ($fields as $i => $field) { - $definition = $this->getTableFieldDefinition($result, $field); - if (PEAR::isError($definition)) { - $db->setOption('idxname_format', $idxname_format); - return $definition; - } - $res[$i] = $definition[0]; - $res[$i]['name'] = $field; - $res[$i]['table'] = $result; - $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); - // 'primary_key', 'unique_key', 'multiple_key' - $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; - // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' - if (!empty($res[$i]['notnull'])) { - $res[$i]['flags'].= ' not_null'; - } - if (!empty($res[$i]['unsigned'])) { - $res[$i]['flags'].= ' unsigned'; - } - if (!empty($res[$i]['auto_increment'])) { - $res[$i]['flags'].= ' autoincrement'; - } - if (!empty($res[$i]['default'])) { - $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); - } - - if ($mode & MDB2_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & MDB2_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - $db->setOption('idxname_format', $idxname_format); - return $res; - } -} + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.43 2009/01/14 15:01:21 quipo Exp $ +// + +/** + * @package MDB2 + * @category Database + */ + +/** + * These are constants for the tableInfo-function + * they are bitwised or'ed. so if there are more constants to be defined + * in the future, adjust MDB2_TABLEINFO_FULL accordingly + */ + +define('MDB2_TABLEINFO_ORDER', 1); +define('MDB2_TABLEINFO_ORDERTABLE', 2); +define('MDB2_TABLEINFO_FULL', 3); + +/** + * Base class for the schema reverse engineering module that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Reverse'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * + * @param string $table [schema and] table name + * + * @return array array(schema, table) + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure. + * The returned array contains an array for each field definition, + * with all or some of these indices, depending on the field data type: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + * + * array ( + * [fields] => array ( + * [field1name] => array() // one entry per each field covered + * [field2name] => array() // by the index + * [field3name] => array( + * [sorting] => ascending + * ) + * ) + * ); + * + * @access public + */ + function getTableIndexDefinition($table, $index) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of an constraints into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @access public + */ + function getTableConstraintDefinition($table, $index) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the structure of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [start] => n
+     *          );
+     *          
+ * @access public + */ + function getSequenceDefinition($sequence) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $start = $db->currId($sequence); + if (PEAR::isError($start)) { + return $start; + } + if ($db->supports('current_id')) { + $start++; + } else { + $db->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [trigger_name]    => 'trigger name',
+     *              [table_name]      => 'table name',
+     *              [trigger_body]    => 'trigger body definition',
+     *              [trigger_type]    => 'BEFORE' | 'AFTER',
+     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
+     *                  //or comma separated list of multiple events, when supported
+     *              [trigger_enabled] => true|false
+     *              [trigger_comment] => 'trigger comment',
+     *          );
+     *          
+ * The oci8 driver also returns a [when_clause] index. + * @access public + */ + function getTriggerDefinition($trigger) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDER + * + *

    In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * num_fields element while the order + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.

    + * + *

    If a result set has identical field names, the last one is + * used.

    + * + *
    +     *   [num_fields] => 3
    +     *   [order] => Array (
    +     *       [fldId] => 2
    +     *       [fldTrans] => 1
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDERTABLE + * + *

    Similar to MDB2_TABLEINFO_ORDER but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE + * turned on, the names of tables and fields will be lower or upper cased. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * MDB2_TABLEINFO_ORDERTABLE, + * MDB2_TABLEINFO_ORDER or + * MDB2_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_string($result)) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $db->loadModule('Manager', null, true); + $fields = $db->manager->listTableFields($result); + if (PEAR::isError($fields)) { + return $fields; + } + + $flags = array(); + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + $indexes = $db->manager->listTableIndexes($result); + if (PEAR::isError($indexes)) { + $db->setOption('idxname_format', $idxname_format); + return $indexes; + } + + foreach ($indexes as $index) { + $definition = $this->getTableIndexDefinition($result, $index); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + if (count($definition['fields']) > 1) { + foreach ($definition['fields'] as $field => $sort) { + $flags[$field] = 'multiple_key'; + } + } + } + + $constraints = $db->manager->listTableConstraints($result); + if (PEAR::isError($constraints)) { + return $constraints; + } + + foreach ($constraints as $constraint) { + $definition = $this->getTableConstraintDefinition($result, $constraint); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $flag = !empty($definition['primary']) + ? 'primary_key' : (!empty($definition['unique']) + ? 'unique_key' : false); + if ($flag) { + foreach ($definition['fields'] as $field => $sort) { + if (empty($flags[$field]) || $flags[$field] != 'primary_key') { + $flags[$field] = $flag; + } + } + } + } + + $res = array(); + + if ($mode) { + $res['num_fields'] = count($fields); + } + + foreach ($fields as $i => $field) { + $definition = $this->getTableFieldDefinition($result, $field); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $res[$i] = $definition[0]; + $res[$i]['name'] = $field; + $res[$i]['table'] = $result; + $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); + // 'primary_key', 'unique_key', 'multiple_key' + $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; + // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' + if (!empty($res[$i]['notnull'])) { + $res[$i]['flags'].= ' not_null'; + } + if (!empty($res[$i]['unsigned'])) { + $res[$i]['flags'].= ' unsigned'; + } + if (!empty($res[$i]['auto_increment'])) { + $res[$i]['flags'].= ' autoincrement'; + } + if (!empty($res[$i]['default'])) { + $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); + } + + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + $db->setOption('idxname_format', $idxname_format); + return $res; + } +} ?> \ No newline at end of file diff --git a/inc/MDB2/Driver/Reverse/mysql.php b/inc/MDB2/Driver/Reverse/mysql.php index ab1f601ef0..40c62da3ba 100755 --- a/inc/MDB2/Driver/Reverse/mysql.php +++ b/inc/MDB2/Driver/Reverse/mysql.php @@ -45,7 +45,7 @@ // $Id: mysql.php,v 1.80 2008/03/26 21:15:37 quipo Exp $ // -require_once 'MDB2/Driver/Reverse/Common.php'; +oc_require_once('MDB2/Driver/Reverse/Common.php'); /** * MDB2 MySQL driver for the schema reverse engineering module diff --git a/inc/MDB2/Driver/Reverse/pgsql.php b/inc/MDB2/Driver/Reverse/pgsql.php index 1fff29dc4b..d010292cd8 100755 --- a/inc/MDB2/Driver/Reverse/pgsql.php +++ b/inc/MDB2/Driver/Reverse/pgsql.php @@ -1,573 +1,573 @@ - | -// | Lorenzo Alberton | -// +----------------------------------------------------------------------+ -// -// $Id: pgsql.php,v 1.75 2008/08/22 16:36:20 quipo Exp $ - -require_once 'MDB2/Driver/Reverse/Common.php'; - -/** - * MDB2 PostGreSQL driver for the schema reverse engineering module - * - * @package MDB2 - * @category Database - * @author Paul Cooper - * @author Lorenzo Alberton - */ -class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common -{ - // {{{ getTableFieldDefinition() - - /** - * Get the structure of a field into an array - * - * @param string $table_name name of table that should be used in method - * @param string $field_name name of field that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableFieldDefinition($table_name, $field_name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $result = $db->loadModule('Datatype', null, true); - if (PEAR::isError($result)) { - return $result; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = "SELECT a.attname AS name, - t.typname AS type, - CASE a.attlen - WHEN -1 THEN - CASE t.typname - WHEN 'numeric' THEN (a.atttypmod / 65536) - WHEN 'decimal' THEN (a.atttypmod / 65536) - WHEN 'money' THEN (a.atttypmod / 65536) - ELSE CASE a.atttypmod - WHEN -1 THEN NULL - ELSE a.atttypmod - 4 - END - END - ELSE a.attlen - END AS length, - CASE t.typname - WHEN 'numeric' THEN (a.atttypmod % 65536) - 4 - WHEN 'decimal' THEN (a.atttypmod % 65536) - 4 - WHEN 'money' THEN (a.atttypmod % 65536) - 4 - ELSE 0 - END AS scale, - a.attnotnull, - a.atttypmod, - a.atthasdef, - (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) - FROM pg_attrdef d - WHERE d.adrelid = a.attrelid - AND d.adnum = a.attnum - AND a.atthasdef - ) as default - FROM pg_attribute a, - pg_class c, - pg_type t - WHERE c.relname = ".$db->quote($table, 'text')." - AND a.atttypid = t.oid - AND c.oid = a.attrelid - AND NOT a.attisdropped - AND a.attnum > 0 - AND a.attname = ".$db->quote($field_name, 'text')." - ORDER BY a.attnum"; - $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($column)) { - return $column; - } - - if (empty($column)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table column', __FUNCTION__); - } - - $column = array_change_key_case($column, CASE_LOWER); - $mapped_datatype = $db->datatype->mapNativeDatatype($column); - if (PEAR::isError($mapped_datatype)) { - return $mapped_datatype; - } - list($types, $length, $unsigned, $fixed) = $mapped_datatype; - $notnull = false; - if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') { - $notnull = true; - } - $default = null; - if ($column['atthasdef'] === 't' - && !preg_match("/nextval\('([^']+)'/", $column['default']) - ) { - $pattern = '/^\'(.*)\'::[\w ]+$/i'; - $default = $column['default'];#substr($column['adsrc'], 1, -1); - if (is_null($default) && $notnull) { - $default = ''; - } elseif (!empty($default) && preg_match($pattern, $default)) { - //remove data type cast - $default = preg_replace ($pattern, '\\1', $default); - } - } - $autoincrement = false; - if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) { - $autoincrement = true; - } - $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); - if (!is_null($length)) { - $definition[0]['length'] = $length; - } - if (!is_null($unsigned)) { - $definition[0]['unsigned'] = $unsigned; - } - if (!is_null($fixed)) { - $definition[0]['fixed'] = $fixed; - } - if ($default !== false) { - $definition[0]['default'] = $default; - } - if ($autoincrement !== false) { - $definition[0]['autoincrement'] = $autoincrement; - } - foreach ($types as $key => $type) { - $definition[$key] = $definition[0]; - if ($type == 'clob' || $type == 'blob') { - unset($definition[$key]['default']); - } - $definition[$key]['type'] = $type; - $definition[$key]['mdb2type'] = $type; - } - return $definition; - } - - // }}} - // {{{ getTableIndexDefinition() - - /** - * Get the structure of an index into an array - * - * @param string $table_name name of table that should be used in method - * @param string $index_name name of index that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableIndexDefinition($table_name, $index_name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = 'SELECT relname, indkey FROM pg_index, pg_class'; - $query.= ' WHERE pg_class.oid = pg_index.indexrelid'; - $query.= " AND indisunique != 't' AND indisprimary != 't'"; - $query.= ' AND pg_class.relname = %s'; - $index_name_mdb2 = $db->getIndexName($index_name); - $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($row) || empty($row)) { - // fallback to the given $index_name, without transformation - $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC); - } - if (PEAR::isError($row)) { - return $row; - } - - if (empty($row)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'it was not specified an existing table index', __FUNCTION__); - } - - $row = array_change_key_case($row, CASE_LOWER); - - $db->loadModule('Manager', null, true); - $columns = $db->manager->listTableFields($table_name); - - $definition = array(); - - $index_column_numbers = explode(' ', $row['indkey']); - - $colpos = 1; - foreach ($index_column_numbers as $number) { - $definition['fields'][$columns[($number - 1)]] = array( - 'position' => $colpos++, - 'sorting' => 'ascending', - ); - } - return $definition; - } - - // }}} - // {{{ getTableConstraintDefinition() - - /** - * Get the structure of a constraint into an array - * - * @param string $table_name name of table that should be used in method - * @param string $constraint_name name of constraint that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - */ - function getTableConstraintDefinition($table_name, $constraint_name) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - list($schema, $table) = $this->splitTableSchema($table_name); - - $query = "SELECT c.oid, - c.conname AS constraint_name, - CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\", - CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\", - CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\", - CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\", - CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable, - CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred, - --array_to_string(c.conkey, ' ') AS constraint_key, - t.relname AS table_name, - t2.relname AS references_table, - CASE confupdtype - WHEN 'a' THEN 'NO ACTION' - WHEN 'r' THEN 'RESTRICT' - WHEN 'c' THEN 'CASCADE' - WHEN 'n' THEN 'SET NULL' - WHEN 'd' THEN 'SET DEFAULT' - END AS onupdate, - CASE confdeltype - WHEN 'a' THEN 'NO ACTION' - WHEN 'r' THEN 'RESTRICT' - WHEN 'c' THEN 'CASCADE' - WHEN 'n' THEN 'SET NULL' - WHEN 'd' THEN 'SET DEFAULT' - END AS ondelete, - CASE confmatchtype - WHEN 'u' THEN 'UNSPECIFIED' - WHEN 'f' THEN 'FULL' - WHEN 'p' THEN 'PARTIAL' - END AS match, - --array_to_string(c.confkey, ' ') AS fk_constraint_key, - consrc - FROM pg_constraint c - LEFT JOIN pg_class t ON c.conrelid = t.oid - LEFT JOIN pg_class t2 ON c.confrelid = t2.oid - WHERE c.conname = %s - AND t.relname = " . $db->quote($table, 'text'); - $constraint_name_mdb2 = $db->getIndexName($constraint_name); - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($row) || empty($row)) { - // fallback to the given $index_name, without transformation - $constraint_name_mdb2 = $constraint_name; - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - } - if (PEAR::isError($row)) { - return $row; - } - $uniqueIndex = false; - if (empty($row)) { - // We might be looking for a UNIQUE index that was not created - // as a constraint but should be treated as such. - $query = 'SELECT relname AS constraint_name, - indkey, - 0 AS "check", - 0 AS "foreign", - 0 AS "primary", - 1 AS "unique", - 0 AS deferrable, - 0 AS initiallydeferred, - NULL AS references_table, - NULL AS onupdate, - NULL AS ondelete, - NULL AS match - FROM pg_index, pg_class - WHERE pg_class.oid = pg_index.indexrelid - AND indisunique = \'t\' - AND pg_class.relname = %s'; - $constraint_name_mdb2 = $db->getIndexName($constraint_name); - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - if (PEAR::isError($row) || empty($row)) { - // fallback to the given $index_name, without transformation - $constraint_name_mdb2 = $constraint_name; - $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); - } - if (PEAR::isError($row)) { - return $row; - } - if (empty($row)) { - return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - $constraint_name . ' is not an existing table constraint', __FUNCTION__); - } - $uniqueIndex = true; - } - - $row = array_change_key_case($row, CASE_LOWER); - - $definition = array( - 'primary' => (boolean)$row['primary'], - 'unique' => (boolean)$row['unique'], - 'foreign' => (boolean)$row['foreign'], - 'check' => (boolean)$row['check'], - 'fields' => array(), - 'references' => array( - 'table' => $row['references_table'], - 'fields' => array(), - ), - 'deferrable' => (boolean)$row['deferrable'], - 'initiallydeferred' => (boolean)$row['initiallydeferred'], - 'onupdate' => $row['onupdate'], - 'ondelete' => $row['ondelete'], - 'match' => $row['match'], - ); - - if ($uniqueIndex) { - $db->loadModule('Manager', null, true); - $columns = $db->manager->listTableFields($table_name); - $index_column_numbers = explode(' ', $row['indkey']); - $colpos = 1; - foreach ($index_column_numbers as $number) { - $definition['fields'][$columns[($number - 1)]] = array( - 'position' => $colpos++, - 'sorting' => 'ascending', - ); - } - return $definition; - } - - $query = 'SELECT a.attname - FROM pg_constraint c - LEFT JOIN pg_class t ON c.conrelid = t.oid - LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey) - WHERE c.conname = %s - AND t.relname = ' . $db->quote($table, 'text'); - $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); - if (PEAR::isError($fields)) { - return $fields; - } - $colpos = 1; - foreach ($fields as $field) { - $definition['fields'][$field] = array( - 'position' => $colpos++, - 'sorting' => 'ascending', - ); - } - - if ($definition['foreign']) { - $query = 'SELECT a.attname - FROM pg_constraint c - LEFT JOIN pg_class t ON c.confrelid = t.oid - LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey) - WHERE c.conname = %s - AND t.relname = ' . $db->quote($definition['references']['table'], 'text'); - $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); - if (PEAR::isError($foreign_fields)) { - return $foreign_fields; - } - $colpos = 1; - foreach ($foreign_fields as $foreign_field) { - $definition['references']['fields'][$foreign_field] = array( - 'position' => $colpos++, - ); - } - } - - if ($definition['check']) { - $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')"); - // ... - } - return $definition; - } - - // }}} - // {{{ getTriggerDefinition() - - /** - * Get the structure of a trigger into an array - * - * EXPERIMENTAL - * - * WARNING: this function is experimental and may change the returned value - * at any time until labelled as non-experimental - * - * @param string $trigger name of trigger that should be used in method - * @return mixed data array on success, a MDB2 error on failure - * @access public - * - * @TODO: add support for plsql functions and functions with args - */ - function getTriggerDefinition($trigger) - { - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $query = "SELECT trg.tgname AS trigger_name, - tbl.relname AS table_name, - CASE - WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();' - ELSE '' - END AS trigger_body, - CASE trg.tgtype & cast(2 as int2) - WHEN 0 THEN 'AFTER' - ELSE 'BEFORE' - END AS trigger_type, - CASE trg.tgtype & cast(28 as int2) - WHEN 16 THEN 'UPDATE' - WHEN 8 THEN 'DELETE' - WHEN 4 THEN 'INSERT' - WHEN 20 THEN 'INSERT, UPDATE' - WHEN 28 THEN 'INSERT, UPDATE, DELETE' - WHEN 24 THEN 'UPDATE, DELETE' - WHEN 12 THEN 'INSERT, DELETE' - END AS trigger_event, - CASE trg.tgenabled - WHEN 'O' THEN 't' - ELSE trg.tgenabled - END AS trigger_enabled, - obj_description(trg.oid, 'pg_trigger') AS trigger_comment - FROM pg_trigger trg, - pg_class tbl, - pg_proc p - WHERE trg.tgrelid = tbl.oid - AND trg.tgfoid = p.oid - AND trg.tgname = ". $db->quote($trigger, 'text'); - $types = array( - 'trigger_name' => 'text', - 'table_name' => 'text', - 'trigger_body' => 'text', - 'trigger_type' => 'text', - 'trigger_event' => 'text', - 'trigger_comment' => 'text', - 'trigger_enabled' => 'boolean', - ); - return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * @param object|string $result MDB2_result object from a query or a - * string containing the name of a table. - * While this also accepts a query result - * resource identifier, this behavior is - * deprecated. - * @param int $mode a valid tableInfo mode - * - * @return array an associative array with the information requested. - * A MDB2_Error object on failure. - * - * @see MDB2_Driver_Common::tableInfo() - */ - function tableInfo($result, $mode = null) - { - if (is_string($result)) { - return parent::tableInfo($result, $mode); - } - - $db =& $this->getDBInstance(); - if (PEAR::isError($db)) { - return $db; - } - - $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; - if (!is_resource($resource)) { - return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'Could not generate result resource', __FUNCTION__); - } - - if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - if ($db->options['field_case'] == CASE_LOWER) { - $case_func = 'strtolower'; - } else { - $case_func = 'strtoupper'; - } - } else { - $case_func = 'strval'; - } - - $count = @pg_num_fields($resource); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - $db->loadModule('Datatype', null, true); - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '', - 'name' => $case_func(@pg_field_name($resource, $i)), - 'type' => @pg_field_type($resource, $i), - 'length' => @pg_field_size($resource, $i), - 'flags' => '', - ); - $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); - if (PEAR::isError($mdb2type_info)) { - return $mdb2type_info; - } - $res[$i]['mdb2type'] = $mdb2type_info[0][0]; - if ($mode & MDB2_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & MDB2_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - return $res; - } -} + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.75 2008/08/22 16:36:20 quipo Exp $ + +oc_require_once('MDB2/Driver/Reverse/Common.php'); + +/** + * MDB2 PostGreSQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Paul Cooper + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT a.attname AS name, + t.typname AS type, + CASE a.attlen + WHEN -1 THEN + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod / 65536) + WHEN 'decimal' THEN (a.atttypmod / 65536) + WHEN 'money' THEN (a.atttypmod / 65536) + ELSE CASE a.atttypmod + WHEN -1 THEN NULL + ELSE a.atttypmod - 4 + END + END + ELSE a.attlen + END AS length, + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod % 65536) - 4 + WHEN 'decimal' THEN (a.atttypmod % 65536) - 4 + WHEN 'money' THEN (a.atttypmod % 65536) - 4 + ELSE 0 + END AS scale, + a.attnotnull, + a.atttypmod, + a.atthasdef, + (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) + FROM pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) as default + FROM pg_attribute a, + pg_class c, + pg_type t + WHERE c.relname = ".$db->quote($table, 'text')." + AND a.atttypid = t.oid + AND c.oid = a.attrelid + AND NOT a.attisdropped + AND a.attnum > 0 + AND a.attname = ".$db->quote($field_name, 'text')." + ORDER BY a.attnum"; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($column)) { + return $column; + } + + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + $column = array_change_key_case($column, CASE_LOWER); + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (PEAR::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') { + $notnull = true; + } + $default = null; + if ($column['atthasdef'] === 't' + && !preg_match("/nextval\('([^']+)'/", $column['default']) + ) { + $pattern = '/^\'(.*)\'::[\w ]+$/i'; + $default = $column['default'];#substr($column['adsrc'], 1, -1); + if (is_null($default) && $notnull) { + $default = ''; + } elseif (!empty($default) && preg_match($pattern, $default)) { + //remove data type cast + $default = preg_replace ($pattern, '\\1', $default); + } + } + $autoincrement = false; + if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) { + $autoincrement = true; + } + $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); + if (!is_null($length)) { + $definition[0]['length'] = $length; + } + if (!is_null($unsigned)) { + $definition[0]['unsigned'] = $unsigned; + } + if (!is_null($fixed)) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = 'SELECT relname, indkey FROM pg_index, pg_class'; + $query.= ' WHERE pg_class.oid = pg_index.indexrelid'; + $query.= " AND indisunique != 't' AND indisprimary != 't'"; + $query.= ' AND pg_class.relname = %s'; + $index_name_mdb2 = $db->getIndexName($index_name); + $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $row = array_change_key_case($row, CASE_LOWER); + + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + + $definition = array(); + + $index_column_numbers = explode(' ', $row['indkey']); + + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT c.oid, + c.conname AS constraint_name, + CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\", + CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\", + CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\", + CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\", + CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable, + CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred, + --array_to_string(c.conkey, ' ') AS constraint_key, + t.relname AS table_name, + t2.relname AS references_table, + CASE confupdtype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS onupdate, + CASE confdeltype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS ondelete, + CASE confmatchtype + WHEN 'u' THEN 'UNSPECIFIED' + WHEN 'f' THEN 'FULL' + WHEN 'p' THEN 'PARTIAL' + END AS match, + --array_to_string(c.confkey, ' ') AS fk_constraint_key, + consrc + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_class t2 ON c.confrelid = t2.oid + WHERE c.conname = %s + AND t.relname = " . $db->quote($table, 'text'); + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + $uniqueIndex = false; + if (empty($row)) { + // We might be looking for a UNIQUE index that was not created + // as a constraint but should be treated as such. + $query = 'SELECT relname AS constraint_name, + indkey, + 0 AS "check", + 0 AS "foreign", + 0 AS "primary", + 1 AS "unique", + 0 AS deferrable, + 0 AS initiallydeferred, + NULL AS references_table, + NULL AS onupdate, + NULL AS ondelete, + NULL AS match + FROM pg_index, pg_class + WHERE pg_class.oid = pg_index.indexrelid + AND indisunique = \'t\' + AND pg_class.relname = %s'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + $uniqueIndex = true; + } + + $row = array_change_key_case($row, CASE_LOWER); + + $definition = array( + 'primary' => (boolean)$row['primary'], + 'unique' => (boolean)$row['unique'], + 'foreign' => (boolean)$row['foreign'], + 'check' => (boolean)$row['check'], + 'fields' => array(), + 'references' => array( + 'table' => $row['references_table'], + 'fields' => array(), + ), + 'deferrable' => (boolean)$row['deferrable'], + 'initiallydeferred' => (boolean)$row['initiallydeferred'], + 'onupdate' => $row['onupdate'], + 'ondelete' => $row['ondelete'], + 'match' => $row['match'], + ); + + if ($uniqueIndex) { + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + $index_column_numbers = explode(' ', $row['indkey']); + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($table, 'text'); + $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (PEAR::isError($fields)) { + return $fields; + } + $colpos = 1; + foreach ($fields as $field) { + $definition['fields'][$field] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + + if ($definition['foreign']) { + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.confrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($definition['references']['table'], 'text'); + $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (PEAR::isError($foreign_fields)) { + return $foreign_fields; + } + $colpos = 1; + foreach ($foreign_fields as $foreign_field) { + $definition['references']['fields'][$foreign_field] = array( + 'position' => $colpos++, + ); + } + } + + if ($definition['check']) { + $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')"); + // ... + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + * + * @TODO: add support for plsql functions and functions with args + */ + function getTriggerDefinition($trigger) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT trg.tgname AS trigger_name, + tbl.relname AS table_name, + CASE + WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();' + ELSE '' + END AS trigger_body, + CASE trg.tgtype & cast(2 as int2) + WHEN 0 THEN 'AFTER' + ELSE 'BEFORE' + END AS trigger_type, + CASE trg.tgtype & cast(28 as int2) + WHEN 16 THEN 'UPDATE' + WHEN 8 THEN 'DELETE' + WHEN 4 THEN 'INSERT' + WHEN 20 THEN 'INSERT, UPDATE' + WHEN 28 THEN 'INSERT, UPDATE, DELETE' + WHEN 24 THEN 'UPDATE, DELETE' + WHEN 12 THEN 'INSERT, DELETE' + END AS trigger_event, + CASE trg.tgenabled + WHEN 'O' THEN 't' + ELSE trg.tgenabled + END AS trigger_enabled, + obj_description(trg.oid, 'pg_trigger') AS trigger_comment + FROM pg_trigger trg, + pg_class tbl, + pg_proc p + WHERE trg.tgrelid = tbl.oid + AND trg.tgfoid = p.oid + AND trg.tgname = ". $db->quote($trigger, 'text'); + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @pg_num_fields($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '', + 'name' => $case_func(@pg_field_name($resource, $i)), + 'type' => @pg_field_type($resource, $i), + 'length' => @pg_field_size($resource, $i), + 'flags' => '', + ); + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} ?> \ No newline at end of file diff --git a/inc/MDB2/Driver/Reverse/sqlite.php b/inc/MDB2/Driver/Reverse/sqlite.php index 432e5ecb91..1b85aa71f9 100755 --- a/inc/MDB2/Driver/Reverse/sqlite.php +++ b/inc/MDB2/Driver/Reverse/sqlite.php @@ -46,7 +46,7 @@ // $Id: sqlite.php,v 1.80 2008/05/03 10:30:14 quipo Exp $ // -require_once 'MDB2/Driver/Reverse/Common.php'; +oc_require_once('MDB2/Driver/Reverse/Common.php'); /** * MDB2 SQlite driver for the schema reverse engineering module diff --git a/inc/MDB2/Driver/mysql.php b/inc/MDB2/Driver/mysql.php index d7b5719f68..091c479d19 100755 --- a/inc/MDB2/Driver/mysql.php +++ b/inc/MDB2/Driver/mysql.php @@ -1,1700 +1,1700 @@ - | -// +----------------------------------------------------------------------+ -// -// $Id: mysql.php,v 1.214 2008/11/16 21:45:08 quipo Exp $ -// - -/** - * MDB2 MySQL driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Driver_mysql extends MDB2_Driver_Common -{ - // {{{ properties - - var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\'); - - var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`'); - - var $sql_comments = array( - array('start' => '-- ', 'end' => "\n", 'escape' => false), - array('start' => '#', 'end' => "\n", 'escape' => false), - array('start' => '/*', 'end' => '*/', 'escape' => false), - ); - - var $server_capabilities_checked = false; - - var $start_transaction = false; - - var $varchar_max_length = 255; - - // }}} - // {{{ constructor - - /** - * Constructor - */ - function __construct() - { - parent::__construct(); - - $this->phptype = 'mysql'; - $this->dbsyntax = 'mysql'; - - $this->supported['sequences'] = 'emulated'; - $this->supported['indexes'] = true; - $this->supported['affected_rows'] = true; - $this->supported['transactions'] = false; - $this->supported['savepoints'] = false; - $this->supported['summary_functions'] = true; - $this->supported['order_by_text'] = true; - $this->supported['current_id'] = 'emulated'; - $this->supported['limit_queries'] = true; - $this->supported['LOBs'] = true; - $this->supported['replace'] = true; - $this->supported['sub_selects'] = 'emulated'; - $this->supported['triggers'] = false; - $this->supported['auto_increment'] = true; - $this->supported['primary_key'] = true; - $this->supported['result_introspection'] = true; - $this->supported['prepared_statements'] = 'emulated'; - $this->supported['identifier_quoting'] = true; - $this->supported['pattern_escaping'] = true; - $this->supported['new_link'] = true; - - $this->options['DBA_username'] = false; - $this->options['DBA_password'] = false; - $this->options['default_table_type'] = ''; - $this->options['max_identifiers_length'] = 64; - - $this->_reCheckSupportedOptions(); - } - - // }}} - // {{{ _reCheckSupportedOptions() - - /** - * If the user changes certain options, other capabilities may depend - * on the new settings, so we need to check them (again). - * - * @access private - */ - function _reCheckSupportedOptions() - { - $this->supported['transactions'] = $this->options['use_transactions']; - $this->supported['savepoints'] = $this->options['use_transactions']; - if ($this->options['default_table_type']) { - switch (strtoupper($this->options['default_table_type'])) { - case 'BLACKHOLE': - case 'MEMORY': - case 'ARCHIVE': - case 'CSV': - case 'HEAP': - case 'ISAM': - case 'MERGE': - case 'MRG_ISAM': - case 'ISAM': - case 'MRG_MYISAM': - case 'MYISAM': - $this->supported['savepoints'] = false; - $this->supported['transactions'] = false; - $this->warnings[] = $this->options['default_table_type'] . - ' is not a supported default table type'; - break; - } - } - } - - // }}} - // {{{ function setOption($option, $value) - - /** - * set the option for the db class - * - * @param string option name - * @param mixed value for the option - * - * @return mixed MDB2_OK or MDB2 Error Object - * - * @access public - */ - function setOption($option, $value) - { - $res = parent::setOption($option, $value); - $this->_reCheckSupportedOptions(); - } - - // }}} - // {{{ errorInfo() - - /** - * This method is used to collect information about an error - * - * @param integer $error - * @return array - * @access public - */ - function errorInfo($error = null) - { - if ($this->connection) { - $native_code = @mysql_errno($this->connection); - $native_msg = @mysql_error($this->connection); - } else { - $native_code = @mysql_errno(); - $native_msg = @mysql_error(); - } - if (is_null($error)) { - static $ecode_map; - if (empty($ecode_map)) { - $ecode_map = array( - 1000 => MDB2_ERROR_INVALID, //hashchk - 1001 => MDB2_ERROR_INVALID, //isamchk - 1004 => MDB2_ERROR_CANNOT_CREATE, - 1005 => MDB2_ERROR_CANNOT_CREATE, - 1006 => MDB2_ERROR_CANNOT_CREATE, - 1007 => MDB2_ERROR_ALREADY_EXISTS, - 1008 => MDB2_ERROR_CANNOT_DROP, - 1009 => MDB2_ERROR_CANNOT_DROP, - 1010 => MDB2_ERROR_CANNOT_DROP, - 1011 => MDB2_ERROR_CANNOT_DELETE, - 1022 => MDB2_ERROR_ALREADY_EXISTS, - 1029 => MDB2_ERROR_NOT_FOUND, - 1032 => MDB2_ERROR_NOT_FOUND, - 1044 => MDB2_ERROR_ACCESS_VIOLATION, - 1045 => MDB2_ERROR_ACCESS_VIOLATION, - 1046 => MDB2_ERROR_NODBSELECTED, - 1048 => MDB2_ERROR_CONSTRAINT, - 1049 => MDB2_ERROR_NOSUCHDB, - 1050 => MDB2_ERROR_ALREADY_EXISTS, - 1051 => MDB2_ERROR_NOSUCHTABLE, - 1054 => MDB2_ERROR_NOSUCHFIELD, - 1060 => MDB2_ERROR_ALREADY_EXISTS, - 1061 => MDB2_ERROR_ALREADY_EXISTS, - 1062 => MDB2_ERROR_ALREADY_EXISTS, - 1064 => MDB2_ERROR_SYNTAX, - 1067 => MDB2_ERROR_INVALID, - 1072 => MDB2_ERROR_NOT_FOUND, - 1086 => MDB2_ERROR_ALREADY_EXISTS, - 1091 => MDB2_ERROR_NOT_FOUND, - 1100 => MDB2_ERROR_NOT_LOCKED, - 1109 => MDB2_ERROR_NOT_FOUND, - 1125 => MDB2_ERROR_ALREADY_EXISTS, - 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, - 1138 => MDB2_ERROR_INVALID, - 1142 => MDB2_ERROR_ACCESS_VIOLATION, - 1143 => MDB2_ERROR_ACCESS_VIOLATION, - 1146 => MDB2_ERROR_NOSUCHTABLE, - 1149 => MDB2_ERROR_SYNTAX, - 1169 => MDB2_ERROR_CONSTRAINT, - 1176 => MDB2_ERROR_NOT_FOUND, - 1177 => MDB2_ERROR_NOSUCHTABLE, - 1213 => MDB2_ERROR_DEADLOCK, - 1216 => MDB2_ERROR_CONSTRAINT, - 1217 => MDB2_ERROR_CONSTRAINT, - 1227 => MDB2_ERROR_ACCESS_VIOLATION, - 1235 => MDB2_ERROR_CANNOT_CREATE, - 1299 => MDB2_ERROR_INVALID_DATE, - 1300 => MDB2_ERROR_INVALID, - 1304 => MDB2_ERROR_ALREADY_EXISTS, - 1305 => MDB2_ERROR_NOT_FOUND, - 1306 => MDB2_ERROR_CANNOT_DROP, - 1307 => MDB2_ERROR_CANNOT_CREATE, - 1334 => MDB2_ERROR_CANNOT_ALTER, - 1339 => MDB2_ERROR_NOT_FOUND, - 1356 => MDB2_ERROR_INVALID, - 1359 => MDB2_ERROR_ALREADY_EXISTS, - 1360 => MDB2_ERROR_NOT_FOUND, - 1363 => MDB2_ERROR_NOT_FOUND, - 1365 => MDB2_ERROR_DIVZERO, - 1451 => MDB2_ERROR_CONSTRAINT, - 1452 => MDB2_ERROR_CONSTRAINT, - 1542 => MDB2_ERROR_CANNOT_DROP, - 1546 => MDB2_ERROR_CONSTRAINT, - 1582 => MDB2_ERROR_CONSTRAINT, - 2003 => MDB2_ERROR_CONNECT_FAILED, - 2019 => MDB2_ERROR_INVALID, - ); - } - if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { - $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; - $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; - $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; - } else { - // Doing this in case mode changes during runtime. - $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; - $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; - $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; - } - if (isset($ecode_map[$native_code])) { - $error = $ecode_map[$native_code]; - } - } - return array($error, $native_code, $native_msg); - } - - // }}} - // {{{ escape() - - /** - * Quotes a string so it can be safely used in a query. It will quote - * the text so it can safely be used within a query. - * - * @param string the input string to quote - * @param bool escape wildcards - * - * @return string quoted string - * - * @access public - */ - function escape($text, $escape_wildcards = false) - { - if ($escape_wildcards) { - $text = $this->escapePattern($text); - } - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $text = @mysql_real_escape_string($text, $connection); - return $text; - } - - // }}} - // {{{ beginTransaction() - - /** - * Start a transaction or set a savepoint. - * - * @param string name of a savepoint to set - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function beginTransaction($savepoint = null) - { - $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - $this->_getServerCapabilities(); - if (!is_null($savepoint)) { - if (!$this->supports('savepoints')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'savepoint cannot be released when changes are auto committed', __FUNCTION__); - } - $query = 'SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } elseif ($this->in_transaction) { - return MDB2_OK; //nothing to do - } - if (!$this->destructor_registered && $this->opened_persistent) { - $this->destructor_registered = true; - register_shutdown_function('MDB2_closeOpenTransactions'); - } - $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 0'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - $this->in_transaction = true; - return MDB2_OK; - } - - // }}} - // {{{ commit() - - /** - * Commit the database changes done during a transaction that is in - * progress or release a savepoint. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after committing the pending changes. - * - * @param string name of a savepoint to release - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function commit($savepoint = null) - { - $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - if (!$this->supports('savepoints')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - $server_info = $this->getServerVersion(); - if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { - return MDB2_OK; - } - $query = 'RELEASE SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } - - if (!$this->supports('transactions')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'transactions are not supported', __FUNCTION__); - } - - $result =& $this->_doQuery('COMMIT', true); - if (PEAR::isError($result)) { - return $result; - } - if (!$this->start_transaction) { - $query = 'SET AUTOCOMMIT = 1'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ rollback() - - /** - * Cancel any database changes done during a transaction or since a specific - * savepoint that is in progress. This function may only be called when - * auto-committing is disabled, otherwise it will fail. Therefore, a new - * transaction is implicitly started after canceling the pending changes. - * - * @param string name of a savepoint to rollback to - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - */ - function rollback($savepoint = null) - { - $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); - if (!$this->in_transaction) { - return $this->raiseError(MDB2_ERROR_INVALID, null, null, - 'rollback cannot be done changes are auto committed', __FUNCTION__); - } - if (!is_null($savepoint)) { - if (!$this->supports('savepoints')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'savepoints are not supported', __FUNCTION__); - } - $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; - return $this->_doQuery($query, true); - } - - $query = 'ROLLBACK'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - if (!$this->start_transaction) { - $query = 'SET AUTOCOMMIT = 1'; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - return $result; - } - } - $this->in_transaction = false; - return MDB2_OK; - } - - // }}} - // {{{ function setTransactionIsolation() - - /** - * Set the transacton isolation level. - * - * @param string standard isolation level - * READ UNCOMMITTED (allows dirty reads) - * READ COMMITTED (prevents dirty reads) - * REPEATABLE READ (prevents nonrepeatable reads) - * SERIALIZABLE (prevents phantom reads) - * @return mixed MDB2_OK on success, a MDB2 error on failure - * - * @access public - * @since 2.1.1 - */ - function setTransactionIsolation($isolation) - { - $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); - if (!$this->supports('transactions')) { - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'transactions are not supported', __FUNCTION__); - } - switch ($isolation) { - case 'READ UNCOMMITTED': - case 'READ COMMITTED': - case 'REPEATABLE READ': - case 'SERIALIZABLE': - break; - default: - return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, - 'isolation level is not supported: '.$isolation, __FUNCTION__); - } - - $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; - return $this->_doQuery($query, true); - } - - // }}} - // {{{ _doConnect() - - /** - * do the grunt work of the connect - * - * @return connection on success or MDB2 Error Object on failure - * @access protected - */ - function _doConnect($username, $password, $persistent = false) - { - if (!PEAR::loadExtension($this->phptype)) { - return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); - } - - $params = array(); - if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') { - $params[0] = ':' . $this->dsn['socket']; - } else { - $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec'] - : 'localhost'; - if ($this->dsn['port']) { - $params[0].= ':' . $this->dsn['port']; - } - } - $params[] = $username ? $username : null; - $params[] = $password ? $password : null; - if (!$persistent) { - if ($this->_isNewLinkSet()) { - $params[] = true; - } else { - $params[] = false; - } - } - if (version_compare(phpversion(), '4.3.0', '>=')) { - $params[] = isset($this->dsn['client_flags']) - ? $this->dsn['client_flags'] : null; - } - $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; - - $connection = @call_user_func_array($connect_function, $params); - if (!$connection) { - if (($err = @mysql_error()) != '') { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - $err, __FUNCTION__); - } else { - return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, - 'unable to establish a connection', __FUNCTION__); - } - } - - if (!empty($this->dsn['charset'])) { - $result = $this->setCharset($this->dsn['charset'], $connection); - if (PEAR::isError($result)) { - $this->disconnect(false); - return $result; - } - } - - return $connection; - } - - // }}} - // {{{ connect() - - /** - * Connect to the database - * - * @return MDB2_OK on success, MDB2 Error Object on failure - * @access public - */ - function connect() - { - if (is_resource($this->connection)) { - //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 - if (MDB2::areEquals($this->connected_dsn, $this->dsn) - && $this->opened_persistent == $this->options['persistent'] - ) { - return MDB2_OK; - } - $this->disconnect(false); - } - - $connection = $this->_doConnect( - $this->dsn['username'], - $this->dsn['password'], - $this->options['persistent'] - ); - if (PEAR::isError($connection)) { - return $connection; - } - - $this->connection = $connection; - $this->connected_dsn = $this->dsn; - $this->connected_database_name = ''; - $this->opened_persistent = $this->options['persistent']; - $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; - - if ($this->database_name) { - if ($this->database_name != $this->connected_database_name) { - if (!@mysql_select_db($this->database_name, $connection)) { - $err = $this->raiseError(null, null, null, - 'Could not select the database: '.$this->database_name, __FUNCTION__); - return $err; - } - $this->connected_database_name = $this->database_name; - } - } - - $this->_getServerCapabilities(); - - return MDB2_OK; - } - - // }}} - // {{{ setCharset() - - /** - * Set the charset on the current connection - * - * @param string charset (or array(charset, collation)) - * @param resource connection handle - * - * @return true on success, MDB2 Error Object on failure - */ - function setCharset($charset, $connection = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - $collation = null; - if (is_array($charset) && 2 == count($charset)) { - $collation = array_pop($charset); - $charset = array_pop($charset); - } - $client_info = mysql_get_client_info(); - if (function_exists('mysql_set_charset') && version_compare($client_info, '5.0.6')) { - if (!$result = mysql_set_charset($charset, $connection)) { - $err =& $this->raiseError(null, null, null, - 'Could not set client character set', __FUNCTION__); - return $err; - } - return $result; - } - $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'"; - if (!is_null($collation)) { - $query .= " COLLATE '".mysqli_real_escape_string($connection, $collation)."'"; - } - return $this->_doQuery($query, true, $connection); - } - - // }}} - // {{{ databaseExists() - - /** - * check if given database name is exists? - * - * @param string $name name of the database that should be checked - * - * @return mixed true/false on success, a MDB2 error on failure - * @access public - */ - function databaseExists($name) - { - $connection = $this->_doConnect($this->dsn['username'], - $this->dsn['password'], - $this->options['persistent']); - if (PEAR::isError($connection)) { - return $connection; - } - - $result = @mysql_select_db($name, $connection); - @mysql_close($connection); - - return $result; - } - - // }}} - // {{{ disconnect() - - /** - * Log out and disconnect from the database. - * - * @param boolean $force if the disconnect should be forced even if the - * connection is opened persistently - * @return mixed true on success, false if not connected and error - * object on error - * @access public - */ - function disconnect($force = true) - { - if (is_resource($this->connection)) { - if ($this->in_transaction) { - $dsn = $this->dsn; - $database_name = $this->database_name; - $persistent = $this->options['persistent']; - $this->dsn = $this->connected_dsn; - $this->database_name = $this->connected_database_name; - $this->options['persistent'] = $this->opened_persistent; - $this->rollback(); - $this->dsn = $dsn; - $this->database_name = $database_name; - $this->options['persistent'] = $persistent; - } - - if (!$this->opened_persistent || $force) { - $ok = @mysql_close($this->connection); - if (!$ok) { - return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, - null, null, null, __FUNCTION__); - } - } - } else { - return false; - } - return parent::disconnect($force); - } - - // }}} - // {{{ standaloneQuery() - - /** - * execute a query as DBA - * - * @param string $query the SQL query - * @param mixed $types array that contains the types of the columns in - * the result set - * @param boolean $is_manip if the query is a manipulation query - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function &standaloneQuery($query, $types = null, $is_manip = false) - { - $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; - $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; - $connection = $this->_doConnect($user, $pass, $this->options['persistent']); - if (PEAR::isError($connection)) { - return $connection; - } - - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - - $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name); - if (!PEAR::isError($result)) { - $result = $this->_affectedRows($connection, $result); - } - - @mysql_close($connection); - return $result; - } - - // }}} - // {{{ _doQuery() - - /** - * Execute a query - * @param string $query query - * @param boolean $is_manip if the query is a manipulation query - * @param resource $connection - * @param string $database_name - * @return result or error object - * @access protected - */ - function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) - { - $this->last_query = $query; - $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - if ($this->options['disable_query']) { - $result = $is_manip ? 0 : null; - return $result; - } - - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - if (is_null($database_name)) { - $database_name = $this->database_name; - } - - if ($database_name) { - if ($database_name != $this->connected_database_name) { - if (!@mysql_select_db($database_name, $connection)) { - $err = $this->raiseError(null, null, null, - 'Could not select the database: '.$database_name, __FUNCTION__); - return $err; - } - $this->connected_database_name = $database_name; - } - } - - $function = $this->options['result_buffering'] - ? 'mysql_query' : 'mysql_unbuffered_query'; - $result = @$function($query, $connection); - if (!$result) { - $err =& $this->raiseError(null, null, null, - 'Could not execute statement', __FUNCTION__); - return $err; - } - - $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ _affectedRows() - - /** - * Returns the number of rows affected - * - * @param resource $result - * @param resource $connection - * @return mixed MDB2 Error Object or the number of rows affected - * @access private - */ - function _affectedRows($connection, $result = null) - { - if (is_null($connection)) { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - } - return @mysql_affected_rows($connection); - } - - // }}} - // {{{ _modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * @param string $query query to modify - * @param boolean $is_manip if it is a DML query - * @param integer $limit limit the number of rows - * @param integer $offset start reading from given offset - * @return string modified query - * @access protected - */ - function _modifyQuery($query, $is_manip, $limit, $offset) - { - if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { - // "DELETE FROM table" gives 0 affected rows in MySQL. - // This little hack lets you know how many rows were deleted. - if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { - $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', - 'DELETE FROM \1 WHERE 1=1', $query); - } - } - if ($limit > 0 - && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) - ) { - $query = rtrim($query); - if (substr($query, -1) == ';') { - $query = substr($query, 0, -1); - } - - // LIMIT doesn't always come last in the query - // @see http://dev.mysql.com/doc/refman/5.0/en/select.html - $after = ''; - if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) { - $after = $matches[0]; - $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query); - } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) { - $after = $matches[0]; - $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query); - } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) { - $after = $matches[0]; - $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query); - } - - if ($is_manip) { - return $query . " LIMIT $limit" . $after; - } else { - return $query . " LIMIT $offset, $limit" . $after; - } - } - return $query; - } - - // }}} - // {{{ getServerVersion() - - /** - * return version information about the server - * - * @param bool $native determines if the raw version string should be returned - * @return mixed array/string with version information or MDB2 error object - * @access public - */ - function getServerVersion($native = false) - { - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - if ($this->connected_server_info) { - $server_info = $this->connected_server_info; - } else { - $server_info = @mysql_get_server_info($connection); - } - if (!$server_info) { - return $this->raiseError(null, null, null, - 'Could not get server information', __FUNCTION__); - } - // cache server_info - $this->connected_server_info = $server_info; - if (!$native) { - $tmp = explode('.', $server_info, 3); - if (isset($tmp[2]) && strpos($tmp[2], '-')) { - $tmp2 = explode('-', @$tmp[2], 2); - } else { - $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null; - $tmp2[1] = null; - } - $server_info = array( - 'major' => isset($tmp[0]) ? $tmp[0] : null, - 'minor' => isset($tmp[1]) ? $tmp[1] : null, - 'patch' => $tmp2[0], - 'extra' => $tmp2[1], - 'native' => $server_info, - ); - } - return $server_info; - } - - // }}} - // {{{ _getServerCapabilities() - - /** - * Fetch some information about the server capabilities - * (transactions, subselects, prepared statements, etc). - * - * @access private - */ - function _getServerCapabilities() - { - if (!$this->server_capabilities_checked) { - $this->server_capabilities_checked = true; - - //set defaults - $this->supported['sub_selects'] = 'emulated'; - $this->supported['prepared_statements'] = 'emulated'; - $this->supported['triggers'] = false; - $this->start_transaction = false; - $this->varchar_max_length = 255; - - $server_info = $this->getServerVersion(); - if (is_array($server_info)) { - $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch']; - - if (!version_compare($server_version, '4.1.0', '<')) { - $this->supported['sub_selects'] = true; - $this->supported['prepared_statements'] = true; - } - - // SAVEPOINTs were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB) - if (version_compare($server_version, '4.1.0', '>=')) { - if (version_compare($server_version, '4.1.1', '<')) { - $this->supported['savepoints'] = false; - } - } elseif (version_compare($server_version, '4.0.14', '<')) { - $this->supported['savepoints'] = false; - } - - if (!version_compare($server_version, '4.0.11', '<')) { - $this->start_transaction = true; - } - - if (!version_compare($server_version, '5.0.3', '<')) { - $this->varchar_max_length = 65532; - } - - if (!version_compare($server_version, '5.0.2', '<')) { - $this->supported['triggers'] = true; - } - } - } - } - - // }}} - // {{{ function _skipUserDefinedVariable($query, $position) - - /** - * Utility method, used by prepare() to avoid misinterpreting MySQL user - * defined variables (SELECT @x:=5) for placeholders. - * Check if the placeholder is a false positive, i.e. if it is an user defined - * variable instead. If so, skip it and advance the position, otherwise - * return the current position, which is valid - * - * @param string $query - * @param integer $position current string cursor position - * @return integer $new_position - * @access protected - */ - function _skipUserDefinedVariable($query, $position) - { - $found = strpos(strrev(substr($query, 0, $position)), '@'); - if ($found === false) { - return $position; - } - $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1; - $substring = substr($query, $pos, $position - $pos + 2); - if (preg_match('/^@\w+\s*:=$/', $substring)) { - return $position + 1; //found an user defined variable: skip it - } - return $position; - } - - // }}} - // {{{ prepare() - - /** - * Prepares a query for multiple execution with execute(). - * With some database backends, this is emulated. - * prepare() requires a generic query as string like - * 'INSERT INTO numbers VALUES(?,?)' or - * 'INSERT INTO numbers VALUES(:foo,:bar)'. - * The ? and :name and are placeholders which can be set using - * bindParam() and the query can be sent off using the execute() method. - * The allowed format for :name can be set with the 'bindname_format' option. - * - * @param string $query the query to prepare - * @param mixed $types array that contains the types of the placeholders - * @param mixed $result_types array that contains the types of the columns in - * the result set or MDB2_PREPARE_RESULT, if set to - * MDB2_PREPARE_MANIP the query is handled as a manipulation query - * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders - * @return mixed resource handle for the prepared query on success, a MDB2 - * error on failure - * @access public - * @see bindParam, execute - */ - function &prepare($query, $types = null, $result_types = null, $lobs = array()) - { - if ($this->options['emulate_prepared'] - || $this->supported['prepared_statements'] !== true - ) { - $obj =& parent::prepare($query, $types, $result_types, $lobs); - return $obj; - } - $is_manip = ($result_types === MDB2_PREPARE_MANIP); - $offset = $this->offset; - $limit = $this->limit; - $this->offset = $this->limit = 0; - $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); - $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); - if ($result) { - if (PEAR::isError($result)) { - return $result; - } - $query = $result; - } - $placeholder_type_guess = $placeholder_type = null; - $question = '?'; - $colon = ':'; - $positions = array(); - $position = 0; - while ($position < strlen($query)) { - $q_position = strpos($query, $question, $position); - $c_position = strpos($query, $colon, $position); - if ($q_position && $c_position) { - $p_position = min($q_position, $c_position); - } elseif ($q_position) { - $p_position = $q_position; - } elseif ($c_position) { - $p_position = $c_position; - } else { - break; - } - if (is_null($placeholder_type)) { - $placeholder_type_guess = $query[$p_position]; - } - - $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); - if (PEAR::isError($new_pos)) { - return $new_pos; - } - if ($new_pos != $position) { - $position = $new_pos; - continue; //evaluate again starting from the new position - } - - //make sure this is not part of an user defined variable - $new_pos = $this->_skipUserDefinedVariable($query, $position); - if ($new_pos != $position) { - $position = $new_pos; - continue; //evaluate again starting from the new position - } - - if ($query[$position] == $placeholder_type_guess) { - if (is_null($placeholder_type)) { - $placeholder_type = $query[$p_position]; - $question = $colon = $placeholder_type; - } - if ($placeholder_type == ':') { - $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; - $parameter = preg_replace($regexp, '\\1', $query); - if ($parameter === '') { - $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, - 'named parameter name must match "bindname_format" option', __FUNCTION__); - return $err; - } - $positions[$p_position] = $parameter; - $query = substr_replace($query, '?', $position, strlen($parameter)+1); - } else { - $positions[$p_position] = count($positions); - } - $position = $p_position + 1; - } else { - $position = $p_position; - } - } - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - static $prep_statement_counter = 1; - $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand())); - $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); - $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); - $statement =& $this->_doQuery($query, true, $connection); - if (PEAR::isError($statement)) { - return $statement; - } - - $class_name = 'MDB2_Statement_'.$this->phptype; - $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); - $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); - return $obj; - } - - // }}} - // {{{ replace() - - /** - * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT - * query, except that if there is already a row in the table with the same - * key field values, the old row is deleted before the new row is inserted. - * - * The REPLACE type of query does not make part of the SQL standards. Since - * practically only MySQL implements it natively, this type of query is - * emulated through this method for other DBMS using standard types of - * queries inside a transaction to assure the atomicity of the operation. - * - * @access public - * - * @param string $table name of the table on which the REPLACE query will - * be executed. - * @param array $fields associative array that describes the fields and the - * values that will be inserted or updated in the specified table. The - * indexes of the array are the names of all the fields of the table. The - * values of the array are also associative arrays that describe the - * values and other properties of the table fields. - * - * Here follows a list of field properties that need to be specified: - * - * value: - * Value to be assigned to the specified field. This value may be - * of specified in database independent type format as this - * function can perform the necessary datatype conversions. - * - * Default: - * this property is required unless the Null property - * is set to 1. - * - * type - * Name of the type of the field. Currently, all types Metabase - * are supported except for clob and blob. - * - * Default: no type conversion - * - * null - * Boolean property that indicates that the value for this field - * should be set to null. - * - * The default value for fields missing in INSERT queries may be - * specified the definition of a table. Often, the default value - * is already null, but since the REPLACE may be emulated using - * an UPDATE query, make sure that all fields of the table are - * listed in this function argument array. - * - * Default: 0 - * - * key - * Boolean property that indicates that this field should be - * handled as a primary key or at least as part of the compound - * unique index of the table that will determine the row that will - * updated if it exists or inserted a new row otherwise. - * - * This function will fail if no key field is specified or if the - * value of a key field is set to null because fields that are - * part of unique index they may not be null. - * - * Default: 0 - * - * @see http://dev.mysql.com/doc/refman/5.0/en/replace.html - * @return mixed MDB2_OK on success, a MDB2 error on failure - */ - function replace($table, $fields) - { - $count = count($fields); - $query = $values = ''; - $keys = $colnum = 0; - for (reset($fields); $colnum < $count; next($fields), $colnum++) { - $name = key($fields); - if ($colnum > 0) { - $query .= ','; - $values.= ','; - } - $query.= $this->quoteIdentifier($name, true); - if (isset($fields[$name]['null']) && $fields[$name]['null']) { - $value = 'NULL'; - } else { - $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; - $value = $this->quote($fields[$name]['value'], $type); - if (PEAR::isError($value)) { - return $value; - } - } - $values.= $value; - if (isset($fields[$name]['key']) && $fields[$name]['key']) { - if ($value === 'NULL') { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'key value '.$name.' may not be NULL', __FUNCTION__); - } - $keys++; - } - } - if ($keys == 0) { - return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, - 'not specified which fields are keys', __FUNCTION__); - } - - $connection = $this->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $table = $this->quoteIdentifier($table, true); - $query = "REPLACE INTO $table ($query) VALUES ($values)"; - $result =& $this->_doQuery($query, true, $connection); - if (PEAR::isError($result)) { - return $result; - } - return $this->_affectedRows($connection, $result); - } - - // }}} - // {{{ nextID() - - /** - * Returns the next free id of a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true the sequence is - * automatic created, if it - * not exists - * - * @return mixed MDB2 Error Object or id - * @access public - */ - function nextID($seq_name, $ondemand = true) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); - $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $this->expectError(MDB2_ERROR_NOSUCHTABLE); - $result =& $this->_doQuery($query, true); - $this->popExpect(); - $this->popErrorHandling(); - if (PEAR::isError($result)) { - if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { - $this->loadModule('Manager', null, true); - $result = $this->manager->createSequence($seq_name); - if (PEAR::isError($result)) { - return $this->raiseError($result, null, null, - 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); - } else { - return $this->nextID($seq_name, false); - } - } - return $result; - } - $value = $this->lastInsertID(); - if (is_numeric($value)) { - $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; - $result =& $this->_doQuery($query, true); - if (PEAR::isError($result)) { - $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; - } - } - return $value; - } - - // }}} - // {{{ lastInsertID() - - /** - * Returns the autoincrement ID if supported or $id or fetches the current - * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) - * - * @param string $table name of the table into which a new row was inserted - * @param string $field name of the field into which a new row was inserted - * @return mixed MDB2 Error Object or id - * @access public - */ - function lastInsertID($table = null, $field = null) - { - // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 - return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); - } - - // }}} - // {{{ currID() - - /** - * Returns the current id of a sequence - * - * @param string $seq_name name of the sequence - * @return mixed MDB2 Error Object or id - * @access public - */ - function currID($seq_name) - { - $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); - $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); - $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; - return $this->queryOne($query, 'integer'); - } -} - -/** - * MDB2 MySQL result driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Result_mysql extends MDB2_Result_Common -{ - // }}} - // {{{ fetchRow() - - /** - * Fetch a row and insert the data into an existing array. - * - * @param int $fetchmode how the array data should be indexed - * @param int $rownum number of the row where the data can be found - * @return int data array on success, a MDB2 error on failure - * @access public - */ - function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) - { - if (!is_null($rownum)) { - $seek = $this->seek($rownum); - if (PEAR::isError($seek)) { - return $seek; - } - } - if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { - $fetchmode = $this->db->fetchmode; - } - if ($fetchmode & MDB2_FETCHMODE_ASSOC) { - $row = @mysql_fetch_assoc($this->result); - if (is_array($row) - && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE - ) { - $row = array_change_key_case($row, $this->db->options['field_case']); - } - } else { - $row = @mysql_fetch_row($this->result); - } - - if (!$row) { - if ($this->result === false) { - $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - return $err; - } - $null = null; - return $null; - } - $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; - $rtrim = false; - if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { - if (empty($this->types)) { - $mode += MDB2_PORTABILITY_RTRIM; - } else { - $rtrim = true; - } - } - if ($mode) { - $this->db->_fixResultArrayValues($row, $mode); - } - if (!empty($this->types)) { - $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); - } - if (!empty($this->values)) { - $this->_assignBindColumns($row); - } - if ($fetchmode === MDB2_FETCHMODE_OBJECT) { - $object_class = $this->db->options['fetch_class']; - if ($object_class == 'stdClass') { - $row = (object) $row; - } else { - $row = new $object_class($row); - } - } - ++$this->rownum; - return $row; - } - - // }}} - // {{{ _getColumnNames() - - /** - * Retrieve the names of columns returned by the DBMS in a query result. - * - * @return mixed Array variable that holds the names of columns as keys - * or an MDB2 error on failure. - * Some DBMS may not return any columns when the result set - * does not contain any rows. - * @access private - */ - function _getColumnNames() - { - $columns = array(); - $numcols = $this->numCols(); - if (PEAR::isError($numcols)) { - return $numcols; - } - for ($column = 0; $column < $numcols; $column++) { - $column_name = @mysql_field_name($this->result, $column); - $columns[$column_name] = $column; - } - if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { - $columns = array_change_key_case($columns, $this->db->options['field_case']); - } - return $columns; - } - - // }}} - // {{{ numCols() - - /** - * Count the number of columns returned by the DBMS in a query result. - * - * @return mixed integer value with the number of columns, a MDB2 error - * on failure - * @access public - */ - function numCols() - { - $cols = @mysql_num_fields($this->result); - if (is_null($cols)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return count($this->types); - } - return $this->db->raiseError(null, null, null, - 'Could not get column count', __FUNCTION__); - } - return $cols; - } - - // }}} - // {{{ free() - - /** - * Free the internal resources associated with result. - * - * @return boolean true on success, false if result is invalid - * @access public - */ - function free() - { - if (is_resource($this->result) && $this->db->connection) { - $free = @mysql_free_result($this->result); - if ($free === false) { - return $this->db->raiseError(null, null, null, - 'Could not free result', __FUNCTION__); - } - } - $this->result = false; - return MDB2_OK; - } -} - -/** - * MDB2 MySQL buffered result driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_BufferedResult_mysql extends MDB2_Result_mysql -{ - // }}} - // {{{ seek() - - /** - * Seek to a specific row in a result set - * - * @param int $rownum number of the row where the data can be found - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function seek($rownum = 0) - { - if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { - if ($this->result === false) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return MDB2_OK; - } - return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, - 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); - } - $this->rownum = $rownum - 1; - return MDB2_OK; - } - - // }}} - // {{{ valid() - - /** - * Check if the end of the result set has been reached - * - * @return mixed true or false on sucess, a MDB2 error on failure - * @access public - */ - function valid() - { - $numrows = $this->numRows(); - if (PEAR::isError($numrows)) { - return $numrows; - } - return $this->rownum < ($numrows - 1); - } - - // }}} - // {{{ numRows() - - /** - * Returns the number of rows in a result object - * - * @return mixed MDB2 Error Object or the number of rows - * @access public - */ - function numRows() - { - $rows = @mysql_num_rows($this->result); - if (false === $rows) { - if (false === $this->result) { - return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, - 'resultset has already been freed', __FUNCTION__); - } elseif (is_null($this->result)) { - return 0; - } - return $this->db->raiseError(null, null, null, - 'Could not get row count', __FUNCTION__); - } - return $rows; - } -} - -/** - * MDB2 MySQL statement driver - * - * @package MDB2 - * @category Database - * @author Lukas Smith - */ -class MDB2_Statement_mysql extends MDB2_Statement_Common -{ - // {{{ _execute() - - /** - * Execute a prepared query statement helper method. - * - * @param mixed $result_class string which specifies which result class to use - * @param mixed $result_wrap_class string which specifies which class to wrap results in - * - * @return mixed MDB2_Result or integer (affected rows) on success, - * a MDB2 error on failure - * @access private - */ - function &_execute($result_class = true, $result_wrap_class = false) - { - if (is_null($this->statement)) { - $result =& parent::_execute($result_class, $result_wrap_class); - return $result; - } - $this->db->last_query = $this->query; - $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); - if ($this->db->getOption('disable_query')) { - $result = $this->is_manip ? 0 : null; - return $result; - } - - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - - $query = 'EXECUTE '.$this->statement; - if (!empty($this->positions)) { - $parameters = array(); - foreach ($this->positions as $parameter) { - if (!array_key_exists($parameter, $this->values)) { - return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, - 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); - } - $value = $this->values[$parameter]; - $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; - if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) { - if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { - if ($match[1] == 'file://') { - $value = $match[2]; - } - $value = @fopen($value, 'r'); - $close = true; - } - if (is_resource($value)) { - $data = ''; - while (!@feof($value)) { - $data.= @fread($value, $this->db->options['lob_buffer_length']); - } - if ($close) { - @fclose($value); - } - $value = $data; - } - } - $quoted = $this->db->quote($value, $type); - if (PEAR::isError($quoted)) { - return $quoted; - } - $param_query = 'SET @'.$parameter.' = '.$quoted; - $result = $this->db->_doQuery($param_query, true, $connection); - if (PEAR::isError($result)) { - return $result; - } - } - $query.= ' USING @'.implode(', @', array_values($this->positions)); - } - - $result = $this->db->_doQuery($query, $this->is_manip, $connection); - if (PEAR::isError($result)) { - return $result; - } - - if ($this->is_manip) { - $affected_rows = $this->db->_affectedRows($connection, $result); - return $affected_rows; - } - - $result =& $this->db->_wrapResult($result, $this->result_types, - $result_class, $result_wrap_class, $this->limit, $this->offset); - $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); - return $result; - } - - // }}} - // {{{ free() - - /** - * Release resources allocated for the specified prepared query. - * - * @return mixed MDB2_OK on success, a MDB2 error on failure - * @access public - */ - function free() - { - if (is_null($this->positions)) { - return $this->db->raiseError(MDB2_ERROR, null, null, - 'Prepared statement has already been freed', __FUNCTION__); - } - $result = MDB2_OK; - - if (!is_null($this->statement)) { - $connection = $this->db->getConnection(); - if (PEAR::isError($connection)) { - return $connection; - } - $query = 'DEALLOCATE PREPARE '.$this->statement; - $result = $this->db->_doQuery($query, true, $connection); - } - - parent::free(); - return $result; - } -} + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.214 2008/11/16 21:45:08 quipo Exp $ +// + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysql extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`'); + + var $sql_comments = array( + array('start' => '-- ', 'end' => "\n", 'escape' => false), + array('start' => '#', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + var $server_capabilities_checked = false; + + var $start_transaction = false; + + var $varchar_max_length = 255; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'mysql'; + $this->dbsyntax = 'mysql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = false; + $this->supported['savepoints'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['sub_selects'] = 'emulated'; + $this->supported['triggers'] = false; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['default_table_type'] = ''; + $this->options['max_identifiers_length'] = 64; + + $this->_reCheckSupportedOptions(); + } + + // }}} + // {{{ _reCheckSupportedOptions() + + /** + * If the user changes certain options, other capabilities may depend + * on the new settings, so we need to check them (again). + * + * @access private + */ + function _reCheckSupportedOptions() + { + $this->supported['transactions'] = $this->options['use_transactions']; + $this->supported['savepoints'] = $this->options['use_transactions']; + if ($this->options['default_table_type']) { + switch (strtoupper($this->options['default_table_type'])) { + case 'BLACKHOLE': + case 'MEMORY': + case 'ARCHIVE': + case 'CSV': + case 'HEAP': + case 'ISAM': + case 'MERGE': + case 'MRG_ISAM': + case 'ISAM': + case 'MRG_MYISAM': + case 'MYISAM': + $this->supported['savepoints'] = false; + $this->supported['transactions'] = false; + $this->warnings[] = $this->options['default_table_type'] . + ' is not a supported default table type'; + break; + } + } + } + + // }}} + // {{{ function setOption($option, $value) + + /** + * set the option for the db class + * + * @param string option name + * @param mixed value for the option + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + */ + function setOption($option, $value) + { + $res = parent::setOption($option, $value); + $this->_reCheckSupportedOptions(); + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if ($this->connection) { + $native_code = @mysql_errno($this->connection); + $native_msg = @mysql_error($this->connection); + } else { + $native_code = @mysql_errno(); + $native_msg = @mysql_error(); + } + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1000 => MDB2_ERROR_INVALID, //hashchk + 1001 => MDB2_ERROR_INVALID, //isamchk + 1004 => MDB2_ERROR_CANNOT_CREATE, + 1005 => MDB2_ERROR_CANNOT_CREATE, + 1006 => MDB2_ERROR_CANNOT_CREATE, + 1007 => MDB2_ERROR_ALREADY_EXISTS, + 1008 => MDB2_ERROR_CANNOT_DROP, + 1009 => MDB2_ERROR_CANNOT_DROP, + 1010 => MDB2_ERROR_CANNOT_DROP, + 1011 => MDB2_ERROR_CANNOT_DELETE, + 1022 => MDB2_ERROR_ALREADY_EXISTS, + 1029 => MDB2_ERROR_NOT_FOUND, + 1032 => MDB2_ERROR_NOT_FOUND, + 1044 => MDB2_ERROR_ACCESS_VIOLATION, + 1045 => MDB2_ERROR_ACCESS_VIOLATION, + 1046 => MDB2_ERROR_NODBSELECTED, + 1048 => MDB2_ERROR_CONSTRAINT, + 1049 => MDB2_ERROR_NOSUCHDB, + 1050 => MDB2_ERROR_ALREADY_EXISTS, + 1051 => MDB2_ERROR_NOSUCHTABLE, + 1054 => MDB2_ERROR_NOSUCHFIELD, + 1060 => MDB2_ERROR_ALREADY_EXISTS, + 1061 => MDB2_ERROR_ALREADY_EXISTS, + 1062 => MDB2_ERROR_ALREADY_EXISTS, + 1064 => MDB2_ERROR_SYNTAX, + 1067 => MDB2_ERROR_INVALID, + 1072 => MDB2_ERROR_NOT_FOUND, + 1086 => MDB2_ERROR_ALREADY_EXISTS, + 1091 => MDB2_ERROR_NOT_FOUND, + 1100 => MDB2_ERROR_NOT_LOCKED, + 1109 => MDB2_ERROR_NOT_FOUND, + 1125 => MDB2_ERROR_ALREADY_EXISTS, + 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 1138 => MDB2_ERROR_INVALID, + 1142 => MDB2_ERROR_ACCESS_VIOLATION, + 1143 => MDB2_ERROR_ACCESS_VIOLATION, + 1146 => MDB2_ERROR_NOSUCHTABLE, + 1149 => MDB2_ERROR_SYNTAX, + 1169 => MDB2_ERROR_CONSTRAINT, + 1176 => MDB2_ERROR_NOT_FOUND, + 1177 => MDB2_ERROR_NOSUCHTABLE, + 1213 => MDB2_ERROR_DEADLOCK, + 1216 => MDB2_ERROR_CONSTRAINT, + 1217 => MDB2_ERROR_CONSTRAINT, + 1227 => MDB2_ERROR_ACCESS_VIOLATION, + 1235 => MDB2_ERROR_CANNOT_CREATE, + 1299 => MDB2_ERROR_INVALID_DATE, + 1300 => MDB2_ERROR_INVALID, + 1304 => MDB2_ERROR_ALREADY_EXISTS, + 1305 => MDB2_ERROR_NOT_FOUND, + 1306 => MDB2_ERROR_CANNOT_DROP, + 1307 => MDB2_ERROR_CANNOT_CREATE, + 1334 => MDB2_ERROR_CANNOT_ALTER, + 1339 => MDB2_ERROR_NOT_FOUND, + 1356 => MDB2_ERROR_INVALID, + 1359 => MDB2_ERROR_ALREADY_EXISTS, + 1360 => MDB2_ERROR_NOT_FOUND, + 1363 => MDB2_ERROR_NOT_FOUND, + 1365 => MDB2_ERROR_DIVZERO, + 1451 => MDB2_ERROR_CONSTRAINT, + 1452 => MDB2_ERROR_CONSTRAINT, + 1542 => MDB2_ERROR_CANNOT_DROP, + 1546 => MDB2_ERROR_CONSTRAINT, + 1582 => MDB2_ERROR_CONSTRAINT, + 2003 => MDB2_ERROR_CONNECT_FAILED, + 2019 => MDB2_ERROR_INVALID, + ); + } + if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { + $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; + $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $text = @mysql_real_escape_string($text, $connection); + return $text; + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + $this->_getServerCapabilities(); + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $server_info = $this->getServerVersion(); + if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + return MDB2_OK; + } + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + $result =& $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 1'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 1'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ _doConnect() + + /** + * do the grunt work of the connect + * + * @return connection on success or MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $persistent = false) + { + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $params = array(); + if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') { + $params[0] = ':' . $this->dsn['socket']; + } else { + $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec'] + : 'localhost'; + if ($this->dsn['port']) { + $params[0].= ':' . $this->dsn['port']; + } + } + $params[] = $username ? $username : null; + $params[] = $password ? $password : null; + if (!$persistent) { + if ($this->_isNewLinkSet()) { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($this->dsn['client_flags']) + ? $this->dsn['client_flags'] : null; + } + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $connection = @call_user_func_array($connect_function, $params); + if (!$connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + $err, __FUNCTION__); + } else { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + $this->disconnect(false); + return $result; + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return MDB2_OK on success, MDB2 Error Object on failure + * @access public + */ + function connect() + { + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + $connection = $this->_doConnect( + $this->dsn['username'], + $this->dsn['password'], + $this->options['persistent'] + ); + if (PEAR::isError($connection)) { + return $connection; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = ''; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + if ($this->database_name) { + if ($this->database_name != $this->connected_database_name) { + if (!@mysql_select_db($this->database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$this->database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $this->database_name; + } + } + + $this->_getServerCapabilities(); + + return MDB2_OK; + } + + // }}} + // {{{ setCharset() + + /** + * Set the charset on the current connection + * + * @param string charset (or array(charset, collation)) + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + $collation = null; + if (is_array($charset) && 2 == count($charset)) { + $collation = array_pop($charset); + $charset = array_pop($charset); + } + $client_info = mysql_get_client_info(); + if (function_exists('mysql_set_charset') && version_compare($client_info, '5.0.6')) { + if (!$result = mysql_set_charset($charset, $connection)) { + $err =& $this->raiseError(null, null, null, + 'Could not set client character set', __FUNCTION__); + return $err; + } + return $result; + } + $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'"; + if (!is_null($collation)) { + $query .= " COLLATE '".mysqli_real_escape_string($connection, $collation)."'"; + } + return $this->_doQuery($query, true, $connection); + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name is exists? + * + * @param string $name name of the database that should be checked + * + * @return mixed true/false on success, a MDB2 error on failure + * @access public + */ + function databaseExists($name) + { + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $result = @mysql_select_db($name, $connection); + @mysql_close($connection); + + return $result; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @param boolean $force if the disconnect should be forced even if the + * connection is opened persistently + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if ($this->in_transaction) { + $dsn = $this->dsn; + $database_name = $this->database_name; + $persistent = $this->options['persistent']; + $this->dsn = $this->connected_dsn; + $this->database_name = $this->connected_database_name; + $this->options['persistent'] = $this->opened_persistent; + $this->rollback(); + $this->dsn = $dsn; + $this->database_name = $database_name; + $this->options['persistent'] = $persistent; + } + + if (!$this->opened_persistent || $force) { + $ok = @mysql_close($this->connection); + if (!$ok) { + return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, + null, null, null, __FUNCTION__); + } + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ standaloneQuery() + + /** + * execute a query as DBA + * + * @param string $query the SQL query + * @param mixed $types array that contains the types of the columns in + * the result set + * @param boolean $is_manip if the query is a manipulation query + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function &standaloneQuery($query, $types = null, $is_manip = false) + { + $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; + $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; + $connection = $this->_doConnect($user, $pass, $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $result =& $this->_doQuery($query, $is_manip, $connection, $this->database_name); + if (!PEAR::isError($result)) { + $result = $this->_affectedRows($connection, $result); + } + + @mysql_close($connection); + return $result; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mysql_select_db($database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $database_name; + } + } + + $function = $this->options['result_buffering'] + ? 'mysql_query' : 'mysql_unbuffered_query'; + $result = @$function($query, $connection); + if (!$result) { + $err =& $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @mysql_affected_rows($connection); + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + + // LIMIT doesn't always come last in the query + // @see http://dev.mysql.com/doc/refman/5.0/en/select.html + $after = ''; + if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query); + } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query); + } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query); + } + + if ($is_manip) { + return $query . " LIMIT $limit" . $after; + } else { + return $query . " LIMIT $offset, $limit" . $after; + } + } + return $query; + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param bool $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = @mysql_get_server_info($connection); + } + if (!$server_info) { + return $this->raiseError(null, null, null, + 'Could not get server information', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + if (isset($tmp[2]) && strpos($tmp[2], '-')) { + $tmp2 = explode('-', @$tmp[2], 2); + } else { + $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null; + $tmp2[1] = null; + } + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => $tmp2[0], + 'extra' => $tmp2[1], + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ _getServerCapabilities() + + /** + * Fetch some information about the server capabilities + * (transactions, subselects, prepared statements, etc). + * + * @access private + */ + function _getServerCapabilities() + { + if (!$this->server_capabilities_checked) { + $this->server_capabilities_checked = true; + + //set defaults + $this->supported['sub_selects'] = 'emulated'; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['triggers'] = false; + $this->start_transaction = false; + $this->varchar_max_length = 255; + + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + $server_version = $server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch']; + + if (!version_compare($server_version, '4.1.0', '<')) { + $this->supported['sub_selects'] = true; + $this->supported['prepared_statements'] = true; + } + + // SAVEPOINTs were introduced in MySQL 4.0.14 and 4.1.1 (InnoDB) + if (version_compare($server_version, '4.1.0', '>=')) { + if (version_compare($server_version, '4.1.1', '<')) { + $this->supported['savepoints'] = false; + } + } elseif (version_compare($server_version, '4.0.14', '<')) { + $this->supported['savepoints'] = false; + } + + if (!version_compare($server_version, '4.0.11', '<')) { + $this->start_transaction = true; + } + + if (!version_compare($server_version, '5.0.3', '<')) { + $this->varchar_max_length = 65532; + } + + if (!version_compare($server_version, '5.0.2', '<')) { + $this->supported['triggers'] = true; + } + } + } + } + + // }}} + // {{{ function _skipUserDefinedVariable($query, $position) + + /** + * Utility method, used by prepare() to avoid misinterpreting MySQL user + * defined variables (SELECT @x:=5) for placeholders. + * Check if the placeholder is a false positive, i.e. if it is an user defined + * variable instead. If so, skip it and advance the position, otherwise + * return the current position, which is valid + * + * @param string $query + * @param integer $position current string cursor position + * @return integer $new_position + * @access protected + */ + function _skipUserDefinedVariable($query, $position) + { + $found = strpos(strrev(substr($query, 0, $position)), '@'); + if ($found === false) { + return $position; + } + $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1; + $substring = substr($query, $pos, $position - $pos + 2); + if (preg_match('/^@\w+\s*:=$/', $substring)) { + return $position + 1; //found an user defined variable: skip it + } + return $position; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function &prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared'] + || $this->supported['prepared_statements'] !== true + ) { + $obj =& parent::prepare($query, $types, $result_types, $lobs); + return $obj; + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + //make sure this is not part of an user defined variable + $new_pos = $this->_skipUserDefinedVariable($query, $position); + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $positions[$p_position] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + static $prep_statement_counter = 1; + $statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand())); + $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); + $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); + $statement =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($statement)) { + return $statement; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the old row is deleted before the new row is inserted. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL implements it natively, this type of query is + * emulated through this method for other DBMS using standard types of + * queries inside a transaction to assure the atomicity of the operation. + * + * @access public + * + * @param string $table name of the table on which the REPLACE query will + * be executed. + * @param array $fields associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. The + * values of the array are also associative arrays that describe the + * values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value: + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: + * this property is required unless the Null property + * is set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * Boolean property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * Boolean property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @see http://dev.mysql.com/doc/refman/5.0/en/replace.html + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function replace($table, $fields) + { + $count = count($fields); + $query = $values = ''; + $keys = $colnum = 0; + for (reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if ($colnum > 0) { + $query .= ','; + $values.= ','; + } + $query.= $this->quoteIdentifier($name, true); + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $table = $this->quoteIdentifier($table, true); + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result =& $this->_doQuery($query, true); + $this->popExpect(); + $this->popErrorHandling(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 + return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 MySQL result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_mysql extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @mysql_fetch_assoc($this->result); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @mysql_fetch_row($this->result); + } + + if (!$row) { + if ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @mysql_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * @access public + */ + function numCols() + { + $cols = @mysql_num_fields($this->result); + if (is_null($cols)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + if (is_resource($this->result) && $this->db->connection) { + $free = @mysql_free_result($this->result); + if ($free === false) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 MySQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_mysql extends MDB2_Result_mysql +{ + // }}} + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mysql_num_rows($this->result); + if (false === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 MySQL statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_mysql extends MDB2_Statement_Common +{ + // {{{ _execute() + + /** + * Execute a prepared query statement helper method. + * + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access private + */ + function &_execute($result_class = true, $result_wrap_class = false) + { + if (is_null($this->statement)) { + $result =& parent::_execute($result_class, $result_wrap_class); + return $result; + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 'EXECUTE '.$this->statement; + if (!empty($this->positions)) { + $parameters = array(); + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) { + if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + $close = true; + } + if (is_resource($value)) { + $data = ''; + while (!@feof($value)) { + $data.= @fread($value, $this->db->options['lob_buffer_length']); + } + if ($close) { + @fclose($value); + } + $value = $data; + } + } + $quoted = $this->db->quote($value, $type); + if (PEAR::isError($quoted)) { + return $quoted; + } + $param_query = 'SET @'.$parameter.' = '.$quoted; + $result = $this->db->_doQuery($param_query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + } + $query.= ' USING @'.implode(', @', array_values($this->positions)); + } + + $result = $this->db->_doQuery($query, $this->is_manip, $connection); + if (PEAR::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result =& $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (!is_null($this->statement)) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} ?> \ No newline at end of file diff --git a/inc/lib_base.php b/inc/lib_base.php index cfd9d4a567..775504a656 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -336,7 +336,7 @@ class OC_DB { 'portability' => MDB2_PORTABILITY_ALL, 'log_line_break' => '
', 'idxname_format' => '%s', - 'debug' => false, + 'debug' => true, 'quote_identifier' => true, ); if($CONFIG_DBTYPE=='sqlite'){ @@ -363,12 +363,12 @@ class OC_DB { ); } self::$DBConnection=&MDB2::factory($dsn,$options); - if (@PEAR::isError(self::$DBConnection)) { + 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::$schema=&MDB2_Schema::factory($dsn,$options); + self::$schema=&MDB2_Schema::factory(self::$DBConnection); } } @@ -634,12 +634,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 4110e77cf4..8ce83b1666 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -209,7 +209,6 @@ class OC_CONFIG{ self::filldatabase(); } }catch(Exception $e){ - echo 'testin'; $error.='error while trying to fill the database
'; } if($CONFIG_DBTYPE=='sqlite'){ diff --git a/inc/lib_user.php b/inc/lib_user.php index 089d0cb54a..5a7b92d478 100755 --- a/inc/lib_user.php +++ b/inc/lib_user.php @@ -63,7 +63,7 @@ class OC_USER { * */ public static function createuser($username,$password){ - if(OC_USER::getuserid($username)!=0){ + if(OC_USER::getuserid($username,true)!=0){ return false; }else{ $usernameclean=strtolower($username); @@ -124,7 +124,7 @@ class OC_USER { * */ public static function creategroup($groupname){ - if(OC_USER::getgroupid($groupname)==0){ + if(OC_USER::getgroupid($groupname,true)==0){ $groupname=OC_DB::escape($groupname); $query="INSERT INTO `groups` (`group_name`) VALUES ('$groupname')"; $result=OC_DB::query($query); @@ -138,9 +138,9 @@ class OC_USER { * get the id of a user * */ - public static function getuserid($username){ + public static function getuserid($username,$nocache=false){ $usernameclean=strtolower($username); - if(isset($_SESSION['user_id_cache'][$usernameclean])){//try to use cached value to save an sql query + 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); @@ -161,8 +161,8 @@ class OC_USER { * get the id of a group * */ - public static function getgroupid($groupname){ - if(isset($_SESSION['group_id_cache'][$groupname])){//try to use cached value to save an sql query + public static function getgroupid($groupname,$nocache=false){ + 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); @@ -183,8 +183,8 @@ class OC_USER { * get the name of a group * */ - public static function getgroupname($groupid){ - if($name=array_search($groupid,$_SESSION['group_id_cache'])){//try to use cached value to save an sql query + public static function getgroupname($groupid,$nocache=false){ + 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; From a26ef8784044b72d054c4942598650040b51b18c Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 5 Jul 2010 13:39:53 +0200 Subject: [PATCH 18/24] Fix some database errors --- inc/HTTP/WebDAV/Server/Filesystem.php | 7 ++++--- inc/lib_config.php | 1 - inc/lib_log.php | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/inc/HTTP/WebDAV/Server/Filesystem.php b/inc/HTTP/WebDAV/Server/Filesystem.php index b99bbdbb81..a41d368730 100755 --- a/inc/HTTP/WebDAV/Server/Filesystem.php +++ b/inc/HTTP/WebDAV/Server/Filesystem.php @@ -324,6 +324,7 @@ */ function PUT(&$options) { + error_log("put $fspath"); $fspath = $options["path"]; $dir = dirname($fspath); if (!OC_FILESYSTEM::file_exists($dir) || !OC_FILESYSTEM::is_dir($dir)) { @@ -641,14 +642,14 @@ return true; } else {//check for indirect refresh $query = "SELECT * - FROM locks + FROM {$CONFIG_DBTABLEPREFIX}locks WHERE recursive = 1 "; $res = OC_DB::select($query); foreach($res as $row){ if(strpos($options['path'],$row['path'])==0){//are we a child of a folder with an recursive lock $where = "WHERE path = '$row[path]' AND token = '$options[update]'"; - $query = "UPDATE `locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + $query = "UPDATE `{$CONFIG_DBTABLEPREFIX}locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; OC_DB::query($query); $options['owner'] = $row['owner']; $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared"; @@ -727,7 +728,7 @@ }else{ //check for recursive locks; $query = "SELECT * - FROM locks + FROM {$CONFIG_DBTABLEPREFIX}locks WHERE recursive = 1 "; $res = OC_DB::select($query); diff --git a/inc/lib_config.php b/inc/lib_config.php index 78e10f9474..dad2fbf815 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -336,7 +336,6 @@ class OC_CONFIG{ $result = pg_exec($connection, $query); } } - global $CONFIG_DBTABLEPREFIX; } } ?> diff --git a/inc/lib_log.php b/inc/lib_log.php index f163fb9fe9..ce46e9a683 100755 --- a/inc/lib_log.php +++ b/inc/lib_log.php @@ -69,7 +69,6 @@ class OC_LOG { $user=$_SESSION['username_clean']; $result = OC_DB::select('select `timestamp`,`user`,`type`,`message` from '.$CONFIG_DBTABLEPREFIX.'log where user=\''.$user.'\' order by timestamp desc limit 20'); } - $result = OC_DB::select($query); foreach($result as $entry){ echo('
'); echo(''); From c718716e571b66178e6397b7eb3ec169cc9a5a20 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 5 Jul 2010 15:31:59 +0200 Subject: [PATCH 19/24] Do not use table perfix for sqlite --- inc/lib_config.php | 4 +++- inc/templates/adminform.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/inc/lib_config.php b/inc/lib_config.php index dad2fbf815..77fd68a189 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -186,11 +186,13 @@ class OC_CONFIG{ //create/fill database $CONFIG_DBTYPE=$dbtype; $CONFIG_DBNAME=$_POST['dbname']; - $CONFIG_DBTABLEPREFIX=$_POST['dbtableprefix']; if($dbtype!='sqlite'){ + $CONFIG_DBTABLEPREFIX=$_POST['dbtableprefix']; $CONFIG_DBHOST=$_POST['dbhost']; $CONFIG_DBUSER=$_POST['dbuser']; $CONFIG_DBPASSWORD=$_POST['dbpassword']; + }else{ + $_POST['dbtableprefix']=''; } try{ if(isset($_POST['createdatabase']) and $CONFIG_DBTYPE!='sqlite'){ diff --git a/inc/templates/adminform.php b/inc/templates/adminform.php index 3adbd04ee7..b962ac6f31 100755 --- a/inc/templates/adminform.php +++ b/inc/templates/adminform.php @@ -28,7 +28,7 @@ function showBackupPath(){ function dbtypechange(){ var dropdown=action=document.getElementById('dbtype'); var type=dropdown.options[dropdown.selectedIndex].value; - var inputs=Array('dbhost','dbuser','dbpass','dbpass_retype','dbcreaterow','dbAdminPwd','dbAdminUser','dbname','dbfill'); + var inputs=Array('dbhost','dbuser','dbpass','dbpass_retype','dbcreaterow','dbAdminPwd','dbAdminUser','dbname','dbfill','dbtableprefix'); var id,element; if(type=='sqlite'){ for(i in inputs){ From 761b54fc17aeedc77e07a5ec5ff8f412ea46e798 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 6 Jul 2010 10:37:11 +0200 Subject: [PATCH 20/24] fix table prefix not being set correctly to an empty string on first run when using sqlite --- inc/lib_config.php | 1 + 1 file changed, 1 insertion(+) diff --git a/inc/lib_config.php b/inc/lib_config.php index 77fd68a189..cbf86b17aa 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -193,6 +193,7 @@ class OC_CONFIG{ $CONFIG_DBPASSWORD=$_POST['dbpassword']; }else{ $_POST['dbtableprefix']=''; + $CONFIG_DBTABLEPREFIX=''; } try{ if(isset($_POST['createdatabase']) and $CONFIG_DBTYPE!='sqlite'){ From 9fe7f992d9323f4509d7fa7c0f895c22141dcc06 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 6 Jul 2010 12:50:37 +0200 Subject: [PATCH 21/24] Initial work on transering files between servers --- files/api.php | 2 ++ files/pull.php | 11 ++++++++ inc/lib_connect.php | 67 +++++++++++++++++++++++++++++++++++++++++++++ inc/lib_files.php | 30 ++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 files/pull.php diff --git a/files/api.php b/files/api.php index 014bbb56bb..fa94a51254 100755 --- a/files/api.php +++ b/files/api.php @@ -72,6 +72,8 @@ if($arguments['action']){ echo 'false'; } break; + case 'pull': + return OC_FILES::pull($arguments['source'],$arguments['token'],$arguments['dir'],$arguments['file']); } } diff --git a/files/pull.php b/files/pull.php new file mode 100644 index 0000000000..1cc8242584 --- /dev/null +++ b/files/pull.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/inc/lib_connect.php b/inc/lib_connect.php index ddf03eb6e7..0c56d1a01d 100644 --- a/inc/lib_connect.php +++ b/inc/lib_connect.php @@ -78,6 +78,7 @@ class OC_REMOTE_CLOUD{ $result=trim(curl_exec($ch)); $info=curl_getinfo($ch); $httpCode=$info['http_code']; + curl_close($ch); if($httpCode==200 or $httpCode==0){ return json_decode($result,$assoc); }else{ @@ -130,6 +131,48 @@ 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); + $token=sha1(uniqid().$source); + $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 +189,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..20d40669d7 100755 --- a/inc/lib_files.php +++ b/inc/lib_files.php @@ -242,6 +242,36 @@ class OC_FILES { static function getMimeType($path){ return OC_FILESYSTEM::getMimeType($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_POST,count($parameters)); + curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string); + 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=''){ From 581328fbc1a9c56654ddd49c407b5b6cc28fbc50 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 6 Jul 2010 19:57:08 +0200 Subject: [PATCH 22/24] Fix custom database names --- db_structure.xml | 14 +++++++------- inc/lib_base.php | 12 +++++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index f5d0f24505..7efb722bd7 100755 --- a/db_structure.xml +++ b/db_structure.xml @@ -1,7 +1,7 @@ - owncloud + *dbname* true false @@ -9,7 +9,7 @@
'.date($CONFIG_DATEFORMAT,$entry['timestamp']).'
- groups + *dbprefix*groups @@ -45,7 +45,7 @@
- locks + *dbprefix*locks @@ -173,7 +173,7 @@
- log + *dbprefix*log @@ -224,7 +224,7 @@
- properties + *dbprefix*properties @@ -289,7 +289,7 @@
- user_group + *dbprefix*user_group @@ -324,7 +324,7 @@
- users + *dbprefix*users diff --git a/inc/lib_base.php b/inc/lib_base.php index 67dbcb2eac..5c9906d361 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -530,7 +530,17 @@ class OC_DB { static function createDBFromStructure($file){ OC_DB::connect(); - $definition=@self::$schema->parseDatabaseDefinitionFile($file); + 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()); } From b38aa771d1ed79ec53ea547e62f9a2c1dd89f5fa Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 7 Jul 2010 12:30:30 +0200 Subject: [PATCH 23/24] Initial version of remote ownCloud access through filesystem abstraction --- files/api.php | 3 + inc/HTTP/WebDAV/Server/Filesystem.php | 1 - inc/lib_base.php | 1 + inc/lib_connect.php | 74 +++++- inc/lib_files.php | 62 ++++- inc/lib_filestorage.php | 6 +- inc/lib_filesystem.php | 2 +- inc/lib_remotestorage.php | 323 ++++++++++++++++++++++++++ 8 files changed, 461 insertions(+), 11 deletions(-) create mode 100644 inc/lib_remotestorage.php diff --git a/files/api.php b/files/api.php index fa94a51254..d6e04d4550 100755 --- a/files/api.php +++ b/files/api.php @@ -55,6 +55,9 @@ if($arguments['action']){ case 'getfiles': echo json_encode(OC_FILES::getdirectorycontent($arguments['dir'])); break; + case 'gettree': + echo json_encode(OC_FILES::getTree($arguments['dir'])); + break; case 'find': echo json_encode(OC_FILESYSTEM::find($arguments['path'])); break; diff --git a/inc/HTTP/WebDAV/Server/Filesystem.php b/inc/HTTP/WebDAV/Server/Filesystem.php index a41d368730..ea0625a5a1 100755 --- a/inc/HTTP/WebDAV/Server/Filesystem.php +++ b/inc/HTTP/WebDAV/Server/Filesystem.php @@ -324,7 +324,6 @@ */ function PUT(&$options) { - error_log("put $fspath"); $fspath = $options["path"]; $dir = dirname($fspath); if (!OC_FILESYSTEM::file_exists($dir) || !OC_FILESYSTEM::is_dir($dir)) { diff --git a/inc/lib_base.php b/inc/lib_base.php index 5c9906d361..a806d3e5bd 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -82,6 +82,7 @@ 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)){ diff --git a/inc/lib_connect.php b/inc/lib_connect.php index 0c56d1a01d..9db867715e 100644 --- a/inc/lib_connect.php +++ b/inc/lib_connect.php @@ -75,7 +75,8 @@ 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); @@ -121,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 @@ -163,7 +228,12 @@ class OC_REMOTE_CLOUD{ global $WEBROOT; $source=$sourceDir.'/'.$sourceFile; $tmp=OC_FILESYSTEM::toTmpFile($source); - $token=sha1(uniqid().$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') { diff --git a/inc/lib_files.php b/inc/lib_files.php index 20d40669d7..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'); @@ -243,6 +247,16 @@ class OC_FILES { 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 @@ -257,8 +271,6 @@ class OC_FILES { $url=$source.="/files/pull.php?token=$token"; $ch=curl_init(); curl_setopt($ch,CURLOPT_URL,$url); - curl_setopt($ch,CURLOPT_POST,count($parameters)); - curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string); curl_setopt($ch, CURLOPT_FILE, $fp); curl_exec($ch); fclose($fp); @@ -306,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_remotestorage.php b/inc/lib_remotestorage.php new file mode 100644 index 0000000000..882f281e41 --- /dev/null +++ b/inc/lib_remotestorage.php @@ -0,0 +1,323 @@ +. +* +*/ + + +class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ + private $url; + private $username; + private $password; + private $remote; + 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']; + $this->remote=OC_CONNECT::connect($this->url,$this->username,$this->password); + } + public function mkdir($path){ + $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){ + $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->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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + return isset($this->statCache[$name]); + } + public function readfile($path){ + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $file=$this->remote->getFile($parent,$name); + readfile($file); + unlink($file); + } + public function filectime($path){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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){ + $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) { + $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) { + 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']); + } + } +} + +?> From 77c423d27bcd255f5490d1592ed104d09e2040c6 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 7 Jul 2010 15:13:09 +0200 Subject: [PATCH 24/24] Only connect to a remote ownCloud when we are actually using it --- inc/lib_remotestorage.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/inc/lib_remotestorage.php b/inc/lib_remotestorage.php index 882f281e41..f8835c1641 100644 --- a/inc/lib_remotestorage.php +++ b/inc/lib_remotestorage.php @@ -26,7 +26,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ private $url; private $username; private $password; - private $remote; + private $remote=false; private $statCache; private $statCacheDir=false; private $changed=array(); @@ -43,9 +43,14 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ $this->url=$arguments['url']; $this->username=$arguments['username']; $this->password=$arguments['password']; - $this->remote=OC_CONNECT::connect($this->url,$this->username,$this->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'); @@ -55,6 +60,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $return; } public function rmdir($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $return=$this->remote->delete($parent,$name); @@ -64,6 +70,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $return; } public function opendir($path){ + $this->connect(); $this->cacheDir($path); $dirs=array_keys($this->statCache); $id=uniqid(); @@ -75,6 +82,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $return; } public function is_dir($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($path); @@ -87,6 +95,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ 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); @@ -96,6 +105,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return ($this->statCache[$name]['type'!='dir']); } public function stat($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -105,6 +115,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $this->statCache[$name]; } public function filetype($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -114,6 +125,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $this->statCache[$name]['type']; } public function filesize($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -123,6 +135,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $this->statCache[$name]['size']; } public function is_readable($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -132,6 +145,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $this->statCache[$name]['readable']; } public function is_writeable($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -141,12 +155,14 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ 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); @@ -154,6 +170,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ unlink($file); } public function filectime($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -163,6 +180,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $this->statCache[$name]['ctime']; } public function filemtime($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -172,6 +190,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $this->statCache[$name]['mtime']; } public function fileatime($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); $this->cacheDir($parent); @@ -181,6 +200,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ 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); @@ -188,6 +208,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ 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); @@ -198,6 +219,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ } } public function unlink($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); if($return=$this->remote->delete($paren,$name)){ @@ -206,6 +228,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $return; } public function rename($path1,$path2){ + $this->connect(); $parent1=dirname($path1); $name1=substr($path1,strlen($parent1)+1); $parent2=dirname($path2); @@ -216,6 +239,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $return; } public function copy($path1,$path2){ + $this->connect(); $parent1=dirname($path1); $name1=substr($path1,strlen($parent1)+1); $parent2=dirname($path2); @@ -226,6 +250,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ return $return; } public function fopen($path,$mode){ + $this->connect(); $changed=false; $parent=dirname($path); $name=substr($path,strlen($parent)+1); @@ -254,6 +279,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ } public function getMimeType($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); if(substr($name,0,1)=='/'){ @@ -267,6 +293,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ } public function toTmpFile($path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); if(substr($name,0,1)=='/'){ @@ -282,6 +309,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ } public function fromTmpFile($tmpFile,$path){ + $this->connect(); $parent=dirname($path); $name=substr($path,strlen($parent)+1); if($this->remote->sendTmpFile($tmpFile,$parent,$name)){ @@ -293,6 +321,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ } public function delTree($dir) { + $this->connect(); $parent=dirname($dir); $name=substr($dir,strlen($parent)+1); $return=$this->remote->delete($parent,$name); @@ -307,6 +336,7 @@ class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ } public function getTree($dir) { + $this->connect(); if($return=$this->remote->getTree($dir)){ $this->notifyObservers($dir,OC_FILEACTION_READ); }