From 9f914275db3f95cb001e635898d5a5a634563bd4 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 9 Jul 2010 13:39:33 +0200 Subject: [PATCH] fix line feeds --- inc/MDB2/Driver/Function/pgsql.php | 228 +- inc/MDB2/Driver/Manager/Common.php | 2026 ++++++++--------- inc/MDB2/Driver/Manager/mysql.php | 2862 +++++++++++------------ inc/MDB2/Driver/Reverse/Common.php | 1032 ++++----- inc/MDB2/Driver/Reverse/pgsql.php | 1144 +++++----- inc/MDB2/Driver/mysql.php | 3398 ++++++++++++++-------------- 6 files changed, 5345 insertions(+), 5345 deletions(-) diff --git a/inc/MDB2/Driver/Function/pgsql.php b/inc/MDB2/Driver/Function/pgsql.php index 99bb238082..985dc4ed2f 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 $ + +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/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..e037a0d16a 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 $ +// + +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/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/pgsql.php b/inc/MDB2/Driver/Reverse/pgsql.php index 1fff29dc4b..e8e217cecf 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 $ + +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/mysql.php b/inc/MDB2/Driver/mysql.php index 5c8df3b6b6..fbb00cc6bb 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