pax_global_header00006660000000000000000000000064121361366450014521gustar00rootroot0000000000000052 comment=433b7a4dcdbb152991876d0efe002ea07e1bfe99 php-mdb2-driver-mysql-1.5.0b4/000077500000000000000000000000001213613664500160175ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/000077500000000000000000000000001213613664500221305ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/000077500000000000000000000000001213613664500226145ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/000077500000000000000000000000001213613664500240475ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Datatype/000077500000000000000000000000001213613664500256225ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Datatype/mysql.php000066400000000000000000000536641213613664500275160ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $ // require_once 'MDB2/Driver/Datatype/Common.php'; /** * MDB2 MySQL driver * * @package MDB2 * @category Database * @author Lukas Smith */ class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common { // {{{ _getCharsetFieldDeclaration() /** * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET * of a field declaration to be used in statements like CREATE TABLE. * * @param string $charset name of the charset * @return string DBMS specific SQL code portion needed to set the CHARACTER SET * of a field declaration. */ function _getCharsetFieldDeclaration($charset) { return 'CHARACTER SET '.$charset; } // }}} // {{{ _getCollationFieldDeclaration() /** * Obtain DBMS specific SQL code portion needed to set the COLLATION * of a field declaration to be used in statements like CREATE TABLE. * * @param string $collation name of the collation * @return string DBMS specific SQL code portion needed to set the COLLATION * of a field declaration. */ function _getCollationFieldDeclaration($collation) { return 'COLLATE '.$collation; } // }}} // {{{ getDeclaration() /** * Obtain DBMS specific SQL code portion needed to declare * of the given type * * @param string $type type to which the value should be converted to * @param string $name name the field to be declared. * @param string $field definition of the field * * @return string DBMS-specific SQL code portion that should be used to * declare the specified field. * @access public */ function getDeclaration($type, $name, $field) { // MySQL DDL syntax forbids combining NOT NULL with DEFAULT NULL. // To get a default of NULL for NOT NULL columns, omit it. if ( isset($field['notnull']) && !empty($field['notnull']) && array_key_exists('default', $field) // do not use isset() here! && null === $field['default'] ) { unset($field['default']); } return parent::getDeclaration($type, $name, $field); } // }}} // {{{ getTypeDeclaration() /** * Obtain DBMS specific SQL code portion needed to declare an text type * field to be used in statements like CREATE TABLE. * * @param array $field associative array with the name of the properties * of the field being declared as array indexes. Currently, the types * of supported field properties are as follows: * * length * Integer value that determines the maximum length of the text * field. If this argument is missing the field should be * declared to have the longest length allowed by the DBMS. * * default * Text 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 string DBMS specific SQL code portion that should be used to * declare the specified field. * @access public */ function getTypeDeclaration($field) { $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } switch ($field['type']) { case 'text': if (empty($field['length']) && array_key_exists('default', $field)) { $field['length'] = $db->varchar_max_length; } $length = !empty($field['length']) ? $field['length'] : false; $fixed = !empty($field['fixed']) ? $field['fixed'] : false; return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR(255)') : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); case 'clob': if (!empty($field['length'])) { $length = $field['length']; if ($length <= 255) { return 'TINYTEXT'; } elseif ($length <= 65532) { return 'TEXT'; } elseif ($length <= 16777215) { return 'MEDIUMTEXT'; } } return 'LONGTEXT'; case 'blob': if (!empty($field['length'])) { $length = $field['length']; if ($length <= 255) { return 'TINYBLOB'; } elseif ($length <= 65532) { return 'BLOB'; } elseif ($length <= 16777215) { return 'MEDIUMBLOB'; } } return 'LONGBLOB'; case 'integer': if (!empty($field['length'])) { $length = $field['length']; if ($length <= 1) { return 'TINYINT'; } elseif ($length == 2) { return 'SMALLINT'; } elseif ($length == 3) { return 'MEDIUMINT'; } elseif ($length == 4) { return 'INT'; } elseif ($length > 4) { return 'BIGINT'; } } return 'INT'; case 'boolean': return 'TINYINT(1)'; case 'date': return 'DATE'; case 'time': return 'TIME'; case 'timestamp': return 'DATETIME'; case 'float': $l = ''; if (!empty($field['length'])) { $l = '(' . $field['length']; if (!empty($field['scale'])) { $l .= ',' . $field['scale']; } $l .= ')'; } return 'DOUBLE' . $l; case 'decimal': $length = !empty($field['length']) ? $field['length'] : 18; $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; return 'DECIMAL('.$length.','.$scale.')'; } return ''; } // }}} // {{{ _getIntegerDeclaration() /** * Obtain DBMS specific SQL code portion needed to declare an integer type * field to be used in statements like CREATE TABLE. * * @param string $name name the field to be declared. * @param string $field associative array with the name of the properties * of the field being declared as array indexes. * Currently, the types of supported field * properties are as follows: * * unsigned * Boolean flag that indicates whether the field * should be declared as unsigned integer if * possible. * * default * Integer 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 string DBMS specific SQL code portion that should be used to * declare the specified field. * @access protected */ function _getIntegerDeclaration($name, $field) { $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } $default = $autoinc = ''; if (!empty($field['autoincrement'])) { $autoinc = ' AUTO_INCREMENT PRIMARY KEY'; } elseif (array_key_exists('default', $field)) { if ($field['default'] === '') { $field['default'] = empty($field['notnull']) ? null : 0; } $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); } $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; if (empty($default) && empty($notnull)) { $default = ' DEFAULT NULL'; } $name = $db->quoteIdentifier($name, true); return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; } // }}} // {{{ _getFloatDeclaration() /** * Obtain DBMS specific SQL code portion needed to declare an float type * field to be used in statements like CREATE TABLE. * * @param string $name name the field to be declared. * @param string $field associative array with the name of the properties * of the field being declared as array indexes. * Currently, the types of supported field * properties are as follows: * * unsigned * Boolean flag that indicates whether the field * should be declared as unsigned float if * possible. * * default * float 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 string DBMS specific SQL code portion that should be used to * declare the specified field. * @access protected */ function _getFloatDeclaration($name, $field) { // Since AUTO_INCREMENT can be used for integer or floating-point types, // reuse the INTEGER declaration // @see http://bugs.mysql.com/bug.php?id=31032 return $this->_getIntegerDeclaration($name, $field); } // }}} // {{{ _getDecimalDeclaration() /** * Obtain DBMS specific SQL code portion needed to declare an decimal type * field to be used in statements like CREATE TABLE. * * @param string $name name the field to be declared. * @param string $field associative array with the name of the properties * of the field being declared as array indexes. * Currently, the types of supported field * properties are as follows: * * unsigned * Boolean flag that indicates whether the field * should be declared as unsigned integer if * possible. * * default * Decimal 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 string DBMS specific SQL code portion that should be used to * declare the specified field. * @access protected */ function _getDecimalDeclaration($name, $field) { $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } $default = ''; if (array_key_exists('default', $field)) { if ($field['default'] === '') { $field['default'] = empty($field['notnull']) ? null : 0; } $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); } elseif (empty($field['notnull'])) { $default = ' DEFAULT NULL'; } $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; $name = $db->quoteIdentifier($name, true); return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull; } // }}} // {{{ matchPattern() /** * build a pattern matching string * * @access public * * @param array $pattern even keys are strings, odd are patterns (% and _) * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) * @param string $field optional field name that is being matched against * (might be required when emulating ILIKE) * * @return string SQL pattern */ function matchPattern($pattern, $operator = null, $field = null) { $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } $match = ''; if (null !== $operator) { $field = (null === $field) ? '' : $field.' '; $operator = strtoupper($operator); switch ($operator) { // case insensitive case 'ILIKE': $match = $field.'LIKE '; break; case 'NOT ILIKE': $match = $field.'NOT LIKE '; break; // case sensitive case 'LIKE': $match = $field.'LIKE BINARY '; break; case 'NOT LIKE': $match = $field.'NOT LIKE BINARY '; break; default: return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, 'not a supported operator type:'. $operator, __FUNCTION__); } } $match.= "'"; foreach ($pattern as $key => $value) { if ($key % 2) { $match.= $value; } else { $match.= $db->escapePattern($db->escape($value)); } } $match.= "'"; $match.= $this->patternEscapeString(); return $match; } // }}} // {{{ _mapNativeDatatype() /** * Maps a native array description of a field to a MDB2 datatype and length * * @param array $field native field description * @return array containing the various possible types, length, sign, fixed * @access public */ function _mapNativeDatatype($field) { $db_type = strtolower($field['type']); $db_type = strtok($db_type, '(), '); if ($db_type == 'national') { $db_type = strtok('(), '); } if (!empty($field['length'])) { $length = strtok($field['length'], ', '); $decimal = strtok(', '); } else { $length = strtok('(), '); $decimal = strtok('(), '); } $type = array(); $unsigned = $fixed = null; switch ($db_type) { case 'tinyint': $type[] = 'integer'; $type[] = 'boolean'; if (preg_match('/^(is|has)/', $field['name'])) { $type = array_reverse($type); } $unsigned = preg_match('/ unsigned/i', $field['type']); $length = 1; break; case 'smallint': $type[] = 'integer'; $unsigned = preg_match('/ unsigned/i', $field['type']); $length = 2; break; case 'mediumint': $type[] = 'integer'; $unsigned = preg_match('/ unsigned/i', $field['type']); $length = 3; break; case 'int': case 'integer': $type[] = 'integer'; $unsigned = preg_match('/ unsigned/i', $field['type']); $length = 4; break; case 'bigint': $type[] = 'integer'; $unsigned = preg_match('/ unsigned/i', $field['type']); $length = 8; break; case 'tinytext': case 'mediumtext': case 'longtext': case 'text': case 'varchar': $fixed = false; case 'string': case 'char': $type[] = 'text'; if ($length == '1') { $type[] = 'boolean'; if (preg_match('/^(is|has)/', $field['name'])) { $type = array_reverse($type); } } elseif (strstr($db_type, 'text')) { $type[] = 'clob'; if ($decimal == 'binary') { $type[] = 'blob'; } $type = array_reverse($type); } if ($fixed !== false) { $fixed = true; } break; case 'enum': $type[] = 'text'; preg_match_all('/\'.+\'/U', $field['type'], $matches); $length = 0; $fixed = false; if (is_array($matches)) { foreach ($matches[0] as $value) { $length = max($length, strlen($value)-2); } if ($length == '1' && count($matches[0]) == 2) { $type[] = 'boolean'; if (preg_match('/^(is|has)/', $field['name'])) { $type = array_reverse($type); } } } $type[] = 'integer'; case 'set': $fixed = false; $type[] = 'text'; $type[] = 'integer'; break; case 'date': $type[] = 'date'; $length = null; break; case 'datetime': case 'timestamp': $type[] = 'timestamp'; $length = null; break; case 'time': $type[] = 'time'; $length = null; break; case 'float': case 'double': case 'real': $type[] = 'float'; $unsigned = preg_match('/ unsigned/i', $field['type']); if ($decimal !== false) { $length = $length.','.$decimal; } break; case 'unknown': case 'decimal': case 'numeric': $type[] = 'decimal'; $unsigned = preg_match('/ unsigned/i', $field['type']); if ($decimal !== false) { $length = $length.','.$decimal; } break; case 'tinyblob': case 'mediumblob': case 'longblob': case 'blob': $type[] = 'blob'; $length = null; break; case 'binary': case 'varbinary': $type[] = 'blob'; break; case 'year': $type[] = 'integer'; $type[] = 'date'; $length = null; break; default: $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, 'unknown database attribute type: '.$db_type, __FUNCTION__); } if ((int)$length <= 0) { $length = null; } return array($type, $length, $unsigned, $fixed); } // }}} } ?> php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Function/000077500000000000000000000000001213613664500256345ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Function/mysql.php000066400000000000000000000130451213613664500275150ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $ // 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_mysql 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 (MDB2::isError($db)) { return $db; } $query = 'CALL '.$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 'UNIX_TIMESTAMP('. $expression.')'; } // }}} // {{{ concat() /** * Returns string to concatenate two or more string parameters * * @param string $value1 * @param string $value2 * @param string $values... * @return string to concatenate two strings * @access public **/ function concat($value1, $value2) { $args = func_get_args(); return "CONCAT(".implode(', ', $args).")"; } // }}} // {{{ guid() /** * Returns global unique identifier * * @return string to get global unique identifier * @access public */ function guid() { return 'UUID()'; } // }}} } ?> php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Manager/000077500000000000000000000000001213613664500254215ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Manager/mysql.php000066400000000000000000001572141213613664500273110ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $ // 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 (MDB2::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 (MDB2::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 (MDB2::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 (MDB2::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 ((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 (MDB2::isError($query)) { return $query; } if (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 (MDB2::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 (MDB2::isError($db)) { return $db; } //delete the triggers associated to existing FK constraints $constraints = $this->listTableConstraints($name); if (!MDB2::isError($constraints) && !empty($constraints)) { $db->loadModule('Reverse', null, true); foreach ($constraints as $constraint) { $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); if (!MDB2::isError($definition) && !empty($definition['foreign'])) { $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); if (MDB2::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 (MDB2::isError($db)) { return $db; } $name = $db->quoteIdentifier($name, true); $result = $db->exec("TRUNCATE TABLE $name"); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } // }}} // {{{ 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 (MDB2::isError($db)) { return $db; } if (empty($table)) { $table = $this->listTables(); if (MDB2::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 (MDB2::isError($result)) { return $result; } if (!empty($options['analyze'])) { $result = $db->exec('ANALYZE TABLE '.$table); if (MDB2::isError($result)) { return $result; } } 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 (MDB2::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); $result = $db->exec("ALTER TABLE $name $query"); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } // }}} // {{{ 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 (MDB2::isError($db)) { return $db; } $result = $db->queryCol('SHOW DATABASES'); if (MDB2::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 (MDB2::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 (MDB2::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 (MDB2::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 (MDB2::isError($db)) { return $db; } $query = 'SHOW TRIGGERS'; if (null !== $table) { $table = $db->quote($table, 'text'); $query .= " LIKE $table"; } $result = $db->queryCol($query); if (MDB2::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 (MDB2::isError($db)) { return $db; } $query = "SHOW /*!50002 FULL*/ TABLES"; if (null !== $database) { $query .= " FROM $database"; } $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); if (MDB2::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 (MDB2::isError($db)) { return $db; } $query = 'SHOW FULL TABLES'; if (null !== $database) { $query.= " FROM $database"; } $query.= " WHERE Table_type = 'VIEW'"; $result = $db->queryCol($query); if (MDB2::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 (MDB2::isError($db)) { return $db; } $table = $db->quoteIdentifier($table, true); $result = $db->queryCol("SHOW COLUMNS FROM $table"); if (MDB2::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 (MDB2::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) . ')'; $result = $db->exec($query); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } // }}} // {{{ 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 (MDB2::isError($db)) { return $db; } $table = $db->quoteIdentifier($table, true); $name = $db->quoteIdentifier($db->getIndexName($name), true); $result = $db->exec("DROP INDEX $name ON $table"); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } // }}} // {{{ 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 (MDB2::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 (MDB2::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 (MDB2::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 (MDB2::isError($result)) { return $result; } } $res = $db->exec($query); if (MDB2::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 (MDB2::isError($db)) { return $db; } if ($primary || strtolower($name) == 'primary') { $query = 'ALTER TABLE '. $db->quoteIdentifier($table, true) .' DROP PRIMARY KEY'; $result = $db->exec($query); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } //is it a FK constraint? If so, also delete the associated triggers $db->loadModule('Reverse', null, true); $definition = $db->reverse->getTableConstraintDefinition($table, $name); if (!MDB2::isError($definition) && !empty($definition['foreign'])) { //first drop the FK enforcing triggers $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); if (MDB2::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"; $result = $db->exec($query); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } $table = $db->quoteIdentifier($table, true); $name = $db->quoteIdentifier($db->getIndexName($name), true); $query = "ALTER TABLE $table DROP INDEX $name"; $result = $db->exec($query); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } // }}} // {{{ _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 (MDB2::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'; $restrict_action2 = empty($conditions2) ? '' : ' AND (' .implode(' OR ', $conditions2) .')'; $restrict_action3 = ' THEN CALL %s_ON_TABLE_'.$table.'_VIOLATES_FOREIGN_KEY_CONSTRAINT();' .' END IF;'; $restrict_action_update = $restrict_action . $restrict_action2 . $restrict_action3; $restrict_action_delete = $restrict_action . $restrict_action3; // There is no NEW row in on DELETE trigger $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 (MDB2::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_update, $trigger_names['pk_update'], 'AFTER UPDATE', 'update'); } elseif ('RESTRICT' == $fkdef['onupdate']) { $sql_update = sprintf($query.$restrict_action_update, $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_delete, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete'); } elseif ('RESTRICT' == $fkdef['ondelete']) { $sql_delete = sprintf($query.$restrict_action_delete, $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 (MDB2::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 (MDB2::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 (MDB2::isError($db)) { return $db; } $triggers = $this->listTableTriggers($table); $triggers2 = $this->listTableTriggers($referenced_table); if (!MDB2::isError($triggers2) && !MDB2::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 (MDB2::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 (MDB2::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 (MDB2::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 (!MDB2::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 (MDB2::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 (MDB2::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 (!MDB2::isError($res)) { return MDB2_OK; } // Handle error $result = $db->exec("DROP TABLE $sequence_name"); if (MDB2::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 (MDB2::isError($db)) { return $db; } $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); $result = $db->exec("DROP TABLE $sequence_name"); if (MDB2::isError($result)) { return $result; } return MDB2_OK; } // }}} // {{{ 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 (MDB2::isError($db)) { return $db; } $query = "SHOW TABLES"; if (null !== $database) { $query .= " FROM $database"; } $table_names = $db->queryCol($query); if (MDB2::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; } // }}} } ?> php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Native/000077500000000000000000000000001213613664500252755ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Native/mysql.php000066400000000000000000000066761213613664500271720ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: mysql.php 215004 2006-06-18 21:59:05Z lsmith $ // require_once 'MDB2/Driver/Native/Common.php'; /** * MDB2 MySQL driver for the native module * * @package MDB2 * @category Database * @author Lukas Smith */ class MDB2_Driver_Native_mysql extends MDB2_Driver_Native_Common { } ?>php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Reverse/000077500000000000000000000000001213613664500254625ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/Reverse/mysql.php000066400000000000000000000554021213613664500273460ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: mysql.php 327310 2012-08-27 15:16:18Z danielc $ // require_once 'MDB2/Driver/Reverse/Common.php'; /** * MDB2 MySQL driver for the schema reverse engineering module * * @package MDB2 * @category Database * @author Lukas Smith * @author Lorenzo Alberton */ class MDB2_Driver_Reverse_mysql 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 (MDB2::isError($db)) { return $db; } $result = $db->loadModule('Datatype', null, true); if (MDB2::isError($result)) { return $result; } list($schema, $table) = $this->splitTableSchema($table_name); $table = $db->quoteIdentifier($table, true); $query = "SHOW FULL COLUMNS FROM $table LIKE ".$db->quote($field_name); $columns = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); if (MDB2::isError($columns)) { return $columns; } foreach ($columns as $column) { $column = array_change_key_case($column, CASE_LOWER); $column['name'] = $column['field']; unset($column['field']); if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { if ($db->options['field_case'] == CASE_LOWER) { $column['name'] = strtolower($column['name']); } else { $column['name'] = strtoupper($column['name']); } } else { $column = array_change_key_case($column, $db->options['field_case']); } if ($field_name == $column['name']) { $mapped_datatype = $db->datatype->mapNativeDatatype($column); if (MDB2::isError($mapped_datatype)) { return $mapped_datatype; } list($types, $length, $unsigned, $fixed) = $mapped_datatype; $notnull = false; if (empty($column['null']) || $column['null'] !== 'YES') { $notnull = true; } $default = false; if (array_key_exists('default', $column)) { $default = $column['default']; if ((null === $default) && $notnull) { $default = ''; } } $definition[0] = array( 'notnull' => $notnull, 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) ); $autoincrement = false; if (!empty($column['extra'])) { if ($column['extra'] == 'auto_increment') { $autoincrement = true; } else { $definition[0]['extra'] = $column['extra']; } } $collate = null; if (!empty($column['collation'])) { $collate = $column['collation']; $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate); } if (null !== $length) { $definition[0]['length'] = $length; } if (null !== $unsigned) { $definition[0]['unsigned'] = $unsigned; } if (null !== $fixed) { $definition[0]['fixed'] = $fixed; } if ($default !== false) { $definition[0]['default'] = $default; } if ($autoincrement !== false) { $definition[0]['autoincrement'] = $autoincrement; } if (null !== $collate) { $definition[0]['collate'] = $collate; $definition[0]['charset'] = $charset; } foreach ($types as $key => $type) { $definition[$key] = $definition[0]; if ($type == 'clob' || $type == 'blob') { unset($definition[$key]['default']); } elseif ($type == 'timestamp' && $notnull && empty($definition[$key]['default'])) { $definition[$key]['default'] = '0000-00-00 00:00:00'; } $definition[$key]['type'] = $type; $definition[$key]['mdb2type'] = $type; } return $definition; } } return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, 'it was not specified an existing table column', __FUNCTION__); } // }}} // {{{ 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 (MDB2::isError($db)) { return $db; } list($schema, $table) = $this->splitTableSchema($table_name); $table = $db->quoteIdentifier($table, true); $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */"; $index_name_mdb2 = $db->getIndexName($index_name); $result = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2))); if (!MDB2::isError($result) && (null !== $result)) { // apply 'idxname_format' only if the query succeeded, otherwise // fallback to the given $index_name, without transformation $index_name = $index_name_mdb2; } $result = $db->query(sprintf($query, $db->quote($index_name))); if (MDB2::isError($result)) { return $result; } $colpos = 1; $definition = array(); while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { $row = array_change_key_case($row, CASE_LOWER); $key_name = $row['key_name']; if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { if ($db->options['field_case'] == CASE_LOWER) { $key_name = strtolower($key_name); } else { $key_name = strtoupper($key_name); } } if ($index_name == $key_name) { if (!$row['non_unique']) { return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, $index_name . ' is not an existing table index', __FUNCTION__); } $column_name = $row['column_name']; if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { if ($db->options['field_case'] == CASE_LOWER) { $column_name = strtolower($column_name); } else { $column_name = strtoupper($column_name); } } $definition['fields'][$column_name] = array( 'position' => $colpos++ ); if (!empty($row['collation'])) { $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' ? 'ascending' : 'descending'); } } } $result->free(); if (empty($definition['fields'])) { return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, $index_name . ' is not an existing table index', __FUNCTION__); } 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 (MDB2::isError($db)) { return $db; } list($schema, $table) = $this->splitTableSchema($table_name); $constraint_name_original = $constraint_name; $table = $db->quoteIdentifier($table, true); $query = "SHOW INDEX FROM $table /*!50002 WHERE Key_name = %s */"; if (strtolower($constraint_name) != 'primary') { $constraint_name_mdb2 = $db->getIndexName($constraint_name); $result = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2))); if (!MDB2::isError($result) && (null !== $result)) { // apply 'idxname_format' only if the query succeeded, otherwise // fallback to the given $index_name, without transformation $constraint_name = $constraint_name_mdb2; } } $result = $db->query(sprintf($query, $db->quote($constraint_name))); if (MDB2::isError($result)) { return $result; } $colpos = 1; //default values, eventually overridden $definition = array( 'primary' => false, 'unique' => false, 'foreign' => false, 'check' => false, 'fields' => array(), 'references' => array( 'table' => '', 'fields' => array(), ), 'onupdate' => '', 'ondelete' => '', 'match' => '', 'deferrable' => false, 'initiallydeferred' => false, ); while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { $row = array_change_key_case($row, CASE_LOWER); $key_name = $row['key_name']; if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { if ($db->options['field_case'] == CASE_LOWER) { $key_name = strtolower($key_name); } else { $key_name = strtoupper($key_name); } } if ($constraint_name == $key_name) { if ($row['non_unique']) { //FOREIGN KEY? return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition); } if ($row['key_name'] == 'PRIMARY') { $definition['primary'] = true; } elseif (!$row['non_unique']) { $definition['unique'] = true; } $column_name = $row['column_name']; if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { if ($db->options['field_case'] == CASE_LOWER) { $column_name = strtolower($column_name); } else { $column_name = strtoupper($column_name); } } $definition['fields'][$column_name] = array( 'position' => $colpos++ ); if (!empty($row['collation'])) { $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' ? 'ascending' : 'descending'); } } } $result->free(); if (empty($definition['fields'])) { return $this->_getTableFKConstraintDefinition($table, $constraint_name_original, $definition); } return $definition; } // }}} // {{{ _getTableFKConstraintDefinition() /** * Get the FK definition from the CREATE TABLE statement * * @param string $table table name * @param string $constraint_name constraint name * @param array $definition default values for constraint definition * * @return array|PEAR_Error * @access private */ function _getTableFKConstraintDefinition($table, $constraint_name, $definition) { $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } //Use INFORMATION_SCHEMA instead? //SELECT * // FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS // WHERE CONSTRAINT_SCHEMA = '$dbname' // AND TABLE_NAME = '$table' // AND CONSTRAINT_NAME = '$constraint_name'; $query = 'SHOW CREATE TABLE '. $db->escape($table); $constraint = $db->queryOne($query, 'text', 1); if (!MDB2::isError($constraint) && !empty($constraint)) { if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { if ($db->options['field_case'] == CASE_LOWER) { $constraint = strtolower($constraint); } else { $constraint = strtoupper($constraint); } } $constraint_name_original = $constraint_name; $constraint_name = $db->getIndexName($constraint_name); $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i'; if (!preg_match($pattern, str_replace('`', '', $constraint), $matches)) { //fallback to original constraint name $pattern = '/\bCONSTRAINT\s+'.$constraint_name_original.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^\s]+) \(([^\)]+)\)(?: ON DELETE ([^\s]+))?(?: ON UPDATE ([^\s]+))?/i'; } if (preg_match($pattern, str_replace('`', '', $constraint), $matches)) { $definition['foreign'] = true; $column_names = explode(',', $matches[1]); $referenced_cols = explode(',', $matches[3]); $definition['references'] = array( 'table' => $matches[2], 'fields' => array(), ); $colpos = 1; foreach ($column_names as $column_name) { $definition['fields'][trim($column_name)] = array( 'position' => $colpos++ ); } $colpos = 1; foreach ($referenced_cols as $column_name) { $definition['references']['fields'][trim($column_name)] = array( 'position' => $colpos++ ); } $definition['ondelete'] = empty($matches[4]) ? 'RESTRICT' : strtoupper($matches[4]); $definition['onupdate'] = empty($matches[5]) ? 'RESTRICT' : strtoupper($matches[5]); $definition['match'] = 'SIMPLE'; return $definition; } } return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, $constraint_name . ' is not an existing table constraint', __FUNCTION__); } // }}} // {{{ 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 */ function getTriggerDefinition($trigger) { $db = $this->getDBInstance(); if (MDB2::isError($db)) { return $db; } $query = 'SELECT trigger_name, event_object_table AS table_name, action_statement AS trigger_body, action_timing AS trigger_type, event_manipulation AS trigger_event FROM information_schema.triggers WHERE trigger_name = '. $db->quote($trigger, 'text'); $types = array( 'trigger_name' => 'text', 'table_name' => 'text', 'trigger_body' => 'text', 'trigger_type' => 'text', 'trigger_event' => 'text', ); $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); if (MDB2::isError($def)) { return $def; } $def['trigger_comment'] = ''; $def['trigger_enabled'] = true; return $def; } // }}} // {{{ tableInfo() /** * Returns information about a table or a result set * * @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::setOption() */ function tableInfo($result, $mode = null) { if (is_string($result)) { return parent::tableInfo($result, $mode); } $db = $this->getDBInstance(); if (MDB2::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 = @mysql_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' => $case_func(@mysql_field_table($resource, $i)), 'name' => $case_func(@mysql_field_name($resource, $i)), 'type' => @mysql_field_type($resource, $i), 'length' => @mysql_field_len($resource, $i), 'flags' => @mysql_field_flags($resource, $i), ); if ($res[$i]['type'] == 'string') { $res[$i]['type'] = 'char'; } elseif ($res[$i]['type'] == 'unknown') { $res[$i]['type'] = 'decimal'; } $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); if (MDB2::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; } } ?> php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/MDB2/Driver/mysql.php000066400000000000000000001761171213613664500257420ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: mysql.php 327320 2012-08-27 15:52:50Z danielc $ // /** * MDB2 MySQL driver * * @package MDB2 * @category Database * @author Lukas Smith */ class MDB2_Driver_mysql extends MDB2_Driver_Common { // {{{ properties public $string_quoting = array( 'start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\', ); public $identifier_quoting = array( 'start' => '`', 'end' => '`', 'escape' => '`', ); public $sql_comments = array( array('start' => '-- ', 'end' => "\n", 'escape' => false), array('start' => '#', 'end' => "\n", 'escape' => false), array('start' => '/*', 'end' => '*/', 'escape' => false), ); protected $server_capabilities_checked = false; protected $start_transaction = false; public $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 (MDB2::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 (MDB2::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 (MDB2::isError($result)) { return $result; } if (!$this->start_transaction) { $query = 'SET AUTOCOMMIT = 1'; $result = $this->_doQuery($query, true); if (MDB2::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 (MDB2::isError($result)) { return $result; } if (!$this->start_transaction) { $query = 'SET AUTOCOMMIT = 1'; $result = $this->_doQuery($query, true); if (MDB2::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) * @param array some transaction options: * 'wait' => 'WAIT' | 'NO WAIT' * 'rw' => 'READ WRITE' | 'READ ONLY' * * @return mixed MDB2_OK on success, a MDB2 error on failure * * @access public * @since 2.1.1 */ function setTransactionIsolation($isolation, $options = array()) { $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 (!extension_loaded($this->phptype)) { return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); } $params = array(); $unix = ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix'); if (empty($this->dsn['hostspec'])) { $this->dsn['hostspec'] = $unix ? '' : 'localhost'; } if ($this->dsn['hostspec']) { $params[0] = $this->dsn['hostspec'] . ($this->dsn['port'] ? ':' . $this->dsn['port'] : ''); } else { $params[0] = ':' . $this->dsn['socket']; } $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 (MDB2::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 (MDB2::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 (MDB2::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 '".mysql_real_escape_string($collation, $connection)."'"; } 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 (MDB2::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 (MDB2::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 (!MDB2::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 (MDB2::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 (MDB2::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 && 0 !== mysql_errno($connection)) { $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 (MDB2::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 (MDB2::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()) { // connect to get server capabilities (http://pear.php.net/bugs/16147) $connection = $this->getConnection(); if (MDB2::isError($connection)) { return $connection; } if ($this->options['emulate_prepared'] || $this->supported['prepared_statements'] !== true ) { return parent::prepare($query, $types, $result_types, $lobs); } $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 (MDB2::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 (MDB2::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; } } 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 (MDB2::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 (MDB2::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 (MDB2::isError($connection)) { return $connection; } $table = $this->quoteIdentifier($table, true); $query = "REPLACE INTO $table ($query) VALUES ($values)"; $result = $this->_doQuery($query, true, $connection); if (MDB2::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 (MDB2::isError($result)) { if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { $this->loadModule('Manager', null, true); $result = $this->manager->createSequence($seq_name); if (MDB2::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 (MDB2::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 // not casting to integer to handle BIGINT http://pear.php.net/bugs/bug.php?id=17650 return $this->queryOne('SELECT LAST_INSERT_ID()'); } // }}} // {{{ 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 (MDB2::isError($seek)) { return $seek; } } if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { $fetchmode = $this->db->fetchmode; } if ( $fetchmode == MDB2_FETCHMODE_ASSOC || $fetchmode == MDB2_FETCHMODE_OBJECT ) { $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; } 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 ( ( $fetchmode != MDB2_FETCHMODE_ASSOC && $fetchmode != MDB2_FETCHMODE_OBJECT) && !empty($this->types) ) { $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); } elseif (($fetchmode == MDB2_FETCHMODE_ASSOC || $fetchmode == MDB2_FETCHMODE_OBJECT) && !empty($this->types_assoc) ) { $row = $this->db->datatype->convertResultRow($this->types_assoc, $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 { $rowObj = new $object_class($row); $row = $rowObj; } } ++$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 (MDB2::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 (MDB2::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 = true) { 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 (MDB2::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__); } $close = false; $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 (MDB2::isError($quoted)) { return $quoted; } $param_query = 'SET @'.$parameter.' = '.$quoted; $result = $this->db->_doQuery($param_query, true, $connection); if (MDB2::isError($result)) { return $result; } } $query.= ' USING @'.implode(', @', array_values($this->positions)); } $result = $this->db->_doQuery($query, $this->is_manip, $connection); if (MDB2::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 (MDB2::isError($connection)) { return $connection; } $query = 'DEALLOCATE PREPARE '.$this->statement; $result = $this->db->_doQuery($query, true, $connection); } parent::free(); return $result; } } ?> php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/package_mysql.xml000066400000000000000000001001431213613664500254710ustar00rootroot00000000000000 MDB2_Driver_mysql pear.php.net mysql MDB2 driver This is the MySQL MDB2 driver. Lukas Kahwe Smith lsmith smith@pooteeweet.org no Lorenzo Alberton quipo l.alberton@quipo.it yes 2012-10-23 1.5.0b4 1.5.0b4 beta beta BSD License - Make varchar_max_length property public, Bug #19582. - Revert 327099 by afz, caused "Notice: Undefined index: charset on line 1003" - PEAR::isError() -> MDB2::isError(), Bug #19491. - PEAR::loadExtension() -> extension_loaded(), Bug #19583. - max value for VARCHAR is 65535 but if used multi-bytes (UTF8) so it is 21844, because UTF8 string takes 3bytes - Fix Bug #19262. Updates conditional stagements to use logical operators to include MDB2_FETCHMODE_OBJECT where appropriate. Was broken in r321197. - Have truncateTable() return MDB2_OK on success, as documented (bug 19201) - Have alterTable() return MDB2_OK on success, as documented (bug 19200) - Have dropIndex() return MDB2_OK on success, as documented (bug 19198) - Have vacuum() return MDB2_OK on success, as documented (bug 19196) - Have createIndex() return MDB2_OK on success, as documented (bug 19195) - Have dropConstraint() return MDB2_OK on success, as documented (bug 19194) - Have dropSequence() return MDB2_OK on success, as documented (bug 19191). - Make setOption('result_wrap_class') actually useful by changing the default value of $result_wrap_class parameters from false to true. - Obtain error information in _doQuery() because standaloneQuery() throws off $this->connection. - FETCHMODE constants are NOT bitwise. - Make $sql_comments public (was before, used in tests, no real harm). - Property visibility - boolean data type - fixed bug #17984: Error is not reported when mysqli_stmt_bind_param() fails [dennylin93] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type - fixed bug #18203: Type introspection breaks with associative arrays if names are identical (patch by Peter Bex) - fixed bug #17892: removed debug message [pdt256] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type open todo items: - use a trigger to emulate setting default now() 5.2.0 1.9.1 MDB2 pear.php.net 2.5.0b4 mysql 1.5.0b4 1.5.0b4 beta beta 2012-10-23 BSD License - Make varchar_max_length property public, Bug #19582. - Revert 327099 by afz, caused "Notice: Undefined index: charset on line 1003" - PEAR::isError() -> MDB2::isError(), Bug #19491. - PEAR::loadExtension() -> extension_loaded(), Bug #19583. - max value for VARCHAR is 65535 but if used multi-bytes (UTF8) so it is 21844, because UTF8 string takes 3bytes - Fix Bug #19262. Updates conditional stagements to use logical operators to include MDB2_FETCHMODE_OBJECT where appropriate. Was broken in r321197. - Have truncateTable() return MDB2_OK on success, as documented (bug 19201) - Have alterTable() return MDB2_OK on success, as documented (bug 19200) - Have dropIndex() return MDB2_OK on success, as documented (bug 19198) - Have vacuum() return MDB2_OK on success, as documented (bug 19196) - Have createIndex() return MDB2_OK on success, as documented (bug 19195) - Have dropConstraint() return MDB2_OK on success, as documented (bug 19194) - Have dropSequence() return MDB2_OK on success, as documented (bug 19191). - Make setOption('result_wrap_class') actually useful by changing the default value of $result_wrap_class parameters from false to true. - Obtain error information in _doQuery() because standaloneQuery() throws off $this->connection. - FETCHMODE constants are NOT bitwise. - Make $sql_comments public (was before, used in tests, no real harm). - Property visibility - boolean data type - fixed bug #17984: Error is not reported when mysqli_stmt_bind_param() fails [dennylin93] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type - fixed bug #18203: Type introspection breaks with associative arrays if names are identical (patch by Peter Bex) - fixed bug #17892: removed debug message [pdt256] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type open todo items: - use a trigger to emulate setting default now() 1.5.0b3 1.5.0b3 beta beta 2010-08-29 BSD License - fixed bug #15650: mysqli function used in setCharset() - fixed bug #16003: incorrect check for error after mysql_store_result - fixed bug #16147: first prepared statement is emulated when using factory with mysql - fixed bug #16669: hostspec is ignored when protocol is unix - fixed bug #17037: 'on update' not mentioned in tableInfo() - fixed bug #17065: There is no NEW row in on DELETE trigger (fix error in FK constraint triggers) - fixed bug #17650: lastInsertId can not handle bigint, forces cast to integer [alexpw] - return ON UPDATE|DELETE action in getTableConstraintDefinition() note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0b2 1.5.0b2 beta beta 2009-01-14 BSD License - fixed bug #12117: disconnect() does not work as documented - fixed bug #13412: sometimes getTableConstraintDefinition() fails for FOREIGN KEYs - fixed bug #13581: wrong query in beginTransaction() for certain MySQL versions - request #13657: in setCharset(), use mysql_set_charset() if available [cwiedmann] - fixed bug #13928: Invalid triggers created for 'ON UPDATE' - fixed bug #15051: Cannot create constraints with field length - add index on FK column(s) or a FK constraint cannot be created in some cases note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0b1 1.5.0b1 alpha alpha 2008-03-15 BSD License - fixed bug #11831: createTable() now supports tables with a multi-field PRIMARY KEY where one field is defined as AUTO_INCREMENT - request #11204: support AUTO_INCREMENT for FLOAT data type and UNSIGNED option for FLOAT and DECIMAL data type [afz] - fixed bug #11692: value of $db->supports('transactions') changes after query [afz] - request #12731: added truncateTable() in the Manager module - request #12732: added vacuum() in the Manager module for OPTIMIZE/VACUUM TABLE abstraction - request #12800: added alterDatabase() in the Manager module [afz] - fixed quoting in createDatabase() in the Manager module - fixed bug #12924: correctly handle internal expected errors even with custom error handling - added standaloneQuery() and databaseExists() - request #13106: added unixtimestamp() in the Function module - fixed regexp in listTableConstraints() in the Manager module to list FOREIGN KEY constraints - fixed bug #13180: MySQL driver tells SAVEPOINT is supported for MyISAM tables - fixed bug #13283: replace() doesn't respect quote_identifiers option - request #13313: setCharSet() supports 'COLLATE' too - fixed bug #13370: some capabilities depend on user options, so check them after a setOption() call - when triggers are supported, two triggers are created to emulate ON UPDATE / ON DELETE actions for FOREIGN KEY constraints. Known limitation: since mysql doesn't support multiple triggers with the same action time and event for one table, if there are multiple table referencing the same table, only the first one will have the triggers created. note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0a2 1.5.0a2 alpha alpha 2007-12-06 BSD License - fixed bug #12516: error in FK constraint creation query - request #12012: added charset/collation support in createDatabase() note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0a1 1.5.0a1 alpha alpha 2007-10-28 BSD License - fixed bug #10024: Added new option 'lob_allow_url_include' (default false) to [dis]allow inserting a LOB from an url (file, http, ...). - fixed bug #10986: Using more random statement names (request #11625) - fixed bug #11055: Using placeholders with := variable assignment fails [bekarau] - initial support for FOREIGN KEY constraints in the Manager and Reverse modules - request #11389: added many new MySQL 5.1 error codes in errorInfo() - fixed bug #11428: propagate quote() errors with invalid data types - fixed bug #11590: _getServerCapabilities() has to be called once per connection - fixed bug #11790: avoid array_diff() because it has a memory leak in PHP 5.1.x - fixed some E_STRICT errors with PHP5 - fixed bug #12010: MDB2_PORTABILITY_RTRIM option was ignored - fixed bug #12083: createTable() in the Manager module now returns MDB2_OK on success, as documented - fixed bug #12217: mysql_num_rows() returns FALSE on failure, not NULL (thanks to zaa@zaa.pp.ru) - fixed bug #12242: missing charset info in the Reverse module (patch by Carsten Wiedmann) - fixed bug #12269: tableInfo() in the Reverse module detect 'clob' data type as first option - fixed bug #12336: supply default value for NOT NULL timestamp fields note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.4.1 1.4.1 stable stable 2007-05-03 BSD License - fixed bug #10378: incorrect query rewrite in setLimit() when using "FOR UPDATE" or "LOCK IN SHARE MODE" (thanks to priyadi) or "INTO OUTFILE" or "INTO DUMPFILE" - return length as "precision,scale" for NUMERIC and DECIMAL fields in mapNativeDatatype() - in getTableIndexDefinition() and getTableConstraintDefinition() in the Reverse module, also return the field position in the index/constraint - fixed bug #10636: transactions broken in release 2.4.0 because of some properties being reset (thanks to Conor Kerr) - fixed bug #10807: connect() Method Incorrectly Caches Connections [fornax] - fixed bug #10895: setLimit() does not work properly when a subquery uses LIMIT note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.4.0 1.4.0 stable stable 2007-03-13 BSD License - fixed bug #9283: missing support for BINARY/VARBINARY data types (thanks to Tom Hendrikx) - propagate errors in getTableFieldDefinition() in the Reverse module - implemented getTriggerDefinition() in the Reverse module (mysql > 5.0.2) [experimental] - implemented listTableTriggers() in the Manager module (mysql > 5.0.2) - implemented listFunctions() in the Manager module - setCharset() now uses "SET NAMES" instead of "SET character_set_client" - select the mysql database in listUsers() in the Manager module - added error codes for MySQL 5 (patch by Adam Harvey) - implemented guid() in the Function module [globally unique identifier] - fixed bug #10033: beginTransaction() does not know server capabilities - fixed bug #10057: createConstraint() returns an error when the definition is incomplete - request #9451: you can set charset, collation, engine and comments in createSequence() - implemented a fallback mechanism within getTableIndexDefinition() and getTableConstraintDefinition() in the Reverse module to ignore the 'idxname_format' option and use the index name as provided in case of failure before returning an error - fixed bug #10181: propagate error when an invalid type is passed to prepare() - added a 'nativetype_map_callback' option to map native data declarations back to custom data types (thanks to Andrew Hill). - fixed bug #10239: execute() misinterprets MySQL's user defined variables - phpdoc fixes note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.3.0 2.3.0 stable stable 2006-11-03 BSD License - added charset and collation support to field declaration - fixed bug #9024: typo in error checking - fix typos in error handling in a few places (bug #9024) - do not skip id generation in nextId() when creating a sequence on demand because this prevents lastInsertID() from working - migrated to package.xml version 2 note: - the multi_query test failes because this is not supported by ext/mysql - use a trigger to emulate setting default now() 1.2.2 1.2.2 stable stable 2006-09-03 BSD License - flip positions property array in prepared statement objects to make it possible to optionally use the same named placeholder in multiple places inside a single prepared statement note: - the multi_query test failes because this is not supported by ext/mysql - use a trigger to emulate setting default now() 1.2.1 1.2.1 stable stable 2006-08-21 BSD License - fixed issue in tableInfo() that originates in getTableFieldDefinition() which led to returning incorrect type values (Bug #8291) - quote identifiers in the reverse module when 'quote_identifiers' is enabled (Bug #8309) - use version_compare() to fix complex version comparisons (Bug #8355) - do not use quote() in setCharset() since it is supposed to set the charset in the connection that was passed to it - return an error if a named placeholder name is used twice inside a single statement - do not list empty contraints and indexes - added support for 'primary' option in createTable() - fixed notnull reverse engineering on mysql 4.x (Bug #8415) - do not set a default if type is a LOB (Request #8074) - if a default value is set, then we need to use VARCHAR instead of TEXT - removed _verifyTableType() since it just adds overhead, is hard to do reliably and you will get an error if the table type is not supported anyways - fixed handling return values when disable_query is set in _doQuery() and _execute() - only call RELEASE SAVEPOINT if the server version if 5.0.3 or higher - increased MDB2 dependency too 2.2.1 note: - the multi_query test failes because this is not supported by ext/mysql - use a trigger to emulate setting default now() 1.2.0 1.2.0 stable stable 2006-07-23 BSD License - added ability to escape wildcard characters in escape() and quote() - added setTransactionIsolation() - added savepoint support to beginTransaction(), commit() and rollback() - added debug() call at the end of a query/prepare/execute calling (Request #7933) - added context array parameter to debug() and make use of it whereever sensible - added optional method name parameter to raiseError() and use whereever possible - added ability to escape wildcard characters in escape() and quote() - added debug() call at the end of a query/prepare/execute calling (Request #7933) - added 'nativetype' output to tableInfo() and getTableFieldDefinition() - added 'mdb2type' output to getTableFieldDefinition() - reworked tableInfo() to use a common implementation based on getTableFieldDefinition() when a table name is passed (Bug #8124) - fixed incorrect regex in mapNativeDatatype() (Bug #8256) (thx ioz at ionosfera dot com) - use old dsn when rolling back open transactions in disconnect() - mysql_insert_id() breaks if the prepared statement that was executed to generate the ID is deallocated before (Bug #8051) 1.1.0 1.1.0 stable stable 2006-06-15 BSD License - tweaked handling of free() for prepared statements - return error if a prepared statement is attempted to be freed twice - added setCharset() - use setCharset() in connect()/_doConnect() - set transaction enabled engine as default - generalized quoteIdentifier() with a property - use general implementation of quoteIdentifier() (Bug #7738) - fixed warning in replace() if type is not set (Bug #7740) - cosmetic performance tweak in getTableFieldDefinition() - switched most array_key_exists() calls to !empty() to improve readability and performance - fixed a few edge cases and potential warnings - added ability to rewrite queries for query(), exec() and prepare() using a debug handler callback - pass limit and offset to the result object constructor in _execute() for read statements - check if result/connection has not yet been freed/dicsonnected before attempting to free a result set(Bug #7790) - revert change that would prefer 'clob' over 'text' for TEXT fields (this was breaking runtime instrospection) 1.0.3 1.0.3 stable stable 2006-05-22 BSD License - fixed unsigned handling and minor tweaks to type mapping in mapNativeDatatype() - use emulated prepared statements on mysql 4.0 or lower - properly quote query to prepare - added "emulate_prepared" option to force prepared statement emulation 1.0.2 1.0.2 stable stable 2006-05-14 BSD License - optimized listTables() and listViews() - optimized show related queries - explicitly set is_manip parameter to false for transaction debug calls - silently change name of primary key contraints to PRIMARY - added ability to hint that a constraint is a primary key in dropConstraint() - typo fixes in phpdoc (thx Stoyan) - added support for fixed and variable types for 'text' in declarations, as well as in reverse engineering (Request #1523) - made _doQuery() return a reference - added userinfo's to all raiseError calls that previously had none - use native prepared queries of mysql 4.1 or higher - added 'prepared_statements' supported meta data setting - added missing supported parameter to prepare() signature 1.0.1 1.0.1 stable stable 2006-04-16 BSD License - handle qualified table names in tableInfo() - aligned _modifyQuery() signature and phpdoc - added support for tabe options in createTable() (bug ##7079) - added optional database parameter to listTables() and listSequences() - added 'result_introspection' supported metadata support - properly quote table names in tableInfo() (related to bug #6573) - use connected_server_info in getServerVersion() as a cache cache - use parent::disconnect() in disconnect() - added support for length in integer reverse engineering - some fixes regarding boolean reverse engineering - added listViews() and hide views in listTables() 1.0.0 1.0.0 stable stable 2006-02-09 BSD License - handle null as resource when disable_query option is enabled in result object 0.2.4 0.2.4 beta beta 2006-02-05 BSD License - added support for length in integer and decimal columns - removed ugly hack for quote parameter in quote() since it was insufficient (escaping also needs to be prevented) - now using TINYINT(1) by default instead of CHAR(1) for the boolean datatype (BC BREAK!) - typo fix in error message in createTable() - improved parsing in getServerInfo() (bug #6550) - fixed subselect emulation - support an arbitrary number of arguments in concat() 0.2.3 0.2.3 beta beta 2006-01-13 BSD License - explicitly pass if the module is phptype specific in all loadModule calls (bug #6226) - properly handle PRIMARY keys in listTableConstraints() - apply _isIndexName() on non primary keys in getTableConstraintDefinition() - fixed signature of quoteIdentifier() to make second param optional - fixed signature of executeStoredProc() - typo fixes in error handling of nextResult() and numRows() calls - _fixIndexName() now just attempts to remove possible formatting - renamed _isSequenceName() to _fixSequenceName() - _fixSequenceName() now just attempts to remove possible formatting, and only returns a boolean if no formatting was applied when the new "check" parameter is set to true 0.2.2 0.2.2 beta beta 2005-12-30 BSD License - fixed typo in sub select detection 0.2.1 0.2.1 beta beta 2005-12-28 BSD License - proper quote new table name in alterTable() 0.2.0 0.2.0 beta beta 2005-12-21 BSD License - do not fix case in listUsers() - unified case fixing in the list*() methods - fixed change and rename in alterTable() - primary key must be called primary - use getConnection() to access connection property - split index and constraint handling - quote identifiers where possible inside the manager methods depending on the new 'quote_identifier' option (defaults to off) - refactored get*Declaration() methods to use getTypeDeclaration() - setting in_transaction to false on disconnect - hide constraints from indexes and vice versa in the list methods - added new Function modules to handle difference in SQL functions - force rollback() with open transactions on disconnect - fixed table renaming - escape floats to make sure they do not contain evil characters (bug #5608) - support column length in create index (mysql only feature, but a nice touch, emulating it with substring is not feasible though) - ensure that there is a connection in the escape() method - split off manipulation queries into exec() method from the query() method *BC BREAK* - only if result_types is set to false in prepare() method the query will be handled as a DML statement *BC BREAK* - use a proper default value if a field is set to not null in _getDeclaration*() (bug #5930) - added ability to determine unsigned in mapNativeDatatype() (only really implemented in the mysql(i) drivers) (bug #6054) - use MDB2_ERROR_NOT_FOUND in getTableConstraintDefinition() and getTableIndexDefinition() (bug #6055) - Sync lastInsertID with the mysqli implementation - use lastInsertID() method in nextID() - added getServerVersion() and use it to determine sub select support - unified array structure in mapNativeDatatype() *BC BREAK* - added 'mdbtype' to tableInfo() output that is generated from mapNativeDatatype() - changed 'len' to 'length' in tableInfo() output *BC BREAK* 0.1.1 0.1.1 beta beta 2005-10-16 BSD License Warning: this release features numerous BC breaks! There have been considerable improvements to the datatype, manager and reverse modules. Furthermore preliminary support for auto increment and primary keys has been added. Please note that making a field auto increment implies a single column primary key on this field. - increased php dependency to 4.3.0 due to the usage of the streams API since beta5 - ensure that instance is connected before using connection property in tableInfo() - added support for auto increment and primary key in schema. - alterTable now needs the full definition to work (use getTableFieldDefinition from Reverse module if you do not have a definition at hand) this eliminates the need of the declaration part in the alterTable array. - ensure that instance is connected before using connection property in tableInfo() - removed support for dummy_primary_key - fix PHP4.4 breakage - moved getInsertID() into core as lastInsertID() - use !empty() instead of isset() in fetchRow to determine if result cols were bound or result types were set - moved all private fetch mode fix methods into _fixResultArrayValues() for performance reasons - renamed MDB2_PORTABILITY_LOWERCASE to MDB2_PORTABILITY_FIX_CASE and use 'field_case' option to determine if to upper- or lowercase (CASE_LOWER/CASE_UPPER) - count() -> !empty() where possible - use array_map() instead of array_flip(array_change_key_case(array_flip())) to fix case of array values - use array_key_exists() instead of isset() where possible - changed structure of field add/remove/change in alterTable() to match MDB2_Schema - removed subSelect() implementation (now in already included in common) - return 0 for manipulation queries when disable_query is enabled - tweaked handling of notnull and default in field reverse engineering - tweaked getTableFieldDefinition() in reverse module 0.1.0 0.1.0 beta beta 2005-04-29 BSD License first unbundled release from MDB2 core - fixed new_link support (bug #4308) - dont just check for isset() for boolean values in order to support setting them false as well (bug #4373) - ensure SQL injection protection in all _quote() methods (was missing in some decimal, float, time, date and timestamp implementations) - dont inherite from the mysqli driver anymore to prevent indirect dependency on mysqli php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/tests/000077500000000000000000000000001213613664500232725ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/tests/Nonstandard/000077500000000000000000000000001213613664500255455ustar00rootroot00000000000000php-mdb2-driver-mysql-1.5.0b4/MDB2_Driver_mysql-1.5.0b4/tests/Nonstandard/MysqlHelper.php000066400000000000000000000101341213613664500305220ustar00rootroot00000000000000 | // +----------------------------------------------------------------------+ // // $Id: MysqlHelper.php 321214 2011-12-19 18:23:44Z danielc $ class Nonstandard_MysqlHelper extends Nonstandard_Base { public $trigger_body = ''; public function createTrigger($trigger_name, $table_name) { $this->trigger_body = 'BEGIN UPDATE '. $table_name .' SET somedescription = OLD.somename WHERE id = NEW.id; END'; $query = 'CREATE TRIGGER '. $trigger_name .' AFTER UPDATE ON '. $table_name .' FOR EACH ROW '. $this->trigger_body .';'; return $this->db->exec($query); } public function checkTrigger($trigger_name, $table_name, $def) { parent::checkTrigger($trigger_name, $table_name, $def); $this->test->assertEquals($this->trigger_body, $def['trigger_body']); } public function dropTrigger($trigger_name, $table_name) { return $this->db->exec('DROP TRIGGER '.$trigger_name); } public function createFunction($name) { $query = 'CREATE FUNCTION '.$name.'(a INT, b INT) RETURNS INT RETURN a + b;'; return $this->db->exec($query); } } php-mdb2-driver-mysql-1.5.0b4/package.xml000066400000000000000000001001351213613664500201340ustar00rootroot00000000000000 MDB2_Driver_mysql pear.php.net mysql MDB2 driver This is the MySQL MDB2 driver. Lukas Kahwe Smith lsmith smith@pooteeweet.org no Lorenzo Alberton quipo l.alberton@quipo.it yes 2012-10-23 1.5.0b4 1.5.0b4 beta beta BSD License - Make varchar_max_length property public, Bug #19582. - Revert 327099 by afz, caused "Notice: Undefined index: charset on line 1003" - PEAR::isError() -> MDB2::isError(), Bug #19491. - PEAR::loadExtension() -> extension_loaded(), Bug #19583. - max value for VARCHAR is 65535 but if used multi-bytes (UTF8) so it is 21844, because UTF8 string takes 3bytes - Fix Bug #19262. Updates conditional stagements to use logical operators to include MDB2_FETCHMODE_OBJECT where appropriate. Was broken in r321197. - Have truncateTable() return MDB2_OK on success, as documented (bug 19201) - Have alterTable() return MDB2_OK on success, as documented (bug 19200) - Have dropIndex() return MDB2_OK on success, as documented (bug 19198) - Have vacuum() return MDB2_OK on success, as documented (bug 19196) - Have createIndex() return MDB2_OK on success, as documented (bug 19195) - Have dropConstraint() return MDB2_OK on success, as documented (bug 19194) - Have dropSequence() return MDB2_OK on success, as documented (bug 19191). - Make setOption('result_wrap_class') actually useful by changing the default value of $result_wrap_class parameters from false to true. - Obtain error information in _doQuery() because standaloneQuery() throws off $this->connection. - FETCHMODE constants are NOT bitwise. - Make $sql_comments public (was before, used in tests, no real harm). - Property visibility - boolean data type - fixed bug #17984: Error is not reported when mysqli_stmt_bind_param() fails [dennylin93] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type - fixed bug #18203: Type introspection breaks with associative arrays if names are identical (patch by Peter Bex) - fixed bug #17892: removed debug message [pdt256] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type open todo items: - use a trigger to emulate setting default now() 5.2.0 1.9.1 MDB2 pear.php.net 2.5.0b4 mysql 1.5.0b4 1.5.0b4 beta beta 2012-10-23 BSD License - Make varchar_max_length property public, Bug #19582. - Revert 327099 by afz, caused "Notice: Undefined index: charset on line 1003" - PEAR::isError() -> MDB2::isError(), Bug #19491. - PEAR::loadExtension() -> extension_loaded(), Bug #19583. - max value for VARCHAR is 65535 but if used multi-bytes (UTF8) so it is 21844, because UTF8 string takes 3bytes - Fix Bug #19262. Updates conditional stagements to use logical operators to include MDB2_FETCHMODE_OBJECT where appropriate. Was broken in r321197. - Have truncateTable() return MDB2_OK on success, as documented (bug 19201) - Have alterTable() return MDB2_OK on success, as documented (bug 19200) - Have dropIndex() return MDB2_OK on success, as documented (bug 19198) - Have vacuum() return MDB2_OK on success, as documented (bug 19196) - Have createIndex() return MDB2_OK on success, as documented (bug 19195) - Have dropConstraint() return MDB2_OK on success, as documented (bug 19194) - Have dropSequence() return MDB2_OK on success, as documented (bug 19191). - Make setOption('result_wrap_class') actually useful by changing the default value of $result_wrap_class parameters from false to true. - Obtain error information in _doQuery() because standaloneQuery() throws off $this->connection. - FETCHMODE constants are NOT bitwise. - Make $sql_comments public (was before, used in tests, no real harm). - Property visibility - boolean data type - fixed bug #17984: Error is not reported when mysqli_stmt_bind_param() fails [dennylin93] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type - fixed bug #18203: Type introspection breaks with associative arrays if names are identical (patch by Peter Bex) - fixed bug #17892: removed debug message [pdt256] - fixed bug #18057: Result of getDeclaration() can have invalid syntax [hschletz] - request #18068: mapNativeDatatype() returns decimal places also for 'float' mdb2type open todo items: - use a trigger to emulate setting default now() 1.5.0b3 1.5.0b3 beta beta 2010-08-29 BSD License - fixed bug #15650: mysqli function used in setCharset() - fixed bug #16003: incorrect check for error after mysql_store_result - fixed bug #16147: first prepared statement is emulated when using factory with mysql - fixed bug #16669: hostspec is ignored when protocol is unix - fixed bug #17037: 'on update' not mentioned in tableInfo() - fixed bug #17065: There is no NEW row in on DELETE trigger (fix error in FK constraint triggers) - fixed bug #17650: lastInsertId can not handle bigint, forces cast to integer [alexpw] - return ON UPDATE|DELETE action in getTableConstraintDefinition() note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0b2 1.5.0b2 beta beta 2009-01-14 BSD License - fixed bug #12117: disconnect() does not work as documented - fixed bug #13412: sometimes getTableConstraintDefinition() fails for FOREIGN KEYs - fixed bug #13581: wrong query in beginTransaction() for certain MySQL versions - request #13657: in setCharset(), use mysql_set_charset() if available [cwiedmann] - fixed bug #13928: Invalid triggers created for 'ON UPDATE' - fixed bug #15051: Cannot create constraints with field length - add index on FK column(s) or a FK constraint cannot be created in some cases note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0b1 1.5.0b1 alpha alpha 2008-03-15 BSD License - fixed bug #11831: createTable() now supports tables with a multi-field PRIMARY KEY where one field is defined as AUTO_INCREMENT - request #11204: support AUTO_INCREMENT for FLOAT data type and UNSIGNED option for FLOAT and DECIMAL data type [afz] - fixed bug #11692: value of $db->supports('transactions') changes after query [afz] - request #12731: added truncateTable() in the Manager module - request #12732: added vacuum() in the Manager module for OPTIMIZE/VACUUM TABLE abstraction - request #12800: added alterDatabase() in the Manager module [afz] - fixed quoting in createDatabase() in the Manager module - fixed bug #12924: correctly handle internal expected errors even with custom error handling - added standaloneQuery() and databaseExists() - request #13106: added unixtimestamp() in the Function module - fixed regexp in listTableConstraints() in the Manager module to list FOREIGN KEY constraints - fixed bug #13180: MySQL driver tells SAVEPOINT is supported for MyISAM tables - fixed bug #13283: replace() doesn't respect quote_identifiers option - request #13313: setCharSet() supports 'COLLATE' too - fixed bug #13370: some capabilities depend on user options, so check them after a setOption() call - when triggers are supported, two triggers are created to emulate ON UPDATE / ON DELETE actions for FOREIGN KEY constraints. Known limitation: since mysql doesn't support multiple triggers with the same action time and event for one table, if there are multiple table referencing the same table, only the first one will have the triggers created. note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0a2 1.5.0a2 alpha alpha 2007-12-06 BSD License - fixed bug #12516: error in FK constraint creation query - request #12012: added charset/collation support in createDatabase() note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.5.0a1 1.5.0a1 alpha alpha 2007-10-28 BSD License - fixed bug #10024: Added new option 'lob_allow_url_include' (default false) to [dis]allow inserting a LOB from an url (file, http, ...). - fixed bug #10986: Using more random statement names (request #11625) - fixed bug #11055: Using placeholders with := variable assignment fails [bekarau] - initial support for FOREIGN KEY constraints in the Manager and Reverse modules - request #11389: added many new MySQL 5.1 error codes in errorInfo() - fixed bug #11428: propagate quote() errors with invalid data types - fixed bug #11590: _getServerCapabilities() has to be called once per connection - fixed bug #11790: avoid array_diff() because it has a memory leak in PHP 5.1.x - fixed some E_STRICT errors with PHP5 - fixed bug #12010: MDB2_PORTABILITY_RTRIM option was ignored - fixed bug #12083: createTable() in the Manager module now returns MDB2_OK on success, as documented - fixed bug #12217: mysql_num_rows() returns FALSE on failure, not NULL (thanks to zaa@zaa.pp.ru) - fixed bug #12242: missing charset info in the Reverse module (patch by Carsten Wiedmann) - fixed bug #12269: tableInfo() in the Reverse module detect 'clob' data type as first option - fixed bug #12336: supply default value for NOT NULL timestamp fields note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.4.1 1.4.1 stable stable 2007-05-03 BSD License - fixed bug #10378: incorrect query rewrite in setLimit() when using "FOR UPDATE" or "LOCK IN SHARE MODE" (thanks to priyadi) or "INTO OUTFILE" or "INTO DUMPFILE" - return length as "precision,scale" for NUMERIC and DECIMAL fields in mapNativeDatatype() - in getTableIndexDefinition() and getTableConstraintDefinition() in the Reverse module, also return the field position in the index/constraint - fixed bug #10636: transactions broken in release 2.4.0 because of some properties being reset (thanks to Conor Kerr) - fixed bug #10807: connect() Method Incorrectly Caches Connections [fornax] - fixed bug #10895: setLimit() does not work properly when a subquery uses LIMIT note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.4.0 1.4.0 stable stable 2007-03-13 BSD License - fixed bug #9283: missing support for BINARY/VARBINARY data types (thanks to Tom Hendrikx) - propagate errors in getTableFieldDefinition() in the Reverse module - implemented getTriggerDefinition() in the Reverse module (mysql > 5.0.2) [experimental] - implemented listTableTriggers() in the Manager module (mysql > 5.0.2) - implemented listFunctions() in the Manager module - setCharset() now uses "SET NAMES" instead of "SET character_set_client" - select the mysql database in listUsers() in the Manager module - added error codes for MySQL 5 (patch by Adam Harvey) - implemented guid() in the Function module [globally unique identifier] - fixed bug #10033: beginTransaction() does not know server capabilities - fixed bug #10057: createConstraint() returns an error when the definition is incomplete - request #9451: you can set charset, collation, engine and comments in createSequence() - implemented a fallback mechanism within getTableIndexDefinition() and getTableConstraintDefinition() in the Reverse module to ignore the 'idxname_format' option and use the index name as provided in case of failure before returning an error - fixed bug #10181: propagate error when an invalid type is passed to prepare() - added a 'nativetype_map_callback' option to map native data declarations back to custom data types (thanks to Andrew Hill). - fixed bug #10239: execute() misinterprets MySQL's user defined variables - phpdoc fixes note: - the multi_query test failes because this is not supported by ext/mysql open todo items: - use a trigger to emulate setting default now() 1.3.0 2.3.0 stable stable 2006-11-03 BSD License - added charset and collation support to field declaration - fixed bug #9024: typo in error checking - fix typos in error handling in a few places (bug #9024) - do not skip id generation in nextId() when creating a sequence on demand because this prevents lastInsertID() from working - migrated to package.xml version 2 note: - the multi_query test failes because this is not supported by ext/mysql - use a trigger to emulate setting default now() 1.2.2 1.2.2 stable stable 2006-09-03 BSD License - flip positions property array in prepared statement objects to make it possible to optionally use the same named placeholder in multiple places inside a single prepared statement note: - the multi_query test failes because this is not supported by ext/mysql - use a trigger to emulate setting default now() 1.2.1 1.2.1 stable stable 2006-08-21 BSD License - fixed issue in tableInfo() that originates in getTableFieldDefinition() which led to returning incorrect type values (Bug #8291) - quote identifiers in the reverse module when 'quote_identifiers' is enabled (Bug #8309) - use version_compare() to fix complex version comparisons (Bug #8355) - do not use quote() in setCharset() since it is supposed to set the charset in the connection that was passed to it - return an error if a named placeholder name is used twice inside a single statement - do not list empty contraints and indexes - added support for 'primary' option in createTable() - fixed notnull reverse engineering on mysql 4.x (Bug #8415) - do not set a default if type is a LOB (Request #8074) - if a default value is set, then we need to use VARCHAR instead of TEXT - removed _verifyTableType() since it just adds overhead, is hard to do reliably and you will get an error if the table type is not supported anyways - fixed handling return values when disable_query is set in _doQuery() and _execute() - only call RELEASE SAVEPOINT if the server version if 5.0.3 or higher - increased MDB2 dependency too 2.2.1 note: - the multi_query test failes because this is not supported by ext/mysql - use a trigger to emulate setting default now() 1.2.0 1.2.0 stable stable 2006-07-23 BSD License - added ability to escape wildcard characters in escape() and quote() - added setTransactionIsolation() - added savepoint support to beginTransaction(), commit() and rollback() - added debug() call at the end of a query/prepare/execute calling (Request #7933) - added context array parameter to debug() and make use of it whereever sensible - added optional method name parameter to raiseError() and use whereever possible - added ability to escape wildcard characters in escape() and quote() - added debug() call at the end of a query/prepare/execute calling (Request #7933) - added 'nativetype' output to tableInfo() and getTableFieldDefinition() - added 'mdb2type' output to getTableFieldDefinition() - reworked tableInfo() to use a common implementation based on getTableFieldDefinition() when a table name is passed (Bug #8124) - fixed incorrect regex in mapNativeDatatype() (Bug #8256) (thx ioz at ionosfera dot com) - use old dsn when rolling back open transactions in disconnect() - mysql_insert_id() breaks if the prepared statement that was executed to generate the ID is deallocated before (Bug #8051) 1.1.0 1.1.0 stable stable 2006-06-15 BSD License - tweaked handling of free() for prepared statements - return error if a prepared statement is attempted to be freed twice - added setCharset() - use setCharset() in connect()/_doConnect() - set transaction enabled engine as default - generalized quoteIdentifier() with a property - use general implementation of quoteIdentifier() (Bug #7738) - fixed warning in replace() if type is not set (Bug #7740) - cosmetic performance tweak in getTableFieldDefinition() - switched most array_key_exists() calls to !empty() to improve readability and performance - fixed a few edge cases and potential warnings - added ability to rewrite queries for query(), exec() and prepare() using a debug handler callback - pass limit and offset to the result object constructor in _execute() for read statements - check if result/connection has not yet been freed/dicsonnected before attempting to free a result set(Bug #7790) - revert change that would prefer 'clob' over 'text' for TEXT fields (this was breaking runtime instrospection) 1.0.3 1.0.3 stable stable 2006-05-22 BSD License - fixed unsigned handling and minor tweaks to type mapping in mapNativeDatatype() - use emulated prepared statements on mysql 4.0 or lower - properly quote query to prepare - added "emulate_prepared" option to force prepared statement emulation 1.0.2 1.0.2 stable stable 2006-05-14 BSD License - optimized listTables() and listViews() - optimized show related queries - explicitly set is_manip parameter to false for transaction debug calls - silently change name of primary key contraints to PRIMARY - added ability to hint that a constraint is a primary key in dropConstraint() - typo fixes in phpdoc (thx Stoyan) - added support for fixed and variable types for 'text' in declarations, as well as in reverse engineering (Request #1523) - made _doQuery() return a reference - added userinfo's to all raiseError calls that previously had none - use native prepared queries of mysql 4.1 or higher - added 'prepared_statements' supported meta data setting - added missing supported parameter to prepare() signature 1.0.1 1.0.1 stable stable 2006-04-16 BSD License - handle qualified table names in tableInfo() - aligned _modifyQuery() signature and phpdoc - added support for tabe options in createTable() (bug ##7079) - added optional database parameter to listTables() and listSequences() - added 'result_introspection' supported metadata support - properly quote table names in tableInfo() (related to bug #6573) - use connected_server_info in getServerVersion() as a cache cache - use parent::disconnect() in disconnect() - added support for length in integer reverse engineering - some fixes regarding boolean reverse engineering - added listViews() and hide views in listTables() 1.0.0 1.0.0 stable stable 2006-02-09 BSD License - handle null as resource when disable_query option is enabled in result object 0.2.4 0.2.4 beta beta 2006-02-05 BSD License - added support for length in integer and decimal columns - removed ugly hack for quote parameter in quote() since it was insufficient (escaping also needs to be prevented) - now using TINYINT(1) by default instead of CHAR(1) for the boolean datatype (BC BREAK!) - typo fix in error message in createTable() - improved parsing in getServerInfo() (bug #6550) - fixed subselect emulation - support an arbitrary number of arguments in concat() 0.2.3 0.2.3 beta beta 2006-01-13 BSD License - explicitly pass if the module is phptype specific in all loadModule calls (bug #6226) - properly handle PRIMARY keys in listTableConstraints() - apply _isIndexName() on non primary keys in getTableConstraintDefinition() - fixed signature of quoteIdentifier() to make second param optional - fixed signature of executeStoredProc() - typo fixes in error handling of nextResult() and numRows() calls - _fixIndexName() now just attempts to remove possible formatting - renamed _isSequenceName() to _fixSequenceName() - _fixSequenceName() now just attempts to remove possible formatting, and only returns a boolean if no formatting was applied when the new "check" parameter is set to true 0.2.2 0.2.2 beta beta 2005-12-30 BSD License - fixed typo in sub select detection 0.2.1 0.2.1 beta beta 2005-12-28 BSD License - proper quote new table name in alterTable() 0.2.0 0.2.0 beta beta 2005-12-21 BSD License - do not fix case in listUsers() - unified case fixing in the list*() methods - fixed change and rename in alterTable() - primary key must be called primary - use getConnection() to access connection property - split index and constraint handling - quote identifiers where possible inside the manager methods depending on the new 'quote_identifier' option (defaults to off) - refactored get*Declaration() methods to use getTypeDeclaration() - setting in_transaction to false on disconnect - hide constraints from indexes and vice versa in the list methods - added new Function modules to handle difference in SQL functions - force rollback() with open transactions on disconnect - fixed table renaming - escape floats to make sure they do not contain evil characters (bug #5608) - support column length in create index (mysql only feature, but a nice touch, emulating it with substring is not feasible though) - ensure that there is a connection in the escape() method - split off manipulation queries into exec() method from the query() method *BC BREAK* - only if result_types is set to false in prepare() method the query will be handled as a DML statement *BC BREAK* - use a proper default value if a field is set to not null in _getDeclaration*() (bug #5930) - added ability to determine unsigned in mapNativeDatatype() (only really implemented in the mysql(i) drivers) (bug #6054) - use MDB2_ERROR_NOT_FOUND in getTableConstraintDefinition() and getTableIndexDefinition() (bug #6055) - Sync lastInsertID with the mysqli implementation - use lastInsertID() method in nextID() - added getServerVersion() and use it to determine sub select support - unified array structure in mapNativeDatatype() *BC BREAK* - added 'mdbtype' to tableInfo() output that is generated from mapNativeDatatype() - changed 'len' to 'length' in tableInfo() output *BC BREAK* 0.1.1 0.1.1 beta beta 2005-10-16 BSD License Warning: this release features numerous BC breaks! There have been considerable improvements to the datatype, manager and reverse modules. Furthermore preliminary support for auto increment and primary keys has been added. Please note that making a field auto increment implies a single column primary key on this field. - increased php dependency to 4.3.0 due to the usage of the streams API since beta5 - ensure that instance is connected before using connection property in tableInfo() - added support for auto increment and primary key in schema. - alterTable now needs the full definition to work (use getTableFieldDefinition from Reverse module if you do not have a definition at hand) this eliminates the need of the declaration part in the alterTable array. - ensure that instance is connected before using connection property in tableInfo() - removed support for dummy_primary_key - fix PHP4.4 breakage - moved getInsertID() into core as lastInsertID() - use !empty() instead of isset() in fetchRow to determine if result cols were bound or result types were set - moved all private fetch mode fix methods into _fixResultArrayValues() for performance reasons - renamed MDB2_PORTABILITY_LOWERCASE to MDB2_PORTABILITY_FIX_CASE and use 'field_case' option to determine if to upper- or lowercase (CASE_LOWER/CASE_UPPER) - count() -> !empty() where possible - use array_map() instead of array_flip(array_change_key_case(array_flip())) to fix case of array values - use array_key_exists() instead of isset() where possible - changed structure of field add/remove/change in alterTable() to match MDB2_Schema - removed subSelect() implementation (now in already included in common) - return 0 for manipulation queries when disable_query is enabled - tweaked handling of notnull and default in field reverse engineering - tweaked getTableFieldDefinition() in reverse module 0.1.0 0.1.0 beta beta 2005-04-29 BSD License first unbundled release from MDB2 core - fixed new_link support (bug #4308) - dont just check for isset() for boolean values in order to support setting them false as well (bug #4373) - ensure SQL injection protection in all _quote() methods (was missing in some decimal, float, time, date and timestamp implementations) - dont inherite from the mysqli driver anymore to prevent indirect dependency on mysqli