diff --git a/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md b/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md index 4d155fb300..496ce46fd5 100644 --- a/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md +++ b/DOCUMENTATION/SYSTEM_ADMINISTRATORS/CONFIGURE.md @@ -173,10 +173,6 @@ The ones that you may want to set are listed below for clarity. Note that the real name of your database should go in there, not literally 'yourdbname'. -* `db_driver`(enum['DB','MDB2'], default null): You can try changing this to - 'MDB2' to use the other driver type for DB_DataObject, but note that it - breaks the OpenID libraries, which only support PEAR::DB. - * `type` (enum["mysql", "pgsql"], default 'mysql'): Used for certain database-specific optimization code. Assumes mysql if not set. "mysql" covers MariaDB, Oracle MySQL, mysqli or otherwise. diff --git a/extlib/DB.php b/extlib/DB.php deleted file mode 100644 index 18e9a287ea..0000000000 --- a/extlib/DB.php +++ /dev/null @@ -1,1506 +0,0 @@ - - * @author Tomas V.V.Cox - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the PEAR class so it can be extended from - */ -require_once 'PEAR.php'; - - -// {{{ constants -// {{{ error codes - -/**#@+ - * One of PEAR DB's portable error codes. - * @see DB_common::errorCode(), DB::errorMessage() - * - * {@internal If you add an error code here, make sure you also add a textual - * version of it in DB::errorMessage().}} - */ - -/** - * The code returned by many methods upon success - */ -define('DB_OK', 1); - -/** - * Unkown error - */ -define('DB_ERROR', -1); - -/** - * Syntax error - */ -define('DB_ERROR_SYNTAX', -2); - -/** - * Tried to insert a duplicate value into a primary or unique index - */ -define('DB_ERROR_CONSTRAINT', -3); - -/** - * An identifier in the query refers to a non-existant object - */ -define('DB_ERROR_NOT_FOUND', -4); - -/** - * Tried to create a duplicate object - */ -define('DB_ERROR_ALREADY_EXISTS', -5); - -/** - * The current driver does not support the action you attempted - */ -define('DB_ERROR_UNSUPPORTED', -6); - -/** - * The number of parameters does not match the number of placeholders - */ -define('DB_ERROR_MISMATCH', -7); - -/** - * A literal submitted did not match the data type expected - */ -define('DB_ERROR_INVALID', -8); - -/** - * The current DBMS does not support the action you attempted - */ -define('DB_ERROR_NOT_CAPABLE', -9); - -/** - * A literal submitted was too long so the end of it was removed - */ -define('DB_ERROR_TRUNCATED', -10); - -/** - * A literal number submitted did not match the data type expected - */ -define('DB_ERROR_INVALID_NUMBER', -11); - -/** - * A literal date submitted did not match the data type expected - */ -define('DB_ERROR_INVALID_DATE', -12); - -/** - * Attempt to divide something by zero - */ -define('DB_ERROR_DIVZERO', -13); - -/** - * A database needs to be selected - */ -define('DB_ERROR_NODBSELECTED', -14); - -/** - * Could not create the object requested - */ -define('DB_ERROR_CANNOT_CREATE', -15); - -/** - * Could not drop the database requested because it does not exist - */ -define('DB_ERROR_CANNOT_DROP', -17); - -/** - * An identifier in the query refers to a non-existant table - */ -define('DB_ERROR_NOSUCHTABLE', -18); - -/** - * An identifier in the query refers to a non-existant column - */ -define('DB_ERROR_NOSUCHFIELD', -19); - -/** - * The data submitted to the method was inappropriate - */ -define('DB_ERROR_NEED_MORE_DATA', -20); - -/** - * The attempt to lock the table failed - */ -define('DB_ERROR_NOT_LOCKED', -21); - -/** - * The number of columns doesn't match the number of values - */ -define('DB_ERROR_VALUE_COUNT_ON_ROW', -22); - -/** - * The DSN submitted has problems - */ -define('DB_ERROR_INVALID_DSN', -23); - -/** - * Could not connect to the database - */ -define('DB_ERROR_CONNECT_FAILED', -24); - -/** - * The PHP extension needed for this DBMS could not be found - */ -define('DB_ERROR_EXTENSION_NOT_FOUND', -25); - -/** - * The present user has inadequate permissions to perform the task requestd - */ -define('DB_ERROR_ACCESS_VIOLATION', -26); - -/** - * The database requested does not exist - */ -define('DB_ERROR_NOSUCHDB', -27); - -/** - * Tried to insert a null value into a column that doesn't allow nulls - */ -define('DB_ERROR_CONSTRAINT_NOT_NULL', -29); -/**#@-*/ - - -// }}} -// {{{ prepared statement-related - - -/**#@+ - * Identifiers for the placeholders used in prepared statements. - * @see DB_common::prepare() - */ - -/** - * Indicates a scalar (?) placeholder was used - * - * Quote and escape the value as necessary. - */ -define('DB_PARAM_SCALAR', 1); - -/** - * Indicates an opaque (&) placeholder was used - * - * The value presented is a file name. Extract the contents of that file - * and place them in this column. - */ -define('DB_PARAM_OPAQUE', 2); - -/** - * Indicates a misc (!) placeholder was used - * - * The value should not be quoted or escaped. - */ -define('DB_PARAM_MISC', 3); -/**#@-*/ - - -// }}} -// {{{ binary data-related - - -/**#@+ - * The different ways of returning binary data from queries. - */ - -/** - * Sends the fetched data straight through to output - */ -define('DB_BINMODE_PASSTHRU', 1); - -/** - * Lets you return data as usual - */ -define('DB_BINMODE_RETURN', 2); - -/** - * Converts the data to hex format before returning it - * - * For example the string "123" would become "313233". - */ -define('DB_BINMODE_CONVERT', 3); -/**#@-*/ - - -// }}} -// {{{ fetch modes - - -/**#@+ - * Fetch Modes. - * @see DB_common::setFetchMode() - */ - -/** - * Indicates the current default fetch mode should be used - * @see DB_common::$fetchmode - */ -define('DB_FETCHMODE_DEFAULT', 0); - -/** - * Column data indexed by numbers, ordered from 0 and up - */ -define('DB_FETCHMODE_ORDERED', 1); - -/** - * Column data indexed by column names - */ -define('DB_FETCHMODE_ASSOC', 2); - -/** - * Column data as object properties - */ -define('DB_FETCHMODE_OBJECT', 3); - -/** - * For multi-dimensional results, make the column name the first level - * of the array and put the row number in the second level of the array - * - * This is flipped from the normal behavior, which puts the row numbers - * in the first level of the array and the column names in the second level. - */ -define('DB_FETCHMODE_FLIPPED', 4); -/**#@-*/ - -/**#@+ - * Old fetch modes. Left here for compatibility. - */ -define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); -define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); -define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED); -/**#@-*/ - - -// }}} -// {{{ tableInfo() && autoPrepare()-related - - -/**#@+ - * The type of information to return from the tableInfo() method. - * - * Bitwised constants, so they can be combined using | - * and removed using ^. - * - * @see DB_common::tableInfo() - * - * {@internal Since the TABLEINFO constants are bitwised, if more of them are - * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}} - */ -define('DB_TABLEINFO_ORDER', 1); -define('DB_TABLEINFO_ORDERTABLE', 2); -define('DB_TABLEINFO_FULL', 3); -/**#@-*/ - - -/**#@+ - * The type of query to create with the automatic query building methods. - * @see DB_common::autoPrepare(), DB_common::autoExecute() - */ -define('DB_AUTOQUERY_INSERT', 1); -define('DB_AUTOQUERY_UPDATE', 2); -/**#@-*/ - - -// }}} -// {{{ portability modes - - -/**#@+ - * Portability Modes. - * - * Bitwised constants, so they can be combined using | - * and removed using ^. - * - * @see DB_common::setOption() - * - * {@internal Since the PORTABILITY constants are bitwised, if more of them are - * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}} - */ - -/** - * Turn off all portability features - */ -define('DB_PORTABILITY_NONE', 0); - -/** - * Convert names of tables and fields to lower case - * when using the get*(), fetch*() and tableInfo() methods - */ -define('DB_PORTABILITY_LOWERCASE', 1); - -/** - * Right trim the data output by get*() and fetch*() - */ -define('DB_PORTABILITY_RTRIM', 2); - -/** - * Force reporting the number of rows deleted - */ -define('DB_PORTABILITY_DELETE_COUNT', 4); - -/** - * Enable hack that makes numRows() work in Oracle - */ -define('DB_PORTABILITY_NUMROWS', 8); - -/** - * Makes certain error messages in certain drivers compatible - * with those from other DBMS's - * - * + mysql, mysqli: change unique/primary key constraints - * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT - * - * + odbc(access): MS's ODBC driver reports 'no such field' as code - * 07001, which means 'too few parameters.' When this option is on - * that code gets mapped to DB_ERROR_NOSUCHFIELD. - */ -define('DB_PORTABILITY_ERRORS', 16); - -/** - * Convert null values to empty strings in data output by - * get*() and fetch*() - */ -define('DB_PORTABILITY_NULL_TO_EMPTY', 32); - -/** - * Turn on all portability features - */ -define('DB_PORTABILITY_ALL', 63); -/**#@-*/ - -// }}} - - -// }}} -// {{{ class DB - -/** - * Database independent query interface - * - * The main "DB" class is simply a container class with some static - * methods for creating DB objects as well as some utility functions - * common to all parts of DB. - * - * The object model of DB is as follows (indentation means inheritance): - *
- * DB           The main DB class.  This is simply a utility class
- *              with some "static" methods for creating DB objects as
- *              well as common utility functions for other DB classes.
- *
- * DB_common    The base for each DB implementation.  Provides default
- * |            implementations (in OO lingo virtual methods) for
- * |            the actual DB implementations as well as a bunch of
- * |            query utility functions.
- * |
- * +-DB_mysql   The DB implementation for MySQL.  Inherits DB_common.
- *              When calling DB::factory or DB::connect for MySQL
- *              connections, the object returned is an instance of this
- *              class.
- * 
- * - * @category Database - * @package DB - * @author Stig Bakken - * @author Tomas V.V.Cox - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB -{ - // {{{ factory() - - /** - * Create a new DB object for the specified database type but don't - * connect to the database - * - * @param string $type the database type (eg "mysql") - * @param array $options an associative array of option names and values - * - * @return object a new DB object. A DB_Error object on failure. - * - * @see DB_common::setOption() - */ - public static function factory($type, $options = []) - { - if (!is_array($options)) { - $options = array('persistent' => $options); - } - - if (isset($options['debug']) && $options['debug'] >= 2) { - // expose php errors with sufficient debug level - include_once "DB/{$type}.php"; - } else { - @include_once "DB/{$type}.php"; - } - - $classname = "DB_${type}"; - - if (!class_exists($classname)) { - $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, - "Unable to include the DB/{$type}.php" - . " file for '$dsn'", - 'DB_Error', true); - return $tmp; - } - - @$obj = new $classname; - - foreach ($options as $option => $value) { - $test = $obj->setOption($option, $value); - if (DB::isError($test)) { - return $test; - } - } - - return $obj; - } - - // }}} - // {{{ connect() - - /** - * Determines if a variable is a DB_Error object - * - * @param mixed $value the variable to check - * - * @return bool whether $value is DB_Error object - */ - public static function isError($value) - { - return is_object($value) && is_a($value, 'DB_Error'); - } - - // }}} - // {{{ apiVersion() - - /** - * Create a new DB object including a connection to the specified database - * - * Example 1. - * - * require_once 'DB.php'; - * - * $dsn = 'pgsql://user:password@host/database'; - * $options = array( - * 'debug' => 2, - * 'portability' => DB_PORTABILITY_ALL, - * ); - * - * $db = DB::connect($dsn, $options); - * if (PEAR::isError($db)) { - * die($db->getMessage()); - * } - * - * - * @param mixed $dsn the string "data source name" or array in the - * format returned by DB::parseDSN() - * @param array $options an associative array of option names and values - * - * @return object a new DB object. A DB_Error object on failure. - * - * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(), - * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(), - * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(), - * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(), - * DB_sybase::connect() - * - * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError() - */ - public static function connect($dsn, $options = array()) - { - $dsninfo = DB::parseDSN($dsn); - $type = $dsninfo['phptype']; - - if (!is_array($options)) { - /* - * For backwards compatibility. $options used to be boolean, - * indicating whether the connection should be persistent. - */ - $options = array('persistent' => $options); - } - - if (isset($options['debug']) && $options['debug'] >= 2) { - // expose php errors with sufficient debug level - include_once "DB/${type}.php"; - } else { - @include_once "DB/${type}.php"; - } - - $classname = "DB_${type}"; - if (!class_exists($classname)) { - $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, - "Unable to include the DB/{$type}.php" - . " file for '" - . DB::getDSNString($dsn, true) . "'", - 'DB_Error', true); - return $tmp; - } - - @$obj = new $classname; - - foreach ($options as $option => $value) { - $test = $obj->setOption($option, $value); - if (DB::isError($test)) { - return $test; - } - } - - $err = $obj->connect($dsninfo, $obj->getOption('persistent')); - if (DB::isError($err)) { - if (is_array($dsn)) { - $err->addUserInfo(DB::getDSNString($dsn, true)); - } else { - $err->addUserInfo($dsn); - } - return $err; - } - - return $obj; - } - - // }}} - // {{{ isError() - - /** - * Parse a data source name - * - * Additional keys can be added by appending a URI query string to the - * end of the DSN. - * - * The format of the supplied DSN is in its fullest form: - * - * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true - * - * - * Most variations are allowed: - * - * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 - * phptype://username:password@hostspec/database_name - * phptype://username:password@hostspec - * phptype://username@hostspec - * phptype://hostspec/database - * phptype://hostspec - * phptype(dbsyntax) - * phptype - * - * - * @param string $dsn Data Source Name to be parsed - * - * @return array an associative array with the following keys: - * + phptype: Database backend used in PHP (mysql, odbc etc.) - * + dbsyntax: Database used with regards to SQL syntax etc. - * + protocol: Communication protocol to use (tcp, unix etc.) - * + hostspec: Host specification (hostname[:port]) - * + database: Database to use on the DBMS server - * + username: User name for login - * + password: Password for login - */ - public static function parseDSN($dsn) - { - $parsed = array( - 'phptype' => false, - 'dbsyntax' => false, - 'username' => false, - 'password' => false, - 'protocol' => false, - 'hostspec' => false, - 'port' => false, - 'socket' => false, - 'database' => false, - ); - - if (is_array($dsn)) { - $dsn = array_merge($parsed, $dsn); - if (!$dsn['dbsyntax']) { - $dsn['dbsyntax'] = $dsn['phptype']; - } - return $dsn; - } - - // Find phptype and dbsyntax - if (($pos = strpos($dsn, '://')) !== false) { - $str = substr($dsn, 0, $pos); - $dsn = substr($dsn, $pos + 3); - } else { - $str = $dsn; - $dsn = null; - } - - // Get phptype and dbsyntax - // $str => phptype(dbsyntax) - if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { - $parsed['phptype'] = $arr[1]; - $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; - } else { - $parsed['phptype'] = $str; - $parsed['dbsyntax'] = $str; - } - - if (empty($dsn)) { - return $parsed; - } - - // Get (if found): username and password - // $dsn => username:password@protocol+hostspec/database - if (($at = strrpos($dsn, '@')) !== false) { - $str = substr($dsn, 0, $at); - $dsn = substr($dsn, $at + 1); - if (($pos = strpos($str, ':')) !== false) { - $parsed['username'] = rawurldecode(substr($str, 0, $pos)); - $parsed['password'] = rawurldecode(substr($str, $pos + 1)); - } else { - $parsed['username'] = rawurldecode($str); - } - } - - // Find protocol and hostspec - - if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { - // $dsn => proto(proto_opts)/database - $proto = $match[1]; - $proto_opts = $match[2] ? $match[2] : false; - $dsn = $match[3]; - - } else { - // $dsn => protocol+hostspec/database (old format) - if (strpos($dsn, '+') !== false) { - list($proto, $dsn) = explode('+', $dsn, 2); - } - if (strpos($dsn, '/') !== false) { - list($proto_opts, $dsn) = explode('/', $dsn, 2); - } else { - $proto_opts = $dsn; - $dsn = null; - } - } - - // process the different protocol options - $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; - $proto_opts = rawurldecode($proto_opts); - if (strpos($proto_opts, ':') !== false) { - list($proto_opts, $parsed['port']) = explode(':', $proto_opts); - } - if ($parsed['protocol'] == 'tcp') { - $parsed['hostspec'] = $proto_opts; - } elseif ($parsed['protocol'] == 'unix') { - $parsed['socket'] = $proto_opts; - } - - // Get dabase if any - // $dsn => database - if ($dsn) { - if (($pos = strpos($dsn, '?')) === false) { - // /database - $parsed['database'] = rawurldecode($dsn); - } else { - // /database?param1=value1¶m2=value2 - $parsed['database'] = rawurldecode(substr($dsn, 0, $pos)); - $dsn = substr($dsn, $pos + 1); - if (strpos($dsn, '&') !== false) { - $opts = explode('&', $dsn); - } else { // database?param1=value1 - $opts = array($dsn); - } - foreach ($opts as $opt) { - list($key, $value) = explode('=', $opt); - if (!isset($parsed[$key])) { - // don't allow params overwrite - $parsed[$key] = rawurldecode($value); - } - } - } - } - - return $parsed; - } - - // }}} - // {{{ isConnection() - - /** - * Returns the given DSN in a string format suitable for output. - * - * @param array|string the DSN to parse and format - * @param boolean true to hide the password, false to include it - * @return string - */ - public static function getDSNString($dsn, $hidePassword) - { - /* Calling parseDSN will ensure that we have all the array elements - * defined, and means that we deal with strings and array in the same - * manner. */ - $dsnArray = DB::parseDSN($dsn); - - if ($hidePassword) { - $dsnArray['password'] = 'PASSWORD'; - } - - /* Protocol is special-cased, as using the default "tcp" along with an - * Oracle TNS connection string fails. */ - if (is_string($dsn) && strpos($dsn, 'tcp') === false && $dsnArray['protocol'] == 'tcp') { - $dsnArray['protocol'] = false; - } - - // Now we just have to construct the actual string. This is ugly. - $dsnString = $dsnArray['phptype']; - if ($dsnArray['dbsyntax']) { - $dsnString .= '(' . $dsnArray['dbsyntax'] . ')'; - } - $dsnString .= '://' - . $dsnArray['username'] - . ':' - . $dsnArray['password'] - . '@' - . $dsnArray['protocol']; - if ($dsnArray['socket']) { - $dsnString .= '(' . $dsnArray['socket'] . ')'; - } - if ($dsnArray['protocol'] && $dsnArray['hostspec']) { - $dsnString .= '+'; - } - $dsnString .= $dsnArray['hostspec']; - if ($dsnArray['port']) { - $dsnString .= ':' . $dsnArray['port']; - } - $dsnString .= '/' . $dsnArray['database']; - - /* Option handling. Unfortunately, parseDSN simply places options into - * the top-level array, so we'll first get rid of the fields defined by - * DB and see what's left. */ - unset($dsnArray['phptype'], - $dsnArray['dbsyntax'], - $dsnArray['username'], - $dsnArray['password'], - $dsnArray['protocol'], - $dsnArray['socket'], - $dsnArray['hostspec'], - $dsnArray['port'], - $dsnArray['database'] - ); - if (count($dsnArray) > 0) { - $dsnString .= '?'; - $i = 0; - foreach ($dsnArray as $key => $value) { - if (++$i > 1) { - $dsnString .= '&'; - } - $dsnString .= $key . '=' . $value; - } - } - - return $dsnString; - } - - // }}} - // {{{ isManip() - - /** - * Determines if a value is a DB_ object - * - * @param mixed $value the value to test - * - * @return bool whether $value is a DB_ object - */ - public static function isConnection($value) - { - return (is_object($value) && - is_subclass_of($value, 'db_common') && - method_exists($value, 'simpleQuery')); - } - - // }}} - // {{{ errorMessage() - - /** - * Tell whether a query is a data manipulation or data definition query - * - * Examples of data manipulation queries are INSERT, UPDATE and DELETE. - * Examples of data definition queries are CREATE, DROP, ALTER, GRANT, - * REVOKE. - * - * @param string $query the query - * - * @return boolean whether $query is a data manipulation query - */ - public static function isManip($query) - { - $manips = 'INSERT|UPDATE|DELETE|REPLACE|' - . 'CREATE|DROP|' - . 'LOAD DATA|SELECT .* INTO .* FROM|COPY|' - . 'ALTER|GRANT|REVOKE|' - . 'LOCK|UNLOCK'; - if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) { - return true; - } - return false; - } - - // }}} - // {{{ parseDSN() - - /** - * Return a textual error message for a DB error code - * - * @param integer $value the DB error code - * - * @return string the error message or false if the error code was - * not recognized - */ - public static function errorMessage($value) - { - static $errorMessages; - if (!isset($errorMessages)) { - $errorMessages = array( - DB_ERROR => 'unknown error', - DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', - DB_ERROR_ALREADY_EXISTS => 'already exists', - DB_ERROR_CANNOT_CREATE => 'can not create', - DB_ERROR_CANNOT_DROP => 'can not drop', - DB_ERROR_CONNECT_FAILED => 'connect failed', - DB_ERROR_CONSTRAINT => 'constraint violation', - DB_ERROR_CONSTRAINT_NOT_NULL => 'null value violates not-null constraint', - DB_ERROR_DIVZERO => 'division by zero', - DB_ERROR_EXTENSION_NOT_FOUND => 'extension not found', - DB_ERROR_INVALID => 'invalid', - DB_ERROR_INVALID_DATE => 'invalid date or time', - DB_ERROR_INVALID_DSN => 'invalid DSN', - DB_ERROR_INVALID_NUMBER => 'invalid number', - DB_ERROR_MISMATCH => 'mismatch', - DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', - DB_ERROR_NODBSELECTED => 'no database selected', - DB_ERROR_NOSUCHDB => 'no such database', - DB_ERROR_NOSUCHFIELD => 'no such field', - DB_ERROR_NOSUCHTABLE => 'no such table', - DB_ERROR_NOT_CAPABLE => 'DB backend not capable', - DB_ERROR_NOT_FOUND => 'not found', - DB_ERROR_NOT_LOCKED => 'not locked', - DB_ERROR_SYNTAX => 'syntax error', - DB_ERROR_UNSUPPORTED => 'not supported', - DB_ERROR_TRUNCATED => 'truncated', - DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', - DB_OK => 'no error', - ); - } - - if (DB::isError($value)) { - $value = $value->getCode(); - } - - return isset($errorMessages[$value]) ? $errorMessages[$value] - : $errorMessages[DB_ERROR]; - } - - // }}} - // {{{ getDSNString() - - /** - * Return the DB API version - * - * @return string the DB API version number - */ - function apiVersion() - { - return '1.9.2'; - } - - // }}} -} - -// }}} -// {{{ class DB_Error - -/** - * DB_Error implements a class for reporting portable database error - * messages - * - * @category Database - * @package DB - * @author Stig Bakken - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_Error extends PEAR_Error -{ - // {{{ constructor - - /** - * DB_Error constructor - * - * @param mixed $code DB error code, or string with error message - * @param int $mode what "error mode" to operate in - * @param int $level what error level to use for $mode & - * PEAR_ERROR_TRIGGER - * @param mixed $debuginfo additional debug info, such as the last query - * - * @see PEAR_Error - */ - function __construct($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, - $level = E_USER_NOTICE, $debuginfo = null) - { - if (is_int($code)) { - parent::__construct('DB Error: ' . DB::errorMessage($code), $code, - $mode, $level, $debuginfo); - } else { - parent::__construct("DB Error: $code", DB_ERROR, - $mode, $level, $debuginfo); - } - } - - /** - * Workaround to both avoid the "Redefining already defined constructor" - * PHP error and provide backward compatibility in case someone is calling - * DB_Error() dynamically - * @param $method - * @param $arguments - * @return bool|mixed - */ - public function __call($method, $arguments) - { - if ($method == 'DB_Error') { - return call_user_func_array(array($this, '__construct'), $arguments); - } - trigger_error( - 'Call to undefined method DB_Error::' . $method . '()', E_USER_ERROR - ); - return false; - } - // }}} -} - -// }}} -// {{{ class DB_result - -/** - * This class implements a wrapper for a DB result set - * - * A new instance of this class will be returned by the DB implementation - * after processing a query that returns data. - * - * @category Database - * @package DB - * @author Stig Bakken - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_result -{ - // {{{ properties - - /** - * Should results be freed automatically when there are no more rows? - * @var boolean - * @see DB_common::$options - */ - var $autofree; - - /** - * A reference to the DB_ object - * @var object - */ - var $dbh; - - /** - * The current default fetch mode - * @var integer - * @see DB_common::$fetchmode - */ - var $fetchmode; - - /** - * The name of the class into which results should be fetched when - * DB_FETCHMODE_OBJECT is in effect - * - * @var string - * @see DB_common::$fetchmode_object_class - */ - var $fetchmode_object_class; - - /** - * The number of rows to fetch from a limit query - * @var integer - */ - var $limit_count = null; - - /** - * The row to start fetching from in limit queries - * @var integer - */ - var $limit_from = null; - - /** - * The execute parameters that created this result - * @var array - * @since Property available since Release 1.7.0 - */ - var $parameters; - - /** - * The query string that created this result - * - * Copied here incase it changes in $dbh, which is referenced - * - * @var string - * @since Property available since Release 1.7.0 - */ - var $query; - - /** - * The query result resource id created by PHP - * @var resource - */ - var $result; - - /** - * The present row being dealt with - * @var integer - */ - var $row_counter = null; - - /** - * The prepared statement resource id created by PHP in $dbh - * - * This resource is only available when the result set was created using - * a driver's native execute() method, not PEAR DB's emulated one. - * - * Copied here incase it changes in $dbh, which is referenced - * - * {@internal Mainly here because the InterBase/Firebird API is only - * able to retrieve data from result sets if the statemnt handle is - * still in scope.}} - * - * @var resource - * @since Property available since Release 1.7.0 - */ - var $statement; - - - // }}} - // {{{ constructor - - /** - * This constructor sets the object's properties - * - * @param object &$dbh the DB object reference - * @param resource $result the result resource id - * @param array $options an associative array with result options - * - * @return void - */ - function __construct(&$dbh, $result, $options = array()) - { - $this->autofree = $dbh->options['autofree']; - $this->dbh = &$dbh; - $this->fetchmode = $dbh->fetchmode; - $this->fetchmode_object_class = $dbh->fetchmode_object_class; - $this->parameters = $dbh->last_parameters; - $this->query = $dbh->last_query; - $this->result = $result; - $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt; - foreach ($options as $key => $value) { - $this->setOption($key, $value); - } - } - - /** - * Set options for the DB_result object - * - * @param string $key the option to set - * @param mixed $value the value to set the option to - * - * @return void - */ - function setOption($key, $value = null) - { - switch ($key) { - case 'limit_from': - $this->limit_from = $value; - break; - case 'limit_count': - $this->limit_count = $value; - } - } - - // }}} - // {{{ fetchRow() - - /** - * Fetch a row of data and return it by reference into an array - * - * The type of array returned can be controlled either by setting this - * method's $fetchmode parameter or by changing the default - * fetch mode setFetchMode() before calling this method. - * - * There are two options for standardizing the information returned - * from databases, ensuring their values are consistent when changing - * DBMS's. These portability options can be turned on when creating a - * new DB object or by using setOption(). - * - * + DB_PORTABILITY_LOWERCASE - * convert names of fields to lower case - * - * + DB_PORTABILITY_RTRIM - * right trim the data - * - * @param int $fetchmode the constant indicating how to format the data - * @param int $rownum the row number to fetch (index starts at 0) - * - * @return mixed an array or object containing the row's data, - * NULL when the end of the result set is reached - * or a DB_Error object on failure. - * - * @see DB_common::setOption(), DB_common::setFetchMode() - */ - function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) - { - if ($fetchmode === DB_FETCHMODE_DEFAULT) { - $fetchmode = $this->fetchmode; - } - if ($fetchmode === DB_FETCHMODE_OBJECT) { - $fetchmode = DB_FETCHMODE_ASSOC; - $object_class = $this->fetchmode_object_class; - } - if (is_null($rownum) && $this->limit_from !== null) { - if ($this->row_counter === null) { - $this->row_counter = $this->limit_from; - // Skip rows - if ($this->dbh->features['limit'] === false) { - $i = 0; - while ($i++ < $this->limit_from) { - $this->dbh->fetchInto($this->result, $arr, $fetchmode); - } - } - } - if ($this->row_counter >= ($this->limit_from + $this->limit_count)) { - if ($this->autofree) { - $this->free(); - } - $tmp = null; - return $tmp; - } - if ($this->dbh->features['limit'] === 'emulate') { - $rownum = $this->row_counter; - } - $this->row_counter++; - } - $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); - if ($res === DB_OK) { - if (isset($object_class)) { - // The default mode is specified in the - // DB_common::fetchmode_object_class property - if ($object_class == 'stdClass') { - $arr = (object)$arr; - } else { - $arr = new $object_class($arr); - } - } - return $arr; - } - if ($res == null && $this->autofree) { - $this->free(); - } - return $res; - } - - // }}} - // {{{ fetchInto() - - /** - * Frees the resources allocated for this result set - * - * @return bool true on success. A DB_Error object on failure. - */ - function free() - { - $err = $this->dbh->freeResult($this->result); - if (DB::isError($err)) { - return $err; - } - $this->result = false; - $this->statement = false; - return true; - } - - // }}} - // {{{ numCols() - - /** - * Fetch a row of data into an array which is passed by reference - * - * The type of array returned can be controlled either by setting this - * method's $fetchmode parameter or by changing the default - * fetch mode setFetchMode() before calling this method. - * - * There are two options for standardizing the information returned - * from databases, ensuring their values are consistent when changing - * DBMS's. These portability options can be turned on when creating a - * new DB object or by using setOption(). - * - * + DB_PORTABILITY_LOWERCASE - * convert names of fields to lower case - * - * + DB_PORTABILITY_RTRIM - * right trim the data - * - * @param array &$arr the variable where the data should be placed - * @param int $fetchmode the constant indicating how to format the data - * @param int $rownum the row number to fetch (index starts at 0) - * - * @return mixed DB_OK if a row is processed, NULL when the end of the - * result set is reached or a DB_Error object on failure - * - * @see DB_common::setOption(), DB_common::setFetchMode() - */ - function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null) - { - if ($fetchmode === DB_FETCHMODE_DEFAULT) { - $fetchmode = $this->fetchmode; - } - if ($fetchmode === DB_FETCHMODE_OBJECT) { - $fetchmode = DB_FETCHMODE_ASSOC; - $object_class = $this->fetchmode_object_class; - } - if (is_null($rownum) && $this->limit_from !== null) { - if ($this->row_counter === null) { - $this->row_counter = $this->limit_from; - // Skip rows - if ($this->dbh->features['limit'] === false) { - $i = 0; - while ($i++ < $this->limit_from) { - $this->dbh->fetchInto($this->result, $arr, $fetchmode); - } - } - } - if ($this->row_counter >= ( - $this->limit_from + $this->limit_count)) { - if ($this->autofree) { - $this->free(); - } - return null; - } - if ($this->dbh->features['limit'] === 'emulate') { - $rownum = $this->row_counter; - } - - $this->row_counter++; - } - $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); - if ($res === DB_OK) { - if (isset($object_class)) { - // default mode specified in the - // DB_common::fetchmode_object_class property - if ($object_class == 'stdClass') { - $arr = (object)$arr; - } else { - $arr = new $object_class($arr); - } - } - return DB_OK; - } - if ($res == null && $this->autofree) { - $this->free(); - } - return $res; - } - - // }}} - // {{{ numRows() - - /** - * Get the the number of columns in a result set - * - * @return int the number of columns. A DB_Error object on failure. - */ - function numCols() - { - return $this->dbh->numCols($this->result); - } - - // }}} - // {{{ nextResult() - - /** - * Get the number of rows in a result set - * - * @return int the number of rows. A DB_Error object on failure. - */ - function numRows() - { - if ($this->dbh->features['numrows'] === 'emulate' - && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS) { - if ($this->dbh->features['prepare']) { - $res = $this->dbh->query($this->query, $this->parameters); - } else { - $res = $this->dbh->query($this->query); - } - if (DB::isError($res)) { - return $res; - } - $i = 0; - while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) { - $i++; - } - $count = $i; - } else { - $count = $this->dbh->numRows($this->result); - } - - /* fbsql is checked for here because limit queries are implemented - * using a TOP() function, which results in fbsql_num_rows still - * returning the total number of rows that would have been returned, - * rather than the real number. As a result, we'll just do the limit - * calculations for fbsql in the same way as a database with emulated - * limits. Unfortunately, we can't just do this in DB_fbsql::numRows() - * because that only gets the result resource, rather than the full - * DB_Result object. */ - if (($this->dbh->features['limit'] === 'emulate' - && $this->limit_from !== null) - || $this->dbh->phptype == 'fbsql') { - $limit_count = is_null($this->limit_count) ? $count : $this->limit_count; - if ($count < $this->limit_from) { - $count = 0; - } elseif ($count < ($this->limit_from + $limit_count)) { - $count -= $this->limit_from; - } else { - $count = $limit_count; - } - } - - return $count; - } - - // }}} - // {{{ free() - - /** - * Get the next result if a batch of queries was executed - * - * @return bool true if a new result is available or false if not - */ - function nextResult() - { - return $this->dbh->nextResult($this->result); - } - - // }}} - // {{{ tableInfo() - - /** - * @param null $mode - * @return - * @see DB_common::tableInfo() - * @deprecated Method deprecated some time before Release 1.2 - */ - function tableInfo($mode = null) - { - if (is_string($mode)) { - return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA); - } - return $this->dbh->tableInfo($this, $mode); - } - - // }}} - // {{{ getQuery() - - /** - * Determine the query string that created this result - * - * @return string the query string - * - * @since Method available since Release 1.7.0 - */ - function getQuery() - { - return $this->query; - } - - // }}} - // {{{ getRowCounter() - - /** - * Tells which row number is currently being processed - * - * @return integer the current row being looked at. Starts at 1. - */ - function getRowCounter() - { - return $this->row_counter; - } - - // }}} -} - -// }}} -// {{{ class DB_row - -/** - * PEAR DB Row Object - * - * The object contains a row of data from a result set. Each column's data - * is placed in a property named for the column. - * - * @category Database - * @package DB - * @author Stig Bakken - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - * @see DB_common::setFetchMode() - */ -class DB_row -{ - // {{{ constructor - - /** - * The constructor places a row's data into properties of this object - * - * @param array the array containing the row's data - * - * @return void - */ - function __construct(&$arr) - { - foreach ($arr as $key => $value) { - $this->$key = &$arr[$key]; - } - } - - // }}} -} - -// }}} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php index b81a0b867a..fe5edac8c7 100644 --- a/extlib/DB/DataObject/Generator.php +++ b/extlib/DB/DataObject/Generator.php @@ -701,7 +701,13 @@ class DB_DataObject_Generator extends DB_DataObject die($res->getMessage()); } - while ($row = $res->fetchRow(DB_FETCHMODE_ASSOC)) { + if ($db_driver == 'DB') { + $fetchmode = DB_FETCHMODE_ASSOC; + } else { + $fetchmode = MDB2_FETCHMODE_ASSOC; + } + + while ($row = $res->fetchRow($fetchmode)) { $treffer = array(); // this only picks up one of these.. see this for why: http://pear.php.net/bugs/bug.php?id=17049 preg_match( @@ -731,7 +737,11 @@ class DB_DataObject_Generator extends DB_DataObject die($res->getMessage()); } - $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); + if ($db_driver == 'DB') { + $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); + } else { + $text = $res->fetchRow(MDB2_FETCHMODE_DEFAULT, 0); + } $treffer = array(); // Extract FOREIGN KEYS preg_match_all( @@ -1558,7 +1568,11 @@ class DB_DataObject_Generator extends DB_DataObject $options = (new PEAR)->getStaticProperty('DB_DataObject', 'options'); $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; $method = $db_driver == 'DB' ? 'getAll' : 'queryAll'; - $res = $__DB->$method('DESCRIBE ' . $table, DB_FETCHMODE_ASSOC); + if ($db_driver == 'DB') { + $res = $__DB->$method('DESCRIBE ' . $table, DB_FETCHMODE_ASSOC); + } else { + $res = $__DB->$method('DESCRIBE ' . $table, MDB2_FETCHMODE_ASSOC); + } $defaults = array(); foreach ($res as $ar) { // this is initially very dumb... -> and it may mess up.. diff --git a/extlib/DB/common.php b/extlib/DB/common.php deleted file mode 100644 index e8750972d7..0000000000 --- a/extlib/DB/common.php +++ /dev/null @@ -1,2313 +0,0 @@ - - * @author Tomas V.V. Cox - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the PEAR class so it can be extended from - */ -require_once 'PEAR.php'; - -/** - * DB_common is the base class from which each database driver class extends - * - * All common methods are declared here. If a given DBMS driver contains - * a particular method, that method will overload the one here. - * - * @category Database - * @package DB - * @author Stig Bakken - * @author Tomas V.V. Cox - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_common extends PEAR -{ - // {{{ properties - - /** - * The current default fetch mode - * @var integer - */ - public $fetchmode = DB_FETCHMODE_ORDERED; - - /** - * The name of the class into which results should be fetched when - * DB_FETCHMODE_OBJECT is in effect - * - * @var string - */ - public $fetchmode_object_class = 'stdClass'; - - /** - * Was a connection present when the object was serialized()? - * @var bool - * @see DB_common::__sleep(), DB_common::__wake() - */ - public $was_connected = null; - - /** - * The most recently executed query - * @var string - */ - public $last_query = ''; - - /** - * Run-time configuration options - * - * The 'optimize' option has been deprecated. Use the 'portability' - * option instead. - * - * @var array - * @see DB_common::setOption() - */ - public $options = array( - 'result_buffering' => 500, - 'persistent' => false, - 'ssl' => false, - 'debug' => 0, - 'seqname_format' => '%s_seq', - 'autofree' => false, - 'portability' => DB_PORTABILITY_NONE, - 'optimize' => 'performance', // Deprecated. Use 'portability'. - ); - - /** - * The parameters from the most recently executed query - * @var array - * @since Property available since Release 1.7.0 - */ - public $last_parameters = array(); - - /** - * The elements from each prepared statement - * @var array - */ - public $prepare_tokens = array(); - - /** - * The data types of the various elements in each prepared statement - * @var array - */ - public $prepare_types = array(); - - /** - * The prepared queries - * @var array - */ - public $prepared_queries = array(); - - /** - * Flag indicating that the last query was a manipulation query. - * @access protected - * @var boolean - */ - public $_last_query_manip = false; - - /** - * Flag indicating that the next query must be a manipulation - * query. - * @access protected - * @var boolean - */ - public $_next_query_manip = false; - - - // }}} - // {{{ DB_common - - /** - * This constructor calls $this->PEAR('DB_Error') - * - * @return void - */ - public function __construct() - { - $this->PEAR('DB_Error'); - } - - // }}} - // {{{ __sleep() - - /** - * Automatically indicates which properties should be saved - * when PHP's serialize() function is called - * - * @return array the array of properties names that should be saved - */ - public function __sleep() - { - if ($this->connection) { - // Don't disconnect(), people use serialize() for many reasons - $this->was_connected = true; - } else { - $this->was_connected = false; - } - if (isset($this->autocommit)) { - return array('autocommit', - 'dbsyntax', - 'dsn', - 'features', - 'fetchmode', - 'fetchmode_object_class', - 'options', - 'was_connected', - ); - } else { - return array('dbsyntax', - 'dsn', - 'features', - 'fetchmode', - 'fetchmode_object_class', - 'options', - 'was_connected', - ); - } - } - - // }}} - // {{{ __wakeup() - - /** - * Automatically reconnects to the database when PHP's unserialize() - * function is called - * - * The reconnection attempt is only performed if the object was connected - * at the time PHP's serialize() function was run. - * - * @return void - */ - public function __wakeup() - { - if ($this->was_connected) { - $this->connect($this->dsn, $this->options['persistent']); - } - } - - // }}} - // {{{ __toString() - - /** - * DEPRECATED: String conversion method - * - * @return string a string describing the current PEAR DB object - * - * @deprecated Method deprecated in Release 1.7.0 - */ - public function toString() - { - return $this->__toString(); - } - - // }}} - // {{{ toString() - - /** - * Automatic string conversion for PHP 5 - * - * @return string a string describing the current PEAR DB object - * - * @since Method available since Release 1.7.0 - */ - public function __toString() - { - $info = strtolower(get_class($this)); - $info .= ': (phptype=' . $this->phptype . - ', dbsyntax=' . $this->dbsyntax . - ')'; - if ($this->connection) { - $info .= ' [connected]'; - } - return $info; - } - - // }}} - // {{{ quoteString() - - /** - * DEPRECATED: Quotes a string so it can be safely used within string - * delimiters in a query - * - * @param string $string the string to be quoted - * - * @return string the quoted string - * - * @see DB_common::quoteSmart(), DB_common::escapeSimple() - * @deprecated Method deprecated some time before Release 1.2 - */ - public function quoteString($string) - { - $string = $this->quoteSmart($string); - if ($string{0} == "'") { - return substr($string, 1, -1); - } - return $string; - } - - // }}} - // {{{ quote() - - /** - * Formats input so it can be safely used in a query - * - * The output depends on the PHP data type of input and the database - * type being used. - * - * @param mixed $in the data to be formatted - * - * @return mixed the formatted data. The format depends on the input's - * PHP type: - *
    - *
  • - * input -> returns - *
  • - *
  • - * null -> the string NULL - *
  • - *
  • - * integer or double -> the unquoted number - *
  • - *
  • - * bool -> output depends on the driver in use - * Most drivers return integers: 1 if - * true or 0 if - * false. - * Some return strings: TRUE if - * true or FALSE if - * false. - * Finally one returns strings: T if - * true or F if - * false. Here is a list of each DBMS, - * the values returned and the suggested column type: - *
      - *
    • - * dbase -> T/F - * (Logical) - *
    • - *
    • - * fbase -> TRUE/FALSE - * (BOOLEAN) - *
    • - *
    • - * ibase -> 1/0 - * (SMALLINT) [1] - *
    • - *
    • - * ifx -> 1/0 - * (SMALLINT) [1] - *
    • - *
    • - * msql -> 1/0 - * (INTEGER) - *
    • - *
    • - * mssql -> 1/0 - * (BIT) - *
    • - *
    • - * mysql -> 1/0 - * (TINYINT(1)) - *
    • - *
    • - * mysqli -> 1/0 - * (TINYINT(1)) - *
    • - *
    • - * oci8 -> 1/0 - * (NUMBER(1)) - *
    • - *
    • - * odbc -> 1/0 - * (SMALLINT) [1] - *
    • - *
    • - * pgsql -> TRUE/FALSE - * (BOOLEAN) - *
    • - *
    • - * sqlite -> 1/0 - * (INTEGER) - *
    • - *
    • - * sybase -> 1/0 - * (TINYINT(1)) - *
    • - *
    - * [1] Accommodate the lowest common denominator because not all - * versions of have BOOLEAN. - *
  • - *
  • - * other (including strings and numeric strings) -> - * the data with single quotes escaped by preceeding - * single quotes, backslashes are escaped by preceeding - * backslashes, then the whole string is encapsulated - * between single quotes - *
  • - *
- * - * @see DB_common::escapeSimple() - * @since Method available since Release 1.6.0 - */ - public function quoteSmart($in) - { - if (is_int($in)) { - return $in; - } elseif (is_float($in)) { - return $this->quoteFloat($in); - } elseif (is_bool($in)) { - return $this->quoteBoolean($in); - } elseif (is_null($in)) { - return 'NULL'; - } else { - if ($this->dbsyntax == 'access' - && preg_match('/^#.+#$/', $in)) { - return $this->escapeSimple($in); - } - return "'" . $this->escapeSimple($in) . "'"; - } - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Formats a float value for use within a query in a locale-independent - * manner. - * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteFloat($float) - { - return "'" . $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))) . "'"; - } - - // }}} - // {{{ quoteSmart() - - /** - * Escapes a string according to the current DBMS's standards - * - * In SQLite, this makes things safe for inserts/updates, but may - * cause problems when performing text comparisons against columns - * containing binary data. See the - * {@link http://php.net/sqlite_escape_string PHP manual} for more info. - * - * @param string $str the string to be escaped - * - * @return string the escaped string - * - * @see DB_common::quoteSmart() - * @since Method available since Release 1.6.0 - */ - public function escapeSimple($str) - { - return str_replace("'", "''", $str); - } - - // }}} - // {{{ quoteBoolean() - - /** - * Formats a boolean value for use within a query in a locale-independent - * manner. - * - * @param boolean the boolean value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteBoolean($boolean) - { - return $boolean ? '1' : '0'; - } - - // }}} - // {{{ quoteFloat() - - /** - * DEPRECATED: Quotes a string so it can be safely used in a query - * - * @param string $string the string to quote - * - * @return string the quoted string or the string NULL - * if the value submitted is null. - * - * @see DB_common::quoteSmart(), DB_common::escapeSimple() - * @deprecated Deprecated in release 1.6.0 - */ - public function quote($string = null) - { - return $this->quoteSmart($string); - } - - // }}} - // {{{ escapeSimple() - - /** - * Quotes a string so it can be safely used as a table or column name - * - * Delimiting style depends on which database driver is being used. - * - * NOTE: just because you CAN use delimited identifiers doesn't mean - * you SHOULD use them. In general, they end up causing way more - * problems than they solve. - * - * Portability is broken by using the following characters inside - * delimited identifiers: - * + backtick (`) -- due to MySQL - * + double quote (") -- due to Oracle - * + brackets ([ or ]) -- due to Access - * - * Delimited identifiers are known to generally work correctly under - * the following drivers: - * + mssql - * + mysql - * + mysqli - * + oci8 - * + odbc(access) - * + odbc(db2) - * + pgsql - * + sqlite - * + sybase (must execute set quoted_identifier on sometime - * prior to use) - * - * InterBase doesn't seem to be able to use delimited identifiers - * via PHP 4. They work fine under PHP 5. - * - * @param string $str the identifier name to be quoted - * - * @return string the quoted identifier - * - * @since Method available since Release 1.6.0 - */ - public function quoteIdentifier($str) - { - return '"' . str_replace('"', '""', $str) . '"'; - } - - // }}} - // {{{ provides() - - /** - * Tells whether the present driver supports a given feature - * - * @param string $feature the feature you're curious about - * - * @return bool whether this driver supports $feature - */ - public function provides($feature) - { - return $this->features[$feature]; - } - - // }}} - // {{{ setFetchMode() - - /** - * Sets the fetch mode that should be used by default for query results - * - * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC - * or DB_FETCHMODE_OBJECT - * @param string $object_class the class name of the object to be returned - * by the fetch methods when the - * DB_FETCHMODE_OBJECT mode is selected. - * If no class is specified by default a cast - * to object from the assoc array row will be - * done. There is also the posibility to use - * and extend the 'DB_row' class. - * - * @return object - * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT - */ - public function setFetchMode($fetchmode, $object_class = 'stdClass') - { - switch ($fetchmode) { - case DB_FETCHMODE_OBJECT: - $this->fetchmode_object_class = $object_class; - // no break - case DB_FETCHMODE_ORDERED: - case DB_FETCHMODE_ASSOC: - $this->fetchmode = $fetchmode; - break; - default: - return $this->raiseError('invalid fetchmode mode'); - } - return null; - } - - // }}} - // {{{ setOption() - - /** - * Communicates an error and invoke error callbacks, etc - * - * Basically a wrapper for PEAR::raiseError without the message string. - * - * @param mixed integer error code, or a PEAR error object (all - * other parameters are ignored if this parameter is - * an object - * @param int error mode, see PEAR_Error docs - * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the - * error level (E_USER_NOTICE etc). If error mode is - * PEAR_ERROR_CALLBACK, this is the callback function, - * either as a function name, or as an array of an - * object and method name. For other error modes this - * parameter is ignored. - * @param string extra debug information. Defaults to the last - * query and native error code. - * @param mixed native error code, integer or string depending the - * backend - * @param mixed dummy parameter for E_STRICT compatibility with - * PEAR::raiseError - * @param mixed dummy parameter for E_STRICT compatibility with - * PEAR::raiseError - * - * @return object the PEAR_Error object - * - * @see PEAR_Error - */ - public function &raiseError( - $code = DB_ERROR, - $mode = null, - $options = null, - $userinfo = null, - $nativecode = null, - $dummy1 = null, - $dummy2 = null - ) - { - // The error is yet a DB error object - if (is_object($code)) { - // because we the static PEAR::raiseError, our global - // handler should be used if it is set - if ($mode === null && !empty($this->_default_error_mode)) { - $mode = $this->_default_error_mode; - $options = $this->_default_error_options; - } - $tmp = PEAR::raiseError( - $code, - null, - $mode, - $options, - null, - null, - true - ); - return $tmp; - } - - if ($userinfo === null) { - $userinfo = $this->last_query; - } - - if ($nativecode) { - $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; - } else { - $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; - } - - $tmp = PEAR::raiseError( - null, - $code, - $mode, - $options, - $userinfo, - 'DB_Error', - true - ); - return $tmp; - } - - // }}} - // {{{ getOption() - - /** - * Sets run-time configuration options for PEAR DB - * - * Options, their data types, default values and description: - *
    - *
  • - * autofree boolean = false - *
    should results be freed automatically when there are no - * more rows? - *
  • - * result_buffering integer = 500 - *
    how many rows of the result set should be buffered? - *
    In mysql: mysql_unbuffered_query() is used instead of - * mysql_query() if this value is 0. (Release 1.7.0) - *
    In oci8: this value is passed to ocisetprefetch(). - * (Release 1.7.0) - *
  • - * debug integer = 0 - *
    debug level - *
  • - * persistent boolean = false - *
    should the connection be persistent? - *
  • - * portability integer = DB_PORTABILITY_NONE - *
    portability mode constant (see below) - *
  • - * seqname_format string = %s_seq - *
    the sprintf() format string used on sequence names. This - * format is applied to sequence names passed to - * createSequence(), nextID() and dropSequence(). - *
  • - * ssl boolean = false - *
    use ssl to connect? - *
  • - *
- * - * ----------------------------------------- - * - * PORTABILITY MODES - * - * These modes are bitwised, so they can be combined using | - * and removed using ^. See the examples section below on how - * to do this. - * - * DB_PORTABILITY_NONE - * turn off all portability features - * - * This mode gets automatically turned on if the deprecated - * optimize option gets set to performance. - * - * - * DB_PORTABILITY_LOWERCASE - * convert names of tables and fields to lower case when using - * get*(), fetch*() and tableInfo() - * - * This mode gets automatically turned on in the following databases - * if the deprecated option optimize gets set to - * portability: - * + oci8 - * - * - * DB_PORTABILITY_RTRIM - * right trim the data output by get*() fetch*() - * - * - * DB_PORTABILITY_DELETE_COUNT - * force reporting the number of rows deleted - * - * Some DBMS's don't count the number of rows deleted when performing - * simple DELETE FROM tablename queries. This portability - * mode tricks such DBMS's into telling the count by adding - * WHERE 1=1 to the end of DELETE queries. - * - * This mode gets automatically turned on in the following databases - * if the deprecated option optimize gets set to - * portability: - * + fbsql - * + mysql - * + mysqli - * + sqlite - * - * - * DB_PORTABILITY_NUMROWS - * enable hack that makes numRows() work in Oracle - * - * This mode gets automatically turned on in the following databases - * if the deprecated option optimize gets set to - * portability: - * + oci8 - * - * - * DB_PORTABILITY_ERRORS - * makes certain error messages in certain drivers compatible - * with those from other DBMS's - * - * + mysql, mysqli: change unique/primary key constraints - * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT - * - * + odbc(access): MS's ODBC driver reports 'no such field' as code - * 07001, which means 'too few parameters.' When this option is on - * that code gets mapped to DB_ERROR_NOSUCHFIELD. - * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD - * - * DB_PORTABILITY_NULL_TO_EMPTY - * convert null values to empty strings in data output by get*() and - * fetch*(). Needed because Oracle considers empty strings to be null, - * while most other DBMS's know the difference between empty and null. - * - * - * DB_PORTABILITY_ALL - * turn on all portability features - * - * ----------------------------------------- - * - * Example 1. Simple setOption() example - * - * $db->setOption('autofree', true); - * - * - * Example 2. Portability for lowercasing and trimming - * - * $db->setOption('portability', - * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); - * - * - * Example 3. All portability options except trimming - * - * $db->setOption('portability', - * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); - * - * - * @param string $option option name - * @param mixed $value value for the option - * - * @return int|object - * - * @see DB_common::$options - */ - public function setOption($option, $value) - { - if (isset($this->options[$option])) { - $this->options[$option] = $value; - - /* - * Backwards compatibility check for the deprecated 'optimize' - * option. Done here in case settings change after connecting. - */ - if ($option == 'optimize') { - if ($value == 'portability') { - switch ($this->phptype) { - case 'oci8': - $this->options['portability'] = - DB_PORTABILITY_LOWERCASE | - DB_PORTABILITY_NUMROWS; - break; - case 'fbsql': - case 'mysql': - case 'mysqli': - case 'sqlite': - $this->options['portability'] = - DB_PORTABILITY_DELETE_COUNT; - break; - } - } else { - $this->options['portability'] = DB_PORTABILITY_NONE; - } - } - - return DB_OK; - } - return $this->raiseError("unknown option $option"); - } - - // }}} - // {{{ prepare() - - /** - * Automaticaly generates an insert or update query and call prepare() - * and execute() with it - * - * @param string $table the table name - * @param array $fields_values the associative array where $key is a - * field name and $value its value - * @param int $mode a type of query to make: - * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - * @param bool $where for update queries: the WHERE clause to - * append to the SQL statement. Don't - * include the "WHERE" keyword. - * - * @return mixed a new DB_result object for successful SELECT queries - * or DB_OK for successul data manipulation queries. - * A DB_Error object on failure. - * - * @uses DB_common::autoPrepare(), DB_common::execute() - */ - public function autoExecute( - $table, - $fields_values, - $mode = DB_AUTOQUERY_INSERT, - $where = false - ) - { - $sth = $this->autoPrepare( - $table, - array_keys($fields_values), - $mode, - $where - ); - if (DB::isError($sth)) { - return $sth; - } - $ret = $this->execute($sth, array_values($fields_values)); - $this->freePrepared($sth); - return $ret; - } - - // }}} - // {{{ autoPrepare() - - /** - * Automaticaly generates an insert or update query and pass it to prepare() - * - * @param string $table the table name - * @param array $table_fields the array of field names - * @param int $mode a type of query to make: - * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - * @param bool $where for update queries: the WHERE clause to - * append to the SQL statement. Don't - * include the "WHERE" keyword. - * - * @return resource|string - * - * @uses DB_common::prepare(), DB_common::buildManipSQL() - */ - public function autoPrepare( - $table, - $table_fields, - $mode = DB_AUTOQUERY_INSERT, - $where = false - ) - { - $query = $this->buildManipSQL($table, $table_fields, $mode, $where); - if (DB::isError($query)) { - return $query; - } - return $this->prepare($query); - } - - // }}} - // {{{ autoExecute() - - /** - * Produces an SQL query string for autoPrepare() - * - * Example: - *
-     * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
-     *               DB_AUTOQUERY_INSERT);
-     * 
- * - * That returns - * - * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) - * - * - * NOTES: - * - This belongs more to a SQL Builder class, but this is a simple - * facility. - * - Be carefull! If you don't give a $where param with an UPDATE - * query, all the records of the table will be updated! - * - * @param string $table the table name - * @param array $table_fields the array of field names - * @param int $mode a type of query to make: - * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE - * @param bool $where for update queries: the WHERE clause to - * append to the SQL statement. Don't - * include the "WHERE" keyword. - * - * @return string the sql query for autoPrepare() - */ - public function buildManipSQL($table, $table_fields, $mode, $where = false) - { - if (count($table_fields) == 0) { - return $this->raiseError(DB_ERROR_NEED_MORE_DATA); - } - $first = true; - switch ($mode) { - case DB_AUTOQUERY_INSERT: - $values = ''; - $names = ''; - foreach ($table_fields as $value) { - if ($first) { - $first = false; - } else { - $names .= ','; - $values .= ','; - } - $names .= $value; - $values .= '?'; - } - return "INSERT INTO $table ($names) VALUES ($values)"; - case DB_AUTOQUERY_UPDATE: - $set = ''; - foreach ($table_fields as $value) { - if ($first) { - $first = false; - } else { - $set .= ','; - } - $set .= "$value = ?"; - } - $sql = "UPDATE $table SET $set"; - if ($where) { - $sql .= " WHERE $where"; - } - return $sql; - default: - return $this->raiseError(DB_ERROR_SYNTAX); - } - } - - // }}} - // {{{ buildManipSQL() - - /** - * Prepares a query for multiple execution with execute() - * - * Creates a query that can be run multiple times. Each time it is run, - * the placeholders, if any, will be replaced by the contents of - * execute()'s $data argument. - * - * Three types of placeholders can be used: - * + ? scalar value (i.e. strings, integers). The system - * will automatically quote and escape the data. - * + ! value is inserted 'as is' - * + & requires a file name. The file's contents get - * inserted into the query (i.e. saving binary - * data in a db) - * - * Example 1. - * - * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); - * $data = array( - * "John's text", - * "'it''s good'", - * 'filename.txt' - * ); - * $res = $db->execute($sth, $data); - * - * - * Use backslashes to escape placeholder characters if you don't want - * them to be interpreted as placeholders: - *
-     *    "UPDATE foo SET col=? WHERE col='over \& under'"
-     * 
- * - * With some database backends, this is emulated. - * - * {@internal ibase and oci8 have their own prepare() methods.}} - * - * @param string $query the query to be prepared - * - * @return mixed DB statement resource on success. A DB_Error object - * on failure. - * - * @see DB_common::execute() - */ - public function prepare($query) - { - $tokens = preg_split( - '/((?prepare_tokens[] = &$newtokens; - end($this->prepare_tokens); - - $k = key($this->prepare_tokens); - $this->prepare_types[$k] = $types; - $this->prepared_queries[$k] = implode(' ', $newtokens); - - return $k; - } - - // }}} - // {{{ execute() - - /** - * Executes a DB statement prepared with prepare() - * - * Example 1. - * - * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); - * $data = array( - * "John's text", - * "'it''s good'", - * 'filename.txt' - * ); - * $res = $db->execute($sth, $data); - * - * - * @param resource $stmt a DB statement resource returned from prepare() - * @param mixed $data array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return mixed a new DB_result object for successful SELECT queries - * or DB_OK for successul data manipulation queries. - * A DB_Error object on failure. - * - * {@internal ibase and oci8 have their own execute() methods.}} - * - * @see DB_common::prepare() - */ - public function &execute($stmt, $data = array()) - { - $realquery = $this->executeEmulateQuery($stmt, $data); - if (DB::isError($realquery)) { - return $realquery; - } - $result = $this->simpleQuery($realquery); - - if ($result === DB_OK || DB::isError($result)) { - return $result; - } else { - $tmp = new DB_result($this, $result); - return $tmp; - } - } - - // }}} - // {{{ executeEmulateQuery() - - /** - * Emulates executing prepared statements if the DBMS not support them - * - * @param resource $stmt a DB statement resource returned from execute() - * @param mixed $data array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return mixed a string containing the real query run when emulating - * prepare/execute. A DB_Error object on failure. - * - * @access protected - * @see DB_common::execute() - */ - public function executeEmulateQuery($stmt, $data = array()) - { - $stmt = (int)$stmt; - $data = (array)$data; - $this->last_parameters = $data; - - if (count($this->prepare_types[$stmt]) != count($data)) { - $this->last_query = $this->prepared_queries[$stmt]; - return $this->raiseError(DB_ERROR_MISMATCH); - } - - $realquery = $this->prepare_tokens[$stmt][0]; - - $i = 0; - foreach ($data as $value) { - if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) { - $realquery .= $this->quoteSmart($value); - } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) { - $fp = @fopen($value, 'rb'); - if (!$fp) { - return $this->raiseError(DB_ERROR_ACCESS_VIOLATION); - } - $realquery .= $this->quoteSmart(fread($fp, filesize($value))); - fclose($fp); - } else { - $realquery .= $value; - } - - $realquery .= $this->prepare_tokens[$stmt][++$i]; - } - - return $realquery; - } - - // }}} - // {{{ executeMultiple() - - /** - * Frees the internal resources associated with a prepared query - * - * @param resource $stmt the prepared statement's PHP resource - * @param bool $free_resource should the PHP resource be freed too? - * Use false if you need to get data - * from the result set later. - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_common::prepare() - */ - public function freePrepared($stmt, $free_resource = true) - { - $stmt = (int)$stmt; - if (isset($this->prepare_tokens[$stmt])) { - unset($this->prepare_tokens[$stmt]); - unset($this->prepare_types[$stmt]); - unset($this->prepared_queries[$stmt]); - return true; - } - return false; - } - - // }}} - // {{{ freePrepared() - - /** - * Performs several execute() calls on the same statement handle - * - * $data must be an array indexed numerically - * from 0, one execute call is done for every "row" in the array. - * - * If an error occurs during execute(), executeMultiple() does not - * execute the unfinished rows, but rather returns that error. - * - * @param resource $stmt query handle from prepare() - * @param array $data numeric array containing the - * data to insert into the query - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::prepare(), DB_common::execute() - */ - public function executeMultiple($stmt, $data) - { - foreach ($data as $value) { - $res = $this->execute($stmt, $value); - if (DB::isError($res)) { - return $res; - } - } - return DB_OK; - } - - // }}} - // {{{ modifyQuery() - - /** - * Changes a query string for various DBMS specific reasons - * - * It is defined here to ensure all drivers have this method available. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(), - * DB_sqlite::modifyQuery() - */ - public function modifyQuery($query) - { - return $query; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Generates and executes a LIMIT query - * - * @param string $query the query - * @param intr $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return mixed a new DB_result object for successful SELECT queries - * or DB_OK for successul data manipulation queries. - * A DB_Error object on failure. - */ - public function &limitQuery($query, $from, $count, $params = array()) - { - $query = $this->modifyLimitQuery($query, $from, $count, $params); - if (DB::isError($query)) { - return $query; - } - $result = $this->query($query, $params); - if (is_object($result) && is_a($result, 'DB_result')) { - $result->setOption('limit_from', $from); - $result->setOption('limit_count', $count); - } - return $result; - } - - // }}} - // {{{ query() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * It is defined here to assure that all implementations - * have this method defined. - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - return $query; - } - - // }}} - // {{{ limitQuery() - - /** - * Sends a query to the database server - * - * The query string can be either a normal statement to be sent directly - * to the server OR if $params are passed the query can have - * placeholders and it will be passed through prepare() and execute(). - * - * @param string $query the SQL query or the statement to prepare - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return mixed a new DB_result object for successful SELECT queries - * or DB_OK for successul data manipulation queries. - * A DB_Error object on failure. - * - * @see DB_result, DB_common::prepare(), DB_common::execute() - */ - public function &query($query, $params = array()) - { - $params = (array)$params; - if (count($params)) { - $sth = $this->prepare($query); - if (DB::isError($sth)) { - return $sth; - } - $ret = $this->execute($sth, $params); - $this->freePrepared($sth, false); - return $ret; - } else { - $this->last_parameters = array(); - $result = $this->simpleQuery($query); - if ($result === DB_OK || DB::isError($result)) { - return $result; - } else { - $tmp = new DB_result($this, $result); - return $tmp; - } - } - } - - // }}} - // {{{ getOne() - - /** - * Fetches the first column of the first row from a query result - * - * Takes care of doing the query and freeing the results when finished. - * - * @param string $query the SQL query - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return mixed the returned value of the query. - * A DB_Error object on failure. - */ - public function &getOne($query, $params = array()) - { - $params = (array)$params; - // modifyLimitQuery() would be nice here, but it causes BC issues - $params = (array)$params; - if (count($params)) { - $sth = $this->prepare($query); - if (DB::isError($sth)) { - return $sth; - } - $res = $this->execute($sth, $params); - $this->freePrepared($sth); - } else { - $res = $this->query($query); - } - - if (DB::isError($res)) { - return $res; - } - - $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); - $res->free(); - - if ($err !== DB_OK) { - return $err; - } - - return $row[0]; - } - - // }}} - // {{{ getRow() - - /** - * Fetches the first row of data returned from a query result - * - * Takes care of doing the query and freeing the results when finished. - * - * @param string $query the SQL query - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * @param int $fetchmode the fetch mode to use - * - * @return array the first row of results as an array. - * A DB_Error object on failure. - */ - public function &getRow( - $query, - $params = array(), - $fetchmode = DB_FETCHMODE_DEFAULT - ) - { - // compat check, the params and fetchmode parameters used to - // have the opposite order - if (!is_array($params)) { - if (is_array($fetchmode)) { - if ($params === null) { - $tmp = DB_FETCHMODE_DEFAULT; - } else { - $tmp = $params; - } - $params = $fetchmode; - $fetchmode = $tmp; - } elseif ($params !== null) { - $fetchmode = $params; - $params = array(); - } - } - // modifyLimitQuery() would be nice here, but it causes BC issues - if (sizeof($params) > 0) { - $sth = $this->prepare($query); - if (DB::isError($sth)) { - return $sth; - } - $res = $this->execute($sth, $params); - $this->freePrepared($sth); - } else { - $res = $this->query($query); - } - - if (DB::isError($res)) { - return $res; - } - - $err = $res->fetchInto($row, $fetchmode); - - $res->free(); - - if ($err !== DB_OK) { - return $err; - } - - return $row; - } - - // }}} - // {{{ getCol() - - /** - * Fetches an entire query result and returns it as an - * associative array using the first column as the key - * - * If the result set contains more than two columns, the value - * will be an array of the values from column 2-n. If the result - * set contains only two columns, the returned value will be a - * scalar with the value of the second column (unless forced to an - * array with the $force_array parameter). A DB error code is - * returned on errors. If the result set contains fewer than two - * columns, a DB_ERROR_TRUNCATED error is returned. - * - * For example, if the table "mytable" contains: - * - *
-     *  ID      TEXT       DATE
-     * --------------------------------
-     *  1       'one'      944679408
-     *  2       'two'      944679408
-     *  3       'three'    944679408
-     * 
- * - * Then the call getAssoc('SELECT id,text FROM mytable') returns: - *
-     *   array(
-     *     '1' => 'one',
-     *     '2' => 'two',
-     *     '3' => 'three',
-     *   )
-     * 
- * - * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: - *
-     *   array(
-     *     '1' => array('one', '944679408'),
-     *     '2' => array('two', '944679408'),
-     *     '3' => array('three', '944679408')
-     *   )
-     * 
- * - * If the more than one row occurs with the same value in the - * first column, the last row overwrites all previous ones by - * default. Use the $group parameter if you don't want to - * overwrite like this. Example: - * - *
-     * getAssoc('SELECT category,id,name FROM mytable', false, null,
-     *          DB_FETCHMODE_ASSOC, true) returns:
-     *
-     *   array(
-     *     '1' => array(array('id' => '4', 'name' => 'number four'),
-     *                  array('id' => '6', 'name' => 'number six')
-     *            ),
-     *     '9' => array(array('id' => '4', 'name' => 'number four'),
-     *                  array('id' => '6', 'name' => 'number six')
-     *            )
-     *   )
-     * 
- * - * Keep in mind that database functions in PHP usually return string - * values for results regardless of the database's internal type. - * - * @param string $query the SQL query - * @param bool $force_array used only when the query returns - * exactly two columns. If true, the values - * of the returned array will be one-element - * arrays instead of scalars. - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of - * items passed must match quantity of - * placeholders in query: meaning 1 - * placeholder for non-array parameters or - * 1 placeholder per array element. - * @param int $fetchmode the fetch mode to use - * @param bool $group if true, the values of the returned array - * is wrapped in another array. If the same - * key value (in the first column) repeats - * itself, the values will be appended to - * this array instead of overwriting the - * existing values. - * - * @return array|object - * A DB_Error object on failure. - */ - public function &getAssoc( - $query, - $force_array = false, - $params = array(), - $fetchmode = DB_FETCHMODE_DEFAULT, - $group = false - ) - { - $params = (array)$params; - if (sizeof($params) > 0) { - $sth = $this->prepare($query); - - if (DB::isError($sth)) { - return $sth; - } - - $res = $this->execute($sth, $params); - $this->freePrepared($sth); - } else { - $res = $this->query($query); - } - - if (DB::isError($res)) { - return $res; - } - if ($fetchmode == DB_FETCHMODE_DEFAULT) { - $fetchmode = $this->fetchmode; - } - $cols = $res->numCols(); - - if ($cols < 2) { - $tmp = $this->raiseError(DB_ERROR_TRUNCATED); - return $tmp; - } - - $results = array(); - - if ($cols > 2 || $force_array) { - // return array values - // XXX this part can be optimized - if ($fetchmode == DB_FETCHMODE_ASSOC) { - while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { - reset($row); - $key = current($row); - unset($row[key($row)]); - if ($group) { - $results[$key][] = $row; - } else { - $results[$key] = $row; - } - } - } elseif ($fetchmode == DB_FETCHMODE_OBJECT) { - while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) { - $arr = get_object_vars($row); - $key = current($arr); - if ($group) { - $results[$key][] = $row; - } else { - $results[$key] = $row; - } - } - } else { - while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { - // we shift away the first element to get - // indices running from 0 again - $key = array_shift($row); - if ($group) { - $results[$key][] = $row; - } else { - $results[$key] = $row; - } - } - } - if (DB::isError($row)) { - $results = $row; - } - } else { - // return scalar values - while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { - if ($group) { - $results[$row[0]][] = $row[1]; - } else { - $results[$row[0]] = $row[1]; - } - } - if (DB::isError($row)) { - $results = $row; - } - } - - $res->free(); - - return $results; - } - - // }}} - // {{{ getAssoc() - - /** - * Fetches all of the rows from a query result - * - * @param string $query the SQL query - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of - * items passed must match quantity of - * placeholders in query: meaning 1 - * placeholder for non-array parameters or - * 1 placeholder per array element. - * @param int $fetchmode the fetch mode to use: - * + DB_FETCHMODE_ORDERED - * + DB_FETCHMODE_ASSOC - * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED - * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED - * - * @return array|object - */ - public function &getAll( - $query, - $params = array(), - $fetchmode = DB_FETCHMODE_DEFAULT - ) - { - // compat check, the params and fetchmode parameters used to - // have the opposite order - if (!is_array($params)) { - if (is_array($fetchmode)) { - if ($params === null) { - $tmp = DB_FETCHMODE_DEFAULT; - } else { - $tmp = $params; - } - $params = $fetchmode; - $fetchmode = $tmp; - } elseif ($params !== null) { - $fetchmode = $params; - $params = array(); - } - } - - $params = (array)$params; - if (count($params)) { - $sth = $this->prepare($query); - - if (DB::isError($sth)) { - return $sth; - } - - $res = $this->execute($sth, $params); - $this->freePrepared($sth); - } else { - $res = $this->query($query); - } - - if ($res === DB_OK || DB::isError($res)) { - return $res; - } - - $results = array(); - while (DB_OK === $res->fetchInto($row, $fetchmode)) { - if ($fetchmode & DB_FETCHMODE_FLIPPED) { - foreach ($row as $key => $val) { - $results[$key][] = $val; - } - } else { - $results[] = $row; - } - } - - $res->free(); - - if (DB::isError($row)) { - $tmp = $this->raiseError($row); - return $tmp; - } - return $results; - } - - // }}} - // {{{ getAll() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int|object - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ autoCommit() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ commit() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ rollback() - - /** - * Determines the number of rows in a query result - * - * @param resource $result the query result idenifier produced by PHP - * - * @return int|object - */ - public function numRows($result) - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ numRows() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int|object - */ - public function affectedRows() - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ affectedRows() - - /** - * Generates the name used inside the database for a sequence - * - * The createSequence() docblock contains notes about storing sequence - * names. - * - * @param string $sqn the sequence's public name - * - * @return string the sequence's name in the backend - * - * @access protected - * @see DB_common::createSequence(), DB_common::dropSequence(), - * DB_common::nextID(), DB_common::setOption() - */ - public function getSequenceName($sqn) - { - return sprintf( - $this->getOption('seqname_format'), - preg_replace('/[^a-z0-9_.]/i', '_', $sqn) - ); - } - - // }}} - // {{{ getSequenceName() - - /** - * Returns the value of an option - * - * @param string $option the option name you're curious about - * - * @return mixed the option's value - */ - public function getOption($option) - { - if (isset($this->options[$option])) { - return $this->options[$option]; - } - return $this->raiseError("unknown option $option"); - } - - // }}} - // {{{ nextId() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::dropSequence(), - * DB_common::getSequenceName() - */ - public function nextId($seq_name, $ondemand = true) - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ createSequence() - - /** - * Creates a new sequence - * - * The name of a given sequence is determined by passing the string - * provided in the $seq_name argument through PHP's sprintf() - * function using the value from the seqname_format option as - * the sprintf()'s format argument. - * - * seqname_format is set via setOption(). - * - * @param string $seq_name name of the new sequence - * - * @return int|object - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_common::nextID() - */ - public function createSequence($seq_name) - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ dropSequence() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int|object - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_common::nextID() - */ - public function dropSequence($seq_name) - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ raiseError() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return mixed the DBMS' error code. A DB_Error object on failure. - */ - public function errorNative() - { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ errorNative() - - /** - * Maps native error codes to DB's portable ones - * - * Uses the $errorcode_map property defined in each driver. - * - * @param string|int $nativecode the error code returned by the DBMS - * - * @return int the portable DB error code. Return DB_ERROR if the - * current driver doesn't have a mapping for the - * $nativecode submitted. - */ - public function errorCode($nativecode) - { - if (isset($this->errorcode_map[$nativecode])) { - return $this->errorcode_map[$nativecode]; - } - // Fall back to DB_ERROR if there was no mapping. - return DB_ERROR; - } - - // }}} - // {{{ errorCode() - - /** - * Maps a DB error code to a textual message - * - * @param integer $dbcode the DB error code - * - * @return string the error message corresponding to the error code - * submitted. FALSE if the error code is unknown. - * - * @see DB::errorMessage() - */ - public function errorMessage($dbcode) - { - return DB::errorMessage($this->errorcode_map[$dbcode]); - } - - // }}} - // {{{ errorMessage() - - /** - * Returns information about a table or a result set - * - * The format of the resulting array depends on which $mode - * you select. The sample output below is based on this query: - *
-     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
-     *    FROM tblFoo
-     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
-     * 
- * - *
    - *
  • - * - * null (default) - *
    -     *   [0] => Array (
    -     *       [table] => tblFoo
    -     *       [name] => fldId
    -     *       [type] => int
    -     *       [len] => 11
    -     *       [flags] => primary_key not_null
    -     *   )
    -     *   [1] => Array (
    -     *       [table] => tblFoo
    -     *       [name] => fldPhone
    -     *       [type] => string
    -     *       [len] => 20
    -     *       [flags] =>
    -     *   )
    -     *   [2] => Array (
    -     *       [table] => tblBar
    -     *       [name] => fldId
    -     *       [type] => int
    -     *       [len] => 11
    -     *       [flags] => primary_key not_null
    -     *   )
    -     *   
    - * - *
  • - * - * DB_TABLEINFO_ORDER - * - *

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

    - * - *

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

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

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

    - * - *
    -     *   [num_fields] => 3
    -     *   [ordertable] => Array (
    -     *       [tblFoo] => Array (
    -     *           [fldId] => 0
    -     *           [fldPhone] => 1
    -     *       )
    -     *       [tblBar] => Array (
    -     *           [fldId] => 2
    -     *       )
    -     *   )
    -     *   
    - * - *
  • - *
- * - * The flags element contains a space separated list - * of extra information about the field. This data is inconsistent - * between DBMS's due to the way each DBMS works. - * + primary_key - * + unique_key - * + multiple_key - * + not_null - * - * Most DBMS's only provide the table and flags - * elements if $result is a table name. The following DBMS's - * provide full information from queries: - * + fbsql - * + mysql - * - * If the 'portability' option has DB_PORTABILITY_LOWERCASE - * turned on, the names of tables and fields will be lowercased. - * - * @param object|string $result DB_result object from a query or a - * string containing the name of a table. - * While this also accepts a query result - * resource identifier, this behavior is - * deprecated. - * @param int $mode either unused or one of the tableInfo modes: - * DB_TABLEINFO_ORDERTABLE, - * DB_TABLEINFO_ORDER or - * DB_TABLEINFO_FULL (which does both). - * These are bitwise, so the first two can be - * combined using |. - * - * @return array|object - * A DB_Error object on failure. - * - * @see DB_common::setOption() - */ - public function tableInfo($result, $mode = null) - { - /* - * If the DB_ class has a tableInfo() method, that one - * overrides this one. But, if the driver doesn't have one, - * this method runs and tells users about that fact. - */ - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ tableInfo() - - /** - * Lists the tables in the current database - * - * @return array the list of tables. A DB_Error object on failure. - * - * @deprecated Method deprecated some time before Release 1.2 - */ - public function getTables() - { - return $this->getListOf('tables'); - } - - // }}} - // {{{ getTables() - - /** - * Lists internal database information - * - * @param string $type type of information being sought. - * Common items being sought are: - * tables, databases, users, views, functions - * Each DBMS's has its own capabilities. - * - * @return array|object - * A DB DB_Error object on failure. - */ - public function getListOf($type) - { - $sql = $this->getSpecialQuery($type); - if ($sql === null) { - $this->last_query = ''; - return $this->raiseError(DB_ERROR_UNSUPPORTED); - } elseif (is_int($sql) || DB::isError($sql)) { - // Previous error - return $this->raiseError($sql); - } elseif (is_array($sql)) { - // Already the result - return $sql; - } - // Launch this query - return $this->getCol($sql); - } - - // }}} - // {{{ getListOf() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - return $this->raiseError(DB_ERROR_UNSUPPORTED); - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Fetches a single column from a query result and returns it as an - * indexed array - * - * @param string $query the SQL query - * @param mixed $col which column to return (integer [column number, - * starting at 0] or string [column name]) - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return array the results as an array. A DB_Error object on failure. - * - * @see DB_common::query() - */ - public function &getCol($query, $col = 0, $params = array()) - { - $params = (array)$params; - if (sizeof($params) > 0) { - $sth = $this->prepare($query); - - if (DB::isError($sth)) { - return $sth; - } - - $res = $this->execute($sth, $params); - $this->freePrepared($sth); - } else { - $res = $this->query($query); - } - - if (DB::isError($res)) { - return $res; - } - - $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; - - if (!is_array($row = $res->fetchRow($fetchmode))) { - $ret = array(); - } else { - if (!array_key_exists($col, $row)) { - $ret = $this->raiseError(DB_ERROR_NOSUCHFIELD); - } else { - $ret = array($row[$col]); - while (is_array($row = $res->fetchRow($fetchmode))) { - $ret[] = $row[$col]; - } - } - } - - $res->free(); - - if (DB::isError($row)) { - $ret = $row; - } - - return $ret; - } - - // }}} - // {{{ nextQueryIsManip() - - /** - * Sets (or unsets) a flag indicating that the next query will be a - * manipulation query, regardless of the usual DB::isManip() heuristics. - * - * @param boolean true to set the flag overriding the isManip() behaviour, - * false to clear it and fall back onto isManip() - * - * @return void - * - * @access public - */ - public function nextQueryIsManip($manip) - { - $this->_next_query_manip = $manip; - } - - // }}} - // {{{ _checkManip() - - /** - * Checks if the given query is a manipulation query. This also takes into - * account the _next_query_manip flag and sets the _last_query_manip flag - * (and resets _next_query_manip) according to the result. - * - * @param string The query to check. - * - * @return boolean true if the query is a manipulation query, false - * otherwise - * - * @access protected - */ - public function _checkManip($query) - { - if ($this->_next_query_manip || DB::isManip($query)) { - $this->_last_query_manip = true; - } else { - $this->_last_query_manip = false; - } - $this->_next_query_manip = false; - return $this->_last_query_manip; - } - - // }}} - // {{{ _rtrimArrayValues() - - /** - * Right-trims all strings in an array - * - * @param array $array the array to be trimmed (passed by reference) - * - * @return void - * - * @access protected - */ - public function _rtrimArrayValues(&$array) - { - foreach ($array as $key => $value) { - if (is_string($value)) { - $array[$key] = rtrim($value); - } - } - } - - // }}} - // {{{ _convertNullArrayValuesToEmpty() - - /** - * Converts all null values in an array to empty strings - * - * @param array $array the array to be de-nullified (passed by reference) - * - * @return void - * - * @access protected - */ - public function _convertNullArrayValuesToEmpty(&$array) - { - foreach ($array as $key => $value) { - if (is_null($value)) { - $array[$key] = ''; - } - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/dbase.php b/extlib/DB/dbase.php deleted file mode 100644 index f72540d894..0000000000 --- a/extlib/DB/dbase.php +++ /dev/null @@ -1,531 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's dbase extension - * for interacting with dBase databases - * - * These methods overload the ones declared in DB_common. - * - * @category Database - * @package DB - * @author Tomas V.V. Cox - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_dbase extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'dbase'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'dbase'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => false, - 'new_link' => false, - 'numrows' => true, - 'pconnect' => false, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array(); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * A means of emulating result resources - * @var array - */ - public $res_row = array(); - - /** - * The quantity of results so far - * - * For emulating result resources. - * - * @var integer - */ - public $result = 0; - - /** - * Maps dbase data type id's to human readable strings - * - * The human readable values are based on the output of PHP's - * dbase_get_header_info() function. - * - * @var array - * @since Property available since Release 1.7.0 - */ - public $types = array( - 'C' => 'character', - 'D' => 'date', - 'L' => 'boolean', - 'M' => 'memo', - 'N' => 'number', - ); - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database and create it if it doesn't exist - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's dbase driver supports the following extra DSN options: - * + mode An integer specifying the read/write mode to use - * (0 = read only, 1 = write only, 2 = read/write). - * Available since PEAR DB 1.7.0. - * + fields An array of arrays that PHP's dbase_create() function needs - * to create a new database. This information is used if the - * dBase file specified in the "database" segment of the DSN - * does not exist. For more info, see the PHP manual's - * {@link http://php.net/dbase_create dbase_create()} page. - * Available since PEAR DB 1.7.0. - * - * Example of how to connect and establish a new dBase file if necessary: - * - * require_once 'DB.php'; - * - * $dsn = array( - * 'phptype' => 'dbase', - * 'database' => '/path/and/name/of/dbase/file', - * 'mode' => 2, - * 'fields' => array( - * array('a', 'N', 5, 0), - * array('b', 'C', 40), - * array('c', 'C', 255), - * array('d', 'C', 20), - * ), - * ); - * $options = array( - * 'debug' => 2, - * 'portability' => DB_PORTABILITY_ALL, - * ); - * - * $db = DB::connect($dsn, $options); - * if ((new PEAR)->isError($db)) { - * die($db->getMessage()); - * } - * - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('dbase')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - /* - * Turn track_errors on for entire script since $php_errormsg - * is the only way to find errors from the dbase extension. - */ - @ini_set('track_errors', 1); - $php_errormsg = ''; - - if (!file_exists($dsn['database'])) { - $this->dsn['mode'] = 2; - if (empty($dsn['fields']) || !is_array($dsn['fields'])) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - 'the dbase file does not exist and ' - . 'it could not be created because ' - . 'the "fields" element of the DSN ' - . 'is not properly set' - ); - } - $this->connection = @dbase_create( - $dsn['database'], - $dsn['fields'] - ); - if (!$this->connection) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - 'the dbase file does not exist and ' - . 'the attempt to create it failed: ' - . $php_errormsg - ); - } - } else { - if (!isset($this->dsn['mode'])) { - $this->dsn['mode'] = 0; - } - $this->connection = @dbase_open( - $dsn['database'], - $this->dsn['mode'] - ); - if (!$this->connection) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @dbase_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ &query() - - public function &query($query = null) - { - // emulate result resources - $this->res_row[(int)$this->result] = 0; - $tmp = new DB_result($this, $this->result++); - return $tmp; - } - - // }}} - // {{{ fetchInto() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum === null) { - $rownum = $this->res_row[(int)$result]++; - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @dbase_get_record_with_names($this->connection, $rownum); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @dbase_get_record($this->connection, $rownum); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ freeResult() - - /** - * Deletes the result set and frees the memory occupied by the result set. - * - * This method is a no-op for dbase, as there aren't result resources in - * the same sense as most other database backends. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return true; - } - - // }}} - // {{{ numCols() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param $foo - * @return int the number of columns. A DB_Error object on failure. - * - * @see DB_result::numCols() - */ - public function numCols($foo) - { - return @dbase_numfields($this->connection); - } - - // }}} - // {{{ numRows() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param $foo - * @return int the number of rows. A DB_Error object on failure. - * - * @see DB_result::numRows() - */ - public function numRows($foo) - { - return @dbase_numrecords($this->connection); - } - - // }}} - // {{{ quoteBoolean() - - /** - * Formats a boolean value for use within a query in a locale-independent - * manner. - * - * @param boolean the boolean value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteBoolean($boolean) - { - return $boolean ? 'T' : 'F'; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about the current database - * - * @param mixed $result THIS IS UNUSED IN DBASE. The current database - * is examined regardless of what is provided here. - * @param int $mode a valid tableInfo mode - * - * @return array|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - * @since Method available since Release 1.7.0 - */ - public function tableInfo($result = null, $mode = null) - { - if (function_exists('dbase_get_header_info')) { - $id = @dbase_get_header_info($this->connection); - if (!$id && $php_errormsg) { - return $this->raiseError( - DB_ERROR, - null, - null, - null, - $php_errormsg - ); - } - } else { - /* - * This segment for PHP 4 is loosely based on code by - * Hadi Rusiah in the comments on - * the dBase reference page in the PHP manual. - */ - $db = @fopen($this->dsn['database'], 'r'); - if (!$db) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - - $id = array(); - $i = 0; - - $line = fread($db, 32); - while (!feof($db)) { - $line = fread($db, 32); - if (substr($line, 0, 1) == chr(13)) { - break; - } else { - $pos = strpos(substr($line, 0, 10), chr(0)); - $pos = ($pos == 0 ? 10 : $pos); - $id[$i] = array( - 'name' => substr($line, 0, $pos), - 'type' => $this->types[substr($line, 11, 1)], - 'length' => ord(substr($line, 16, 1)), - 'precision' => ord(substr($line, 17, 1)), - ); - } - $i++; - } - - fclose($db); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $res = array(); - $count = count($id); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => $this->dsn['database'], - 'name' => $case_func($id[$i]['name']), - 'type' => $id[$i]['type'], - 'len' => $id[$i]['length'], - 'flags' => '' - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - return $res; - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/fbsql.php b/extlib/DB/fbsql.php deleted file mode 100644 index 3c1c3f6651..0000000000 --- a/extlib/DB/fbsql.php +++ /dev/null @@ -1,795 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's fbsql extension - * for interacting with FrontBase databases - * - * These methods overload the ones declared in DB_common. - * - * @category Database - * @package DB - * @author Frank M. Kromann - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - * @since Class functional since Release 1.7.0 - */ -class DB_fbsql extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'fbsql'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'fbsql'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'alter', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - 22 => DB_ERROR_SYNTAX, - 85 => DB_ERROR_ALREADY_EXISTS, - 108 => DB_ERROR_SYNTAX, - 116 => DB_ERROR_NOSUCHTABLE, - 124 => DB_ERROR_VALUE_COUNT_ON_ROW, - 215 => DB_ERROR_NOSUCHFIELD, - 217 => DB_ERROR_INVALID_NUMBER, - 226 => DB_ERROR_NOSUCHFIELD, - 231 => DB_ERROR_INVALID, - 239 => DB_ERROR_TRUNCATED, - 251 => DB_ERROR_SYNTAX, - 266 => DB_ERROR_NOT_FOUND, - 357 => DB_ERROR_CONSTRAINT_NOT_NULL, - 358 => DB_ERROR_CONSTRAINT, - 360 => DB_ERROR_CONSTRAINT, - 361 => DB_ERROR_CONSTRAINT, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('fbsql')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $params = array( - $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', - $dsn['username'] ? $dsn['username'] : null, - $dsn['password'] ? $dsn['password'] : null, - ); - - $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect'; - - $ini = ini_get('track_errors'); - $php_errormsg = ''; - if ($ini) { - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - } else { - @ini_set('track_errors', 1); - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - @ini_set('track_errors', $ini); - } - - if (!$this->connection) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - - if ($dsn['database']) { - if (!@fbsql_select_db($dsn['database'], $this->connection)) { - return $this->fbsqlRaiseError(); - } - } - - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_fbsql::errorNative(), DB_common::errorCode() - */ - public function fbsqlRaiseError($errno = null) - { - if ($errno === null) { - $errno = $this->errorCode(fbsql_errno($this->connection)); - } - return $this->raiseError( - $errno, - null, - null, - null, - @fbsql_error($this->connection) - ); - } - - // }}} - // {{{ simpleQuery() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @fbsql_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ nextResult() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $this->last_query = $query; - $query = $this->modifyQuery($query); - $result = @fbsql_query("$query;", $this->connection); - if (!$result) { - return $this->fbsqlRaiseError(); - } - // Determine which queries that should return data, and which - // should return an error code only. - if ($this->_checkManip($query)) { - return DB_OK; - } - return $result; - } - - // }}} - // {{{ fetchInto() - - /** - * Move the internal fbsql result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return @fbsql_next_result($result); - } - - // }}} - // {{{ freeResult() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@fbsql_data_seek($result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @fbsql_fetch_array($result, FBSQL_ASSOC); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @fbsql_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ autoCommit() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? fbsql_free_result($result) : false; - } - - // }}} - // {{{ commit() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - if ($onoff) { - $this->query("SET COMMIT TRUE"); - } else { - $this->query("SET COMMIT FALSE"); - } - return null; - } - - // }}} - // {{{ rollback() - - /** - * Commits the current transaction - * - * @return int DB_OK on success. A DB_Error object on failure. - */ - public function commit() - { - @fbsql_commit($this->connection); - return 0; - } - - // }}} - // {{{ numCols() - - /** - * Reverts the current transaction - * - * @return int DB_OK on success. A DB_Error object on failure. - */ - public function rollback() - { - @fbsql_rollback($this->connection); - return 0; - } - - // }}} - // {{{ numRows() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @fbsql_num_fields($result); - if (!$cols) { - return $this->fbsqlRaiseError(); - } - return $cols; - } - - // }}} - // {{{ affectedRows() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @fbsql_num_rows($result); - if ($rows === null) { - return $this->fbsqlRaiseError(); - } - return $rows; - } - - // }}} - // {{{ nextId() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if ($this->_last_query_manip) { - $result = @fbsql_affected_rows($this->connection); - } else { - $result = 0; - } - return $result; - } - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_fbsql::createSequence(), DB_fbsql::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - do { - $repeat = 0; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query('SELECT UNIQUE FROM ' . $seqname); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - $repeat = 1; - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $result; - } - } else { - $repeat = 0; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->fbsqlRaiseError(); - } - $result->fetchInto($tmp, DB_FETCHMODE_ORDERED); - return $tmp[0]; - } - - // }}} - // {{{ dropSequence() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_fbsql::nextID(), DB_fbsql::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER NOT NULL,' - . ' PRIMARY KEY(id))'); - if ($res) { - $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname); - } - return $res; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_fbsql::nextID(), DB_fbsql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name) - . ' RESTRICT'); - } - - // }}} - // {{{ quoteBoolean() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - if (DB::isManip($query) || $this->_next_query_manip) { - return preg_replace( - '/^([\s(])*SELECT/i', - "\\1SELECT TOP($count)", - $query - ); - } else { - return preg_replace( - '/([\s(])*SELECT/i', - "\\1SELECT TOP($from, $count)", - $query - ); - } - } - - // }}} - // {{{ quoteFloat() - - /** - * Formats a boolean value for use within a query in a locale-independent - * manner. - * - * @param boolean the boolean value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteBoolean($boolean) - { - return $boolean ? 'TRUE' : 'FALSE'; - } - - // }}} - // {{{ fbsqlRaiseError() - - /** - * Formats a float value for use within a query in a locale-independent - * manner. - * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteFloat($float) - { - return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code - */ - public function errorNative() - { - return @fbsql_errno($this->connection); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @fbsql_list_fields( - $this->dsn['database'], - $result, - $this->connection - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @fbsql_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => $case_func(@fbsql_field_table($id, $i)), - 'name' => $case_func(@fbsql_field_name($id, $i)), - 'type' => @fbsql_field_type($id, $i), - 'len' => @fbsql_field_len($id, $i), - 'flags' => @fbsql_field_flags($id, $i), - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @fbsql_free_result($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SELECT "table_name" FROM information_schema.tables' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk AND' - . ' "table_type" = \'BASE TABLE\'' - . ' AND "schema_name" = current_schema'; - case 'views': - return 'SELECT "table_name" FROM information_schema.tables' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk AND' - . ' "table_type" = \'VIEW\'' - . ' AND "schema_name" = current_schema'; - case 'users': - return 'SELECT "user_name" from information_schema.users'; - case 'functions': - return 'SELECT "routine_name" FROM' - . ' information_schema.psm_routines' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk' - . ' AND "routine_kind"=\'FUNCTION\'' - . ' AND "schema_name" = current_schema'; - case 'procedures': - return 'SELECT "routine_name" FROM' - . ' information_schema.psm_routines' - . ' t0, information_schema.schemata t1' - . ' WHERE t0.schema_pk=t1.schema_pk' - . ' AND "routine_kind"=\'PROCEDURE\'' - . ' AND "schema_name" = current_schema'; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/ibase.php b/extlib/DB/ibase.php deleted file mode 100644 index a91bd35772..0000000000 --- a/extlib/DB/ibase.php +++ /dev/null @@ -1,1092 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's interbase extension - * for interacting with Interbase and Firebird databases - * - * These methods overload the ones declared in DB_common. - * - * While this class works with PHP 4, PHP's InterBase extension is - * unstable in PHP 4. Use PHP 5. - * - * NOTICE: limitQuery() only works for Firebird. - * - * @category Database - * @package DB - * @author Sterling Hughes - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - * @since Class became stable in Release 1.7.0 - */ -class DB_ibase extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'ibase'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'ibase'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * NOTE: only firebird supports limit. - * - * @var array - */ - public $features = array( - 'limit' => false, - 'new_link' => false, - 'numrows' => 'emulate', - 'pconnect' => true, - 'prepare' => true, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - -104 => DB_ERROR_SYNTAX, - -150 => DB_ERROR_ACCESS_VIOLATION, - -151 => DB_ERROR_ACCESS_VIOLATION, - -155 => DB_ERROR_NOSUCHTABLE, - -157 => DB_ERROR_NOSUCHFIELD, - -158 => DB_ERROR_VALUE_COUNT_ON_ROW, - -170 => DB_ERROR_MISMATCH, - -171 => DB_ERROR_MISMATCH, - -172 => DB_ERROR_INVALID, - // -204 => // Covers too many errors, need to use regex on msg - -205 => DB_ERROR_NOSUCHFIELD, - -206 => DB_ERROR_NOSUCHFIELD, - -208 => DB_ERROR_INVALID, - -219 => DB_ERROR_NOSUCHTABLE, - -297 => DB_ERROR_CONSTRAINT, - -303 => DB_ERROR_INVALID, - -413 => DB_ERROR_INVALID_NUMBER, - -530 => DB_ERROR_CONSTRAINT, - -551 => DB_ERROR_ACCESS_VIOLATION, - -552 => DB_ERROR_ACCESS_VIOLATION, - // -607 => // Covers too many errors, need to use regex on msg - -625 => DB_ERROR_CONSTRAINT_NOT_NULL, - -803 => DB_ERROR_CONSTRAINT, - -804 => DB_ERROR_VALUE_COUNT_ON_ROW, - // -902 => // Covers too many errors, need to use regex on msg - -904 => DB_ERROR_CONNECT_FAILED, - -922 => DB_ERROR_NOSUCHDB, - -923 => DB_ERROR_CONNECT_FAILED, - -924 => DB_ERROR_CONNECT_FAILED - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * The number of rows affected by a data manipulation query - * @var integer - * @access private - */ - public $affected = 0; - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The prepared statement handle from the most recently executed statement - * - * {@internal Mainly here because the InterBase/Firebird API is only - * able to retrieve data from result sets if the statemnt handle is - * still in scope.}} - * - * @var resource - */ - public $last_stmt; - - /** - * Is the given prepared statement a data manipulation query? - * @var array - * @access private - */ - public $manip_query = array(); - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's ibase driver supports the following extra DSN options: - * + buffers The number of database buffers to allocate for the - * server-side cache. - * + charset The default character set for a database. - * + dialect The default SQL dialect for any statement - * executed within a connection. Defaults to the - * highest one supported by client libraries. - * Functional only with InterBase 6 and up. - * + role Functional only with InterBase 5 and up. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('interbase')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - if ($this->dbsyntax == 'firebird') { - $this->features['limit'] = 'alter'; - } - - $params = array( - $dsn['hostspec'] - ? ($dsn['hostspec'] . ':' . $dsn['database']) - : $dsn['database'], - $dsn['username'] ? $dsn['username'] : null, - $dsn['password'] ? $dsn['password'] : null, - isset($dsn['charset']) ? $dsn['charset'] : null, - isset($dsn['buffers']) ? $dsn['buffers'] : null, - isset($dsn['dialect']) ? $dsn['dialect'] : null, - isset($dsn['role']) ? $dsn['role'] : null, - ); - - $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect'; - - $this->connection = @call_user_func_array($connect_function, $params); - if (!$this->connection) { - return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_ibase::errorNative(), DB_ibase::errorCode() - */ - public function &ibaseRaiseError($errno = null) - { - if ($errno === null) { - $errno = $this->errorCode($this->errorNative()); - } - $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg()); - return $tmp; - } - - // }}} - // {{{ simpleQuery() - - /** - * Maps native error codes to DB's portable ones - * - * @param int $nativecode the error code returned by the DBMS - * - * @return int the portable DB error code. Return DB_ERROR if the - * current driver doesn't have a mapping for the - * $nativecode submitted. - * - * @since Method available since Release 1.7.0 - */ - public function errorCode($nativecode = null) - { - if (isset($this->errorcode_map[$nativecode])) { - return $this->errorcode_map[$nativecode]; - } - - static $error_regexps; - if (!isset($error_regexps)) { - $error_regexps = array( - '/generator .* is not defined/' - => DB_ERROR_SYNTAX, // for compat. w ibase_errcode() - '/violation of [\w ]+ constraint/i' - => DB_ERROR_CONSTRAINT, - '/table.*(not exist|not found|unknown)/i' - => DB_ERROR_NOSUCHTABLE, - '/table .* already exists/i' - => DB_ERROR_ALREADY_EXISTS, - '/unsuccessful metadata update .* failed attempt to store duplicate value/i' - => DB_ERROR_ALREADY_EXISTS, - '/unsuccessful metadata update .* not found/i' - => DB_ERROR_NOT_FOUND, - '/validation error for column .* value "\*\*\* null/i' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/conversion error from string/i' - => DB_ERROR_INVALID_NUMBER, - '/no permission for/i' - => DB_ERROR_ACCESS_VIOLATION, - '/arithmetic exception, numeric overflow, or string truncation/i' - => DB_ERROR_INVALID, - '/feature is not supported/i' - => DB_ERROR_NOT_CAPABLE, - ); - } - - $errormsg = @ibase_errmsg(); - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - return DB_ERROR; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code. NULL if there is no error code. - * - * @since Method available since Release 1.7.0 - */ - public function errorNative() - { - if (function_exists('ibase_errcode')) { - return @ibase_errcode(); - } - if (preg_match( - '/^Dynamic SQL Error SQL error code = ([0-9-]+)/i', - @ibase_errmsg(), - $m - )) { - return (int)$m[1]; - } - return null; - } - - // }}} - // {{{ nextResult() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @ibase_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ fetchInto() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - $query = $this->modifyQuery($query); - $result = @ibase_query($this->connection, $query); - - if (!$result) { - return $this->ibaseRaiseError(); - } - if ($this->autocommit && $ismanip) { - @ibase_commit($this->connection); - } - if ($ismanip) { - $this->affected = $result; - return DB_OK; - } else { - $this->affected = 0; - return $result; - } - } - - // }}} - // {{{ freeResult() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * Only works with Firebird. - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - if ($this->dsn['dbsyntax'] == 'firebird') { - $query = preg_replace( - '/^([\s(])*SELECT/i', - "SELECT FIRST $count SKIP $from", - $query - ); - } - return $query; - } - - // }}} - // {{{ freeQuery() - - /** - * Move the internal ibase result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ affectedRows() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - if (function_exists('ibase_fetch_assoc')) { - $arr = @ibase_fetch_assoc($result); - } else { - $arr = get_object_vars(ibase_fetch_object($result)); - } - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @ibase_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ numCols() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? ibase_free_result($result) : false; - } - - // }}} - // {{{ prepare() - - public function freeQuery($query) - { - return is_resource($query) ? ibase_free_query($query) : false; - } - - // }}} - // {{{ execute() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int|object - */ - public function affectedRows() - { - if (is_integer($this->affected)) { - return $this->affected; - } - return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); - } - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @ibase_num_fields($result); - if (!$cols) { - return $this->ibaseRaiseError(); - } - return $cols; - } - - // }}} - // {{{ autoCommit() - - /** - * Prepares a query for multiple execution with execute(). - * - * prepare() requires a generic query as string like - * INSERT INTO numbers VALUES (?, ?, ?) - * . The ? characters are placeholders. - * - * Three types of placeholders can be used: - * + ? a quoted scalar value, i.e. strings, integers - * + ! value is inserted 'as is' - * + & requires a file name. The file's contents get - * inserted into the query (i.e. saving binary - * data in a db) - * - * Use backslashes to escape placeholder characters if you don't want - * them to be interpreted as placeholders. Example: - * "UPDATE foo SET col=? WHERE col='over \& under'" - * - * - * @param string $query query to be prepared - * @return mixed DB statement resource on success. DB_Error on failure. - */ - public function prepare($query) - { - $tokens = preg_split( - '/((? $val) { - switch ($val) { - case '?': - $types[$token++] = DB_PARAM_SCALAR; - break; - case '&': - $types[$token++] = DB_PARAM_OPAQUE; - break; - case '!': - $types[$token++] = DB_PARAM_MISC; - break; - default: - $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val); - $newquery .= $tokens[$key] . '?'; - } - } - - $newquery = substr($newquery, 0, -1); - $this->last_query = $query; - $newquery = $this->modifyQuery($newquery); - $stmt = @ibase_prepare(/*$this->connection,*/ $newquery); - - if ($stmt === false) { - $stmt = $this->ibaseRaiseError(); - } else { - $this->prepare_types[(int)$stmt] = $types; - $this->manip_query[(int)$stmt] = DB::isManip($query); - } - - return $stmt; - } - - // }}} - // {{{ commit() - - /** - * Executes a DB statement prepared with prepare(). - * - * @param resource $stmt a DB statement resource returned from prepare() - * @param mixed $data array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 for non-array items or the - * quantity of elements in the array. - * @return object a new DB_Result or a DB_Error when fail - * @see DB_ibase::prepare() - * @access public - */ - public function &execute($stmt, $data = array()) - { - $data = (array)$data; - $this->last_parameters = $data; - - $types = $this->prepare_types[(int)$stmt]; - if (count($types) != count($data)) { - $tmp = $this->raiseError(DB_ERROR_MISMATCH); - return $tmp; - } - - $i = 0; - foreach ($data as $key => $value) { - if ($types[$i] == DB_PARAM_MISC) { - /* - * ibase doesn't seem to have the ability to pass a - * parameter along unchanged, so strip off quotes from start - * and end, plus turn two single quotes to one single quote, - * in order to avoid the quotes getting escaped by - * ibase and ending up in the database. - */ - $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]); - $data[$key] = str_replace("''", "'", $data[$key]); - } elseif ($types[$i] == DB_PARAM_OPAQUE) { - $fp = @fopen($data[$key], 'rb'); - if (!$fp) { - $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION); - return $tmp; - } - $data[$key] = fread($fp, filesize($data[$key])); - fclose($fp); - } - $i++; - } - - array_unshift($data, $stmt); - - $res = call_user_func_array('ibase_execute', $data); - if (!$res) { - $tmp = $this->ibaseRaiseError(); - return $tmp; - } - /* XXX need this? - if ($this->autocommit && $this->manip_query[(int)$stmt]) { - @ibase_commit($this->connection); - }*/ - $this->last_stmt = $stmt; - if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) { - $this->_last_query_manip = true; - $this->_next_query_manip = false; - $tmp = DB_OK; - } else { - $this->_last_query_manip = false; - $tmp = new DB_result($this, $res); - } - return $tmp; - } - - // }}} - // {{{ rollback() - - /** - * Frees the internal resources associated with a prepared query - * - * @param resource $stmt the prepared statement's PHP resource - * @param bool $free_resource should the PHP resource be freed too? - * Use false if you need to get data - * from the result set later. - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_ibase::prepare() - */ - public function freePrepared($stmt, $free_resource = true) - { - if (!is_resource($stmt)) { - return false; - } - if ($free_resource) { - @ibase_free_query($stmt); - } - unset($this->prepare_tokens[(int)$stmt]); - unset($this->prepare_types[(int)$stmt]); - unset($this->manip_query[(int)$stmt]); - return true; - } - - // }}} - // {{{ transactionInit() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - $this->autocommit = $onoff ? 1 : 0; - return DB_OK; - } - - // }}} - // {{{ nextId() - - /** - * Commits the current transaction - * - * @return int DB_OK on success. A DB_Error object on failure. - */ - public function commit() - { - return @ibase_commit($this->connection); - } - - // }}} - // {{{ createSequence() - - /** - * Reverts the current transaction - * - * @return int DB_OK on success. A DB_Error object on failure. - */ - public function rollback() - { - return @ibase_rollback($this->connection); - } - - // }}} - // {{{ dropSequence() - - public function transactionInit($trans_args = 0) - { - return $trans_args - ? @ibase_trans($trans_args, $this->connection) - : @ibase_trans(); - } - - // }}} - // {{{ _ibaseFieldFlags() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_ibase::createSequence(), DB_ibase::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $sqn = strtoupper($this->getSequenceName($seq_name)); - $repeat = 0; - do { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("SELECT GEN_ID(${sqn}, 1) " - . 'FROM RDB$GENERATORS ' - . "WHERE RDB\$GENERATOR_NAME='${sqn}'"); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result)) { - $repeat = 1; - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $result; - } - } else { - $repeat = 0; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); - $result->free(); - return $arr[0]; - } - - // }}} - // {{{ ibaseRaiseError() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_ibase::nextID(), DB_ibase::dropSequence() - */ - public function createSequence($seq_name) - { - $sqn = strtoupper($this->getSequenceName($seq_name)); - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("CREATE GENERATOR ${sqn}"); - $this->popErrorHandling(); - - return $result; - } - - // }}} - // {{{ errorNative() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_ibase::nextID(), DB_ibase::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DELETE FROM RDB$GENERATORS ' - . "WHERE RDB\$GENERATOR_NAME='" - . strtoupper($this->getSequenceName($seq_name)) - . "'"); - } - - // }}} - // {{{ errorCode() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @ibase_query( - $this->connection, - "SELECT * FROM $result WHERE 1=0" - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @ibase_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $info = @ibase_field_info($id, $i); - $res[$i] = array( - 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func($info['name']), - 'type' => $info['type'], - 'len' => $info['length'], - 'flags' => ($got_string) - ? $this->_ibaseFieldFlags($info['name'], $result) - : '', - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @ibase_free_result($id); - } - return $res; - } - - // }}} - // {{{ tableInfo() - - /** - * Get the column's flags - * - * Supports "primary_key", "unique_key", "not_null", "default", - * "computed" and "blob". - * - * @param string $field_name the name of the field - * @param string $table_name the name of the table - * - * @return string the flags - * - * @access private - */ - public function _ibaseFieldFlags($field_name, $table_name) - { - $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE' - . ' FROM RDB$INDEX_SEGMENTS I' - . ' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME' - . ' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\'' - . ' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''; - - $result = @ibase_query($this->connection, $sql); - if (!$result) { - return $this->ibaseRaiseError(); - } - - $flags = ''; - if ($obj = @ibase_fetch_object($result)) { - @ibase_free_result($result); - if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') { - $flags .= 'primary_key '; - } - if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') { - $flags .= 'unique_key '; - } - } - - $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,' - . ' R.RDB$DEFAULT_SOURCE AS DSOURCE,' - . ' F.RDB$FIELD_TYPE AS FTYPE,' - . ' F.RDB$COMPUTED_SOURCE AS CSOURCE' - . ' FROM RDB$RELATION_FIELDS R ' - . ' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME' - . ' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'' - . ' AND R.RDB$FIELD_NAME=\'' . $field_name . '\''; - - $result = @ibase_query($this->connection, $sql); - if (!$result) { - return $this->ibaseRaiseError(); - } - if ($obj = @ibase_fetch_object($result)) { - @ibase_free_result($result); - if (isset($obj->NFLAG)) { - $flags .= 'not_null '; - } - if (isset($obj->DSOURCE)) { - $flags .= 'default '; - } - if (isset($obj->CSOURCE)) { - $flags .= 'computed '; - } - if (isset($obj->FTYPE) && $obj->FTYPE == 261) { - $flags .= 'blob '; - } - } - - return trim($flags); - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM ' - . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0'; - case 'views': - return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS'; - case 'users': - return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES'; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/ifx.php b/extlib/DB/ifx.php deleted file mode 100644 index d398dd2693..0000000000 --- a/extlib/DB/ifx.php +++ /dev/null @@ -1,686 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's ifx extension - * for interacting with Informix databases - * - * These methods overload the ones declared in DB_common. - * - * More info on Informix errors can be found at: - * http://www.informix.com/answers/english/ierrors.htm - * - * TODO: - * - set needed env Informix vars on connect - * - implement native prepare/execute - * - * @category Database - * @package DB - * @author Tomas V.V.Cox - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_ifx extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'ifx'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'ifx'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => 'emulate', - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - '-201' => DB_ERROR_SYNTAX, - '-206' => DB_ERROR_NOSUCHTABLE, - '-217' => DB_ERROR_NOSUCHFIELD, - '-236' => DB_ERROR_VALUE_COUNT_ON_ROW, - '-239' => DB_ERROR_CONSTRAINT, - '-253' => DB_ERROR_SYNTAX, - '-268' => DB_ERROR_CONSTRAINT, - '-292' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-310' => DB_ERROR_ALREADY_EXISTS, - '-316' => DB_ERROR_ALREADY_EXISTS, - '-319' => DB_ERROR_NOT_FOUND, - '-329' => DB_ERROR_NODBSELECTED, - '-346' => DB_ERROR_CONSTRAINT, - '-386' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-391' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-554' => DB_ERROR_SYNTAX, - '-691' => DB_ERROR_CONSTRAINT, - '-692' => DB_ERROR_CONSTRAINT, - '-703' => DB_ERROR_CONSTRAINT_NOT_NULL, - '-1202' => DB_ERROR_DIVZERO, - '-1204' => DB_ERROR_INVALID_DATE, - '-1205' => DB_ERROR_INVALID_DATE, - '-1206' => DB_ERROR_INVALID_DATE, - '-1209' => DB_ERROR_INVALID_DATE, - '-1210' => DB_ERROR_INVALID_DATE, - '-1212' => DB_ERROR_INVALID_DATE, - '-1213' => DB_ERROR_INVALID_NUMBER, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The quantity of transactions begun - * - * {@internal While this is private, it can't actually be designated - * private in PHP 5 because it is directly accessed in the test suite.}} - * - * @var integer - * @access private - */ - public $transaction_opcount = 0; - - /** - * The number of rows affected by a data manipulation query - * @var integer - * @access private - */ - public $affected = 0; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('informix') && - !PEAR::loadExtension('Informix')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : ''; - $dbname = $dsn['database'] ? $dsn['database'] . $dbhost : ''; - $user = $dsn['username'] ? $dsn['username'] : ''; - $pw = $dsn['password'] ? $dsn['password'] : ''; - - $connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect'; - - $this->connection = @$connect_function($dbname, $user, $pw); - if (!is_resource($this->connection)) { - return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_ifx::errorNative(), DB_ifx::errorCode() - */ - public function ifxRaiseError($errno = null) - { - if ($errno === null) { - $errno = $this->errorCode(ifx_error()); - } - return $this->raiseError( - $errno, - null, - null, - null, - $this->errorNative() - ); - } - - // }}} - // {{{ simpleQuery() - - /** - * Maps native error codes to DB's portable ones. - * - * Requires that the DB implementation's constructor fills - * in the $errorcode_map property. - * - * @param string $nativecode error code returned by the database - * @return int a portable DB error code, or DB_ERROR if this DB - * implementation has no mapping for the given error code. - */ - public function errorCode($nativecode) - { - if (preg_match('/SQLCODE=(.*)]/', $nativecode, $match)) { - $code = $match[1]; - if (isset($this->errorcode_map[$code])) { - return $this->errorcode_map[$code]; - } - } - return DB_ERROR; - } - - // }}} - // {{{ nextResult() - - /** - * Gets the DBMS' native error code and message produced by the last query - * - * @return string the DBMS' error code and message - */ - public function errorNative() - { - return @ifx_error() . ' ' . @ifx_errormsg(); - } - - // }}} - // {{{ affectedRows() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @ifx_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ fetchInto() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - $this->affected = null; - if (preg_match('/(SELECT|EXECUTE)/i', $query)) { //TESTME: Use !DB::isManip()? - // the scroll is needed for fetching absolute row numbers - // in a select query result - $result = @ifx_query($query, $this->connection, IFX_SCROLL); - } else { - if (!$this->autocommit && $ismanip) { - if ($this->transaction_opcount == 0) { - $result = @ifx_query('BEGIN WORK', $this->connection); - if (!$result) { - return $this->ifxRaiseError(); - } - } - $this->transaction_opcount++; - } - $result = @ifx_query($query, $this->connection); - } - if (!$result) { - return $this->ifxRaiseError(); - } - $this->affected = @ifx_affected_rows($result); - // Determine which queries should return data, and which - // should return an error code only. - if (preg_match('/(SELECT|EXECUTE)/i', $query)) { - return $result; - } - // XXX Testme: free results inside a transaction - // may cause to stop it and commit the work? - - // Result has to be freed even with a insert or update - @ifx_free_result($result); - - return DB_OK; - } - - // }}} - // {{{ numCols() - - /** - * Move the internal ifx result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ freeResult() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if ($this->_last_query_manip) { - return $this->affected; - } else { - return 0; - } - } - - // }}} - // {{{ autoCommit() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if (($rownum !== null) && ($rownum < 0)) { - return null; - } - if ($rownum === null) { - /* - * Even though fetch_row() should return the next row if - * $rownum is null, it doesn't in all cases. Bug 598. - */ - $rownum = 'NEXT'; - } else { - // Index starts at row 1, unlike most DBMS's starting at 0. - $rownum++; - } - if (!$arr = @ifx_fetch_row($result, $rownum)) { - return null; - } - if ($fetchmode !== DB_FETCHMODE_ASSOC) { - $i = 0; - $order = array(); - foreach ($arr as $val) { - $order[$i++] = $val; - } - $arr = $order; - } elseif ($fetchmode == DB_FETCHMODE_ASSOC && - $this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ commit() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - if (!$cols = @ifx_num_fields($result)) { - return $this->ifxRaiseError(); - } - return $cols; - } - - // }}} - // {{{ rollback() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? ifx_free_result($result) : false; - } - - // }}} - // {{{ ifxRaiseError() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = true) - { - // XXX if $this->transaction_opcount > 0, we should probably - // issue a warning here. - $this->autocommit = $onoff ? true : false; - return DB_OK; - } - - // }}} - // {{{ errorNative() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if ($this->transaction_opcount > 0) { - $result = @ifx_query('COMMIT WORK', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->ifxRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ errorCode() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if ($this->transaction_opcount > 0) { - $result = @ifx_query('ROLLBACK WORK', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->ifxRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' if $result is a table name. - * - * If analyzing a query result and the result has duplicate field names, - * an error will be raised saying - * can't distinguish duplicate field names. - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - * @since Method available since Release 1.6.0 - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @ifx_query( - "SELECT * FROM $result WHERE 1=0", - $this->connection - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - $flds = @ifx_fieldproperties($id); - $count = @ifx_num_fields($id); - - if (count($flds) != $count) { - return $this->raiseError("can't distinguish duplicate field names"); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $i = 0; - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - foreach ($flds as $key => $value) { - $props = explode(';', $value); - $res[$i] = array( - 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func($key), - 'type' => $props[0], - 'len' => $props[1], - 'flags' => $props[4] == 'N' ? 'not_null' : '', - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - $i++; - } - - // free the result only if we were called on a table - if ($got_string) { - @ifx_free_result($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SELECT tabname FROM systables WHERE tabid >= 100'; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/msql.php b/extlib/DB/msql.php deleted file mode 100644 index b583b5c635..0000000000 --- a/extlib/DB/msql.php +++ /dev/null @@ -1,845 +0,0 @@ - - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's msql extension - * for interacting with Mini SQL databases - * - * These methods overload the ones declared in DB_common. - * - * PHP's mSQL extension did weird things with NULL values prior to PHP - * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds - * those versions. - * - * @category Database - * @package DB - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - * @since Class not functional until Release 1.7.0 - */ -class DB_msql extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'msql'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'msql'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array(); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * The query result resource created by PHP - * - * Used to make affectedRows() work. Only contains the result for - * data manipulation queries. Contains false for other queries. - * - * @var resource - * @access private - */ - public $_result; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * Example of how to connect: - * - * require_once 'DB.php'; - * - * // $dsn = 'msql://hostname/dbname'; // use a TCP connection - * $dsn = 'msql:///dbname'; // use a socket - * $options = array( - * 'portability' => DB_PORTABILITY_ALL, - * ); - * - * $db = DB::connect($dsn, $options); - * if ((new PEAR)->isError($db)) { - * die($db->getMessage()); - * } - * - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('msql')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $params = array(); - if ($dsn['hostspec']) { - $params[] = $dsn['port'] - ? $dsn['hostspec'] . ',' . $dsn['port'] - : $dsn['hostspec']; - } - - $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect'; - - $ini = ini_get('track_errors'); - $php_errormsg = ''; - if ($ini) { - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - } else { - @ini_set('track_errors', 1); - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - @ini_set('track_errors', $ini); - } - - if (!$this->connection) { - if (($err = @msql_error()) != '') { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $err - ); - } else { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - } - - if (!@msql_select_db($dsn['database'], $this->connection)) { - return $this->msqlRaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_msql::errorNative(), DB_msql::errorCode() - */ - public function msqlRaiseError($errno = null) - { - $native = $this->errorNative(); - if ($errno === null) { - $errno = $this->errorCode($native); - } - return $this->raiseError($errno, null, null, null, $native); - } - - // }}} - // {{{ simpleQuery() - - /** - * Gets the DBMS' native error message produced by the last query - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return @msql_error(); - } - - - // }}} - // {{{ nextResult() - - /** - * Determines PEAR::DB error code from the database's text error message - * - * @param string $errormsg the error message returned from the database - * - * @return integer the error number from a DB_ERROR* constant - */ - public function errorCode($errormsg) - { - static $error_regexps; - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg); - - if (!isset($error_regexps)) { - $error_regexps = array( - '/^Access to database denied/i' - => DB_ERROR_ACCESS_VIOLATION, - '/^Bad index name/i' - => DB_ERROR_ALREADY_EXISTS, - '/^Bad order field/i' - => DB_ERROR_SYNTAX, - '/^Bad type for comparison/i' - => DB_ERROR_SYNTAX, - '/^Can\'t perform LIKE on/i' - => DB_ERROR_SYNTAX, - '/^Can\'t use TEXT fields in LIKE comparison/i' - => DB_ERROR_SYNTAX, - '/^Couldn\'t create temporary table/i' - => DB_ERROR_CANNOT_CREATE, - '/^Error creating table file/i' - => DB_ERROR_CANNOT_CREATE, - '/^Field .* cannot be null$/i' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/^Index (field|condition) .* cannot be null$/i' - => DB_ERROR_SYNTAX, - '/^Invalid date format/i' - => DB_ERROR_INVALID_DATE, - '/^Invalid time format/i' - => DB_ERROR_INVALID, - '/^Literal value for .* is wrong type$/i' - => DB_ERROR_INVALID_NUMBER, - '/^No Database Selected/i' - => DB_ERROR_NODBSELECTED, - '/^No value specified for field/i' - => DB_ERROR_VALUE_COUNT_ON_ROW, - '/^Non unique value for unique index/i' - => DB_ERROR_CONSTRAINT, - '/^Out of memory for temporary table/i' - => DB_ERROR_CANNOT_CREATE, - '/^Permission denied/i' - => DB_ERROR_ACCESS_VIOLATION, - '/^Reference to un-selected table/i' - => DB_ERROR_SYNTAX, - '/^syntax error/i' - => DB_ERROR_SYNTAX, - '/^Table .* exists$/i' - => DB_ERROR_ALREADY_EXISTS, - '/^Unknown database/i' - => DB_ERROR_NOSUCHDB, - '/^Unknown field/i' - => DB_ERROR_NOSUCHFIELD, - '/^Unknown (index|system variable)/i' - => DB_ERROR_NOT_FOUND, - '/^Unknown table/i' - => DB_ERROR_NOSUCHTABLE, - '/^Unqualified field/i' - => DB_ERROR_SYNTAX, - ); - } - - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - return DB_ERROR; - } - - // }}} - // {{{ fetchInto() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @msql_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ freeResult() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $this->last_query = $query; - $query = $this->modifyQuery($query); - $result = @msql_query($query, $this->connection); - if (!$result) { - return $this->msqlRaiseError(); - } - // Determine which queries that should return data, and which - // should return an error code only. - if ($this->_checkManip($query)) { - $this->_result = $result; - return DB_OK; - } else { - $this->_result = false; - return $result; - } - } - - // }}} - // {{{ numCols() - - /** - * Move the internal msql result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ numRows() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * PHP's mSQL extension did weird things with NULL values prior to PHP - * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds - * those versions. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@msql_data_seek($result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @msql_fetch_array($result, MSQL_ASSOC); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @msql_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ affected() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? msql_free_result($result) : false; - } - - // }}} - // {{{ nextId() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @msql_num_fields($result); - if (!$cols) { - return $this->msqlRaiseError(); - } - return $cols; - } - - // }}} - // {{{ createSequence() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @msql_num_rows($result); - if ($rows === false) { - return $this->msqlRaiseError(); - } - return $rows; - } - - // }}} - // {{{ dropSequence() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if (!$this->_result) { - return 0; - } - return msql_affected_rows($this->_result); - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_msql::createSequence(), DB_msql::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - $repeat = false; - do { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("SELECT _seq FROM ${seqname}"); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - $repeat = true; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->createSequence($seq_name); - $this->popErrorHandling(); - if (DB::isError($result)) { - return $this->raiseError($result); - } - } else { - $repeat = false; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); - $result->free(); - return $arr[0]; - } - - // }}} - // {{{ quoteFloat() - - /** - * Creates a new sequence - * - * Also creates a new table to associate the sequence with. Uses - * a separate table to ensure portability with other drivers. - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_msql::nextID(), DB_msql::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER NOT NULL)'); - if (DB::isError($res)) { - return $res; - } - $res = $this->query("CREATE SEQUENCE ON ${seqname}"); - return $res; - } - - // }}} - // {{{ escapeSimple() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_msql::nextID(), DB_msql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ msqlRaiseError() - - /** - * mSQL does not support delimited identifiers - * - * @param string $str the identifier name to be quoted - * - * @return object a DB_Error object - * - * @see DB_common::quoteIdentifier() - * @since Method available since Release 1.7.0 - */ - public function quoteIdentifier($str) - { - return $this->raiseError(DB_ERROR_UNSUPPORTED); - } - - // }}} - // {{{ errorNative() - - /** - * Formats a float value for use within a query in a locale-independent - * manner. - * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteFloat($float) - { - return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); - } - - // }}} - // {{{ errorCode() - - /** - * Escapes a string according to the current DBMS's standards - * - * @param string $str the string to be escaped - * - * @return string the escaped string - * - * @see DB_common::quoteSmart() - * @since Method available since Release 1.7.0 - */ - public function escapeSimple($str) - { - return addslashes($str); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::setOption() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @msql_query( - "SELECT * FROM $result", - $this->connection - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->raiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @msql_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $tmp = @msql_fetch_field($id); - - $flags = ''; - if ($tmp->not_null) { - $flags .= 'not_null '; - } - if ($tmp->unique) { - $flags .= 'unique_key '; - } - $flags = trim($flags); - - $res[$i] = array( - 'table' => $case_func($tmp->table), - 'name' => $case_func($tmp->name), - 'type' => $tmp->type, - 'len' => msql_field_len($id, $i), - 'flags' => $flags, - ); - - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @msql_free_result($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtain a list of a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return array|object - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'databases': - $id = @msql_list_dbs($this->connection); - break; - case 'tables': - $id = @msql_list_tables( - $this->dsn['database'], - $this->connection - ); - break; - default: - return null; - } - if (!$id) { - return $this->msqlRaiseError(); - } - $out = array(); - while ($row = @msql_fetch_row($id)) { - $out[] = $row[0]; - } - return $out; - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/mssql.php b/extlib/DB/mssql.php deleted file mode 100644 index 4a764100ef..0000000000 --- a/extlib/DB/mssql.php +++ /dev/null @@ -1,994 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's mssql extension - * for interacting with Microsoft SQL Server databases - * - * These methods overload the ones declared in DB_common. - * - * DB's mssql driver is only for Microsfoft SQL Server databases. - * - * If you're connecting to a Sybase database, you MUST specify "sybase" - * as the "phptype" in the DSN. - * - * This class only works correctly if you have compiled PHP using - * --with-mssql=[dir_to_FreeTDS]. - * - * @category Database - * @package DB - * @author Sterling Hughes - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_mssql extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'mssql'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'mssql'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX - public $errorcode_map = array( - 102 => DB_ERROR_SYNTAX, - 110 => DB_ERROR_VALUE_COUNT_ON_ROW, - 155 => DB_ERROR_NOSUCHFIELD, - 156 => DB_ERROR_SYNTAX, - 170 => DB_ERROR_SYNTAX, - 207 => DB_ERROR_NOSUCHFIELD, - 208 => DB_ERROR_NOSUCHTABLE, - 245 => DB_ERROR_INVALID_NUMBER, - 319 => DB_ERROR_SYNTAX, - 321 => DB_ERROR_NOSUCHFIELD, - 325 => DB_ERROR_SYNTAX, - 336 => DB_ERROR_SYNTAX, - 515 => DB_ERROR_CONSTRAINT_NOT_NULL, - 547 => DB_ERROR_CONSTRAINT, - 1018 => DB_ERROR_SYNTAX, - 1035 => DB_ERROR_SYNTAX, - 1913 => DB_ERROR_ALREADY_EXISTS, - 2209 => DB_ERROR_SYNTAX, - 2223 => DB_ERROR_SYNTAX, - 2248 => DB_ERROR_SYNTAX, - 2256 => DB_ERROR_SYNTAX, - 2257 => DB_ERROR_SYNTAX, - 2627 => DB_ERROR_CONSTRAINT, - 2714 => DB_ERROR_ALREADY_EXISTS, - 3607 => DB_ERROR_DIVZERO, - 3701 => DB_ERROR_NOSUCHTABLE, - 7630 => DB_ERROR_SYNTAX, - 8134 => DB_ERROR_DIVZERO, - 9303 => DB_ERROR_SYNTAX, - 9317 => DB_ERROR_SYNTAX, - 9318 => DB_ERROR_SYNTAX, - 9331 => DB_ERROR_SYNTAX, - 9332 => DB_ERROR_SYNTAX, - 15253 => DB_ERROR_SYNTAX, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The quantity of transactions begun - * - * {@internal While this is private, it can't actually be designated - * private in PHP 5 because it is directly accessed in the test suite.}} - * - * @var integer - * @access private - */ - public $transaction_opcount = 0; - - /** - * The database specified in the DSN - * - * It's a fix to allow calls to different databases in the same script. - * - * @var string - * @access private - */ - public $_db = null; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase') - && !PEAR::loadExtension('sybase_ct')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $params = array( - $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', - $dsn['username'] ? $dsn['username'] : null, - $dsn['password'] ? $dsn['password'] : null, - ); - if ($dsn['port']) { - $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':') - . $dsn['port']; - } - - $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect'; - - $this->connection = @call_user_func_array($connect_function, $params); - - if (!$this->connection) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - @mssql_get_last_message() - ); - } - if ($dsn['database']) { - if (!@mssql_select_db($dsn['database'], $this->connection)) { - return $this->raiseError( - DB_ERROR_NODBSELECTED, - null, - null, - null, - @mssql_get_last_message() - ); - } - $this->_db = $dsn['database']; - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @mssql_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ simpleQuery() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - if (!@mssql_select_db($this->_db, $this->connection)) { - return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); - } - $query = $this->modifyQuery($query); - if (!$this->autocommit && $ismanip) { - if ($this->transaction_opcount == 0) { - $result = @mssql_query('BEGIN TRAN', $this->connection); - if (!$result) { - return $this->mssqlRaiseError(); - } - } - $this->transaction_opcount++; - } - $result = @mssql_query($query, $this->connection); - if (!$result) { - return $this->mssqlRaiseError(); - } - // Determine which queries that should return data, and which - // should return an error code only. - return $ismanip ? DB_OK : $result; - } - - // }}} - // {{{ nextResult() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param null $code - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_mssql::errorNative(), DB_mssql::errorCode() - */ - public function mssqlRaiseError($code = null) - { - $message = @mssql_get_last_message(); - if (!$code) { - $code = $this->errorNative(); - } - return $this->raiseError( - $this->errorCode($code, $message), - null, - null, - null, - "$code - $message" - ); - } - - // }}} - // {{{ fetchInto() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code - */ - public function errorNative() - { - $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection); - if (!$res) { - return DB_ERROR; - } - $row = @mssql_fetch_row($res); - return $row[0]; - } - - // }}} - // {{{ freeResult() - - /** - * Determines PEAR::DB error code from mssql's native codes. - * - * If $nativecode isn't known yet, it will be looked up. - * - * @param mixed $nativecode mssql error code, if known - * @param string $msg - * @return integer an error number from a DB error constant - * @see errorNative() - */ - public function errorCode($nativecode = null, $msg = '') - { - if (!$nativecode) { - $nativecode = $this->errorNative(); - } - if (isset($this->errorcode_map[$nativecode])) { - if ($nativecode == 3701 - && preg_match('/Cannot drop the index/i', $msg)) { - return DB_ERROR_NOT_FOUND; - } - return $this->errorcode_map[$nativecode]; - } else { - return DB_ERROR; - } - } - - // }}} - // {{{ numCols() - - /** - * Move the internal mssql result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return @mssql_next_result($result); - } - - // }}} - // {{{ numRows() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@mssql_data_seek($result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @mssql_fetch_assoc($result); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @mssql_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ autoCommit() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? mssql_free_result($result) : false; - } - - // }}} - // {{{ commit() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @mssql_num_fields($result); - if (!$cols) { - return $this->mssqlRaiseError(); - } - return $cols; - } - - // }}} - // {{{ rollback() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @mssql_num_rows($result); - if ($rows === false) { - return $this->mssqlRaiseError(); - } - return $rows; - } - - // }}} - // {{{ affectedRows() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - // XXX if $this->transaction_opcount > 0, we should probably - // issue a warning here. - $this->autocommit = $onoff ? true : false; - return DB_OK; - } - - // }}} - // {{{ nextId() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if ($this->transaction_opcount > 0) { - if (!@mssql_select_db($this->_db, $this->connection)) { - return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); - } - $result = @mssql_query('COMMIT TRAN', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->mssqlRaiseError(); - } - } - return DB_OK; - } - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if ($this->transaction_opcount > 0) { - if (!@mssql_select_db($this->_db, $this->connection)) { - return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); - } - $result = @mssql_query('ROLLBACK TRAN', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->mssqlRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ dropSequence() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int|object - */ - public function affectedRows() - { - if ($this->_last_query_manip) { - $res = @mssql_query('select @@rowcount', $this->connection); - if (!$res) { - return $this->mssqlRaiseError(); - } - $ar = @mssql_fetch_row($res); - if (!$ar) { - $result = 0; - } else { - @mssql_free_result($res); - $result = $ar[0]; - } - } else { - $result = 0; - } - return $result; - } - - // }}} - // {{{ escapeSimple() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_mssql::createSequence(), DB_mssql::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - if (!@mssql_select_db($this->_db, $this->connection)) { - return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); - } - $repeat = 0; - do { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result) && - ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) { - $repeat = 1; - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $this->raiseError($result); - } - } elseif (!DB::isError($result)) { - $result = $this->query("SELECT IDENT_CURRENT('$seqname')"); - if (DB::isError($result)) { - /* Fallback code for MS SQL Server 7.0, which doesn't have - * IDENT_CURRENT. This is *not* safe for concurrent - * requests, and really, if you're using it, you're in a - * world of hurt. Nevertheless, it's here to ensure BC. See - * bug #181 for the gory details.*/ - $result = $this->query("SELECT @@IDENTITY FROM $seqname"); - } - $repeat = 0; - } else { - $repeat = false; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $result = $result->fetchRow(DB_FETCHMODE_ORDERED); - return $result[0]; - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_mssql::nextID(), DB_mssql::dropSequence() - */ - public function createSequence($seq_name) - { - return $this->query('CREATE TABLE ' - . $this->getSequenceName($seq_name) - . ' ([id] [int] IDENTITY (1, 1) NOT NULL,' - . ' [vapor] [int] NULL)'); - } - - // }}} - // {{{ mssqlRaiseError() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_mssql::nextID(), DB_mssql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ errorNative() - - /** - * Escapes a string in a manner suitable for SQL Server. - * - * @param string $str the string to be escaped - * @return string the escaped string - * - * @see DB_common::quoteSmart() - * @since Method available since Release 1.6.0 - */ - public function escapeSimple($str) - { - return str_replace( - array("'", "\\\r\n", "\\\n"), - array("''", "\\\\\r\n\r\n", "\\\\\n\n"), - $str - ); - } - - // }}} - // {{{ errorCode() - - /** - * Quotes a string so it can be safely used as a table or column name - * - * @param string $str identifier name to be quoted - * - * @return string quoted identifier string - * - * @see DB_common::quoteIdentifier() - * @since Method available since Release 1.6.0 - */ - public function quoteIdentifier($str) - { - return '[' . str_replace(']', ']]', $str) . ']'; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - if (!@mssql_select_db($this->_db, $this->connection)) { - return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); - } - $id = @mssql_query( - "SELECT * FROM $result WHERE 1=0", - $this->connection - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @mssql_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - if ($got_string) { - $flags = $this->_mssql_field_flags( - $result, - @mssql_field_name($id, $i) - ); - if (DB::isError($flags)) { - return $flags; - } - } else { - $flags = ''; - } - - $res[$i] = array( - 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func(@mssql_field_name($id, $i)), - 'type' => @mssql_field_type($id, $i), - 'len' => @mssql_field_length($id, $i), - 'flags' => $flags, - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @mssql_free_result($id); - } - return $res; - } - - // }}} - // {{{ _mssql_field_flags() - - /** - * Get a column's flags - * - * Supports "not_null", "primary_key", - * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), - * "unique_key" (mssql unique index, unique check or primary_key) and - * "multiple_key" (multikey index) - * - * mssql timestamp is NOT similar to the mysql timestamp so this is maybe - * not useful at all - is the behaviour of mysql_field_flags that primary - * keys are alway unique? is the interpretation of multiple_key correct? - * - * @param string $table the table name - * @param string $column the field name - * - * @return array|string - * - * @access private - * @author Joern Barthel - */ - public function _mssql_field_flags($table, $column) - { - static $tableName = null; - static $flags = array(); - - if ($table != $tableName) { - $flags = array(); - $tableName = $table; - - // get unique and primary keys - $res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC); - if (DB::isError($res)) { - return $res; - } - - foreach ($res as $val) { - $keys = explode(', ', $val['index_keys']); - - if (sizeof($keys) > 1) { - foreach ($keys as $key) { - $this->_add_flag($flags[$key], 'multiple_key'); - } - } - - if (strpos($val['index_description'], 'primary key')) { - foreach ($keys as $key) { - $this->_add_flag($flags[$key], 'primary_key'); - } - } elseif (strpos($val['index_description'], 'unique')) { - foreach ($keys as $key) { - $this->_add_flag($flags[$key], 'unique_key'); - } - } - } - - // get auto_increment, not_null and timestamp - $res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC); - if (DB::isError($res)) { - return $res; - } - - foreach ($res as $val) { - $val = array_change_key_case($val, CASE_LOWER); - if ($val['nullable'] == '0') { - $this->_add_flag($flags[$val['column_name']], 'not_null'); - } - if (strpos($val['type_name'], 'identity')) { - $this->_add_flag($flags[$val['column_name']], 'auto_increment'); - } - if (strpos($val['type_name'], 'timestamp')) { - $this->_add_flag($flags[$val['column_name']], 'timestamp'); - } - } - } - - if (array_key_exists($column, $flags)) { - return (implode(' ', $flags[$column])); - } - return ''; - } - - // }}} - // {{{ _add_flag() - - /** - * Adds a string to the flags array if the flag is not yet in there - * - if there is no flag present the array is created - * - * @param array &$array the reference to the flag-array - * @param string $value the flag value - * - * @return void - * - * @access private - * @author Joern Barthel - */ - public function _add_flag(&$array, $value) - { - if (!is_array($array)) { - $array = array($value); - } elseif (!in_array($value, $array)) { - array_push($array, $value); - } - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return "SELECT name FROM sysobjects WHERE type = 'U'" - . ' ORDER BY name'; - case 'views': - return "SELECT name FROM sysobjects WHERE type = 'V'"; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/mysql.php b/extlib/DB/mysql.php deleted file mode 100644 index a992e28ade..0000000000 --- a/extlib/DB/mysql.php +++ /dev/null @@ -1,1049 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's mysql extension - * for interacting with MySQL databases - * - * These methods overload the ones declared in DB_common. - * - * @category Database - * @package DB - * @author Stig Bakken - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_mysql extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'mysql'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'mysql'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'alter', - 'new_link' => '4.2.0', - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - 1004 => DB_ERROR_CANNOT_CREATE, - 1005 => DB_ERROR_CANNOT_CREATE, - 1006 => DB_ERROR_CANNOT_CREATE, - 1007 => DB_ERROR_ALREADY_EXISTS, - 1008 => DB_ERROR_CANNOT_DROP, - 1022 => DB_ERROR_ALREADY_EXISTS, - 1044 => DB_ERROR_ACCESS_VIOLATION, - 1046 => DB_ERROR_NODBSELECTED, - 1048 => DB_ERROR_CONSTRAINT, - 1049 => DB_ERROR_NOSUCHDB, - 1050 => DB_ERROR_ALREADY_EXISTS, - 1051 => DB_ERROR_NOSUCHTABLE, - 1054 => DB_ERROR_NOSUCHFIELD, - 1061 => DB_ERROR_ALREADY_EXISTS, - 1062 => DB_ERROR_ALREADY_EXISTS, - 1064 => DB_ERROR_SYNTAX, - 1091 => DB_ERROR_NOT_FOUND, - 1100 => DB_ERROR_NOT_LOCKED, - 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, - 1142 => DB_ERROR_ACCESS_VIOLATION, - 1146 => DB_ERROR_NOSUCHTABLE, - 1216 => DB_ERROR_CONSTRAINT, - 1217 => DB_ERROR_CONSTRAINT, - 1356 => DB_ERROR_DIVZERO, - 1451 => DB_ERROR_CONSTRAINT, - 1452 => DB_ERROR_CONSTRAINT, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The quantity of transactions begun - * - * {@internal While this is private, it can't actually be designated - * private in PHP 5 because it is directly accessed in the test suite.}} - * - * @var integer - * @access private - */ - public $transaction_opcount = 0; - - /** - * The database specified in the DSN - * - * It's a fix to allow calls to different databases in the same script. - * - * @var string - * @access private - */ - public $_db = ''; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's mysql driver supports the following extra DSN options: - * + new_link If set to true, causes subsequent calls to connect() - * to return a new connection link instead of the - * existing one. WARNING: this is not portable to - * other DBMS's. Available since PEAR DB 1.7.0. - * + client_flags Any combination of MYSQL_CLIENT_* constants. - * Only used if PHP is at version 4.3.0 or greater. - * Available since PEAR DB 1.7.0. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('mysql')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $params = array(); - if ($dsn['protocol'] && $dsn['protocol'] == 'unix') { - $params[0] = ':' . $dsn['socket']; - } else { - $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] - : 'localhost'; - if ($dsn['port']) { - $params[0] .= ':' . $dsn['port']; - } - } - $params[] = $dsn['username'] ? $dsn['username'] : null; - $params[] = $dsn['password'] ? $dsn['password'] : null; - - if (!$persistent) { - if (isset($dsn['new_link']) - && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) { - $params[] = true; - } else { - $params[] = false; - } - } - if (version_compare(phpversion(), '4.3.0', '>=')) { - $params[] = isset($dsn['client_flags']) - ? $dsn['client_flags'] : null; - } - - $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; - - $ini = ini_get('track_errors'); - $php_errormsg = ''; - if ($ini) { - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - } else { - @ini_set('track_errors', 1); - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - @ini_set('track_errors', $ini); - } - - if (!$this->connection) { - if (($err = @mysql_error()) != '') { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $err - ); - } else { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - } - - if ($dsn['database']) { - if (!@mysql_select_db($dsn['database'], $this->connection)) { - return $this->mysqlRaiseError(); - } - $this->_db = $dsn['database']; - } - - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_mysql::errorNative(), DB_common::errorCode() - */ - public function mysqlRaiseError($errno = null) - { - if ($errno === null) { - if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { - $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; - $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; - } else { - // Doing this in case mode changes during runtime. - $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; - } - $errno = $this->errorCode(mysql_errno($this->connection)); - } - return $this->raiseError( - $errno, - null, - null, - null, - @mysql_errno($this->connection) . ' ** ' . - @mysql_error($this->connection) - ); - } - - // }}} - // {{{ simpleQuery() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @mysql_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ nextResult() - - /** - * Sends a query to the database server - * - * Generally uses mysql_query(). If you want to use - * mysql_unbuffered_query() set the "result_buffering" option to 0 using - * setOptions(). This option was added in Release 1.7.0. - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - $query = $this->modifyQuery($query); - if ($this->_db) { - if (!@mysql_select_db($this->_db, $this->connection)) { - return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); - } - } - if (!$this->autocommit && $ismanip) { - if ($this->transaction_opcount == 0) { - $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); - $result = @mysql_query('BEGIN', $this->connection); - if (!$result) { - return $this->mysqlRaiseError(); - } - } - $this->transaction_opcount++; - } - if (!$this->options['result_buffering']) { - $result = @mysql_unbuffered_query($query, $this->connection); - } else { - $result = @mysql_query($query, $this->connection); - } - if (!$result) { - return $this->mysqlRaiseError(); - } - if (is_resource($result)) { - return $result; - } - return DB_OK; - } - - // }}} - // {{{ fetchInto() - - /** - * Changes a query string for various DBMS specific reasons - * - * This little hack lets you know how many rows were deleted - * when running a "DELETE FROM table" query. Only implemented - * if the DB_PORTABILITY_DELETE_COUNT portability option is on. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - * @see DB_common::setOption() - */ - public function modifyQuery($query) - { - if ($this->options['portability'] & DB_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 - ); - } - } - return $query; - } - - // }}} - // {{{ freeResult() - - /** - * Move the internal mysql result pointer to the next available result - * - * This method has not been implemented yet. - * - * @param a valid sql result resource - * - * @return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ numCols() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@mysql_data_seek($result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @mysql_fetch_array($result, MYSQL_ASSOC); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @mysql_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - /* - * Even though this DBMS already trims output, we do this because - * a field might have intentional whitespace at the end that - * gets removed by DB_PORTABILITY_RTRIM under another driver. - */ - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ numRows() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? mysql_free_result($result) : false; - } - - // }}} - // {{{ autoCommit() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @mysql_num_fields($result); - if (!$cols) { - return $this->mysqlRaiseError(); - } - return $cols; - } - - // }}} - // {{{ commit() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @mysql_num_rows($result); - if ($rows === null) { - return $this->mysqlRaiseError(); - } - return $rows; - } - - // }}} - // {{{ rollback() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - // XXX if $this->transaction_opcount > 0, we should probably - // issue a warning here. - $this->autocommit = $onoff ? true : false; - return DB_OK; - } - - // }}} - // {{{ affectedRows() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if ($this->transaction_opcount > 0) { - if ($this->_db) { - if (!@mysql_select_db($this->_db, $this->connection)) { - return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); - } - } - $result = @mysql_query('COMMIT', $this->connection); - $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->mysqlRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ nextId() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if ($this->transaction_opcount > 0) { - if ($this->_db) { - if (!@mysql_select_db($this->_db, $this->connection)) { - return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); - } - } - $result = @mysql_query('ROLLBACK', $this->connection); - $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->mysqlRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ createSequence() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if ($this->_last_query_manip) { - return @mysql_affected_rows($this->connection); - } else { - return 0; - } - } - - // }}} - // {{{ dropSequence() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_mysql::createSequence(), DB_mysql::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - do { - $repeat = 0; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("UPDATE ${seqname} " . - 'SET id=LAST_INSERT_ID(id+1)'); - $this->popErrorHandling(); - if ($result === DB_OK) { - // COMMON CASE - $id = @mysql_insert_id($this->connection); - if ($id != 0) { - return $id; - } - // EMPTY SEQ TABLE - // Sequence table must be empty for some reason, so fill - // it and return 1 and obtain a user-level lock - $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); - if (DB::isError($result)) { - return $this->raiseError($result); - } - if ($result == 0) { - // Failed to get the lock - return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); - } - - // add the default value - $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)"); - if (DB::isError($result)) { - return $this->raiseError($result); - } - - // Release the lock - $result = $this->getOne('SELECT RELEASE_LOCK(' - . "'${seqname}_lock')"); - if (DB::isError($result)) { - return $this->raiseError($result); - } - // We know what the result will be, so no need to try again - return 1; - } elseif ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - // ONDEMAND TABLE CREATION - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $this->raiseError($result); - } else { - $repeat = 1; - } - } elseif (DB::isError($result) && - $result->getCode() == DB_ERROR_ALREADY_EXISTS) { - // BACKWARDS COMPAT - // see _BCsequence() comment - $result = $this->_BCsequence($seqname); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $repeat = 1; - } - } while ($repeat); - - return $this->raiseError($result); - } - - // }}} - // {{{ _BCsequence() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_mysql::nextID(), DB_mysql::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' - . ' PRIMARY KEY(id))'); - if (DB::isError($res)) { - return $res; - } - // insert yields value 1, nextId call will generate ID 2 - $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); - if (DB::isError($res)) { - return $res; - } - // so reset to zero - return $this->query("UPDATE ${seqname} SET id = 0"); - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Backwards compatibility with old sequence emulation implementation - * (clean up the dupes) - * - * @param string $seqname the sequence name to clean up - * - * @return bool|object - * - * @access private - */ - public function _BCsequence($seqname) - { - // Obtain a user-level lock... this will release any previous - // application locks, but unlike LOCK TABLES, it does not abort - // the current transaction and is much less frequently used. - $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); - if (DB::isError($result)) { - return $result; - } - if ($result == 0) { - // Failed to get the lock, can't do the conversion, bail - // with a DB_ERROR_NOT_LOCKED error - return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); - } - - $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); - if (DB::isError($highest_id)) { - return $highest_id; - } - // This should kill all rows except the highest - // We should probably do something if $highest_id isn't - // numeric, but I'm at a loss as how to handle that... - $result = $this->query('DELETE FROM ' . $seqname - . " WHERE id <> $highest_id"); - if (DB::isError($result)) { - return $result; - } - - // If another thread has been waiting for this lock, - // it will go thru the above procedure, but will have no - // real effect - $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); - if (DB::isError($result)) { - return $result; - } - return true; - } - - // }}} - // {{{ escapeSimple() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_mysql::nextID(), DB_mysql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ modifyQuery() - - /** - * Quotes a string so it can be safely used as a table or column name - * (WARNING: using names that require this is a REALLY BAD IDEA) - * - * WARNING: Older versions of MySQL can't handle the backtick - * character (`) in table or column names. - * - * @param string $str identifier name to be quoted - * - * @return string quoted identifier string - * - * @see DB_common::quoteIdentifier() - * @since Method available since Release 1.6.0 - */ - public function quoteIdentifier($str) - { - return '`' . str_replace('`', '``', $str) . '`'; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Escapes a string according to the current DBMS's standards - * - * @param string $str the string to be escaped - * - * @return string the escaped string - * - * @see DB_common::quoteSmart() - * @since Method available since Release 1.6.0 - */ - public function escapeSimple($str) - { - if (function_exists('mysql_real_escape_string')) { - return @mysql_real_escape_string($str, $this->connection); - } else { - return @mysql_escape_string($str); - } - } - - // }}} - // {{{ mysqlRaiseError() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - if (DB::isManip($query) || $this->_next_query_manip) { - return $query . " LIMIT $count"; - } else { - return $query . " LIMIT $from, $count"; - } - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code - */ - public function errorNative() - { - return @mysql_errno($this->connection); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - // Fix for bug #11580. - if ($this->_db) { - if (!@mysql_select_db($this->_db, $this->connection)) { - return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); - } - } - - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @mysql_query( - "SELECT * FROM $result LIMIT 0", - $this->connection - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @mysql_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => $case_func(@mysql_field_table($id, $i)), - 'name' => $case_func(@mysql_field_name($id, $i)), - 'type' => @mysql_field_type($id, $i), - 'len' => @mysql_field_len($id, $i), - 'flags' => @mysql_field_flags($id, $i), - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @mysql_free_result($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SHOW TABLES'; - case 'users': - return 'SELECT DISTINCT User FROM mysql.user'; - case 'databases': - return 'SHOW DATABASES'; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/mysqli.php b/extlib/DB/mysqli.php deleted file mode 100644 index cc59c11570..0000000000 --- a/extlib/DB/mysqli.php +++ /dev/null @@ -1,1114 +0,0 @@ - - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's mysqli extension - * for interacting with MySQL databases - * - * This is for MySQL versions 4.1 and above. Requires PHP 5. - * - * Note that persistent connections no longer exist. - * - * These methods overload the ones declared in DB_common. - * - * @category Database - * @package DB - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - * @since Class functional since Release 1.6.3 - */ -class DB_mysqli extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'mysqli'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'mysqli'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'alter', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => false, - 'prepare' => false, - 'ssl' => true, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - 1004 => DB_ERROR_CANNOT_CREATE, - 1005 => DB_ERROR_CANNOT_CREATE, - 1006 => DB_ERROR_CANNOT_CREATE, - 1007 => DB_ERROR_ALREADY_EXISTS, - 1008 => DB_ERROR_CANNOT_DROP, - 1022 => DB_ERROR_ALREADY_EXISTS, - 1044 => DB_ERROR_ACCESS_VIOLATION, - 1046 => DB_ERROR_NODBSELECTED, - 1048 => DB_ERROR_CONSTRAINT, - 1049 => DB_ERROR_NOSUCHDB, - 1050 => DB_ERROR_ALREADY_EXISTS, - 1051 => DB_ERROR_NOSUCHTABLE, - 1054 => DB_ERROR_NOSUCHFIELD, - 1061 => DB_ERROR_ALREADY_EXISTS, - 1062 => DB_ERROR_ALREADY_EXISTS, - 1064 => DB_ERROR_SYNTAX, - 1091 => DB_ERROR_NOT_FOUND, - 1100 => DB_ERROR_NOT_LOCKED, - 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, - 1142 => DB_ERROR_ACCESS_VIOLATION, - 1146 => DB_ERROR_NOSUCHTABLE, - 1216 => DB_ERROR_CONSTRAINT, - 1217 => DB_ERROR_CONSTRAINT, - 1356 => DB_ERROR_DIVZERO, - 1451 => DB_ERROR_CONSTRAINT, - 1452 => DB_ERROR_CONSTRAINT, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The quantity of transactions begun - * - * {@internal While this is private, it can't actually be designated - * private in PHP 5 because it is directly accessed in the test suite.}} - * - * @var integer - * @access private - */ - public $transaction_opcount = 0; - - /** - * The database specified in the DSN - * - * It's a fix to allow calls to different databases in the same script. - * - * @var string - * @access private - */ - public $_db = ''; - - /** - * Array for converting MYSQLI_*_FLAG constants to text values - * @var array - * @access public - * @since Property available since Release 1.6.5 - */ - public $mysqli_flags = array( - MYSQLI_NOT_NULL_FLAG => 'not_null', - MYSQLI_PRI_KEY_FLAG => 'primary_key', - MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', - MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', - MYSQLI_BLOB_FLAG => 'blob', - MYSQLI_UNSIGNED_FLAG => 'unsigned', - MYSQLI_ZEROFILL_FLAG => 'zerofill', - MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', - MYSQLI_TIMESTAMP_FLAG => 'timestamp', - MYSQLI_SET_FLAG => 'set', - // MYSQLI_NUM_FLAG => 'numeric', // unnecessary - // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie - MYSQLI_GROUP_FLAG => 'group_by' - ); - - /** - * Array for converting MYSQLI_TYPE_* constants to text values - * @var array - * @access public - * @since Property available since Release 1.6.5 - */ - public $mysqli_types = array( - MYSQLI_TYPE_DECIMAL => 'decimal', - MYSQLI_TYPE_TINY => 'tinyint', - MYSQLI_TYPE_SHORT => 'int', - MYSQLI_TYPE_LONG => 'int', - MYSQLI_TYPE_FLOAT => 'float', - MYSQLI_TYPE_DOUBLE => 'double', - // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it - MYSQLI_TYPE_TIMESTAMP => 'timestamp', - MYSQLI_TYPE_LONGLONG => 'bigint', - MYSQLI_TYPE_INT24 => 'mediumint', - MYSQLI_TYPE_DATE => 'date', - MYSQLI_TYPE_TIME => 'time', - MYSQLI_TYPE_DATETIME => 'datetime', - MYSQLI_TYPE_YEAR => 'year', - MYSQLI_TYPE_NEWDATE => 'date', - MYSQLI_TYPE_ENUM => 'enum', - MYSQLI_TYPE_SET => 'set', - MYSQLI_TYPE_TINY_BLOB => 'tinyblob', - MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob', - MYSQLI_TYPE_LONG_BLOB => 'longblob', - MYSQLI_TYPE_BLOB => 'blob', - MYSQLI_TYPE_VAR_STRING => 'varchar', - MYSQLI_TYPE_STRING => 'char', - MYSQLI_TYPE_GEOMETRY => 'geometry', - /* These constants are conditionally compiled in ext/mysqli, so we'll - * define them by number rather than constant. */ - 16 => 'bit', - 246 => 'decimal', - ); - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's mysqli driver supports the following extra DSN options: - * + When the 'ssl' $option passed to DB::connect() is true: - * + key The path to the key file. - * + cert The path to the certificate file. - * + ca The path to the certificate authority file. - * + capath The path to a directory that contains trusted SSL - * CA certificates in pem format. - * + cipher The list of allowable ciphers for SSL encryption. - * - * Example of how to connect using SSL: - * - * require_once 'DB.php'; - * - * $dsn = array( - * 'phptype' => 'mysqli', - * 'username' => 'someuser', - * 'password' => 'apasswd', - * 'hostspec' => 'localhost', - * 'database' => 'thedb', - * 'key' => 'client-key.pem', - * 'cert' => 'client-cert.pem', - * 'ca' => 'cacert.pem', - * 'capath' => '/path/to/ca/dir', - * 'cipher' => 'AES', - * ); - * - * $options = array( - * 'ssl' => true, - * ); - * - * $db = DB::connect($dsn, $options); - * if ((new PEAR)->isError($db)) { - * die($db->getMessage()); - * } - * - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('mysqli')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $ini = ini_get('track_errors'); - @ini_set('track_errors', 1); - $php_errormsg = ''; - - if (((int)$this->getOption('ssl')) === 1) { - $init = mysqli_init(); - mysqli_ssl_set( - $init, - empty($dsn['key']) ? null : $dsn['key'], - empty($dsn['cert']) ? null : $dsn['cert'], - empty($dsn['ca']) ? null : $dsn['ca'], - empty($dsn['capath']) ? null : $dsn['capath'], - empty($dsn['cipher']) ? null : $dsn['cipher'] - ); - if ($this->connection = @mysqli_real_connect( - $init, - $dsn['hostspec'], - $dsn['username'], - $dsn['password'], - $dsn['database'], - $dsn['port'], - $dsn['socket'] - )) { - $this->connection = $init; - } - } else { - $this->connection = @mysqli_connect( - $dsn['hostspec'], - $dsn['username'], - $dsn['password'], - $dsn['database'], - $dsn['port'], - $dsn['socket'] - ); - } - - @ini_set('track_errors', $ini); - - if (!$this->connection) { - if (($err = @mysqli_connect_error()) != '') { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $err - ); - } else { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - } - - if ($dsn['database']) { - $this->_db = $dsn['database']; - } - - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @mysqli_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ simpleQuery() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - $query = $this->modifyQuery($query); - if ($this->_db) { - if (!@mysqli_select_db($this->connection, $this->_db)) { - return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); - } - } - if (!$this->autocommit && $ismanip) { - if ($this->transaction_opcount == 0) { - $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=0'); - $result = @mysqli_query($this->connection, 'BEGIN'); - if (!$result) { - return $this->mysqliRaiseError(); - } - } - $this->transaction_opcount++; - } - $result = @mysqli_query($this->connection, $query); - if (!$result) { - return $this->mysqliRaiseError(); - } - if (is_object($result)) { - return $result; - } - return DB_OK; - } - - // }}} - // {{{ nextResult() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_mysqli::errorNative(), DB_common::errorCode() - */ - public function mysqliRaiseError($errno = null) - { - if ($errno === null) { - if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { - $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; - $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; - } else { - // Doing this in case mode changes during runtime. - $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; - $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; - $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; - } - $errno = $this->errorCode(mysqli_errno($this->connection)); - } - return $this->raiseError( - $errno, - null, - null, - null, - @mysqli_errno($this->connection) . ' ** ' . - @mysqli_error($this->connection) - ); - } - - // }}} - // {{{ fetchInto() - - /** - * Move the internal mysql result pointer to the next available result. - * - * This method has not been implemented yet. - * - * @param resource $result a valid sql result resource - * @return false - * @access public - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ freeResult() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@mysqli_data_seek($result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @mysqli_fetch_array($result, MYSQLI_ASSOC); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @mysqli_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - /* - * Even though this DBMS already trims output, we do this because - * a field might have intentional whitespace at the end that - * gets removed by DB_PORTABILITY_RTRIM under another driver. - */ - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ numCols() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - if (!$result instanceof mysqli_result) { - return false; - } - mysqli_free_result($result); - return true; - } - - // }}} - // {{{ numRows() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @mysqli_num_fields($result); - if (!$cols) { - return $this->mysqliRaiseError(); - } - return $cols; - } - - // }}} - // {{{ autoCommit() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @mysqli_num_rows($result); - if ($rows === null) { - return $this->mysqliRaiseError(); - } - return $rows; - } - - // }}} - // {{{ commit() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - // XXX if $this->transaction_opcount > 0, we should probably - // issue a warning here. - $this->autocommit = $onoff ? true : false; - return DB_OK; - } - - // }}} - // {{{ rollback() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if ($this->transaction_opcount > 0) { - if ($this->_db) { - if (!@mysqli_select_db($this->connection, $this->_db)) { - return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); - } - } - $result = @mysqli_query($this->connection, 'COMMIT'); - $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1'); - $this->transaction_opcount = 0; - if (!$result) { - return $this->mysqliRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ affectedRows() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if ($this->transaction_opcount > 0) { - if ($this->_db) { - if (!@mysqli_select_db($this->connection, $this->_db)) { - return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); - } - } - $result = @mysqli_query($this->connection, 'ROLLBACK'); - $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1'); - $this->transaction_opcount = 0; - if (!$result) { - return $this->mysqliRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ nextId() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if ($this->_last_query_manip) { - return @mysqli_affected_rows($this->connection); - } else { - return 0; - } - } - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_mysqli::createSequence(), DB_mysqli::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - do { - $repeat = 0; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query('UPDATE ' . $seqname - . ' SET id = LAST_INSERT_ID(id + 1)'); - $this->popErrorHandling(); - if ($result === DB_OK) { - // COMMON CASE - $id = @mysqli_insert_id($this->connection); - if ($id != 0) { - return $id; - } - - // EMPTY SEQ TABLE - // Sequence table must be empty for some reason, - // so fill it and return 1 - // Obtain a user-level lock - $result = $this->getOne('SELECT GET_LOCK(' - . "'${seqname}_lock', 10)"); - if (DB::isError($result)) { - return $this->raiseError($result); - } - if ($result == 0) { - return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED); - } - - // add the default value - $result = $this->query('REPLACE INTO ' . $seqname - . ' (id) VALUES (0)'); - if (DB::isError($result)) { - return $this->raiseError($result); - } - - // Release the lock - $result = $this->getOne('SELECT RELEASE_LOCK(' - . "'${seqname}_lock')"); - if (DB::isError($result)) { - return $this->raiseError($result); - } - // We know what the result will be, so no need to try again - return 1; - } elseif ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - // ONDEMAND TABLE CREATION - $result = $this->createSequence($seq_name); - - // Since createSequence initializes the ID to be 1, - // we do not need to retrieve the ID again (or we will get 2) - if (DB::isError($result)) { - return $this->raiseError($result); - } else { - // First ID of a newly created sequence is 1 - return 1; - } - } elseif (DB::isError($result) && - $result->getCode() == DB_ERROR_ALREADY_EXISTS) { - // BACKWARDS COMPAT - // see _BCsequence() comment - $result = $this->_BCsequence($seqname); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $repeat = 1; - } - } while ($repeat); - - return $this->raiseError($result); - } - - // }}} - // {{{ dropSequence() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_mysqli::nextID(), DB_mysqli::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $res = $this->query('CREATE TABLE ' . $seqname - . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' - . ' PRIMARY KEY(id))'); - if (DB::isError($res)) { - return $res; - } - // insert yields value 1, nextId call will generate ID 2 - return $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); - } - - // }}} - // {{{ _BCsequence() - - /** - * Backwards compatibility with old sequence emulation implementation - * (clean up the dupes) - * - * @param string $seqname the sequence name to clean up - * - * @return bool|object - * - * @access private - */ - public function _BCsequence($seqname) - { - // Obtain a user-level lock... this will release any previous - // application locks, but unlike LOCK TABLES, it does not abort - // the current transaction and is much less frequently used. - $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); - if (DB::isError($result)) { - return $result; - } - if ($result == 0) { - // Failed to get the lock, can't do the conversion, bail - // with a DB_ERROR_NOT_LOCKED error - return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED); - } - - $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); - if (DB::isError($highest_id)) { - return $highest_id; - } - - // This should kill all rows except the highest - // We should probably do something if $highest_id isn't - // numeric, but I'm at a loss as how to handle that... - $result = $this->query('DELETE FROM ' . $seqname - . " WHERE id <> $highest_id"); - if (DB::isError($result)) { - return $result; - } - - // If another thread has been waiting for this lock, - // it will go thru the above procedure, but will have no - // real effect - $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); - if (DB::isError($result)) { - return $result; - } - return true; - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_mysql::nextID(), DB_mysql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ escapeSimple() - - /** - * Quotes a string so it can be safely used as a table or column name - * (WARNING: using names that require this is a REALLY BAD IDEA) - * - * WARNING: Older versions of MySQL can't handle the backtick - * character (`) in table or column names. - * - * @param string $str identifier name to be quoted - * - * @return string quoted identifier string - * - * @see DB_common::quoteIdentifier() - * @since Method available since Release 1.6.0 - */ - public function quoteIdentifier($str) - { - return '`' . str_replace('`', '``', $str) . '`'; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Escapes a string according to the current DBMS's standards - * - * @param string $str the string to be escaped - * - * @return string the escaped string - * - * @see DB_common::quoteSmart() - * @since Method available since Release 1.6.0 - */ - public function escapeSimple($str) - { - return @mysqli_real_escape_string($this->connection, $str); - } - - // }}} - // {{{ mysqliRaiseError() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - if (DB::isManip($query) || $this->_next_query_manip) { - return $query . " LIMIT $count"; - } else { - return $query . " LIMIT $from, $count"; - } - } - - // }}} - // {{{ errorNative() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code - */ - public function errorNative() - { - return @mysqli_errno($this->connection); - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::setOption() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - // Fix for bug #11580. - if ($this->_db) { - if (!@mysqli_select_db($this->connection, $this->_db)) { - return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); - } - } - - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @mysqli_query( - $this->connection, - "SELECT * FROM $result LIMIT 0" - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_object($id) || !is_a($id, 'mysqli_result')) { - return $this->mysqliRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @mysqli_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $tmp = @mysqli_fetch_field($id); - - $flags = ''; - foreach ($this->mysqli_flags as $const => $means) { - if ($tmp->flags & $const) { - $flags .= $means . ' '; - } - } - if ($tmp->def) { - $flags .= 'default_' . rawurlencode($tmp->def); - } - $flags = trim($flags); - - $res[$i] = array( - 'table' => $case_func($tmp->table), - 'name' => $case_func($tmp->name), - 'type' => isset($this->mysqli_types[$tmp->type]) - ? $this->mysqli_types[$tmp->type] - : 'unknown', - // http://bugs.php.net/?id=36579 - // Doc Bug #36579: mysqli_fetch_field length handling - // https://bugs.php.net/bug.php?id=62426 - // Bug #62426: mysqli_fetch_field_direct returns incorrect - // length on UTF8 fields - 'len' => $tmp->length, - 'flags' => $flags, - ); - - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @mysqli_free_result($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SHOW TABLES'; - case 'users': - return 'SELECT DISTINCT User FROM mysql.user'; - case 'databases': - return 'SHOW DATABASES'; - default: - return null; - } - } - - public function getVersion() - { - return mysqli_get_server_version($this->connection); - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/oci8.php b/extlib/DB/oci8.php deleted file mode 100644 index 8fba3cb0f2..0000000000 --- a/extlib/DB/oci8.php +++ /dev/null @@ -1,1182 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's oci8 extension - * for interacting with Oracle databases - * - * Definitely works with versions 8 and 9 of Oracle. - * - * These methods overload the ones declared in DB_common. - * - * Be aware... OCIError() only appears to return anything when given a - * statement, so functions return the generic DB_ERROR instead of more - * useful errors that have to do with feedback from the database. - * - * @category Database - * @package DB - * @author James L. Pine - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_oci8 extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'oci8'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'oci8'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'alter', - 'new_link' => '5.0.0', - 'numrows' => 'subquery', - 'pconnect' => true, - 'prepare' => true, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - 1 => DB_ERROR_CONSTRAINT, - 900 => DB_ERROR_SYNTAX, - 904 => DB_ERROR_NOSUCHFIELD, - 913 => DB_ERROR_VALUE_COUNT_ON_ROW, - 921 => DB_ERROR_SYNTAX, - 923 => DB_ERROR_SYNTAX, - 942 => DB_ERROR_NOSUCHTABLE, - 955 => DB_ERROR_ALREADY_EXISTS, - 1400 => DB_ERROR_CONSTRAINT_NOT_NULL, - 1401 => DB_ERROR_INVALID, - 1407 => DB_ERROR_CONSTRAINT_NOT_NULL, - 1418 => DB_ERROR_NOT_FOUND, - 1476 => DB_ERROR_DIVZERO, - 1722 => DB_ERROR_INVALID_NUMBER, - 2289 => DB_ERROR_NOSUCHTABLE, - 2291 => DB_ERROR_CONSTRAINT, - 2292 => DB_ERROR_CONSTRAINT, - 2449 => DB_ERROR_CONSTRAINT, - 12899 => DB_ERROR_INVALID, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * Stores the $data passed to execute() in the oci8 driver - * - * Gets reset to array() when simpleQuery() is run. - * - * Needed in case user wants to call numRows() after prepare/execute - * was used. - * - * @var array - * @access private - */ - public $_data = array(); - - /** - * The result or statement handle from the most recently executed query - * @var resource - */ - public $last_stmt; - - /** - * Is the given prepared statement a data manipulation query? - * @var array - * @access private - */ - public $manip_query = array(); - - /** - * Store of prepared SQL queries. - * @var array - * @access private - */ - public $_prepared_queries = array(); - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * If PHP is at version 5.0.0 or greater: - * + Generally, oci_connect() or oci_pconnect() are used. - * + But if the new_link DSN option is set to true, oci_new_connect() - * is used. - * - * When using PHP version 4.x, OCILogon() or OCIPLogon() are used. - * - * PEAR DB's oci8 driver supports the following extra DSN options: - * + charset The character set to be used on the connection. - * Only used if PHP is at version 5.0.0 or greater - * and the Oracle server is at 9.2 or greater. - * Available since PEAR DB 1.7.0. - * + new_link If set to true, causes subsequent calls to - * connect() to return a new connection link - * instead of the existing one. WARNING: this is - * not portable to other DBMS's. - * Available since PEAR DB 1.7.0. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('oci8')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - // Backwards compatibility with DB < 1.7.0 - if (empty($dsn['database']) && !empty($dsn['hostspec'])) { - $db = $dsn['hostspec']; - } else { - $db = $dsn['database']; - } - - if (function_exists('oci_connect')) { - if (isset($dsn['new_link']) - && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) { - $connect_function = 'oci_new_connect'; - } else { - $connect_function = $persistent ? 'oci_pconnect' - : 'oci_connect'; - } - if (isset($this->dsn['port']) && $this->dsn['port']) { - $db = '//' . $db . ':' . $this->dsn['port']; - } - - $char = empty($dsn['charset']) ? null : $dsn['charset']; - $this->connection = @$connect_function( - $dsn['username'], - $dsn['password'], - $db, - $char - ); - $error = OCIError(); - if (!empty($error) && $error['code'] == 12541) { - // Couldn't find TNS listener. Try direct connection. - $this->connection = @$connect_function( - $dsn['username'], - $dsn['password'], - null, - $char - ); - } - } else { - $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon'; - if ($db) { - $this->connection = @$connect_function( - $dsn['username'], - $dsn['password'], - $db - ); - } elseif ($dsn['username'] || $dsn['password']) { - $this->connection = @$connect_function( - $dsn['username'], - $dsn['password'] - ); - } - } - - if (!$this->connection) { - $error = OCIError(); - $error = (is_array($error)) ? $error['message'] : null; - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $error - ); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - if (function_exists('oci_close')) { - $ret = @oci_close($this->connection); - } else { - $ret = @OCILogOff($this->connection); - } - $this->connection = null; - return $ret; - } - - // }}} - // {{{ simpleQuery() - - /** - * Sends a query to the database server - * - * To determine how many rows of a result set get buffered using - * ocisetprefetch(), see the "result_buffering" option in setOptions(). - * This option was added in Release 1.7.0. - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $this->_data = array(); - $this->last_parameters = array(); - $this->last_query = $query; - $query = $this->modifyQuery($query); - $result = @OCIParse($this->connection, $query); - if (!$result) { - return $this->oci8RaiseError(); - } - if ($this->autocommit) { - $success = @OCIExecute($result, OCI_COMMIT_ON_SUCCESS); - } else { - $success = @OCIExecute($result, OCI_DEFAULT); - } - if (!$success) { - return $this->oci8RaiseError($result); - } - $this->last_stmt = $result; - if ($this->_checkManip($query)) { - return DB_OK; - } else { - @ocisetprefetch($result, $this->options['result_buffering']); - return $result; - } - } - - // }}} - // {{{ nextResult() - - /** - * Changes a query string for various DBMS specific reasons - * - * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - */ - public function modifyQuery($query) - { - if (preg_match('/^\s*SELECT/i', $query) && - !preg_match('/\sFROM\s/i', $query)) { - $query .= ' FROM dual'; - } - return $query; - } - - // }}} - // {{{ fetchInto() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_oci8::errorNative(), DB_oci8::errorCode() - */ - public function oci8RaiseError($errno = null) - { - if ($errno === null) { - $error = @OCIError($this->connection); - return $this->raiseError( - $this->errorCode($error['code']), - null, - null, - null, - $error['message'] - ); - } elseif (is_resource($errno)) { - $error = @OCIError($errno); - return $this->raiseError( - $this->errorCode($error['code']), - null, - null, - null, - $error['message'] - ); - } - return $this->raiseError($this->errorCode($errno)); - } - - // }}} - // {{{ freeResult() - - /** - * Move the internal oracle result pointer to the next available result - * - * @param a valid oci8 result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $moredata = @OCIFetchInto($result, $arr, OCI_ASSOC + OCI_RETURN_NULLS + OCI_RETURN_LOBS); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && - $moredata) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $moredata = OCIFetchInto($result, $arr, OCI_RETURN_NULLS + OCI_RETURN_LOBS); - } - if (!$moredata) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ numRows() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? OCIFreeStatement($result) : false; - } - - // }}} - // {{{ numCols() - - /** - * Frees the internal resources associated with a prepared query - * - * @param resource $stmt the prepared statement's resource - * @param bool $free_resource should the PHP resource be freed too? - * Use false if you need to get data - * from the result set later. - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_oci8::prepare() - */ - public function freePrepared($stmt, $free_resource = true) - { - if (!is_resource($stmt)) { - return false; - } - if ($free_resource) { - @ocifreestatement($stmt); - } - if (isset($this->prepare_types[(int)$stmt])) { - unset($this->prepare_types[(int)$stmt]); - unset($this->manip_query[(int)$stmt]); - unset($this->_prepared_queries[(int)$stmt]); - } else { - return false; - } - return true; - } - - // }}} - // {{{ prepare() - - /** - * Gets the number of rows in a result set - * - * Only works if the DB_PORTABILITY_NUMROWS portability option - * is turned on. - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows(), DB_common::setOption() - */ - public function numRows($result) - { - // emulate numRows for Oracle. yuck. - if ($this->options['portability'] & DB_PORTABILITY_NUMROWS && - $result === $this->last_stmt) { - $countquery = 'SELECT COUNT(*) FROM (' . $this->last_query . ')'; - $save_query = $this->last_query; - $save_stmt = $this->last_stmt; - - $count = $this->query($countquery); - - // Restore the last query and statement. - $this->last_query = $save_query; - $this->last_stmt = $save_stmt; - - if (DB::isError($count) || - DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED))) { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - return $row[0]; - } - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - - // }}} - // {{{ execute() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @OCINumCols($result); - if (!$cols) { - return $this->oci8RaiseError($result); - } - return $cols; - } - - // }}} - // {{{ autoCommit() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - $this->autocommit = (bool)$onoff;; - return DB_OK; - } - - // }}} - // {{{ commit() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - $result = @OCICommit($this->connection); - if (!$result) { - return $this->oci8RaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ rollback() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - $result = @OCIRollback($this->connection); - if (!$result) { - return $this->oci8RaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ affectedRows() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int|object - */ - public function affectedRows() - { - if ($this->last_stmt === false) { - return $this->oci8RaiseError(); - } - $result = @OCIRowCount($this->last_stmt); - if ($result === false) { - return $this->oci8RaiseError($this->last_stmt); - } - return $result; - } - - // }}} - // {{{ modifyQuery() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - // Let Oracle return the name of the columns instead of - // coding a "home" SQL parser - - if (count($params)) { - $result = $this->prepare("SELECT * FROM ($query) " - . 'WHERE NULL = NULL'); - $tmp = $this->execute($result, $params); - } else { - $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL"; - - if (!$result = @OCIParse($this->connection, $q_fields)) { - $this->last_query = $q_fields; - return $this->oci8RaiseError(); - } - if (!@OCIExecute($result, OCI_DEFAULT)) { - $this->last_query = $q_fields; - return $this->oci8RaiseError($result); - } - } - - $ncols = OCINumCols($result); - $cols = array(); - for ($i = 1; $i <= $ncols; $i++) { - $cols[] = '"' . OCIColumnName($result, $i) . '"'; - } - $fields = implode(', ', $cols); - // XXX Test that (tip by John Lim) - //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) { - // // Introduce the FIRST_ROWS Oracle query optimizer - // $query = substr($query, strlen($match[0]), strlen($query)); - // $query = "SELECT /* +FIRST_ROWS */ " . $query; - //} - - // Construct the query - // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2 - // Perhaps this could be optimized with the use of Unions - $query = "SELECT $fields FROM" . - " (SELECT rownum as linenum, $fields FROM" . - " ($query)" . - ' WHERE rownum <= ' . ($from + $count) . - ') WHERE linenum >= ' . ++$from; - return $query; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Prepares a query for multiple execution with execute(). - * - * With oci8, this is emulated. - * - * prepare() requires a generic query as string like - * INSERT INTO numbers VALUES (?, ?, ?) - * . The ? characters are placeholders. - * - * Three types of placeholders can be used: - * + ? a quoted scalar value, i.e. strings, integers - * + ! value is inserted 'as is' - * + & requires a file name. The file's contents get - * inserted into the query (i.e. saving binary - * data in a db) - * - * Use backslashes to escape placeholder characters if you don't want - * them to be interpreted as placeholders. Example: - * "UPDATE foo SET col=? WHERE col='over \& under'" - * - * - * @param string $query the query to be prepared - * - * @return mixed DB statement resource on success. DB_Error on failure. - * - * @see DB_oci8::execute() - */ - public function prepare($query) - { - $tokens = preg_split( - '/((? $val) { - switch ($val) { - case '?': - $types[$token++] = DB_PARAM_SCALAR; - unset($tokens[$key]); - break; - case '&': - $types[$token++] = DB_PARAM_OPAQUE; - unset($tokens[$key]); - break; - case '!': - $types[$token++] = DB_PARAM_MISC; - unset($tokens[$key]); - break; - default: - $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val); - if ($key != $binds) { - $newquery .= $tokens[$key] . ':bind' . $token; - } else { - $newquery .= $tokens[$key]; - } - } - } - - $this->last_query = $query; - $newquery = $this->modifyQuery($newquery); - if (!$stmt = @OCIParse($this->connection, $newquery)) { - return $this->oci8RaiseError(); - } - $this->prepare_types[(int)$stmt] = $types; - $this->manip_query[(int)$stmt] = DB::isManip($query); - $this->_prepared_queries[(int)$stmt] = $newquery; - return $stmt; - } - - // }}} - // {{{ nextId() - - /** - * Executes a DB statement prepared with prepare(). - * - * To determine how many rows of a result set get buffered using - * ocisetprefetch(), see the "result_buffering" option in setOptions(). - * This option was added in Release 1.7.0. - * - * @param resource $stmt a DB statement resource returned from prepare() - * @param mixed $data array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 for non-array items or the - * quantity of elements in the array. - * - * @return mixed returns an oic8 result resource for successful SELECT - * queries, DB_OK for other successful queries. - * A DB error object is returned on failure. - * - * @see DB_oci8::prepare() - */ - public function &execute($stmt, $data = array()) - { - $data = (array)$data; - $this->last_parameters = $data; - $this->last_query = $this->_prepared_queries[(int)$stmt]; - $this->_data = $data; - - $types = $this->prepare_types[(int)$stmt]; - if (count($types) != count($data)) { - $tmp = $this->raiseError(DB_ERROR_MISMATCH); - return $tmp; - } - - $i = 0; - foreach ($data as $key => $value) { - if ($types[$i] == DB_PARAM_MISC) { - /* - * Oracle doesn't seem to have the ability to pass a - * parameter along unchanged, so strip off quotes from start - * and end, plus turn two single quotes to one single quote, - * in order to avoid the quotes getting escaped by - * Oracle and ending up in the database. - */ - $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]); - $data[$key] = str_replace("''", "'", $data[$key]); - } elseif ($types[$i] == DB_PARAM_OPAQUE) { - $fp = @fopen($data[$key], 'rb'); - if (!$fp) { - $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION); - return $tmp; - } - $data[$key] = fread($fp, filesize($data[$key])); - fclose($fp); - } elseif ($types[$i] == DB_PARAM_SCALAR) { - // Floats have to be converted to a locale-neutral - // representation. - if (is_float($data[$key])) { - $data[$key] = $this->quoteFloat($data[$key]); - } - } - if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) { - $tmp = $this->oci8RaiseError($stmt); - return $tmp; - } - $this->last_query = preg_replace( - "/:bind$i(?!\d)/", - $this->quoteSmart($data[$key]), - $this->last_query, - 1 - ); - $i++; - } - if ($this->autocommit) { - $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS); - } else { - $success = @OCIExecute($stmt, OCI_DEFAULT); - } - if (!$success) { - $tmp = $this->oci8RaiseError($stmt); - return $tmp; - } - $this->last_stmt = $stmt; - if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) { - $this->_last_query_manip = true; - $this->_next_query_manip = false; - $tmp = DB_OK; - } else { - $this->_last_query_manip = false; - @ocisetprefetch($stmt, $this->options['result_buffering']); - $tmp = new DB_result($this, $stmt); - } - return $tmp; - } - - /** - * Formats a float value for use within a query in a locale-independent - * manner. - * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteFloat($float) - { - return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); - } - - // }}} - // {{{ dropSequence() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_oci8::createSequence(), DB_oci8::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - $repeat = 0; - do { - $this->expectError(DB_ERROR_NOSUCHTABLE); - $result = $this->query("SELECT ${seqname}.nextval FROM dual"); - $this->popExpect(); - if ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - $repeat = 1; - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $this->raiseError($result); - } - } else { - $repeat = 0; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); - return $arr[0]; - } - - // }}} - // {{{ oci8RaiseError() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_oci8::nextID(), DB_oci8::dropSequence() - */ - public function createSequence($seq_name) - { - return $this->query('CREATE SEQUENCE ' - . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ errorNative() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_oci8::nextID(), DB_oci8::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP SEQUENCE ' - . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ tableInfo() - - /** - * Gets the DBMS' native error code produced by the last query - * - * @return int the DBMS' error code. FALSE if the code could not be - * determined - */ - public function errorNative() - { - if (is_resource($this->last_stmt)) { - $error = @OCIError($this->last_stmt); - } else { - $error = @OCIError($this->connection); - } - if (is_array($error)) { - return $error['code']; - } - return false; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * NOTE: flags won't contain index information. - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - */ - public function tableInfo($result, $mode = null) - { - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $res = array(); - - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $result = strtoupper($result); - $q_fields = 'SELECT column_name, data_type, data_length, ' - . 'nullable ' - . 'FROM user_tab_columns ' - . "WHERE table_name='$result' ORDER BY column_id"; - - $this->last_query = $q_fields; - - if (!$stmt = @OCIParse($this->connection, $q_fields)) { - return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA); - } - if (!@OCIExecute($stmt, OCI_DEFAULT)) { - return $this->oci8RaiseError($stmt); - } - - $i = 0; - while (@OCIFetch($stmt)) { - $res[$i] = array( - 'table' => $case_func($result), - 'name' => $case_func(@OCIResult($stmt, 1)), - 'type' => @OCIResult($stmt, 2), - 'len' => @OCIResult($stmt, 3), - 'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '', - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - $i++; - } - - if ($mode) { - $res['num_fields'] = $i; - } - @OCIFreeStatement($stmt); - } else { - if (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $result = $result->result; - } - - $res = array(); - - if ($result === $this->last_stmt) { - $count = @OCINumCols($result); - if ($mode) { - $res['num_fields'] = $count; - } - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => '', - 'name' => $case_func(@OCIColumnName($result, $i + 1)), - 'type' => @OCIColumnType($result, $i + 1), - 'len' => @OCIColumnSize($result, $i + 1), - 'flags' => '', - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - } else { - return $this->raiseError(DB_ERROR_NOT_CAPABLE); - } - } - return $res; - } - - // }}} - // {{{ quoteFloat() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SELECT table_name FROM user_tables'; - case 'synonyms': - return 'SELECT synonym_name FROM user_synonyms'; - case 'views': - return 'SELECT view_name FROM user_views'; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/odbc.php b/extlib/DB/odbc.php deleted file mode 100644 index 0d1e11a479..0000000000 --- a/extlib/DB/odbc.php +++ /dev/null @@ -1,889 +0,0 @@ - - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's odbc extension - * for interacting with databases via ODBC connections - * - * These methods overload the ones declared in DB_common. - * - * More info on ODBC errors could be found here: - * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp - * - * @category Database - * @package DB - * @author Stig Bakken - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_odbc extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'odbc'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'sql92'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * NOTE: The feature set of the following drivers are different than - * the default: - * + solid: 'transactions' = true - * + navision: 'limit' = false - * - * @var array - */ - public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array( - '01004' => DB_ERROR_TRUNCATED, - '07001' => DB_ERROR_MISMATCH, - '21S01' => DB_ERROR_VALUE_COUNT_ON_ROW, - '21S02' => DB_ERROR_MISMATCH, - '22001' => DB_ERROR_INVALID, - '22003' => DB_ERROR_INVALID_NUMBER, - '22005' => DB_ERROR_INVALID_NUMBER, - '22008' => DB_ERROR_INVALID_DATE, - '22012' => DB_ERROR_DIVZERO, - '23000' => DB_ERROR_CONSTRAINT, - '23502' => DB_ERROR_CONSTRAINT_NOT_NULL, - '23503' => DB_ERROR_CONSTRAINT, - '23504' => DB_ERROR_CONSTRAINT, - '23505' => DB_ERROR_CONSTRAINT, - '24000' => DB_ERROR_INVALID, - '34000' => DB_ERROR_INVALID, - '37000' => DB_ERROR_SYNTAX, - '42000' => DB_ERROR_SYNTAX, - '42601' => DB_ERROR_SYNTAX, - 'IM001' => DB_ERROR_UNSUPPORTED, - 'S0000' => DB_ERROR_NOSUCHTABLE, - 'S0001' => DB_ERROR_ALREADY_EXISTS, - 'S0002' => DB_ERROR_NOSUCHTABLE, - 'S0011' => DB_ERROR_ALREADY_EXISTS, - 'S0012' => DB_ERROR_NOT_FOUND, - 'S0021' => DB_ERROR_ALREADY_EXISTS, - 'S0022' => DB_ERROR_NOSUCHFIELD, - 'S1009' => DB_ERROR_INVALID, - 'S1090' => DB_ERROR_INVALID, - 'S1C00' => DB_ERROR_NOT_CAPABLE, - ); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * The number of rows affected by a data manipulation query - * @var integer - * @access private - */ - public $affected = 0; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's odbc driver supports the following extra DSN options: - * + cursor The type of cursor to be used for this connection. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('odbc')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - switch ($this->dbsyntax) { - case 'access': - case 'db2': - case 'solid': - $this->features['transactions'] = true; - break; - case 'navision': - $this->features['limit'] = false; - } - - /* - * This is hear for backwards compatibility. Should have been using - * 'database' all along, but prior to 1.6.0RC3 'hostspec' was used. - */ - if ($dsn['database']) { - $odbcdsn = $dsn['database']; - } elseif ($dsn['hostspec']) { - $odbcdsn = $dsn['hostspec']; - } else { - $odbcdsn = 'localhost'; - } - - $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect'; - - if (empty($dsn['cursor'])) { - $this->connection = @$connect_function( - $odbcdsn, - $dsn['username'], - $dsn['password'] - ); - } else { - $this->connection = @$connect_function( - $odbcdsn, - $dsn['username'], - $dsn['password'], - $dsn['cursor'] - ); - } - - if (!is_resource($this->connection)) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $this->errorNative() - ); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Gets the DBMS' native error code and message produced by the last query - * - * @return string the DBMS' error code and message - */ - public function errorNative() - { - if (!is_resource($this->connection)) { - return @odbc_error() . ' ' . @odbc_errormsg(); - } - return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection); - } - - // }}} - // {{{ simpleQuery() - - /** - * Disconnects from the database server - * - * @return bool|void - */ - public function disconnect() - { - $err = @odbc_close($this->connection); - $this->connection = null; - return $err; - } - - // }}} - // {{{ nextResult() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $this->last_query = $query; - $query = $this->modifyQuery($query); - $result = @odbc_exec($this->connection, $query); - if (!$result) { - return $this->odbcRaiseError(); // XXX ERRORMSG - } - // Determine which queries that should return data, and which - // should return an error code only. - if ($this->_checkManip($query)) { - $this->affected = $result; // For affectedRows() - return DB_OK; - } - $this->affected = 0; - return $result; - } - - // }}} - // {{{ fetchInto() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_odbc::errorNative(), DB_common::errorCode() - */ - public function odbcRaiseError($errno = null) - { - if ($errno === null) { - switch ($this->dbsyntax) { - case 'access': - if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { - $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD; - } else { - // Doing this in case mode changes during runtime. - $this->errorcode_map['07001'] = DB_ERROR_MISMATCH; - } - - $native_code = odbc_error($this->connection); - - // S1000 is for "General Error." Let's be more specific. - if ($native_code == 'S1000') { - $errormsg = odbc_errormsg($this->connection); - static $error_regexps; - if (!isset($error_regexps)) { - $error_regexps = array( - '/includes related records.$/i' => DB_ERROR_CONSTRAINT, - '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $this->raiseError( - $code, - null, - null, - null, - $native_code . ' ' . $errormsg - ); - } - } - $errno = DB_ERROR; - } else { - $errno = $this->errorCode($native_code); - } - break; - default: - $errno = $this->errorCode(odbc_error($this->connection)); - } - } - return $this->raiseError( - $errno, - null, - null, - null, - $this->errorNative() - ); - } - - // }}} - // {{{ freeResult() - - /** - * Move the internal odbc result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return @odbc_next_result($result); - } - - // }}} - // {{{ numCols() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - $arr = array(); - if ($rownum !== null) { - $rownum++; // ODBC first row is 1 - if (version_compare(phpversion(), '4.2.0', 'ge')) { - $cols = @odbc_fetch_into($result, $arr, $rownum); - } else { - $cols = @odbc_fetch_into($result, $rownum, $arr); - } - } else { - $cols = @odbc_fetch_into($result, $arr); - } - if (!$cols) { - return null; - } - if ($fetchmode !== DB_FETCHMODE_ORDERED) { - for ($i = 0; $i < count($arr); $i++) { - $colName = @odbc_field_name($result, $i + 1); - $a[$colName] = $arr[$i]; - } - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $a = array_change_key_case($a, CASE_LOWER); - } - $arr = $a; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ affectedRows() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? odbc_free_result($result) : false; - } - - // }}} - // {{{ numRows() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @odbc_num_fields($result); - if (!$cols) { - return $this->odbcRaiseError(); - } - return $cols; - } - - // }}} - // {{{ quoteIdentifier() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int|object - */ - public function affectedRows() - { - if (empty($this->affected)) { // In case of SELECT stms - return 0; - } - $nrows = @odbc_num_rows($this->affected); - if ($nrows == -1) { - return $this->odbcRaiseError(); - } - return $nrows; - } - - // }}} - // {{{ nextId() - - /** - * Gets the number of rows in a result set - * - * Not all ODBC drivers support this functionality. If they don't - * a DB_Error object for DB_ERROR_UNSUPPORTED is returned. - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $nrows = @odbc_num_rows($result); - if ($nrows == -1) { - return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED); - } - if ($nrows === false) { - return $this->odbcRaiseError(); - } - return $nrows; - } - - /** - * Quotes a string so it can be safely used as a table or column name - * - * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked - * "Use ANSI quoted identifiers" when setting up the ODBC data source. - * - * @param string $str identifier name to be quoted - * - * @return string quoted identifier string - * - * @see DB_common::quoteIdentifier() - * @since Method available since Release 1.6.0 - */ - public function quoteIdentifier($str) - { - switch ($this->dsn['dbsyntax']) { - case 'access': - return '[' . $str . ']'; - case 'mssql': - case 'sybase': - return '[' . str_replace(']', ']]', $str) . ']'; - case 'mysql': - case 'mysqli': - return '`' . $str . '`'; - default: - return '"' . str_replace('"', '""', $str) . '"'; - } - } - - // }}} - // {{{ dropSequence() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_odbc::createSequence(), DB_odbc::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - $repeat = 0; - do { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("update ${seqname} set id = id + 1"); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - $repeat = 1; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->createSequence($seq_name); - $this->popErrorHandling(); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $result = $this->query("insert into ${seqname} (id) values(0)"); - } else { - $repeat = 0; - } - } while ($repeat); - - if (DB::isError($result)) { - return $this->raiseError($result); - } - - $result = $this->query("select id from ${seqname}"); - if (DB::isError($result)) { - return $result; - } - - $row = $result->fetchRow(DB_FETCHMODE_ORDERED); - if (DB::isError($row || !$row)) { - return $row; - } - - return $row[0]; - } - - // }}} - // {{{ autoCommit() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_odbc::nextID(), DB_odbc::dropSequence() - */ - public function createSequence($seq_name) - { - return $this->query('CREATE TABLE ' - . $this->getSequenceName($seq_name) - . ' (id integer NOT NULL,' - . ' PRIMARY KEY(id))'); - } - - // }}} - // {{{ commit() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_odbc::nextID(), DB_odbc::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ rollback() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int|object - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - if (!@odbc_autocommit($this->connection, $onoff)) { - return $this->odbcRaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ odbcRaiseError() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if (!@odbc_commit($this->connection)) { - return $this->odbcRaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ errorNative() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if (!@odbc_rollback($this->connection)) { - return $this->odbcRaiseError(); - } - return DB_OK; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - * @since Method available since Release 1.7.0 - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @odbc_exec($this->connection, "SELECT * FROM $result"); - if (!$id) { - return $this->odbcRaiseError(); - } - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @odbc_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $col = $i + 1; - $res[$i] = array( - 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func(@odbc_field_name($id, $col)), - 'type' => @odbc_field_type($id, $col), - 'len' => @odbc_field_len($id, $col), - 'flags' => '', - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @odbc_free_result($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com. - * - * @param string $type the kind of objects you want to retrieve - * - * @return array|string - * - * @access protected - * @see DB_common::getListOf() - * @since Method available since Release 1.7.0 - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'databases': - if (!function_exists('odbc_data_source')) { - return null; - } - $res = @odbc_data_source($this->connection, SQL_FETCH_FIRST); - if (is_array($res)) { - $out = array($res['server']); - while ($res = @odbc_data_source( - $this->connection, - SQL_FETCH_NEXT - )) { - $out[] = $res['server']; - } - return $out; - } else { - return $this->odbcRaiseError(); - } - break; - case 'tables': - case 'schema.tables': - $keep = 'TABLE'; - break; - case 'views': - $keep = 'VIEW'; - break; - default: - return null; - } - - /* - * Removing non-conforming items in the while loop rather than - * in the odbc_tables() call because some backends choke on this: - * odbc_tables($this->connection, '', '', '', 'TABLE') - */ - $res = @odbc_tables($this->connection); - if (!$res) { - return $this->odbcRaiseError(); - } - $out = array(); - while ($row = odbc_fetch_array($res)) { - if ($row['TABLE_TYPE'] != $keep) { - continue; - } - if ($type == 'schema.tables') { - $out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME']; - } else { - $out[] = $row['TABLE_NAME']; - } - } - return $out; - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/pgsql.php b/extlib/DB/pgsql.php deleted file mode 100644 index e0387a3e22..0000000000 --- a/extlib/DB/pgsql.php +++ /dev/null @@ -1,1131 +0,0 @@ - - * @author Stig Bakken - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's pgsql extension - * for interacting with PostgreSQL databases - * - * These methods overload the ones declared in DB_common. - * - * @category Database - * @package DB - * @author Rui Hirokawa - * @author Stig Bakken - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_pgsql extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'pgsql'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'pgsql'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'alter', - 'new_link' => '4.3.0', - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => true, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array(); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The quantity of transactions begun - * - * {@internal While this is private, it can't actually be designated - * private in PHP 5 because it is directly accessed in the test suite.}} - * - * @var integer - * @access private - */ - public $transaction_opcount = 0; - - /** - * The number of rows affected by a data manipulation query - * @var integer - */ - public $affected = 0; - - /** - * The current row being looked at in fetchInto() - * @var array - * @access private - */ - public $row = array(); - - /** - * The number of rows in a given result set - * @var array - * @access private - */ - public $_num_rows = array(); - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's pgsql driver supports the following extra DSN options: - * + connect_timeout How many seconds to wait for a connection to - * be established. Available since PEAR DB 1.7.0. - * + new_link If set to true, causes subsequent calls to - * connect() to return a new connection link - * instead of the existing one. WARNING: this is - * not portable to other DBMS's. Available only - * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0. - * + options Command line options to be sent to the server. - * Available since PEAR DB 1.6.4. - * + service Specifies a service name in pg_service.conf that - * holds additional connection parameters. - * Available since PEAR DB 1.7.0. - * + sslmode How should SSL be used when connecting? Values: - * disable, allow, prefer or require. - * Available since PEAR DB 1.7.0. - * + tty This was used to specify where to send server - * debug output. Available since PEAR DB 1.6.4. - * - * Example of connecting to a new link via a socket: - * - * require_once 'DB.php'; - * - * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true'; - * $options = array( - * 'portability' => DB_PORTABILITY_ALL, - * ); - * - * $db = DB::connect($dsn, $options); - * if ((new PEAR)->isError($db)) { - * die($db->getMessage()); - * } - * - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - * - * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('pgsql')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp'; - - $params = array(''); - if ($protocol == 'tcp') { - if ($dsn['hostspec']) { - $params[0] .= 'host=' . $dsn['hostspec']; - } - if ($dsn['port']) { - $params[0] .= ' port=' . $dsn['port']; - } - } elseif ($protocol == 'unix') { - // Allow for pg socket in non-standard locations. - if ($dsn['socket']) { - $params[0] .= 'host=' . $dsn['socket']; - } - if ($dsn['port']) { - $params[0] .= ' port=' . $dsn['port']; - } - } - if ($dsn['database']) { - $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\''; - } - if ($dsn['username']) { - $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\''; - } - if ($dsn['password']) { - $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\''; - } - if (!empty($dsn['options'])) { - $params[0] .= ' options=' . $dsn['options']; - } - if (!empty($dsn['tty'])) { - $params[0] .= ' tty=' . $dsn['tty']; - } - if (!empty($dsn['connect_timeout'])) { - $params[0] .= ' connect_timeout=' . $dsn['connect_timeout']; - } - if (!empty($dsn['sslmode'])) { - $params[0] .= ' sslmode=' . $dsn['sslmode']; - } - if (!empty($dsn['service'])) { - $params[0] .= ' service=' . $dsn['service']; - } - - if (isset($dsn['new_link']) - && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) { - if (version_compare(phpversion(), '4.3.0', '>=')) { - $params[] = PGSQL_CONNECT_FORCE_NEW; - } - } - - $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; - - $ini = ini_get('track_errors'); - $php_errormsg = ''; - if ($ini) { - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - } else { - @ini_set('track_errors', 1); - $this->connection = @call_user_func_array( - $connect_function, - $params - ); - @ini_set('track_errors', $ini); - } - - if (!$this->connection) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - $php_errormsg - ); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @pg_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ simpleQuery() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - $query = $this->modifyQuery($query); - if (!$this->autocommit && $ismanip) { - if ($this->transaction_opcount == 0) { - $result = @pg_query($this->connection, 'begin;'); - if (!$result) { - return $this->pgsqlRaiseError(); - } - } - $this->transaction_opcount++; - } - $result = @pg_query($this->connection, $query); - if (!$result) { - return $this->pgsqlRaiseError(); - } - - /* - * Determine whether queries produce affected rows, result or nothing. - * - * This logic was introduced in version 1.1 of the file by ssb, - * though the regex has been modified slightly since then. - * - * PostgreSQL commands: - * ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, - * CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, - * GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, - * REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, - * UNLISTEN, UPDATE, VACUUM, WITH - */ - if ($ismanip) { - $this->affected = @pg_affected_rows($result); - return DB_OK; - } elseif (preg_match( - '/^\s*\(*\s*(SELECT|EXPLAIN|FETCH|SHOW|WITH)\s/si', - $query - )) { - $this->row[(int)$result] = 0; // reset the row counter. - $numrows = $this->numRows($result); - if (is_object($numrows)) { - return $numrows; - } - $this->_num_rows[(int)$result] = $numrows; - $this->affected = 0; - return $result; - } else { - $this->affected = 0; - return DB_OK; - } - } - - // }}} - // {{{ nextResult() - - /** - * Checks if the given query is a manipulation query. This also takes into - * account the _next_query_manip flag and sets the _last_query_manip flag - * (and resets _next_query_manip) according to the result. - * - * @param string The query to check. - * - * @return boolean true if the query is a manipulation query, false - * otherwise - * - * @access protected - */ - public function _checkManip($query) - { - return (preg_match('/^\s*(SAVEPOINT|RELEASE)\s+/i', $query) - || parent::_checkManip($query)); - } - - // }}} - // {{{ fetchInto() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_pgsql::errorNative(), DB_pgsql::errorCode() - */ - public function pgsqlRaiseError($errno = null) - { - $native = $this->errorNative(); - if (!$native) { - $native = 'Database connection has been lost.'; - $errno = DB_ERROR_CONNECT_FAILED; - } - if ($errno === null) { - $errno = $this->errorCode($native); - } - return $this->raiseError($errno, null, null, null, $native); - } - - // }}} - // {{{ freeResult() - - /** - * Gets the DBMS' native error message produced by the last query - * - * {@internal Error messages are used instead of error codes - * in order to support older versions of PostgreSQL.}} - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return @pg_errormessage($this->connection); - } - - // }}} - // {{{ quoteBoolean() - - /** - * Determines PEAR::DB error code from the database's text error message. - * - * @param string $errormsg error message returned from the database - * @return integer an error number from a DB error constant - */ - public function errorCode($errormsg) - { - static $error_regexps; - if (!isset($error_regexps)) { - $error_regexps = array( - '/column .* (of relation .*)?does not exist/i' - => DB_ERROR_NOSUCHFIELD, - '/(relation|sequence|table).*does not exist|class .* not found/i' - => DB_ERROR_NOSUCHTABLE, - '/index .* does not exist/' - => DB_ERROR_NOT_FOUND, - '/relation .* already exists/i' - => DB_ERROR_ALREADY_EXISTS, - '/(divide|division) by zero$/i' - => DB_ERROR_DIVZERO, - '/pg_atoi: error in .*: can\'t parse /i' - => DB_ERROR_INVALID_NUMBER, - '/invalid input syntax for( type)? (integer|numeric)/i' - => DB_ERROR_INVALID_NUMBER, - '/value .* is out of range for type \w*int/i' - => DB_ERROR_INVALID_NUMBER, - '/integer out of range/i' - => DB_ERROR_INVALID_NUMBER, - '/value too long for type character/i' - => DB_ERROR_INVALID, - '/attribute .* not found|relation .* does not have attribute/i' - => DB_ERROR_NOSUCHFIELD, - '/column .* specified in USING clause does not exist in (left|right) table/i' - => DB_ERROR_NOSUCHFIELD, - '/parser: parse error at or near/i' - => DB_ERROR_SYNTAX, - '/syntax error at/' - => DB_ERROR_SYNTAX, - '/column reference .* is ambiguous/i' - => DB_ERROR_SYNTAX, - '/permission denied/' - => DB_ERROR_ACCESS_VIOLATION, - '/violates not-null constraint/' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/violates [\w ]+ constraint/' - => DB_ERROR_CONSTRAINT, - '/referential integrity violation/' - => DB_ERROR_CONSTRAINT, - '/more expressions than target columns/i' - => DB_ERROR_VALUE_COUNT_ON_ROW, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - // Fall back to DB_ERROR if there was no mapping. - return DB_ERROR; - } - - // }}} - // {{{ escapeSimple() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @pg_numrows($result); - if ($rows === null) { - return $this->pgsqlRaiseError(); - } - return $rows; - } - - // }}} - // {{{ numCols() - - /** - * Move the internal pgsql result pointer to the next available result - * - * @param a valid fbsql result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ numRows() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - $result_int = (int)$result; - $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int]; - if ($rownum >= $this->_num_rows[$result_int]) { - return null; - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @pg_fetch_row($result, $rownum); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - $this->row[$result_int] = ++$rownum; - return DB_OK; - } - - // }}} - // {{{ autoCommit() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - if (is_resource($result)) { - unset($this->row[(int)$result]); - unset($this->_num_rows[(int)$result]); - $this->affected = 0; - return @pg_freeresult($result); - } - return false; - } - - // }}} - // {{{ commit() - - /** - * Formats a boolean value for use within a query in a locale-independent - * manner. - * - * @param boolean the boolean value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteBoolean($boolean) - { - return $boolean ? 'TRUE' : 'FALSE'; - } - - // }}} - // {{{ rollback() - - /** - * Escapes a string according to the current DBMS's standards - * - * {@internal PostgreSQL treats a backslash as an escape character, - * so they are escaped as well. - * - * @param string $str the string to be escaped - * - * @return string the escaped string - * - * @see DB_common::quoteSmart() - * @since Method available since Release 1.6.0 - */ - public function escapeSimple($str) - { - if (function_exists('pg_escape_string')) { - /* This fixes an undocumented BC break in PHP 5.2.0 which changed - * the prototype of pg_escape_string. I'm not thrilled about having - * to sniff the PHP version, quite frankly, but it's the only way - * to deal with the problem. Revision 1.331.2.13.2.10 on - * php-src/ext/pgsql/pgsql.c (PHP_5_2 branch) is to blame, for the - * record. */ - if (version_compare(PHP_VERSION, '5.2.0', '>=')) { - return pg_escape_string($this->connection, $str); - } else { - return pg_escape_string($str); - } - } else { - return str_replace("'", "''", str_replace('\\', '\\\\', $str)); - } - } - - // }}} - // {{{ affectedRows() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @pg_numfields($result); - if (!$cols) { - return $this->pgsqlRaiseError(); - } - return $cols; - } - - // }}} - // {{{ nextId() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - // XXX if $this->transaction_opcount > 0, we should probably - // issue a warning here. - $this->autocommit = $onoff ? true : false; - return DB_OK; - } - - // }}} - // {{{ createSequence() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if ($this->transaction_opcount > 0) { - // (disabled) hack to shut up error messages from libpq.a - //@fclose(@fopen("php://stderr", "w")); - $result = @pg_query($this->connection, 'end;'); - $this->transaction_opcount = 0; - if (!$result) { - return $this->pgsqlRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ dropSequence() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if ($this->transaction_opcount > 0) { - $result = @pg_query($this->connection, 'abort;'); - $this->transaction_opcount = 0; - if (!$result) { - return $this->pgsqlRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - return $this->affected; - } - - // }}} - // {{{ pgsqlRaiseError() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_pgsql::createSequence(), DB_pgsql::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - $repeat = false; - do { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("SELECT NEXTVAL('${seqname}')"); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - $repeat = true; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->createSequence($seq_name); - $this->popErrorHandling(); - if (DB::isError($result)) { - return $this->raiseError($result); - } - } else { - $repeat = false; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); - $result->free(); - return $arr[0]; - } - - // }}} - // {{{ errorNative() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_pgsql::nextID(), DB_pgsql::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $result = $this->query("CREATE SEQUENCE ${seqname}"); - return $result; - } - - // }}} - // {{{ errorCode() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_pgsql::nextID(), DB_pgsql::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP SEQUENCE ' - . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ tableInfo() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - return "$query LIMIT $count OFFSET $from"; - } - - // }}} - // {{{ _pgFieldFlags() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @pg_query($this->connection, "SELECT * FROM $result LIMIT 0"); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @pg_numfields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $res[$i] = array( - 'table' => $got_string ? $case_func($result) : '', - 'name' => $case_func(@pg_fieldname($id, $i)), - 'type' => @pg_fieldtype($id, $i), - 'len' => @pg_fieldsize($id, $i), - 'flags' => $got_string - ? $this->_pgFieldFlags($id, $i, $result) - : '', - ); - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @pg_freeresult($id); - } - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Get a column's flags - * - * Supports "not_null", "default_value", "primary_key", "unique_key" - * and "multiple_key". The default value is passed through - * rawurlencode() in case there are spaces in it. - * - * @param int $resource the PostgreSQL result identifier - * @param int $num_field the field number - * - * @param $table_name - * @return string the flags - * - * @access private - */ - public function _pgFieldFlags($resource, $num_field, $table_name) - { - $field_name = @pg_fieldname($resource, $num_field); - - // Check if there's a schema in $table_name and update things - // accordingly. - $from = 'pg_attribute f, pg_class tab, pg_type typ'; - if (strpos($table_name, '.') !== false) { - $from .= ', pg_namespace nsp'; - list($schema, $table) = explode('.', $table_name); - $tableWhere = "tab.relname = '$table' AND tab.relnamespace = nsp.oid AND nsp.nspname = '$schema'"; - } else { - $tableWhere = "tab.relname = '$table_name'"; - } - - $result = @pg_query($this->connection, "SELECT f.attnotnull, f.atthasdef - FROM $from - WHERE tab.relname = typ.typname - AND typ.typrelid = f.attrelid - AND f.attname = '$field_name' - AND $tableWhere"); - if (@pg_numrows($result) > 0) { - $row = @pg_fetch_row($result, 0); - $flags = ($row[0] == 't') ? 'not_null ' : ''; - - if ($row[1] == 't') { - $result = @pg_query($this->connection, "SELECT a.adsrc - FROM $from, pg_attrdef a - WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid - AND f.attrelid = a.adrelid AND f.attname = '$field_name' - AND $tableWhere AND f.attnum = a.adnum"); - $row = @pg_fetch_row($result, 0); - $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]); - $flags .= 'default_' . rawurlencode($num) . ' '; - } - } else { - $flags = ''; - } - $result = @pg_query($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey - FROM $from, pg_index i - WHERE tab.relname = typ.typname - AND typ.typrelid = f.attrelid - AND f.attrelid = i.indrelid - AND f.attname = '$field_name' - AND $tableWhere"); - $count = @pg_numrows($result); - - for ($i = 0; $i < $count; $i++) { - $row = @pg_fetch_row($result, $i); - $keys = explode(' ', $row[2]); - - if (in_array($num_field + 1, $keys)) { - $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : ''; - $flags .= ($row[1] == 't') ? 'primary_key ' : ''; - if (count($keys) > 1) { - $flags .= 'multiple_key '; - } - } - } - - return trim($flags); - } - - // }}} - // {{{ _checkManip() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return 'SELECT c.relname AS "Name"' - . ' FROM pg_class c, pg_user u' - . ' WHERE c.relowner = u.usesysid' - . " AND c.relkind = 'r'" - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_views' - . ' WHERE viewname = c.relname)' - . " AND c.relname !~ '^(pg_|sql_)'" - . ' UNION' - . ' SELECT c.relname AS "Name"' - . ' FROM pg_class c' - . " WHERE c.relkind = 'r'" - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_views' - . ' WHERE viewname = c.relname)' - . ' AND NOT EXISTS' - . ' (SELECT 1 FROM pg_user' - . ' WHERE usesysid = c.relowner)' - . " AND c.relname !~ '^pg_'"; - case 'schema.tables': - return "SELECT schemaname || '.' || tablename" - . ' AS "Name"' - . ' FROM pg_catalog.pg_tables' - . ' WHERE schemaname NOT IN' - . " ('pg_catalog', 'information_schema', 'pg_toast')"; - case 'schema.views': - return "SELECT schemaname || '.' || viewname from pg_views WHERE schemaname" - . " NOT IN ('information_schema', 'pg_catalog')"; - case 'views': - // Table cols: viewname | viewowner | definition - return 'SELECT viewname from pg_views WHERE schemaname' - . " NOT IN ('information_schema', 'pg_catalog')"; - case 'users': - // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil - return 'SELECT usename FROM pg_user'; - case 'databases': - return 'SELECT datname FROM pg_database'; - case 'functions': - case 'procedures': - return 'SELECT proname FROM pg_proc WHERE proowner <> 1'; - default: - return null; - } - } -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/sqlite.php b/extlib/DB/sqlite.php deleted file mode 100644 index 5e7c67b21a..0000000000 --- a/extlib/DB/sqlite.php +++ /dev/null @@ -1,978 +0,0 @@ - - * @author Mika Tuupola - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's sqlite extension - * for interacting with SQLite databases - * - * These methods overload the ones declared in DB_common. - * - * NOTICE: This driver needs PHP's track_errors ini setting to be on. - * It is automatically turned on when connecting to the database. - * Make sure your scripts don't turn it off. - * - * @category Database - * @package DB - * @author Urs Gehrig - * @author Mika Tuupola - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_sqlite extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'sqlite'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'sqlite'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'alter', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => false, - ); - - /** - * A mapping of native error codes to DB error codes - * - * {@internal Error codes according to sqlite_exec. See the online - * manual at http://sqlite.org/c_interface.html for info. - * This error handling based on sqlite_exec is not yet implemented.}} - * - * @var array - */ - public $errorcode_map = array(); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * SQLite data types - * - * @link http://www.sqlite.org/datatypes.html - * - * @var array - */ - public $keywords = array( - 'BLOB' => '', - 'BOOLEAN' => '', - 'CHARACTER' => '', - 'CLOB' => '', - 'FLOAT' => '', - 'INTEGER' => '', - 'KEY' => '', - 'NATIONAL' => '', - 'NUMERIC' => '', - 'NVARCHAR' => '', - 'PRIMARY' => '', - 'TEXT' => '', - 'TIMESTAMP' => '', - 'UNIQUE' => '', - 'VARCHAR' => '', - 'VARYING' => '', - ); - - /** - * The most recent error message from $php_errormsg - * @var string - * @access private - */ - public $_lasterror = ''; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's sqlite driver supports the following extra DSN options: - * + mode The permissions for the database file, in four digit - * chmod octal format (eg "0600"). - * - * Example of connecting to a database in read-only mode: - * - * require_once 'DB.php'; - * - * $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400'; - * $options = array( - * 'portability' => DB_PORTABILITY_ALL, - * ); - * - * $db = DB::connect($dsn, $options); - * if ((new PEAR)->isError($db)) { - * die($db->getMessage()); - * } - * - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('sqlite')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - if (!$dsn['database']) { - return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION); - } - - if ($dsn['database'] !== ':memory:') { - if (!file_exists($dsn['database'])) { - if (!touch($dsn['database'])) { - return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); - } - if (!isset($dsn['mode']) || - !is_numeric($dsn['mode'])) { - $mode = 0644; - } else { - $mode = octdec($dsn['mode']); - } - if (!chmod($dsn['database'], $mode)) { - return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); - } - if (!file_exists($dsn['database'])) { - return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); - } - } - if (!is_file($dsn['database'])) { - return $this->sqliteRaiseError(DB_ERROR_INVALID); - } - if (!is_readable($dsn['database'])) { - return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION); - } - } - - $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open'; - - // track_errors must remain on for simpleQuery() - @ini_set('track_errors', 1); - $php_errormsg = ''; - - if (!$this->connection = @$connect_function($dsn['database'])) { - return $this->raiseError( - DB_ERROR_NODBSELECTED, - null, - null, - null, - $php_errormsg - ); - } - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_sqlite::errorNative(), DB_sqlite::errorCode() - */ - public function sqliteRaiseError($errno = null) - { - $native = $this->errorNative(); - if ($errno === null) { - $errno = $this->errorCode($native); - } - - $errorcode = @sqlite_last_error($this->connection); - $userinfo = "$errorcode ** $this->last_query"; - - return $this->raiseError($errno, null, null, $userinfo, $native); - } - - // }}} - // {{{ simpleQuery() - - /** - * Gets the DBMS' native error message produced by the last query - * - * {@internal This is used to retrieve more meaningfull error messages - * because sqlite_last_error() does not provide adequate info.}} - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return $this->_lasterror; - } - - // }}} - // {{{ nextResult() - - /** - * Determines PEAR::DB error code from the database's text error message - * - * @param string $errormsg the error message returned from the database - * - * @return integer the DB error number - */ - public function errorCode($errormsg) - { - static $error_regexps; - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg); - - if (!isset($error_regexps)) { - $error_regexps = array( - '/^no such table:/' => DB_ERROR_NOSUCHTABLE, - '/^no such index:/' => DB_ERROR_NOT_FOUND, - '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS, - '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT, - '/is not unique/' => DB_ERROR_CONSTRAINT, - '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT, - '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT, - '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL, - '/^no such column:/' => DB_ERROR_NOSUCHFIELD, - '/no column named/' => DB_ERROR_NOSUCHFIELD, - '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD, - '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX, - '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW, - ); - } - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - // Fall back to DB_ERROR if there was no mapping. - return DB_ERROR; - } - - // }}} - // {{{ fetchInto() - - /** - * Disconnects from the database server - * - * @return bool|void - */ - public function disconnect() - { - $ret = @sqlite_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ freeResult() - - /** - * Sends a query to the database server - * - * NOTICE: This method needs PHP's track_errors ini setting to be on. - * It is automatically turned on when connecting to the database. - * Make sure your scripts don't turn it off. - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - $query = $this->modifyQuery($query); - - $php_errormsg = ''; - - $result = @sqlite_query($query, $this->connection); - $this->_lasterror = $php_errormsg ? $php_errormsg : ''; - - $this->result = $result; - if (!$this->result) { - return $this->sqliteRaiseError(null); - } - - // sqlite_query() seems to allways return a resource - // so cant use that. Using $ismanip instead - if (!$ismanip) { - $numRows = $this->numRows($result); - if (is_object($numRows)) { - // we've got PEAR_Error - return $numRows; - } - return $result; - } - return DB_OK; - } - - // }}} - // {{{ numCols() - - /** - * Changes a query string for various DBMS specific reasons - * - * This little hack lets you know how many rows were deleted - * when running a "DELETE FROM table" query. Only implemented - * if the DB_PORTABILITY_DELETE_COUNT portability option is on. - * - * @param string $query the query string to modify - * - * @return string the modified query string - * - * @access protected - * @see DB_common::setOption() - */ - public function modifyQuery($query) - { - if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { - 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 - ); - } - } - return $query; - } - - // }}} - // {{{ numRows() - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @sqlite_num_rows($result); - if ($rows === null) { - return $this->sqliteRaiseError(); - } - return $rows; - } - - // }}} - // {{{ affected() - - /** - * Move the internal sqlite result pointer to the next available result - * - * @param resource $result the valid sqlite result resource - * - * @return bool true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ dropSequence() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@sqlite_seek($this->result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - $arr = @sqlite_fetch_array($result, SQLITE_ASSOC); - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - - /* Remove extraneous " characters from the fields in the result. - * Fixes bug #11716. */ - if (is_array($arr) && count($arr) > 0) { - $strippedArr = array(); - foreach ($arr as $field => $value) { - $strippedArr[trim($field, '"')] = $value; - } - $arr = $strippedArr; - } - } else { - $arr = @sqlite_fetch_array($result, SQLITE_NUM); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - /* - * Even though this DBMS already trims output, we do this because - * a field might have intentional whitespace at the end that - * gets removed by DB_PORTABILITY_RTRIM under another driver. - */ - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult(&$result) - { - // XXX No native free? - if (!is_resource($result)) { - return false; - } - $result = null; - return true; - } - - // }}} - // {{{ nextId() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @sqlite_num_fields($result); - if (!$cols) { - return $this->sqliteRaiseError(); - } - return $cols; - } - - // }}} - // {{{ getDbFileStats() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - return @sqlite_changes($this->connection); - } - - // }}} - // {{{ escapeSimple() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_sqlite::nextID(), DB_sqlite::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ modifyLimitQuery() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_sqlite::createSequence(), DB_sqlite::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - - do { - $repeat = 0; - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)"); - $this->popErrorHandling(); - if ($result === DB_OK) { - $id = @sqlite_last_insert_rowid($this->connection); - if ($id != 0) { - return $id; - } - } elseif ($ondemand && DB::isError($result) && - $result->getCode() == DB_ERROR_NOSUCHTABLE) { - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $this->raiseError($result); - } else { - $repeat = 1; - } - } - } while ($repeat); - - return $this->raiseError($result); - } - - // }}} - // {{{ modifyQuery() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_sqlite::nextID(), DB_sqlite::dropSequence() - */ - public function createSequence($seq_name) - { - $seqname = $this->getSequenceName($seq_name); - $query = 'CREATE TABLE ' . $seqname . - ' (id INTEGER UNSIGNED PRIMARY KEY) '; - $result = $this->query($query); - if (DB::isError($result)) { - return ($result); - } - $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname - BEGIN - DELETE FROM $seqname WHERE idquery($query); - //if (DB::isError($result)) { - return ($result); - //} - } - - // }}} - // {{{ sqliteRaiseError() - - /** - * Get the file stats for the current database - * - * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size, - * atime, mtime, ctime, blksize, blocks or a numeric key between - * 0 and 12. - * - * @param string $arg the array key for stats() - * - * @return mixed an array on an unspecified key, integer on a passed - * arg and false at a stats error - */ - public function getDbFileStats($arg = '') - { - $stats = stat($this->dsn['database']); - if ($stats == false) { - return false; - } - if (is_array($stats)) { - if (is_numeric($arg)) { - if (((int)$arg <= 12) & ((int)$arg >= 0)) { - return false; - } - return $stats[$arg]; - } - if (array_key_exists(trim($arg), $stats)) { - return $stats[$arg]; - } - } - return $stats; - } - - // }}} - // {{{ errorNative() - - /** - * Escapes a string according to the current DBMS's standards - * - * In SQLite, this makes things safe for inserts/updates, but may - * cause problems when performing text comparisons against columns - * containing binary data. See the - * {@link http://php.net/sqlite_escape_string PHP manual} for more info. - * - * @param string $str the string to be escaped - * - * @return string the escaped string - * - * @since Method available since Release 1.6.1 - * @see DB_common::escapeSimple() - */ - public function escapeSimple($str) - { - return @sqlite_escape_string($str); - } - - // }}} - // {{{ errorCode() - - /** - * Adds LIMIT clauses to a query string according to current DBMS standards - * - * @param string $query the query to modify - * @param int $from the row to start to fetching (0 = the first row) - * @param int $count the numbers of rows to fetch - * @param mixed $params array, string or numeric data to be used in - * execution of the statement. Quantity of items - * passed must match quantity of placeholders in - * query: meaning 1 placeholder for non-array - * parameters or 1 placeholder per array element. - * - * @return string the query string with LIMIT clauses added - * - * @access protected - */ - public function modifyLimitQuery($query, $from, $count, $params = array()) - { - return "$query LIMIT $count OFFSET $from"; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table - * - * @param string $result a string containing the name of a table - * @param int $mode a valid tableInfo mode - * - * @return array|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - * @since Method available since Release 1.7.0 - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - $id = @sqlite_array_query( - $this->connection, - "PRAGMA table_info('$result');", - SQLITE_ASSOC - ); - $got_string = true; - } else { - $this->last_query = ''; - return $this->raiseError( - DB_ERROR_NOT_CAPABLE, - null, - null, - null, - 'This DBMS can not obtain tableInfo' . - ' from result sets' - ); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = count($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - if (strpos($id[$i]['type'], '(') !== false) { - $bits = explode('(', $id[$i]['type']); - $type = $bits[0]; - $len = rtrim($bits[1], ')'); - } else { - $type = $id[$i]['type']; - $len = 0; - } - - $flags = ''; - if ($id[$i]['pk']) { - $flags .= 'primary_key '; - if (strtoupper($type) == 'INTEGER') { - $flags .= 'auto_increment '; - } - } - if ($id[$i]['notnull']) { - $flags .= 'not_null '; - } - if ($id[$i]['dflt_value'] !== null) { - $flags .= 'default_' . rawurlencode($id[$i]['dflt_value']); - } - $flags = trim($flags); - - $res[$i] = array( - 'table' => $case_func($result), - 'name' => $case_func($id[$i]['name']), - 'type' => $type, - 'len' => $len, - 'flags' => $flags, - ); - - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - return $res; - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * @param array $args SQLITE DRIVER ONLY: a private array of arguments - * used by the getSpecialQuery(). Do not use - * this directly. - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type, $args = array()) - { - if (!is_array($args)) { - return $this->raiseError( - 'no key specified', - null, - null, - null, - 'Argument has to be an array.' - ); - } - - switch ($type) { - case 'master': - return 'SELECT * FROM sqlite_master;'; - case 'tables': - return "SELECT name FROM sqlite_master WHERE type='table' " - . 'UNION ALL SELECT name FROM sqlite_temp_master ' - . "WHERE type='table' ORDER BY name;"; - case 'schema': - return 'SELECT sql FROM (SELECT * FROM sqlite_master ' - . 'UNION ALL SELECT * FROM sqlite_temp_master) ' - . "WHERE type!='meta' " - . 'ORDER BY tbl_name, type DESC, name;'; - case 'schemax': - case 'schema_x': - /* - * Use like: - * $res = $db->query($db->getSpecialQuery('schema_x', - * array('table' => 'table3'))); - */ - return 'SELECT sql FROM (SELECT * FROM sqlite_master ' - . 'UNION ALL SELECT * FROM sqlite_temp_master) ' - . "WHERE tbl_name LIKE '{$args['table']}' " - . "AND type!='meta' " - . 'ORDER BY type DESC, name;'; - case 'alter': - /* - * SQLite does not support ALTER TABLE; this is a helper query - * to handle this. 'table' represents the table name, 'rows' - * the news rows to create, 'save' the row(s) to keep _with_ - * the data. - * - * Use like: - * $args = array( - * 'table' => $table, - * 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT", - * 'save' => "NULL, titel, content, datetime" - * ); - * $res = $db->query( $db->getSpecialQuery('alter', $args)); - */ - $rows = strtr($args['rows'], $this->keywords); - - $q = array( - 'BEGIN TRANSACTION', - "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})", - "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}", - "DROP TABLE {$args['table']}", - "CREATE TABLE {$args['table']} ({$args['rows']})", - "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup", - "DROP TABLE {$args['table']}_backup", - 'COMMIT', - ); - - /* - * This is a dirty hack, since the above query will not get - * executed with a single query call so here the query method - * will be called directly and return a select instead. - */ - foreach ($q as $query) { - $this->query($query); - } - return "SELECT * FROM {$args['table']};"; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/storage.php b/extlib/DB/storage.php deleted file mode 100644 index 2ddc4c0b49..0000000000 --- a/extlib/DB/storage.php +++ /dev/null @@ -1,548 +0,0 @@ - - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB class so it can be extended from - */ -require_once 'DB.php'; - -/** - * Provides an object interface to a table row - * - * It lets you add, delete and change rows using objects rather than SQL - * statements. - * - * @category Database - * @package DB - * @author Stig Bakken - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_storage extends PEAR -{ - // {{{ properties - - /** the name of the table (or view, if the backend database supports - * updates in views) we hold data from */ - public $_table = null; - - /** which column(s) in the table contains primary keys, can be a - * string for single-column primary keys, or an array of strings - * for multiple-column primary keys */ - public $_keycolumn = null; - - /** DB connection handle used for all transactions */ - public $_dbh = null; - - /** an assoc with the names of database fields stored as properties - * in this object */ - public $_properties = array(); - - /** an assoc with the names of the properties in this object that - * have been changed since they were fetched from the database */ - public $_changes = array(); - - /** flag that decides if data in this object can be changed. - * objects that don't have their table's key column in their - * property lists will be flagged as read-only. */ - public $_readonly = false; - - /** function or method that implements a validator for fields that - * are set, this validator function returns true if the field is - * valid, false if not */ - public $_validator = null; - - // }}} - // {{{ constructor - - /** - * Constructor - * - * @param $table string the name of the database table - * - * @param $keycolumn mixed string with name of key column, or array of - * strings if the table has a primary key of more than one column - * - * @param $dbh object database connection object - * - * @param $validator mixed function or method used to validate - * each new value, called with three parameters: the name of the - * field/column that is changing, a reference to the new value and - * a reference to this object - * - */ - public function __construct($table, $keycolumn, &$dbh, $validator = null) - { - $this->PEAR('DB_Error'); - $this->_table = $table; - $this->_keycolumn = $keycolumn; - $this->_dbh = $dbh; - $this->_readonly = false; - $this->_validator = $validator; - } - - // }}} - // {{{ _makeWhere() - - /** - * Create a new (empty) row in the configured table for this - * object. - * @param $newpk - * @return |null - */ - public function insert($newpk) - { - if (is_array($this->_keycolumn)) { - $primarykey = $this->_keycolumn; - } else { - $primarykey = array($this->_keycolumn); - } - settype($newpk, "array"); - for ($i = 0; $i < sizeof($primarykey); $i++) { - $pkvals[] = $this->_dbh->quote($newpk[$i]); - } - - $sth = $this->_dbh->query("INSERT INTO $this->_table (" . - implode(",", $primarykey) . ") VALUES(" . - implode(",", $pkvals) . ")"); - if (DB::isError($sth)) { - return $sth; - } - if (sizeof($newpk) == 1) { - $newpk = $newpk[0]; - } - $this->setup($newpk); - return null; - } - - // }}} - // {{{ setup() - - /** - * Method used to initialize a DB_storage object from the - * configured table. - * - * @param $keyval mixed the key[s] of the row to fetch (string or array) - * - * @return int|object - */ - public function setup($keyval) - { - $whereclause = $this->_makeWhere($keyval); - $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; - $sth = $this->_dbh->query($query); - if (DB::isError($sth)) { - return $sth; - } - $row = $sth->fetchRow(DB_FETCHMODE_ASSOC); - if (DB::isError($row)) { - return $row; - } - if (!$row) { - return $this->raiseError( - null, - DB_ERROR_NOT_FOUND, - null, - null, - $query, - null, - true - ); - } - foreach ($row as $key => $value) { - $this->_properties[$key] = true; - $this->$key = $value; - } - return DB_OK; - } - - // }}} - // {{{ insert() - - /** - * Utility method to build a "WHERE" clause to locate ourselves in - * the table. - * - * XXX future improvement: use rowids? - * - * @access private - * @param null $keyval - * @return mixed|string|null - */ - public function _makeWhere($keyval = null) - { - if (is_array($this->_keycolumn)) { - if ($keyval === null) { - for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { - $keyval[] = $this->{$this->_keycolumn[$i]}; - } - } - $whereclause = ''; - for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { - if ($i > 0) { - $whereclause .= ' AND '; - } - $whereclause .= $this->_keycolumn[$i]; - if (is_null($keyval[$i])) { - // there's not much point in having a NULL key, - // but we support it anyway - $whereclause .= ' IS NULL'; - } else { - $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); - } - } - } else { - if ($keyval === null) { - $keyval = @$this->{$this->_keycolumn}; - } - $whereclause = $this->_keycolumn; - if (is_null($keyval)) { - // there's not much point in having a NULL key, - // but we support it anyway - $whereclause .= ' IS NULL'; - } else { - $whereclause .= ' = ' . $this->_dbh->quote($keyval); - } - } - return $whereclause; - } - - // }}} - // {{{ toString() - - /** - * Output a simple description of this DB_storage object. - * @return string object description - */ - public function toString() - { - $info = strtolower(get_class($this)); - $info .= " (table="; - $info .= $this->_table; - $info .= ", keycolumn="; - if (is_array($this->_keycolumn)) { - $info .= "(" . implode(",", $this->_keycolumn) . ")"; - } else { - $info .= $this->_keycolumn; - } - $info .= ", dbh="; - if (is_object($this->_dbh)) { - $info .= $this->_dbh->toString(); - } else { - $info .= "null"; - } - $info .= ")"; - if (sizeof($this->_properties)) { - $info .= " [loaded, key="; - $keyname = $this->_keycolumn; - if (is_array($keyname)) { - $info .= "("; - for ($i = 0; $i < sizeof($keyname); $i++) { - if ($i > 0) { - $info .= ","; - } - $info .= $this->$keyname[$i]; - } - $info .= ")"; - } else { - $info .= $this->$keyname; - } - $info .= "]"; - } - if (sizeof($this->_changes)) { - $info .= " [modified]"; - } - return $info; - } - - // }}} - // {{{ dump() - - /** - * Dump the contents of this object to "standard output". - */ - public function dump() - { - foreach ($this->_properties as $prop => $foo) { - print "$prop = "; - print htmlentities($this->$prop); - print "
\n"; - } - } - - // }}} - // {{{ &create() - - /** - * Static method used to create new DB storage objects. - * @param $table - * @param $data assoc. array where the keys are the names - * of properties/columns - * @return object a new instance of DB_storage or a subclass of it - */ - public function &create($table, &$data) - { - $classname = strtolower(get_class($this)); - $obj = new $classname($table); - foreach ($data as $name => $value) { - $obj->_properties[$name] = true; - $obj->$name = &$value; - } - return $obj; - } - - // }}} - // {{{ loadFromQuery() - - /** - * Loads data into this object from the given query. If this - * object already contains table data, changes will be saved and - * the object re-initialized first. - * - * @param $query SQL query - * - * @param $params parameter list in case you want to use - * prepare/execute mode - * - * @return int DB_OK on success, DB_WARNING_READ_ONLY if the - * returned object is read-only (because the object's specified - * key column was not found among the columns returned by $query), - * or another DB error code in case of errors. - */ - // XXX commented out for now - /* - function loadFromQuery($query, $params = null) - { - if (sizeof($this->_properties)) { - if (sizeof($this->_changes)) { - $this->store(); - $this->_changes = array(); - } - $this->_properties = array(); - } - $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); - if (DB::isError($rowdata)) { - return $rowdata; - } - reset($rowdata); - $found_keycolumn = false; - while (list($key, $value) = each($rowdata)) { - if ($key == $this->_keycolumn) { - $found_keycolumn = true; - } - $this->_properties[$key] = true; - $this->$key = &$value; - unset($value); // have to unset, or all properties will - // refer to the same value - } - if (!$found_keycolumn) { - $this->_readonly = true; - return DB_WARNING_READ_ONLY; - } - return DB_OK; - } - */ - - // }}} - // {{{ set() - - /** - * Modify an attriute value. - * @param $property - * @param $newvalue - * @return bool|object - */ - public function set($property, $newvalue) - { - // only change if $property is known and object is not - // read-only - if ($this->_readonly) { - return $this->raiseError( - null, - DB_WARNING_READ_ONLY, - null, - null, - null, - null, - true - ); - } - if (@isset($this->_properties[$property])) { - if (empty($this->_validator)) { - $valid = true; - } else { - $valid = @call_user_func( - $this->_validator, - $this->_table, - $property, - $newvalue, - $this->$property, - $this - ); - } - if ($valid) { - $this->$property = $newvalue; - if (empty($this->_changes[$property])) { - $this->_changes[$property] = 0; - } else { - $this->_changes[$property]++; - } - } else { - return $this->raiseError( - null, - DB_ERROR_INVALID, - null, - null, - "invalid field: $property", - null, - true - ); - } - return true; - } - return $this->raiseError( - null, - DB_ERROR_NOSUCHFIELD, - null, - null, - "unknown field: $property", - null, - true - ); - } - - // }}} - // {{{ &get() - - /** - * Fetch an attribute value. - * - * @param string attribute name - * - * @return attribute contents, or null if the attribute name is - * unknown - */ - public function &get($property) - { - // only return if $property is known - if (isset($this->_properties[$property])) { - return $this->$property; - } - $tmp = null; - return $tmp; - } - - // }}} - // {{{ _DB_storage() - - /** - * Destructor, calls DB_storage::store() if there are changes - * that are to be kept. - */ - public function _DB_storage() - { - if (sizeof($this->_changes)) { - $this->store(); - } - $this->_properties = array(); - $this->_changes = array(); - $this->_table = null; - } - - // }}} - // {{{ store() - - /** - * Stores changes to this object in the database. - * - * @return DB_OK|int - */ - public function store() - { - $params = array(); - $vars = array(); - foreach ($this->_changes as $name => $foo) { - $params[] = &$this->$name; - $vars[] = $name . ' = ?'; - } - if ($vars) { - $query = 'UPDATE ' . $this->_table . ' SET ' . - implode(', ', $vars) . ' WHERE ' . - $this->_makeWhere(); - $stmt = $this->_dbh->prepare($query); - $res = $this->_dbh->execute($stmt, $params); - if (DB::isError($res)) { - return $res; - } - $this->_changes = array(); - } - return DB_OK; - } - - // }}} - // {{{ remove() - - /** - * Remove the row represented by this object from the database. - * - * @return mixed DB_OK or a DB error - */ - public function remove() - { - if ($this->_readonly) { - return $this->raiseError( - null, - DB_WARNING_READ_ONLY, - null, - null, - null, - null, - true - ); - } - $query = 'DELETE FROM ' . $this->_table . ' WHERE ' . - $this->_makeWhere(); - $res = $this->_dbh->query($query); - if (DB::isError($res)) { - return $res; - } - foreach ($this->_properties as $prop => $foo) { - unset($this->$prop); - } - $this->_properties = array(); - $this->_changes = array(); - return DB_OK; - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/DB/sybase.php b/extlib/DB/sybase.php deleted file mode 100644 index 01f2121d48..0000000000 --- a/extlib/DB/sybase.php +++ /dev/null @@ -1,955 +0,0 @@ - - * @author Ant�nio Carlos Ven�ncio J�nior - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id$ - * @link http://pear.php.net/package/DB - */ - -/** - * Obtain the DB_common class so it can be extended from - */ -//require_once 'DB/common.php'; -require_once 'common.php'; - -/** - * The methods PEAR DB uses to interact with PHP's sybase extension - * for interacting with Sybase databases - * - * These methods overload the ones declared in DB_common. - * - * WARNING: This driver may fail with multiple connections under the - * same user/pass/host and different databases. - * - * @category Database - * @package DB - * @author Sterling Hughes - * @author Ant�nio Carlos Ven�ncio J�nior - * @author Daniel Convissor - * @copyright 1997-2007 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version Release: 1.9.2 - * @link http://pear.php.net/package/DB - */ -class DB_sybase extends DB_common -{ - // {{{ properties - - /** - * The DB driver type (mysql, oci8, odbc, etc.) - * @var string - */ - public $phptype = 'sybase'; - - /** - * The database syntax variant to be used (db2, access, etc.), if any - * @var string - */ - public $dbsyntax = 'sybase'; - - /** - * The capabilities of this DB implementation - * - * The 'new_link' element contains the PHP version that first provided - * new_link support for this DBMS. Contains false if it's unsupported. - * - * Meaning of the 'limit' element: - * + 'emulate' = emulate with fetch row by number - * + 'alter' = alter the query - * + false = skip rows - * - * @var array - */ - public $features = array( - 'limit' => 'emulate', - 'new_link' => false, - 'numrows' => true, - 'pconnect' => true, - 'prepare' => false, - 'ssl' => false, - 'transactions' => true, - ); - - /** - * A mapping of native error codes to DB error codes - * @var array - */ - public $errorcode_map = array(); - - /** - * The raw database connection created by PHP - * @var resource - */ - public $connection; - - /** - * The DSN information for connecting to a database - * @var array - */ - public $dsn = array(); - - - /** - * Should data manipulation queries be committed automatically? - * @var bool - * @access private - */ - public $autocommit = true; - - /** - * The quantity of transactions begun - * - * {@internal While this is private, it can't actually be designated - * private in PHP 5 because it is directly accessed in the test suite.}} - * - * @var integer - * @access private - */ - public $transaction_opcount = 0; - - /** - * The database specified in the DSN - * - * It's a fix to allow calls to different databases in the same script. - * - * @var string - * @access private - */ - public $_db = ''; - - - // }}} - // {{{ constructor - - /** - * This constructor calls parent::__construct() - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - // }}} - // {{{ connect() - - /** - * Connect to the database server, log in and open the database - * - * Don't call this method directly. Use DB::connect() instead. - * - * PEAR DB's sybase driver supports the following extra DSN options: - * + appname The application name to use on this connection. - * Available since PEAR DB 1.7.0. - * + charset The character set to use on this connection. - * Available since PEAR DB 1.7.0. - * - * @param array $dsn the data source name - * @param bool $persistent should the connection be persistent? - * - * @return int|object - */ - public function connect($dsn, $persistent = false) - { - if (!PEAR::loadExtension('sybase') && - !PEAR::loadExtension('sybase_ct')) { - return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); - } - - $this->dsn = $dsn; - if ($dsn['dbsyntax']) { - $this->dbsyntax = $dsn['dbsyntax']; - } - - $dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost'; - $dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false; - $dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false; - $dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false; - - $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect'; - - if ($dsn['username']) { - $this->connection = @$connect_function( - $dsn['hostspec'], - $dsn['username'], - $dsn['password'], - $dsn['charset'], - $dsn['appname'] - ); - } else { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - 'The DSN did not contain a username.' - ); - } - - if (!$this->connection) { - return $this->raiseError( - DB_ERROR_CONNECT_FAILED, - null, - null, - null, - @sybase_get_last_message() - ); - } - - if ($dsn['database']) { - if (!@sybase_select_db($dsn['database'], $this->connection)) { - return $this->raiseError( - DB_ERROR_NODBSELECTED, - null, - null, - null, - @sybase_get_last_message() - ); - } - $this->_db = $dsn['database']; - } - - return DB_OK; - } - - // }}} - // {{{ disconnect() - - /** - * Disconnects from the database server - * - * @return bool TRUE on success, FALSE on failure - */ - public function disconnect() - { - $ret = @sybase_close($this->connection); - $this->connection = null; - return $ret; - } - - // }}} - // {{{ simpleQuery() - - /** - * Sends a query to the database server - * - * @param string the SQL query string - * - * @return mixed + a PHP result resrouce for successful SELECT queries - * + the DB_OK constant for other successful queries - * + a DB_Error object on failure - */ - public function simpleQuery($query) - { - $ismanip = $this->_checkManip($query); - $this->last_query = $query; - if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { - return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); - } - $query = $this->modifyQuery($query); - if (!$this->autocommit && $ismanip) { - if ($this->transaction_opcount == 0) { - $result = @sybase_query('BEGIN TRANSACTION', $this->connection); - if (!$result) { - return $this->sybaseRaiseError(); - } - } - $this->transaction_opcount++; - } - $result = @sybase_query($query, $this->connection); - if (!$result) { - return $this->sybaseRaiseError(); - } - if (is_resource($result)) { - return $result; - } - // Determine which queries that should return data, and which - // should return an error code only. - return $ismanip ? DB_OK : $result; - } - - // }}} - // {{{ nextResult() - - /** - * Produces a DB_Error object regarding the current problem - * - * @param int $errno if the error is being manually raised pass a - * DB_ERROR* constant here. If this isn't passed - * the error information gathered from the DBMS. - * - * @return object the DB_Error object - * - * @see DB_common::raiseError(), - * DB_sybase::errorNative(), DB_sybase::errorCode() - */ - public function sybaseRaiseError($errno = null) - { - $native = $this->errorNative(); - if ($errno === null) { - $errno = $this->errorCode($native); - } - return $this->raiseError($errno, null, null, null, $native); - } - - // }}} - // {{{ fetchInto() - - /** - * Gets the DBMS' native error message produced by the last query - * - * @return string the DBMS' error message - */ - public function errorNative() - { - return @sybase_get_last_message(); - } - - // }}} - // {{{ freeResult() - - /** - * Determines PEAR::DB error code from the database's text error message. - * - * @param string $errormsg error message returned from the database - * @return integer an error number from a DB error constant - */ - public function errorCode($errormsg) - { - static $error_regexps; - - // PHP 5.2+ prepends the function name to $php_errormsg, so we need - // this hack to work around it, per bug #9599. - $errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg); - - if (!isset($error_regexps)) { - $error_regexps = array( - '/Incorrect syntax near/' - => DB_ERROR_SYNTAX, - '/^Unclosed quote before the character string [\"\'].*[\"\']\./' - => DB_ERROR_SYNTAX, - '/Implicit conversion (from datatype|of NUMERIC value)/i' - => DB_ERROR_INVALID_NUMBER, - '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./' - => DB_ERROR_NOSUCHTABLE, - '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./' - => DB_ERROR_ACCESS_VIOLATION, - '/^.+ permission denied on object .+, database .+, owner .+/' - => DB_ERROR_ACCESS_VIOLATION, - '/^.* permission denied, database .+, owner .+/' - => DB_ERROR_ACCESS_VIOLATION, - '/[^.*] not found\./' - => DB_ERROR_NOSUCHTABLE, - '/There is already an object named/' - => DB_ERROR_ALREADY_EXISTS, - '/Invalid column name/' - => DB_ERROR_NOSUCHFIELD, - '/does not allow null values/' - => DB_ERROR_CONSTRAINT_NOT_NULL, - '/Command has been aborted/' - => DB_ERROR_CONSTRAINT, - '/^Cannot drop the index .* because it doesn\'t exist/i' - => DB_ERROR_NOT_FOUND, - '/^There is already an index/i' - => DB_ERROR_ALREADY_EXISTS, - '/^There are fewer columns in the INSERT statement than values specified/i' - => DB_ERROR_VALUE_COUNT_ON_ROW, - '/Divide by zero/i' - => DB_ERROR_DIVZERO, - ); - } - - foreach ($error_regexps as $regexp => $code) { - if (preg_match($regexp, $errormsg)) { - return $code; - } - } - return DB_ERROR; - } - - // }}} - // {{{ numCols() - - /** - * Move the internal sybase result pointer to the next available result - * - * @param a valid sybase result resource - * - * @access public - * - * @return true if a result is available otherwise return false - */ - public function nextResult($result) - { - return false; - } - - // }}} - // {{{ numRows() - - /** - * Places a row from the result set into the given array - * - * Formating of the array and the data therein are configurable. - * See DB_result::fetchInto() for more information. - * - * This method is not meant to be called directly. Use - * DB_result::fetchInto() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result the query result resource - * @param array $arr the referenced array to put the data in - * @param int $fetchmode how the resulting array should be indexed - * @param int $rownum the row number to fetch (0 = first row) - * - * @return mixed DB_OK on success, NULL when the end of a result set is - * reached or on failure - * - * @see DB_result::fetchInto() - */ - public function fetchInto($result, &$arr, $fetchmode, $rownum = null) - { - if ($rownum !== null) { - if (!@sybase_data_seek($result, $rownum)) { - return null; - } - } - if ($fetchmode & DB_FETCHMODE_ASSOC) { - if (function_exists('sybase_fetch_assoc')) { - $arr = @sybase_fetch_assoc($result); - } else { - if ($arr = @sybase_fetch_array($result)) { - foreach ($arr as $key => $value) { - if (is_int($key)) { - unset($arr[$key]); - } - } - } - } - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { - $arr = array_change_key_case($arr, CASE_LOWER); - } - } else { - $arr = @sybase_fetch_row($result); - } - if (!$arr) { - return null; - } - if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { - $this->_rtrimArrayValues($arr); - } - if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { - $this->_convertNullArrayValuesToEmpty($arr); - } - return DB_OK; - } - - // }}} - // {{{ affectedRows() - - /** - * Deletes the result set and frees the memory occupied by the result set - * - * This method is not meant to be called directly. Use - * DB_result::free() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return bool TRUE on success, FALSE if $result is invalid - * - * @see DB_result::free() - */ - public function freeResult($result) - { - return is_resource($result) ? sybase_free_result($result) : false; - } - - // }}} - // {{{ nextId() - - /** - * Gets the number of columns in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numCols() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numCols() - */ - public function numCols($result) - { - $cols = @sybase_num_fields($result); - if (!$cols) { - return $this->sybaseRaiseError(); - } - return $cols; - } - - /** - * Gets the number of rows in a result set - * - * This method is not meant to be called directly. Use - * DB_result::numRows() instead. It can't be declared "protected" - * because DB_result is a separate object. - * - * @param resource $result PHP's query result resource - * - * @return int|object - * - * @see DB_result::numRows() - */ - public function numRows($result) - { - $rows = @sybase_num_rows($result); - if ($rows === false) { - return $this->sybaseRaiseError(); - } - return $rows; - } - - // }}} - // {{{ dropSequence() - - /** - * Determines the number of rows affected by a data maniuplation query - * - * 0 is returned for queries that don't manipulate data. - * - * @return int the number of rows. A DB_Error object on failure. - */ - public function affectedRows() - { - if ($this->_last_query_manip) { - $result = @sybase_affected_rows($this->connection); - } else { - $result = 0; - } - return $result; - } - - // }}} - // {{{ quoteFloat() - - /** - * Returns the next free id in a sequence - * - * @param string $seq_name name of the sequence - * @param boolean $ondemand when true, the seqence is automatically - * created if it does not exist - * - * @return int|object - * A DB_Error object on failure. - * - * @see DB_common::nextID(), DB_common::getSequenceName(), - * DB_sybase::createSequence(), DB_sybase::dropSequence() - */ - public function nextId($seq_name, $ondemand = true) - { - $seqname = $this->getSequenceName($seq_name); - if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { - return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); - } - $repeat = 0; - do { - $this->pushErrorHandling(PEAR_ERROR_RETURN); - $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); - $this->popErrorHandling(); - if ($ondemand && DB::isError($result) && - ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) { - $repeat = 1; - $result = $this->createSequence($seq_name); - if (DB::isError($result)) { - return $this->raiseError($result); - } - } elseif (!DB::isError($result)) { - $result = $this->query("SELECT @@IDENTITY FROM $seqname"); - $repeat = 0; - } else { - $repeat = false; - } - } while ($repeat); - if (DB::isError($result)) { - return $this->raiseError($result); - } - $result = $result->fetchRow(DB_FETCHMODE_ORDERED); - return $result[0]; - } - - // }}} - // {{{ autoCommit() - - /** - * Creates a new sequence - * - * @param string $seq_name name of the new sequence - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::createSequence(), DB_common::getSequenceName(), - * DB_sybase::nextID(), DB_sybase::dropSequence() - */ - public function createSequence($seq_name) - { - return $this->query('CREATE TABLE ' - . $this->getSequenceName($seq_name) - . ' (id numeric(10, 0) IDENTITY NOT NULL,' - . ' vapor int NULL)'); - } - - // }}} - // {{{ commit() - - /** - * Deletes a sequence - * - * @param string $seq_name name of the sequence to be deleted - * - * @return int DB_OK on success. A DB_Error object on failure. - * - * @see DB_common::dropSequence(), DB_common::getSequenceName(), - * DB_sybase::nextID(), DB_sybase::createSequence() - */ - public function dropSequence($seq_name) - { - return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); - } - - // }}} - // {{{ rollback() - - /** - * Formats a float value for use within a query in a locale-independent - * manner. - * - * @param float the float value to be quoted. - * @return string the quoted string. - * @see DB_common::quoteSmart() - * @since Method available since release 1.7.8. - */ - public function quoteFloat($float) - { - return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); - } - - // }}} - // {{{ sybaseRaiseError() - - /** - * Enables or disables automatic commits - * - * @param bool $onoff true turns it on, false turns it off - * - * @return int DB_OK on success. A DB_Error object if the driver - * doesn't support auto-committing transactions. - */ - public function autoCommit($onoff = false) - { - // XXX if $this->transaction_opcount > 0, we should probably - // issue a warning here. - $this->autocommit = $onoff ? true : false; - return DB_OK; - } - - // }}} - // {{{ errorNative() - - /** - * Commits the current transaction - * - * @return int|object - */ - public function commit() - { - if ($this->transaction_opcount > 0) { - if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { - return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); - } - $result = @sybase_query('COMMIT', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->sybaseRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ errorCode() - - /** - * Reverts the current transaction - * - * @return int|object - */ - public function rollback() - { - if ($this->transaction_opcount > 0) { - if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { - return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); - } - $result = @sybase_query('ROLLBACK', $this->connection); - $this->transaction_opcount = 0; - if (!$result) { - return $this->sybaseRaiseError(); - } - } - return DB_OK; - } - - // }}} - // {{{ tableInfo() - - /** - * Returns information about a table or a result set - * - * NOTE: only supports 'table' and 'flags' if $result - * is a table name. - * - * @param object|string $result DB_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|object - * A DB_Error object on failure. - * - * @see DB_common::tableInfo() - * @since Method available since Release 1.6.0 - */ - public function tableInfo($result, $mode = null) - { - if (is_string($result)) { - /* - * Probably received a table name. - * Create a result resource identifier. - */ - if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { - return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); - } - $id = @sybase_query( - "SELECT * FROM $result WHERE 1=0", - $this->connection - ); - $got_string = true; - } elseif (isset($result->result)) { - /* - * Probably received a result object. - * Extract the result resource identifier. - */ - $id = $result->result; - $got_string = false; - } else { - /* - * Probably received a result resource identifier. - * Copy it. - * Deprecated. Here for compatibility only. - */ - $id = $result; - $got_string = false; - } - - if (!is_resource($id)) { - return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA); - } - - if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { - $case_func = 'strtolower'; - } else { - $case_func = 'strval'; - } - - $count = @sybase_num_fields($id); - $res = array(); - - if ($mode) { - $res['num_fields'] = $count; - } - - for ($i = 0; $i < $count; $i++) { - $f = @sybase_fetch_field($id, $i); - // column_source is often blank - $res[$i] = array( - 'table' => $got_string - ? $case_func($result) - : $case_func($f->column_source), - 'name' => $case_func($f->name), - 'type' => $f->type, - 'len' => $f->max_length, - 'flags' => '', - ); - if ($res[$i]['table']) { - $res[$i]['flags'] = $this->_sybase_field_flags( - $res[$i]['table'], - $res[$i]['name'] - ); - } - if ($mode & DB_TABLEINFO_ORDER) { - $res['order'][$res[$i]['name']] = $i; - } - if ($mode & DB_TABLEINFO_ORDERTABLE) { - $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; - } - } - - // free the result only if we were called on a table - if ($got_string) { - @sybase_free_result($id); - } - return $res; - } - - // }}} - // {{{ _sybase_field_flags() - - /** - * Get the flags for a field - * - * Currently supports: - * + unique_key (unique index, unique check or primary_key) - * + multiple_key (multi-key index) - * - * @param string $table the table name - * @param string $column the field name - * - * @return string space delimited string of flags. Empty string if none. - * - * @access private - */ - public function _sybase_field_flags($table, $column) - { - static $tableName = null; - static $flags = array(); - - if ($table != $tableName) { - $flags = array(); - $tableName = $table; - - /* We're running sp_helpindex directly because it doesn't exist in - * older versions of ASE -- unfortunately, we can't just use - * DB::isError() because the user may be using callback error - * handling. */ - $res = @sybase_query("sp_helpindex $table", $this->connection); - - if ($res === false || $res === true) { - // Fake a valid response for BC reasons. - return ''; - } - - while (($val = sybase_fetch_assoc($res)) !== false) { - if (!isset($val['index_keys'])) { - /* No useful information returned. Break and be done with - * it, which preserves the pre-1.7.9 behaviour. */ - break; - } - - $keys = explode(', ', trim($val['index_keys'])); - - if (sizeof($keys) > 1) { - foreach ($keys as $key) { - $this->_add_flag($flags[$key], 'multiple_key'); - } - } - - if (strpos($val['index_description'], 'unique')) { - foreach ($keys as $key) { - $this->_add_flag($flags[$key], 'unique_key'); - } - } - } - - sybase_free_result($res); - } - - if (array_key_exists($column, $flags)) { - return (implode(' ', $flags[$column])); - } - - return ''; - } - - // }}} - // {{{ _add_flag() - - /** - * Adds a string to the flags array if the flag is not yet in there - * - if there is no flag present the array is created - * - * @param array $array reference of flags array to add a value to - * @param mixed $value value to add to the flag array - * - * @return void - * - * @access private - */ - public function _add_flag(&$array, $value) - { - if (!is_array($array)) { - $array = array($value); - } elseif (!in_array($value, $array)) { - array_push($array, $value); - } - } - - // }}} - // {{{ getSpecialQuery() - - /** - * Obtains the query string needed for listing a given type of objects - * - * @param string $type the kind of objects you want to retrieve - * - * @return string the SQL query string or null if the driver doesn't - * support the object type requested - * - * @access protected - * @see DB_common::getListOf() - */ - public function getSpecialQuery($type) - { - switch ($type) { - case 'tables': - return "SELECT name FROM sysobjects WHERE type = 'U'" - . ' ORDER BY name'; - case 'views': - return "SELECT name FROM sysobjects WHERE type = 'V'"; - default: - return null; - } - } - - // }}} -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - */ diff --git a/extlib/MDB2.php b/extlib/MDB2.php new file mode 100644 index 0000000000..037009c598 --- /dev/null +++ b/extlib/MDB2.php @@ -0,0 +1,4571 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +require_once 'PEAR.php'; + +// {{{ Error constants + +/** + * The method mapErrorCode in each MDB2_dbtype implementation maps + * native error codes to one of these. + * + * If you add an error code here, make sure you also add a textual + * version of it in MDB2::errorMessage(). + */ + +define('MDB2_OK', true); +define('MDB2_ERROR', -1); +define('MDB2_ERROR_SYNTAX', -2); +define('MDB2_ERROR_CONSTRAINT', -3); +define('MDB2_ERROR_NOT_FOUND', -4); +define('MDB2_ERROR_ALREADY_EXISTS', -5); +define('MDB2_ERROR_UNSUPPORTED', -6); +define('MDB2_ERROR_MISMATCH', -7); +define('MDB2_ERROR_INVALID', -8); +define('MDB2_ERROR_NOT_CAPABLE', -9); +define('MDB2_ERROR_TRUNCATED', -10); +define('MDB2_ERROR_INVALID_NUMBER', -11); +define('MDB2_ERROR_INVALID_DATE', -12); +define('MDB2_ERROR_DIVZERO', -13); +define('MDB2_ERROR_NODBSELECTED', -14); +define('MDB2_ERROR_CANNOT_CREATE', -15); +define('MDB2_ERROR_CANNOT_DELETE', -16); +define('MDB2_ERROR_CANNOT_DROP', -17); +define('MDB2_ERROR_NOSUCHTABLE', -18); +define('MDB2_ERROR_NOSUCHFIELD', -19); +define('MDB2_ERROR_NEED_MORE_DATA', -20); +define('MDB2_ERROR_NOT_LOCKED', -21); +define('MDB2_ERROR_VALUE_COUNT_ON_ROW', -22); +define('MDB2_ERROR_INVALID_DSN', -23); +define('MDB2_ERROR_CONNECT_FAILED', -24); +define('MDB2_ERROR_EXTENSION_NOT_FOUND',-25); +define('MDB2_ERROR_NOSUCHDB', -26); +define('MDB2_ERROR_ACCESS_VIOLATION', -27); +define('MDB2_ERROR_CANNOT_REPLACE', -28); +define('MDB2_ERROR_CONSTRAINT_NOT_NULL',-29); +define('MDB2_ERROR_DEADLOCK', -30); +define('MDB2_ERROR_CANNOT_ALTER', -31); +define('MDB2_ERROR_MANAGER', -32); +define('MDB2_ERROR_MANAGER_PARSE', -33); +define('MDB2_ERROR_LOADMODULE', -34); +define('MDB2_ERROR_INSUFFICIENT_DATA', -35); +define('MDB2_ERROR_NO_PERMISSION', -36); +define('MDB2_ERROR_DISCONNECT_FAILED', -37); + +// }}} +// {{{ Verbose constants +/** + * These are just helper constants to more verbosely express parameters to prepare() + */ + +define('MDB2_PREPARE_MANIP', false); +define('MDB2_PREPARE_RESULT', null); + +// }}} +// {{{ Fetchmode constants + +/** + * This is a special constant that tells MDB2 the user hasn't specified + * any particular get mode, so the default should be used. + */ +define('MDB2_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ +define('MDB2_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ +define('MDB2_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ +define('MDB2_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results: normally the first level of arrays + * is the row number, and the second level indexed by column number or name. + * MDB2_FETCHMODE_FLIPPED switches this order, so the first level of arrays + * is the column name, and the second level the row number. + */ +define('MDB2_FETCHMODE_FLIPPED', 4); + +// }}} +// {{{ Portability mode constants + +/** + * Portability: turn off all portability features. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_NONE', 0); + +/** + * Portability: convert names of tables and fields to case defined in the + * "field_case" option when using the query*(), fetch*() and tableInfo() methods. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_FIX_CASE', 1); + +/** + * Portability: right trim the data output by query*() and fetch*(). + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_RTRIM', 2); + +/** + * Portability: force reporting the number of rows deleted. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_DELETE_COUNT', 4); + +/** + * Portability: not needed in MDB2 (just left here for compatibility to DB) + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_NUMROWS', 8); + +/** + * Portability: makes certain error messages in certain drivers compatible + * with those from other DBMS's. + * + * + mysql, mysqli: change unique/primary key constraints + * MDB2_ERROR_ALREADY_EXISTS -> MDB2_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to MDB2_ERROR_NOSUCHFIELD. + * + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_ERRORS', 16); + +/** + * Portability: convert empty values to null strings in data output by + * query*() and fetch*(). + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_EMPTY_TO_NULL', 32); + +/** + * Portability: removes database/table qualifiers from associative indexes + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES', 64); + +/** + * Portability: turn on all portability features. + * @see MDB2_Driver_Common::setOption() + */ +define('MDB2_PORTABILITY_ALL', 127); + +// }}} +// {{{ Globals for class instance tracking + +/** + * These are global variables that are used to track the various class instances + */ + +$GLOBALS['_MDB2_databases'] = array(); +$GLOBALS['_MDB2_dsninfo_default'] = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false, + 'mode' => false, +); + +// }}} +// {{{ class MDB2 + +/** + * The main 'MDB2' class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of MDB2 is as follows (indentation means inheritance): + * + * MDB2 The main MDB2 class. This is simply a utility class + * with some 'static' methods for creating MDB2 objects as + * well as common utility functions for other MDB2 classes. + * + * MDB2_Driver_Common The base for each MDB2 implementation. Provides default + * | implementations (in OO lingo virtual methods) for + * | the actual DB implementations as well as a bunch of + * | query utility functions. + * | + * +-MDB2_Driver_mysql The MDB2 implementation for MySQL. Inherits MDB2_Driver_Common. + * When calling MDB2::factory or MDB2::connect for MySQL + * connections, the object returned is an instance of this + * class. + * +-MDB2_Driver_pgsql The MDB2 implementation for PostGreSQL. Inherits MDB2_Driver_Common. + * When calling MDB2::factory or MDB2::connect for PostGreSQL + * connections, the object returned is an instance of this + * class. + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2 +{ + // {{{ function setOptions($db, $options) + + /** + * set option array in an exiting database object + * + * @param MDB2_Driver_Common MDB2 object + * @param array An associative array of option names and their values. + * + * @return mixed MDB2_OK or a PEAR Error object + * + * @access public + */ + static function setOptions($db, $options) + { + if (is_array($options)) { + foreach ($options as $option => $value) { + $test = $db->setOption($option, $value); + if (MDB2::isError($test)) { + return $test; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ function classExists($classname) + + /** + * Checks if a class exists without triggering __autoload + * + * @param string classname + * + * @return bool true success and false on error + * @static + * @access public + */ + static function classExists($classname) + { + return class_exists($classname, false); + } + + // }}} + // {{{ function loadClass($class_name, $debug) + + /** + * Loads a PEAR class. + * + * @param string classname to load + * @param bool if errors should be suppressed + * + * @return mixed true success or PEAR_Error on failure + * + * @access public + */ + static function loadClass($class_name, $debug) + { + if (!MDB2::classExists($class_name)) { + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + if ($debug) { + $include = include_once($file_name); + } else { + $include = @include_once($file_name); + } + if (!$include) { + if (!MDB2::fileExists($file_name)) { + $msg = "unable to find package '$class_name' file '$file_name'"; + } else { + $msg = "unable to load class '$class_name' from file '$file_name'"; + } + $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg); + return $err; + } + if (!MDB2::classExists($class_name)) { + $msg = "unable to load class '$class_name' from file '$file_name'"; + $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, $msg); + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function factory($dsn, $options = false) + + /** + * Create a new MDB2 object for the specified database type + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 object, or false on error + * + * @access public + */ + static function factory($dsn, $options = false) + { + $dsninfo = MDB2::parseDSN($dsn); + if (empty($dsninfo['phptype'])) { + $err = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, + null, null, 'no RDBMS driver specified'); + return $err; + } + $class_name = 'MDB2_Driver_'.$dsninfo['phptype']; + + $debug = (!empty($options['debug'])); + $err = MDB2::loadClass($class_name, $debug); + if (MDB2::isError($err)) { + return $err; + } + + $db = new $class_name(); + $db->setDSN($dsninfo); + $err = MDB2::setOptions($db, $options); + if (MDB2::isError($err)) { + return $err; + } + + return $db; + } + + // }}} + // {{{ function connect($dsn, $options = false) + + /** + * Create a new MDB2_Driver_* connection object and connect to the specified + * database + * + * @param mixed $dsn 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array $options An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 connection object, or a MDB2 + * error object on error + * + * @access public + * @see MDB2::parseDSN + */ + static function connect($dsn, $options = false) + { + $db = MDB2::factory($dsn, $options); + if (MDB2::isError($db)) { + return $db; + } + + $err = $db->connect(); + if (MDB2::isError($err)) { + $dsn = $db->getDSN('string', 'xxx'); + $db->disconnect(); + $err->addUserInfo($dsn); + return $err; + } + + return $db; + } + + // }}} + // {{{ function singleton($dsn = null, $options = false) + + /** + * Returns a MDB2 connection with the requested DSN. + * A new MDB2 connection object is only created if no object with the + * requested DSN exists yet. + * + * @param mixed 'data source name', see the MDB2::parseDSN + * method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * @param array An associative array of option names and + * their values. + * + * @return mixed a newly created MDB2 connection object, or a MDB2 + * error object on error + * + * @access public + * @see MDB2::parseDSN + */ + static function singleton($dsn = null, $options = false) + { + if ($dsn) { + $dsninfo = MDB2::parseDSN($dsn); + $dsninfo = array_merge($GLOBALS['_MDB2_dsninfo_default'], $dsninfo); + $keys = array_keys($GLOBALS['_MDB2_databases']); + for ($i=0, $j=count($keys); $i<$j; ++$i) { + if (isset($GLOBALS['_MDB2_databases'][$keys[$i]])) { + $tmp_dsn = $GLOBALS['_MDB2_databases'][$keys[$i]]->getDSN('array'); + if (count(array_diff_assoc($tmp_dsn, $dsninfo)) == 0) { + MDB2::setOptions($GLOBALS['_MDB2_databases'][$keys[$i]], $options); + return $GLOBALS['_MDB2_databases'][$keys[$i]]; + } + } + } + } elseif (is_array($GLOBALS['_MDB2_databases']) && reset($GLOBALS['_MDB2_databases'])) { + return $GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])]; + } + $db = MDB2::factory($dsn, $options); + return $db; + } + + // }}} + // {{{ function areEquals() + + /** + * It looks like there's a memory leak in array_diff() in PHP 5.1.x, + * so use this method instead. + * @see http://pear.php.net/bugs/bug.php?id=11790 + * + * @param array $arr1 + * @param array $arr2 + * @return boolean + */ + static function areEquals($arr1, $arr2) + { + if (count($arr1) != count($arr2)) { + return false; + } + foreach (array_keys($arr1) as $k) { + if (!array_key_exists($k, $arr2) || $arr1[$k] != $arr2[$k]) { + return false; + } + } + return true; + } + + // }}} + // {{{ function loadFile($file) + + /** + * load a file (like 'Date') + * + * @param string $file name of the file in the MDB2 directory (without '.php') + * + * @return string name of the file that was included + * + * @access public + */ + static function loadFile($file) + { + $file_name = 'MDB2'.DIRECTORY_SEPARATOR.$file.'.php'; + if (!MDB2::fileExists($file_name)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to find: '.$file_name); + } + if (!include_once($file_name)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to load driver class: '.$file_name); + } + return $file_name; + } + + // }}} + // {{{ function apiVersion() + + /** + * Return the MDB2 API version + * + * @return string the MDB2 API version number + * + * @access public + */ + static function apiVersion() + { + return '@package_version@'; + } + + // }}} + // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed int error code + * + * @param int error mode, see PEAR_Error docs + * + * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * + * @param string Extra debug information. Defaults to the last + * query and native error code. + * + * @return PEAR_Error instance of a PEAR Error object + * + * @access private + * @see PEAR_Error + */ + public static function &raiseError($code = null, + $mode = null, + $options = null, + $userinfo = null, + $dummy1 = null, + $dummy2 = null, + $dummy3 = false) + { + $pear = new PEAR; + $err = $pear->raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); + return $err; + } + + // }}} + // {{{ function isError($data, $code = null) + + /** + * Tell whether a value is a MDB2 error. + * + * @param mixed the value to test + * @param int if is an error object, return true + * only if $code is a string and + * $db->getMessage() == $code or + * $code is an integer and $db->getCode() == $code + * + * @return bool true if parameter is an error + * + * @access public + */ + static function isError($data, $code = null) + { + if ($data instanceof MDB2_Error) { + if (null === $code) { + return true; + } + if (is_string($code)) { + return $data->getMessage() === $code; + } + return in_array($data->getCode(), (array)$code); + } + return false; + } + + // }}} + // {{{ function isConnection($value) + + /** + * Tell whether a value is a MDB2 connection + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 connection + * @access public + */ + static function isConnection($value) + { + return ($value instanceof MDB2_Driver_Common); + } + + // }}} + // {{{ function isResult($value) + + /** + * Tell whether a value is a MDB2 result + * + * @param mixed $value value to test + * + * @return bool whether $value is a MDB2 result + * + * @access public + */ + static function isResult($value) + { + return ($value instanceof MDB2_Result); + } + + // }}} + // {{{ function isResultCommon($value) + + /** + * Tell whether a value is a MDB2 result implementing the common interface + * + * @param mixed $value value to test + * + * @return bool whether $value is a MDB2 result implementing the common interface + * + * @access public + */ + static function isResultCommon($value) + { + return ($value instanceof MDB2_Result_Common); + } + + // }}} + // {{{ function isStatement($value) + + /** + * Tell whether a value is a MDB2 statement interface + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 statement interface + * + * @access public + */ + static function isStatement($value) + { + return ($value instanceof MDB2_Statement_Common); + } + + // }}} + // {{{ function errorMessage($value = null) + + /** + * Return a textual error message for a MDB2 error code + * + * @param int|array integer error code, + null to get the current error code-message map, + or an array with a new error code-message map + * + * @return string error message, or false if the error code was + * not recognized + * + * @access public + */ + static function errorMessage($value = null) + { + static $errorMessages; + + if (is_array($value)) { + $errorMessages = $value; + return MDB2_OK; + } + + if (!isset($errorMessages)) { + $errorMessages = array( + MDB2_OK => 'no error', + MDB2_ERROR => 'unknown error', + MDB2_ERROR_ALREADY_EXISTS => 'already exists', + MDB2_ERROR_CANNOT_CREATE => 'can not create', + MDB2_ERROR_CANNOT_ALTER => 'can not alter', + MDB2_ERROR_CANNOT_REPLACE => 'can not replace', + MDB2_ERROR_CANNOT_DELETE => 'can not delete', + MDB2_ERROR_CANNOT_DROP => 'can not drop', + MDB2_ERROR_CONSTRAINT => 'constraint violation', + MDB2_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint', + MDB2_ERROR_DIVZERO => 'division by zero', + MDB2_ERROR_INVALID => 'invalid', + MDB2_ERROR_INVALID_DATE => 'invalid date or time', + MDB2_ERROR_INVALID_NUMBER => 'invalid number', + MDB2_ERROR_MISMATCH => 'mismatch', + MDB2_ERROR_NODBSELECTED => 'no database selected', + MDB2_ERROR_NOSUCHFIELD => 'no such field', + MDB2_ERROR_NOSUCHTABLE => 'no such table', + MDB2_ERROR_NOT_CAPABLE => 'MDB2 backend not capable', + MDB2_ERROR_NOT_FOUND => 'not found', + MDB2_ERROR_NOT_LOCKED => 'not locked', + MDB2_ERROR_SYNTAX => 'syntax error', + MDB2_ERROR_UNSUPPORTED => 'not supported', + MDB2_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + MDB2_ERROR_INVALID_DSN => 'invalid DSN', + MDB2_ERROR_CONNECT_FAILED => 'connect failed', + MDB2_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + MDB2_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + MDB2_ERROR_NOSUCHDB => 'no such database', + MDB2_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + MDB2_ERROR_LOADMODULE => 'error while including on demand module', + MDB2_ERROR_TRUNCATED => 'truncated', + MDB2_ERROR_DEADLOCK => 'deadlock detected', + MDB2_ERROR_NO_PERMISSION => 'no permission', + MDB2_ERROR_DISCONNECT_FAILED => 'disconnect failed', + ); + } + + if (null === $value) { + return $errorMessages; + } + + if (MDB2::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? + $errorMessages[$value] : $errorMessages[MDB2_ERROR]; + } + + // }}} + // {{{ function parseDSN($dsn) + + /** + * Parse a data source name. + * + * Additional keys can be added by appending a URI query string to the + * end of the DSN. + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true + * + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644 + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * + * @param string Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + phptype: Database backend used in PHP (mysql, odbc etc.) + * + dbsyntax: Database used with regards to SQL syntax etc. + * + protocol: Communication protocol to use (tcp, unix etc.) + * + hostspec: Host specification (hostname[:port]) + * + database: Database to use on the DBMS server + * + username: User name for login + * + password: Password for login + * + * @access public + * @author Tomas V.V.Cox + */ + static function parseDSN($dsn) + { + $parsed = $GLOBALS['_MDB2_dsninfo_default']; + + if (is_array($dsn)) { + $dsn = array_merge($parsed, $dsn); + if (!$dsn['dbsyntax']) { + $dsn['dbsyntax'] = $dsn['phptype']; + } + return $dsn; + } + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = null; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + $dsn = trim($dsn); + if (!strlen($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + // $dsn => proto(proto_opts)/database + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + $proto = $match[1]; + $proto_opts = $match[2] ? $match[2] : false; + $dsn = $match[3]; + + // $dsn => protocol+hostspec/database (old format) + } else { + if (strpos($dsn, '+') !== false) { + list($proto, $dsn) = explode('+', $dsn, 2); + } + if ( strpos($dsn, '//') === 0 + && strpos($dsn, '/', 2) !== false + && $parsed['phptype'] == 'oci8' + ) { + //oracle's "Easy Connect" syntax: + //"username/password@[//]host[:port][/service_name]" + //e.g. "scott/tiger@//mymachine:1521/oracle" + $proto_opts = $dsn; + $pos = strrpos($proto_opts, '/'); + $dsn = substr($proto_opts, $pos + 1); + $proto_opts = substr($proto_opts, 0, $pos); + } elseif (strpos($dsn, '/') !== false) { + list($proto_opts, $dsn) = explode('/', $dsn, 2); + } else { + $proto_opts = $dsn; + $dsn = null; + } + } + + // process the different protocol options + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp'; + $proto_opts = rawurldecode($proto_opts); + if (strpos($proto_opts, ':') !== false) { + list($proto_opts, $parsed['port']) = explode(':', $proto_opts); + } + if ($parsed['protocol'] == 'tcp') { + $parsed['hostspec'] = $proto_opts; + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if ($dsn) { + // /database + if (($pos = strpos($dsn, '?')) === false) { + $parsed['database'] = rawurldecode($dsn); + // /database?param1=value1¶m2=value2 + } else { + $parsed['database'] = rawurldecode(substr($dsn, 0, $pos)); + $dsn = substr($dsn, $pos + 1); + if (strpos($dsn, '&') !== false) { + $opts = explode('&', $dsn); + } else { // database?param1=value1 + $opts = array($dsn); + } + foreach ($opts as $opt) { + list($key, $value) = explode('=', $opt); + if (!array_key_exists($key, $parsed) || false === $parsed[$key]) { + // don't allow params overwrite + $parsed[$key] = rawurldecode($value); + } + } + } + } + + return $parsed; + } + + // }}} + // {{{ function fileExists($file) + + /** + * Checks if a file exists in the include path + * + * @param string filename + * + * @return bool true success and false on error + * + * @access public + */ + static function fileExists($file) + { + // safe_mode does notwork with is_readable() + if (!@ini_get('safe_mode')) { + $dirs = explode(PATH_SEPARATOR, ini_get('include_path')); + foreach ($dirs as $dir) { + if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) { + return true; + } + } + } else { + $fp = @fopen($file, 'r', true); + if (is_resource($fp)) { + @fclose($fp); + return true; + } + } + return false; + } + // }}} +} + +// }}} +// {{{ class MDB2_Error extends PEAR_Error + +/** + * MDB2_Error implements a class for reporting portable database error + * messages. + * + * @package MDB2 + * @category Database + * @author Stig Bakken + */ +class MDB2_Error extends PEAR_Error +{ + // {{{ constructor: function MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null) + + /** + * MDB2_Error constructor. + * + * @param mixed MDB2 error code, or string with error message. + * @param int what 'error mode' to operate in + * @param int what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param mixed additional debug info, such as the last query + */ + function __construct($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null, $dummy = null) + { + if (null === $code) { + $code = MDB2_ERROR; + } + $this->PEAR_Error('MDB2 Error: '.MDB2::errorMessage($code), $code, + $mode, $level, $debuginfo); + } + + // }}} +} + +// }}} +// {{{ class MDB2_Driver_Common extends PEAR + +/** + * MDB2_Driver_Common: Base class that is extended by each MDB2 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Common +{ + // {{{ Variables (Properties) + + /** + * @var MDB2_Driver_Datatype_Common + */ + public $datatype; + + /** + * @var MDB2_Extended + */ + public $extended; + + /** + * @var MDB2_Driver_Function_Common + */ + public $function; + + /** + * @var MDB2_Driver_Manager_Common + */ + public $manager; + + /** + * @var MDB2_Driver_Native_Commonn + */ + public $native; + + /** + * @var MDB2_Driver_Reverse_Common + */ + public $reverse; + + /** + * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array + * @var int + * @access public + */ + public $db_index = 0; + + /** + * DSN used for the next query + * @var array + * @access protected + */ + public $dsn = array(); + + /** + * DSN that was used to create the current connection + * @var array + * @access protected + */ + public $connected_dsn = array(); + + /** + * connection resource + * @var mixed + * @access protected + */ + public $connection = 0; + + /** + * if the current opened connection is a persistent connection + * @var bool + * @access protected + */ + public $opened_persistent; + + /** + * the name of the database for the next query + * @var string + * @access public + */ + public $database_name = ''; + + /** + * the name of the database currently selected + * @var string + * @access protected + */ + public $connected_database_name = ''; + + /** + * server version information + * @var string + * @access protected + */ + public $connected_server_info = ''; + + /** + * list of all supported features of the given driver + * @var array + * @access public + */ + public $supported = array( + 'sequences' => false, + 'indexes' => false, + 'affected_rows' => false, + 'summary_functions' => false, + 'order_by_text' => false, + 'transactions' => false, + 'savepoints' => false, + 'current_id' => false, + 'limit_queries' => false, + 'LOBs' => false, + 'replace' => false, + 'sub_selects' => false, + 'triggers' => false, + 'auto_increment' => false, + 'primary_key' => false, + 'result_introspection' => false, + 'prepared_statements' => false, + 'identifier_quoting' => false, + 'pattern_escaping' => false, + 'new_link' => false, + ); + + /** + * Array of supported options that can be passed to the MDB2 instance. + * + * The options can be set during object creation, using + * MDB2::connect(), MDB2::factory() or MDB2::singleton(). The options can + * also be set after the object is created, using MDB2::setOptions() or + * MDB2_Driver_Common::setOption(). + * The list of available option includes: + *
    + *
  • $options['ssl'] -> boolean: determines if ssl should be used for connections
  • + *
  • $options['field_case'] -> CASE_LOWER|CASE_UPPER: determines what case to force on field/table names
  • + *
  • $options['disable_query'] -> boolean: determines if queries should be executed
  • + *
  • $options['result_class'] -> string: class used for result sets
  • + *
  • $options['buffered_result_class'] -> string: class used for buffered result sets
  • + *
  • $options['result_wrap_class'] -> string: class used to wrap result sets into
  • + *
  • $options['result_buffering'] -> boolean should results be buffered or not?
  • + *
  • $options['fetch_class'] -> string: class to use when fetch mode object is used
  • + *
  • $options['persistent'] -> boolean: persistent connection?
  • + *
  • $options['debug'] -> integer: numeric debug level
  • + *
  • $options['debug_handler'] -> string: function/method that captures debug messages
  • + *
  • $options['debug_expanded_output'] -> bool: BC option to determine if more context information should be send to the debug handler
  • + *
  • $options['default_text_field_length'] -> integer: default text field length to use
  • + *
  • $options['lob_buffer_length'] -> integer: LOB buffer length
  • + *
  • $options['log_line_break'] -> string: line-break format
  • + *
  • $options['idxname_format'] -> string: pattern for index name
  • + *
  • $options['seqname_format'] -> string: pattern for sequence name
  • + *
  • $options['savepoint_format'] -> string: pattern for auto generated savepoint names
  • + *
  • $options['statement_format'] -> string: pattern for prepared statement names
  • + *
  • $options['seqcol_name'] -> string: sequence column name
  • + *
  • $options['quote_identifier'] -> boolean: if identifier quoting should be done when check_option is used
  • + *
  • $options['use_transactions'] -> boolean: if transaction use should be enabled
  • + *
  • $options['decimal_places'] -> integer: number of decimal places to handle
  • + *
  • $options['portability'] -> integer: portability constant
  • + *
  • $options['modules'] -> array: short to long module name mapping for __call()
  • + *
  • $options['emulate_prepared'] -> boolean: force prepared statements to be emulated
  • + *
  • $options['datatype_map'] -> array: map user defined datatypes to other primitive datatypes
  • + *
  • $options['datatype_map_callback'] -> array: callback function/method that should be called
  • + *
  • $options['bindname_format'] -> string: regular expression pattern for named parameters
  • + *
  • $options['multi_query'] -> boolean: determines if queries returning multiple result sets should be executed
  • + *
  • $options['max_identifiers_length'] -> integer: max identifier length
  • + *
  • $options['default_fk_action_onupdate'] -> string: default FOREIGN KEY ON UPDATE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']
  • + *
  • $options['default_fk_action_ondelete'] -> string: default FOREIGN KEY ON DELETE action ['RESTRICT'|'NO ACTION'|'SET DEFAULT'|'SET NULL'|'CASCADE']
  • + *
+ * + * @var array + * @access public + * @see MDB2::connect() + * @see MDB2::factory() + * @see MDB2::singleton() + * @see MDB2_Driver_Common::setOption() + */ + public $options = array( + 'ssl' => false, + 'field_case' => CASE_LOWER, + 'disable_query' => false, + 'result_class' => 'MDB2_Result_%s', + 'buffered_result_class' => 'MDB2_BufferedResult_%s', + 'result_wrap_class' => false, + 'result_buffering' => true, + 'fetch_class' => 'stdClass', + 'persistent' => false, + 'debug' => 0, + 'debug_handler' => 'MDB2_defaultDebugOutput', + 'debug_expanded_output' => false, + 'default_text_field_length' => 4096, + 'lob_buffer_length' => 8192, + 'log_line_break' => "\n", + 'idxname_format' => '%s_idx', + 'seqname_format' => '%s_seq', + 'savepoint_format' => 'MDB2_SAVEPOINT_%s', + 'statement_format' => 'MDB2_STATEMENT_%1$s_%2$s', + 'seqcol_name' => 'sequence', + 'quote_identifier' => false, + 'use_transactions' => true, + 'decimal_places' => 2, + 'portability' => MDB2_PORTABILITY_ALL, + 'modules' => array( + 'ex' => 'Extended', + 'dt' => 'Datatype', + 'mg' => 'Manager', + 'rv' => 'Reverse', + 'na' => 'Native', + 'fc' => 'Function', + ), + 'emulate_prepared' => false, + 'datatype_map' => array(), + 'datatype_map_callback' => array(), + 'nativetype_map_callback' => array(), + 'lob_allow_url_include' => false, + 'bindname_format' => '(?:\d+)|(?:[a-zA-Z][a-zA-Z0-9_]*)', + 'max_identifiers_length' => 30, + 'default_fk_action_onupdate' => 'RESTRICT', + 'default_fk_action_ondelete' => 'RESTRICT', + ); + + /** + * string array + * @var string + * @access public + */ + public $string_quoting = array( + 'start' => "'", + 'end' => "'", + 'escape' => false, + 'escape_pattern' => false, + ); + + /** + * identifier quoting + * @var array + * @access public + */ + public $identifier_quoting = array( + 'start' => '"', + 'end' => '"', + 'escape' => '"', + ); + + /** + * sql comments + * @var array + * @access protected + */ + public $sql_comments = array( + array('start' => '--', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + /** + * comparision wildcards + * @var array + * @access protected + */ + protected $wildcards = array('%', '_'); + + /** + * column alias keyword + * @var string + * @access protected + */ + public $as_keyword = ' AS '; + + /** + * warnings + * @var array + * @access protected + */ + public $warnings = array(); + + /** + * string with the debugging information + * @var string + * @access public + */ + public $debug_output = ''; + + /** + * determine if there is an open transaction + * @var bool + * @access protected + */ + public $in_transaction = false; + + /** + * the smart transaction nesting depth + * @var int + * @access protected + */ + public $nested_transaction_counter = null; + + /** + * the first error that occured inside a nested transaction + * @var MDB2_Error|bool + * @access protected + */ + protected $has_transaction_error = false; + + /** + * result offset used in the next query + * @var int + * @access public + */ + public $offset = 0; + + /** + * result limit used in the next query + * @var int + * @access public + */ + public $limit = 0; + + /** + * Database backend used in PHP (mysql, odbc etc.) + * @var string + * @access public + */ + public $phptype; + + /** + * Database used with regards to SQL syntax etc. + * @var string + * @access public + */ + public $dbsyntax; + + /** + * the last query sent to the driver + * @var string + * @access public + */ + public $last_query; + + /** + * the default fetchmode used + * @var int + * @access public + */ + public $fetchmode = MDB2_FETCHMODE_ORDERED; + + /** + * array of module instances + * @var array + * @access protected + */ + protected $modules = array(); + + /** + * determines of the PHP4 destructor emulation has been enabled yet + * @var array + * @access protected + */ + protected $destructor_registered = true; + + /** + * @var PEAR + */ + protected $pear; + + // }}} + // {{{ constructor: function __construct() + + /** + * Constructor + */ + function __construct() + { + end($GLOBALS['_MDB2_databases']); + $db_index = key($GLOBALS['_MDB2_databases']) + 1; + $GLOBALS['_MDB2_databases'][$db_index] = &$this; + $this->db_index = $db_index; + $this->pear = new PEAR; + } + + // }}} + // {{{ destructor: function __destruct() + + /** + * Destructor + */ + function __destruct() + { + $this->disconnect(false); + } + + // }}} + // {{{ function free() + + /** + * Free the internal references so that the instance can be destroyed + * + * @return bool true on success, false if result is invalid + * + * @access public + */ + function free() + { + unset($GLOBALS['_MDB2_databases'][$this->db_index]); + unset($this->db_index); + return MDB2_OK; + } + + // }}} + // {{{ function __toString() + + /** + * String conversation + * + * @return string representation of the object + * + * @access public + */ + function __toString() + { + $info = get_class($this); + $info.= ': (phptype = '.$this->phptype.', dbsyntax = '.$this->dbsyntax.')'; + if ($this->connection) { + $info.= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ function errorInfo($error = null) + + /** + * This method is used to collect information about an error + * + * @param mixed error code or resource + * + * @return array with MDB2 errorcode, native error code, native message + * + * @access public + */ + function errorInfo($error = null) + { + return array($error, null, null); + } + + // }}} + // {{{ function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed $code integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int $mode error mode, see PEAR_Error docs + * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string $userinfo Extra debug information. Defaults to the last + * query and native error code. + * @param string $method name of the method that triggered the error + * @param string $dummy1 not used + * @param bool $dummy2 not used + * + * @return PEAR_Error instance of a PEAR Error object + * @access public + * @see PEAR_Error + */ + function &raiseError($code = null, + $mode = null, + $options = null, + $userinfo = null, + $method = null, + $dummy1 = null, + $dummy2 = false + ) { + $userinfo = "[Error message: $userinfo]\n"; + // The error is yet a MDB2 error object + if (MDB2::isError($code)) { + // because we use the static PEAR::raiseError, our global + // handler should be used if it is set + if ((null === $mode) && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + if (null === $userinfo) { + $userinfo = $code->getUserinfo(); + } + $code = $code->getCode(); + } elseif ($code == MDB2_ERROR_NOT_FOUND) { + // extension not loaded: don't call $this->errorInfo() or the script + // will die + } elseif (isset($this->connection)) { + if (!empty($this->last_query)) { + $userinfo.= "[Last executed query: {$this->last_query}]\n"; + } + $native_errno = $native_msg = null; + list($code, $native_errno, $native_msg) = $this->errorInfo($code); + if ((null !== $native_errno) && $native_errno !== '') { + $userinfo.= "[Native code: $native_errno]\n"; + } + if ((null !== $native_msg) && $native_msg !== '') { + $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n"; + } + if (null !== $method) { + $userinfo = $method.': '.$userinfo; + } + } + + $err = $this->pear->raiseError(null, $code, $mode, $options, $userinfo, 'MDB2_Error', true); + if ($err->getMode() !== PEAR_ERROR_RETURN + && isset($this->nested_transaction_counter) && !$this->has_transaction_error) { + $this->has_transaction_error = $err; + } + return $err; + } + + // }}} + // {{{ function resetWarnings() + + /** + * reset the warning array + * + * @return void + * + * @access public + */ + function resetWarnings() + { + $this->warnings = array(); + } + + // }}} + // {{{ function getWarnings() + + /** + * Get all warnings in reverse order. + * This means that the last warning is the first element in the array + * + * @return array with warnings + * + * @access public + * @see resetWarnings() + */ + function getWarnings() + { + return array_reverse($this->warnings); + } + + // }}} + // {{{ function setFetchMode($fetchmode, $object_class = 'stdClass') + + /** + * Sets which fetch mode should be used by default on queries + * on this connection + * + * @param int MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC + * or MDB2_FETCHMODE_OBJECT + * @param string the class name of the object to be returned + * by the fetch methods when the + * MDB2_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the possibility to use + * and extend the 'MDB2_row' class. + * + * @return mixed MDB2_OK or MDB2 Error Object + * + * @access public + * @see MDB2_FETCHMODE_ORDERED, MDB2_FETCHMODE_ASSOC, MDB2_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case MDB2_FETCHMODE_OBJECT: + $this->options['fetch_class'] = $object_class; + case MDB2_FETCHMODE_ORDERED: + case MDB2_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'invalid fetchmode mode', __FUNCTION__); + } + + return MDB2_OK; + } + + // }}} + // {{{ 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) + { + if (array_key_exists($option, $this->options)) { + $this->options[$option] = $value; + return MDB2_OK; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown option $option", __FUNCTION__); + } + + // }}} + // {{{ function getOption($option) + + /** + * Returns the value of an option + * + * @param string option name + * + * @return mixed the option value or error object + * + * @access public + */ + function getOption($option) + { + if (array_key_exists($option, $this->options)) { + return $this->options[$option]; + } + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown option $option", __FUNCTION__); + } + + // }}} + // {{{ function debug($message, $scope = '', $is_manip = null) + + /** + * set a debug message + * + * @param string message that should be appended to the debug variable + * @param string usually the method name that triggered the debug call: + * for example 'query', 'prepare', 'execute', 'parameters', + * 'beginTransaction', 'commit', 'rollback' + * @param array contains context information about the debug() call + * common keys are: is_manip, time, result etc. + * + * @return void + * + * @access public + */ + function debug($message, $scope = '', $context = array()) + { + if ($this->options['debug'] && $this->options['debug_handler']) { + if (!$this->options['debug_expanded_output']) { + if (!empty($context['when']) && $context['when'] !== 'pre') { + return null; + } + $context = empty($context['is_manip']) ? false : $context['is_manip']; + } + return call_user_func_array($this->options['debug_handler'], array(&$this, $scope, $message, $context)); + } + return null; + } + + // }}} + // {{{ function getDebugOutput() + + /** + * output debug info + * + * @return string content of the debug_output class variable + * + * @access public + */ + function getDebugOutput() + { + return $this->debug_output; + } + + // }}} + // {{{ function escape($text) + + /** + * 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); + } + + $text = str_replace($this->string_quoting['end'], $this->string_quoting['escape'] . $this->string_quoting['end'], $text); + return $text; + } + + // }}} + // {{{ function escapePattern($text) + + /** + * Quotes pattern (% and _) characters in a string) + * + * @param string the input string to quote + * + * @return string quoted string + * + * @access public + */ + function escapePattern($text) + { + if ($this->string_quoting['escape_pattern']) { + $text = str_replace($this->string_quoting['escape_pattern'], $this->string_quoting['escape_pattern'] . $this->string_quoting['escape_pattern'], $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, $this->string_quoting['escape_pattern'] . $wildcard, $text); + } + } + return $text; + } + + // }}} + // {{{ function quoteIdentifier($str, $check_option = false) + + /** + * Quote a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * NOTE: if you have table names containing periods, don't use this method + * (@see bug #11906) + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (`) -- due to MySQL + * + double quote (") -- due to Oracle + * + brackets ([ or ]) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + pgsql + * + sqlite + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string identifier name to be quoted + * @param bool check the 'quote_identifier' option + * + * @return string quoted identifier string + * + * @access public + */ + function quoteIdentifier($str, $check_option = false) + { + if ($check_option && !$this->options['quote_identifier']) { + return $str; + } + $str = str_replace($this->identifier_quoting['end'], $this->identifier_quoting['escape'] . $this->identifier_quoting['end'], $str); + $parts = explode('.', $str); + foreach (array_keys($parts) as $k) { + $parts[$k] = $this->identifier_quoting['start'] . $parts[$k] . $this->identifier_quoting['end']; + } + return implode('.', $parts); + } + + // }}} + // {{{ function getAsKeyword() + + /** + * Gets the string to alias column + * + * @return string to use when aliasing a column + */ + function getAsKeyword() + { + return $this->as_keyword; + } + + // }}} + // {{{ function getConnection() + + /** + * Returns a native connection + * + * @return mixed a valid MDB2 connection object, + * or a MDB2 error object on error + * + * @access public + */ + function getConnection() + { + $result = $this->connect(); + if (MDB2::isError($result)) { + return $result; + } + return $this->connection; + } + + // }}} + // {{{ function _fixResultArrayValues(&$row, $mode) + + /** + * Do all necessary conversions on result arrays to fix DBMS quirks + * + * @param array the array to be fixed (passed by reference) + * @param array bit-wise addition of the required portability modes + * + * @return void + * + * @access protected + */ + function _fixResultArrayValues(&$row, $mode) + { + switch ($mode) { + case MDB2_PORTABILITY_EMPTY_TO_NULL: + foreach ($row as $key => $value) { + if ($value === '') { + $row[$key] = null; + } + } + break; + case MDB2_PORTABILITY_RTRIM: + foreach ($row as $key => $value) { + if (is_string($value)) { + $row[$key] = rtrim($value); + } + } + break; + case MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES: + $tmp_row = array(); + foreach ($row as $key => $value) { + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL): + foreach ($row as $key => $value) { + if ($value === '') { + $row[$key] = null; + } elseif (is_string($value)) { + $row[$key] = rtrim($value); + } + } + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if (is_string($value)) { + $value = rtrim($value); + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if ($value === '') { + $value = null; + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + case (MDB2_PORTABILITY_RTRIM + MDB2_PORTABILITY_EMPTY_TO_NULL + MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES): + $tmp_row = array(); + foreach ($row as $key => $value) { + if ($value === '') { + $value = null; + } elseif (is_string($value)) { + $value = rtrim($value); + } + $tmp_row[preg_replace('/^(?:.*\.)?([^.]+)$/', '\\1', $key)] = $value; + } + $row = $tmp_row; + break; + } + } + + // }}} + // {{{ function loadModule($module, $property = null, $phptype_specific = null) + + /** + * loads a module + * + * @param string name of the module that should be loaded + * (only used for error messages) + * @param string name of the property into which the class will be loaded + * @param bool if the class to load for the module is specific to the + * phptype + * + * @return object on success a reference to the given module is returned + * and on failure a PEAR error + * + * @access public + */ + function loadModule($module, $property = null, $phptype_specific = null) + { + if (!$property) { + $property = strtolower($module); + } + + if (!isset($this->{$property})) { + $version = $phptype_specific; + if ($phptype_specific !== false) { + $version = true; + $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype; + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + } + if ($phptype_specific === false + || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name)) + ) { + $version = false; + $class_name = 'MDB2_'.$module; + $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; + } + + $err = MDB2::loadClass($class_name, $this->getOption('debug')); + if (MDB2::isError($err)) { + return $err; + } + + // load module in a specific version + if ($version) { + if (method_exists($class_name, 'getClassName')) { + $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index); + if ($class_name != $class_name_new) { + $class_name = $class_name_new; + $err = MDB2::loadClass($class_name, $this->getOption('debug')); + if (MDB2::isError($err)) { + return $err; + } + } + } + } + + if (!MDB2::classExists($class_name)) { + $err = $this->raiseError(MDB2_ERROR_LOADMODULE, null, null, + "unable to load module '$module' into property '$property'", __FUNCTION__); + return $err; + } + $this->{$property} = new $class_name($this->db_index); + $this->modules[$module] = $this->{$property}; + if ($version) { + // this will be used in the connect method to determine if the module + // needs to be loaded with a different version if the server + // version changed in between connects + $this->loaded_version_modules[] = $property; + } + } + + return $this->{$property}; + } + + // }}} + // {{{ function __call($method, $params) + + /** + * Calls a module method using the __call magic method + * + * @param string Method name. + * @param array Arguments. + * + * @return mixed Returned value. + */ + function __call($method, $params) + { + $module = null; + if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match) + && isset($this->options['modules'][$match[1]]) + ) { + $module = $this->options['modules'][$match[1]]; + $method = strtolower($match[2]).$match[3]; + if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) { + $result = $this->loadModule($module); + if (MDB2::isError($result)) { + return $result; + } + } + } else { + foreach ($this->modules as $key => $foo) { + if (is_object($this->modules[$key]) + && method_exists($this->modules[$key], $method) + ) { + $module = $key; + break; + } + } + } + if (null !== $module) { + return call_user_func_array(array(&$this->modules[$module], $method), $params); + } + trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $method), E_USER_ERROR); + } + + // }}} + // {{{ function beginTransaction($savepoint = null) + + /** + * 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', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + // }}} + // {{{ function commit($savepoint = null) + + /** + * 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)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'commiting transactions is not supported', __FUNCTION__); + } + + // }}} + // {{{ function rollback($savepoint = null) + + /** + * 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)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'rolling back transactions is not supported', __FUNCTION__); + } + + // }}} + // {{{ function inTransaction($ignore_nested = false) + + /** + * If a transaction is currently open. + * + * @param bool if the nested transaction count should be ignored + * @return int|bool - an integer with the nesting depth is returned if a + * nested transaction is open + * - true is returned for a normal open transaction + * - false is returned if no transaction is open + * + * @access public + */ + function inTransaction($ignore_nested = false) + { + if (!$ignore_nested && isset($this->nested_transaction_counter)) { + return $this->nested_transaction_counter; + } + return $this->in_transaction; + } + + // }}} + // {{{ function setTransactionIsolation($isolation) + + /** + * 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)); + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level setting is not supported', __FUNCTION__); + } + + // }}} + // {{{ function beginNestedTransaction($savepoint = false) + + /** + * Start a nested transaction. + * + * @return mixed MDB2_OK on success/savepoint name, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function beginNestedTransaction() + { + if ($this->in_transaction) { + ++$this->nested_transaction_counter; + $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); + if ($this->supports('savepoints') && $savepoint) { + return $this->beginTransaction($savepoint); + } + return MDB2_OK; + } + $this->has_transaction_error = false; + $result = $this->beginTransaction(); + $this->nested_transaction_counter = 1; + return $result; + } + + // }}} + // {{{ function completeNestedTransaction($force_rollback = false, $release = false) + + /** + * Finish a nested transaction by rolling back if an error occured or + * committing otherwise. + * + * @param bool if the transaction should be rolled back regardless + * even if no error was set within the nested transaction + * @return mixed MDB_OK on commit/counter decrementing, false on rollback + * and a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function completeNestedTransaction($force_rollback = false) + { + if ($this->nested_transaction_counter > 1) { + $savepoint = sprintf($this->options['savepoint_format'], $this->nested_transaction_counter); + if ($this->supports('savepoints') && $savepoint) { + if ($force_rollback || $this->has_transaction_error) { + $result = $this->rollback($savepoint); + if (!MDB2::isError($result)) { + $result = false; + $this->has_transaction_error = false; + } + } else { + $result = $this->commit($savepoint); + } + } else { + $result = MDB2_OK; + } + --$this->nested_transaction_counter; + return $result; + } + + $this->nested_transaction_counter = null; + $result = MDB2_OK; + + // transaction has not yet been rolled back + if ($this->in_transaction) { + if ($force_rollback || $this->has_transaction_error) { + $result = $this->rollback(); + if (!MDB2::isError($result)) { + $result = false; + } + } else { + $result = $this->commit(); + } + } + $this->has_transaction_error = false; + return $result; + } + + // }}} + // {{{ function failNestedTransaction($error = null, $immediately = false) + + /** + * Force setting nested transaction to failed. + * + * @param mixed value to return in getNestededTransactionError() + * @param bool if the transaction should be rolled back immediately + * @return bool MDB2_OK + * + * @access public + * @since 2.1.1 + */ + function failNestedTransaction($error = null, $immediately = false) + { + if (null !== $error) { + $error = $this->has_transaction_error ? $this->has_transaction_error : true; + } elseif (!$error) { + $error = true; + } + $this->has_transaction_error = $error; + if (!$immediately) { + return MDB2_OK; + } + return $this->rollback(); + } + + // }}} + // {{{ function getNestedTransactionError() + + /** + * The first error that occured since the transaction start. + * + * @return MDB2_Error|bool MDB2 error object if an error occured or false. + * + * @access public + * @since 2.1.1 + */ + function getNestedTransactionError() + { + return $this->has_transaction_error; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ 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) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ setCharset($charset, $connection = null) + + /** + * Set the charset on the current connection + * + * @param string charset + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function disconnect($force = true) + + /** + * Log out and disconnect from the database. + * + * @param boolean $force whether 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) + { + $this->connection = 0; + $this->connected_dsn = array(); + $this->connected_database_name = ''; + $this->opened_persistent = null; + $this->connected_server_info = ''; + $this->in_transaction = null; + $this->nested_transaction_counter = null; + return MDB2_OK; + } + + // }}} + // {{{ function setDatabase($name) + + /** + * Select a different database + * + * @param string name of the database that should be selected + * + * @return string name of the database previously connected to + * + * @access public + */ + function setDatabase($name) + { + $previous_database_name = (isset($this->database_name)) ? $this->database_name : ''; + $this->database_name = $name; + if (!empty($this->connected_database_name) && ($this->connected_database_name != $this->database_name)) { + $this->disconnect(false); + } + return $previous_database_name; + } + + // }}} + // {{{ function getDatabase() + + /** + * Get the current database + * + * @return string name of the database + * + * @access public + */ + function getDatabase() + { + return $this->database_name; + } + + // }}} + // {{{ function setDSN($dsn) + + /** + * set the DSN + * + * @param mixed DSN string or array + * + * @return MDB2_OK + * + * @access public + */ + function setDSN($dsn) + { + $dsn_default = $GLOBALS['_MDB2_dsninfo_default']; + $dsn = MDB2::parseDSN($dsn); + if (array_key_exists('database', $dsn)) { + $this->database_name = $dsn['database']; + unset($dsn['database']); + } + $this->dsn = array_merge($dsn_default, $dsn); + return $this->disconnect(false); + } + + // }}} + // {{{ function getDSN($type = 'string', $hidepw = false) + + /** + * return the DSN as a string + * + * @param string format to return ("array", "string") + * @param string string to hide the password with + * + * @return mixed DSN in the chosen type + * + * @access public + */ + function getDSN($type = 'string', $hidepw = false) + { + $dsn = array_merge($GLOBALS['_MDB2_dsninfo_default'], $this->dsn); + $dsn['phptype'] = $this->phptype; + $dsn['database'] = $this->database_name; + if ($hidepw) { + $dsn['password'] = $hidepw; + } + switch ($type) { + // expand to include all possible options + case 'string': + $dsn = $dsn['phptype']. + ($dsn['dbsyntax'] ? ('('.$dsn['dbsyntax'].')') : ''). + '://'.$dsn['username'].':'. + $dsn['password'].'@'.$dsn['hostspec']. + ($dsn['port'] ? (':'.$dsn['port']) : ''). + '/'.$dsn['database']; + break; + case 'array': + default: + break; + } + return $dsn; + } + + // }}} + // {{{ _isNewLinkSet() + + /** + * Check if the 'new_link' option is set + * + * @return boolean + * + * @access protected + */ + function _isNewLinkSet() + { + return (isset($this->dsn['new_link']) + && ($this->dsn['new_link'] === true + || (is_string($this->dsn['new_link']) && preg_match('/^true$/i', $this->dsn['new_link'])) + || (is_numeric($this->dsn['new_link']) && 0 != (int)$this->dsn['new_link']) + ) + ); + } + + // }}} + // {{{ function &standaloneQuery($query, $types = null, $is_manip = false) + + /** + * execute a query as database administrator + * + * @param string the SQL query + * @param mixed array that contains the types of the columns in + * the result set + * @param bool 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) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + $result = $this->_doQuery($query, $is_manip, $connection, false); + if (MDB2::isError($result)) { + return $result; + } + + if ($is_manip) { + $affected_rows = $this->_affectedRows($connection, $result); + return $affected_rows; + } + $result = $this->_wrapResult($result, $types, true, true, $limit, $offset); + return $result; + } + + // }}} + // {{{ function _modifyQuery($query, $is_manip, $limit, $offset) + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string query to modify + * @param bool if it is a DML query + * @param int limit the number of rows + * @param int start reading from given offset + * + * @return string modified query + * + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + return $query; + } + + // }}} + // {{{ function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + + /** + * Execute a query + * @param string query + * @param bool if the query is a manipulation query + * @param resource connection handle + * @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; + } + $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $err; + } + + // }}} + // {{{ function _affectedRows($connection, $result = null) + + /** + * Returns the number of rows affected + * + * @param resource result handle + * @param resource connection handle + * + * @return mixed MDB2 Error Object or the number of rows affected + * + * @access private + */ + function _affectedRows($connection, $result = null) + { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function &exec($query) + + /** + * Execute a manipulation query to the database and return the number of affected rows + * + * @param string the SQL query + * + * @return mixed number of affected rows on success, a MDB2 error on failure + * + * @access public + */ + function exec($query) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, true, $limit, $offset); + + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + $result = $this->_doQuery($query, true, $connection, $this->database_name); + if (MDB2::isError($result)) { + return $result; + } + + $affectedRows = $this->_affectedRows($connection, $result); + return $affectedRows; + } + + // }}} + // {{{ function &query($query, $types = null, $result_class = true, $result_wrap_class = false) + + /** + * Send a query to the database and return any results + * + * @param string the SQL query + * @param mixed array that contains the types of the columns in + * the result set + * @param mixed string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * + * @return mixed an MDB2_Result handle on success, a MDB2 error on failure + * + * @access public + */ + function query($query, $types = null, $result_class = true, $result_wrap_class = true) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, false, $limit, $offset); + + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + $result = $this->_doQuery($query, false, $connection, $this->database_name); + if (MDB2::isError($result)) { + return $result; + } + + $result = $this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset); + return $result; + } + + // }}} + // {{{ function _wrapResult($result_resource, $types = array(), $result_class = true, $result_wrap_class = false, $limit = null, $offset = null) + + /** + * wrap a result set into the correct class + * + * @param resource result handle + * @param mixed array that contains the types of the columns in + * the result set + * @param mixed string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * @param string number of rows to select + * @param string first row to select + * + * @return mixed an MDB2_Result, a MDB2 error on failure + * + * @access protected + */ + function _wrapResult($result_resource, $types = array(), $result_class = true, + $result_wrap_class = true, $limit = null, $offset = null) + { + if ($types === true) { + if ($this->supports('result_introspection')) { + $this->loadModule('Reverse', null, true); + $tableInfo = $this->reverse->tableInfo($result_resource); + if (MDB2::isError($tableInfo)) { + return $tableInfo; + } + $types = array(); + $types_assoc = array(); + foreach ($tableInfo as $field) { + $types[] = $field['mdb2type']; + $types_assoc[$field['name']] = $field['mdb2type']; + } + } else { + $types = null; + } + } + + if ($result_class === true) { + $result_class = $this->options['result_buffering'] + ? $this->options['buffered_result_class'] : $this->options['result_class']; + } + + if ($result_class) { + $class_name = sprintf($result_class, $this->phptype); + if (!MDB2::classExists($class_name)) { + $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class does not exist '.$class_name, __FUNCTION__); + return $err; + } + $result = new $class_name($this, $result_resource, $limit, $offset); + if (!MDB2::isResultCommon($result)) { + $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class is not extended from MDB2_Result_Common', __FUNCTION__); + return $err; + } + + if (!empty($types)) { + $err = $result->setResultTypes($types); + if (MDB2::isError($err)) { + $result->free(); + return $err; + } + } + if (!empty($types_assoc)) { + $err = $result->setResultTypes($types_assoc); + if (MDB2::isError($err)) { + $result->free(); + return $err; + } + } + + if ($result_wrap_class === true) { + $result_wrap_class = $this->options['result_wrap_class']; + } + if ($result_wrap_class) { + if (!MDB2::classExists($result_wrap_class)) { + $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result wrap class does not exist '.$result_wrap_class, __FUNCTION__); + return $err; + } + $result = new $result_wrap_class($result, $this->fetchmode); + } + + return $result; + } + + return $result_resource; + } + + // }}} + // {{{ function getServerVersion($native = false) + + /** + * return version information about the server + * + * @param bool determines if the raw version string should be returned + * + * @return mixed array with version information or row string + * + * @access public + */ + function getServerVersion($native = false) + { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function setLimit($limit, $offset = null) + + /** + * set the range of the next query + * + * @param string number of rows to select + * @param string first row to select + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function setLimit($limit, $offset = null) + { + if (!$this->supports('limit_queries')) { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'limit is not supported by this driver', __FUNCTION__); + } + $limit = (int)$limit; + if ($limit < 0) { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid selected range row limit', __FUNCTION__); + } + $this->limit = $limit; + if (null !== $offset) { + $offset = (int)$offset; + if ($offset < 0) { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid first selected range row', __FUNCTION__); + } + $this->offset = $offset; + } + return MDB2_OK; + } + + // }}} + // {{{ function subSelect($query, $type = false) + + /** + * simple subselect emulation: leaves the query untouched for all RDBMS + * that support subselects + * + * @param string the SQL query for the subselect that may only + * return a column + * @param string determines type of the field + * + * @return string the query + * + * @access public + */ + function subSelect($query, $type = false) + { + if ($this->supports('sub_selects') === true) { + return $query; + } + + if (!$this->supports('sub_selects')) { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $col = $this->queryCol($query, $type); + if (MDB2::isError($col)) { + return $col; + } + if (!is_array($col) || count($col) == 0) { + return 'NULL'; + } + if ($type) { + $this->loadModule('Datatype', null, true); + return $this->datatype->implodeArray($col, $type); + } + return implode(', ', $col); + } + + // }}} + // {{{ function replace($table, $fields) + + /** + * 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 and SQLite implement it natively, this type of + * query isemulated through this method for other DBMS using standard types + * of queries inside a transaction to assure the atomicity of the operation. + * + * @param string name of the table on which the REPLACE query will + * be executed. + * @param array 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 MDB2 + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * bool 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 + * bool 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 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function replace($table, $fields) + { + if (!$this->supports('replace')) { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'replace query is not supported', __FUNCTION__); + } + $count = count($fields); + $condition = $values = array(); + for ($colnum = 0, reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + 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); + } + $values[$name] = $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $condition[] = $this->quoteIdentifier($name, true) . '=' . $value; + } + } + if (empty($condition)) { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $result = null; + $in_transaction = $this->in_transaction; + if (!$in_transaction && MDB2::isError($result = $this->beginTransaction())) { + return $result; + } + + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + $condition = ' WHERE '.implode(' AND ', $condition); + $query = 'DELETE FROM ' . $this->quoteIdentifier($table, true) . $condition; + $result = $this->_doQuery($query, true, $connection); + if (!MDB2::isError($result)) { + $affected_rows = $this->_affectedRows($connection, $result); + $insert = ''; + foreach ($values as $key => $value) { + $insert .= ($insert?', ':'') . $this->quoteIdentifier($key, true); + } + $values = implode(', ', $values); + $query = 'INSERT INTO '. $this->quoteIdentifier($table, true) . "($insert) VALUES ($values)"; + $result = $this->_doQuery($query, true, $connection); + if (!MDB2::isError($result)) { + $affected_rows += $this->_affectedRows($connection, $result);; + } + } + + if (!$in_transaction) { + if (MDB2::isError($result)) { + $this->rollback(); + } else { + $result = $this->commit(); + } + } + + if (MDB2::isError($result)) { + return $result; + } + + return $affected_rows; + } + + // }}} + // {{{ function &prepare($query, $types = null, $result_types = null, $lobs = array()) + + /** + * 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 the query to prepare + * @param mixed array that contains the types of the placeholders + * @param mixed 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 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()) + { + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $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 (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 + } + + if ($query[$position] == $placeholder_type_guess) { + if (null === $placeholder_type) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + if (is_int(key($types))) { + $types_tmp = $types; + $types = array(); + $count = -1; + } + } else { + $types = array_values($types); + } + } + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err = MDB2_Driver_Common::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); + // use parameter name in type array + if (isset($count) && isset($types_tmp[++$count])) { + $types[$parameter] = $types_tmp[$count]; + } + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $class_name = 'MDB2_Statement_'.$this->phptype; + $statement = null; + $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ function _skipDelimitedStrings($query, $position, $p_position) + + /** + * Utility method, used by prepare() to avoid replacing placeholders within delimited strings. + * Check if the placeholder is contained within a delimited string. + * 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 + * @param integer $p_position placeholder position + * + * @return mixed integer $new_position on success + * MDB2_Error on failure + * + * @access protected + */ + function _skipDelimitedStrings($query, $position, $p_position) + { + $ignores = array(); + $ignores[] = $this->string_quoting; + $ignores[] = $this->identifier_quoting; + $ignores = array_merge($ignores, $this->sql_comments); + + foreach ($ignores as $ignore) { + if (!empty($ignore['start'])) { + if (is_int($start_quote = strpos($query, $ignore['start'], $position)) && $start_quote < $p_position) { + $end_quote = $start_quote; + do { + if (!is_int($end_quote = strpos($query, $ignore['end'], $end_quote + 1))) { + if ($ignore['end'] === "\n") { + $end_quote = strlen($query) - 1; + } else { + $err = MDB2_Driver_Common::raiseError(MDB2_ERROR_SYNTAX, null, null, + 'query with an unterminated text string specified', __FUNCTION__); + return $err; + } + } + } while ($ignore['escape'] + && $end_quote-1 != $start_quote + && $query[($end_quote - 1)] == $ignore['escape'] + && ( $ignore['escape_pattern'] !== $ignore['escape'] + || $query[($end_quote - 2)] != $ignore['escape']) + ); + + $position = $end_quote + 1; + return $position; + } + } + } + return $position; + } + + // }}} + // {{{ function quote($value, $type = null, $quote = true) + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string text string value that is intended to be converted. + * @param string type to which the value should be converted to + * @param bool quote + * @param bool escape wildcards + * + * @return string text string that represents the given argument value in + * a DBMS specific format. + * + * @access public + */ + function quote($value, $type = null, $quote = true, $escape_wildcards = false) + { + $result = $this->loadModule('Datatype', null, true); + if (MDB2::isError($result)) { + return $result; + } + + return $this->datatype->quote($value, $type, $quote, $escape_wildcards); + } + + // }}} + // {{{ function getDeclaration($type, $name, $field) + + /** + * Obtain DBMS specific SQL code portion needed to declare + * of the given type + * + * @param string type to which the value should be converted to + * @param string name the field to be declared. + * @param string 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) + { + $result = $this->loadModule('Datatype', null, true); + if (MDB2::isError($result)) { + return $result; + } + return $this->datatype->getDeclaration($type, $name, $field); + } + + // }}} + // {{{ function compareDefinition($current, $previous) + + /** + * Obtain an array of changes that may need to applied + * + * @param array new definition + * @param array old definition + * + * @return array containing all changes that will need to be applied + * + * @access public + */ + function compareDefinition($current, $previous) + { + $result = $this->loadModule('Datatype', null, true); + if (MDB2::isError($result)) { + return $result; + } + return $this->datatype->compareDefinition($current, $previous); + } + + // }}} + // {{{ function supports($feature) + + /** + * Tell whether a DB implementation or its backend extension + * supports a given feature. + * + * @param string name of the feature (see the MDB2 class doc) + * + * @return bool|string if this DB implementation supports a given feature + * false means no, true means native, + * 'emulated' means emulated + * + * @access public + */ + function supports($feature) + { + if (array_key_exists($feature, $this->supported)) { + return $this->supported[$feature]; + } + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + "unknown support feature $feature", __FUNCTION__); + } + + // }}} + // {{{ function getSequenceName($sqn) + + /** + * adds sequence name formatting to a sequence name + * + * @param string name of the sequence + * + * @return string formatted sequence name + * + * @access public + */ + function getSequenceName($sqn) + { + return sprintf($this->options['seqname_format'], + preg_replace('/[^a-z0-9_\-\$.]/i', '_', $sqn)); + } + + // }}} + // {{{ function getIndexName($idx) + + /** + * adds index name formatting to a index name + * + * @param string name of the index + * + * @return string formatted index name + * + * @access public + */ + function getIndexName($idx) + { + return sprintf($this->options['idxname_format'], + preg_replace('/[^a-z0-9_\-\$.]/i', '_', $idx)); + } + + // }}} + // {{{ function nextID($seq_name, $ondemand = true) + + /** + * Returns the next free id of a sequence + * + * @param string name of the sequence + * @param bool when true missing sequences are automatic created + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function lastInsertID($table = null, $field = null) + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string name of the table into which a new row was inserted + * @param string 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) + { + return MDB2_Driver_Common::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function currID($seq_name) + + /** + * Returns the current id of a sequence + * + * @param string name of the sequence + * + * @return mixed MDB2 Error Object or id + * + * @access public + */ + function currID($seq_name) + { + $this->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + return $this->nextID($seq_name); + } + + // }}} + // {{{ function queryOne($query, $type = null, $colnum = 0) + + /** + * Execute the specified query, fetch the value from the first column of + * the first row of the result set and then frees + * the result set. + * + * @param string $query the SELECT query statement to be executed. + * @param string $type optional argument that specifies the expected + * datatype of the result set field, so that an eventual + * conversion may be performed. The default datatype is + * text, meaning that no conversion is performed + * @param mixed $colnum the column number (or name) to fetch + * + * @return mixed MDB2_OK or field value on success, a MDB2 error on failure + * + * @access public + */ + function queryOne($query, $type = null, $colnum = 0) + { + $result = $this->query($query, $type); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $one = $result->fetchOne($colnum); + $result->free(); + return $one; + } + + // }}} + // {{{ function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + + /** + * Execute the specified query, fetch the values from the first + * row of the result set into an array and then frees + * the result set. + * + * @param string the SELECT query statement to be executed. + * @param array optional array argument that specifies a list of + * expected datatypes of the result set columns, so that the eventual + * conversions may be performed. The default list of datatypes is + * empty, meaning that no conversion is performed. + * @param int how the array data should be indexed + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryRow($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $result = $this->query($query, $types); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $row = $result->fetchRow($fetchmode); + $result->free(); + return $row; + } + + // }}} + // {{{ function queryCol($query, $type = null, $colnum = 0) + + /** + * Execute the specified query, fetch the value from the first column of + * each row of the result set into an array and then frees the result set. + * + * @param string $query the SELECT query statement to be executed. + * @param string $type optional argument that specifies the expected + * datatype of the result set field, so that an eventual + * conversion may be performed. The default datatype is text, + * meaning that no conversion is performed + * @param mixed $colnum the column number (or name) to fetch + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * @access public + */ + function queryCol($query, $type = null, $colnum = 0) + { + $result = $this->query($query, $type); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $col = $result->fetchCol($colnum); + $result->free(); + return $col; + } + + // }}} + // {{{ function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) + + /** + * Execute the specified query, fetch all the rows of the result set into + * a two dimensional array and then frees the result set. + * + * @param string the SELECT query statement to be executed. + * @param array optional array argument that specifies a list of + * expected datatypes of the result set columns, so that the eventual + * conversions may be performed. The default list of datatypes is + * empty, meaning that no conversion is performed. + * @param int how the array data should be indexed + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return mixed MDB2_OK or data array on success, a MDB2 error on failure + * + * @access public + */ + function queryAll($query, $types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, + $rekey = false, $force_array = false, $group = false) + { + $result = $this->query($query, $types); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); + $result->free(); + return $all; + } + + // }}} + // {{{ function delExpect($error_code) + + /** + * This method deletes all occurences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * + * @uses PEAR::delExpect() + */ + public function delExpect($error_code) + { + return $this->pear->delExpect($error_code); + } + + // }}} + // {{{ function expectError($code) + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * + * @uses PEAR::expectError() + */ + public function expectError($code = '*') + { + return $this->pear->expectError($code); + } + + // }}} + // {{{ function getStaticProperty($class, $var) + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + * + * @uses PEAR::getStaticProperty() + */ + public function &getStaticProperty($class, $var) + { + $tmp =& $this->pear->getStaticProperty($class, $var); + return $tmp; + } + + // }}} + // {{{ function popErrorHandling() + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + * @uses PEAR::popErrorHandling() + */ + public function popErrorHandling() + { + return $this->pear->popErrorHandling(); + } + + // }}} + // {{{ function popExpect() + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + * + * @uses PEAR::popExpect() + */ + public function popExpect() + { + return $this->pear->popExpect(); + } + + // }}} + // {{{ function pushErrorHandling($mode, $options = null) + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + * @uses PEAR::pushErrorHandling() + */ + public function pushErrorHandling($mode, $options = null) + { + return $this->pear->pushErrorHandling($mode, $options); + } + + // }}} + // {{{ function registerShutdownFunc($func, $args = array()) + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * @return void + * + * @uses PEAR::registerShutdownFunc() + */ + public function registerShutdownFunc($func, $args = array()) + { + return $this->pear->registerShutdownFunc($func, $args); + } + + // }}} + // {{{ function setErrorHandling($mode = null, $options = null) + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + * @uses PEAR::setErrorHandling($mode, $options) + */ + public function setErrorHandling($mode = null, $options = null) + { + return $this->pear->setErrorHandling($mode, $options); + } + + /** + * @uses PEAR::staticPopErrorHandling() + */ + public function staticPopErrorHandling() + { + return $this->pear->staticPopErrorHandling(); + } + + // }}} + // {{{ function staticPushErrorHandling($mode, $options = null) + + /** + * @uses PEAR::staticPushErrorHandling($mode, $options) + */ + public function staticPushErrorHandling($mode, $options = null) + { + return $this->pear->staticPushErrorHandling($mode, $options); + } + + // }}} + // {{{ function &throwError($message = null, $code = null, $userinfo = null) + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @return object a PEAR error object + * @see PEAR::raiseError + * @uses PEAR::&throwError() + */ + public function &throwError($message = null, $code = null, $userinfo = null) + { + $tmp =& $this->pear->throwError($message, $code, $userinfo); + return $tmp; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Result + +/** + * The dummy class that all user space result classes should extend from + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result +{ +} + +// }}} +// {{{ class MDB2_Result_Common extends MDB2_Result + +/** + * The common result class for MDB2 result objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_Common extends MDB2_Result +{ + // {{{ Variables (Properties) + + public $db; + public $result; + public $rownum = -1; + public $types = array(); + public $types_assoc = array(); + public $values = array(); + public $offset; + public $offset_count = 0; + public $limit; + public $column_names; + + // }}} + // {{{ constructor: function __construct($db, &$result, $limit = 0, $offset = 0) + + /** + * Constructor + */ + function __construct($db, &$result, $limit = 0, $offset = 0) + { + $this->db = $db; + $this->result = $result; + $this->offset = $offset; + $this->limit = max(0, $limit - 1); + } + + // }}} + // {{{ function setResultTypes($types) + + /** + * Define the list of types to be associated with the columns of a given + * result set. + * + * This function may be called before invoking fetchRow(), fetchOne(), + * fetchCol() and fetchAll() so that the necessary data type + * conversions are performed on the data to be retrieved by them. If this + * function is not called, the type of all result set columns is assumed + * to be text, thus leading to not perform any conversions. + * + * @param array variable that lists the + * data types to be expected in the result set columns. If this array + * contains less types than the number of columns that are returned + * in the result set, the remaining columns are assumed to be of the + * type text. Currently, the types clob and blob are not fully + * supported. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function setResultTypes($types) + { + $load = $this->db->loadModule('Datatype', null, true); + if (MDB2::isError($load)) { + return $load; + } + $types = $this->db->datatype->checkResultTypes($types); + if (MDB2::isError($types)) { + return $types; + } + foreach ($types as $key => $value) { + if (is_numeric($key)) { + $this->types[$key] = $value; + } else { + $this->types_assoc[$key] = $value; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function seek($rownum = 0) + + /** + * Seek to a specific row in a result set + * + * @param int 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) + { + $target_rownum = $rownum - 1; + if ($this->rownum > $target_rownum) { + return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'seeking to previous rows not implemented', __FUNCTION__); + } + while ($this->rownum < $target_rownum) { + $this->fetchRow(); + } + return MDB2_OK; + } + + // }}} + // {{{ function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + + /** + * Fetch and return a row of data + * + * @param int how the array data should be indexed + * @param int 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) + { + $err = MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $err; + } + + // }}} + // {{{ function fetchOne($colnum = 0) + + /** + * fetch single column from the next row from a result set + * + * @param int|string the column number (or name) to fetch + * @param int number of the row where the data can be found + * + * @return string data on success, a MDB2 error on failure + * @access public + */ + function fetchOne($colnum = 0, $rownum = null) + { + $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; + $row = $this->fetchRow($fetchmode, $rownum); + if (!is_array($row) || MDB2::isError($row)) { + return $row; + } + if (!array_key_exists($colnum, $row)) { + return MDB2::raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'column is not defined in the result set: '.$colnum, __FUNCTION__); + } + return $row[$colnum]; + } + + // }}} + // {{{ function fetchCol($colnum = 0) + + /** + * Fetch and return a column from the current row pointer position + * + * @param int|string the column number (or name) to fetch + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function fetchCol($colnum = 0) + { + $column = array(); + $fetchmode = is_numeric($colnum) ? MDB2_FETCHMODE_ORDERED : MDB2_FETCHMODE_ASSOC; + $row = $this->fetchRow($fetchmode); + if (is_array($row)) { + if (!array_key_exists($colnum, $row)) { + return MDB2::raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'column is not defined in the result set: '.$colnum, __FUNCTION__); + } + do { + $column[] = $row[$colnum]; + } while (is_array($row = $this->fetchRow($fetchmode))); + } + if (MDB2::isError($row)) { + return $row; + } + return $column; + } + + // }}} + // {{{ function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, $force_array = false, $group = false) + + /** + * Fetch and return all rows from the current row pointer position + * + * @param int $fetchmode the fetch mode to use: + * + MDB2_FETCHMODE_ORDERED + * + MDB2_FETCHMODE_ASSOC + * + MDB2_FETCHMODE_ORDERED | MDB2_FETCHMODE_FLIPPED + * + MDB2_FETCHMODE_ASSOC | MDB2_FETCHMODE_FLIPPED + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return mixed data array on success, a MDB2 error on failure + * + * @access public + * @see getAssoc() + */ + function fetchAll($fetchmode = MDB2_FETCHMODE_DEFAULT, $rekey = false, + $force_array = false, $group = false) + { + $all = array(); + $row = $this->fetchRow($fetchmode); + if (MDB2::isError($row)) { + return $row; + } elseif (!$row) { + return $all; + } + + $shift_array = $rekey ? false : null; + if (null !== $shift_array) { + if (is_object($row)) { + $colnum = count(get_object_vars($row)); + } else { + $colnum = count($row); + } + if ($colnum < 2) { + return MDB2::raiseError(MDB2_ERROR_TRUNCATED, null, null, + 'rekey feature requires atleast 2 column', __FUNCTION__); + } + $shift_array = (!$force_array && $colnum == 2); + } + + if ($rekey) { + do { + if (is_object($row)) { + $arr = get_object_vars($row); + $key = reset($arr); + unset($row->{$key}); + } else { + if ( $fetchmode == MDB2_FETCHMODE_ASSOC + || $fetchmode == MDB2_FETCHMODE_OBJECT + ) { + $key = reset($row); + unset($row[key($row)]); + } else { + $key = array_shift($row); + } + if ($shift_array) { + $row = array_shift($row); + } + } + if ($group) { + $all[$key][] = $row; + } else { + $all[$key] = $row; + } + } while (($row = $this->fetchRow($fetchmode))); + } elseif ($fetchmode == MDB2_FETCHMODE_FLIPPED) { + do { + foreach ($row as $key => $val) { + $all[$key][] = $val; + } + } while (($row = $this->fetchRow($fetchmode))); + } else { + do { + $all[] = $row; + } while (($row = $this->fetchRow($fetchmode))); + } + + return $all; + } + + // }}} + // {{{ function rowCount() + /** + * Returns the actual row number that was last fetched (count from 0) + * @return int + * + * @access public + */ + function rowCount() + { + return $this->rownum + 1; + } + + // }}} + // {{{ function numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * + * @access public + */ + function numRows() + { + return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * + * @access public + */ + function nextResult() + { + return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result or + * from the cache. + * + * @param bool If set to true the values are the column names, + * otherwise the names of the columns are the keys. + * @return mixed Array variable that holds the names of columns or an + * MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * + * @access public + */ + function getColumnNames($flip = false) + { + if (!isset($this->column_names)) { + $result = $this->_getColumnNames(); + if (MDB2::isError($result)) { + return $result; + } + $this->column_names = $result; + } + if ($flip) { + return array_flip($this->column_names); + } + return $this->column_names; + } + + // }}} + // {{{ function _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() + { + return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function 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() + { + return MDB2::raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ function getResource() + + /** + * return the resource associated with the result object + * + * @return resource + * + * @access public + */ + function getResource() + { + return $this->result; + } + + // }}} + // {{{ function bindColumn($column, &$value, $type = null) + + /** + * Set bind variable to a column. + * + * @param int column number or name + * @param mixed variable reference + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindColumn($column, &$value, $type = null) + { + if (!is_numeric($column)) { + $column_names = $this->getColumnNames(); + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($this->db->options['field_case'] == CASE_LOWER) { + $column = strtolower($column); + } else { + $column = strtoupper($column); + } + } + $column = $column_names[$column]; + } + $this->values[$column] =& $value; + if (null !== $type) { + $this->types[$column] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function _assignBindColumns($row) + + /** + * Bind a variable to a value in the result row. + * + * @param array row data + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access private + */ + function _assignBindColumns($row) + { + $row = array_values($row); + foreach ($row as $column => $value) { + if (array_key_exists($column, $this->values)) { + $this->values[$column] = $value; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function free() + + /** + * Free the internal resources associated with result. + * + * @return bool true on success, false if result is invalid + * + * @access public + */ + function free() + { + $this->result = false; + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Row + +/** + * The simple class that accepts row data as an array + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Row +{ + // {{{ constructor: function __construct(&$row) + + /** + * constructor + * + * @param resource row data as array + */ + function __construct(&$row) + { + foreach ($row as $key => $value) { + $this->$key = &$row[$key]; + } + } + + // }}} +} + +// }}} +// {{{ class MDB2_Statement_Common + +/** + * The common statement class for MDB2 statement objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_Common +{ + // {{{ Variables (Properties) + + var $db; + var $statement; + var $query; + var $result_types; + var $types; + var $values = array(); + var $limit; + var $offset; + var $is_manip; + + // }}} + // {{{ constructor: function __construct($db, $statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + + /** + * Constructor + */ + function __construct($db, $statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + { + $this->db = $db; + $this->statement = $statement; + $this->positions = $positions; + $this->query = $query; + $this->types = (array)$types; + $this->result_types = (array)$result_types; + $this->limit = $limit; + $this->is_manip = $is_manip; + $this->offset = $offset; + } + + // }}} + // {{{ function bindValue($parameter, &$value, $type = null) + + /** + * Set the value of a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed value that is meant to be assigned to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindValue($parameter, $value, $type = null) + { + if (!is_numeric($parameter)) { + if (strpos($parameter, ':') === 0) { + $parameter = substr($parameter, 1); + } + } + if (!in_array($parameter, $this->positions)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] = $value; + if (null !== $type) { + $this->types[$parameter] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function bindValueArray($values, $types = null) + + /** + * Set the values of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindValue() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindValueArray($values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + $this->db->pushErrorHandling(PEAR_ERROR_RETURN); + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + foreach ($parameters as $key => $parameter) { + $err = $this->bindValue($parameter, $values[$parameter], $types[$key]); + if (MDB2::isError($err)) { + if ($err->getCode() == MDB2_ERROR_NOT_FOUND) { + //ignore (extra value for missing placeholder) + continue; + } + $this->db->popExpect(); + $this->db->popErrorHandling(); + return $err; + } + } + $this->db->popExpect(); + $this->db->popErrorHandling(); + return MDB2_OK; + } + + // }}} + // {{{ function bindParam($parameter, &$value, $type = null) + + /** + * Bind a variable to a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed variable that is meant to be bound to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindParam($parameter, &$value, $type = null) + { + if (!is_numeric($parameter)) { + if (strpos($parameter, ':') === 0) { + $parameter = substr($parameter, 1); + } + } + if (!in_array($parameter, $this->positions)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] =& $value; + if (null !== $type) { + $this->types[$parameter] = $type; + } + return MDB2_OK; + } + + // }}} + // {{{ function bindParamArray(&$values, $types = null) + + /** + * Bind the variables of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindParamArray(&$values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $err = $this->bindParam($parameter, $values[$parameter], $types[$key]); + if (MDB2::isError($err)) { + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding + * to the number of the position of the parameter. + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access public + */ + function execute($values = null, $result_class = true, $result_wrap_class = false) + { + if (null === $this->positions) { + return MDB2::raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + $values = (array)$values; + if (!empty($values)) { + $err = $this->bindValueArray($values); + if (MDB2::isError($err)) { + return MDB2::raiseError(MDB2_ERROR, null, null, + 'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__); + } + } + $result = $this->_execute($result_class, $result_wrap_class); + return $result; + } + + // }}} + // {{{ function _execute($result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement helper method. + * + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access private + */ + function _execute($result_class = true, $result_wrap_class = false) + { + $this->last_query = $this->query; + $query = ''; + $last_position = 0; + foreach ($this->positions as $current_position => $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $query.= substr($this->query, $last_position, $current_position - $last_position); + if (!isset($value)) { + $value_quoted = 'NULL'; + } else { + $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null; + $value_quoted = $this->db->quote($value, $type); + if (MDB2::isError($value_quoted)) { + return $value_quoted; + } + } + $query.= $value_quoted; + $last_position = $current_position + 1; + } + $query.= substr($this->query, $last_position); + + $this->db->offset = $this->offset; + $this->db->limit = $this->limit; + if ($this->is_manip) { + $result = $this->db->exec($query); + } else { + $result = $this->db->query($query, $this->result_types, $result_class, $result_wrap_class); + } + return $result; + } + + // }}} + // {{{ function 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 (null === $this->positions) { + return MDB2::raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + $this->statement = null; + $this->positions = null; + $this->query = null; + $this->types = null; + $this->result_types = null; + $this->limit = null; + $this->is_manip = null; + $this->offset = null; + $this->values = null; + + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_Module_Common + +/** + * The common modules class for MDB2 module objects + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Module_Common +{ + // {{{ Variables (Properties) + + /** + * contains the key to the global MDB2 instance array of the associated + * MDB2 instance + * + * @var int + * @access protected + */ + protected $db_index; + + // }}} + // {{{ constructor: function __construct($db_index) + + /** + * Constructor + */ + function __construct($db_index) + { + $this->db_index = $db_index; + } + + // }}} + // {{{ function getDBInstance() + + /** + * Get the instance of MDB2 associated with the module instance + * + * @return object MDB2 instance or a MDB2 error on failure + * + * @access public + */ + function getDBInstance() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $result = $GLOBALS['_MDB2_databases'][$this->db_index]; + } else { + $result = MDB2::raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'could not find MDB2 instance'); + } + return $result; + } + + // }}} +} + +// }}} +// {{{ function MDB2_closeOpenTransactions() + +/** + * Close any open transactions form persistent connections + * + * @return void + * + * @access public + */ + +function MDB2_closeOpenTransactions() +{ + reset($GLOBALS['_MDB2_databases']); + while (next($GLOBALS['_MDB2_databases'])) { + $key = key($GLOBALS['_MDB2_databases']); + if ($GLOBALS['_MDB2_databases'][$key]->opened_persistent + && $GLOBALS['_MDB2_databases'][$key]->in_transaction + ) { + $GLOBALS['_MDB2_databases'][$key]->rollback(); + } + } +} + +// }}} +// {{{ function MDB2_defaultDebugOutput(&$db, $scope, $message, $is_manip = null) + +/** + * default debug output handler + * + * @param object reference to an MDB2 database object + * @param string usually the method name that triggered the debug call: + * for example 'query', 'prepare', 'execute', 'parameters', + * 'beginTransaction', 'commit', 'rollback' + * @param string message that should be appended to the debug variable + * @param array contains context information about the debug() call + * common keys are: is_manip, time, result etc. + * + * @return void|string optionally return a modified message, this allows + * rewriting a query before being issued or prepared + * + * @access public + */ +function MDB2_defaultDebugOutput(&$db, $scope, $message, $context = array()) +{ + $db->debug_output.= $scope.'('.$db->db_index.'): '; + $db->debug_output.= $message.$db->getOption('log_line_break'); + return $message; +} + +// }}} +?> diff --git a/extlib/MDB2/Date.php b/extlib/MDB2/Date.php new file mode 100644 index 0000000000..145f7544fc --- /dev/null +++ b/extlib/MDB2/Date.php @@ -0,0 +1,183 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Several methods to convert the MDB2 native timestamp format (ISO based) + * to and from data structures that are convenient to worth with in side of php. + * For more complex date arithmetic please take a look at the Date package in PEAR + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Date +{ + // {{{ mdbNow() + + /** + * return the current datetime + * + * @return string current datetime in the MDB2 format + * @access public + */ + public static function mdbNow() + { + return date('Y-m-d H:i:s'); + } + // }}} + + // {{{ mdbToday() + + /** + * return the current date + * + * @return string current date in the MDB2 format + * @access public + */ + public static function mdbToday() + { + return date('Y-m-d'); + } + // }}} + + // {{{ mdbTime() + + /** + * return the current time + * + * @return string current time in the MDB2 format + * @access public + */ + public static function mdbTime() + { + return date('H:i:s'); + } + // }}} + + // {{{ date2Mdbstamp() + + /** + * convert a date into a MDB2 timestamp + * + * @param int hour of the date + * @param int minute of the date + * @param int second of the date + * @param int month of the date + * @param int day of the date + * @param int year of the date + * + * @return string a valid MDB2 timestamp + * @access public + */ + public static function date2Mdbstamp($hour = null, $minute = null, $second = null, + $month = null, $day = null, $year = null) + { + return MDB2_Date::unix2Mdbstamp(mktime($hour, $minute, $second, $month, $day, $year, -1)); + } + // }}} + + // {{{ unix2Mdbstamp() + + /** + * convert a unix timestamp into a MDB2 timestamp + * + * @param int a valid unix timestamp + * + * @return string a valid MDB2 timestamp + * @access public + */ + public static function unix2Mdbstamp($unix_timestamp) + { + return date('Y-m-d H:i:s', $unix_timestamp); + } + // }}} + + // {{{ mdbstamp2Unix() + + /** + * convert a MDB2 timestamp into a unix timestamp + * + * @param int a valid MDB2 timestamp + * @return string unix timestamp with the time stored in the MDB2 format + * + * @access public + */ + public static function mdbstamp2Unix($mdb_timestamp) + { + $arr = MDB2_Date::mdbstamp2Date($mdb_timestamp); + + return mktime($arr['hour'], $arr['minute'], $arr['second'], $arr['month'], $arr['day'], $arr['year'], -1); + } + // }}} + + // {{{ mdbstamp2Date() + + /** + * convert a MDB2 timestamp into an array containing all + * values necessary to pass to php's date() function + * + * @param int a valid MDB2 timestamp + * + * @return array with the time split + * @access public + */ + public static function mdbstamp2Date($mdb_timestamp) + { + list($arr['year'], $arr['month'], $arr['day'], $arr['hour'], $arr['minute'], $arr['second']) = + sscanf($mdb_timestamp, "%04u-%02u-%02u %02u:%02u:%02u"); + return $arr; + } + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/Common.php b/extlib/MDB2/Driver/Datatype/Common.php new file mode 100644 index 0000000000..d74c200c0e --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/Common.php @@ -0,0 +1,1842 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/LOB.php'; + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * MDB2_Driver_Common: Base class that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Datatype'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_Common extends MDB2_Module_Common +{ + var $valid_default_values = array( + 'text' => '', + 'boolean' => true, + 'integer' => 0, + 'decimal' => 0.0, + 'float' => 0.0, + 'timestamp' => '1970-01-01 00:00:00', + 'time' => '00:00:00', + 'date' => '1970-01-01', + 'clob' => '', + 'blob' => '', + ); + + /** + * contains all LOB objects created with this MDB2 instance + * @var array + * @access protected + */ + var $lobs = array(); + + // }}} + // {{{ getValidTypes() + + /** + * Get the list of valid types + * + * This function returns an array of valid types as keys with the values + * being possible default values for all native datatypes and mapped types + * for custom datatypes. + * + * @return mixed array on success, a MDB2 error on failure + * @access public + */ + function getValidTypes() + { + $types = $this->valid_default_values; + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map'])) { + foreach ($db->options['datatype_map'] as $type => $mapped_type) { + if (array_key_exists($mapped_type, $types)) { + $types[$type] = $types[$mapped_type]; + } elseif (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'mapped_type' => $mapped_type); + $default = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + $types[$type] = $default; + } + } + } + return $types; + } + + // }}} + // {{{ checkResultTypes() + + /** + * Define the list of types to be associated with the columns of a given + * result set. + * + * This function may be called before invoking fetchRow(), fetchOne() + * fetchCole() and fetchAll() so that the necessary data type + * conversions are performed on the data to be retrieved by them. If this + * function is not called, the type of all result set columns is assumed + * to be text, thus leading to not perform any conversions. + * + * @param array $types array variable that lists the + * data types to be expected in the result set columns. If this array + * contains less types than the number of columns that are returned + * in the result set, the remaining columns are assumed to be of the + * type text. Currently, the types clob and blob are not fully + * supported. + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function checkResultTypes($types) + { + $types = is_array($types) ? $types : array($types); + foreach ($types as $key => $type) { + if (!isset($this->valid_default_values[$type])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (empty($db->options['datatype_map'][$type])) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + $type.' for '.$key.' is not a supported column type', __FUNCTION__); + } + } + } + return $types; + } + + // }}} + // {{{ _baseConvertResult() + + /** + * General type conversion method + * + * @param mixed $value reference to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object an MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + switch ($type) { + case 'text': + if ($rtrim) { + $value = rtrim($value); + } + return $value; + case 'integer': + return intval($value); + case 'boolean': + return !empty($value); + case 'decimal': + return $value; + case 'float': + return doubleval($value); + case 'date': + return $value; + case 'time': + return $value; + case 'timestamp': + return $value; + case 'clob': + case 'blob': + $this->lobs[] = array( + 'buffer' => null, + 'position' => 0, + 'lob_index' => null, + 'endOfLOB' => false, + 'resource' => $value, + 'value' => null, + 'loaded' => false, + ); + end($this->lobs); + $lob_index = key($this->lobs); + $this->lobs[$lob_index]['lob_index'] = $lob_index; + return fopen('MDB2LOB://'.$lob_index.'@'.$this->db_index, 'r+'); + } + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_INVALID, null, null, + 'attempt to convert result value to an unknown type :' . $type, __FUNCTION__); + } + + // }}} + // {{{ convertResult() + + /** + * Convert a value to a RDBMS indipendent MDB2 type + * + * @param mixed $value value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return mixed converted value + * @access public + */ + function convertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'value' => $value, 'rtrim' => $rtrim); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + return $this->_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ convertResultRow() + + /** + * Convert a result row + * + * @param array $types + * @param array $row specifies the types to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return mixed MDB2_OK on success, an MDB2 error on failure + * @access public + */ + function convertResultRow($types, $row, $rtrim = true) + { + //$types = $this->_sortResultFieldTypes(array_keys($row), $types); + $keys = array_keys($row); + if (is_int($keys[0])) { + $types = $this->_sortResultFieldTypes($keys, $types); + } + foreach ($row as $key => $value) { + if (empty($types[$key])) { + continue; + } + $value = $this->convertResult($row[$key], $types[$key], $rtrim); + if (MDB2::isError($value)) { + return $value; + } + $row[$key] = $value; + } + return $row; + } + + // }}} + // {{{ _sortResultFieldTypes() + + /** + * convert a result row + * + * @param array $types + * @param array $row specifies the types to convert to + * @param bool $rtrim if to rtrim text values or not + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function _sortResultFieldTypes($columns, $types) + { + $n_cols = count($columns); + $n_types = count($types); + if ($n_cols > $n_types) { + for ($i= $n_cols - $n_types; $i >= 0; $i--) { + $types[] = null; + } + } + $sorted_types = array(); + foreach ($columns as $col) { + $sorted_types[$col] = null; + } + foreach ($types as $name => $type) { + if (array_key_exists($name, $sorted_types)) { + $sorted_types[$name] = $type; + unset($types[$name]); + } + } + // if there are left types in the array, fill the null values of the + // sorted array with them, in order. + if (count($types)) { + reset($types); + foreach (array_keys($sorted_types) as $k) { + if (null === $sorted_types[$k]) { + $sorted_types[$k] = current($types); + next($types); + } + } + } + return $sorted_types; + } + + // }}} + // {{{ 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) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'name' => $name, 'field' => $field); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + $field['type'] = $type; + } + + if (!method_exists($this, "_get{$type}Declaration")) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'type not defined: '.$type, __FUNCTION__); + } + return $this->{"_get{$type}Declaration"}($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': + $length = !empty($field['length']) ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + return 'TEXT'; + case 'blob': + return 'TEXT'; + case 'integer': + return 'INT'; + case 'boolean': + return 'INT'; + case 'date': + return 'CHAR ('.strlen('YYYY-MM-DD').')'; + case 'time': + return 'CHAR ('.strlen('HH:MM:SS').')'; + case 'timestamp': + return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; + case 'float': + return 'TEXT'; + case 'decimal': + return 'TEXT'; + } + return ''; + } + + // }}} + // {{{ _getDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field, or a MDB2_Error on failure + * @access protected + */ + function _getDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $declaration_options = $db->datatype->_getDeclarationOptions($field); + if (MDB2::isError($declaration_options)) { + return $declaration_options; + } + return $name.' '.$this->getTypeDeclaration($field).$declaration_options; + } + + // }}} + // {{{ _getDeclarationOptions() + + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statement like CREATE TABLE, without the field name + * and type values (ie. just the character set, default value, if the + * field is permitted to be NULL or not, and the collation options). + * + * @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: + * + * 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. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field's options. + * @access protected + */ + function _getDeclarationOptions($field) + { + $charset = empty($field['charset']) ? '' : + ' '.$this->_getCharsetFieldDeclaration($field['charset']); + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $valid_default_values = $this->getValidTypes(); + $field['default'] = $valid_default_values[$field['type']]; + if ($field['default'] === '' && ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL)) { + $field['default'] = ' '; + } + } + if (null !== $field['default']) { + $default = ' DEFAULT ' . $this->quote($field['default'], $field['type']); + } + } + + $collation = empty($field['collation']) ? '' : + ' '.$this->_getCollationFieldDeclaration($field['collation']); + + return $charset.$default.$notnull.$collation; + } + + // }}} + // {{{ _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 ''; + } + + // }}} + // {{{ _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 ''; + } + + // }}} + // {{{ _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 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: + * + * 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) + { + if (!empty($field['unsigned'])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTextDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 protected + */ + function _getTextDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getCLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getBLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBooleanDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a boolean type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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: + * + * default + * Boolean value to be used as default for this field. + * + * notnullL + * 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 _getBooleanDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getDateDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a date type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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: + * + * default + * Date 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 _getDateDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTimestampDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a timestamp + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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: + * + * default + * Timestamp 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 _getTimestampDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getTimeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a time + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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: + * + * default + * Time 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 _getTimeDeclaration($name, $field) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getFloatDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a float type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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: + * + * 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) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ _getDecimalDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare a decimal type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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: + * + * 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) + { + return $this->_getDeclaration($name, $field); + } + + // }}} + // {{{ compareDefinition() + + /** + * Obtain an array of changes that may need to applied + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access public + */ + function compareDefinition($current, $previous) + { + $type = !empty($current['type']) ? $current['type'] : null; + + if (!method_exists($this, "_compare{$type}Definition")) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('current' => $current, 'previous' => $previous); + $change = call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + return $change; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type "'.$current['type'].'" is not yet supported', __FUNCTION__); + } + + if (empty($previous['type']) || $previous['type'] != $type) { + return $current; + } + + $change = $this->{"_compare{$type}Definition"}($current, $previous); + + if ($previous['type'] != $type) { + $change['type'] = true; + } + + $previous_notnull = !empty($previous['notnull']) ? $previous['notnull'] : false; + $notnull = !empty($current['notnull']) ? $current['notnull'] : false; + if ($previous_notnull != $notnull) { + $change['notnull'] = true; + } + + $previous_default = array_key_exists('default', $previous) ? $previous['default'] : + ($previous_notnull ? '' : null); + $default = array_key_exists('default', $current) ? $current['default'] : + ($notnull ? '' : null); + if ($previous_default !== $default) { + $change['default'] = true; + } + + return $change; + } + + // }}} + // {{{ _compareIntegerDefinition() + + /** + * Obtain an array of changes that may need to applied to an integer field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareIntegerDefinition($current, $previous) + { + $change = array(); + $previous_unsigned = !empty($previous['unsigned']) ? $previous['unsigned'] : false; + $unsigned = !empty($current['unsigned']) ? $current['unsigned'] : false; + if ($previous_unsigned != $unsigned) { + $change['unsigned'] = true; + } + $previous_autoincrement = !empty($previous['autoincrement']) ? $previous['autoincrement'] : false; + $autoincrement = !empty($current['autoincrement']) ? $current['autoincrement'] : false; + if ($previous_autoincrement != $autoincrement) { + $change['autoincrement'] = true; + } + return $change; + } + + // }}} + // {{{ _compareTextDefinition() + + /** + * Obtain an array of changes that may need to applied to an text field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTextDefinition($current, $previous) + { + $change = array(); + $previous_length = !empty($previous['length']) ? $previous['length'] : 0; + $length = !empty($current['length']) ? $current['length'] : 0; + if ($previous_length != $length) { + $change['length'] = true; + } + $previous_fixed = !empty($previous['fixed']) ? $previous['fixed'] : 0; + $fixed = !empty($current['fixed']) ? $current['fixed'] : 0; + if ($previous_fixed != $fixed) { + $change['fixed'] = true; + } + return $change; + } + + // }}} + // {{{ _compareCLOBDefinition() + + /** + * Obtain an array of changes that may need to applied to an CLOB field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareCLOBDefinition($current, $previous) + { + return $this->_compareTextDefinition($current, $previous); + } + + // }}} + // {{{ _compareBLOBDefinition() + + /** + * Obtain an array of changes that may need to applied to an BLOB field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareBLOBDefinition($current, $previous) + { + return $this->_compareTextDefinition($current, $previous); + } + + // }}} + // {{{ _compareDateDefinition() + + /** + * Obtain an array of changes that may need to applied to an date field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareDateDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareTimeDefinition() + + /** + * Obtain an array of changes that may need to applied to an time field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTimeDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareTimestampDefinition() + + /** + * Obtain an array of changes that may need to applied to an timestamp field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareTimestampDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareBooleanDefinition() + + /** + * Obtain an array of changes that may need to applied to an boolean field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareBooleanDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareFloatDefinition() + + /** + * Obtain an array of changes that may need to applied to an float field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareFloatDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ _compareDecimalDefinition() + + /** + * Obtain an array of changes that may need to applied to an decimal field + * + * @param array $current new definition + * @param array $previous old definition + * @return array containing all changes that will need to be applied + * @access protected + */ + function _compareDecimalDefinition($current, $previous) + { + return array(); + } + + // }}} + // {{{ quote() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param string $type type to which the value should be converted to + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access public + */ + function quote($value, $type = null, $quote = true, $escape_wildcards = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if ((null === $value) + || ($value === '' && $db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) + ) { + if (!$quote) { + return null; + } + return 'NULL'; + } + + if (null === $type) { + switch (gettype($value)) { + case 'integer': + $type = 'integer'; + break; + case 'double': + // todo: default to decimal as float is quite unusual + // $type = 'float'; + $type = 'decimal'; + break; + case 'boolean': + $type = 'boolean'; + break; + case 'array': + $value = serialize($value); + case 'object': + $type = 'text'; + break; + default: + if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value)) { + $type = 'timestamp'; + } elseif (preg_match('/^\d{2}:\d{2}$/', $value)) { + $type = 'time'; + } elseif (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + $type = 'date'; + } else { + $type = 'text'; + } + break; + } + } elseif (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type, 'value' => $value, 'quote' => $quote, 'escape_wildcards' => $escape_wildcards); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + if (!method_exists($this, "_quote{$type}")) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'type not defined: '.$type, __FUNCTION__); + } + $value = $this->{"_quote{$type}"}($value, $quote, $escape_wildcards); + if ($quote && $escape_wildcards && $db->string_quoting['escape_pattern'] + && $db->string_quoting['escape'] !== $db->string_quoting['escape_pattern'] + ) { + $value.= $this->patternEscapeString(); + } + return $value; + } + + // }}} + // {{{ _quoteInteger() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteInteger($value, $quote, $escape_wildcards) + { + return (int)$value; + } + + // }}} + // {{{ _quoteText() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that already contains any DBMS specific + * escaped character sequences. + * @access protected + */ + function _quoteText($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $value = $db->escape($value, $escape_wildcards); + if (MDB2::isError($value)) { + return $value; + } + return "'".$value."'"; + } + + // }}} + // {{{ _readFile() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _readFile($value) + { + $close = false; + if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + $close = true; + if (strtolower($match[1]) == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + } + + if (is_resource($value)) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $fp = $value; + $value = ''; + while (!@feof($fp)) { + $value.= @fread($fp, $db->options['lob_buffer_length']); + } + if ($close) { + @fclose($fp); + } + } + + return $value; + } + + // }}} + // {{{ _quoteLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteLOB($value, $quote, $escape_wildcards) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if ($db->options['lob_allow_url_include']) { + $value = $this->_readFile($value); + if (MDB2::isError($value)) { + return $value; + } + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + return $this->_quoteLOB($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + return $this->_quoteLOB($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBoolean() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBoolean($value, $quote, $escape_wildcards) + { + return ($value ? 1 : 0); + } + + // }}} + // {{{ _quoteDate() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDate($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_DATE') { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (isset($db->function) && is_object($this->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('date'); + } + return 'CURRENT_DATE'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTimestamp() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTimestamp($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_TIMESTAMP') { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (isset($db->function) && is_object($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('timestamp'); + } + return 'CURRENT_TIMESTAMP'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTime() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTime($value, $quote, $escape_wildcards) + { + if ($value === 'CURRENT_TIME') { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (isset($db->function) && is_object($this->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + return $db->function->now('time'); + } + return 'CURRENT_TIME'; + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteFloat() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteFloat($value, $quote, $escape_wildcards) + { + if (preg_match('/^(.*)e([-+])(\d+)$/i', $value, $matches)) { + $decimal = $this->_quoteDecimal($matches[1], $quote, $escape_wildcards); + $sign = $matches[2]; + $exponent = str_pad($matches[3], 2, '0', STR_PAD_LEFT); + $value = $decimal.'E'.$sign.$exponent; + } else { + $value = $this->_quoteDecimal($value, $quote, $escape_wildcards); + } + return $value; + } + + // }}} + // {{{ _quoteDecimal() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDecimal($value, $quote, $escape_wildcards) + { + $value = (string)$value; + $value = preg_replace('/[^\d\.,\-+eE]/', '', $value); + if (preg_match('/[^\.\d]/', $value)) { + if (strpos($value, ',')) { + // 1000,00 + if (!strpos($value, '.')) { + // convert the last "," to a "." + $value = strrev(str_replace(',', '.', strrev($value))); + // 1.000,00 + } elseif (strpos($value, '.') && strpos($value, '.') < strpos($value, ',')) { + $value = str_replace('.', '', $value); + // convert the last "," to a "." + $value = strrev(str_replace(',', '.', strrev($value))); + // 1,000.00 + } else { + $value = str_replace(',', '', $value); + } + } + } + return $value; + } + + // }}} + // {{{ writeLOBToFile() + + /** + * retrieve LOB from the database + * + * @param resource $lob stream handle + * @param string $file name of the file into which the LOb should be fetched + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function writeLOBToFile($lob, $file) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) { + if ($match[1] == 'file://') { + $file = $match[2]; + } + } + + $fp = @fopen($file, 'wb'); + while (!@feof($lob)) { + $result = @fread($lob, $db->options['lob_buffer_length']); + $read = strlen($result); + if (@fwrite($fp, $result, $read) != $read) { + @fclose($fp); + return $db->raiseError(MDB2_ERROR, null, null, + 'could not write to the output file', __FUNCTION__); + } + } + @fclose($fp); + return MDB2_OK; + } + + // }}} + // {{{ _retrieveLOB() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _retrieveLOB(&$lob) + { + if (null === $lob['value']) { + $lob['value'] = $lob['resource']; + } + $lob['loaded'] = true; + return MDB2_OK; + } + + // }}} + // {{{ readLOB() + + /** + * Read data from large object input stream. + * + * @param resource $lob stream handle + * @param string $data reference to a variable that will hold data + * to be read from the large object input stream + * @param integer $length value that indicates the largest ammount ofdata + * to be read from the large object input stream. + * @return mixed the effective number of bytes read from the large object + * input stream on sucess or an MDB2 error object. + * @access public + * @see endOfLOB() + */ + function _readLOB($lob, $length) + { + return substr($lob['value'], $lob['position'], $length); + } + + // }}} + // {{{ _endOfLOB() + + /** + * Determine whether it was reached the end of the large object and + * therefore there is no more data to be read for the its input stream. + * + * @param array $lob array + * @return mixed true or false on success, a MDB2 error on failure + * @access protected + */ + function _endOfLOB($lob) + { + return $lob['endOfLOB']; + } + + // }}} + // {{{ destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param resource $lob stream handle + * @access public + */ + function destroyLOB($lob) + { + $lob_data = stream_get_meta_data($lob); + $lob_index = $lob_data['wrapper_data']->lob_index; + fclose($lob); + if (isset($this->lobs[$lob_index])) { + $this->_destroyLOB($this->lobs[$lob_index]); + unset($this->lobs[$lob_index]); + } + return MDB2_OK; + } + + // }}} + // {{{ _destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param array $lob array + * @access private + */ + function _destroyLOB(&$lob) + { + return MDB2_OK; + } + + // }}} + // {{{ implodeArray() + + /** + * apply a type to all values of an array and return as a comma seperated string + * useful for generating IN statements + * + * @access public + * + * @param array $array data array + * @param string $type determines type of the field + * + * @return string comma seperated values + */ + function implodeArray($array, $type = false) + { + if (!is_array($array) || empty($array)) { + return 'NULL'; + } + if ($type) { + foreach ($array as $value) { + $return[] = $this->quote($value, $type); + } + } else { + $return = $array; + } + return implode(', ', $return); + } + + // }}} + // {{{ 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) { + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + if (null === $field) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'case insensitive LIKE matching requires passing the field name', __FUNCTION__); + } + $db->loadModule('Function', null, true); + $match = $db->function->lower($field).' LIKE '; + break; + case 'NOT ILIKE': + if (null === $field) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'case insensitive NOT ILIKE matching requires passing the field name', __FUNCTION__); + } + $db->loadModule('Function', null, true); + $match = $db->function->lower($field).' NOT LIKE '; + break; + // case sensitive + case 'LIKE': + $match = (null === $field) ? 'LIKE ' : ($field.' LIKE '); + break; + case 'NOT LIKE': + $match = (null === $field) ? 'NOT LIKE ' : ($field.' NOT LIKE '); + 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 { + $escaped = $db->escape($value); + if (MDB2::isError($escaped)) { + return $escaped; + } + $match.= $db->escapePattern($escaped); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define pattern escape character + * + * @access public + * + * @return string define pattern escape character + */ + function patternEscapeString() + { + return ''; + } + + // }}} + // {{{ 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 = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + // If the user has specified an option to map the native field + // type to a custom MDB2 datatype... + $db_type = strtok($field['type'], '(), '); + if (!empty($db->options['nativetype_map_callback'][$db_type])) { + return call_user_func_array($db->options['nativetype_map_callback'][$db_type], array($db, $field)); + } + + // Otherwise perform the built-in (i.e. normal) MDB2 native type to + // MDB2 datatype conversion + return $this->_mapNativeDatatype($field); + } + + // }}} + // {{{ _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 = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ mapPrepareDatatype() + + /** + * Maps an mdb2 datatype to mysqli prepare type + * + * @param string $type + * @return string + * @access public + */ + function mapPrepareDatatype($type) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + return $type; + } +} +?> diff --git a/extlib/MDB2/Driver/Datatype/fbsql.php b/extlib/MDB2/Driver/Datatype/fbsql.php new file mode 100644 index 0000000000..1bf99d2c36 --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/fbsql.php @@ -0,0 +1,350 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 FrontbaseSQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_fbsql extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param string $rtrim if text should be rtrimmed + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'boolean': + return $value == 'T'; + case 'time': + if ($value[0] == '+') { + return substr($value, 1); + } else { + return $value; + } + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? 'CHAR('.$length.')' : 'VARCHAR('.$length.')'; + case 'clob': + return 'CLOB'; + case 'blob': + return 'BLOB'; + case 'integer': + return 'INT'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'TIMESTAMP'; + case 'float': + return 'FLOAT'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'DECIMAL('.$length.','.$scale.')'; + } + return ''; + } + + // }}} + // {{{ _quoteBoolean() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBoolean($value, $quote, $escape_wildcards) + { + return ($value ? 'True' : 'False'); + } + + // }}} + // {{{ _quoteDate() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDate($value, $quote, $escape_wildcards) + { + return 'DATE'.$this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTimestamp() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTimestamp($value, $quote, $escape_wildcards) + { + return 'TIMESTAMP'.$this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTime() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTime($value, $quote, $escape_wildcards) + { + return 'TIME'.$this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _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']); + $length = $field['length']; + if ($length == '-1' && !empty($field['atttypmod'])) { + $length = $field['atttypmod'] - 4; + } + $type = array(); + $unsigned = $fixed = null; + switch ($db_type) { + case 'tinyint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 1; + break; + case 'smallint': + $type[] = 'integer'; + $unsigned = false; + $length = 2; + if ($length == '2') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'int': + case 'integer': + $type[] = 'integer'; + $unsigned = false; + $length = 4; + break; + case 'longint': + $type[] = 'integer'; + $unsigned = false; + $length = 8; + break; + case 'boolean': + $type[] = 'boolean'; + $length = null; + break; + case 'character varying': + case 'char varying': + case 'varchar': + case 'national character varying': + case 'national char varying': + case 'nchar varying': + $fixed = false; + case 'unknown': + case 'char': + case 'character': + case 'national character': + case 'national char': + case 'nchar': + $type[] = 'text'; + if (strstr($db_type, 'text')) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'timestamp': + case 'timestamp with time zone': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + case 'time with time zone': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double precision': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + break; + case 'blob': + $type[] = 'blob'; + $length = null; + break; + case 'clob': + $type[] = 'clob'; + $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); + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Datatype/ibase.php b/extlib/MDB2/Driver/Datatype/ibase.php new file mode 100644 index 0000000000..888a568c2c --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/ibase.php @@ -0,0 +1,455 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 Firebird/Interbase driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Datatype_ibase extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * General type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (MDB2::isError($connection)) { + return $connection; + } + + switch ($type) { + case 'text': + $blob_info = @ibase_blob_info($connection, $value); + if (is_array($blob_info) && $blob_info['length'] > 0) { + //LOB => fetch into variable + $clob = $this->_baseConvertResult($value, 'clob', $rtrim); + if (!MDB2::isError($clob) && is_resource($clob)) { + $clob_value = ''; + while (!feof($clob)) { + $clob_value .= fread($clob, 8192); + } + $this->destroyLOB($clob); + } + $value = $clob_value; + } + if ($rtrim) { + $value = rtrim($value); + } + return $value; + case 'timestamp': + return substr($value, 0, strlen('YYYY-MM-DD HH:MM:SS')); + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ _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; + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? 'CHAR('.$length.')' : 'VARCHAR('.$length.')'; + case 'clob': + return 'BLOB SUB_TYPE 1'; + case 'blob': + return 'BLOB SUB_TYPE 0'; + case 'integer': + return 'INT'; + case 'boolean': + return 'SMALLINT'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'TIMESTAMP'; + case 'float': + return 'DOUBLE PRECISION'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'DECIMAL('.$length.','.$scale.')'; + } + return ''; + } + + // }}} + // {{{ _quoteLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteLOB($value, $quote, $escape_wildcards) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $connection = $db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + $close = true; + if (is_resource($value)) { + $close = false; + } elseif (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + } else { + $fp = @tmpfile(); + @fwrite($fp, $value); + @rewind($fp); + $value = $fp; + } + $blob_id = @ibase_blob_import($connection, $value); + + if ($close) { + @fclose($value); + } + return $blob_id; + } + + // }}} + // {{{ _retrieveLOB() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _retrieveLOB(&$lob) + { + if (empty($lob['handle'])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $connection = $db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $lob['handle'] = @ibase_blob_open($connection, $lob['resource']); + if (!$lob['handle']) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(null, null, null, + 'Could not open fetched large object field', __FUNCTION__); + } + } + $lob['loaded'] = true; + return MDB2_OK; + } + + // }}} + // {{{ _readLOB() + + /** + * Read data from large object input stream. + * + * @param array $lob array + * @param blob $data reference to a variable that will hold data to be + * read from the large object input stream + * @param int $length integer value that indicates the largest ammount of + * data to be read from the large object input stream. + * @return mixed length on success, a MDB2 error on failure + * @access protected + */ + function _readLOB(&$lob, $length) + { + $data = @ibase_blob_get($lob['handle'], $length); + if (!is_string($data)) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(null, null, null, + 'Unable to read LOB', __FUNCTION__); + } + return $data; + } + + // }}} + // {{{ _destroyLOB() + + /** + * Free any resources allocated during the lifetime of the large object + * handler object. + * + * @param array $lob array + * @access protected + */ + function _destroyLOB(&$lob) + { + if (isset($lob['handle'])) { + @ibase_blob_close($lob['handle']); + unset($lob['handle']); + } + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define escape pattern string + * + * @access public + * + * @return string define escape pattern + */ + function patternEscapeString() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + return " ESCAPE '". $db->string_quoting['escape_pattern'] ."'"; + } + + // }}} + // {{{ _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) + { + $length = $field['length']; + if ((int)$length <= 0) { + $length = null; + } + $type = array(); + $unsigned = $fixed = null; + $db_type = strtolower($field['type']); + $field['field_sub_type'] = !empty($field['field_sub_type']) + ? strtolower($field['field_sub_type']) : null; + switch ($db_type) { + case 'smallint': + case 'integer': + case 'int64': + //these may be 'numeric' or 'decimal' + if (isset($field['field_sub_type'])) { + $field['type'] = $field['field_sub_type']; + return $this->mapNativeDatatype($field); + } + case 'bigint': + case 'quad': + $type[] = 'integer'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'varchar': + $fixed = false; + case 'char': + case 'cstring': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'double precision': + case 'd_float': + $type[] = 'float'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $length = $field['precision'].','.$field['scale']; + break; + case 'blob': + $type[] = ($field['field_sub_type'] == 'text') ? 'clob' : 'blob'; + $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); + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Datatype/mssql.php b/extlib/MDB2/Driver/Datatype/mssql.php new file mode 100644 index 0000000000..66064fd829 --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/mssql.php @@ -0,0 +1,497 @@ + | +// | Daniel Convissor | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MS SQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_mssql extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'boolean': + return ($value === 0)? false : !empty($value); + case 'date': + if (strlen($value) > 10) { + $value = substr($value,0,10); + } + return $value; + case 'time': + if (strlen($value) > 8) { + $value = substr($value,11,8); + } + return $value; + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ _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; + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 8000) { + return 'VARCHAR('.$length.')'; + } + } + return 'TEXT'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 8000) { + return "VARBINARY($length)"; + } + } + return 'IMAGE'; + case 'integer': + return 'INT'; + case 'boolean': + return 'BIT'; + case 'date': + return 'CHAR ('.strlen('YYYY-MM-DD').')'; + case 'time': + return 'CHAR ('.strlen('HH:MM:SS').')'; + case 'timestamp': + return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; + case 'float': + return 'FLOAT'; + 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; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $default = $autoinc = ''; + if (!empty($field['autoincrement'])) { + $autoinc = ' IDENTITY PRIMARY KEY'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = 0; + } + if (null === $field['default']) { + $default = ' DEFAULT (null)'; + } else { + $default = ' DEFAULT (' . $this->quote($field['default'], 'integer') . ')'; + } + } + + if (!empty($field['unsigned'])) { + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull.$default.$autoinc; + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getCLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getBLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + $value = '0x'.bin2hex($this->_readFile($value)); + return $value; + } + + // }}} + // {{{ 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 '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE '; + 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) + { + // todo: handle length of various int variations + $db_type = preg_replace('/\d/', '', strtolower($field['type'])); + $length = $field['length']; + $type = array(); + // todo: unsigned handling seems to be missing + $unsigned = $fixed = null; + switch ($db_type) { + case 'bit': + $type[0] = 'boolean'; + break; + case 'tinyint': + $type[0] = 'integer'; + $length = 1; + break; + case 'smallint': + $type[0] = 'integer'; + $length = 2; + break; + case 'int': + $type[0] = 'integer'; + $length = 4; + break; + case 'bigint': + $type[0] = 'integer'; + $length = 8; + break; + case 'smalldatetime': + case 'datetime': + $type[0] = 'timestamp'; + break; + case 'float': + case 'real': + case 'numeric': + $type[0] = 'float'; + break; + case 'decimal': + case 'money': + $type[0] = 'decimal'; + $length = $field['numeric_precision'].','.$field['numeric_scale']; + break; + case 'text': + case 'ntext': + case 'varchar': + case 'nvarchar': + $fixed = false; + case 'char': + case 'nchar': + $type[0] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'image': + case 'varbinary': + case 'timestamp': + $type[] = 'blob'; + $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); + } + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/mysqli.php b/extlib/MDB2/Driver/Datatype/mysqli.php new file mode 100644 index 0000000000..569171777d --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/mysqli.php @@ -0,0 +1,640 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MySQLi driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_mysqli 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); + } + + // }}} + // {{{ mapPrepareDatatype() + + /** + * Maps an MDB2 datatype to native prepare type + * + * @param string $type + * @return string + * @access public + */ + function mapPrepareDatatype($type) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + switch ($type) { + case 'boolean': + case 'integer': + return 'i'; + case 'float': + return 'd'; + case 'blob': + return 'b'; + default: + break; + } + return 's'; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Datatype/oci8.php b/extlib/MDB2/Driver/Datatype/oci8.php new file mode 100644 index 0000000000..30ab7a3fbb --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/oci8.php @@ -0,0 +1,499 @@ + | +// +----------------------------------------------------------------------+ + +// $Id$ + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 OCI8 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_oci8 extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'text': + if (is_object($value) && is_a($value, 'OCI-Lob')) { + //LOB => fetch into variable + $clob = $this->_baseConvertResult($value, 'clob', $rtrim); + if (!MDB2::isError($clob) && is_resource($clob)) { + $clob_value = ''; + while (!feof($clob)) { + $clob_value .= fread($clob, 8192); + } + $this->destroyLOB($clob); + } + $value = $clob_value; + } + if ($rtrim) { + $value = rtrim($value); + } + return $value; + case 'date': + return substr($value, 0, strlen('YYYY-MM-DD')); + case 'time': + return substr($value, strlen('YYYY-MM-DD '), strlen('HH:MI:SS')); + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? 'CHAR('.$length.')' : 'VARCHAR2('.$length.')'; + case 'clob': + return 'CLOB'; + case 'blob': + return 'BLOB'; + case 'integer': + if (!empty($field['length'])) { + switch((int)$field['length']) { + case 1: $digit = 3; break; + case 2: $digit = 5; break; + case 3: $digit = 8; break; + case 4: $digit = 10; break; + case 5: $digit = 13; break; + case 6: $digit = 15; break; + case 7: $digit = 17; break; + case 8: $digit = 20; break; + default: $digit = 10; + } + return 'NUMBER('.$digit.')'; + } + return 'INT'; + case 'boolean': + return 'NUMBER(1)'; + case 'date': + case 'time': + case 'timestamp': + return 'DATE'; + case 'float': + return 'NUMBER'; + case 'decimal': + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'NUMBER(*,'.$scale.')'; + } + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + return 'EMPTY_CLOB()'; + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + return 'EMPTY_BLOB()'; + } + + // }}} + // {{{ _quoteDate() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDate($value, $quote, $escape_wildcards) + { + return $this->_quoteText("$value 00:00:00", $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTimestamp() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTimestamp($value, $quote, $escape_wildcards) + { + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTime() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTime($value, $quote, $escape_wildcards) + { + return $this->_quoteText("0001-01-01 $value", $quote, $escape_wildcards); + } + + // }}} + // {{{ writeLOBToFile() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @param string $file name of the file into which the LOb should be fetched + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function writeLOBToFile($lob, $file) + { + if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) { + if ($match[1] == 'file://') { + $file = $match[2]; + } + } + $lob_data = stream_get_meta_data($lob); + $lob_index = $lob_data['wrapper_data']->lob_index; + $result = $this->lobs[$lob_index]['resource']->writetofile($file); + $this->lobs[$lob_index]['resource']->seek(0); + if (!$result) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(null, null, null, + 'Unable to write LOB to file', __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ _retrieveLOB() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _retrieveLOB(&$lob) + { + if (!is_object($lob['resource'])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'attemped to retrieve LOB from non existing or NULL column', __FUNCTION__); + } + + if (!$lob['loaded'] +# && !method_exists($lob['resource'], 'read') + ) { + $lob['value'] = $lob['resource']->load(); + $lob['resource']->seek(0); + } + $lob['loaded'] = true; + return MDB2_OK; + } + + // }}} + // {{{ _readLOB() + + /** + * Read data from large object input stream. + * + * @param array $lob array + * @param blob $data reference to a variable that will hold data to be + * read from the large object input stream + * @param int $length integer value that indicates the largest ammount of + * data to be read from the large object input stream. + * @return mixed length on success, a MDB2 error on failure + * @access protected + */ + function _readLOB($lob, $length) + { + if ($lob['loaded']) { + return parent::_readLOB($lob, $length); + } + + if (!is_object($lob['resource'])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'attemped to retrieve LOB from non existing or NULL column', __FUNCTION__); + } + + $data = $lob['resource']->read($length); + if (!is_string($data)) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(null, null, null, + 'Unable to read LOB', __FUNCTION__); + } + return $data; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define escape pattern string + * + * @access public + * + * + * @return string define escape pattern + */ + function patternEscapeString() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + return " ESCAPE '". $db->string_quoting['escape_pattern'] ."'"; + } + + // }}} + // {{{ _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']); + $type = array(); + $length = $unsigned = $fixed = null; + if (!empty($field['length'])) { + $length = $field['length']; + } + switch ($db_type) { + case 'integer': + case 'pls_integer': + case 'binary_integer': + $type[] = 'integer'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'varchar': + case 'varchar2': + case 'nvarchar2': + $fixed = false; + case 'char': + case 'nchar': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'float': + $type[] = 'float'; + break; + case 'number': + if (!empty($field['scale'])) { + $type[] = 'decimal'; + $length = $length.','.$field['scale']; + } else { + $type[] = 'integer'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + } + break; + case 'long': + $type[] = 'text'; + case 'clob': + case 'nclob': + $type[] = 'clob'; + break; + case 'blob': + case 'raw': + case 'long raw': + case 'bfile': + $type[] = 'blob'; + $length = null; + break; + case 'rowid': + case 'urowid': + 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); + } +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/odbc.php b/extlib/MDB2/Driver/Datatype/odbc.php new file mode 100644 index 0000000000..db369d686a --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/odbc.php @@ -0,0 +1,397 @@ + + */ +class MDB2_Driver_Datatype_odbc extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (is_null($value)) { + return null; + } + switch ($type) { + case 'boolean': + return $value == '1'; + case 'date': + if (strlen($value) > 10) { + $value = substr($value,0,10); + } + return $value; + case 'time': + if (strlen($value) > 8) { + $value = substr($value,11,8); + } + return $value; + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ _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; + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 8000) { + return 'VARCHAR('.$length.')'; + } + } + return 'TEXT'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 8000) { + return "VARBINARY($length)"; + } + } + return 'IMAGE'; + case 'integer': + return 'INT'; + case 'boolean': + return 'BIT'; + case 'date': + return 'CHAR ('.strlen('YYYY-MM-DD').')'; + case 'time': + return 'CHAR ('.strlen('HH:MM:SS').')'; + case 'timestamp': + return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; + case 'float': + return 'FLOAT'; + 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; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $default = $autoinc = ''; + if (!empty($field['autoincrement'])) { + $autoinc = ' IDENTITY PRIMARY KEY'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = 0; + } + if (is_null($field['default'])) { + $default = ' DEFAULT (null)'; + } else { + $default = ' DEFAULT (' . $this->quote($field['default'], 'integer') . ')'; + } + } + + if (!empty($field['unsigned'])) { + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull.$default.$autoinc; + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getCLOBDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getBLOBDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + $value = '0x'.bin2hex($this->_readFile($value)); + return $value; + } + + // }}} + // {{{ _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) + { + + // todo: handle length of various int variations + $db_type = preg_replace('/\d/', '', strtolower($field['type'])); + $length = $field['length']; + $type = array(); + // todo: unsigned handling seems to be missing + $unsigned = $fixed = null; + switch ($db_type) { + case 'bit': + $type[0] = 'boolean'; + break; + case 'tinyint': + $type[0] = 'integer'; + $length = 1; + break; + case 'smallint': + $type[0] = 'integer'; + $length = 2; + break; + case 'integer': + $type[0] = 'integer'; + $length = 4; + break; + case 'bigint': + $type[0] = 'integer'; + $length = 8; + break; + case 'smalldatetime': + case 'timestamp': + case 'time': + case 'date': + $type[0] = 'timestamp'; + break; + case 'float': + case 'real': + case 'numeric': + $type[0] = 'float'; + break; + case 'decimal': + case 'money': + $type[0] = 'decimal'; + $length = $field['numeric_precision'].','.$field['numeric_scale']; + break; + case 'text': + case 'ntext': + case 'varchar() for bit data': + case 'varchar': + case 'nvarchar': + $fixed = false; + case 'char': + case 'nchar': + $type[0] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'image': + case 'blob': + case 'vargraphic() ccsid ': + case 'varbinary': + $type[] = 'blob'; + $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); + } + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/pgsql.php b/extlib/MDB2/Driver/Datatype/pgsql.php new file mode 100644 index 0000000000..1709579f47 --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/pgsql.php @@ -0,0 +1,579 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 PostGreSQL driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_Datatype_pgsql extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * General type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'boolean': + return ($value == 'f')? false : !empty($value); + case 'float': + return doubleval($value); + case 'date': + return $value; + case 'time': + return substr($value, 0, strlen('HH:MM:SS')); + case 'timestamp': + return substr($value, 0, strlen('YYYY-MM-DD HH:MM:SS')); + case 'blob': + $value = pg_unescape_bytea($value); + return parent::_baseConvertResult($value, $type, $rtrim); + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + return 'TEXT'; + case 'blob': + return 'BYTEA'; + case 'integer': + if (!empty($field['autoincrement'])) { + if (!empty($field['length'])) { + $length = $field['length']; + if ($length > 4) { + return 'BIGSERIAL PRIMARY KEY'; + } + } + return 'SERIAL PRIMARY KEY'; + } + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INT'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INT'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME without time zone'; + case 'timestamp': + return 'TIMESTAMP without time zone'; + case 'float': + return 'FLOAT8'; + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'NUMERIC('.$length.','.$scale.')'; + } + } + + // }}} + // {{{ _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 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: + * + * 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; + } + + if (!empty($field['unsigned'])) { + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + if (!empty($field['autoincrement'])) { + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field); + } + $default = ''; + if (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'; + if (empty($default) && empty($notnull)) { + $default = ' DEFAULT NULL'; + } + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$default.$notnull; + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if ($db->options['lob_allow_url_include']) { + $value = $this->_readFile($value); + if (MDB2::isError($value)) { + return $value; + } + } + return $this->_quoteText($value, $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if ($db->options['lob_allow_url_include']) { + $value = $this->_readFile($value); + if (MDB2::isError($value)) { + return $value; + } + } + if (version_compare(PHP_VERSION, '5.2.0RC6', '>=')) { + $connection = $db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $value = @pg_escape_bytea($connection, $value); + } else { + $value = @pg_escape_bytea($value); + } + return "'".$value."'"; + } + + // }}} + // {{{ _quoteBoolean() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBoolean($value, $quote, $escape_wildcards) + { + $value = $value ? 't' : 'f'; + if (!$quote) { + return $value; + } + return "'".$value."'"; + } + + // }}} + // {{{ 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.'ILIKE '; + break; + case 'NOT ILIKE': + $match = $field.'NOT ILIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'LIKE '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE '; + 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; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define escape pattern string + * + * @access public + * + * + * @return string define escape pattern + */ + function patternEscapeString() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + return ' ESCAPE '.$this->quote($db->string_quoting['escape_pattern']); + } + + // }}} + // {{{ _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']); + $length = $field['length']; + $type = array(); + $unsigned = $fixed = null; + switch ($db_type) { + case 'smallint': + case 'int2': + $type[] = 'integer'; + $unsigned = false; + $length = 2; + if ($length == '2') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'int': + case 'int4': + case 'integer': + case 'serial': + case 'serial4': + $type[] = 'integer'; + $unsigned = false; + $length = 4; + break; + case 'bigint': + case 'int8': + case 'bigserial': + case 'serial8': + $type[] = 'integer'; + $unsigned = false; + $length = 8; + break; + case 'bool': + case 'boolean': + $type[] = 'boolean'; + $length = null; + break; + case 'text': + case 'varchar': + $fixed = false; + case 'unknown': + case 'char': + case 'bpchar': + $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'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + case 'timestamptz': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'float4': + case 'float8': + case 'double': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'money': + case 'numeric': + $type[] = 'decimal'; + if (isset($field['scale'])) { + $length = $length.','.$field['scale']; + } + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + case 'bytea': + $type[] = 'blob'; + $length = null; + break; + case 'oid': + $type[] = 'blob'; + $type[] = 'clob'; + $length = null; + 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); + } + + // }}} + // {{{ mapPrepareDatatype() + + /** + * Maps an mdb2 datatype to native prepare type + * + * @param string $type + * @return string + * @access public + */ + function mapPrepareDatatype($type) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!empty($db->options['datatype_map'][$type])) { + $type = $db->options['datatype_map'][$type]; + if (!empty($db->options['datatype_map_callback'][$type])) { + $parameter = array('type' => $type); + return call_user_func_array($db->options['datatype_map_callback'][$type], array(&$db, __FUNCTION__, $parameter)); + } + } + + switch ($type) { + case 'integer': + return 'int'; + case 'boolean': + return 'bool'; + case 'decimal': + case 'float': + return 'numeric'; + case 'clob': + return 'text'; + case 'blob': + return 'bytea'; + default: + break; + } + return $type; + } + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Datatype/querysim.php b/extlib/MDB2/Driver/Datatype/querysim.php new file mode 100644 index 0000000000..7f71663a0e --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/querysim.php @@ -0,0 +1,99 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 QuerySim driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_querysim extends MDB2_Driver_Datatype_Common +{ + // {{{ 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/sqlite.php b/extlib/MDB2/Driver/Datatype/sqlite.php new file mode 100644 index 0000000000..5ae2ea6bb2 --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/sqlite.php @@ -0,0 +1,418 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 SQLite driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_sqlite extends MDB2_Driver_Datatype_Common +{ + // {{{ _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; + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($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 <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INTEGER'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INTEGER'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'DOUBLE'.($db->options['fixed_float'] ? '('. + ($db->options['fixed_float']+2).','.$db->options['fixed_float'].')' : ''); + 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 = ' 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; + } + + // }}} + // {{{ 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 '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE '; + 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']); + $length = !empty($field['length']) ? $field['length'] : null; + $unsigned = !empty($field['unsigned']) ? $field['unsigned'] : null; + $fixed = null; + $type = array(); + switch ($db_type) { + case 'boolean': + $type[] = 'boolean'; + break; + 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': + case 'serial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 4; + break; + case 'bigint': + case 'bigserial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 8; + break; + case 'clob': + $type[] = 'clob'; + $fixed = false; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'varchar': + case 'varchar2': + $fixed = false; + 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'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + 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'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $length = $length.','.$field['decimal']; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $length = null; + 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); + } + + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/sqlite3.php b/extlib/MDB2/Driver/Datatype/sqlite3.php new file mode 100644 index 0000000000..9301f1a4c8 --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/sqlite3.php @@ -0,0 +1,451 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 SQLite driver + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_Datatype_sqlite3 extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * General type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'decimal': + switch (gettype($value)) { + case 'integer': + //return (float)($value * 1.0); + return sprintf('%0.2f', $value); + case 'string': + if (!strpos($value, '.')) { + $value .= '.00'; + } + break; + } + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ _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; + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($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 <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INTEGER'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INTEGER'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'DOUBLE'.($db->options['fixed_float'] ? '('. + ($db->options['fixed_float']+2).','.$db->options['fixed_float'].')' : ''); + 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 = ' 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; + } + + // }}} + // {{{ 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 '; + break; + case 'NOT LIKE': + $match = $field.'NOT LIKE '; + 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']); + $length = !empty($field['length']) ? $field['length'] : null; + $unsigned = !empty($field['unsigned']) ? $field['unsigned'] : null; + $fixed = null; + $type = array(); + switch ($db_type) { + case 'boolean': + $type[] = 'boolean'; + break; + 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': + case 'serial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 4; + break; + case 'bigint': + case 'bigserial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 8; + break; + case 'clob': + $type[] = 'clob'; + $fixed = false; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'varchar': + case 'varchar2': + $fixed = false; + 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'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + 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'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $length = $length.','.$field['decimal']; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $length = null; + 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); + } + + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Datatype/sqlsrv.php b/extlib/MDB2/Driver/Datatype/sqlsrv.php new file mode 100644 index 0000000000..bc00fa16a6 --- /dev/null +++ b/extlib/MDB2/Driver/Datatype/sqlsrv.php @@ -0,0 +1,459 @@ + | +// | Daniel Convissor | +// +----------------------------------------------------------------------+ + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MS SQL driver + * + * @package MDB2 + * @category Database + */ +class MDB2_Driver_Datatype_sqlsrv extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'boolean': + return ($value === 0)? false : !empty($value); + case 'date': + if (strlen($value) > 10) { + $value = substr($value,0,10); + } + return $value; + case 'time': + if (strlen($value) > 8) { + $value = substr($value,11,8); + } + return $value; + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ _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; + } + + // }}} + // {{{ 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': + $length = !empty($field['length']) + ? $field['length'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'VARCHAR(MAX)'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 8000) { + return 'VARCHAR('.$length.')'; + } + } + return 'VARCHAR(MAX)'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 8000) { + return "VARBINARY($length)"; + } + } + return 'IMAGE'; + case 'integer': + return 'INT'; + case 'boolean': + return 'BIT'; + case 'date': + return 'CHAR ('.strlen('YYYY-MM-DD').')'; + case 'time': + return 'CHAR ('.strlen('HH:MM:SS').')'; + case 'timestamp': + return 'CHAR ('.strlen('YYYY-MM-DD HH:MM:SS').')'; + case 'float': + return 'FLOAT'; + 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; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $default = $autoinc = ''; + if (!empty($field['autoincrement'])) { + $autoinc = ' IDENTITY PRIMARY KEY'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = 0; + } + if (null === $field['default']) { + $default = ' DEFAULT (NULL)'; + } else { + $default = ' DEFAULT (' . $this->quote($field['default'], 'integer') . ')'; + } + } + + if (!empty($field['unsigned'])) { + $db->warnings[] = "unsigned integer field \"$name\" is being declared as signed integer"; + } + + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull.$default.$autoinc; + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getCLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @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 large + * object field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * 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 _getBLOBDeclaration($name, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $name = $db->quoteIdentifier($name, true); + return $name.' '.$this->getTypeDeclaration($field).$notnull; + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + if (!$quote) { + return $value; + } + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if ($db->options['lob_allow_url_include']) { + $value = '0x'.bin2hex($this->_readFile($value)); + } + return "'".$value."'"; + } + + // }}} + // {{{ _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) + { + // todo: handle length of various int variations + $db_type = $field['type']; + $length = $field['length']; + $type = array(); + // todo: unsigned handling seems to be missing + $unsigned = $fixed = null; + switch ($db_type) { + case 'bit': + case SQLSRV_SQLTYPE_BIT: + $type[0] = 'boolean'; + break; + case 'tinyint': + case SQLSRV_SQLTYPE_TINYINT: + $type[0] = 'integer'; + $length = 1; + break; + case 'smallint': + case SQLSRV_SQLTYPE_SMALLINT: + $type[0] = 'integer'; + $length = 2; + break; + case 'int': + case SQLSRV_SQLTYPE_INT: + $type[0] = 'integer'; + $length = 4; + break; + case 'bigint': + case SQLSRV_SQLTYPE_BIGINT: + $type[0] = 'integer'; + $length = 8; + break; + case 'datetime': + case SQLSRV_SQLTYPE_DATETIME: + $type[0] = 'timestamp'; + break; + case 'float': + case SQLSRV_SQLTYPE_FLOAT: + case 'real': + case SQLSRV_SQLTYPE_REAL: + $type[0] = 'float'; + break; + case 'numeric': + case SQLSRV_SQLTYPE_NUMERIC: + case 'decimal': + case SQLSRV_SQLTYPE_DECIMAL: + case 'money': + case SQLSRV_SQLTYPE_MONEY: + $type[0] = 'decimal'; + $length = $field['numeric_precision'].','.$field['numeric_scale']; + break; + case 'text': + case SQLSRV_SQLTYPE_TEXT: + case 'ntext': + case SQLSRV_SQLTYPE_NTEXT: + case 'varchar': + case SQLSRV_SQLTYPE_VARCHAR: + case 'nvarchar': + case SQLSRV_SQLTYPE_NVARCHAR: + $fixed = false; + case 'char': + case SQLSRV_SQLTYPE_CHAR: + case 'nchar': + case SQLSRV_SQLTYPE_NCHAR: + $type[0] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text') || strstr($db_type, SQLSRV_SQLTYPE_TEXT)) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'image': + case SQLSRV_SQLTYPE_IMAGE: + case 'varbinary': + case SQLSRV_SQLTYPE_VARBINARY: + case 'timestamp': + case SQLSRV_SQLTYPE_TIMESTAMP: + $type[] = 'blob'; + $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); + } + // }}} +} + +?> diff --git a/extlib/MDB2/Driver/Function/Common.php b/extlib/MDB2/Driver/Function/Common.php new file mode 100644 index 0000000000..6d328df535 --- /dev/null +++ b/extlib/MDB2/Driver/Function/Common.php @@ -0,0 +1,293 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Base class for the function modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Function'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_Common extends MDB2_Module_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; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} + // {{{ functionTable() + + /** + * return string for internal table used when calling only a function + * + * @return string for internal table used when calling only a function + * @access public + */ + function functionTable() + { + return ''; + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @param string $type 'timestamp' | 'time' | 'date' + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'CURRENT_TIME'; + case 'date': + return 'CURRENT_DATE'; + case 'timestamp': + default: + return 'CURRENT_TIMESTAMP'; + } + } + + // }}} + // {{{ 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) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTRING($value FROM $position FOR $length)"; + } + return "SUBSTRING($value FROM $position)"; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get replace inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + return "REPLACE($str, $from_str , $to_str)"; + } + + // }}} + // {{{ 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 "(".implode(' || ', $args).")"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'RAND()'; + } + + // }}} + // {{{ lower() + + /** + * return string to call a function to lower the case of an expression + * + * @param string $expression + * + * @return return string to lower case of an expression + * @access public + */ + function lower($expression) + { + return "LOWER($expression)"; + } + + // }}} + // {{{ upper() + + /** + * return string to call a function to upper the case of an expression + * + * @param string $expression + * + * @return return string to upper case of an expression + * @access public + */ + function upper($expression) + { + return "UPPER($expression)"; + } + + // }}} + // {{{ length() + + /** + * return string to call a function to get the length of a string expression + * + * @param string $expression + * + * @return return string to get the string expression length + * @access public + */ + function length($expression) + { + return "LENGTH($expression)"; + } + + // }}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Function/fbsql.php b/extlib/MDB2/Driver/Function/fbsql.php new file mode 100644 index 0000000000..27c6f612ac --- /dev/null +++ b/extlib/MDB2/Driver/Function/fbsql.php @@ -0,0 +1,61 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 FrontBase driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_fbsql extends MDB2_Driver_Function_Common +{ +} + +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Function/ibase.php b/extlib/MDB2/Driver/Function/ibase.php new file mode 100644 index 0000000000..df02bc41e9 --- /dev/null +++ b/extlib/MDB2/Driver/Function/ibase.php @@ -0,0 +1,108 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 FireBird/InterBase driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_ibase extends MDB2_Driver_Function_Common +{ + // {{{ functionTable() + + /** + * return string for internal table used when calling only a function + * + * @return string for internal table used when calling only a function + * @access public + */ + function functionTable() + { + return ' FROM RDB$DATABASE'; + } + + // }}} + // {{{ length() + + /** + * return string to call a function to get a replacement inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function length($expression) + { + return "STRLEN($expression)"; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get a replacement inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Function/mssql.php b/extlib/MDB2/Driver/Function/mssql.php new file mode 100644 index 0000000000..5e11dd42c4 --- /dev/null +++ b/extlib/MDB2/Driver/Function/mssql.php @@ -0,0 +1,193 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +// {{{ class MDB2_Driver_Function_mssql +/** + * MDB2 MSSQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_mssql 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 = 'EXECUTE '.$name; + $query .= $params ? ' '.implode(', ', $params) : ''; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + case 'date': + case 'timestamp': + default: + return 'GETDATE()'; + } + } + + // }}} + // {{{ 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 'DATEDIFF(second, \'19700101\', '. $expression.') + DATEDIFF(second, GETDATE(), GETUTCDATE())'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTRING($value, $position, $length)"; + } + return "SUBSTRING($value, $position, LEN($value) - $position + 1)"; + } + + // }}} + // {{{ 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 "(".implode(' + ', $args).")"; + } + + // }}} + // {{{ length() + + /** + * return string to call a function to get the length of a string expression + * + * @param string $expression + * @return return string to get the string expression length + * @access public + */ + function length($expression) + { + return "LEN($expression)"; + } + + // }}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + return 'NEWID()'; + } + + // }}} +} +// }}} +?> diff --git a/extlib/MDB2/Driver/Function/mysqli.php b/extlib/MDB2/Driver/Function/mysqli.php new file mode 100644 index 0000000000..99dcfa6d6d --- /dev/null +++ b/extlib/MDB2/Driver/Function/mysqli.php @@ -0,0 +1,144 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQLi driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_mysqli 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; + } + + $multi_query = $db->getOption('multi_query'); + if (!$multi_query) { + $db->setOption('multi_query', true); + } + $query = 'CALL '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + $result = $db->query($query, $types, $result_class, $result_wrap_class); + if (!$multi_query) { + $db->setOption('multi_query', false); + } + return $result; + } + + // }}} + // {{{ 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()'; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Function/oci8.php b/extlib/MDB2/Driver/Function/oci8.php new file mode 100644 index 0000000000..ed248c06ca --- /dev/null +++ b/extlib/MDB2/Driver/Function/oci8.php @@ -0,0 +1,187 @@ + | +// +----------------------------------------------------------------------+ + +// $Id$ + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 oci8 driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_oci8 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 = 'EXEC '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ functionTable() + + /** + * return string for internal table used when calling only a function + * + * @return string for internal table used when calling only a function + * @access public + */ + function functionTable() + { + return ' FROM dual'; + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'date': + case 'time': + case 'timestamp': + default: + return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; + } + } + + // }}} + // {{{ 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) + { + $utc_offset = 'CAST(SYS_EXTRACT_UTC(SYSTIMESTAMP) AS DATE) - CAST(SYSTIMESTAMP AS DATE)'; + $epoch_date = 'to_date(\'19700101\', \'YYYYMMDD\')'; + return '(CAST('.$expression.' AS DATE) - '.$epoch_date.' + '.$utc_offset.') * 86400 seconds'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTR($value, $position, $length)"; + } + return "SUBSTR($value, $position)"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'dbms_random.value'; + } + + // }}}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + return 'SYS_GUID()'; + } + + // }}}} +} +?> diff --git a/extlib/MDB2/Driver/Function/pgsql.php b/extlib/MDB2/Driver/Function/pgsql.php new file mode 100644 index 0000000000..da9de7b02c --- /dev/null +++ b/extlib/MDB2/Driver/Function/pgsql.php @@ -0,0 +1,132 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_pgsql extends MDB2_Driver_Function_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = 'SELECT * FROM '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + return 'EXTRACT(EPOCH FROM DATE_TRUNC(\'seconds\', CAST ((' . $expression . ') AS TIMESTAMP)))'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTRING(CAST($value AS VARCHAR) FROM $position FOR $length)"; + } + return "SUBSTRING(CAST($value AS VARCHAR) FROM $position)"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'RANDOM()'; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Function/sqlite.php b/extlib/MDB2/Driver/Function/sqlite.php new file mode 100644 index 0000000000..fd63c90d3c --- /dev/null +++ b/extlib/MDB2/Driver/Function/sqlite.php @@ -0,0 +1,162 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 SQLite driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_sqlite extends MDB2_Driver_Function_Common +{ + // {{{ constructor + + /** + * Constructor + */ + function __construct($db_index) + { + parent::__construct($db_index); + // create all sorts of UDFs + } + + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time. + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'time(\'now\')'; + case 'date': + return 'date(\'now\')'; + case 'timestamp': + default: + return 'datetime(\'now\')'; + } + } + + // }}} + // {{{ 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 'strftime("%s",'. $expression.', "utc")'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "substr($value, $position, $length)"; + } + return "substr($value, $position, length($value))"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return '((RANDOM()+2147483648)/4294967296)'; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get a replacement inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Function/sqlite3.php b/extlib/MDB2/Driver/Function/sqlite3.php new file mode 100644 index 0000000000..9efb48b55d --- /dev/null +++ b/extlib/MDB2/Driver/Function/sqlite3.php @@ -0,0 +1,162 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 SQLite3 driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_Function_sqlite3 extends MDB2_Driver_Function_Common +{ + // {{{ constructor + + /** + * Constructor + */ + function __construct($db_index) + { + parent::__construct($db_index); + // create all sorts of UDFs + } + + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time. + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'time(\'now\')'; + case 'date': + return 'date(\'now\')'; + case 'timestamp': + default: + return 'datetime(\'now\')'; + } + } + + // }}} + // {{{ 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 'strftime("%s",'. $expression.', "utc")'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "substr($value, $position, $length)"; + } + return "substr($value, $position, length($value))"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return '((RANDOM()+9223372036854775808)/18446744073709551616)'; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get a replacement inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $error = $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Function/sqlsrv.php b/extlib/MDB2/Driver/Function/sqlsrv.php new file mode 100644 index 0000000000..8378c472e9 --- /dev/null +++ b/extlib/MDB2/Driver/Function/sqlsrv.php @@ -0,0 +1,189 @@ + | +// +----------------------------------------------------------------------+ + +require_once 'MDB2/Driver/Function/Common.php'; + +// {{{ class MDB2_Driver_Function_sqlsrv +/** + * MDB2 MSSQL driver for the function modules + * + * @package MDB2 + * @category Database + */ +class MDB2_Driver_Function_sqlsrv 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 = 'EXECUTE '.$name; + $query .= $params ? ' '.implode(', ', $params) : ''; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + case 'date': + case 'timestamp': + default: + return 'GETDATE()'; + } + } + + // }}} + // {{{ 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 'DATEDIFF(second, \'19700101\', '. $expression.') + DATEDIFF(second, GETDATE(), GETUTCDATE())'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTRING($value, $position, $length)"; + } + return "SUBSTRING($value, $position, LEN($value) - $position + 1)"; + } + + // }}} + // {{{ 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 "(".implode(' + ', $args).")"; + } + + // }}} + // {{{ length() + + /** + * return string to call a function to get the length of a string expression + * + * @param string $expression + * @return return string to get the string expression length + * @access public + */ + function length($expression) + { + return "LEN($expression)"; + } + + // }}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + return 'NEWID()'; + } + + // }}} +} +// }}} +?> diff --git a/extlib/MDB2/Driver/Manager/Common.php b/extlib/MDB2/Driver/Manager/Common.php new file mode 100644 index 0000000000..60ee95c31e --- /dev/null +++ b/extlib/MDB2/Driver/Manager/Common.php @@ -0,0 +1,1038 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ + +/** + * Base class for the management modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Manager'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * + * @param string $table [schema and] table name + * + * @return array array(schema, table) + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getFieldDeclarationList() + + /** + * Get declaration of a number of field in bulk + * + * @param array $fields a multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return mixed string on success, a MDB2 error on failure + * @access public + */ + function getFieldDeclarationList($fields) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!is_array($fields) || empty($fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'missing any fields', __FUNCTION__); + } + foreach ($fields as $field_name => $field) { + $query = $db->getDeclaration($field['type'], $field_name, $field); + if (MDB2::isError($query)) { + return $query; + } + $query_fields[] = $query; + } + return implode(', ', $query_fields); + } + + // }}} + // {{{ _fixSequenceName() + + /** + * Removes any formatting in an sequence name using the 'seqname_format' option + * + * @param string $sqn string that containts name of a potential sequence + * @param bool $check if only formatted sequences should be returned + * @return string name of the sequence with possible formatting removed + * @access protected + */ + function _fixSequenceName($sqn, $check = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; + $seq_name = preg_replace($seq_pattern, '\\1', $sqn); + if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { + return $seq_name; + } + if ($check) { + return false; + } + return $sqn; + } + + // }}} + // {{{ _fixIndexName() + + /** + * Removes any formatting in an index name using the 'idxname_format' option + * + * @param string $idx string that containts name of anl index + * @return string name of the index with eventual formatting removed + * @access protected + */ + function _fixIndexName($idx) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; + $idx_name = preg_replace($idx_pattern, '\\1', $idx); + if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { + return $idx_name; + } + return $idx; + } + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($database, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($database, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($database) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (MDB2::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'TEMPORARY'; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (MDB2::isError($query)) { + return $query; + } + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $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; + } + + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("DROP TABLE $name"); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ 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("DELETE FROM $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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implementedd', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the view names from + * a database other than the current one + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default. + * NB: not all the drivers can get the table names from + * a database other than the current one + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (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 (array_keys($definition['fields']) as $field) { + $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; + } + + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $result = $db->exec("DROP INDEX $name"); + 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + return ''; + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * The full structure of the array looks like this: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table ADD CONSTRAINT $name"; + if (!empty($definition['primary'])) { + $query.= ' PRIMARY KEY'; + } elseif (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $query.= ' FOREIGN KEY'; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + 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; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $result = $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); + 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the sequence names from + * a database other than the current one + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Manager/fbsql.php b/extlib/MDB2/Driver/Manager/fbsql.php new file mode 100644 index 0000000000..678f3cf6e9 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/fbsql.php @@ -0,0 +1,597 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 FrontBase driver for the management modules + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Driver_Manager_fbsql 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"; + 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 = "DELETE DATABASE $name"; + return $db->standaloneQuery($query, null, true); + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param object $dbs database object that is extended by this class + * @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; + } + + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("DROP TABLE $name CASCADE"); + 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'], $old_field_name, $field['definition']); + } + } + + if (!empty($rename) && is_array($rename)) { + foreach ($rename as $renamed_field_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $old_field_name = $rename[$renamed_field_name]; + $field = $changes['rename'][$old_field_name]; + $query.= 'CHANGE ' . $db->getDeclaration($field['definition']['type'], $old_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; + } + + return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, + 'not capable', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->queryCol('SELECT "user_name" FROM information_schema.users'); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table_names = $db->queryCol('SELECT "table_name"' + . ' FROM information_schema.tables' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'BASE TABLE\'' + . ' AND "schema_name" = current_schema'); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + for ($i = 0, $j = count($table_names); $i < $j; ++$i) { + if (!$this->_fixSequenceName($table_names[$i], true)) { + $result[] = $table_names[$i]; + } + } + 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; + } + + // }}} + // {{{ 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("ALTER TABLE $table DROP INDEX $name"); + 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'; + 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); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryCol($query, 'text', $key_name); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index) { + if ($index != 'PRIMARY' && ($index = $this->_fixIndexName($index))) { + $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); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + $query = "CREATE TABLE $sequence_name ($seqcol_name INTEGER DEFAULT UNIQUE, PRIMARY KEY($seqcol_name))"; + $res = $db->exec($query); + $res = $db->exec("SET UNIQUE = 1 FOR $sequence_name"); + if (MDB2::isError($res)) { + return $res; + } + if ($start == 1) { + return MDB2_OK; + } + $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + 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 CASCADE"); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table_names = $db->queryCol('SHOW TABLES', 'text'); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + for ($i = 0, $j = count($table_names); $i < $j; ++$i) { + if ($sqn = $this->_fixSequenceName($table_names[$i], 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; + } + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Manager/ibase.php b/extlib/MDB2/Driver/Manager/ibase.php new file mode 100644 index 0000000000..09f1dd9cc4 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/ibase.php @@ -0,0 +1,1136 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 FireBird/InterBase driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_ibase 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 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, 'Create database', + 'PHP Interbase API does not support direct queries. You have to '. + 'create the db manually by using isql command or a similar program', __FUNCTION__); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, 'Drop database', + 'PHP Interbase API does not support direct queries. You have '. + 'to drop the db manually by using isql command or a similar program', __FUNCTION__); + } + + // }}} + // {{{ _silentCommit() + + /** + * conditional COMMIT query to make changes permanent, when auto + * @access private + */ + function _silentCommit() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if (!$db->in_transaction) { + @$db->exec('COMMIT'); + } + } + + // }}} + // {{{ _makeAutoincrement() + + /** + * add an autoincrement sequence + trigger + * + * @param string $name name of the PK field + * @param string $table name of the table + * @param string $start start value for the sequence + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _makeAutoincrement($name, $table, $start = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table_quoted = $db->quoteIdentifier($table, true); + if (null === $start) { + $db->beginTransaction(); + $query = 'SELECT MAX(' . $db->quoteIdentifier($name, true) . ') FROM ' . $table_quoted; + $start = $this->db->queryOne($query, 'integer'); + if (MDB2::isError($start)) { + return $start; + } + ++$start; + $result = $db->manager->createSequence($table, $start); + $db->commit(); + } else { + $result = $db->manager->createSequence($table, $start); + } + if (MDB2::isError($result)) { + return $db->raiseError(null, null, null, + 'sequence for autoincrement PK could not be created', __FUNCTION__); + } + + $sequence_name = $db->getSequenceName($table); + $trigger_name = $db->quoteIdentifier($table . '_AI_PK', true); + $name = $db->quoteIdentifier($name, true); + $trigger_sql = 'CREATE TRIGGER ' . $trigger_name . ' FOR ' . $table_quoted . ' + ACTIVE BEFORE INSERT POSITION 0 + AS + BEGIN + IF (NEW.' . $name . ' IS NULL OR NEW.' . $name . ' = 0) THEN + NEW.' . $name . ' = GEN_ID('.$sequence_name.', 1); + END'; + $result = $db->exec($trigger_sql); + if (MDB2::isError($result)) { + return $result; + } + $this->_silentCommit(); + return MDB2_OK; + } + + // }}} + // {{{ _dropAutoincrement() + + /** + * drop an existing autoincrement sequence + trigger + * + * @param string $table name of the table + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropAutoincrement($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $result = $db->manager->dropSequence($table); + if (MDB2::isError($result)) { + return $db->raiseError(null, null, null, + 'sequence for autoincrement PK could not be dropped', __FUNCTION__); + } + //remove autoincrement trigger associated with the table + $table = $db->quote(strtoupper($table), 'text'); + $trigger_name = $db->quote(strtoupper($table) . '_AI_PK', 'text'); + $trigger_name_old = $db->quote(strtoupper($table) . '_AUTOINCREMENT_PK', 'text'); + $query = "DELETE FROM RDB\$TRIGGERS + WHERE UPPER(RDB\$RELATION_NAME)=$table + AND (UPPER(RDB\$TRIGGER_NAME)=$trigger_name + OR UPPER(RDB\$TRIGGER_NAME)=$trigger_name_old)"; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $db->raiseError(null, null, null, + 'trigger for autoincrement PK could not be dropped', __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ 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. + * + * Example + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1, + * 'notnull' => 1, + * 'default' => 0, + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12, + * ), + * 'description' => array( + * 'type' => 'text', + * 'length' => 12, + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (MDB2::isError($query)) { + return $query; + } + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = '/* '.$db->quote($options['comment'], 'text'). ' */'; + } + + if (!empty($options_strings)) { + $query.= ' '.implode(' ', $options_strings); + } + + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + $this->_silentCommit(); + foreach ($fields as $field_name => $field) { + if (!empty($field['autoincrement'])) { + //create PK constraint + $pk_definition = array( + 'fields' => array($field_name => array()), + 'primary' => true, + ); + //$pk_name = $name.'_PK'; + $pk_name = null; + $result = $this->createConstraint($name, $pk_name, $pk_definition); + if (MDB2::isError($result)) { + return $result; + } + //create autoincrement sequence + trigger + return $this->_makeAutoincrement($field_name, $name, 1); + } + } + return MDB2_OK; + } + + // }}} + // {{{ checkSupportedChanges() + + /** + * Check if planned changes are supported + * + * @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 checkSupportedChanges(&$changes) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'notnull': + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'it is not supported changes to field not null constraint', __FUNCTION__); + case 'default': + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'it is not supported changes to field default value', __FUNCTION__); + case 'length': + /* + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'it is not supported changes to field default length', __FUNCTION__); + */ + case 'unsigned': + case 'type': + case 'declaration': + case 'definition': + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'it is not supported change of type' . $change_name, __FUNCTION__); + } + } + return MDB2_OK; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'GLOBAL TEMPORARY'; + } + + // }}} + // {{{ _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['onupdate'])) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete'])) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + return $query; + } + + // }}} + // {{{ 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) + { + $result = $this->_dropAutoincrement($name); + if (MDB2::isError($result)) { + return $result; + } + $result = parent::dropTable($name); + if (MDB2::isError($result)) { + return $result; + } + $this->_silentCommit(); + return $result; + } + + // }}} + // {{{ 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()) + { + // not needed in Interbase/Firebird + 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. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + 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 'rename': + break; + case 'change': + foreach ($changes['change'] as $field) { + if (MDB2::isError($err = $this->checkSupportedChanges($field))) { + return $err; + } + } + 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['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; + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'ALTER ' . $field_name . ' TO ' . $db->quoteIdentifier($field['name'], true); + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + // missing support to change DEFAULT and NULLability + foreach ($changes['change'] as $field_name => $field) { + if (MDB2::isError($err = $this->checkSupportedChanges($field))) { + return $err; + } + if ($query) { + $query.= ', '; + } + $db->loadModule('Datatype', null, true); + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'ALTER ' . $field_name.' TYPE ' . $db->datatype->getTypeDeclaration($field['definition']); + } + } + + if (!strlen($query)) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("ALTER TABLE $name $query"); + if (MDB2::isError($result)) { + return $result; + } + $this->_silentCommit(); + return MDB2_OK; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $query = 'SELECT RDB$RELATION_NAME + FROM RDB$RELATIONS + WHERE (RDB$SYSTEM_FLAG=0 OR RDB$SYSTEM_FLAG IS NULL) + AND RDB$VIEW_BLR IS NULL + ORDER BY RDB$RELATION_NAME'; + $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->quote(strtoupper($table), 'text'); + $query = "SELECT RDB\$FIELD_NAME + FROM RDB\$RELATION_FIELDS + WHERE UPPER(RDB\$RELATION_NAME)=$table + AND (RDB\$SYSTEM_FLAG=0 OR RDB\$SYSTEM_FLAG IS NULL)"; + $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; + } + + // }}} + // {{{ 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 RDB$USER FROM RDB$USER_PRIVILEGES'); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $result = $db->queryCol('SELECT DISTINCT RDB$VIEW_NAME FROM RDB$VIEW_RELATIONS'); + 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; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = 'SELECT DISTINCT RDB$VIEW_NAME FROM RDB$VIEW_RELATIONS'; + $table = $db->quote(strtoupper($table), 'text'); + $query .= " WHERE UPPER(RDB\$RELATION_NAME)=$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; + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions (and stored procedures) 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 RDB$FUNCTION_NAME FROM RDB$FUNCTIONS WHERE (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0) + UNION + SELECT RDB$PROCEDURE_NAME FROM RDB$PROCEDURES'; + $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 = 'SELECT RDB$TRIGGER_NAME + FROM RDB$TRIGGERS + WHERE (RDB$SYSTEM_FLAG IS NULL + OR RDB$SYSTEM_FLAG = 0)'; + if (null !== $table) { + $table = $db->quote(strtoupper($table), 'text'); + $query .= " AND UPPER(RDB\$RELATION_NAME)=$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; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function support() to determine whether the DBMS driver can manage indexes. + + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $query = 'CREATE'; + + $query_sort = ''; + foreach ($definition['fields'] as $field) { + if (!strcmp($query_sort, '') && isset($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $query_sort = ' ASC'; + break; + case 'descending': + $query_sort = ' DESC'; + break; + } + } + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query .= $query_sort. " INDEX $name ON $table"; + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('.implode(', ', $fields) . ')'; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + $this->_silentCommit(); + 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; + } + $table = $db->quote(strtoupper($table), 'text'); + $query = "SELECT RDB\$INDEX_NAME + FROM RDB\$INDICES + WHERE UPPER(RDB\$RELATION_NAME)=$table + AND (RDB\$SYSTEM_FLAG IS NULL OR RDB\$SYSTEM_FLAG = 0) + AND (RDB\$UNIQUE_FLAG IS NULL OR RDB\$UNIQUE_FLAG = 0) + AND RDB\$FOREIGN_KEY IS NULL"; + $indexes = $db->queryCol($query, 'text'); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index) { + $index = $this->_fixIndexName($index); + if (!empty($index)) { + $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; + } + $table = $db->quoteIdentifier($table, true); + if (!empty($name)) { + $name = $db->quoteIdentifier($db->getIndexName($name), true); + } + $query = "ALTER TABLE $table ADD"; + if (!empty($definition['primary'])) { + if (!empty($name)) { + $query.= ' CONSTRAINT '.$name; + } + $query.= ' PRIMARY KEY'; + } else { + $query.= ' CONSTRAINT '. $name; + if (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $query.= ' FOREIGN KEY'; + } + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + $this->_silentCommit(); + 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; + } + $table = $db->quote(strtoupper($table), 'text'); + $query = "SELECT RDB\$INDEX_NAME + FROM RDB\$INDICES + WHERE UPPER(RDB\$RELATION_NAME)=$table + AND ( + (RDB\$UNIQUE_FLAG IS NOT NULL AND RDB\$UNIQUE_FLAG <> 0) + OR RDB\$FOREIGN_KEY IS NOT NULL + )"; + $constraints = $db->queryCol($query); + if (MDB2::isError($constraints)) { + return $constraints; + } + + $result = array(); + foreach ($constraints as $constraint) { + $constraint = $this->_fixIndexName($constraint); + if (!empty($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 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $sequence_name = $db->getSequenceName($seq_name); + if (MDB2::isError($result = $db->exec('CREATE GENERATOR '.$sequence_name))) { + return $result; + } + if (MDB2::isError($result = $db->exec('SET GENERATOR '.$sequence_name.' TO '.($start-1)))) { + if (MDB2::isError($err = $db->dropSequence($seq_name))) { + return $db->raiseError($result, null, null, + 'Could not setup sequence start value and then it was not possible to drop it', __FUNCTION__); + } + } + return MDB2_OK; + } + + // }}} + // {{{ 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->getSequenceName($seq_name); + $sequence_name = $db->quote($sequence_name, 'text'); + $query = "DELETE FROM RDB\$GENERATORS WHERE UPPER(RDB\$GENERATOR_NAME)=$sequence_name"; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = 'SELECT RDB$GENERATOR_NAME FROM RDB$GENERATORS WHERE (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0)'; + $table_names = $db->queryCol($query); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + $result[] = $this->_fixSequenceName($table_name); + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } +} +?> diff --git a/extlib/MDB2/Driver/Manager/mssql.php b/extlib/MDB2/Driver/Manager/mssql.php new file mode 100644 index 0000000000..0f1ae11342 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/mssql.php @@ -0,0 +1,1145 @@ + | +// | David Coallier | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +// {{{ class MDB2_Driver_Manager_mssql + +/** + * MDB2 MSSQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + * @author David Coallier + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_mssql 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 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 ($db->options['database_device']) { + $query.= ' ON '.$db->options['database_device']; + $query.= $db->options['database_size'] ? '=' . + $db->options['database_size'] : ''; + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + 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 name, 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 = ''; + if (!empty($options['name'])) { + $query .= ' MODIFY NAME = ' .$db->quoteIdentifier($options['name'], true); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + if (!empty($query)) { + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true) . $query; + return $db->standaloneQuery($query, null, true); + } + return MDB2_OK; + } + + // }}} + // {{{ 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); + return $db->standaloneQuery("DROP DATABASE $name", null, true); + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * Override the parent method. + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return ''; + } + + // }}} + // {{{ _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['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. + * + * Example + * array( + * + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1, + * 'notnull' => 1, + * 'default' => 0, + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12, + * ), + * 'description' => array( + * 'type' => 'text', + * 'length' => 12, + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + if (!empty($options['temporary'])) { + $name = '#'.$name; + } + return parent::createTable($name, $fields, $options); + } + + // }}} + // {{{ 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] + * + * NB: you have to run the NSControl Create utility to enable VACUUM + * + * @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; + } + $timeout = isset($options['timeout']) ? (int)$options['timeout'] : 300; + + $query = 'NSControl Create'; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + + $result = $db->exec('EXEC NSVacuum '.$timeout); + 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. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $name_quoted = $db->quoteIdentifier($name, true); + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'remove': + case 'rename': + case 'add': + case 'change': + 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; + } + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + $result = $this->_dropConflictingIndices($name, array_keys($changes['remove'])); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + $result = $this->_dropConflictingConstraints($name, array_keys($changes['remove'])); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + + $query = ''; + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'COLUMN ' . $field_name; + } + + $result = $db->exec("ALTER TABLE $name_quoted DROP $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $result = $db->exec("sp_rename '$name_quoted.$field_name', '".$field['name']."', 'COLUMN'"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + $query = ''; + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } else { + $query.= 'ADD '; + } + $query.= $db->getDeclaration($field['type'], $field_name, $field); + } + + $result = $db->exec("ALTER TABLE $name_quoted $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + $dropped_indices = array(); + $dropped_constraints = array(); + + if (!empty($changes['change']) && is_array($changes['change'])) { + $dropped = $this->_dropConflictingIndices($name, array_keys($changes['change'])); + if (MDB2::isError($dropped)) { + $db->setOption('idxname_format', $idxname_format); + return $dropped; + } + $dropped_indices = array_merge($dropped_indices, $dropped); + $dropped = $this->_dropConflictingConstraints($name, array_keys($changes['change'])); + if (MDB2::isError($dropped)) { + $db->setOption('idxname_format', $idxname_format); + return $dropped; + } + $dropped_constraints = array_merge($dropped_constraints, $dropped); + + foreach ($changes['change'] as $field_name => $field) { + //MSSQL doesn't allow multiple ALTER COLUMNs in one query + $query = 'ALTER COLUMN '; + + //MSSQL doesn't allow changing the DEFAULT value of a field in altering mode + if (array_key_exists('default', $field['definition'])) { + unset($field['definition']['default']); + } + + $query .= $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + $result = $db->exec("ALTER TABLE $name_quoted $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + } + + // restore the dropped conflicting indices and constraints + foreach ($dropped_indices as $index_name => $index) { + $result = $this->createIndex($name, $index_name, $index); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + foreach ($dropped_constraints as $constraint_name => $constraint) { + $result = $this->createConstraint($name, $constraint_name, $constraint); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + $db->setOption('idxname_format', $idxname_format); + + if (!empty($changes['name'])) { + $new_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("sp_rename '$name_quoted', '$new_name'"); + if (MDB2::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _dropConflictingIndices() + + /** + * Drop the indices that prevent a successful ALTER TABLE action + * + * @param string $table table name + * @param array $fields array of names of the fields affected by the change + * + * @return array dropped indices definitions + */ + function _dropConflictingIndices($table, $fields) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $dropped = array(); + $index_names = $this->listTableIndexes($table); + if (MDB2::isError($index_names)) { + return $index_names; + } + $db->loadModule('Reverse'); + $indexes = array(); + foreach ($index_names as $index_name) { + $idx_def = $db->reverse->getTableIndexDefinition($table, $index_name); + if (!MDB2::isError($idx_def)) { + $indexes[$index_name] = $idx_def; + } + } + foreach ($fields as $field_name) { + foreach ($indexes as $index_name => $index) { + if (!isset($dropped[$index_name]) && array_key_exists($field_name, $index['fields'])) { + $dropped[$index_name] = $index; + $result = $this->dropIndex($table, $index_name); + if (MDB2::isError($result)) { + return $result; + } + } + } + } + + return $dropped; + } + + // }}} + // {{{ _dropConflictingConstraints() + + /** + * Drop the constraints that prevent a successful ALTER TABLE action + * + * @param string $table table name + * @param array $fields array of names of the fields affected by the change + * + * @return array dropped constraints definitions + */ + function _dropConflictingConstraints($table, $fields) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $dropped = array(); + $constraint_names = $this->listTableConstraints($table); + if (MDB2::isError($constraint_names)) { + return $constraint_names; + } + $db->loadModule('Reverse'); + $constraints = array(); + foreach ($constraint_names as $constraint_name) { + $cons_def = $db->reverse->getTableConstraintDefinition($table, $constraint_name); + if (!MDB2::isError($cons_def)) { + $constraints[$constraint_name] = $cons_def; + } + } + foreach ($fields as $field_name) { + foreach ($constraints as $constraint_name => $constraint) { + if (!isset($dropped[$constraint_name]) && array_key_exists($field_name, $constraint['fields'])) { + $dropped[$constraint_name] = $constraint; + $result = $this->dropConstraint($table, $constraint_name); + if (MDB2::isError($result)) { + return $result; + } + } + } + // also drop implicit DEFAULT constraints + $default = $this->_getTableFieldDefaultConstraint($table, $field_name); + if (!MDB2::isError($default) && !empty($default)) { + $result = $this->dropConstraint($table, $default); + if (MDB2::isError($result)) { + return $result; + } + } + } + + return $dropped; + } + + // }}} + // {{{ _getTableFieldDefaultConstraint() + + /** + * Get the default constraint for a table field + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * + * @return mixed name of default constraint on success, a MDB2 error on failure + * @access private + */ + function _getTableFieldDefaultConstraint($table, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $field = $db->quote($field, 'text'); + $query = "SELECT OBJECT_NAME(syscolumns.cdefault) + FROM syscolumns + WHERE syscolumns.id = object_id('$table') + AND syscolumns.name = $field + AND syscolumns.cdefault <> 0"; + return $db->queryOne($query); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + + if (MDB2::isError($db)) { + return $db; + } + + $query = 'EXEC sp_tables @table_type = "\'TABLE\'"'; + $table_names = $db->queryCol($query, null, 2); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + 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->quote($table, 'text'); + $columns = $db->queryCol("SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = $table"); + if (MDB2::isError($columns)) { + return $columns; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $columns); + } + return $columns; + } + + // }}} + // {{{ 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 = 'INDEX_NAME'; + $pk_name = 'PK_NAME'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $pk_name = strtolower($pk_name); + } else { + $key_name = strtoupper($key_name); + $pk_name = strtoupper($pk_name); + } + } + $table = $db->quote($table, 'text'); + $query = "EXEC sp_statistics @table_name=$table"; + $indexes = $db->queryCol($query, 'text', $key_name); + if (MDB2::isError($indexes)) { + return $indexes; + } + $query = "EXEC sp_pkeys @table_name=$table"; + $pk_all = $db->queryCol($query, 'text', $pk_name); + $result = array(); + foreach ($indexes as $index) { + if (!in_array($index, $pk_all) && ($index = $this->_fixIndexName($index))) { + $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); + } + + // }}} + // {{{ 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('SELECT name FROM sys.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; + } + + $result = $db->queryCol('SELECT DISTINCT loginame FROM master..sysprocesses'); + if (MDB2::isError($result) || empty($result)) { + return $result; + } + foreach (array_keys($result) as $k) { + $result[$k] = trim($result[$k]); + } + return $result; + } + + // }}} + // {{{ 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 sysobjects + WHERE objectproperty(id, N'IsMSShipped') = 0 + AND (objectproperty(id, N'IsTableFunction') = 1 + OR objectproperty(id, N'IsScalarFunction') = 1)"; + /* + 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, otherwise, false which + * could be a db error if the db is not instantiated or could + * be the results of the error that occured during the + * querying of the sysobject module. + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT o.name + FROM sysobjects o + WHERE xtype = 'TR' + AND OBJECTPROPERTY(o.id, 'IsMSShipped') = 0"; + if (null !== $table) { + $query .= " AND object_name(parent_obj) = $table"; + } + + $result = $db->queryCol($query); + if (MDB2::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE && + $db->options['field_case'] == CASE_LOWER) + { + $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() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name + FROM sysobjects + WHERE xtype = 'V'"; + /* + SELECT * + FROM sysobjects + WHERE objectproperty(id, N'IsMSShipped') = 0 + AND objectproperty(id, N'IsView') = 1 + */ + + $result = $db->queryCol($query); + if (MDB2::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE && + $db->options['field_case'] == CASE_LOWER) + { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? + 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ 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 $table.$name"); + 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; + } + $table = $db->quote($table, 'text'); + + $query = "SELECT c.constraint_name + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c + WHERE c.constraint_catalog = DB_NAME() + AND c.table_name = $table"; + $constraints = $db->queryCol($query); + if (MDB2::isError($constraints)) { + return $constraints; + } + + $result = array(); + foreach ($constraints as $constraint) { + $constraint = $this->_fixIndexName($constraint); + if (!empty($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 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $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); + $query = "CREATE TABLE $sequence_name ($seqcol_name " . + "INT PRIMARY KEY CLUSTERED IDENTITY($start,1) NOT NULL)"; + + $res = $db->exec($query); + if (MDB2::isError($res)) { + return $res; + } + + $query = "SET IDENTITY_INSERT $sequence_name ON ". + "INSERT INTO $sequence_name ($seqcol_name) VALUES ($start)"; + $res = $db->exec($query); + + if (!MDB2::isError($res)) { + return MDB2_OK; + } + + $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() + + /** + * This function drops an 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 + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sysobjects WHERE xtype = 'U'"; + $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; + } + + // }}} +} + +// }}} +?> diff --git a/extlib/MDB2/Driver/Manager/mysqli.php b/extlib/MDB2/Driver/Manager/mysqli.php new file mode 100644 index 0000000000..7133287703 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/mysqli.php @@ -0,0 +1,1471 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQLi driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_mysqli 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; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Manager/oci8.php b/extlib/MDB2/Driver/Manager/oci8.php new file mode 100644 index 0000000000..736a8bf5d7 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/oci8.php @@ -0,0 +1,1355 @@ + | +// +----------------------------------------------------------------------+ + +// $Id$ + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 oci8 driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_oci8 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; + } + + $username = $db->options['database_name_prefix'].$name; + $password = $db->dsn['password'] ? $db->dsn['password'] : $name; + $tablespace = $db->options['default_tablespace'] + ? ' DEFAULT TABLESPACE '.$db->options['default_tablespace'] : ''; + + $query = 'CREATE USER '.$username.' IDENTIFIED BY '.$password.$tablespace; + $result = $db->standaloneQuery($query, null, true); + if (MDB2::isError($result)) { + return $result; + } + $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO '.$username; + $result = $db->standaloneQuery($query, null, true); + if (MDB2::isError($result)) { + $query = 'DROP USER '.$username.' CASCADE'; + $result2 = $db->standaloneQuery($query, null, true); + if (MDB2::isError($result2)) { + return $db->raiseError($result2, null, null, + 'could not setup the database user', __FUNCTION__); + } + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * IMPORTANT: the safe way to change the db charset is to do a full import/export! + * If - and only if - the new character set is a strict superset of the current + * character set, it is possible to use the ALTER DATABASE CHARACTER SET to + * expedite the change in the database character set. + * + * @param string $name name of the database that is intended to be changed + * @param array $options array with name, charset info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($name, $options = array()) + { + //disabled + //return parent::alterDatabase($name, $options); + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!empty($options['name'])) { + $query = 'ALTER DATABASE ' . $db->quoteIdentifier($name, true) + .' RENAME GLOBAL_NAME TO ' . $db->quoteIdentifier($options['name'], true); + $result = $db->standaloneQuery($query); + if (MDB2::isError($result)) { + return $result; + } + } + + if (!empty($options['charset'])) { + $queries = array(); + $queries[] = 'SHUTDOWN IMMEDIATE'; //or NORMAL + $queries[] = 'STARTUP MOUNT'; + $queries[] = 'ALTER SYSTEM ENABLE RESTRICTED SESSION'; + $queries[] = 'ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0'; + $queries[] = 'ALTER DATABASE OPEN'; + $queries[] = 'ALTER DATABASE CHARACTER SET ' . $options['charset']; + $queries[] = 'ALTER DATABASE NATIONAL CHARACTER SET ' . $options['charset']; + $queries[] = 'SHUTDOWN IMMEDIATE'; //or NORMAL + $queries[] = 'STARTUP'; + + foreach ($queries as $query) { + $result = $db->standaloneQuery($query); + if (MDB2::isError($result)) { + return $result; + } + } + } + + return MDB2_OK; + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param object $db database object that is extended by this class + * @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; + } + + $username = $db->options['database_name_prefix'].$name; + return $db->standaloneQuery('DROP USER '.$username.' CASCADE', null, true); + } + + + // }}} + // {{{ _makeAutoincrement() + + /** + * add an autoincrement sequence + trigger + * + * @param string $name name of the PK field + * @param string $table name of the table + * @param string $start start value for the sequence + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _makeAutoincrement($name, $table, $start = 1) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table_uppercase = strtoupper($table); + $index_name = $table_uppercase . '_AI_PK'; + $definition = array( + 'primary' => true, + 'fields' => array($name => true), + ); + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + $result = $this->createConstraint($table, $index_name, $definition); + $db->setOption('idxname_format', $idxname_format); + if (MDB2::isError($result)) { + return $db->raiseError($result, null, null, + 'primary key for autoincrement PK could not be created', __FUNCTION__); + } + + if (null === $start) { + $db->beginTransaction(); + $query = 'SELECT MAX(' . $db->quoteIdentifier($name, true) . ') FROM ' . $db->quoteIdentifier($table, true); + $start = $this->db->queryOne($query, 'integer'); + if (MDB2::isError($start)) { + return $start; + } + ++$start; + $result = $this->createSequence($table, $start); + $db->commit(); + } else { + $result = $this->createSequence($table, $start); + } + if (MDB2::isError($result)) { + return $db->raiseError($result, null, null, + 'sequence for autoincrement PK could not be created', __FUNCTION__); + } + $seq_name = $db->getSequenceName($table); + $trigger_name = $db->quoteIdentifier($table_uppercase . '_AI_PK', true); + $seq_name_quoted = $db->quoteIdentifier($seq_name, true); + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($name, true); + $trigger_sql = ' +CREATE TRIGGER '.$trigger_name.' + BEFORE INSERT + ON '.$table.' + FOR EACH ROW +DECLARE + last_Sequence NUMBER; + last_InsertID NUMBER; +BEGIN + SELECT '.$seq_name_quoted.'.NEXTVAL INTO :NEW.'.$name.' FROM DUAL; + IF (:NEW.'.$name.' IS NULL OR :NEW.'.$name.' = 0) THEN + SELECT '.$seq_name_quoted.'.NEXTVAL INTO :NEW.'.$name.' FROM DUAL; + ELSE + SELECT NVL(Last_Number, 0) INTO last_Sequence + FROM User_Sequences + WHERE UPPER(Sequence_Name) = UPPER(\''.$seq_name.'\'); + SELECT :NEW.'.$name.' INTO last_InsertID FROM DUAL; + WHILE (last_InsertID > last_Sequence) LOOP + SELECT '.$seq_name_quoted.'.NEXTVAL INTO last_Sequence FROM DUAL; + END LOOP; + END IF; +END; +'; + $result = $db->exec($trigger_sql); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ _dropAutoincrement() + + /** + * drop an existing autoincrement sequence + trigger + * + * @param string $table name of the table + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropAutoincrement($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = strtoupper($table); + $trigger_name = $table . '_AI_PK'; + $trigger_name_quoted = $db->quote($trigger_name, 'text'); + $query = 'SELECT trigger_name FROM user_triggers'; + $query.= ' WHERE trigger_name='.$trigger_name_quoted.' OR trigger_name='.strtoupper($trigger_name_quoted); + $trigger = $db->queryOne($query); + if (MDB2::isError($trigger)) { + return $trigger; + } + + if ($trigger) { + $trigger_name = $db->quoteIdentifier($table . '_AI_PK', true); + $trigger_sql = 'DROP TRIGGER ' . $trigger_name; + $result = $db->exec($trigger_sql); + if (MDB2::isError($result)) { + return $db->raiseError($result, null, null, + 'trigger for autoincrement PK could not be dropped', __FUNCTION__); + } + + $result = $this->dropSequence($table); + if (MDB2::isError($result)) { + return $db->raiseError($result, null, null, + 'sequence for autoincrement PK could not be dropped', __FUNCTION__); + } + + $index_name = $table . '_AI_PK'; + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + $result1 = $this->dropConstraint($table, $index_name); + $db->setOption('idxname_format', $idxname_format); + $result2 = $this->dropConstraint($table, $index_name); + if (MDB2::isError($result1) && MDB2::isError($result2)) { + return $db->raiseError($result1, null, null, + 'primary key for autoincrement PK could not be dropped', __FUNCTION__); + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'GLOBAL TEMPORARY'; + } + + // }}} + // {{{ _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['ondelete']) && (strtoupper($definition['ondelete']) != 'NO ACTION')) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + 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. + * + * Example + * array( + * + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $db->beginNestedTransaction(); + $result = parent::createTable($name, $fields, $options); + if (!MDB2::isError($result)) { + foreach ($fields as $field_name => $field) { + if (!empty($field['autoincrement'])) { + $result = $this->_makeAutoincrement($field_name, $name); + } + } + } + $db->completeNestedTransaction(); + return $result; + } + + // }}} + // {{{ 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; + } + $db->beginNestedTransaction(); + $result = $this->_dropAutoincrement($name); + if (!MDB2::isError($result)) { + $result = parent::dropTable($name); + if (MDB2::isError($result)) { + return $result; + } + } + $db->completeNestedTransaction(); + return $result; + } + + // }}} + // {{{ 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()) + { + // not needed in Oracle + 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 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + + if (!empty($changes['add']) && is_array($changes['add'])) { + $fields = array(); + foreach ($changes['add'] as $field_name => $field) { + $fields[] = $db->getDeclaration($field['type'], $field_name, $field); + } + $result = $db->exec("ALTER TABLE $name ADD (". implode(', ', $fields).')'); + if (MDB2::isError($result)) { + return $result; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + $fields = array(); + foreach ($changes['change'] as $field_name => $field) { + //fix error "column to be modified to NOT NULL is already NOT NULL" + if (!array_key_exists('notnull', $field)) { + unset($field['definition']['notnull']); + } + $fields[] = $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + $result = $db->exec("ALTER TABLE $name MODIFY (". implode(', ', $fields).')'); + if (MDB2::isError($result)) { + return $result; + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $query = "ALTER TABLE $name RENAME COLUMN $field_name TO ".$db->quoteIdentifier($field['name']); + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + $fields = array(); + foreach ($changes['remove'] as $field_name => $field) { + $fields[] = $db->quoteIdentifier($field_name, true); + } + $result = $db->exec("ALTER TABLE $name DROP COLUMN ". implode(', ', $fields)); + if (MDB2::isError($result)) { + return $result; + } + } + + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("ALTER TABLE $name RENAME TO ".$change_name); + if (MDB2::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _fetchCol() + + /** + * Utility method to fetch and format a column from a resultset + * + * @param resource $result + * @param boolean $fixname (used when listing indices or constraints) + * @return mixed array of names on success, a MDB2 error on failure + * @access private + */ + function _fetchCol($result, $fixname = false) + { + if (MDB2::isError($result)) { + return $result; + } + $col = $result->fetchCol(); + if (MDB2::isError($col)) { + return $col; + } + $result->free(); + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if ($fixname) { + foreach ($col as $k => $v) { + $col[$k] = $this->_fixIndexName($v); + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + && $db->options['field_case'] == CASE_LOWER + ) { + $col = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $col); + } + return $col; + } + + // }}} + // {{{ 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; + } + + if (!$db->options['emulate_database']) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'database listing is only supported if the "emulate_database" option is enabled', __FUNCTION__); + } + + if ($db->options['database_name_prefix']) { + $query = 'SELECT SUBSTR(username, '; + $query.= (strlen($db->options['database_name_prefix'])+1); + $query.= ") FROM sys.dba_users WHERE username LIKE '"; + $query.= $db->options['database_name_prefix']."%'"; + } else { + $query = 'SELECT username FROM sys.dba_users'; + } + $result = $db->standaloneQuery($query, array('text'), false); + return $this->_fetchCol($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; + } + + if ($db->options['emulate_database'] && $db->options['database_name_prefix']) { + $query = 'SELECT SUBSTR(username, '; + $query.= (strlen($db->options['database_name_prefix'])+1); + $query.= ") FROM sys.dba_users WHERE username NOT LIKE '"; + $query.= $db->options['database_name_prefix']."%'"; + } else { + $query = 'SELECT username FROM sys.dba_users'; + } + return $db->queryCol($query); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string owner, the current is default + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($owner = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT view_name + FROM sys.all_views + WHERE owner=? OR owner=?'; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @param string owner, the current is default + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions($owner = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = "SELECT name + FROM sys.all_source + WHERE line = 1 + AND type = 'FUNCTION' + AND (owner=? OR owner=?)"; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + return $this->_fetchCol($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; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = "SELECT trigger_name + FROM sys.all_triggers + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?)"; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the database + * + * @param string owner, the current is default + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($owner = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT table_name + FROM sys.all_tables + WHERE owner=? OR owner=?'; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + return $this->_fetchCol($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; + } + + list($owner, $table) = $this->splitTableSchema($table); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT column_name + FROM all_tab_columns + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?) + ORDER BY column_id'; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result); + } + + // }}} + // {{{ 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; + } + + list($owner, $table) = $this->splitTableSchema($table); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT i.index_name name + FROM all_indexes i + LEFT JOIN all_constraints c + ON c.index_name = i.index_name + AND c.owner = i.owner + AND c.table_name = i.table_name + WHERE (i.table_name=? OR i.table_name=?) + AND (i.owner=? OR i.owner=?) + AND c.index_name IS NULL + AND i.generated=' .$db->quote('N', 'text'); + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result, true); + } + + // }}} + // {{{ 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) + { + $result = parent::createConstraint($table, $name, $definition); + if (MDB2::isError($result)) { + return $result; + } + 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; + } + + //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; + } + } + + return parent::dropConstraint($table, $name, $primary); + } + + // }}} + // {{{ _createFKTriggers() + + /** + * Create triggers to enforce the FOREIGN KEY constraint on the table + * + * @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 = $db->quoteIdentifier($table, true); + foreach ($foreign_keys as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); + if ('RESTRICT' == $fkdef['onupdate'] || 'NO ACTION' == $fkdef['onupdate']) { + // already handled by default + continue; + } + + $trigger_name = substr(strtolower($fkname.'_pk_upd_trg'), 0, $db->options['max_identifiers_length']); + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + + //create the ON UPDATE trigger on the primary table + $restrict_action = ' IF (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $table .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$table + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $iloadModule('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.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';'; + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW ' + .' BEGIN '; + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_name, 'BEFORE UPDATE', 'update') . $cascade_action; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_name, 'BEFORE UPDATE', 'update') . $setnull_action; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_name, 'BEFORE UPDATE', 'update') . $setdefault_action; + } + $sql_update .= ' END;'; + $result = $db->exec($sql_update); + if (MDB2::isError($result)) { + if ($result->getCode() === MDB2_ERROR_ALREADY_EXISTS) { + return MDB2_OK; + } + return $result; + } + } + } + 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); + $trigger_name = substr(strtolower($fkname.'_pk_upd_trg'), 0, $db->options['max_identifiers_length']); + $pattern = '/^'.$trigger_name.'$/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; + } + + list($owner, $table) = $this->splitTableSchema($table); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT constraint_name + FROM all_constraints + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?)'; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result, true); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param object $db database object that is extended by this class + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $query = "CREATE SEQUENCE $sequence_name START WITH $start INCREMENT BY 1 NOCACHE"; + $query.= ($start < 1 ? " MINVALUE $start" : ''); + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param object $db database object that is extended by this class + * @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 SEQUENCE $sequence_name"); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string owner, the current is default + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($owner = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT sequence_name + FROM sys.all_sequences + WHERE (sequence_owner=? OR sequence_owner=?)'; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + if (MDB2::isError($result)) { + return $result; + } + $col = $result->fetchCol(); + if (MDB2::isError($col)) { + return $col; + } + $result->free(); + + foreach ($col as $k => $v) { + $col[$k] = $this->_fixSequenceName($v); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + && $db->options['field_case'] == CASE_LOWER + ) { + $col = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $col); + } + return $col; + } +} +?> diff --git a/extlib/MDB2/Driver/Manager/odbc.php b/extlib/MDB2/Driver/Manager/odbc.php new file mode 100644 index 0000000000..99bef43a1e --- /dev/null +++ b/extlib/MDB2/Driver/Manager/odbc.php @@ -0,0 +1,1163 @@ + | +// | David Coallier | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.109 2008/03/05 12:55:57 afz Exp $ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +// {{{ class MDB2_Driver_Manager_mssql + +/** + * MDB2 MSSQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + * @author David Coallier + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_odbc 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 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 ($db->options['database_device']) { + $query.= ' ON '.$db->options['database_device']; + $query.= $db->options['database_size'] ? '=' . + $db->options['database_size'] : ''; + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + 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 name, 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 = ''; + if (!empty($options['name'])) { + $query .= ' MODIFY NAME = ' .$db->quoteIdentifier($options['name'], true); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + if (!empty($query)) { + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true) . $query; + return $db->standaloneQuery($query, null, true); + } + return MDB2_OK; + } + + // }}} + // {{{ 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); + return $db->standaloneQuery("DROP DATABASE $name", null, true); + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * Override the parent method. + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return ''; + } + + // }}} + // {{{ _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['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. + * + * Example + * array( + * + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1, + * 'notnull' => 1, + * 'default' => 0, + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12, + * ), + * 'description' => array( + * 'type' => 'text', + * 'length' => 12, + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + if (!empty($options['temporary'])) { + $name = '#'.$name; + } + return parent::createTable($name, $fields, $options); + } + + // }}} + // {{{ 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] + * + * NB: you have to run the NSControl Create utility to enable VACUUM + * + * @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; + } + $timeout = isset($options['timeout']) ? (int)$options['timeout'] : 300; + + $query = 'NSControl Create'; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + + $result = $db->exec('EXEC NSVacuum '.$timeout); + 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. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $name_quoted = $db->quoteIdentifier($name, true); + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'remove': + case 'rename': + case 'add': + case 'change': + 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; + } + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + $result = $this->_dropConflictingIndices($name, array_keys($changes['remove'])); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + $result = $this->_dropConflictingConstraints($name, array_keys($changes['remove'])); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + + $query = ''; + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'COLUMN ' . $field_name; + } + + $result = $db->exec("ALTER TABLE $name_quoted DROP $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $result = $db->exec("sp_rename '$name_quoted.$field_name', '".$field['name']."', 'COLUMN'"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + $query = ''; + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } else { + $query.= 'ADD '; + } + $query.= $db->getDeclaration($field['type'], $field_name, $field); + } + + $result = $db->exec("ALTER TABLE $name_quoted $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + $dropped_indices = array(); + $dropped_constraints = array(); + + if (!empty($changes['change']) && is_array($changes['change'])) { + $dropped = $this->_dropConflictingIndices($name, array_keys($changes['change'])); + if (MDB2::isError($dropped)) { + $db->setOption('idxname_format', $idxname_format); + return $dropped; + } + $dropped_indices = array_merge($dropped_indices, $dropped); + $dropped = $this->_dropConflictingConstraints($name, array_keys($changes['change'])); + if (MDB2::isError($dropped)) { + $db->setOption('idxname_format', $idxname_format); + return $dropped; + } + $dropped_constraints = array_merge($dropped_constraints, $dropped); + + foreach ($changes['change'] as $field_name => $field) { + //MSSQL doesn't allow multiple ALTER COLUMNs in one query + $query = 'ALTER COLUMN '; + + //MSSQL doesn't allow changing the DEFAULT value of a field in altering mode + if (array_key_exists('default', $field['definition'])) { + unset($field['definition']['default']); + } + + $query .= $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + $result = $db->exec("ALTER TABLE $name_quoted $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + } + + // restore the dropped conflicting indices and constraints + foreach ($dropped_indices as $index_name => $index) { + $result = $this->createIndex($name, $index_name, $index); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + foreach ($dropped_constraints as $constraint_name => $constraint) { + $result = $this->createConstraint($name, $constraint_name, $constraint); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + $db->setOption('idxname_format', $idxname_format); + + if (!empty($changes['name'])) { + $new_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("sp_rename '$name_quoted', '$new_name'"); + if (MDB2::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _dropConflictingIndices() + + /** + * Drop the indices that prevent a successful ALTER TABLE action + * + * @param string $table table name + * @param array $fields array of names of the fields affected by the change + * + * @return array dropped indices definitions + */ + function _dropConflictingIndices($table, $fields) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $dropped = array(); + $index_names = $this->listTableIndexes($table); + if (MDB2::isError($index_names)) { + return $index_names; + } + $db->loadModule('Reverse'); + $indexes = array(); + foreach ($index_names as $index_name) { + $idx_def = $db->reverse->getTableIndexDefinition($table, $index_name); + if (!MDB2::isError($idx_def)) { + $indexes[$index_name] = $idx_def; + } + } + foreach ($fields as $field_name) { + foreach ($indexes as $index_name => $index) { + if (!isset($dropped[$index_name]) && array_key_exists($field_name, $index['fields'])) { + $dropped[$index_name] = $index; + $result = $this->dropIndex($table, $index_name); + if (MDB2::isError($result)) { + return $result; + } + } + } + } + + return $dropped; + } + + // }}} + // {{{ _dropConflictingConstraints() + + /** + * Drop the constraints that prevent a successful ALTER TABLE action + * + * @param string $table table name + * @param array $fields array of names of the fields affected by the change + * + * @return array dropped constraints definitions + */ + function _dropConflictingConstraints($table, $fields) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $dropped = array(); + $constraint_names = $this->listTableConstraints($table); + if (MDB2::isError($constraint_names)) { + return $constraint_names; + } + $db->loadModule('Reverse'); + $constraints = array(); + foreach ($constraint_names as $constraint_name) { + $cons_def = $db->reverse->getTableConstraintDefinition($table, $constraint_name); + if (!MDB2::isError($cons_def)) { + $constraints[$constraint_name] = $cons_def; + } + } + foreach ($fields as $field_name) { + foreach ($constraints as $constraint_name => $constraint) { + if (!isset($dropped[$constraint_name]) && array_key_exists($field_name, $constraint['fields'])) { + $dropped[$constraint_name] = $constraint; + $result = $this->dropConstraint($table, $constraint_name); + if (MDB2::isError($result)) { + return $result; + } + } + } + // also drop implicit DEFAULT constraints + $default = $this->_getTableFieldDefaultConstraint($table, $field_name); + if (!MDB2::isError($default) && !empty($default)) { + $result = $this->dropConstraint($table, $default); + if (MDB2::isError($result)) { + return $result; + } + } + } + + return $dropped; + } + + // }}} + // {{{ _getTableFieldDefaultConstraint() + + /** + * Get the default constraint for a table field + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * + * @return mixed name of default constraint on success, a MDB2 error on failure + * @access private + */ + function _getTableFieldDefaultConstraint($table, $field) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $field = $db->quote($field, 'text'); + $query = "SELECT OBJECT_NAME(syscolumns.cdefault) + FROM syscolumns + WHERE syscolumns.id = object_id('$table') + AND syscolumns.name = $field + AND syscolumns.cdefault <> 0"; + return $db->queryOne($query); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db =& $this->getDBInstance(); + + if (MDB2::isError($db)) { + return $db; + } + $res = odbc_tables($db->connection); + $result = array(); + while (odbc_fetch_row($res)){ + if(odbc_result($res,"TABLE_TYPE")=="TABLE") { + $table_name = odbc_result($res,"TABLE_NAME"); + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + } + + 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; + } + + $rs = odbc_columns($db->connection, "%", "%", $table); + $columns = array(); + while($data = odbc_fetch_array($rs)) { + $columns[] = $data[COLUMN_NAME]; + } + + /* + throw new Exception(); + + $table = $db->quoteIdentifier($table, true); + $columns = $db->queryCol("SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '$table'"); + if (MDB2::isError($columns)) { + return $columns; + } + */ + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $columns); + } + return $columns; + } + + // }}} + // {{{ 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 = 'INDEX_NAME'; + $pk_name = 'PK_NAME'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $pk_name = strtolower($pk_name); + } else { + $key_name = strtoupper($key_name); + $pk_name = strtoupper($pk_name); + } + } + + //$table = $db->quote($table, 'text'); + //$query = "EXEC sp_statistics @table_name=$table"; + $table = "V_PARTNERSTAMM"; + $res = odbc_statistics($db->connection,$db->dsn[username],$db->dsn[username],$table,0,0); + $indexes = array(); + while($data = odbc_fetch_array($res)) { + $indexes[] = $data[$key_name]; + } + + + $res = odbc_primarykeys($db->connection,$db->dsn[username],$db->dsn[username],$table); + + + $pk_all = array(); + while($data = odbc_fetch_array($res)) { + $pk_all[] = $data[$key_name]; + } + + $result = array(); + foreach ($indexes as $index) { + if (!in_array($index, $pk_all) && ($index = $this->_fixIndexName($index))) { + $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); + } + + // }}} + // {{{ 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('SELECT name FROM sys.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; + } + + $result = $db->queryCol('SELECT DISTINCT loginame FROM master..sysprocesses'); + if (MDB2::isError($result) || empty($result)) { + return $result; + } + foreach (array_keys($result) as $k) { + $result[$k] = trim($result[$k]); + } + return $result; + } + + // }}} + // {{{ 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 sysobjects + WHERE objectproperty(id, N'IsMSShipped') = 0 + AND (objectproperty(id, N'IsTableFunction') = 1 + OR objectproperty(id, N'IsScalarFunction') = 1)"; + /* + 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, otherwise, false which + * could be a db error if the db is not instantiated or could + * be the results of the error that occured during the + * querying of the sysobject module. + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT o.name + FROM sysobjects o + WHERE xtype = 'TR' + AND OBJECTPROPERTY(o.id, 'IsMSShipped') = 0"; + if (!is_null($table)) { + $query .= " AND object_name(parent_obj) = $table"; + } + + $result = $db->queryCol($query); + if (MDB2::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE && + $db->options['field_case'] == CASE_LOWER) + { + $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() + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $res = odbc_tables($db->connection); + $result = array(); + while (odbc_fetch_row($res)){ + if(odbc_result($res,"TABLE_TYPE")=="VIEW") { + $table_name = odbc_result($res,"TABLE_NAME"); + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE && + $db->options['field_case'] == CASE_LOWER) + { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? + 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ 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 $table.$name"); + 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) + { + return array(); + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $table = $db->quoteIdentifier($table, true); + + $query = "SELECT c.constraint_name + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c + WHERE c.constraint_catalog = DB_NAME() + AND c.table_name = '$table'"; + $constraints = $db->queryCol($query); + if (MDB2::isError($constraints)) { + return $constraints; + } + + $result = array(); + foreach ($constraints as $constraint) { + $constraint = $this->_fixIndexName($constraint); + if (!empty($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 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $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); + $query = "CREATE TABLE $sequence_name ($seqcol_name " . + "INT PRIMARY KEY CLUSTERED IDENTITY($start,1) NOT NULL)"; + + $res = $db->exec($query); + if (MDB2::isError($res)) { + return $res; + } + + $query = "SET IDENTITY_INSERT $sequence_name ON ". + "INSERT INTO $sequence_name ($seqcol_name) VALUES ($start)"; + $res = $db->exec($query); + + if (!MDB2::isError($res)) { + return MDB2_OK; + } + + $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() + + /** + * This function drops an 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 + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + return array(); + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sysobjects WHERE xtype = 'U'"; + $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; + } + + // }}} +} + +// }}} +?> diff --git a/extlib/MDB2/Driver/Manager/pgsql.php b/extlib/MDB2/Driver/Manager/pgsql.php new file mode 100644 index 0000000000..2d543556a6 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/pgsql.php @@ -0,0 +1,978 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_Manager_pgsql 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 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 .= ' WITH ENCODING ' . $db->quote($options['charset'], '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 name, owner 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 = ''; + if (!empty($options['name'])) { + $query .= ' RENAME TO ' . $options['name']; + } + if (!empty($options['owner'])) { + $query .= ' OWNER TO ' . $options['owner']; + } + + if (empty($query)) { + return MDB2_OK; + } + + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true) . $query; + 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']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + // }}} + // {{{ 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; + } + $query = 'VACUUM'; + + if (!empty($options['full'])) { + $query .= ' FULL'; + } + if (!empty($options['freeze'])) { + $query .= ' FREEZE'; + } + if (!empty($options['analyze'])) { + $query .= ' ANALYZE'; + } + + if (!empty($table)) { + $query .= ' '.$db->quoteIdentifier($table, true); + } + $result = $db->exec($query); + 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 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'\" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $query = 'DROP ' . $field_name; + $result = $db->exec("ALTER TABLE $name $query"); + if (MDB2::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $result = $db->exec("ALTER TABLE $name RENAME COLUMN $field_name TO ".$db->quoteIdentifier($field['name'], true)); + if (MDB2::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + $query = 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + $result = $db->exec("ALTER TABLE $name $query"); + if (MDB2::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + if (!empty($field['definition']['type'])) { + $server_info = $db->getServerVersion(); + if (MDB2::isError($server_info)) { + return $server_info; + } + if (is_array($server_info) && $server_info['major'] < 8) { + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'changing column type for "'.$change_name.'\" requires PostgreSQL 8.0 or above', __FUNCTION__); + } + $db->loadModule('Datatype', null, true); + $type = $db->datatype->getTypeDeclaration($field['definition']); + $query = "ALTER $field_name TYPE $type USING CAST($field_name AS $type)"; + $result = $db->exec("ALTER TABLE $name $query"); + if (MDB2::isError($result)) { + return $result; + } + } + if (array_key_exists('default', $field['definition'])) { + $query = "ALTER $field_name SET DEFAULT ".$db->quote($field['definition']['default'], $field['definition']['type']); + $result = $db->exec("ALTER TABLE $name $query"); + if (MDB2::isError($result)) { + return $result; + } + } + if (array_key_exists('notnull', $field['definition'])) { + $query = "ALTER $field_name ".($field['definition']['notnull'] ? 'SET' : 'DROP').' NOT NULL'; + $result = $db->exec("ALTER TABLE $name $query"); + if (MDB2::isError($result)) { + return $result; + } + } + } + } + + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("ALTER TABLE $name RENAME TO ".$change_name); + 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; + } + + $query = 'SELECT datname FROM pg_database'; + $result2 = $db->standaloneQuery($query, array('text'), false); + if (!MDB2::isResultCommon($result2)) { + return $result2; + } + + $result = $result2->fetchCol(); + $result2->free(); + 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; + } + + $query = 'SELECT usename FROM pg_user'; + $result2 = $db->standaloneQuery($query, array('text'), false); + if (!MDB2::isResultCommon($result2)) { + return $result2; + } + + $result = $result2->fetchCol(); + $result2->free(); + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT viewname + FROM pg_views + WHERE schemaname NOT IN ('pg_catalog', 'information_schema') + AND viewname !~ '^pg_'"; + $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; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = 'SELECT viewname FROM pg_views NATURAL JOIN pg_tables'; + $query.= ' WHERE tablename ='.$db->quote($table, 'text'); + $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; + } + + // }}} + // {{{ 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 + proname + FROM + pg_proc pr, + pg_type tp + WHERE + tp.oid = pr.prorettype + AND pr.proisagg = FALSE + AND tp.typname <> 'trigger' + AND pr.pronamespace IN + (SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; + $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 = 'SELECT trg.tgname AS trigger_name + FROM pg_trigger trg, + pg_class tbl + WHERE trg.tgrelid = tbl.oid'; + if (null !== $table) { + $table = $db->quote(strtoupper($table), 'text'); + $query .= " AND UPPER(tbl.relname) = $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 + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + // gratuitously stolen from PEAR DB _getSpecialQuery in pgsql.php + $query = 'SELECT c.relname AS "Name"' + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; + $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; + } + + list($schema, $table) = $this->splitTableSchema($table); + + $table = $db->quoteIdentifier($table, true); + if (!empty($schema)) { + $table = $db->quoteIdentifier($schema, true) . '.' .$table; + } + $db->setLimit(1); + $result2 = $db->query("SELECT * FROM $table"); + if (MDB2::isError($result2)) { + return $result2; + } + $result = $result2->getColumnNames(); + $result2->free(); + if (MDB2::isError($result)) { + return $result; + } + return array_flip($result); + } + + // }}} + // {{{ 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; + } + + list($schema, $table) = $this->splitTableSchema($table); + + $table = $db->quote($table, 'text'); + $subquery = "SELECT indexrelid + FROM pg_index + LEFT JOIN pg_class ON pg_class.oid = pg_index.indrelid + LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid + WHERE pg_class.relname = $table + AND indisunique != 't' + AND indisprimary != 't'"; + if (!empty($schema)) { + $subquery .= ' AND pg_namespace.nspname = '.$db->quote($schema, 'text'); + } + $query = "SELECT relname FROM pg_class WHERE oid IN ($subquery)"; + $indexes = $db->queryCol($query, 'text'); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index) { + $index = $this->_fixIndexName($index); + if (!empty($index)) { + $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); + } + + // }}} + // {{{ 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; + } + + // is it an UNIQUE index? + $query = 'SELECT relname + FROM pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = '.$db->quote($table, 'text').' + AND pg_class.oid = pg_index.indrelid + AND indisunique = \'t\') + EXCEPT + SELECT conname + FROM pg_constraint, pg_class + WHERE pg_constraint.conrelid = pg_class.oid + AND relname = '. $db->quote($table, 'text'); + $unique = $db->queryCol($query, 'text'); + if (MDB2::isError($unique) || empty($unique)) { + // not an UNIQUE index, maybe a CONSTRAINT + return parent::dropConstraint($table, $name, $primary); + } + + if (in_array($name, $unique)) { + $result = $db->exec('DROP INDEX '.$db->quoteIdentifier($name, true)); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + $idxname = $db->getIndexName($name); + if (in_array($idxname, $unique)) { + $result = $db->exec('DROP INDEX '.$db->quoteIdentifier($idxname, true)); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $name . ' is not an existing constraint for table ' . $table, __FUNCTION__); + } + + // }}} + // {{{ 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; + } + + list($schema, $table) = $this->splitTableSchema($table); + + $table = $db->quote($table, 'text'); + $query = 'SELECT conname + FROM pg_constraint + LEFT JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid + WHERE relname = ' .$table; + if (!empty($schema)) { + $query .= ' AND pg_namespace.nspname = ' . $db->quote($schema, 'text'); + } + $query .= ' + UNION DISTINCT + SELECT relname + FROM pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index + LEFT JOIN pg_class ON pg_class.oid = pg_index.indrelid + LEFT JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid + WHERE pg_class.relname = '.$table.' + AND indisunique = \'t\''; + if (!empty($schema)) { + $query .= ' AND pg_namespace.nspname = ' . $db->quote($schema, 'text'); + } + $query .= ')'; + $constraints = $db->queryCol($query); + if (MDB2::isError($constraints)) { + return $constraints; + } + + $result = array(); + foreach ($constraints as $constraint) { + $constraint = $this->_fixIndexName($constraint); + if (!empty($constraint)) { + $result[$constraint] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + && $db->options['field_case'] == CASE_LOWER + ) { + $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 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $result = $db->exec("CREATE SEQUENCE $sequence_name INCREMENT 1". + ($start < 1 ? " MINVALUE $start" : '')." START $start"); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ 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 SEQUENCE $sequence_name"); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT relname FROM pg_class WHERE relkind = 'S' AND relnamespace IN"; + $query.= "(SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema')"; + $table_names = $db->queryCol($query); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + $result[] = $this->_fixSequenceName($table_name); + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } +} +?> diff --git a/extlib/MDB2/Driver/Manager/sqlite.php b/extlib/MDB2/Driver/Manager/sqlite.php new file mode 100644 index 0000000000..2d042a241d --- /dev/null +++ b/extlib/MDB2/Driver/Manager/sqlite.php @@ -0,0 +1,1390 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 SQLite driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_sqlite 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 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; + } + + $database_file = $db->_getDatabaseFile($name); + if (file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_ALREADY_EXISTS, null, null, + 'database already exists', __FUNCTION__); + } + $php_errormsg = ''; + $handle = @sqlite_open($database_file, $db->dsn['mode'], $php_errormsg); + if (!$handle) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not create the database file'), __FUNCTION__); + } + if (!empty($options['charset'])) { + $query = 'PRAGMA encoding = ' . $db->quote($options['charset'], 'text'); + @sqlite_query($query, $handle); + } + @sqlite_close($handle); + return MDB2_OK; + } + + // }}} + // {{{ 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; + } + + $database_file = $db->_getDatabaseFile($name); + if (!@file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + 'database does not exist', __FUNCTION__); + } + $result = @unlink($database_file); + if (!$result) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not remove the database file'), __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ _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']) && (strtoupper($definition['onupdate']) != 'NO ACTION')) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete']) && (strtoupper($definition['ondelete']) != 'NO ACTION')) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (MDB2::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + if (!empty($options['foreign_keys'])) { + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + $query_fields.= ', CONSTRAINT '.$fkname.' FOREIGN KEY ('.implode(', ', array_keys($fkdef['fields'])).')'; + $query_fields.= ' REFERENCES '.$fkdef['references']['table'].' ('.implode(', ', array_keys($fkdef['references']['fields'])).')'; + $query_fields.= $this->_getAdvancedFKOptions($fkdef); + } + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ 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 + * @param array $options An associative array of table options + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $result = parent::createTable($name, $fields, $options); + if (MDB2::isError($result)) { + return $result; + } + // create triggers to enforce FOREIGN KEY constraints + if (!empty($options['foreign_keys'])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + foreach ($options['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', + ); + + //create the [insert|update] triggers on the FK table + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + $query = 'CREATE TRIGGER %s BEFORE %s ON '.$name + .' FOR EACH ROW BEGIN' + .' SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($referenced_fields as $field) { + $aliased_fields[] = $fkdef['references']['table'] .'.'.$field .' AS '.$field; + } + $query .= implode(',', $aliased_fields) + .' FROM '.$fkdef['references']['table'] + .' WHERE '; + $conditions = array(); + for ($i=0; $iexec(sprintf($query, $trigger_names['insert'], 'INSERT', 'insert')); + if (MDB2::isError($result)) { + return $result; + } + + $result = $db->exec(sprintf($query, $trigger_names['update'], 'UPDATE', 'update')); + if (MDB2::isError($result)) { + return $result; + } + + //create the ON [UPDATE|DELETE] triggers on the primary table + $restrict_action = 'SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $name .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$name + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $i OLD.'.$referenced_fields[$i]; + } + $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' + .' AND (' .implode(' OR ', $conditions2) .')'; + + $cascade_action_update = 'UPDATE '.$name.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions); + $cascade_action_delete = 'DELETE FROM '.$name.' WHERE '.implode(' AND ', $conditions); + $setnull_action = 'UPDATE '.$name.' 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($name, $field); + if (MDB2::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$name.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions); + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW BEGIN '; + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . $cascade_action_update. '; END;'; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . '; END;'; + } elseif ('RESTRICT' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . '; END;'; + } + if ('CASCADE' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . $cascade_action_delete. '; END;'; + } elseif ('SET NULL' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . '; END;'; + } elseif ('RESTRICT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . '; END;'; + } + + if (MDB2::isError($result)) { + return $result; + } + $result = $db->exec($sql_delete); + if (MDB2::isError($result)) { + return $result; + } + $result = $db->exec($sql_update); + if (MDB2::isError($result)) { + return $result; + } + } + } + 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; + } + } + } + } + + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("DROP 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; + } + + $query = 'VACUUM'; + if (!empty($table)) { + $query .= ' '.$db->quoteIdentifier($table, true); + } + $result = $db->exec($query); + 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, $options = array()) + { + $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 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $db->loadModule('Reverse', null, true); + + // actually sqlite 2.x supports no ALTER TABLE at all .. so we emulate it + $fields = $db->manager->listTableFields($name); + if (MDB2::isError($fields)) { + return $fields; + } + + $fields = array_flip($fields); + foreach ($fields as $field => $value) { + $definition = $db->reverse->getTableFieldDefinition($name, $field); + if (MDB2::isError($definition)) { + return $definition; + } + $fields[$field] = $definition[0]; + } + + $indexes = $db->manager->listTableIndexes($name); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $indexes = array_flip($indexes); + foreach ($indexes as $index => $value) { + $definition = $db->reverse->getTableIndexDefinition($name, $index); + if (MDB2::isError($definition)) { + return $definition; + } + $indexes[$index] = $definition; + } + + $constraints = $db->manager->listTableConstraints($name); + if (MDB2::isError($constraints)) { + return $constraints; + } + + if (!array_key_exists('foreign_keys', $options)) { + $options['foreign_keys'] = array(); + } + $constraints = array_flip($constraints); + foreach ($constraints as $constraint => $value) { + if (!empty($definition['primary'])) { + if (!array_key_exists('primary', $options)) { + $options['primary'] = $definition['fields']; + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } + } else { + $c_definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (MDB2::isError($c_definition)) { + return $c_definition; + } + if (!empty($c_definition['foreign'])) { + if (!array_key_exists($constraint, $options['foreign_keys'])) { + $options['foreign_keys'][$constraint] = $c_definition; + } + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } else { + $constraints[$constraint] = $c_definition; + } + } + } + + $name_new = $name; + $create_order = $select_fields = array_keys($fields); + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field; + $create_order[] = $field_name; + } + break; + case 'remove': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $select_fields = array_diff($select_fields, array($field_name)); + $create_order = array_diff($create_order, array($field_name)); + } + break; + case 'change': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field['definition']; + } + break; + case 'name': + $name_new = $change; + break; + case 'rename': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $fields[$field['name']] = $field['definition']; + $create_order[array_search($field_name, $create_order)] = $field['name']; + } + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + $data = null; + if (!empty($select_fields)) { + $query = 'SELECT '.implode(', ', $select_fields).' FROM '.$db->quoteIdentifier($name, true); + $data = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + } + + $result = $this->dropTable($name); + if (MDB2::isError($result)) { + return $result; + } + + $result = $this->createTable($name_new, $fields, $options); + if (MDB2::isError($result)) { + return $result; + } + + foreach ($indexes as $index => $definition) { + $this->createIndex($name_new, $index, $definition); + } + + foreach ($constraints as $constraint => $definition) { + $this->createConstraint($name_new, $constraint, $definition); + } + + if (!empty($select_fields) && !empty($data)) { + $query = 'INSERT INTO '.$db->quoteIdentifier($name_new, true); + $query.= '('.implode(', ', array_slice(array_keys($fields), 0, count($select_fields))).')'; + $query.=' VALUES (?'.str_repeat(', ?', (count($select_fields) - 1)).')'; + $stmt = $db->prepare($query, null, MDB2_PREPARE_MANIP); + if (MDB2::isError($stmt)) { + return $stmt; + } + foreach ($data as $row) { + $result = $stmt->execute($row); + 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $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; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $views = $db->queryAll($query, array('text', 'text'), MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($views)) { + return $views; + } + $result = array(); + foreach ($views as $row) { + if (preg_match("/^create view .* \bfrom\b\s+\b{$table}\b /i", $row['sql'])) { + if (!empty($row['name'])) { + $result[$row['name']] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $table_names = $db->queryCol($query); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + 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; + } + + $result = $db->loadModule('Reverse', null, true); + if (MDB2::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (MDB2::isError($sql)) { + return $sql; + } + $columns = $db->reverse->_getTableColumns($sql); + $fields = array(); + foreach ($columns as $column) { + 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']); + } + $fields[] = $column['name']; + } + return $fields; + } + + // }}} + // {{{ 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 = "SELECT name FROM sqlite_master WHERE type='trigger' AND sql NOT NULL"; + if (null !== $table) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(tbl_name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= ' AND tbl_name='.$db->quote($table, 'text'); + } + } + $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; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function support() to determine whether the DBMS driver can manage indexes. + + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (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_name => $field) { + $field_string = $db->quoteIdentifier($field_name, true); + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $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; + } + + $name = $db->getIndexName($name); + $result = $db->exec("DROP INDEX $name"); + 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; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $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; + } + + if (!empty($definition['primary'])) { + return $db->manager->alterTable($table, array(), false, array('primary' => $definition['fields'])); + } + + if (!empty($definition['foreign'])) { + return $db->manager->alterTable($table, array(), false, array('foreign_keys' => array($name => $definition))); + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE UNIQUE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field_name => $field) { + $field_string = $field_name; + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $query .= ' ('.implode(', ', $fields) . ')'; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + 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) + { + if ($primary || $name == 'PRIMARY') { + return $this->alterTable($table, array(), false, array('primary' => null)); + } + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + //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 + return $this->alterTable($table, array(), false, array('foreign_keys' => array($name => null))); + } + + $name = $db->getIndexName($name); + $result = $db->exec("DROP INDEX $name"); + if (MDB2::isError($result)) { + return $result; + } + 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; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create unique index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $result[$index] = true; + } + } + } + + // also search in table definition for PRIMARY KEYs... + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.strtolower($table); + } else { + $query.= "name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $table_def = $db->queryOne($query, 'text'); + if (MDB2::isError($table_def)) { + return $table_def; + } + if (preg_match("/\bPRIMARY\s+KEY\b/i", $table_def, $tmp)) { + $result['primary'] = true; + } + + // ...and for FOREIGN KEYs + if (preg_match_all("/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN\s+KEY/imsx", $table_def, $tmp)) { + foreach ($tmp[1] as $fk) { + $result[$fk] = 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 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $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); + $query = "CREATE TABLE $sequence_name ($seqcol_name INTEGER PRIMARY KEY DEFAULT 0 NOT NULL)"; + $res = $db->exec($query); + if (MDB2::isError($res)) { + return $res; + } + if ($start == 1) { + return MDB2_OK; + } + $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + 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 + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $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; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Manager/sqlite3.php b/extlib/MDB2/Driver/Manager/sqlite3.php new file mode 100644 index 0000000000..d345f65798 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/sqlite3.php @@ -0,0 +1,1390 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 SQLite driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_sqlite3 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 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; + } + + $database_file = $db->_getDatabaseFile($name); + if (file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_ALREADY_EXISTS, null, null, + 'database already exists', __FUNCTION__); + } + $php_errormsg = ''; + $handle = new SQLite3($database_file); + if (!$handle) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not create the database file'), __FUNCTION__); + } + if (!empty($options['charset'])) { + $query = 'PRAGMA encoding = ' . $db->quote($options['charset'], 'text'); + @$handle->query($query); + } + @$handle->close(); + return MDB2_OK; + } + + // }}} + // {{{ 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; + } + + $database_file = $db->_getDatabaseFile($name); + if (!@file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + 'database does not exist', __FUNCTION__); + } + $result = @unlink($database_file); + if (!$result) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not remove the database file'), __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ _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']) && (strtoupper($definition['onupdate']) != 'NO ACTION')) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete']) && (strtoupper($definition['ondelete']) != 'NO ACTION')) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (MDB2::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + if (!empty($options['foreign_keys'])) { + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + $query_fields.= ', CONSTRAINT '.$fkname.' FOREIGN KEY ('.implode(', ', array_keys($fkdef['fields'])).')'; + $query_fields.= ' REFERENCES '.$fkdef['references']['table'].' ('.implode(', ', array_keys($fkdef['references']['fields'])).')'; + $query_fields.= $this->_getAdvancedFKOptions($fkdef); + } + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ 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 + * @param array $options An associative array of table options + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $result = parent::createTable($name, $fields, $options); + if (MDB2::isError($result)) { + return $result; + } + // create triggers to enforce FOREIGN KEY constraints + if (!empty($options['foreign_keys'])) { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + foreach ($options['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', + ); + + //create the [insert|update] triggers on the FK table + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + $query = 'CREATE TRIGGER %s BEFORE %s ON '.$name + .' FOR EACH ROW BEGIN' + .' SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($referenced_fields as $field) { + $aliased_fields[] = $fkdef['references']['table'] .'.'.$field .' AS '.$field; + } + $query .= implode(',', $aliased_fields) + .' FROM '.$fkdef['references']['table'] + .' WHERE '; + $conditions = array(); + for ($i=0; $iexec(sprintf($query, $trigger_names['insert'], 'INSERT', 'insert')); + if (MDB2::isError($result)) { + return $result; + } + + $result = $db->exec(sprintf($query, $trigger_names['update'], 'UPDATE', 'update')); + if (MDB2::isError($result)) { + return $result; + } + + //create the ON [UPDATE|DELETE] triggers on the primary table + $restrict_action = 'SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $name .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$name + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $i OLD.'.$referenced_fields[$i]; + } + $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' + .' AND (' .implode(' OR ', $conditions2) .')'; + + $cascade_action_update = 'UPDATE '.$name.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions); + $cascade_action_delete = 'DELETE FROM '.$name.' WHERE '.implode(' AND ', $conditions); + $setnull_action = 'UPDATE '.$name.' 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($name, $field); + if (MDB2::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$name.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions); + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW BEGIN '; + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . $cascade_action_update. '; END;'; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . '; END;'; + } elseif ('RESTRICT' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . '; END;'; + } + if ('CASCADE' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . $cascade_action_delete. '; END;'; + } elseif ('SET NULL' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . '; END;'; + } elseif ('RESTRICT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . '; END;'; + } + + if (MDB2::isError($result)) { + return $result; + } + $result = $db->exec($sql_delete); + if (MDB2::isError($result)) { + return $result; + } + $result = $db->exec($sql_update); + if (MDB2::isError($result)) { + return $result; + } + } + } + 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; + } + } + } + } + + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("DROP 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; + } + + $query = 'VACUUM'; // applies to entire DB + //if (!empty($table)) { + // $query .= ' '.$db->quoteIdentifier($table, true); + //} + $result = $db->exec($query); + 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, $options = array()) + { + $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 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $db->loadModule('Reverse', null, true); + + // actually sqlite 2.x supports no ALTER TABLE at all .. so we emulate it + $fields = $db->manager->listTableFields($name); + if (MDB2::isError($fields)) { + return $fields; + } + + $fields = array_flip($fields); + foreach ($fields as $field => $value) { + $definition = $db->reverse->getTableFieldDefinition($name, $field); + if (MDB2::isError($definition)) { + return $definition; + } + $fields[$field] = $definition[0]; + } + + $indexes = $db->manager->listTableIndexes($name); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $indexes = array_flip($indexes); + foreach ($indexes as $index => $value) { + $definition = $db->reverse->getTableIndexDefinition($name, $index); + if (MDB2::isError($definition)) { + return $definition; + } + $indexes[$index] = $definition; + } + + $constraints = $db->manager->listTableConstraints($name); + if (MDB2::isError($constraints)) { + return $constraints; + } + + if (!array_key_exists('foreign_keys', $options)) { + $options['foreign_keys'] = array(); + } + $constraints = array_flip($constraints); + foreach ($constraints as $constraint => $value) { + if (!empty($definition['primary'])) { + if (!array_key_exists('primary', $options)) { + $options['primary'] = $definition['fields']; + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } + } else { + $c_definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (MDB2::isError($c_definition)) { + return $c_definition; + } + if (!empty($c_definition['foreign'])) { + if (!array_key_exists($constraint, $options['foreign_keys'])) { + $options['foreign_keys'][$constraint] = $c_definition; + } + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } else { + $constraints[$constraint] = $c_definition; + } + } + } + + $name_new = $name; + $create_order = $select_fields = array_keys($fields); + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field; + $create_order[] = $field_name; + } + break; + case 'remove': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $select_fields = array_diff($select_fields, array($field_name)); + $create_order = array_diff($create_order, array($field_name)); + } + break; + case 'change': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field['definition']; + } + break; + case 'name': + $name_new = $change; + break; + case 'rename': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $fields[$field['name']] = $field['definition']; + $create_order[array_search($field_name, $create_order)] = $field['name']; + } + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + $data = null; + if (!empty($select_fields)) { + $query = 'SELECT '.implode(', ', $select_fields).' FROM '.$db->quoteIdentifier($name, true); + $data = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + } + + $result = $this->dropTable($name); + if (MDB2::isError($result)) { + return $result; + } + + $result = $this->createTable($name_new, $fields, $options); + if (MDB2::isError($result)) { + return $result; + } + + foreach ($indexes as $index => $definition) { + $this->createIndex($name_new, $index, $definition); + } + + foreach ($constraints as $constraint => $definition) { + $this->createConstraint($name_new, $constraint, $definition); + } + + if (!empty($select_fields) && !empty($data)) { + $query = 'INSERT INTO '.$db->quoteIdentifier($name_new, true); + $query.= '('.implode(', ', array_slice(array_keys($fields), 0, count($select_fields))).')'; + $query.=' VALUES (?'.str_repeat(', ?', (count($select_fields) - 1)).')'; + $stmt = $db->prepare($query, null, MDB2_PREPARE_MANIP); + if (MDB2::isError($stmt)) { + return $stmt; + } + foreach ($data as $row) { + $result = $stmt->execute($row); + 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @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 = "SELECT name FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $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; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $views = $db->queryAll($query, array('text', 'text'), MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($views)) { + return $views; + } + $result = array(); + foreach ($views as $row) { + if (preg_match("/^create view .* \bfrom\b\s+\b{$table}\b /i", $row['sql'])) { + if (!empty($row['name'])) { + $result[$row['name']] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @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 = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $table_names = $db->queryCol($query); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + 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; + } + + $result = $db->loadModule('Reverse', null, true); + if (MDB2::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (MDB2::isError($sql)) { + return $sql; + } + $columns = $db->reverse->_getTableColumns($sql); + $fields = array(); + foreach ($columns as $column) { + 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']); + } + $fields[] = $column['name']; + } + return $fields; + } + + // }}} + // {{{ 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 = "SELECT name FROM sqlite_master WHERE type='trigger' AND sql NOT NULL"; + if (null !== $table) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(tbl_name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= ' AND tbl_name='.$db->quote($table, 'text'); + } + } + $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; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function support() to determine whether the DBMS driver can manage indexes. + + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db = $this->getDBInstance(); + if (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_name => $field) { + $field_string = $db->quoteIdentifier($field_name, true); + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $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; + } + + $name = $db->getIndexName($name); + $result = $db->exec("DROP INDEX $name"); + 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; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $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; + } + + if (!empty($definition['primary'])) { + return $db->manager->alterTable($table, array(), false, array('primary' => $definition['fields'])); + } + + if (!empty($definition['foreign'])) { + return $db->manager->alterTable($table, array(), false, array('foreign_keys' => array($name => $definition))); + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE UNIQUE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field_name => $field) { + $field_string = $field_name; + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $query .= ' ('.implode(', ', $fields) . ')'; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + 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) + { + if ($primary || $name == 'PRIMARY') { + return $this->alterTable($table, array(), false, array('primary' => null)); + } + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + //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 + return $this->alterTable($table, array(), false, array('foreign_keys' => array($name => null))); + } + + $name = $db->getIndexName($name); + $result = $db->exec("DROP INDEX $name"); + if (MDB2::isError($result)) { + return $result; + } + 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; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (MDB2::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create unique index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $result[$index] = true; + } + } + } + + // also search in table definition for PRIMARY KEYs... + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.strtolower($table); + } else { + $query.= "name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $table_def = $db->queryOne($query, 'text'); + if (MDB2::isError($table_def)) { + return $table_def; + } + if (preg_match("/\bPRIMARY\s+KEY\b/i", $table_def, $tmp)) { + $result['primary'] = true; + } + + // ...and for FOREIGN KEYs + if (preg_match_all("/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN\s+KEY/imsx", $table_def, $tmp)) { + foreach ($tmp[1] as $fk) { + $result[$fk] = 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 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $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); + $query = "CREATE TABLE $sequence_name ($seqcol_name INTEGER PRIMARY KEY DEFAULT 0 NOT NULL)"; + $res = $db->exec($query); + if (MDB2::isError($res)) { + return $res; + } + if ($start == 1) { + return MDB2_OK; + } + $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + 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 + * + * @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 = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $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; + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Manager/sqlsrv.php b/extlib/MDB2/Driver/Manager/sqlsrv.php new file mode 100644 index 0000000000..b326990089 --- /dev/null +++ b/extlib/MDB2/Driver/Manager/sqlsrv.php @@ -0,0 +1,1416 @@ + | +// | David Coallier | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ + +require_once 'MDB2/Driver/Manager/Common.php'; + +// {{{ class MDB2_Driver_Manager_sqlsrv + +/** + * MDB2 MSSQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + * @author David Coallier + * @author Lorenzo Alberton + */ +class MDB2_Driver_Manager_sqlsrv 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 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 ($db->options['database_device']) { + $query.= ' ON '.$db->options['database_device']; + $query.= $db->options['database_size'] ? '=' . + $db->options['database_size'] : ''; + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + 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 name, 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 = ''; + if (!empty($options['name'])) { + $query .= ' MODIFY NAME = ' .$db->quoteIdentifier($options['name'], true); + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + if (!empty($query)) { + $query = 'ALTER DATABASE '. $db->quoteIdentifier($name, true) . $query; + return $db->standaloneQuery($query, null, true); + } + return MDB2_OK; + } + + // }}} + // {{{ 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); + return $db->standaloneQuery("DROP DATABASE $name", null, true); + } + + // }}} + // {{{ 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; + } + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("IF EXISTS (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='$name') DROP TABLE $name"); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * Override the parent method. + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return ''; + } + + // }}} + // {{{ _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['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. + * + * Example + * array( + * + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1, + * 'notnull' => 1, + * 'default' => 0, + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12, + * ), + * 'description' => array( + * 'type' => 'text', + * 'length' => 12, + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + /*if (!empty($options['temporary'])) { + $name = '#'.$name;//would make subsequent calls fail because it would go out ot scope and be destroyed already + }*/ + return parent::createTable($name, $fields, $options); + } + + // }}} + // {{{ 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] + * + * NB: you have to run the NSControl Create utility to enable VACUUM + * + * @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; + } + $timeout = isset($options['timeout']) ? (int)$options['timeout'] : 300; + + $query = 'NSControl Create'; + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + + $result = $db->exec('EXEC NSVacuum '.$timeout); + 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. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $name_quoted = $db->quoteIdentifier($name, true); + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'remove': + case 'rename': + case 'add': + case 'change': + 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; + } + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + $result = $this->_dropConflictingIndices($name, array_keys($changes['remove'])); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + $result = $this->_dropConflictingConstraints($name, array_keys($changes['remove'])); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + + $query = ''; + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'COLUMN ' . $field_name; + } + + $result = $db->exec("ALTER TABLE $name_quoted DROP $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $result = $db->exec("sp_rename '$name_quoted.$field_name', '".$field['name']."', 'COLUMN'"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + $query = ''; + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } else { + $query.= 'ADD '; + } + $query.= $db->getDeclaration($field['type'], $field_name, $field); + } + + $result = $db->exec("ALTER TABLE $name_quoted $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + $dropped_indices = array(); + $dropped_constraints = array(); + + if (!empty($changes['change']) && is_array($changes['change'])) { + $dropped = $this->_dropConflictingIndices($name, array_keys($changes['change'])); + if (MDB2::isError($dropped)) { + $db->setOption('idxname_format', $idxname_format); + return $dropped; + } + $dropped_indices = array_merge($dropped_indices, $dropped); + $dropped = $this->_dropConflictingConstraints($name, array_keys($changes['change'])); + if (MDB2::isError($dropped)) { + $db->setOption('idxname_format', $idxname_format); + return $dropped; + } + $dropped_constraints = array_merge($dropped_constraints, $dropped); + + foreach ($changes['change'] as $field_name => $field) { + //MSSQL doesn't allow multiple ALTER COLUMNs in one query + $query = 'ALTER COLUMN '; + + //MSSQL doesn't allow changing the DEFAULT value of a field in altering mode + if (array_key_exists('default', $field['definition'])) { + unset($field['definition']['default']); + } + + $query .= $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + $result = $db->exec("ALTER TABLE $name_quoted $query"); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + } + + // restore the dropped conflicting indices and constraints + foreach ($dropped_indices as $index_name => $index) { + $result = $this->createIndex($name, $index_name, $index); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + foreach ($dropped_constraints as $constraint_name => $constraint) { + $result = $this->createConstraint($name, $constraint_name, $constraint); + if (MDB2::isError($result)) { + $db->setOption('idxname_format', $idxname_format); + return $result; + } + } + + $db->setOption('idxname_format', $idxname_format); + + if (!empty($changes['name'])) { + $new_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("sp_rename '$name_quoted', '$new_name'"); + if (MDB2::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _dropConflictingIndices() + + /** + * Drop the indices that prevent a successful ALTER TABLE action + * + * @param string $table table name + * @param array $fields array of names of the fields affected by the change + * + * @return array dropped indices definitions + */ + function _dropConflictingIndices($table, $fields) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $dropped = array(); + $index_names = $this->listTableIndexes($table); + if (MDB2::isError($index_names)) { + return $index_names; + } + $db->loadModule('Reverse'); + $indexes = array(); + foreach ($index_names as $index_name) { + $idx_def = $db->reverse->getTableIndexDefinition($table, $index_name); + if (!MDB2::isError($idx_def)) { + $indexes[$index_name] = $idx_def; + } + } + foreach ($fields as $field_name) { + foreach ($indexes as $index_name => $index) { + if (!isset($dropped[$index_name]) && array_key_exists($field_name, $index['fields'])) { + $dropped[$index_name] = $index; + $result = $this->dropIndex($table, $index_name); + if (MDB2::isError($result)) { + return $result; + } + } + } + } + + return $dropped; + } + + // }}} + // {{{ _dropConflictingConstraints() + + /** + * Drop the constraints that prevent a successful ALTER TABLE action + * + * @param string $table table name + * @param array $fields array of names of the fields affected by the change + * + * @return array dropped constraints definitions + */ + function _dropConflictingConstraints($table, $fields) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $dropped = array(); + $constraint_names = $this->listTableConstraints($table); + if (MDB2::isError($constraint_names)) { + return $constraint_names; + } + $db->loadModule('Reverse'); + $constraints = array(); + foreach ($constraint_names as $constraint_name) { + $cons_def = $db->reverse->getTableConstraintDefinition($table, $constraint_name); + if (!MDB2::isError($cons_def)) { + $constraints[$constraint_name] = $cons_def; + } + } + foreach ($fields as $field_name) { + foreach ($constraints as $constraint_name => $constraint) { + if (!isset($dropped[$constraint_name]) && array_key_exists($field_name, $constraint['fields'])) { + $dropped[$constraint_name] = $constraint; + $result = $this->dropConstraint($table, $constraint_name); + if (MDB2::isError($result)) { + return $result; + } + } + } + // also drop implicit DEFAULT constraints + $default = $this->_getTableFieldDefaultConstraint($table, $field_name); + if (!MDB2::isError($default) && !empty($default)) { + $result = $this->dropConstraint($table, $default); + if (MDB2::isError($result)) { + return $result; + } + } + } + + return $dropped; + } + + // }}} + // {{{ _getTableFieldDefaultConstraint() + + /** + * Get the default constraint for a table field + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * + * @return mixed name of default constraint on success, a MDB2 error on failure + * @access private + */ + function _getTableFieldDefaultConstraint($table, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $field = $db->quote($field, 'text'); + $query = "SELECT OBJECT_NAME(syscolumns.cdefault) + FROM syscolumns + WHERE syscolumns.id = object_id('$table') + AND syscolumns.name = $field + AND syscolumns.cdefault <> 0"; + return $db->queryOne($query); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db = $this->getDBInstance(); + + if (MDB2::isError($db)) { + return $db; + } + + $query = 'EXEC sp_tables @table_type = "\'TABLE\'"'; + $table_names = $db->queryCol($query, null, 2); + if (MDB2::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + 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); + $columns = $db->queryCol("SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '$table'"); + if (MDB2::isError($columns)) { + return $columns; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $columns); + } + return $columns; + } + + // }}} + // {{{ 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 = 'INDEX_NAME'; + $pk_name = 'PK_NAME'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $pk_name = strtolower($pk_name); + } else { + $key_name = strtoupper($key_name); + $pk_name = strtoupper($pk_name); + } + } + $table = $db->quote($table, 'text'); + $query = "EXEC sp_statistics @table_name=$table"; + $indexes = $db->queryCol($query, 'text', $key_name); + if (MDB2::isError($indexes)) { + return $indexes; + } + $query = "EXEC sp_pkeys @table_name=$table"; + $pk_all = $db->queryCol($query, 'text', $pk_name); + $result = array(); + foreach ($indexes as $index) { + if (!in_array($index, $pk_all) && ($index = $this->_fixIndexName($index))) { + $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); + } + + // }}} + // {{{ 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('SELECT name FROM sys.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; + } + + $result = $db->queryCol('SELECT DISTINCT loginame FROM master..sysprocesses'); + if (MDB2::isError($result) || empty($result)) { + return $result; + } + foreach (array_keys($result) as $k) { + $result[$k] = trim($result[$k]); + } + return $result; + } + + // }}} + // {{{ 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 sysobjects + WHERE objectproperty(id, N'IsMSShipped') = 0 + AND (objectproperty(id, N'IsTableFunction') = 1 + OR objectproperty(id, N'IsScalarFunction') = 1)"; + /* + 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, otherwise, false which + * could be a db error if the db is not instantiated or could + * be the results of the error that occured during the + * querying of the sysobject module. + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT o.name + FROM sysobjects o + WHERE xtype = 'TR' + AND OBJECTPROPERTY(o.id, 'IsMSShipped') = 0"; + if (null !== $table) { + $query .= " AND object_name(parent_obj) = $table"; + } + + $result = $db->queryCol($query); + if (MDB2::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE && + $db->options['field_case'] == CASE_LOWER) + { + $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() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name + FROM sysobjects + WHERE xtype = 'V'"; + /* + SELECT * + FROM sysobjects + WHERE objectproperty(id, N'IsMSShipped') = 0 + AND objectproperty(id, N'IsView') = 1 + */ + + $result = $db->queryCol($query); + if (MDB2::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE && + $db->options['field_case'] == CASE_LOWER) + { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? + 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ 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 $table.$name"); + 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; + } + $query = "SELECT c.constraint_name + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS c + WHERE c.constraint_catalog = DB_NAME() + AND c.table_name = '$table'"; + $constraints = $db->queryCol($query); + if (MDB2::isError($constraints)) { + return $constraints; + } + + $result = array(); + foreach ($constraints as $constraint) { + $constraint = $this->_fixIndexName($constraint); + if (!empty($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); + } + + // }}} + // {{{ + + /** + * Create a basic SQL query for a new table creation + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * Supported options are: + * 'primary' An array of column names in the array keys + * that form the primary key of the table + * 'temporary' If true, creates the table as a temporary table + * @return mixed string The SQL query on success, or MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (MDB2::isError($query_fields)) { + return $query_fields; + } + /*Removed since you can't get the PK name from Schema here, will result in a redefinition of PK index error + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + }*/ + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary']) && $options['temporary']) { + $result .= $this->_getTemporaryTableQuery() . ' '; + } + $result .= "TABLE $name ($query_fields)"; + return $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 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $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); + $query = "CREATE TABLE $sequence_name ($seqcol_name " . + "INT PRIMARY KEY CLUSTERED IDENTITY($start,1) NOT NULL)"; + + $res = $db->exec($query); + if (MDB2::isError($res)) { + return $res; + } + + $query = "SET IDENTITY_INSERT $sequence_name ON ". + "INSERT INTO $sequence_name ($seqcol_name) VALUES ($start)"; + $res = $db->exec($query); + + if (!MDB2::isError($res)) { + return MDB2_OK; + } + + $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() + + /** + * This function drops an 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 + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sysobjects WHERE xtype = 'U'"; + $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; + } + + // }}} + /** + * New OPENX method to check table name according to specifications: + * http://msdn.microsoft.com/en-us/library/aa258255(SQL.80).aspx + * + * Table names must conform to the rules for identifiers. The combination of owner.table_name + * must be unique within the database. table_name can contain a maximum of 128 characters, + * except for local temporary table names (names prefixed with a single number sign (#)) that + * cannot exceed 116 characters. + * + * @param string $name table name to check + * @return true if name is correct and PEAR error on failure + */ + function validateTableName($name) + { + // Table name maximum length is 128 + if (strlen($name) > 128) { + return PEAR::raiseError( + 'SQL Server table names are limited to 128 characters in length'); + } + return true; + } + + /** + * New OpenX method + * + * @param string $table + * @return array + */ + function getTableStatus($table) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "exec sp_spaceused '{$table}'"; + $result = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($result)) + { + return array(); + } + $result[0]['data_length'] = (isset($result[0]['data'])) ? $result[0]['data'] : 0; + $result[0]['data_free'] = (isset($result[0]['unused'])) ? $result[0]['unused'] : 0; + //data_length,rows,auto_increment,data_free + $query = "SELECT IDENT_CURRENT ('{$table}') + IDENT_INCR ('{$table}') AS auto_increment"; + $resultIdentity = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + $result[0]['auto_increment'] = (isset($resultIdentity[0]['auto_increment'])) ? $resultIdentity[0]['auto_increment'] : 0; + return $result; + } + + function checkTable($tableName) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $query = 'CHECK TABLE '.$tableName; + $result = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($result)) + { + return array('msg_text' => $result->getUserInfo()); + } + return $result; + } + + /** + * New OPENX method to check database name according to specifications: + * Mysql specification: http://dev.mysql.com/doc/refman/4.1/en/identifiers.html + * Mysql specification: http://dev.mysql.com/doc/refman/5.0/en/identifiers.html + * For 4.0, 4.1, 5.0 seem to be the same + * + * @param string $name database name to check + * @return true in name is correct and PEAR error on failure + */ + function validateDatabaseName($name) + { + return $this->_validateEntityName($name, 'Database'); + } + + /** + * New OPENX method to check entity name according to specifications: + * Mysql specification: http://dev.mysql.com/doc/refman/4.1/en/identifiers.html + * Mysql specification: http://dev.mysql.com/doc/refman/5.0/en/identifiers.html + * For 4.0, 4.1, 5.0 seem to be the same + * + * There are some restrictions on the characters that may appear in identifiers: + * - No identifier can contain ASCII 0 (0x00) or a byte with a value of 255. + * - Before MySQL 4.1, identifier quote characters should not be used in identifiers. + * - Database, table, and column names should not end with space characters. + * - Database and table names cannot contain "/", "\", ".", or characters that are not allowed in filenames. + * + * Table/Database name maximum length: + * - 64 + * + * @param string $name table name to check + * @param string $entityType + * + * @return true if name is correct and PEAR error on failure + */ + function _validateEntityName($name, $entityType) + { + // Table name maximum length is 64 + if (strlen($name) > 64) { + return PEAR::raiseError( + $entityType.' names are limited to 64 characters in length'); + } + + // Database, table, and column names should not end with space characters. + // Extended for leading and ending spaces + if ($name != trim($name)) { + return PEAR::raiseError( + $entityType.' names should not start or end with space characters'); + } + + // No identifier can contain ASCII 0 (0x00) or a byte with a value of 255. + if (preg_match( '/([\x00\xff])/', $name)) { + return PEAR::raiseError( + $entityType.' names cannot contain ASCII 0 (0x00) or a byte with a value of 255'); + } + + //Before MySQL 4.1, identifier quote characters should not be used in identifiers. + //we actually extend that and disallow quoting at all + if (preg_match( '/(\\\\|\/|\.|\"|\\\'| |\\(|\\)|\\:|\\;)/', $name)) { + return PEAR::raiseError( + $entityType.' names cannot contain "/", "\\", ".", or characters that are not allowed in filenames'); + } + + return true; + } + + // {{{ 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; + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + if (!empty($definition['primary']) && empty($definition['unique'])) { + $query = "ALTER TABLE $table ADD CONSTRAINT $name"; + if (!empty($definition['primary'])) { + $query.= ' PRIMARY KEY'; + } elseif (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } + } elseif (!empty($definition['unique'])) { + $query = "CREATE UNIQUE NONCLUSTERED INDEX $name ON $table"; + } elseif (!empty($definition['foreign'])) { + $query = "ALTER TABLE $table ADD CONSTRAINT $name FOREIGN KEY"; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + //deals with NULL values and UNIQUE indexes, this solution is only available in SQL Server 2008 + //https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=299229 + if (!empty($definition['unique']) && empty($definition['primary'])) { + for($i=0;$iquoteIdentifier($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); + } + $result = $db->exec($query); + if (MDB2::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + +} + +// }}} +?> diff --git a/extlib/MDB2/Driver/Native/Common.php b/extlib/MDB2/Driver/Native/Common.php new file mode 100644 index 0000000000..67dc1bddd0 --- /dev/null +++ b/extlib/MDB2/Driver/Native/Common.php @@ -0,0 +1,61 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * Base class for the natuve modules that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Native'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_Common extends MDB2_Module_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/fbsql.php b/extlib/MDB2/Driver/Native/fbsql.php new file mode 100644 index 0000000000..b57793d943 --- /dev/null +++ b/extlib/MDB2/Driver/Native/fbsql.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 FrontBase driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_fbsql extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/ibase.php b/extlib/MDB2/Driver/Native/ibase.php new file mode 100644 index 0000000000..abb13c38e9 --- /dev/null +++ b/extlib/MDB2/Driver/Native/ibase.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 FireBird/InterBase driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_ibase extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/mssql.php b/extlib/MDB2/Driver/Native/mssql.php new file mode 100644 index 0000000000..ec256ff530 --- /dev/null +++ b/extlib/MDB2/Driver/Native/mssql.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 MSSQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_mssql extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/mysqli.php b/extlib/MDB2/Driver/Native/mysqli.php new file mode 100644 index 0000000000..0d6ebc5978 --- /dev/null +++ b/extlib/MDB2/Driver/Native/mysqli.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 MySQLi driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_mysqli extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/oci8.php b/extlib/MDB2/Driver/Native/oci8.php new file mode 100644 index 0000000000..c830db3b3c --- /dev/null +++ b/extlib/MDB2/Driver/Native/oci8.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 Oracle driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_oci8 extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/pgsql.php b/extlib/MDB2/Driver/Native/pgsql.php new file mode 100644 index 0000000000..45f19b71a8 --- /dev/null +++ b/extlib/MDB2/Driver/Native/pgsql.php @@ -0,0 +1,88 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 PostGreSQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_Native_pgsql extends MDB2_Driver_Native_Common +{ + // }}} + // {{{ deleteOID() + + /** + * delete an OID + * + * @param integer $OID + * @return mixed MDB2_OK on success or MDB2 Error Object on failure + * @access public + */ + function deleteOID($OID) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $connection = $db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + if (!@pg_lo_unlink($connection, $OID)) { + return $db->raiseError(null, null, null, + 'Unable to unlink OID: '.$OID, __FUNCTION__); + } + return MDB2_OK; + } + +} +?> diff --git a/extlib/MDB2/Driver/Native/sqlite.php b/extlib/MDB2/Driver/Native/sqlite.php new file mode 100644 index 0000000000..4eb796dce7 --- /dev/null +++ b/extlib/MDB2/Driver/Native/sqlite.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 SQLite driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_sqlite extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/sqlite3.php b/extlib/MDB2/Driver/Native/sqlite3.php new file mode 100644 index 0000000000..8f8b50d529 --- /dev/null +++ b/extlib/MDB2/Driver/Native/sqlite3.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 SQLite driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_sqlite3 extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Native/sqlsrv.php b/extlib/MDB2/Driver/Native/sqlsrv.php new file mode 100644 index 0000000000..f9a811d240 --- /dev/null +++ b/extlib/MDB2/Driver/Native/sqlsrv.php @@ -0,0 +1,57 @@ + | +// +----------------------------------------------------------------------+ + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 MSSQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_sqlsrv extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/extlib/MDB2/Driver/Reverse/Common.php b/extlib/MDB2/Driver/Reverse/Common.php new file mode 100644 index 0000000000..63cd6ebfbd --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/Common.php @@ -0,0 +1,517 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * @package MDB2 + * @category Database + */ + +/** + * These are constants for the tableInfo-function + * they are bitwised or'ed. so if there are more constants to be defined + * in the future, adjust MDB2_TABLEINFO_FULL accordingly + */ + +define('MDB2_TABLEINFO_ORDER', 1); +define('MDB2_TABLEINFO_ORDERTABLE', 2); +define('MDB2_TABLEINFO_FULL', 3); + +/** + * Base class for the schema reverse engineering module that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Reverse'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * + * @param string $table [schema and] table name + * + * @return array array(schema, table) + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure. + * The returned array contains an array for each field definition, + * with all or some of these indices, depending on the field data type: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table, $field) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + * + * array ( + * [fields] => array ( + * [field1name] => array() // one entry per each field covered + * [field2name] => array() // by the index + * [field3name] => array( + * [sorting] => ascending + * ) + * ) + * ); + * + * @access public + */ + function getTableIndexDefinition($table, $index) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of an constraints into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @access public + */ + function getTableConstraintDefinition($table, $index) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the structure of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [start] => n
+     *          );
+     *          
+ * @access public + */ + function getSequenceDefinition($sequence) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $start = $db->currId($sequence); + if (MDB2::isError($start)) { + return $start; + } + if ($db->supports('current_id')) { + $start++; + } else { + $db->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [trigger_name]    => 'trigger name',
+     *              [table_name]      => 'table name',
+     *              [trigger_body]    => 'trigger body definition',
+     *              [trigger_type]    => 'BEFORE' | 'AFTER',
+     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
+     *                  //or comma separated list of multiple events, when supported
+     *              [trigger_enabled] => true|false
+     *              [trigger_comment] => 'trigger comment',
+     *          );
+     *          
+ * The oci8 driver also returns a [when_clause] index. + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDER + * + *

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

    + * + *

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

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

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

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE + * turned on, the names of tables and fields will be lower or upper cased. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * MDB2_TABLEINFO_ORDERTABLE, + * MDB2_TABLEINFO_ORDER or + * MDB2_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if (!is_string($result)) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $db->loadModule('Manager', null, true); + $fields = $db->manager->listTableFields($result); + if (MDB2::isError($fields)) { + return $fields; + } + + $flags = array(); + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + $indexes = $db->manager->listTableIndexes($result); + if (MDB2::isError($indexes)) { + $db->setOption('idxname_format', $idxname_format); + return $indexes; + } + + foreach ($indexes as $index) { + $definition = $this->getTableIndexDefinition($result, $index); + if (MDB2::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + if (count($definition['fields']) > 1) { + foreach ($definition['fields'] as $field => $sort) { + $flags[$field] = 'multiple_key'; + } + } + } + + $constraints = $db->manager->listTableConstraints($result); + if (MDB2::isError($constraints)) { + return $constraints; + } + + foreach ($constraints as $constraint) { + $definition = $this->getTableConstraintDefinition($result, $constraint); + if (MDB2::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $flag = !empty($definition['primary']) + ? 'primary_key' : (!empty($definition['unique']) + ? 'unique_key' : false); + if ($flag) { + foreach ($definition['fields'] as $field => $sort) { + if (empty($flags[$field]) || $flags[$field] != 'primary_key') { + $flags[$field] = $flag; + } + } + } + } + + $res = array(); + + if ($mode) { + $res['num_fields'] = count($fields); + } + + foreach ($fields as $i => $field) { + $definition = $this->getTableFieldDefinition($result, $field); + if (MDB2::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $res[$i] = $definition[0]; + $res[$i]['name'] = $field; + $res[$i]['table'] = $result; + $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); + // 'primary_key', 'unique_key', 'multiple_key' + $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; + // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' + if (!empty($res[$i]['notnull'])) { + $res[$i]['flags'].= ' not_null'; + } + if (!empty($res[$i]['unsigned'])) { + $res[$i]['flags'].= ' unsigned'; + } + if (!empty($res[$i]['auto_increment'])) { + $res[$i]['flags'].= ' autoincrement'; + } + if (!empty($res[$i]['default'])) { + $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); + } + + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + $db->setOption('idxname_format', $idxname_format); + return $res; + } +} +?> diff --git a/extlib/MDB2/Driver/Reverse/fbsql.php b/extlib/MDB2/Driver/Reverse/fbsql.php new file mode 100644 index 0000000000..71ac6ad889 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/fbsql.php @@ -0,0 +1,132 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 FrontBase driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_fbsql extends MDB2_Driver_Reverse_Common +{ + // }}} + // {{{ 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::tableInfo() + */ + 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 = @fbsql_num_fields($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@fbsql_field_table($resource, $i)), + 'name' => $case_func(@fbsql_field_name($resource, $i)), + 'type' => @fbsql_field_type($resource, $i), + 'length' => @fbsql_field_len($resource, $i), + 'flags' => @fbsql_field_flags($resource, $i), + ); + // todo: implement $db->datatype->mapNativeDatatype(); + $res[$i]['mdb2type'] = $res[$i]['type']; + 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; + } +} +?> diff --git a/extlib/MDB2/Driver/Reverse/ibase.php b/extlib/MDB2/Driver/Reverse/ibase.php new file mode 100644 index 0000000000..cf4ec572a5 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/ibase.php @@ -0,0 +1,590 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 InterbaseBase driver for the reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_ibase extends MDB2_Driver_Reverse_Common +{ + /** + * Array for converting constant values to text values + * @var array + * @access public + */ + var $types = array( + 7 => 'smallint', + 8 => 'integer', + 9 => 'quad', + 10 => 'float', + 11 => 'd_float', + 12 => 'date', //dialect 3 DATE + 13 => 'time', + 14 => 'char', + 16 => 'int64', + 27 => 'double', + 35 => 'timestamp', //DATE in older versions + 37 => 'varchar', + 40 => 'cstring', + 261 => 'blob', + ); + + /** + * Array for converting constant values to text values + * @var array + * @access public + */ + var $subtypes = array( + //char subtypes + 14 => array( + 0 => 'unspecified', + 1 => 'fixed', //BINARY data + ), + //blob subtypes + 261 => array( + 0 => 'unspecified', + 1 => 'text', + 2 => 'BLR', //Binary Language Representation + 3 => 'access control list', + 4 => 'reserved for future use', + 5 => 'encoded description of a table\'s current metadata', + 6 => 'description of multi-database transaction that finished irregularly', + ), + //smallint subtypes + 7 => array( + 0 => 'RDB$FIELD_TYPE', + 1 => 'numeric', + 2 => 'decimal', + ), + //integer subtypes + 8 => array( + 0 => 'RDB$FIELD_TYPE', + 1 => 'numeric', + 2 => 'decimal', + ), + //int64 subtypes + 16 => array( + 0 => 'RDB$FIELD_TYPE', + 1 => 'numeric', + 2 => 'decimal', + ), + ); + + // {{{ 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->quote(strtoupper($table), 'text'); + $field_name = $db->quote(strtoupper($field_name), 'text'); + $query = "SELECT RDB\$RELATION_FIELDS.RDB\$FIELD_NAME AS name, + RDB\$FIELDS.RDB\$FIELD_LENGTH AS \"length\", + RDB\$FIELDS.RDB\$FIELD_PRECISION AS \"precision\", + (RDB\$FIELDS.RDB\$FIELD_SCALE * -1) AS \"scale\", + RDB\$FIELDS.RDB\$FIELD_TYPE AS field_type_code, + RDB\$FIELDS.RDB\$FIELD_SUB_TYPE AS field_sub_type_code, + RDB\$RELATION_FIELDS.RDB\$DESCRIPTION AS description, + RDB\$RELATION_FIELDS.RDB\$NULL_FLAG AS null_flag, + RDB\$FIELDS.RDB\$DEFAULT_SOURCE AS default_source, + RDB\$CHARACTER_SETS.RDB\$CHARACTER_SET_NAME AS \"charset\", + RDB\$COLLATIONS.RDB\$COLLATION_NAME AS \"collation\" + FROM RDB\$FIELDS + LEFT JOIN RDB\$RELATION_FIELDS ON RDB\$FIELDS.RDB\$FIELD_NAME = RDB\$RELATION_FIELDS.RDB\$FIELD_SOURCE + LEFT JOIN RDB\$CHARACTER_SETS ON RDB\$FIELDS.RDB\$CHARACTER_SET_ID = RDB\$CHARACTER_SETS.RDB\$CHARACTER_SET_ID + LEFT JOIN RDB\$COLLATIONS ON RDB\$FIELDS.RDB\$COLLATION_ID = RDB\$COLLATIONS.RDB\$COLLATION_ID + WHERE UPPER(RDB\$RELATION_FIELDS.RDB\$RELATION_NAME)=$table + AND UPPER(RDB\$RELATION_FIELDS.RDB\$FIELD_NAME)=$field_name;"; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($column)) { + return $column; + } + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + $column = array_change_key_case($column, CASE_LOWER); + 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']); + } + } + + $column['type'] = array_key_exists((int)$column['field_type_code'], $this->types) + ? $this->types[(int)$column['field_type_code']] : 'undefined'; + if ($column['field_sub_type_code'] + && array_key_exists((int)$column['field_type_code'], $this->subtypes) + && array_key_exists($column['field_sub_type_code'], $this->subtypes[(int)$column['field_type_code']]) + ) { + $column['field_sub_type'] = $this->subtypes[(int)$column['field_type_code']][$column['field_sub_type_code']]; + } else { + $column['field_sub_type'] = null; + } + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (MDB2::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = !empty($column['null_flag']); + $default = $column['default_source']; + if ((null === $default) && $notnull) { + $default = ($types[0] == 'integer') ? 0 : ''; + } + + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => $column['type'], + 'charset' => $column['charset'], + 'collation' => $column['collation'], + ); + if (null !== $length) { + $definition[0]['length'] = $length; + } + if (null !== $unsigned) { + $definition[0]['unsigned'] = $unsigned; + } + if (null !== $fixed) { + $definition[0]['fixed'] = $fixed; + } + if (false !== $default) { + $definition[0]['default'] = $default; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name, $format_index_name = true) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $table = $db->quote(strtoupper($table), 'text'); + $query = "SELECT RDB\$INDEX_SEGMENTS.RDB\$FIELD_NAME AS field_name, + RDB\$INDICES.RDB\$DESCRIPTION AS description, + (RDB\$INDEX_SEGMENTS.RDB\$FIELD_POSITION + 1) AS field_position + FROM RDB\$INDEX_SEGMENTS + LEFT JOIN RDB\$INDICES ON RDB\$INDICES.RDB\$INDEX_NAME = RDB\$INDEX_SEGMENTS.RDB\$INDEX_NAME + LEFT JOIN RDB\$RELATION_CONSTRAINTS ON RDB\$RELATION_CONSTRAINTS.RDB\$INDEX_NAME = RDB\$INDEX_SEGMENTS.RDB\$INDEX_NAME + WHERE UPPER(RDB\$INDICES.RDB\$RELATION_NAME)=$table + AND UPPER(RDB\$INDICES.RDB\$INDEX_NAME)=%s + AND RDB\$RELATION_CONSTRAINTS.RDB\$CONSTRAINT_TYPE IS NULL + ORDER BY RDB\$INDEX_SEGMENTS.RDB\$FIELD_POSITION"; + $index_name_mdb2 = $db->quote(strtoupper($db->getIndexName($index_name)), 'text'); + $result = $db->queryRow(sprintf($query, $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; + } else { + $index_name = $db->quote(strtoupper($index_name), 'text'); + } + $result = $db->query(sprintf($query, $index_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['field_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' => (int)$row['field_position'], + ); + /* + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + */ + } + + $result->free(); + if (empty($definition)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified 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); + + $table = $db->quote(strtoupper($table), 'text'); + $query = "SELECT rc.RDB\$CONSTRAINT_NAME, + s.RDB\$FIELD_NAME AS field_name, + CASE WHEN rc.RDB\$CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 1 ELSE 0 END AS \"primary\", + CASE WHEN rc.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY' THEN 1 ELSE 0 END AS \"foreign\", + CASE WHEN rc.RDB\$CONSTRAINT_TYPE = 'UNIQUE' THEN 1 ELSE 0 END AS \"unique\", + CASE WHEN rc.RDB\$CONSTRAINT_TYPE = 'CHECK' THEN 1 ELSE 0 END AS \"check\", + i.RDB\$DESCRIPTION AS description, + CASE WHEN rc.RDB\$DEFERRABLE = 'NO' THEN 0 ELSE 1 END AS deferrable, + CASE WHEN rc.RDB\$INITIALLY_DEFERRED = 'NO' THEN 0 ELSE 1 END AS initiallydeferred, + refc.RDB\$UPDATE_RULE AS onupdate, + refc.RDB\$DELETE_RULE AS ondelete, + refc.RDB\$MATCH_OPTION AS \"match\", + i2.RDB\$RELATION_NAME AS references_table, + s2.RDB\$FIELD_NAME AS references_field, + (s.RDB\$FIELD_POSITION + 1) AS field_position + FROM RDB\$INDEX_SEGMENTS s + LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME + LEFT JOIN RDB\$RELATION_CONSTRAINTS rc ON rc.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME + LEFT JOIN RDB\$REF_CONSTRAINTS refc ON rc.RDB\$CONSTRAINT_NAME = refc.RDB\$CONSTRAINT_NAME + LEFT JOIN RDB\$RELATION_CONSTRAINTS rc2 ON rc2.RDB\$CONSTRAINT_NAME = refc.RDB\$CONST_NAME_UQ + LEFT JOIN RDB\$INDICES i2 ON i2.RDB\$INDEX_NAME = rc2.RDB\$INDEX_NAME + LEFT JOIN RDB\$INDEX_SEGMENTS s2 ON i2.RDB\$INDEX_NAME = s2.RDB\$INDEX_NAME + AND s.RDB\$FIELD_POSITION = s2.RDB\$FIELD_POSITION + WHERE UPPER(i.RDB\$RELATION_NAME)=$table + AND UPPER(rc.RDB\$CONSTRAINT_NAME)=%s + AND rc.RDB\$CONSTRAINT_TYPE IS NOT NULL + ORDER BY s.RDB\$FIELD_POSITION"; + $constraint_name_mdb2 = $db->quote(strtoupper($db->getIndexName($constraint_name)), 'text'); + $result = $db->queryRow(sprintf($query, $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; + } else { + $constraint_name = $db->quote(strtoupper($constraint_name), 'text'); + } + $result = $db->query(sprintf($query, $constraint_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['field_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' => (int)$row['field_position'] + ); + if ($row['foreign']) { + $ref_column_name = $row['references_field']; + $ref_table_name = $row['references_table']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $ref_column_name = strtolower($ref_column_name); + $ref_table_name = strtolower($ref_table_name); + } else { + $ref_column_name = strtoupper($ref_column_name); + $ref_table_name = strtoupper($ref_table_name); + } + } + $definition['references']['table'] = $ref_table_name; + $definition['references']['fields'][$ref_column_name] = array( + 'position' => (int)$row['field_position'] + ); + } + //collation?!? + /* + if (!empty($row['collation'])) { + $definition['fields'][$field]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + */ + $lastrow = $row; + // otherwise $row is no longer usable on exit from loop + } + $result->free(); + if (empty($definition)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['primary'] = (boolean)$lastrow['primary']; + $definition['unique'] = (boolean)$lastrow['unique']; + $definition['foreign'] = (boolean)$lastrow['foreign']; + $definition['check'] = (boolean)$lastrow['check']; + $definition['deferrable'] = (boolean)$lastrow['deferrable']; + $definition['initiallydeferred'] = (boolean)$lastrow['initiallydeferred']; + $definition['onupdate'] = $lastrow['onupdate']; + $definition['ondelete'] = $lastrow['ondelete']; + $definition['match'] = $lastrow['match']; + + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $trigger = $db->quote(strtoupper($trigger), 'text'); + $query = "SELECT RDB\$TRIGGER_NAME AS trigger_name, + RDB\$RELATION_NAME AS table_name, + RDB\$TRIGGER_SOURCE AS trigger_body, + CASE RDB\$TRIGGER_TYPE + WHEN 1 THEN 'BEFORE' + WHEN 2 THEN 'AFTER' + WHEN 3 THEN 'BEFORE' + WHEN 4 THEN 'AFTER' + WHEN 5 THEN 'BEFORE' + WHEN 6 THEN 'AFTER' + END AS trigger_type, + CASE RDB\$TRIGGER_TYPE + WHEN 1 THEN 'INSERT' + WHEN 2 THEN 'INSERT' + WHEN 3 THEN 'UPDATE' + WHEN 4 THEN 'UPDATE' + WHEN 5 THEN 'DELETE' + WHEN 6 THEN 'DELETE' + END AS trigger_event, + CASE RDB\$TRIGGER_INACTIVE + WHEN 1 THEN 0 ELSE 1 + END AS trigger_enabled, + RDB\$DESCRIPTION AS trigger_comment + FROM RDB\$TRIGGERS + WHERE UPPER(RDB\$TRIGGER_NAME)=$trigger"; + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'clob', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($def)) { + return $def; + } + + $clob = $def['trigger_body']; + if (!MDB2::isError($clob) && is_resource($clob)) { + $value = ''; + while (!feof($clob)) { + $data = fread($clob, 8192); + $value.= $data; + } + $db->datatype->destroyLOB($clob); + $def['trigger_body'] = $value; + } + + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (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 = @ibase_num_fields($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $info = @ibase_field_info($resource, $i); + if (($pos = strpos($info['type'], '(')) !== false) { + $info['type'] = substr($info['type'], 0, $pos); + } + $res[$i] = array( + 'table' => $case_func($info['relation']), + 'name' => $case_func($info['name']), + 'type' => $info['type'], + 'length' => $info['length'], + 'flags' => '', + ); + $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; + } +} +?> diff --git a/extlib/MDB2/Driver/Reverse/mssql.php b/extlib/MDB2/Driver/Reverse/mssql.php new file mode 100644 index 0000000000..8b93c5f41d --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/mssql.php @@ -0,0 +1,653 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MSSQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_mssql 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); + $fldname = $db->quoteIdentifier($field_name, true); + + $query = "SELECT t.table_name, + c.column_name 'name', + c.data_type 'type', + CASE c.is_nullable WHEN 'YES' THEN 1 ELSE 0 END AS 'is_nullable', + c.column_default, + c.character_maximum_length 'length', + c.numeric_precision, + c.numeric_scale, + c.character_set_name, + c.collation_name + FROM INFORMATION_SCHEMA.TABLES t, + INFORMATION_SCHEMA.COLUMNS c + WHERE t.table_name = c.table_name + AND t.table_name = '$table' + AND c.column_name = '$fldname'"; + if (!empty($schema)) { + $query .= " AND t.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ' ORDER BY t.table_name'; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($column)) { + return $column; + } + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + 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']); + } + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (MDB2::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = true; + if ($column['is_nullable']) { + $notnull = false; + } + $default = false; + if (array_key_exists('column_default', $column)) { + $default = $column['column_default']; + if ((null === $default) && $notnull) { + $default = ''; + } elseif (strlen($default) > 4 + && substr($default, 0, 1) == '(' + && substr($default, -1, 1) == ')' + ) { + //mssql wraps the default value in parentheses: "((1234))", "(NULL)" + $default = trim($default, '()'); + if ($default == 'NULL') { + $default = null; + } + } + } + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + if (null !== $length) { + $definition[0]['length'] = $length; + } + if (null !== $unsigned) { + $definition[0]['unsigned'] = $unsigned; + } + if (null !== $fixed) { + $definition[0]['fixed'] = $fixed; + } + if (false !== $default) { + $definition[0]['default'] = $default; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $table = $db->quoteIdentifier($table, true); + //$idxname = $db->quoteIdentifier($index_name, true); + + $query = "SELECT OBJECT_NAME(i.id) tablename, + i.name indexname, + c.name field_name, + CASE INDEXKEY_PROPERTY(i.id, i.indid, ik.keyno, 'IsDescending') + WHEN 1 THEN 'DESC' ELSE 'ASC' + END 'collation', + ik.keyno 'position' + FROM sysindexes i + JOIN sysindexkeys ik ON ik.id = i.id AND ik.indid = i.indid + JOIN syscolumns c ON c.id = ik.id AND c.colid = ik.colid + WHERE OBJECT_NAME(i.id) = '$table' + AND i.name = '%s' + AND NOT EXISTS ( + SELECT * + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + WHERE k.table_name = OBJECT_NAME(i.id) + AND k.constraint_name = i.name"; + if (!empty($schema)) { + $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ') + ORDER BY tablename, indexname, ik.keyno'; + + $index_name_mdb2 = $db->getIndexName($index_name); + $result = $db->queryRow(sprintf($query, $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, $index_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $column_name = $row['field_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' => (int)$row['position'], + ); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC' + ? 'ascending' : 'descending'); + } + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified 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); + + $table = $db->quoteIdentifier($table, true); + $query = "SELECT k.table_name, + k.column_name field_name, + CASE c.constraint_type WHEN 'PRIMARY KEY' THEN 1 ELSE 0 END 'primary', + CASE c.constraint_type WHEN 'UNIQUE' THEN 1 ELSE 0 END 'unique', + CASE c.constraint_type WHEN 'FOREIGN KEY' THEN 1 ELSE 0 END 'foreign', + CASE c.constraint_type WHEN 'CHECK' THEN 1 ELSE 0 END 'check', + CASE c.is_deferrable WHEN 'NO' THEN 0 ELSE 1 END 'deferrable', + CASE c.initially_deferred WHEN 'NO' THEN 0 ELSE 1 END 'initiallydeferred', + rc.match_option 'match', + rc.update_rule 'onupdate', + rc.delete_rule 'ondelete', + kcu.table_name 'references_table', + kcu.column_name 'references_field', + k.ordinal_position 'field_position' + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS c + ON k.table_name = c.table_name + AND k.table_schema = c.table_schema + AND k.table_catalog = c.table_catalog + AND k.constraint_catalog = c.constraint_catalog + AND k.constraint_name = c.constraint_name + LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + ON rc.constraint_schema = c.constraint_schema + AND rc.constraint_catalog = c.constraint_catalog + AND rc.constraint_name = c.constraint_name + LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu + ON rc.unique_constraint_schema = kcu.constraint_schema + AND rc.unique_constraint_catalog = kcu.constraint_catalog + AND rc.unique_constraint_name = kcu.constraint_name + AND k.ordinal_position = kcu.ordinal_position + WHERE k.constraint_catalog = DB_NAME() + AND k.table_name = '$table' + AND k.constraint_name = '%s'"; + if (!empty($schema)) { + $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ' ORDER BY k.constraint_name, + k.ordinal_position'; + + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $result = $db->queryRow(sprintf($query, $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, $constraint_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array( + 'fields' => array() + ); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['field_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' => (int)$row['field_position'] + ); + if ($row['foreign']) { + $ref_column_name = $row['references_field']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $ref_column_name = strtolower($ref_column_name); + } else { + $ref_column_name = strtoupper($ref_column_name); + } + } + $definition['references']['table'] = $row['references_table']; + $definition['references']['fields'][$ref_column_name] = array( + 'position' => (int)$row['field_position'] + ); + } + //collation?!? + /* + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC' + ? 'ascending' : 'descending'); + } + */ + $lastrow = $row; + // otherwise $row is no longer usable on exit from loop + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['primary'] = (boolean)$lastrow['primary']; + $definition['unique'] = (boolean)$lastrow['unique']; + $definition['foreign'] = (boolean)$lastrow['foreign']; + $definition['check'] = (boolean)$lastrow['check']; + $definition['deferrable'] = (boolean)$lastrow['deferrable']; + $definition['initiallydeferred'] = (boolean)$lastrow['initiallydeferred']; + $definition['onupdate'] = $lastrow['onupdate']; + $definition['ondelete'] = $lastrow['ondelete']; + $definition['match'] = $lastrow['match']; + + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT sys1.name trigger_name, + sys2.name table_name, + c.text trigger_body, + c.encrypted is_encripted, + CASE + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsTriggerDisabled') = 1 + THEN 0 ELSE 1 + END trigger_enabled, + CASE + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsertTrigger') = 1 + THEN 'INSERT' + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsUpdateTrigger') = 1 + THEN 'UPDATE' + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsDeleteTrigger') = 1 + THEN 'DELETE' + END trigger_event, + CASE WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsteadOfTrigger') = 1 + THEN 'INSTEAD OF' ELSE 'AFTER' + END trigger_type, + '' trigger_comment + FROM sysobjects sys1 + JOIN sysobjects sys2 ON sys1.parent_obj = sys2.id + JOIN syscomments c ON sys1.id = c.id + WHERE sys1.xtype = 'TR' + AND sys1.name = ". $db->quote($trigger, 'text'); + + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + 'is_encripted' => 'boolean', + ); + + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($def)) { + return $def; + } + $trg_body = $db->queryCol('EXEC sp_helptext '. $db->quote($trigger, 'text'), 'text'); + if (!MDB2::isError($trg_body)) { + $def['trigger_body'] = implode(' ', $trg_body); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (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 = @mssql_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' => '', + 'name' => $case_func(@mssql_field_name($resource, $i)), + 'type' => @mssql_field_type($resource, $i), + 'length' => @mssql_field_length($resource, $i), + 'flags' => '', + ); + $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; + } + + // }}} + // {{{ _mssql_field_flags() + + /** + * Get a column's flags + * + * Supports "not_null", "primary_key", + * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), + * "unique_key" (mssql unique index, unique check or primary_key) and + * "multiple_key" (multikey index) + * + * mssql timestamp is NOT similar to the mysql timestamp so this is maybe + * not useful at all - is the behaviour of mysql_field_flags that primary + * keys are alway unique? is the interpretation of multiple_key correct? + * + * @param string $table the table name + * @param string $column the field name + * + * @return string the flags + * + * @access protected + * @author Joern Barthel + */ + function _mssql_field_flags($table, $column) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + + $flags = array(); + $tableName = $table; + + // get unique and primary keys + $res = $db->queryAll("EXEC SP_HELPINDEX[$table]", null, MDB2_FETCHMODE_ASSOC); + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + $keys = explode(', ', $val['index_keys']); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'primary key')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'primary_key'); + } + } elseif (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + // get auto_increment, not_null and timestamp + $res = $db->queryAll("EXEC SP_COLUMNS[$table]", null, MDB2_FETCHMODE_ASSOC); + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + if ($val['nullable'] == '0') { + $this->_add_flag($flags[$val['column_name']], 'not_null'); + } + if (strpos($val['type_name'], 'identity')) { + $this->_add_flag($flags[$val['column_name']], 'auto_increment'); + } + if (strpos($val['type_name'], 'timestamp')) { + $this->_add_flag($flags[$val['column_name']], 'timestamp'); + } + } + } + + if (!empty($flags[$column])) { + return(implode(' ', $flags[$column])); + } + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array &$array the reference to the flag-array + * @param string $value the flag value + * + * @return void + * + * @access protected + * @author Joern Barthel + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Reverse/mysqli.php b/extlib/MDB2/Driver/Reverse/mysqli.php new file mode 100644 index 0000000000..77a4148ec2 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/mysqli.php @@ -0,0 +1,610 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MySQLi driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_mysqli extends MDB2_Driver_Reverse_Common +{ + /** + * Array for converting MYSQLI_*_FLAG constants to text values + * @var array + * @access public + */ + var $flags = array( + MYSQLI_NOT_NULL_FLAG => 'not_null', + MYSQLI_PRI_KEY_FLAG => 'primary_key', + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', + MYSQLI_BLOB_FLAG => 'blob', + MYSQLI_UNSIGNED_FLAG => 'unsigned', + MYSQLI_ZEROFILL_FLAG => 'zerofill', + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', + MYSQLI_TIMESTAMP_FLAG => 'timestamp', + MYSQLI_SET_FLAG => 'set', + // MYSQLI_NUM_FLAG => 'numeric', // unnecessary + // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie + MYSQLI_GROUP_FLAG => 'group_by' + ); + + /** + * Array for converting MYSQLI_TYPE_* constants to text values + * @var array + * @access public + */ + var $types = array( + MYSQLI_TYPE_DECIMAL => 'decimal', + 246 => 'decimal', + MYSQLI_TYPE_TINY => 'tinyint', + MYSQLI_TYPE_SHORT => 'int', + MYSQLI_TYPE_LONG => 'int', + MYSQLI_TYPE_FLOAT => 'float', + MYSQLI_TYPE_DOUBLE => 'double', + // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it + MYSQLI_TYPE_TIMESTAMP => 'timestamp', + MYSQLI_TYPE_LONGLONG => 'bigint', + MYSQLI_TYPE_INT24 => 'mediumint', + MYSQLI_TYPE_DATE => 'date', + MYSQLI_TYPE_TIME => 'time', + MYSQLI_TYPE_DATETIME => 'datetime', + MYSQLI_TYPE_YEAR => 'year', + MYSQLI_TYPE_NEWDATE => 'date', + MYSQLI_TYPE_ENUM => 'enum', + MYSQLI_TYPE_SET => 'set', + MYSQLI_TYPE_TINY_BLOB => 'tinyblob', + MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob', + MYSQLI_TYPE_LONG_BLOB => 'longblob', + MYSQLI_TYPE_BLOB => 'blob', + MYSQLI_TYPE_VAR_STRING => 'varchar', + MYSQLI_TYPE_STRING => 'char', + MYSQLI_TYPE_GEOMETRY => 'geometry', + ); + + // {{{ 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_object($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 = @mysqli_num_fields($resource); + $res = array(); + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $tmp = @mysqli_fetch_field($resource); + + $flags = ''; + foreach ($this->flags as $const => $means) { + if ($tmp->flags & $const) { + $flags.= $means . ' '; + } + } + if ($tmp->def) { + $flags.= 'default_' . rawurlencode($tmp->def); + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($tmp->table), + 'name' => $case_func($tmp->name), + 'type' => isset($this->types[$tmp->type]) + ? $this->types[$tmp->type] : 'unknown', + // http://bugs.php.net/?id=36579 + 'length' => $tmp->length, + 'flags' => $flags, + ); + $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; + } +} +?> diff --git a/extlib/MDB2/Driver/Reverse/oci8.php b/extlib/MDB2/Driver/Reverse/oci8.php new file mode 100644 index 0000000000..0ec987cd70 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/oci8.php @@ -0,0 +1,621 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 Oracle driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_oci8 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($owner, $table) = $this->splitTableSchema($table_name); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT column_name name, + data_type "type", + nullable, + data_default "default", + COALESCE(data_precision, data_length) "length", + data_scale "scale" + FROM all_tab_columns + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?) + AND (column_name=? OR column_name=?) + ORDER BY column_id'; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + $field_name, + strtoupper($field_name) + ); + $result = $stmt->execute($args); + if (MDB2::isError($result)) { + return $result; + } + $column = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($column)) { + return $column; + } + $stmt->free(); + $result->free(); + + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $field_name . ' is not a column in table ' . $table_name, __FUNCTION__); + } + + $column = array_change_key_case($column, CASE_LOWER); + 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']); + } + } + $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['nullable']) && $column['nullable'] == 'N') { + $notnull = true; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if ($default === 'NULL') { + $default = null; + } + if ((null === $default) && $notnull) { + $default = ''; + } + } + + $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); + 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; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + if ($type == 'integer') { + $query= "SELECT trigger_body + FROM all_triggers + WHERE table_name=? + AND triggering_event='INSERT' + AND trigger_type='BEFORE EACH ROW'"; + // ^^ pretty reasonable mimic for "auto_increment" in oracle? + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(strtoupper($table)); + if (MDB2::isError($result)) { + return $result; + } + while ($triggerstr = $result->fetchOne()) { + if (preg_match('/.*SELECT\W+(.+)\.nextval +into +\:NEW\.'.$field_name.' +FROM +dual/im', $triggerstr, $matches)) { + $definition[0]['autoincrement'] = $matches[1]; + } + } + $stmt->free(); + $result->free(); + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($owner, $table) = $this->splitTableSchema($table_name); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = "SELECT aic.column_name, + aic.column_position, + aic.descend, + aic.table_owner, + alc.constraint_type + FROM all_ind_columns aic + LEFT JOIN all_constraints alc + ON aic.index_name = alc.constraint_name + AND aic.table_name = alc.table_name + AND aic.table_owner = alc.owner + WHERE (aic.table_name=? OR aic.table_name=?) + AND (aic.index_name=? OR aic.index_name=?) + AND (aic.table_owner=? OR aic.table_owner=?) + ORDER BY column_position"; + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + $indexnames = array_unique(array($db->getIndexName($index_name), $index_name)); + $i = 0; + $row = null; + while ((null === $row) && array_key_exists($i, $indexnames)) { + $args = array( + $table, + strtoupper($table), + $indexnames[$i], + strtoupper($indexnames[$i]), + $owner, + strtoupper($owner) + ); + $result = $stmt->execute($args); + if (MDB2::isError($result)) { + return $result; + } + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($row)) { + return $row; + } + $i++; + } + if (null === $row) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name. ' is not an index on table '. $table_name, __FUNCTION__); + } + if ($row['constraint_type'] == 'U' || $row['constraint_type'] == 'P') { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name. ' is a constraint, not an index on table '. $table_name, __FUNCTION__); + } + + $definition = array(); + while (null !== $row) { + $row = array_change_key_case($row, CASE_LOWER); + $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' => (int)$row['column_position'], + ); + if (!empty($row['descend'])) { + $definition['fields'][$column_name]['sorting'] = + ($row['descend'] == 'ASC' ? 'ascending' : 'descending'); + } + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name. ' is not an index on table '. $table_name, __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($owner, $table) = $this->splitTableSchema($table_name); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT alc.constraint_name, + CASE alc.constraint_type WHEN \'P\' THEN 1 ELSE 0 END "primary", + CASE alc.constraint_type WHEN \'R\' THEN 1 ELSE 0 END "foreign", + CASE alc.constraint_type WHEN \'U\' THEN 1 ELSE 0 END "unique", + CASE alc.constraint_type WHEN \'C\' THEN 1 ELSE 0 END "check", + alc.DELETE_RULE "ondelete", + \'NO ACTION\' "onupdate", + \'SIMPLE\' "match", + CASE alc.deferrable WHEN \'NOT DEFERRABLE\' THEN 0 ELSE 1 END "deferrable", + CASE alc.deferred WHEN \'IMMEDIATE\' THEN 0 ELSE 1 END "initiallydeferred", + alc.search_condition, + alc.table_name, + cols.column_name, + cols.position, + r_alc.table_name "references_table", + r_cols.column_name "references_field", + r_cols.position "references_field_position" + FROM all_cons_columns cols + LEFT JOIN all_constraints alc + ON alc.constraint_name = cols.constraint_name + AND alc.owner = cols.owner + LEFT JOIN all_constraints r_alc + ON alc.r_constraint_name = r_alc.constraint_name + AND alc.r_owner = r_alc.owner + LEFT JOIN all_cons_columns r_cols + ON r_alc.constraint_name = r_cols.constraint_name + AND r_alc.owner = r_cols.owner + AND cols.position = r_cols.position + WHERE (alc.constraint_name=? OR alc.constraint_name=?) + AND alc.constraint_name = cols.constraint_name + AND (alc.owner=? OR alc.owner=?)'; + $tablenames = array(); + if (!empty($table)) { + $query.= ' AND (alc.table_name=? OR alc.table_name=?)'; + $tablenames = array($table, strtoupper($table)); + } + $stmt = $db->prepare($query); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $constraintnames = array_unique(array($db->getIndexName($constraint_name), $constraint_name)); + $c = 0; + $row = null; + while ((null === $row) && array_key_exists($c, $constraintnames)) { + $args = array( + $constraintnames[$c], + strtoupper($constraintnames[$c]), + $owner, + strtoupper($owner) + ); + if (!empty($table)) { + $args = array_merge($args, $tablenames); + } + $result = $stmt->execute($args); + if (MDB2::isError($result)) { + return $result; + } + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($row)) { + return $row; + } + $c++; + } + + $definition = array( + 'primary' => (boolean)$row['primary'], + 'unique' => (boolean)$row['unique'], + 'foreign' => (boolean)$row['foreign'], + 'check' => (boolean)$row['check'], + 'deferrable' => (boolean)$row['deferrable'], + 'initiallydeferred' => (boolean)$row['initiallydeferred'], + 'ondelete' => $row['ondelete'], + 'onupdate' => $row['onupdate'], + 'match' => $row['match'], + ); + + if ($definition['check']) { + // pattern match constraint for check constraint values into enum-style output: + $enumregex = '/'.$row['column_name'].' in \((.+?)\)/i'; + if (preg_match($enumregex, $row['search_condition'], $rangestr)) { + $definition['fields'][$column_name] = array(); + $allowed = explode(',', $rangestr[1]); + foreach ($allowed as $val) { + $val = trim($val); + $val = preg_replace('/^\'/', '', $val); + $val = preg_replace('/\'$/', '', $val); + array_push($definition['fields'][$column_name], $val); + } + } + } + + while (null !== $row) { + $row = array_change_key_case($row, CASE_LOWER); + $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' => (int)$row['position'] + ); + if ($row['foreign']) { + $ref_column_name = $row['references_field']; + $ref_table_name = $row['references_table']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $ref_column_name = strtolower($ref_column_name); + $ref_table_name = strtolower($ref_table_name); + } else { + $ref_column_name = strtoupper($ref_column_name); + $ref_table_name = strtoupper($ref_table_name); + } + } + $definition['references']['table'] = $ref_table_name; + $definition['references']['fields'][$ref_column_name] = array( + 'position' => (int)$row['references_field_position'] + ); + } + $lastrow = $row; + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not a constraint on table '. $table_name, __FUNCTION__); + } + + return $definition; + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the structure of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getSequenceDefinition($sequence) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $sequence_name = $db->getSequenceName($sequence); + $query = 'SELECT last_number FROM user_sequences'; + $query.= ' WHERE sequence_name='.$db->quote($sequence_name, 'text'); + $query.= ' OR sequence_name='.$db->quote(strtoupper($sequence_name), 'text'); + $start = $db->queryOne($query, 'integer'); + if (MDB2::isError($start)) { + return $start; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = 'SELECT trigger_name, + table_name, + trigger_body, + trigger_type, + triggering_event trigger_event, + description trigger_comment, + 1 trigger_enabled, + when_clause + FROM user_triggers + WHERE trigger_name = \''. strtoupper($trigger).'\''; + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + 'when_clause' => 'text', + ); + $result = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($result)) { + return $result; + } + if (!empty($result['trigger_type'])) { + //$result['trigger_type'] = array_shift(explode(' ', $result['trigger_type'])); + $result['trigger_type'] = preg_replace('/(\S+).*/', '\\1', $result['trigger_type']); + } + return $result; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * NOTE: flags won't contain index information. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (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 = @OCINumCols($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $column = array( + 'table' => '', + 'name' => $case_func(@OCIColumnName($resource, $i+1)), + 'type' => @OCIColumnType($resource, $i+1), + 'length' => @OCIColumnSize($resource, $i+1), + 'flags' => '', + ); + $res[$i] = $column; + $res[$i]['mdb2type'] = $db->datatype->mapNativeDatatype($res[$i]); + 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; + } +} +?> diff --git a/extlib/MDB2/Driver/Reverse/odbc.php b/extlib/MDB2/Driver/Reverse/odbc.php new file mode 100644 index 0000000000..6fc3b15f86 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/odbc.php @@ -0,0 +1,631 @@ + + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_odbc 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); + $fldname = $db->quoteIdentifier($field_name, true); + + $res = odbc_columns($db->connection,"%","%",$table); + $column = array(); + while($data = odbc_fetch_array($res)) { + + if(strcasecmp($field_name,$data['COLUMN_NAME'])) { + $column['table_name'] = $data['TABLE_NAME']; + $column['name'] = $data['COLUMN_NAME']; + $column['type'] = $data['TYPE_NAME']; + if($data['IS_NULLABLE'] == "YES") { + $column['is_nullable'] = 1; + } else { + $column['is_nullable'] = 0; + } + $column['column_default'] = $data['COLUMN_DEF']; + $column['length'] = $data['COLUMNZ_SIZE']; + $column['numeric_precision'] = $data['NUM_PREC_RADIX']; + $column['numeric_scale'] = $data['DECIMAL_DIGITS']; + $column['collation_name'] = NULL; + } + } + + /* + $query = "SELECT t.table_name, + c.column_name 'name', + c.data_type 'type', + CASE c.is_nullable WHEN 'YES' THEN 1 ELSE 0 END AS 'is_nullable', + c.column_default, + c.character_maximum_length 'length', + c.numeric_precision, + c.numeric_scale, + c.character_set_name, + c.collation_name + FROM INFORMATION_SCHEMA.TABLES t, + INFORMATION_SCHEMA.COLUMNS c + WHERE t.table_name = c.table_name + AND t.table_name = '$table' + AND c.column_name = '$fldname'"; + if (!empty($schema)) { + $query .= " AND t.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ' ORDER BY t.table_name'; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($column)) { + return $column; + } + */ + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + 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']); + } + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (MDB2::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = true; + if ($column['is_nullable']) { + $notnull = false; + } + $default = false; + if (array_key_exists('column_default', $column)) { + $default = $column['column_default']; + if (is_null($default) && $notnull) { + $default = ''; + } elseif (strlen($default) > 4 + && substr($default, 0, 1) == '(' + && substr($default, -1, 1) == ')' + ) { + //mssql wraps the default value in parentheses: "((1234))", "(NULL)" + $default = trim($default, '()'); + if ($default == 'NULL') { + $default = null; + } + } + } + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + if (!is_null($length)) { + $definition[0]['length'] = $length; + } + if (!is_null($unsigned)) { + $definition[0]['unsigned'] = $unsigned; + } + if (!is_null($fixed)) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $table = $db->quoteIdentifier($table, true); + //$idxname = $db->quoteIdentifier($index_name, true); + + $query = "SELECT OBJECT_NAME(i.id) tablename, + i.name indexname, + c.name field_name, + CASE INDEXKEY_PROPERTY(i.id, i.indid, ik.keyno, 'IsDescending') + WHEN 1 THEN 'DESC' ELSE 'ASC' + END 'collation', + ik.keyno 'position' + FROM sysindexes i + JOIN sysindexkeys ik ON ik.id = i.id AND ik.indid = i.indid + JOIN syscolumns c ON c.id = ik.id AND c.colid = ik.colid + WHERE OBJECT_NAME(i.id) = '$table' + AND i.name = '%s' + AND NOT EXISTS ( + SELECT * + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + WHERE k.table_name = OBJECT_NAME(i.id) + AND k.constraint_name = i.name"; + if (!empty($schema)) { + $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ') + ORDER BY tablename, indexname, ik.keyno'; + + $index_name_mdb2 = $db->getIndexName($index_name); + $result = $db->queryRow(sprintf($query, $index_name_mdb2)); + if (!MDB2::isError($result) && !is_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, $index_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $column_name = $row['field_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' => (int)$row['position'], + ); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC' + ? 'ascending' : 'descending'); + } + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified 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); + + $table = $db->quoteIdentifier($table, true); + $query = "SELECT k.table_name, + k.column_name field_name, + CASE c.constraint_type WHEN 'PRIMARY KEY' THEN 1 ELSE 0 END 'primary', + CASE c.constraint_type WHEN 'UNIQUE' THEN 1 ELSE 0 END 'unique', + CASE c.constraint_type WHEN 'FOREIGN KEY' THEN 1 ELSE 0 END 'foreign', + CASE c.constraint_type WHEN 'CHECK' THEN 1 ELSE 0 END 'check', + CASE c.is_deferrable WHEN 'NO' THEN 0 ELSE 1 END 'deferrable', + CASE c.initially_deferred WHEN 'NO' THEN 0 ELSE 1 END 'initiallydeferred', + rc.match_option 'match', + rc.update_rule 'onupdate', + rc.delete_rule 'ondelete', + kcu.table_name 'references_table', + kcu.column_name 'references_field', + k.ordinal_position 'field_position' + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS c + ON k.table_name = c.table_name + AND k.table_schema = c.table_schema + AND k.table_catalog = c.table_catalog + AND k.constraint_catalog = c.constraint_catalog + AND k.constraint_name = c.constraint_name + LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + ON rc.constraint_schema = c.constraint_schema + AND rc.constraint_catalog = c.constraint_catalog + AND rc.constraint_name = c.constraint_name + LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu + ON rc.unique_constraint_schema = kcu.constraint_schema + AND rc.unique_constraint_catalog = kcu.constraint_catalog + AND rc.unique_constraint_name = kcu.constraint_name + AND k.ordinal_position = kcu.ordinal_position + WHERE k.constraint_catalog = DB_NAME() + AND k.table_name = '$table' + AND k.constraint_name = '%s'"; + if (!empty($schema)) { + $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ' ORDER BY k.constraint_name, + k.ordinal_position'; + + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $result = $db->queryRow(sprintf($query, $constraint_name_mdb2)); + if (!MDB2::isError($result) && !is_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, $constraint_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array( + 'fields' => array() + ); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['field_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' => (int)$row['field_position'] + ); + if ($row['foreign']) { + $ref_column_name = $row['references_field']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $ref_column_name = strtolower($ref_column_name); + } else { + $ref_column_name = strtoupper($ref_column_name); + } + } + $definition['references']['table'] = $row['references_table']; + $definition['references']['fields'][$ref_column_name] = array( + 'position' => (int)$row['field_position'] + ); + } + //collation?!? + /* + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC' + ? 'ascending' : 'descending'); + } + */ + $lastrow = $row; + // otherwise $row is no longer usable on exit from loop + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['primary'] = (boolean)$lastrow['primary']; + $definition['unique'] = (boolean)$lastrow['unique']; + $definition['foreign'] = (boolean)$lastrow['foreign']; + $definition['check'] = (boolean)$lastrow['check']; + $definition['deferrable'] = (boolean)$lastrow['deferrable']; + $definition['initiallydeferred'] = (boolean)$lastrow['initiallydeferred']; + $definition['onupdate'] = $lastrow['onupdate']; + $definition['ondelete'] = $lastrow['ondelete']; + $definition['match'] = $lastrow['match']; + + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT sys1.name trigger_name, + sys2.name table_name, + c.text trigger_body, + c.encrypted is_encripted, + CASE + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsTriggerDisabled') = 1 + THEN 0 ELSE 1 + END trigger_enabled, + CASE + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsertTrigger') = 1 + THEN 'INSERT' + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsUpdateTrigger') = 1 + THEN 'UPDATE' + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsDeleteTrigger') = 1 + THEN 'DELETE' + END trigger_event, + CASE WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsteadOfTrigger') = 1 + THEN 'INSTEAD OF' ELSE 'AFTER' + END trigger_type, + '' trigger_comment + FROM sysobjects sys1 + JOIN sysobjects sys2 ON sys1.parent_obj = sys2.id + JOIN syscomments c ON sys1.id = c.id + WHERE sys1.xtype = 'TR' + AND sys1.name = ". $db->quote($trigger, 'text'); + + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + 'is_encripted' => 'boolean', + ); + + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($def)) { + return $def; + } + $trg_body = $db->queryCol('EXEC sp_helptext '. $db->quote($trigger, 'text'), 'text'); + if (!MDB2::isError($trg_body)) { + $def['trigger_body'] = implode(' ', $trg_body); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db =& $this->getDBInstance(); + if (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 = @mssql_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' => '', + 'name' => $case_func(@mssql_field_name($resource, $i)), + 'type' => @mssql_field_type($resource, $i), + 'length' => @mssql_field_length($resource, $i), + 'flags' => '', + ); + $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; + } + + // }}} + // {{{ _mssql_field_flags() + + /** + * Get a column's flags + * + * Supports "not_null", "primary_key", + * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), + * "unique_key" (mssql unique index, unique check or primary_key) and + * "multiple_key" (multikey index) + * + * mssql timestamp is NOT similar to the mysql timestamp so this is maybe + * not useful at all - is the behaviour of mysql_field_flags that primary + * keys are alway unique? is the interpretation of multiple_key correct? + * + * @param string $table the table name + * @param string $column the field name + * + * @return string the flags + * + * @access protected + * @author Joern Barthel + */ + function _mssql_field_flags($table, $column) + { + $db =& $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + + $flags = array(); + $tableName = $table; + + // get unique and primary keys + $res = $db->queryAll("EXEC SP_HELPINDEX[$table]", null, MDB2_FETCHMODE_ASSOC); + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + $keys = explode(', ', $val['index_keys']); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'primary key')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'primary_key'); + } + } elseif (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + // get auto_increment, not_null and timestamp + $res = $db->queryAll("EXEC SP_COLUMNS[$table]", null, MDB2_FETCHMODE_ASSOC); + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + if ($val['nullable'] == '0') { + $this->_add_flag($flags[$val['column_name']], 'not_null'); + } + if (strpos($val['type_name'], 'identity')) { + $this->_add_flag($flags[$val['column_name']], 'auto_increment'); + } + if (strpos($val['type_name'], 'timestamp')) { + $this->_add_flag($flags[$val['column_name']], 'timestamp'); + } + } + } + + if (!empty($flags[$column])) { + return(implode(' ', $flags[$column])); + } + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array &$array the reference to the flag-array + * @param string $value the flag value + * + * @return void + * + * @access protected + * @author Joern Barthel + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/Reverse/pgsql.php b/extlib/MDB2/Driver/Reverse/pgsql.php new file mode 100644 index 0000000000..fc892d786d --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/pgsql.php @@ -0,0 +1,574 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 PostGreSQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Paul Cooper + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (MDB2::isError($result)) { + return $result; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT a.attname AS name, + t.typname AS type, + CASE a.attlen + WHEN -1 THEN + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod / 65536) + WHEN 'decimal' THEN (a.atttypmod / 65536) + WHEN 'money' THEN (a.atttypmod / 65536) + ELSE CASE a.atttypmod + WHEN -1 THEN NULL + ELSE a.atttypmod - 4 + END + END + ELSE a.attlen + END AS length, + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod % 65536) - 4 + WHEN 'decimal' THEN (a.atttypmod % 65536) - 4 + WHEN 'money' THEN (a.atttypmod % 65536) - 4 + ELSE 0 + END AS scale, + a.attnotnull, + a.atttypmod, + a.atthasdef, + (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) + FROM pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) as default + FROM pg_attribute a, + pg_class c, + pg_type t + WHERE c.relname = ".$db->quote($table, 'text')." + AND a.atttypid = t.oid + AND c.oid = a.attrelid + AND NOT a.attisdropped + AND a.attnum > 0 + AND a.attname = ".$db->quote($field_name, 'text')." + ORDER BY a.attnum"; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($column)) { + return $column; + } + + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + $column = array_change_key_case($column, CASE_LOWER); + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (MDB2::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') { + $notnull = true; + } + $default = null; + if ($column['atthasdef'] === 't' + && strpos($column['default'], 'NULL') !== 0 + && !preg_match("/nextval\('([^']+)'/", $column['default']) + ) { + $pattern = '/^\'(.*)\'::[\w ]+$/i'; + $default = $column['default'];#substr($column['adsrc'], 1, -1); + if ((null === $default) && $notnull) { + $default = ''; + } elseif (!empty($default) && preg_match($pattern, $default)) { + //remove data type cast + $default = preg_replace ($pattern, '\\1', $default); + } + } + $autoincrement = false; + if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) { + $autoincrement = true; + } + $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); + if (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; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = 'SELECT relname, indkey FROM pg_index, pg_class'; + $query.= ' WHERE pg_class.oid = pg_index.indexrelid'; + $query.= " AND indisunique != 't' AND indisprimary != 't'"; + $query.= ' AND pg_class.relname = %s'; + $index_name_mdb2 = $db->getIndexName($index_name); + $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (MDB2::isError($row)) { + return $row; + } + + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $row = array_change_key_case($row, CASE_LOWER); + + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + + $definition = array(); + + $index_column_numbers = explode(' ', $row['indkey']); + + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT c.oid, + c.conname AS constraint_name, + CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\", + CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\", + CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\", + CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\", + CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable, + CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred, + --array_to_string(c.conkey, ' ') AS constraint_key, + t.relname AS table_name, + t2.relname AS references_table, + CASE confupdtype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS onupdate, + CASE confdeltype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS ondelete, + CASE confmatchtype + WHEN 'u' THEN 'UNSPECIFIED' + WHEN 'f' THEN 'FULL' + WHEN 'p' THEN 'PARTIAL' + END AS match, + --array_to_string(c.confkey, ' ') AS fk_constraint_key, + consrc + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_class t2 ON c.confrelid = t2.oid + WHERE c.conname = %s + AND t.relname = " . $db->quote($table, 'text'); + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (MDB2::isError($row)) { + return $row; + } + $uniqueIndex = false; + if (empty($row)) { + // We might be looking for a UNIQUE index that was not created + // as a constraint but should be treated as such. + $query = 'SELECT relname AS constraint_name, + indkey, + 0 AS "check", + 0 AS "foreign", + 0 AS "primary", + 1 AS "unique", + 0 AS deferrable, + 0 AS initiallydeferred, + NULL AS references_table, + NULL AS onupdate, + NULL AS ondelete, + NULL AS match + FROM pg_index, pg_class + WHERE pg_class.oid = pg_index.indexrelid + AND indisunique = \'t\' + AND pg_class.relname = %s'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (MDB2::isError($row)) { + return $row; + } + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + $uniqueIndex = true; + } + + $row = array_change_key_case($row, CASE_LOWER); + + $definition = array( + 'primary' => (boolean)$row['primary'], + 'unique' => (boolean)$row['unique'], + 'foreign' => (boolean)$row['foreign'], + 'check' => (boolean)$row['check'], + 'fields' => array(), + 'references' => array( + 'table' => $row['references_table'], + 'fields' => array(), + ), + 'deferrable' => (boolean)$row['deferrable'], + 'initiallydeferred' => (boolean)$row['initiallydeferred'], + 'onupdate' => $row['onupdate'], + 'ondelete' => $row['ondelete'], + 'match' => $row['match'], + ); + + if ($uniqueIndex) { + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + $index_column_numbers = explode(' ', $row['indkey']); + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($table, 'text'); + $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (MDB2::isError($fields)) { + return $fields; + } + $colpos = 1; + foreach ($fields as $field) { + $definition['fields'][$field] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + + if ($definition['foreign']) { + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.confrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.confkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($definition['references']['table'], 'text'); + $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (MDB2::isError($foreign_fields)) { + return $foreign_fields; + } + $colpos = 1; + foreach ($foreign_fields as $foreign_field) { + $definition['references']['fields'][$foreign_field] = array( + 'position' => $colpos++, + ); + } + } + + if ($definition['check']) { + $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')"); + // ... + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + * + * @TODO: add support for plsql functions and functions with args + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT trg.tgname AS trigger_name, + tbl.relname AS table_name, + CASE + WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();' + ELSE '' + END AS trigger_body, + CASE trg.tgtype & cast(2 as int2) + WHEN 0 THEN 'AFTER' + ELSE 'BEFORE' + END AS trigger_type, + CASE trg.tgtype & cast(28 as int2) + WHEN 16 THEN 'UPDATE' + WHEN 8 THEN 'DELETE' + WHEN 4 THEN 'INSERT' + WHEN 20 THEN 'INSERT, UPDATE' + WHEN 28 THEN 'INSERT, UPDATE, DELETE' + WHEN 24 THEN 'UPDATE, DELETE' + WHEN 12 THEN 'INSERT, DELETE' + END AS trigger_event, + CASE trg.tgenabled + WHEN 'O' THEN 't' + ELSE trg.tgenabled + END AS trigger_enabled, + obj_description(trg.oid, 'pg_trigger') AS trigger_comment + FROM pg_trigger trg, + pg_class tbl, + pg_proc p + WHERE trg.tgrelid = tbl.oid + AND trg.tgfoid = p.oid + AND trg.tgname = ". $db->quote($trigger, 'text'); + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (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 = @pg_num_fields($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '', + 'name' => $case_func(@pg_field_name($resource, $i)), + 'type' => @pg_field_type($resource, $i), + 'length' => @pg_field_size($resource, $i), + 'flags' => '', + ); + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (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; + } +} +?> diff --git a/extlib/MDB2/Driver/Reverse/sqlite.php b/extlib/MDB2/Driver/Reverse/sqlite.php new file mode 100644 index 0000000000..e218ce89f6 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/sqlite.php @@ -0,0 +1,611 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 SQlite driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_sqlite extends MDB2_Driver_Reverse_Common +{ + /** + * Remove SQL comments from the field definition + * + * @access private + */ + function _removeComments($sql) { + $lines = explode("\n", $sql); + foreach ($lines as $k => $line) { + $pieces = explode('--', $line); + if (count($pieces) > 1 && (substr_count($pieces[0], '\'') % 2) == 0) { + $lines[$k] = substr($line, 0, strpos($line, '--')); + } + } + return implode("\n", $lines); + } + + /** + * + */ + function _getTableColumns($sql) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_def = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + // replace the decimal length-places-separator with a colon + $column_def = preg_replace('/(\d),(\d)/', '\1:\2', $column_def); + $column_def = $this->_removeComments($column_def); + $column_sql = explode(',', $column_def); + $columns = array(); + $count = count($column_sql); + if ($count == 0) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected empty table column definition list', __FUNCTION__); + } + $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|TINYINT|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; + $regexp2 = '/^\s*([^ ]+) +(PRIMARY|UNIQUE|CHECK)$/i'; + for ($i=0, $j=0; $i<$count; ++$i) { + if (!preg_match($regexp, trim($column_sql[$i]), $matches)) { + if (!preg_match($regexp2, trim($column_sql[$i]))) { + continue; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected table column SQL definition: "'.$column_sql[$i].'"', __FUNCTION__); + } + $columns[$j]['name'] = trim($matches[1], implode('', $db->identifier_quoting)); + $columns[$j]['type'] = strtolower($matches[2]); + if (isset($matches[4]) && strlen($matches[4])) { + $columns[$j]['length'] = $matches[4]; + } + if (isset($matches[6]) && strlen($matches[6])) { + $columns[$j]['decimal'] = $matches[6]; + } + if (isset($matches[8]) && strlen($matches[8])) { + $columns[$j]['unsigned'] = true; + } + if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['autoincrement'] = true; + } + if (isset($matches[12]) && strlen($matches[12])) { + $default = $matches[12]; + if (strlen($default) && $default[0]=="'") { + $default = str_replace("''", "'", substr($default, 1, strlen($default)-2)); + } + if ($default === 'NULL') { + $default = null; + } + $columns[$j]['default'] = $default; + } else { + $columns[$j]['default'] = null; + } + if (isset($matches[7]) && strlen($matches[7])) { + $columns[$j]['notnull'] = ($matches[7] === ' NOT NULL'); + } else if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['notnull'] = ($matches[9] === ' NOT NULL'); + } else if (isset($matches[13]) && strlen($matches[13])) { + $columns[$j]['notnull'] = ($matches[13] === ' NOT NULL'); + } + ++$j; + } + return $columns; + } + + // {{{ getTableFieldDefinition() + + /** + * Get the stucture 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. + * The returned array contains an array for each field definition, + * with (some of) these indices: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $result = $db->loadModule('Datatype', null, true); + if (MDB2::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (MDB2::isError($sql)) { + return $sql; + } + $columns = $this->_getTableColumns($sql); + foreach ($columns as $column) { + 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['notnull'])) { + $notnull = $column['notnull']; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if ((null === $default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['autoincrement'])) { + $autoincrement = true; + } + + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + 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; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the stucture 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); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $index_name_mdb2 = $db->getIndexName($index_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (MDB2::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (MDB2::isError($sql)) { + return $sql; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $definition = array(); + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i], ' '); + $collation = strtok(' '); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the stucture 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); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (MDB2::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (MDB2::isError($sql)) { + return $sql; + } + //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, + ); + if (!$sql) { + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $query.= " AND sql NOT NULL ORDER BY name"; + $sql = $db->queryOne($query, 'text'); + if (MDB2::isError($sql)) { + return $sql; + } + if ($constraint_name == 'primary') { + // search in table definition for PRIMARY KEYs + if (preg_match("/\bPRIMARY\s+KEY\b\s*\(([^)]+)/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + if (preg_match("/\"([^\"]+)\"[^\,\"]+\bPRIMARY\s+KEY\b[^\,\)]*/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + } else { + // search in table definition for FOREIGN KEYs + $pattern = "/\bCONSTRAINT\b\s+%s\s+ + \bFOREIGN\s+KEY\b\s*\(([^\)]+)\)\s* + \bREFERENCES\b\s+([^\s]+)\s*\(([^\)]+)\)\s* + (?:\bMATCH\s*([^\s]+))?\s* + (?:\bON\s+UPDATE\s+([^\s,\)]+))?\s* + (?:\bON\s+DELETE\s+([^\s,\)]+))?\s* + /imsx"; + $found_fk = false; + if (preg_match(sprintf($pattern, $constraint_name_mdb2), $sql, $tmp)) { + $found_fk = true; + } elseif (preg_match(sprintf($pattern, $constraint_name), $sql, $tmp)) { + $found_fk = true; + } + if ($found_fk) { + $definition['foreign'] = true; + $definition['match'] = 'SIMPLE'; + $definition['onupdate'] = 'NO ACTION'; + $definition['ondelete'] = 'NO ACTION'; + $definition['references']['table'] = $tmp[2]; + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $referenced_cols = explode(',', $tmp[3]); + $colpos = 1; + foreach ($referenced_cols as $column_name) { + $definition['references']['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + if (isset($tmp[4])) { + $definition['match'] = $tmp[4]; + } + if (isset($tmp[5])) { + $definition['onupdate'] = $tmp[5]; + } + if (isset($tmp[6])) { + $definition['ondelete'] = $tmp[6]; + } + return $definition; + } + } + $sql = false; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (!preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['unique'] = true; + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i]," "); + $collation = strtok(" "); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name as trigger_name, + tbl_name AS table_name, + sql AS trigger_body, + NULL AS trigger_type, + NULL AS trigger_event, + NULL AS trigger_comment, + 1 AS trigger_enabled + FROM sqlite_master + WHERE type='trigger'"; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(name)='.$db->quote(strtolower($trigger), 'text'); + } else { + $query.= ' AND name='.$db->quote($trigger, 'text'); + } + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($def)) { + return $def; + } + if (empty($def)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing trigger', __FUNCTION__); + } + if (preg_match("/^create\s+(?:temp|temporary)?trigger\s+(?:if\s+not\s+exists\s+)?.*(before|after)?\s+(insert|update|delete)/Uims", $def['trigger_body'], $tmp)) { + $def['trigger_type'] = strtoupper($tmp[1]); + $def['trigger_event'] = strtoupper($tmp[2]); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table + * + * @param string $result a string containing the name of a table + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, + 'This DBMS can not obtain tableInfo from result sets', __FUNCTION__); + } +} + +?> diff --git a/extlib/MDB2/Driver/Reverse/sqlite3.php b/extlib/MDB2/Driver/Reverse/sqlite3.php new file mode 100644 index 0000000000..532e8d6837 --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/sqlite3.php @@ -0,0 +1,611 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 SQlite driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_sqlite3 extends MDB2_Driver_Reverse_Common +{ + /** + * Remove SQL comments from the field definition + * + * @access private + */ + function _removeComments($sql) { + $lines = explode("\n", $sql); + foreach ($lines as $k => $line) { + $pieces = explode('--', $line); + if (count($pieces) > 1 && (substr_count($pieces[0], '\'') % 2) == 0) { + $lines[$k] = substr($line, 0, strpos($line, '--')); + } + } + return implode("\n", $lines); + } + + /** + * + */ + function _getTableColumns($sql) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_def = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + // replace the decimal length-places-separator with a colon + $column_def = preg_replace('/(\d),(\d)/', '\1:\2', $column_def); + $column_def = $this->_removeComments($column_def); + $column_sql = explode(',', $column_def); + $columns = array(); + $count = count($column_sql); + if ($count == 0) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected empty table column definition list', __FUNCTION__); + } + $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|TINYINT|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; + $regexp2 = '/^\s*([^ ]+) +(PRIMARY|UNIQUE|CHECK)$/i'; + for ($i=0, $j=0; $i<$count; ++$i) { + if (!preg_match($regexp, trim($column_sql[$i]), $matches)) { + if (!preg_match($regexp2, trim($column_sql[$i]))) { + continue; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected table column SQL definition: "'.$column_sql[$i].'"', __FUNCTION__); + } + $columns[$j]['name'] = trim($matches[1], implode('', $db->identifier_quoting)); + $columns[$j]['type'] = strtolower($matches[2]); + if (isset($matches[4]) && strlen($matches[4])) { + $columns[$j]['length'] = $matches[4]; + } + if (isset($matches[6]) && strlen($matches[6])) { + $columns[$j]['decimal'] = $matches[6]; + } + if (isset($matches[8]) && strlen($matches[8])) { + $columns[$j]['unsigned'] = true; + } + if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['autoincrement'] = true; + } + if (isset($matches[12]) && strlen($matches[12])) { + $default = $matches[12]; + if (strlen($default) && $default[0]=="'") { + $default = str_replace("''", "'", substr($default, 1, strlen($default)-2)); + } + if ($default === 'NULL') { + $default = null; + } + $columns[$j]['default'] = $default; + } else { + $columns[$j]['default'] = null; + } + if (isset($matches[7]) && strlen($matches[7])) { + $columns[$j]['notnull'] = ($matches[7] === ' NOT NULL'); + } else if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['notnull'] = ($matches[9] === ' NOT NULL'); + } else if (isset($matches[13]) && strlen($matches[13])) { + $columns[$j]['notnull'] = ($matches[13] === ' NOT NULL'); + } + ++$j; + } + return $columns; + } + + // {{{ getTableFieldDefinition() + + /** + * Get the stucture 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. + * The returned array contains an array for each field definition, + * with (some of) these indices: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $result = $db->loadModule('Datatype', null, true); + if (MDB2::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (MDB2::isError($sql)) { + return $sql; + } + $columns = $this->_getTableColumns($sql); + foreach ($columns as $column) { + 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['notnull'])) { + $notnull = $column['notnull']; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if ((null === $default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['autoincrement'])) { + $autoincrement = true; + } + + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + 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; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the stucture 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); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $index_name_mdb2 = $db->getIndexName($index_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (MDB2::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (MDB2::isError($sql)) { + return $sql; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $definition = array(); + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i], ' '); + $collation = strtok(' '); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the stucture 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); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (MDB2::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (MDB2::isError($sql)) { + return $sql; + } + //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, + ); + if (!$sql) { + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $query.= " AND sql NOT NULL ORDER BY name"; + $sql = $db->queryOne($query, 'text'); + if (MDB2::isError($sql)) { + return $sql; + } + if ($constraint_name == 'primary') { + // search in table definition for PRIMARY KEYs + if (preg_match("/\bPRIMARY\s+KEY\b\s*\(([^)]+)/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + if (preg_match("/\"([^\"]+)\"[^\,\"]+\bPRIMARY\s+KEY\b[^\,\)]*/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + } else { + // search in table definition for FOREIGN KEYs + $pattern = "/\bCONSTRAINT\b\s+%s\s+ + \bFOREIGN\s+KEY\b\s*\(([^\)]+)\)\s* + \bREFERENCES\b\s+([^\s]+)\s*\(([^\)]+)\)\s* + (?:\bMATCH\s*([^\s]+))?\s* + (?:\bON\s+UPDATE\s+([^\s,\)]+))?\s* + (?:\bON\s+DELETE\s+([^\s,\)]+))?\s* + /imsx"; + $found_fk = false; + if (preg_match(sprintf($pattern, $constraint_name_mdb2), $sql, $tmp)) { + $found_fk = true; + } elseif (preg_match(sprintf($pattern, $constraint_name), $sql, $tmp)) { + $found_fk = true; + } + if ($found_fk) { + $definition['foreign'] = true; + $definition['match'] = 'SIMPLE'; + $definition['onupdate'] = 'NO ACTION'; + $definition['ondelete'] = 'NO ACTION'; + $definition['references']['table'] = $tmp[2]; + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $referenced_cols = explode(',', $tmp[3]); + $colpos = 1; + foreach ($referenced_cols as $column_name) { + $definition['references']['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + if (isset($tmp[4])) { + $definition['match'] = $tmp[4]; + } + if (isset($tmp[5])) { + $definition['onupdate'] = $tmp[5]; + } + if (isset($tmp[6])) { + $definition['ondelete'] = $tmp[6]; + } + return $definition; + } + } + $sql = false; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (!preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['unique'] = true; + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i]," "); + $collation = strtok(" "); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT name as trigger_name, + tbl_name AS table_name, + sql AS trigger_body, + NULL AS trigger_type, + NULL AS trigger_event, + NULL AS trigger_comment, + 1 AS trigger_enabled + FROM sqlite_master + WHERE type='trigger'"; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(name)='.$db->quote(strtolower($trigger), 'text'); + } else { + $query.= ' AND name='.$db->quote($trigger, 'text'); + } + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($def)) { + return $def; + } + if (empty($def)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing trigger', __FUNCTION__); + } + if (preg_match("/^create\s+(?:temp|temporary)?trigger\s+(?:if\s+not\s+exists\s+)?.*(before|after)?\s+(insert|update|delete)/Uims", $def['trigger_body'], $tmp)) { + $def['trigger_type'] = strtoupper($tmp[1]); + $def['trigger_event'] = strtoupper($tmp[2]); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table + * + * @param string $result a string containing the name of a table + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, + 'This DBMS can not obtain tableInfo from result sets', __FUNCTION__); + } +} + +?> diff --git a/extlib/MDB2/Driver/Reverse/sqlsrv.php b/extlib/MDB2/Driver/Reverse/sqlsrv.php new file mode 100644 index 0000000000..1a7bfe139a --- /dev/null +++ b/extlib/MDB2/Driver/Reverse/sqlsrv.php @@ -0,0 +1,653 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MSSQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_sqlsrv 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); + $fldname = $db->quoteIdentifier($field_name, true); + + $query = "SELECT t.table_name, + c.column_name 'name', + c.data_type 'type', + CASE c.is_nullable WHEN 'YES' THEN 1 ELSE 0 END AS 'is_nullable', + c.column_default, + c.character_maximum_length 'length', + c.numeric_precision, + c.numeric_scale, + c.character_set_name, + c.collation_name + FROM INFORMATION_SCHEMA.TABLES t, + INFORMATION_SCHEMA.COLUMNS c + WHERE t.table_name = c.table_name + AND t.table_name = '$table' + AND c.column_name = '$fldname'"; + if (!empty($schema)) { + $query .= " AND t.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ' ORDER BY t.table_name'; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($column)) { + return $column; + } + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + 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']); + } + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (MDB2::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = true; + if ($column['is_nullable']) { + $notnull = false; + } + $default = false; + if (array_key_exists('column_default', $column)) { + $default = $column['column_default']; + if ((null === $default) && $notnull) { + $default = ''; + } elseif (strlen($default) > 4 + && substr($default, 0, 1) == '(' + && substr($default, -1, 1) == ')' + ) { + //mssql wraps the default value in parentheses: "((1234))", "(NULL)" + $default = trim($default, '()'); + if ($default == 'NULL') { + $default = null; + } + } + } + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + 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; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $table = $db->quoteIdentifier($table, true); + //$idxname = $db->quoteIdentifier($index_name, true); + + $query = "SELECT OBJECT_NAME(i.id) tablename, + i.name indexname, + c.name field_name, + CASE INDEXKEY_PROPERTY(i.id, i.indid, ik.keyno, 'IsDescending') + WHEN 1 THEN 'DESC' ELSE 'ASC' + END 'collation', + ik.keyno 'position' + FROM sysindexes i + JOIN sysindexkeys ik ON ik.id = i.id AND ik.indid = i.indid + JOIN syscolumns c ON c.id = ik.id AND c.colid = ik.colid + WHERE OBJECT_NAME(i.id) = '$table' + AND i.name = '%s' + AND NOT EXISTS ( + SELECT * + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + WHERE k.table_name = OBJECT_NAME(i.id) + AND k.constraint_name = i.name"; + if (!empty($schema)) { + $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ') + ORDER BY tablename, indexname, ik.keyno'; + + $index_name_mdb2 = $db->getIndexName($index_name); + $result = $db->queryRow(sprintf($query, $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, $index_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $column_name = $row['field_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' => (int)$row['position'], + ); + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC' + ? 'ascending' : 'descending'); + } + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified 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); + + $table = $db->quoteIdentifier($table, true); + $query = "SELECT k.table_name, + k.column_name field_name, + CASE c.constraint_type WHEN 'PRIMARY KEY' THEN 1 ELSE 0 END 'primary', + CASE c.constraint_type WHEN 'UNIQUE' THEN 1 ELSE 0 END 'unique', + CASE c.constraint_type WHEN 'FOREIGN KEY' THEN 1 ELSE 0 END 'foreign', + CASE c.constraint_type WHEN 'CHECK' THEN 1 ELSE 0 END 'check', + CASE c.is_deferrable WHEN 'NO' THEN 0 ELSE 1 END 'deferrable', + CASE c.initially_deferred WHEN 'NO' THEN 0 ELSE 1 END 'initiallydeferred', + rc.match_option 'match', + rc.update_rule 'onupdate', + rc.delete_rule 'ondelete', + kcu.table_name 'references_table', + kcu.column_name 'references_field', + k.ordinal_position 'field_position' + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k + LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS c + ON k.table_name = c.table_name + AND k.table_schema = c.table_schema + AND k.table_catalog = c.table_catalog + AND k.constraint_catalog = c.constraint_catalog + AND k.constraint_name = c.constraint_name + LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + ON rc.constraint_schema = c.constraint_schema + AND rc.constraint_catalog = c.constraint_catalog + AND rc.constraint_name = c.constraint_name + LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu + ON rc.unique_constraint_schema = kcu.constraint_schema + AND rc.unique_constraint_catalog = kcu.constraint_catalog + AND rc.unique_constraint_name = kcu.constraint_name + AND k.ordinal_position = kcu.ordinal_position + WHERE k.constraint_catalog = DB_NAME() + AND k.table_name = '$table' + AND k.constraint_name = '%s'"; + if (!empty($schema)) { + $query .= " AND k.table_schema = '" .$db->quoteIdentifier($schema, true) ."'"; + } + $query .= ' ORDER BY k.constraint_name, + k.ordinal_position'; + + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $result = $db->queryRow(sprintf($query, $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, $constraint_name)); + if (MDB2::isError($result)) { + return $result; + } + + $definition = array( + 'fields' => array() + ); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['field_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' => (int)$row['field_position'] + ); + if ($row['foreign']) { + $ref_column_name = $row['references_field']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $ref_column_name = strtolower($ref_column_name); + } else { + $ref_column_name = strtoupper($ref_column_name); + } + } + $definition['references']['table'] = $row['references_table']; + $definition['references']['fields'][$ref_column_name] = array( + 'position' => (int)$row['field_position'] + ); + } + //collation?!? + /* + if (!empty($row['collation'])) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'ASC' + ? 'ascending' : 'descending'); + } + */ + $lastrow = $row; + // otherwise $row is no longer usable on exit from loop + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['primary'] = (boolean)$lastrow['primary']; + $definition['unique'] = (boolean)$lastrow['unique']; + $definition['foreign'] = (boolean)$lastrow['foreign']; + $definition['check'] = (boolean)$lastrow['check']; + $definition['deferrable'] = (boolean)$lastrow['deferrable']; + $definition['initiallydeferred'] = (boolean)$lastrow['initiallydeferred']; + $definition['onupdate'] = $lastrow['onupdate']; + $definition['ondelete'] = $lastrow['ondelete']; + $definition['match'] = $lastrow['match']; + + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $query = "SELECT sys1.name trigger_name, + sys2.name table_name, + c.text trigger_body, + c.encrypted is_encripted, + CASE + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsTriggerDisabled') = 1 + THEN 0 ELSE 1 + END trigger_enabled, + CASE + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsertTrigger') = 1 + THEN 'INSERT' + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsUpdateTrigger') = 1 + THEN 'UPDATE' + WHEN OBJECTPROPERTY(sys1.id, 'ExecIsDeleteTrigger') = 1 + THEN 'DELETE' + END trigger_event, + CASE WHEN OBJECTPROPERTY(sys1.id, 'ExecIsInsteadOfTrigger') = 1 + THEN 'INSTEAD OF' ELSE 'AFTER' + END trigger_type, + '' trigger_comment + FROM sysobjects sys1 + JOIN sysobjects sys2 ON sys1.parent_obj = sys2.id + JOIN syscomments c ON sys1.id = c.id + WHERE sys1.xtype = 'TR' + AND sys1.name = ". $db->quote($trigger, 'text'); + + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + 'is_encripted' => 'boolean', + ); + + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (MDB2::isError($def)) { + return $def; + } + $trg_body = $db->queryCol('EXEC sp_helptext '. $db->quote($trigger, 'text'), 'text'); + if (!MDB2::isError($trg_body)) { + $def['trigger_body'] = implode(' ', $trg_body); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (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'; + } + + $meta = @sqlsrv_field_metadata($resource); + $count = count($meta); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => '', + 'name' => $case_func($meta[$i]['Name']), + 'type' => $meta[$i]['Type'], + 'length' => $meta[$i]['Size'], + 'numeric_precision' => $meta[$i]['Precision'], + 'numeric_scale' => $meta[$i]['Scale'], + 'flags' => '' + ); + $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; + } + + // }}} + // {{{ _mssql_field_flags() + + /** + * Get a column's flags + * + * Supports "not_null", "primary_key", + * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), + * "unique_key" (mssql unique index, unique check or primary_key) and + * "multiple_key" (multikey index) + * + * mssql timestamp is NOT similar to the mysql timestamp so this is maybe + * not useful at all - is the behaviour of mysql_field_flags that primary + * keys are alway unique? is the interpretation of multiple_key correct? + * + * @param string $table the table name + * @param string $column the field name + * + * @return string the flags + * + * @access protected + * @author Joern Barthel + */ + function _mssql_field_flags($table, $column) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + + $flags = array(); + $tableName = $table; + + // get unique and primary keys + $res = $db->queryAll("EXEC SP_HELPINDEX[$table]", null, MDB2_FETCHMODE_ASSOC); + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + $keys = explode(', ', $val['index_keys']); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'primary key')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'primary_key'); + } + } elseif (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + // get auto_increment, not_null and timestamp + $res = $db->queryAll("EXEC SP_COLUMNS[$table]", null, MDB2_FETCHMODE_ASSOC); + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + if ($val['nullable'] == '0') { + $this->_add_flag($flags[$val['column_name']], 'not_null'); + } + if (strpos($val['type_name'], 'identity')) { + $this->_add_flag($flags[$val['column_name']], 'auto_increment'); + } + if (strpos($val['type_name'], 'timestamp')) { + $this->_add_flag($flags[$val['column_name']], 'timestamp'); + } + } + } + + if (!empty($flags[$column])) { + return(implode(' ', $flags[$column])); + } + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array &$array the reference to the flag-array + * @param string $value the flag value + * + * @return void + * + * @access protected + * @author Joern Barthel + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} +} +?> diff --git a/extlib/MDB2/Driver/fbsql.php b/extlib/MDB2/Driver/fbsql.php new file mode 100644 index 0000000000..bc3c18d8f4 --- /dev/null +++ b/extlib/MDB2/Driver/fbsql.php @@ -0,0 +1,914 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * MDB2 FrontBase driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Frank M. Kromann + */ +class MDB2_Driver_fbsql extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'fbsql'; + $this->dbsyntax = 'fbsql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = 'emulated'; + $this->supported['LOBs'] = true; + $this->supported['replace'] ='emulated'; + $this->supported['sub_selects'] = true; + $this->supported['auto_increment'] = false; // not implemented + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = false; + $this->supported['new_link'] = false; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + } + + // }}} + // {{{ 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 = @fbsql_errno($this->connection); + $native_msg = @fbsql_error($this->connection); + } else { + $native_code = @fbsql_errno(); + $native_msg = @fbsql_error(); + } + if (null === $error) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 22 => MDB2_ERROR_SYNTAX, + 85 => MDB2_ERROR_ALREADY_EXISTS, + 108 => MDB2_ERROR_SYNTAX, + 116 => MDB2_ERROR_NOSUCHTABLE, + 124 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 215 => MDB2_ERROR_NOSUCHFIELD, + 217 => MDB2_ERROR_INVALID_NUMBER, + 226 => MDB2_ERROR_NOSUCHFIELD, + 231 => MDB2_ERROR_INVALID, + 239 => MDB2_ERROR_TRUNCATED, + 251 => MDB2_ERROR_SYNTAX, + 266 => MDB2_ERROR_NOT_FOUND, + 357 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 358 => MDB2_ERROR_CONSTRAINT, + 360 => MDB2_ERROR_CONSTRAINT, + 361 => MDB2_ERROR_CONSTRAINT, + ); + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ 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)); + if (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if ($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'); + } + $result =& $this->_doQuery('SET COMMIT FALSE;', 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 (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $result =& $this->_doQuery('COMMIT;', true); + if (MDB2::isError($result)) { + return $result; + } + $result =& $this->_doQuery('SET COMMIT TRUE;', true); + if (MDB2::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ + + /** + * 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 (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $result =& $this->_doQuery('ROLLBACK;', true); + if (MDB2::isError($result)) { + return $result; + } + $result =& $this->_doQuery('SET COMMIT TRUE;', true); + if (MDB2::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ _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( + $this->dsn['hostspec'] ? $this->dsn['hostspec'] : 'localhost', + $username ? $username : null, + $password ? $password : null, + ); + + $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect'; + + $connection = @call_user_func_array($connect_function, $params); + if ($connection <= 0) { + 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)) { + return $result; + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + **/ + 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 (!@fbsql_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; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ 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 = @fbsql_select_db($name, $connection); + @fbsql_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) { + @fbsql_close($this->connection); + } + } + 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); + } + + @fbsql_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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + if (null === $database_name) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@fbsql_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; + } + } + + $result = @fbsql_query($query, $connection); + if (!$result) { + $err =& $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @fbsql_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 ($limit > 0) { + if ($is_manip) { + return preg_replace('/^([\s(])*SELECT(?!\s*TOP\s*\()/i', + "\\1SELECT TOP($limit)", $query); + } else { + return preg_replace('/([\s(])*SELECT(?!\s*TOP\s*\()/i', + "\\1SELECT TOP($offset,$limit)", $query); + } + } + // Add ; to the end of the query. This is required by FrontBase + return $query.';'; + } + + // }}} + // {{{ 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) + { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $value = @fbsql_insert_id($connection); + if (!$value) { + return $this->raiseError(null, null, null, + 'Could not get last insert ID', __FUNCTION__); + } + return $value; + } + + // }}} + // {{{ 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 FrontbaseSQL result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_fbsql 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 (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 = @fbsql_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 = @fbsql_fetch_row($this->result); + } + if (!$row) { + if (false === $this->result) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (($fetchmode != MDB2_FETCHMODE_ASSOC) && !empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } elseif (($fetchmode == MDB2_FETCHMODE_ASSOC) && !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 { + $row = new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (MDB2::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @fbsql_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 = @fbsql_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return false; + } + return @fbsql_next_result($this->result); + } + + // }}} + // {{{ 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 = @fbsql_free_result($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 FrontbaseSQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +class MDB2_BufferedResult_fbsql extends MDB2_Result_fbsql +{ + // }}} + // {{{ 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) && !@fbsql_data_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (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 = @fbsql_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 FrontbaseSQL statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +class MDB2_Statement_fbsql extends MDB2_Statement_Common +{ + +} + +?> diff --git a/extlib/MDB2/Driver/ibase.php b/extlib/MDB2/Driver/ibase.php new file mode 100644 index 0000000000..6b0f52ba76 --- /dev/null +++ b/extlib/MDB2/Driver/ibase.php @@ -0,0 +1,1575 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * MDB2 FireBird/InterBase driver + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_ibase extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => false); + + var $transaction_id = 0; + + var $query_parameters = array(); + + var $query_parameter_values = array(); + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'ibase'; + $this->dbsyntax = 'ibase'; + + $this->supported['sequences'] = true; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = function_exists('ibase_affected_rows'); + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = true; + $this->supported['current_id'] = true; + $this->supported['limit_queries'] = 'emulated'; + $this->supported['LOBs'] = true; + $this->supported['replace'] = false; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = true; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = false; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['database_path'] = ''; + $this->options['database_extension'] = '.gdb'; + $this->options['server_version'] = ''; + $this->options['max_identifiers_length'] = 31; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + $native_msg = @ibase_errmsg(); + + if (function_exists('ibase_errcode')) { + $native_code = @ibase_errcode(); + } else { + // memo for the interbase php module hackers: we need something similar + // to mysql_errno() to retrieve error codes instead of this ugly hack + if (preg_match('/^([^0-9\-]+)([0-9\-]+)\s+(.*)$/', $native_msg, $m)) { + $native_code = (int)$m[2]; + } else { + $native_code = null; + } + } + if (null === $error) { + $error = MDB2_ERROR; + if ($native_code) { + // try to interpret Interbase error code (that's why we need ibase_errno() + // in the interbase module to return the real error code) + switch ($native_code) { + case -204: + if (isset($m[3]) && is_int(strpos($m[3], 'Table unknown'))) { + $errno = MDB2_ERROR_NOSUCHTABLE; + } + break; + default: + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + -104 => MDB2_ERROR_SYNTAX, + -150 => MDB2_ERROR_ACCESS_VIOLATION, + -151 => MDB2_ERROR_ACCESS_VIOLATION, + -155 => MDB2_ERROR_NOSUCHTABLE, + -157 => MDB2_ERROR_NOSUCHFIELD, + -158 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + -170 => MDB2_ERROR_MISMATCH, + -171 => MDB2_ERROR_MISMATCH, + -172 => MDB2_ERROR_INVALID, + // -204 => // Covers too many errors, need to use regex on msg + -205 => MDB2_ERROR_NOSUCHFIELD, + -206 => MDB2_ERROR_NOSUCHFIELD, + -208 => MDB2_ERROR_INVALID, + -219 => MDB2_ERROR_NOSUCHTABLE, + -297 => MDB2_ERROR_CONSTRAINT, + -303 => MDB2_ERROR_INVALID, + -413 => MDB2_ERROR_INVALID_NUMBER, + -530 => MDB2_ERROR_CONSTRAINT, + -551 => MDB2_ERROR_ACCESS_VIOLATION, + -552 => MDB2_ERROR_ACCESS_VIOLATION, + // -607 => // Covers too many errors, need to use regex on msg + -625 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + -803 => MDB2_ERROR_CONSTRAINT, + -804 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + // -902 => // Covers too many errors, need to use regex on msg + -904 => MDB2_ERROR_CONNECT_FAILED, + -922 => MDB2_ERROR_NOSUCHDB, + -923 => MDB2_ERROR_CONNECT_FAILED, + -924 => MDB2_ERROR_CONNECT_FAILED + ); + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + break; + } + } else { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/generator .* is not defined/' + => MDB2_ERROR_SYNTAX, // for compat. w ibase_errcode() + '/table.*(not exist|not found|unknown)/i' + => MDB2_ERROR_NOSUCHTABLE, + '/table .* already exists/i' + => MDB2_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* failed attempt to store duplicate value/i' + => MDB2_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* not found/i' + => MDB2_ERROR_NOT_FOUND, + '/validation error for column .* value "\*\*\* null/i' + => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/violation of [\w ]+ constraint/i' + => MDB2_ERROR_CONSTRAINT, + '/conversion error from string/i' + => MDB2_ERROR_INVALID_NUMBER, + '/no permission for/i' + => MDB2_ERROR_ACCESS_VIOLATION, + '/arithmetic exception, numeric overflow, or string truncation/i' + => MDB2_ERROR_INVALID, + '/feature is not supported/i' + => MDB2_ERROR_NOT_CAPABLE, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg, $m)) { + $error = $code; + break; + } + } + } + } + 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) + { + //Remove a NULL-character (may break queries when inserted): + $text = str_replace("\x00", '', $text); + + return parent::escape($text, $escape_wildcards); + } + + // }}} + // }}} + // {{{ quoteIdentifier() + + /** + * Delimited identifiers are a nightmare with InterBase, so they're disabled + * + * @param string $str identifier name to be quoted + * @param bool $check_option check the 'quote_identifier' option + * + * @return string quoted identifier string + * + * @access public + */ + function quoteIdentifier($str, $check_option = false) + { + if ($check_option && !$this->options['quote_identifier']) { + return $str; + } + + return parent::quoteIdentifier(strtoupper($str), $check_option); + } + + // }}} + // {{{ getConnection() + + /** + * Returns a native connection + * + * @return mixed a valid MDB2 connection object, + * or a MDB2 error object on error + * @access public + */ + function getConnection() + { + $result = $this->connect(); + if (MDB2::isError($result)) { + return $result; + } + if ($this->in_transaction) { + return $this->transaction_id; + } + return $this->connection; + } + + // }}} + // {{{ 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)); + if (null !== $savepoint) { + 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); + } + if ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $result = @ibase_trans(IBASE_DEFAULT, $connection); + if (!$result) { + return $this->raiseError(null, null, null, + 'could not start a transaction', __FUNCTION__); + } + $this->transaction_id = $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 (null !== $savepoint) { + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if (!@ibase_commit($this->transaction_id)) { + return $this->raiseError(null, null, null, + 'could not commit a transaction', __FUNCTION__); + } + $this->in_transaction = false; + $this->transaction_id = 0; + 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 (null !== $savepoint) { + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if ($this->transaction_id && !@ibase_rollback($this->transaction_id)) { + return $this->raiseError(null, null, null, + 'Could not rollback a pending transaction: '.@ibase_errmsg(), __FUNCTION__); + } + $this->in_transaction = false; + $this->transaction_id = 0; + return MDB2_OK; + } + + // }}} + // {{{ setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level (SQL-92) + * 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)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $ibase_isolation = 'READ COMMITTED RECORD_VERSION'; + break; + case 'READ COMMITTED': + $ibase_isolation = 'READ COMMITTED NO RECORD_VERSION'; + break; + case 'REPEATABLE READ': + $ibase_isolation = 'SNAPSHOT'; + break; + case 'SERIALIZABLE': + $ibase_isolation = 'SNAPSHOT TABLE STABILITY'; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + if (!empty($options['wait'])) { + switch ($options['wait']) { + case 'WAIT': + case 'NO WAIT': + $wait = $options['wait']; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'wait option is not supported: '.$options['wait'], __FUNCTION__); + } + } + + if (!empty($options['rw'])) { + switch ($options['rw']) { + case 'READ ONLY': + case 'READ WRITE': + $rw = $options['rw']; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'rw option is not supported: '.$options['rw'], __FUNCTION__); + } + } + + $query = "SET TRANSACTION $rw $wait ISOLATION LEVEL $ibase_isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ getDatabaseFile($database_name) + + /** + * Builds the string with path+dbname+extension + * + * @return string full database path+file + * @access protected + */ + function _getDatabaseFile($database_name) + { + if ($database_name == '') { + return $database_name; + } + $ret = $this->options['database_path'] . $database_name; + if (!preg_match('/\.[fg]db$/i', $database_name)) { + $ret .= $this->options['database_extension']; + } + return $ret; + } + + // }}} + // {{{ _doConnect() + + /** + * Does the grunt work of connecting to the database + * + * @return mixed connection resource on success, MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $database_name, $persistent = false) + { + if (!extension_loaded('interbase')) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $database_file = $this->_getDatabaseFile($database_name); + $dbhost = $this->dsn['hostspec'] ? + ($this->dsn['hostspec'].':'.$database_file) : $database_file; + + $params = array(); + $params[] = $dbhost; + $params[] = !empty($username) ? $username : null; + $params[] = !empty($password) ? $password : null; + $params[] = isset($this->dsn['charset']) ? $this->dsn['charset'] : null; + $params[] = isset($this->dsn['buffers']) ? $this->dsn['buffers'] : null; + $params[] = isset($this->dsn['dialect']) ? $this->dsn['dialect'] : null; + $params[] = isset($this->dsn['role']) ? $this->dsn['role'] : null; + + $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect'; + $connection = @call_user_func_array($connect_function, $params); + if ($connection <= 0) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if (empty($this->dsn['disable_iso_date'])) { + if (function_exists('ibase_timefmt')) { + @ibase_timefmt("%Y-%m-%d %H:%M:%S", IBASE_TIMESTAMP); + @ibase_timefmt("%Y-%m-%d", IBASE_DATE); + } else { + @ini_set("ibase.timestampformat", "%Y-%m-%d %H:%M:%S"); + //@ini_set("ibase.timeformat", "%H:%M:%S"); + @ini_set("ibase.dateformat", "%Y-%m-%d"); + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + * @access public + */ + function connect() + { + $database_file = $this->_getDatabaseFile($this->database_name); + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->connected_database_name == $database_file + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (empty($this->database_name)) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->database_name, + $this->options['persistent']); + if (MDB2::isError($connection)) { + return $connection; + } + $this->connection =& $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $database_file; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + $this->supported['limit_queries'] = ($this->dbsyntax == 'firebird') ? true : 'emulated'; + + return MDB2_OK; + } + + // }}} + // {{{ 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) + { + $database_file = $this->_getDatabaseFile($name); + $result = file_exists($database_file); + 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 = @ibase_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->database_name, $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); + if (!MDB2::isError($result)) { + $result = $this->_affectedRows($connection, $result); + } + + @ibase_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->getOption('disable_query')) { + if ($is_manip) { + return 0; + } + return null; + } + + if (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + $result = @ibase_query($connection, $query); + + if (false === $result) { + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return (function_exists('ibase_affected_rows') ? @ibase_affected_rows($connection) : 0); + } + + // }}} + // {{{ _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 ($limit > 0 && $this->supports('limit_queries') === true) { + $query = preg_replace('/^([\s(])*SELECT(?!\s*FIRST\s*\d+)/i', + "SELECT FIRST $limit SKIP $offset", $query); + } + 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) + { + $server_info = false; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } elseif ($this->options['server_version']) { + $server_info = $this->options['server_version']; + } else { + $username = $this->options['DBA_username'] ? $this->options['DBA_username'] : $this->dsn['username']; + $password = $this->options['DBA_password'] ? $this->options['DBA_password'] : $this->dsn['password']; + $ibserv = @ibase_service_attach($this->dsn['hostspec'], $username, $password); + $server_info = @ibase_server_info($ibserv, IBASE_SVC_SERVER_VERSION); + @ibase_service_detach($ibserv); + } + if (!$server_info) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Requires either "server_version" or "DBA_username"/"DBA_password" option', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + //WI-V1.5.3.4854 Firebird 1.5 + //WI-T2.1.0.16780 Firebird 2.1 Beta 2 + if (!preg_match('/-[VT]([\d\.]*)/', $server_info, $matches)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Could not parse version information:'.$server_info, __FUNCTION__); + } + $tmp = explode('.', $matches[1], 4); + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => isset($tmp[3]) ? $tmp[3] : null, + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared']) { + 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; + $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 (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 + } + + if ($query[$position] == $placeholder_type_guess) { + if (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[] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $statement = @ibase_prepare($connection, $query); + if (!$statement) { + $err = $this->raiseError(null, null, null, + 'Could not create statement', __FUNCTION__); + return $err; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ getSequenceName() + + /** + * adds sequence name formatting to a sequence name + * + * @param string $sqn name of the sequence + * @return string formatted sequence name + * @access public + */ + function getSequenceName($sqn) + { + return strtoupper(parent::getSequenceName($sqn)); + } + + // }}} + // {{{ 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->getSequenceName($seq_name); + $query = 'SELECT GEN_ID('.$sequence_name.', 1) as the_value FROM RDB$DATABASE'; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError('*'); + $result = $this->queryOne($query, 'integer'); + $this->popExpect(); + $this->popErrorHandling(); + if (MDB2::isError($result)) { + if ($ondemand) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (MDB2::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + } + return $result; + } + + // }}} + // {{{ 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) + { + //$seq = $table.(empty($field) ? '' : '_'.$field); + return $this->currID($table); + } + + // }}} + // {{{ 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->getSequenceName($seq_name); + $query = 'SELECT GEN_ID('.$sequence_name.', 0) as the_value FROM RDB$DATABASE'; + $value = $this->queryOne($query); + if (MDB2::isError($value)) { + return $this->raiseError($value, null, null, + 'Unable to select from ' . $seq_name, __FUNCTION__); + } + if (!is_numeric($value)) { + return $this->raiseError(MDB2_ERROR, null, null, + 'could not find value in sequence table', __FUNCTION__); + } + return $value; + } + + // }}} +} + +/** + * MDB2 FireBird/InterBase result driver + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Result_ibase extends MDB2_Result_Common +{ + // {{{ _skipLimitOffset() + + /** + * Skip the first row of a result set. + * + * @param resource $result + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _skipLimitOffset() + { + if ($this->db->supports('limit_queries') === true) { + return true; + } + if ($this->limit) { + if ($this->rownum > $this->limit) { + return false; + } + } + if ($this->offset) { + while ($this->offset_count < $this->offset) { + ++$this->offset_count; + if (!is_array(@ibase_fetch_row($this->result))) { + $this->offset_count = $this->offset; + return false; + } + } + } + return true; + } + + // }}} + // {{{ 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 (true === $this->result) { + //query successfully executed, but without results... + return null; + } + if (!$this->_skipLimitOffset()) { + return null; + } + if (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 = @ibase_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 = @ibase_fetch_row($this->result); + } + if (!$row) { + if (false === $this->result) { + $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_info = @ibase_field_info($this->result, $column); + $columns[$column_info['alias']] = $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() + { + if (true === $this->result) { + //query successfully executed, but without results... + return 0; + } + + if (!is_resource($this->result)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'numCols(): not a valid ibase resource', __FUNCTION__); + } + $cols = @ibase_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (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 = @ibase_free_result($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } + + // }}} +} + +/** + * MDB2 FireBird/InterBase buffered result driver + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_BufferedResult_ibase extends MDB2_Result_ibase +{ + // {{{ class vars + + var $buffer; + var $buffer_rownum = - 1; + + // }}} + // {{{ _fillBuffer() + + /** + * Fill the row buffer + * + * @param int $rownum row number upto which the buffer should be filled + * if the row number is null all rows are ready into the buffer + * @return boolean true on success, false on failure + * @access protected + */ + function _fillBuffer($rownum = null) + { + if (isset($this->buffer) && is_array($this->buffer)) { + if (null === $rownum) { + if (!end($this->buffer)) { + return false; + } + } elseif (isset($this->buffer[$rownum])) { + return (bool) $this->buffer[$rownum]; + } + } + + if (!$this->_skipLimitOffset()) { + return false; + } + + $buffer = true; + while (((null === $rownum) || $this->buffer_rownum < $rownum) + && (!$this->limit || $this->buffer_rownum < $this->limit) + && ($buffer = @ibase_fetch_row($this->result)) + ) { + ++$this->buffer_rownum; + $this->buffer[$this->buffer_rownum] = $buffer; + } + + if (!$buffer) { + ++$this->buffer_rownum; + $this->buffer[$this->buffer_rownum] = false; + return false; + } elseif ($this->limit && $this->buffer_rownum >= $this->limit) { + ++$this->buffer_rownum; + $this->buffer[$this->buffer_rownum] = false; + } + return true; + } + + // }}} + // {{{ 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 (true === $this->result || (null === $this->result)) { + //query successfully executed, but without results... + return null; + } + if (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (MDB2::isError($seek)) { + return $seek; + } + } + $target_rownum = $this->rownum + 1; + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if (!$this->_fillBuffer($target_rownum)) { + return null; + } + $row = $this->buffer[$target_rownum]; + if ( $fetchmode == MDB2_FETCHMODE_ASSOC + || $fetchmode == MDB2_FETCHMODE_OBJECT + ) { + $column_names = $this->getColumnNames(); + foreach ($column_names as $name => $i) { + $column_names[$name] = $row[$i]; + } + $row = $column_names; + } + $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; + } + + // }}} + // {{{ 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 (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __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() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return true; + } + if ($this->_fillBuffer($this->rownum + 1)) { + return true; + } + return false; + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + $this->_fillBuffer(); + return $this->buffer_rownum; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with $result. + * + * @return boolean true on success, false if $result is invalid + * @access public + */ + function free() + { + $this->buffer = null; + $this->buffer_rownum = null; + return parent::free(); + } + + // }}} +} + +/** + * MDB2 FireBird/InterBase statement driver + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Statement_ibase 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 (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; + } + + $parameters = array(0 => $this->statement); + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = !empty($this->types[$parameter]) ? $this->types[$parameter] : null; + $quoted = $this->db->quote($value, $type, false); + if (MDB2::isError($quoted)) { + return $quoted; + } + $parameters[] = $quoted; + } + + $result = @call_user_func_array('ibase_execute', $parameters); + if (false === $result) { + $err = $this->db->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection); + 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 (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if ((null !== $this->statement) && !@ibase_free_query($this->statement)) { + $result = $this->db->raiseError(null, null, null, + 'Could not free statement', __FUNCTION__); + } + + parent::free(); + return $result; + } +} +?> diff --git a/extlib/MDB2/Driver/mssql.php b/extlib/MDB2/Driver/mssql.php new file mode 100644 index 0000000000..73536d4f4a --- /dev/null +++ b/extlib/MDB2/Driver/mssql.php @@ -0,0 +1,1182 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// +// {{{ Class MDB2_Driver_mssql +/** + * MDB2 MSSQL Server driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Driver_mssql extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '[', 'end' => ']', 'escape' => ']'); + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'mssql'; + $this->dbsyntax = 'mssql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = 'emulated'; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['database_device'] = false; + $this->options['database_size'] = false; + $this->options['max_identifiers_length'] = 128; // MS Access: 64 + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null, $connection = null) + { + if (null === $connection) { + $connection = $this->connection; + } + + $native_code = null; + if ($connection) { + $result = @mssql_query('select @@ERROR as ErrorCode', $connection); + if ($result) { + $native_code = @mssql_result($result, 0, 0); + @mssql_free_result($result); + } + } + $native_msg = @mssql_get_last_message(); + if (null === $error) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 102 => MDB2_ERROR_SYNTAX, + 110 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 155 => MDB2_ERROR_NOSUCHFIELD, + 156 => MDB2_ERROR_SYNTAX, + 170 => MDB2_ERROR_SYNTAX, + 207 => MDB2_ERROR_NOSUCHFIELD, + 208 => MDB2_ERROR_NOSUCHTABLE, + 245 => MDB2_ERROR_INVALID_NUMBER, + 319 => MDB2_ERROR_SYNTAX, + 321 => MDB2_ERROR_NOSUCHFIELD, + 325 => MDB2_ERROR_SYNTAX, + 336 => MDB2_ERROR_SYNTAX, + 515 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 547 => MDB2_ERROR_CONSTRAINT, + 911 => MDB2_ERROR_NOT_FOUND, + 1018 => MDB2_ERROR_SYNTAX, + 1035 => MDB2_ERROR_SYNTAX, + 1801 => MDB2_ERROR_ALREADY_EXISTS, + 1913 => MDB2_ERROR_ALREADY_EXISTS, + 2209 => MDB2_ERROR_SYNTAX, + 2223 => MDB2_ERROR_SYNTAX, + 2248 => MDB2_ERROR_SYNTAX, + 2256 => MDB2_ERROR_SYNTAX, + 2257 => MDB2_ERROR_SYNTAX, + 2627 => MDB2_ERROR_CONSTRAINT, + 2714 => MDB2_ERROR_ALREADY_EXISTS, + 3607 => MDB2_ERROR_DIVZERO, + 3701 => MDB2_ERROR_NOSUCHTABLE, + 7630 => MDB2_ERROR_SYNTAX, + 8134 => MDB2_ERROR_DIVZERO, + 9303 => MDB2_ERROR_SYNTAX, + 9317 => MDB2_ERROR_SYNTAX, + 9318 => MDB2_ERROR_SYNTAX, + 9331 => MDB2_ERROR_SYNTAX, + 9332 => MDB2_ERROR_SYNTAX, + 15253 => MDB2_ERROR_SYNTAX, + ); + } + if (isset($ecode_map[$native_code])) { + if ($native_code == 3701 + && preg_match('/Cannot drop the index/i', $native_msg) + ) { + $error = MDB2_ERROR_NOT_FOUND; + } else { + $error = $ecode_map[$native_code]; + } + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ function escapePattern($text) + + /** + * Quotes pattern (% and _) characters in a string) + * + * @param string the input string to quote + * + * @return string quoted string + * + * @access public + */ + function escapePattern($text) + { + $text = str_replace("[", "[ [ ]", $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, '[' . $wildcard . ']', $text); + } + return $text; + } + + // }}} + // {{{ 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 $text the input string to quote + * @param bool $escape_wildcards flag + * + * @return string quoted string + * @access public + */ + function escape($text, $escape_wildcards = false) + { + $text = parent::escape($text, $escape_wildcards); + // http://pear.php.net/bugs/bug.php?id=16118 + // http://support.microsoft.com/kb/164291 + return preg_replace("/\\\\(\r\n|\r|\n)/", '\\\\$1', $text); + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string $savepoint 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)); + if (null !== $savepoint) { + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVE TRANSACTION '.$savepoint; + return $this->_doQuery($query, true); + } + if ($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'); + } + $result =& $this->_doQuery('BEGIN TRANSACTION', 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 $savepoint 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 (null !== $savepoint) { + return MDB2_OK; + } + + $result =& $this->_doQuery('COMMIT TRANSACTION', 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 $savepoint 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 (null !== $savepoint) { + $query = 'ROLLBACK TRANSACTION '.$savepoint; + return $this->_doQuery($query, true); + } + + $result =& $this->_doQuery('ROLLBACK TRANSACTION', true); + if (MDB2::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ _doConnect() + + /** + * do the grunt work of the connect + * + * @param string $username + * @param string $password + * @param boolean $persistent + * + * @return connection on success or MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $persistent = false) + { + if ( !extension_loaded($this->phptype) + && !extension_loaded('sybase_ct') + && !extension_loaded('odbtp') + && !function_exists('mssql_connect') + ) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $params = array( + $this->dsn['hostspec'] ? $this->dsn['hostspec'] : 'localhost', + $username ? $username : null, + $password ? $password : null, + ); + if ($this->dsn['port']) { + $params[0].= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':').$this->dsn['port']; + } + if (!$persistent) { + if ($this->_isNewLinkSet()) { + $params[] = true; + } else { + $params[] = false; + } + } + + $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect'; + + $connection = @call_user_func_array($connect_function, $params); + if ($connection <= 0) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__, __FUNCTION__); + } + + @mssql_query('SET ANSI_NULL_DFLT_ON ON', $connection); + + /* + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + */ + + if ((bool)ini_get('mssql.datetimeconvert')) { + // his isn't the most elegant way of doing it but it prevents from + // breaking anything thus preserves BC. Bug #11849 + if (isset($this->options['datetimeconvert']) && (bool)$this->options['datetimeconvert'] !== false) { + @ini_set('mssql.datetimeconvert', '1'); + } else { + @ini_set('mssql.datetimeconvert', '0'); + } + } + + if (empty($this->dsn['disable_iso_date'])) { + @mssql_query('SET DATEFORMAT ymd', $connection); + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + 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 (!@mssql_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; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ 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 = @mssql_select_db($name, $connection); + $errorInfo = $this->errorInfo(null, $connection); + @mssql_close($connection); + if (!$result) { + if ($errorInfo[0] != MDB2_ERROR_NOT_FOUND) { + exit; + $result = $this->raiseError($errorInfo[0], null, null, $errorInfo[2], __FUNCTION__); + return $result; + } + $result = false; + } + + 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 = @mssql_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); + } + + @mssql_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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + if (null === $database_name) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mssql_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; + } + } + + $result = @mssql_query($query, $connection); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @mssql_rows_affected($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 ($limit > 0) { + $fetch = $offset + $limit; + if (!$is_manip) { + return preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i', + "\\1SELECT\\2 TOP $fetch", $query); + } + } + 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) + { + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $query = 'SELECT @@VERSION'; + $server_info = $this->queryOne($query, 'text'); + if (MDB2::isError($server_info)) { + return $server_info; + } + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native && !MDB2::isError($server_info)) { + if (preg_match('/(\d+)\.(\d+)\.(\d+)/', $server_info, $tmp)) { + $server_info = array( + 'major' => $tmp[1], + 'minor' => $tmp[2], + 'patch' => $tmp[3], + 'extra' => null, + 'native' => $server_info, + ); + } else { + $server_info = array( + 'major' => null, + 'minor' => null, + 'patch' => null, + 'extra' => null, + 'native' => $server_info, + ); + } + } + return $server_info; + } + + // }}} + // {{{ _checkSequence + + /** + * Checks if there's a sequence that exists. + * + * @param string $seq_name The sequence name to verify. + * @return bool $tableExists The value if the table exists or not + * @access private + */ + function _checkSequence($seq_name) + { + $query = "SELECT * FROM $seq_name"; + $tableExists = $this->_doQuery($query, true); + if (MDB2::isError($tableExists)) { + if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) { + return false; + } + //return $tableExists; + return false; + } + return mssql_result($tableExists, 0, 0); + } + + // }}} + // {{{ 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); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + + $seq_val = $this->_checkSequence($sequence_name); + + if ($seq_val) { + $query = "SET IDENTITY_INSERT $sequence_name OFF ". + "INSERT INTO $sequence_name DEFAULT VALUES"; + } else { + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (0)"; + } + $result = $this->_doQuery($query, true); + $this->popExpect(); + $this->popErrorHandling(); + if (MDB2::isError($result)) { + if ($ondemand && !$this->_checkSequence($sequence_name)) { + $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 { + /** + * Little off-by-one problem with the sequence emulation + * here being fixed, that instead of re-calling nextID + * and forcing an increment by one, we simply check if it + * exists, then we get the last inserted id if it does. + * + * In theory, $seq_name should be created otherwise there would + * have been an error thrown somewhere up there.. + * + * @todo confirm + */ + if ($this->_checkSequence($seq_name)) { + return $this->lastInsertID($seq_name); + } + + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID($sequence_name); + 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) + { + $server_info = $this->getServerVersion(); + if (is_array($server_info) && (null !== $server_info['major']) + && $server_info['major'] >= 8 + ) { + $query = "SELECT IDENT_CURRENT('$table')"; + } else { + $query = "SELECT @@IDENTITY"; + if (null !== $table) { + $query .= ' FROM '.$this->quoteIdentifier($table, true); + } + } + + return $this->queryOne($query, 'integer'); + } + + // }}} +} + +// }}} +// {{{ Class MDB2_Result_mssql + +/** + * MDB2 MSSQL Server result driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Result_mssql extends MDB2_Result_Common +{ + // {{{ _skipLimitOffset() + + /** + * Skip the first row of a result set. + * + * @param resource $result + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _skipLimitOffset() + { + if ($this->limit) { + if ($this->rownum >= $this->limit) { + return false; + } + } + if ($this->offset) { + while ($this->offset_count < $this->offset) { + ++$this->offset_count; + if (!is_array(@mssql_fetch_row($this->result))) { + $this->offset_count = $this->limit; + return false; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ 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 (!$this->_skipLimitOffset()) { + return null; + } + if (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 = @mssql_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 = @mssql_fetch_row($this->result); + } + if (!$row) { + if (false === $this->result) { + $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 = @mssql_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 = @mssql_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return false; + } + return @mssql_next_result($this->result); + } + + // }}} + // {{{ 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 = @mssql_free_result($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } + + // }}} +} + +// }}} +// {{{ class MDB2_BufferedResult_mssql + +/** + * MDB2 MSSQL Server buffered result driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_BufferedResult_mssql extends MDB2_Result_mssql +{ + // {{{ 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) && !@mssql_data_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (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 = @mssql_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + if ($this->limit) { + $rows -= $this->offset; + if ($rows > $this->limit + 1) { + $rows = $this->limit + 1; + } + if ($rows < 0) { + $rows = 0; + } + } + return $rows; + } +} + +// }}} +// {{{ MDB2_Statement_mssql + +/** + * MDB2 MSSQL Server statement driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Statement_mssql extends MDB2_Statement_Common +{ + +} + +// }}} +?> diff --git a/extlib/MDB2/Driver/mysqli.php b/extlib/MDB2/Driver/mysqli.php new file mode 100644 index 0000000000..3dd9d113d2 --- /dev/null +++ b/extlib/MDB2/Driver/mysqli.php @@ -0,0 +1,1906 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * MDB2 MySQLi driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysqli extends MDB2_Driver_Common +{ + // {{{ properties + + public $string_quoting = array( + 'start' => "'", + 'end' => "'", + 'escape' => '\\', + 'escape_pattern' => '\\', + ); + + public $identifier_quoting = array( + 'start' => '`', + 'end' => '`', + 'escape' => '`', + ); + + /** + * The ouptut of mysqli_errno() in _doQuery(), if any. + * @var integer + */ + protected $_query_errno; + + /** + * The ouptut of mysqli_error() in _doQuery(), if any. + * @var string + */ + protected $_query_error; + + 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 = 'mysqli'; + $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['multi_query'] = false; + $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->_query_errno) { + $native_code = $this->_query_errno; + $native_msg = $this->_query_error; + } elseif ($this->connection) { + $native_code = @mysqli_errno($this->connection); + $native_msg = @mysqli_error($this->connection); + } else { + $native_code = @mysqli_connect_errno(); + $native_msg = @mysqli_connect_error(); + } + if (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 = @mysqli_real_escape_string($connection, $text); + 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 (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); + } + if ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + $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 (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 (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__); + } + + $connection = @mysqli_init(); + if (!empty($this->dsn['charset']) && defined('MYSQLI_SET_CHARSET_NAME')) { + @mysqli_options($connection, MYSQLI_SET_CHARSET_NAME, $this->dsn['charset']); + } + + if ($this->options['ssl']) { + @mysqli_ssl_set( + $connection, + empty($this->dsn['key']) ? null : $this->dsn['key'], + empty($this->dsn['cert']) ? null : $this->dsn['cert'], + empty($this->dsn['ca']) ? null : $this->dsn['ca'], + empty($this->dsn['capath']) ? null : $this->dsn['capath'], + empty($this->dsn['cipher']) ? null : $this->dsn['cipher'] + ); + } + + if (!@mysqli_real_connect( + $connection, + $this->dsn['hostspec'], + $username, + $password, + $this->database_name, + $this->dsn['port'], + $this->dsn['socket'] + )) { + if (($err = @mysqli_connect_error()) != '') { + return $this->raiseError(null, + 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']) && !defined('MYSQLI_SET_CHARSET_NAME')) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + if (is_object($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0) { + if (MDB2::areEquals($this->connected_dsn, $this->dsn)) { + return MDB2_OK; + } + $this->connection = 0; + } + + $connection = $this->_doConnect( + $this->dsn['username'], + $this->dsn['password'] + ); + if (MDB2::isError($connection)) { + return $connection; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $this->database_name; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + $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 (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 = mysqli_get_client_version(); + if (OS_WINDOWS && ((40111 > $client_info) || + ((50000 <= $client_info) && (50006 > $client_info))) + ) { + $query = "SET NAMES '".mysqli_real_escape_string($connection, $charset)."'"; + if (null !== $collation) { + $query .= " COLLATE '".mysqli_real_escape_string($connection, $collation)."'"; + } + return $this->_doQuery($query, true, $connection); + } + if (!$result = mysqli_set_charset($connection, $charset)) { + $err = $this->raiseError(null, null, null, + 'Could not set client character set', __FUNCTION__); + return $err; + } + return $result; + } + + // }}} + // {{{ 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']); + if (MDB2::isError($connection)) { + return $connection; + } + + $result = @mysqli_select_db($connection, $name); + @mysqli_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_object($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 ($force) { + $ok = @mysqli_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); + 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); + } + + @mysqli_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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + if (null === $database_name) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mysqli_select_db($connection, $database_name)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $database_name; + } + } + + if ($this->options['multi_query']) { + $result = mysqli_multi_query($connection, $query); + } else { + $resultmode = $this->options['result_buffering'] ? MYSQLI_USE_RESULT : MYSQLI_USE_RESULT; + $result = mysqli_query($connection, $query); + } + + if (!$result) { + // Store now because standaloneQuery throws off $this->connection. + $this->_query_errno = mysqli_errno($connection); + if (0 !== $this->_query_errno) { + $this->_query_error = mysqli_error($connection); + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + } + + if ($this->options['multi_query']) { + if ($this->options['result_buffering']) { + if (!($result = @mysqli_store_result($connection))) { + $err = $this->raiseError(null, null, null, + 'Could not get the first result from a multi query', __FUNCTION__); + return $err; + } + } elseif (!($result = @mysqli_use_result($connection))) { + $err = $this->raiseError(null, null, null, + 'Could not get the first result from a multi query', __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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @mysqli_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 = @mysqli_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 (false === $found) { + 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 (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 (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; + } + } + + if (!$is_manip) { + static $prep_statement_counter = 1; + $randomStr = sprintf('%d', $prep_statement_counter++) . sha1(microtime() . sprintf('%d', mt_rand())); + $statement_name = sprintf($this->options['statement_format'], $this->phptype, $randomStr); + $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; + } + $statement = $statement_name; + } else { + $statement = @mysqli_prepare($connection, $query); + if (!$statement) { + $err = $this->raiseError(null, null, null, + 'Unable to create prepared statement handle', __FUNCTION__); + return $err; + } + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement, $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 MySQLi result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_mysqli 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 (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 = @mysqli_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 = @mysqli_fetch_row($this->result); + } + + if (!$row) { + if (false === $this->result) { + $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_info = @mysqli_fetch_field_direct($this->result, $column); + $columns[$column_info->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 = @mysqli_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + $connection = $this->db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + if (!@mysqli_more_results($connection)) { + return false; + } + if (!@mysqli_next_result($connection)) { + return false; + } + if (!($this->result = @mysqli_use_result($connection))) { + return false; + } + return MDB2_OK; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + do { + if (is_object($this->result) && $this->db->connection) { + $free = @mysqli_free_result($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + } while ($this->result = $this->nextResult()); + + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 MySQLi buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_mysqli extends MDB2_Result_mysqli +{ + // }}} + // {{{ 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) && !@mysqli_data_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (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 = @mysqli_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @param a valid result resource + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + $connection = $this->db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + if (!@mysqli_more_results($connection)) { + return false; + } + if (!@mysqli_next_result($connection)) { + return false; + } + if (!($this->result = @mysqli_store_result($connection))) { + return false; + } + return MDB2_OK; + } +} + +/** + * MDB2 MySQLi statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_mysqli 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 (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; + } + + if (!is_object($this->statement)) { + $query = 'EXECUTE '.$this->statement; + } + if (!empty($this->positions)) { + $paramReferences = array(); + $parameters = array(0 => $this->statement, 1 => ''); + $lobs = array(); + $i = 0; + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (!is_object($this->statement)) { + 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; + } + } else { + if (is_resource($value) || $type == 'clob' || $type == 'blob') { + $paramReferences[$i] = null; + // mysqli_stmt_bind_param() requires parameters to be passed by reference + $parameters[] =& $paramReferences[$i]; + $parameters[1].= 'b'; + $lobs[$i] = $parameter; + } else { + $paramReferences[$i] = $this->db->quote($value, $type, false); + if (MDB2::isError($paramReferences[$i])) { + return $paramReferences[$i]; + } + // mysqli_stmt_bind_param() requires parameters to be passed by reference + $parameters[] =& $paramReferences[$i]; + $parameters[1].= $this->db->datatype->mapPrepareDatatype($type); + } + ++$i; + } + } + + if (!is_object($this->statement)) { + $query.= ' USING @'.implode(', @', array_values($this->positions)); + } else { + $result = call_user_func_array('mysqli_stmt_bind_param', $parameters); + if (false === $result) { + $err = $this->db->raiseError(null, null, null, + 'Unable to bind parameters', __FUNCTION__); + return $err; + } + + foreach ($lobs as $i => $parameter) { + $value = $this->values[$parameter]; + $close = false; + if (!is_resource($value)) { + $close = true; + if (preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + } else { + $fp = @tmpfile(); + @fwrite($fp, $value); + @rewind($fp); + $value = $fp; + } + } + while (!@feof($value)) { + $data = @fread($value, $this->db->options['lob_buffer_length']); + @mysqli_stmt_send_long_data($this->statement, $i, $data); + } + if ($close) { + @fclose($value); + } + } + } + } + + if (!is_object($this->statement)) { + $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); + } else { + if (!mysqli_stmt_execute($this->statement)) { + $err = $this->db->raiseError(null, null, null, + 'Unable to execute statement', __FUNCTION__); + return $err; + } + + if ($this->is_manip) { + $affected_rows = @mysqli_stmt_affected_rows($this->statement); + return $affected_rows; + } + + if ($this->db->options['result_buffering']) { + @mysqli_stmt_store_result($this->statement); + } + + $result = $this->db->_wrapResult($this->statement, $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 (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (is_object($this->statement)) { + if (!@mysqli_stmt_close($this->statement)) { + $result = $this->db->raiseError(null, null, null, + 'Could not free statement', __FUNCTION__); + } + } elseif (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; + } +} +?> diff --git a/extlib/MDB2/Driver/oci8.php b/extlib/MDB2/Driver/oci8.php new file mode 100644 index 0000000000..007ecd16c1 --- /dev/null +++ b/extlib/MDB2/Driver/oci8.php @@ -0,0 +1,1667 @@ + | +// +----------------------------------------------------------------------+ + +// $Id$ + +/** + * MDB2 OCI8 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_oci8 extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '@'); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + var $uncommitedqueries = 0; + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'oci8'; + $this->dbsyntax = 'oci8'; + + $this->supported['sequences'] = true; + $this->supported['indexes'] = true; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = true; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = false; // implementation is broken + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = true; + $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['database_name_prefix'] = false; + $this->options['emulate_database'] = true; + $this->options['default_tablespace'] = false; + $this->options['default_text_field_length'] = 2000; + $this->options['lob_allow_url_include'] = false; + $this->options['result_prefetching'] = false; + $this->options['max_identifiers_length'] = 30; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if (is_resource($error)) { + $error_data = @OCIError($error); + $error = null; + } elseif ($this->connection) { + $error_data = @OCIError($this->connection); + } else { + $error_data = @OCIError(); + } + $native_code = $error_data['code']; + $native_msg = $error_data['message']; + if (null === $error) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1 => MDB2_ERROR_CONSTRAINT, + 900 => MDB2_ERROR_SYNTAX, + 904 => MDB2_ERROR_NOSUCHFIELD, + 911 => MDB2_ERROR_SYNTAX, //invalid character + 913 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 921 => MDB2_ERROR_SYNTAX, + 923 => MDB2_ERROR_SYNTAX, + 942 => MDB2_ERROR_NOSUCHTABLE, + 955 => MDB2_ERROR_ALREADY_EXISTS, + 1400 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 1401 => MDB2_ERROR_INVALID, + 1407 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 1418 => MDB2_ERROR_NOT_FOUND, + 1435 => MDB2_ERROR_NOT_FOUND, + 1476 => MDB2_ERROR_DIVZERO, + 1722 => MDB2_ERROR_INVALID_NUMBER, + 2289 => MDB2_ERROR_NOSUCHTABLE, + 2291 => MDB2_ERROR_CONSTRAINT, + 2292 => MDB2_ERROR_CONSTRAINT, + 2449 => MDB2_ERROR_CONSTRAINT, + 4081 => MDB2_ERROR_ALREADY_EXISTS, //trigger already exists + 24344 => MDB2_ERROR_SYNTAX, //success with compilation error + ); + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ 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)); + if (null !== $savepoint) { + 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); + } + if ($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'); + } + $this->in_transaction = true; + ++$this->uncommitedqueries; + 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 (null !== $savepoint) { + return MDB2_OK; + } + + if ($this->uncommitedqueries) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + if (!@OCICommit($connection)) { + return $this->raiseError(null, null, null, + 'Unable to commit transaction', __FUNCTION__); + } + $this->uncommitedqueries = 0; + } + $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 (null !== $savepoint) { + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if ($this->uncommitedqueries) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + if (!@OCIRollback($connection)) { + return $this->raiseError(null, null, null, + 'Unable to rollback transaction', __FUNCTION__); + } + $this->uncommitedqueries = 0; + } + $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)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $isolation = 'READ COMMITTED'; + case 'READ COMMITTED': + case 'REPEATABLE READ': + $isolation = 'SERIALIZABLE'; + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "ALTER SESSION 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__); + } + + $sid = ''; + + if (!empty($this->dsn['service']) && $this->dsn['hostspec']) { + //oci8://username:password@foo.example.com[:port]/?service=service + // service name is given, it is assumed that hostspec is really a + // hostname, we try to construct an oracle connection string from this + $port = $this->dsn['port'] ? $this->dsn['port'] : 1521; + $sid = sprintf("(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP) + (HOST=%s) (PORT=%s))) + (CONNECT_DATA=(SERVICE_NAME=%s)))", + $this->dsn['hostspec'], + $port, + $this->dsn['service'] + ); + } elseif ($this->dsn['hostspec']) { + // we are given something like 'oci8://username:password@foo/' + // we have hostspec but not a service name, now we assume that + // hostspec is a tnsname defined in tnsnames.ora + $sid = $this->dsn['hostspec']; + if (isset($this->dsn['port']) && $this->dsn['port']) { + $sid = $sid.':'.$this->dsn['port']; + } + } else { + // oci://username:password@ + // if everything fails, we have to rely on environment variables + // not before a check to 'emulate_database' + if (!$this->options['emulate_database'] && $this->database_name) { + $sid = $this->database_name; + } elseif (getenv('ORACLE_SID')) { + $sid = getenv('ORACLE_SID'); + } elseif ($sid = getenv('TWO_TASK')) { + $sid = getenv('TWO_TASK'); + } else { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'not a valid connection string or environment variable [ORACLE_SID|TWO_TASK] not set', + __FUNCTION__); + } + } + + if (function_exists('oci_connect')) { + if ($this->_isNewLinkSet()) { + $connect_function = 'oci_new_connect'; + } else { + $connect_function = $persistent ? 'oci_pconnect' : 'oci_connect'; + } + + $charset = empty($this->dsn['charset']) ? null : $this->dsn['charset']; + $session_mode = empty($this->dsn['session_mode']) ? null : $this->dsn['session_mode']; + $connection = @$connect_function($username, $password, $sid, $charset, $session_mode); + $error = @OCIError(); + if (isset($error['code']) && $error['code'] == 12541) { + // Couldn't find TNS listener. Try direct connection. + $connection = @$connect_function($username, $password, null, $charset); + } + } else { + $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon'; + $connection = @$connect_function($username, $password, $sid); + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + } + + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if (empty($this->dsn['disable_iso_date'])) { + $query = "ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"; + $err =& $this->_doQuery($query, true, $connection); + if (MDB2::isError($err)) { + $this->disconnect(false); + return $err; + } + } + + $query = "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='. '"; + $err =& $this->_doQuery($query, true, $connection); + if (MDB2::isError($err)) { + $this->disconnect(false); + return $err; + } + + 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); + } + + if ($this->database_name && $this->options['emulate_database']) { + $this->dsn['username'] = $this->options['database_name_prefix'].$this->database_name; + } + + $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) { + $query = 'ALTER SESSION SET CURRENT_SCHEMA = "' .strtoupper($this->database_name) .'"'; + $result = $this->_doQuery($query); + if (MDB2::isError($result)) { + $err = $this->raiseError($result, null, null, + 'Could not select the database: '.$this->database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $this->database_name; + } + } + + $this->as_keyword = ' '; + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + if ($server_info['major'] >= '10') { + $this->as_keyword = ' AS '; + } + } + return MDB2_OK; + } + + // }}} + // {{{ 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; + } + + $query = 'ALTER SESSION SET CURRENT_SCHEMA = "' .strtoupper($name) .'"'; + $result = $this->_doQuery($query, true, $connection, false); + if (MDB2::isError($result)) { + if (!MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) { + return $result; + } + return false; + } + return true; + } + + // }}} + // {{{ 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 = false; + if (function_exists('oci_close')) { + $ok = @oci_close($this->connection); + } else { + $ok = @OCILogOff($this->connection); + } + if (!$ok) { + return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, + null, null, null, __FUNCTION__); + } + } + $this->uncommitedqueries = 0; + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ standaloneQuery() + + /** + * execute a query as DBA + * + * @param string $query the SQL query + * @param mixed $types array containing 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, false); + if (!MDB2::isError($result)) { + if ($is_manip) { + $result = $this->_affectedRows($connection, $result); + } else { + $result = $this->_wrapResult($result, $types, true, true, $limit, $offset); + } + } + + @OCILogOff($connection); + return $result; + } + + // }}} + // {{{ _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 (preg_match('/^\s*SELECT/i', $query)) { + if (!preg_match('/\sFROM\s/i', $query)) { + $query.= " FROM dual"; + } + if ($limit > 0) { + // taken from http://svn.ez.no/svn/ezcomponents/packages/Database + $max = $offset + $limit; + if ($offset > 0) { + $min = $offset + 1; + $query = "SELECT * FROM (SELECT a.*, ROWNUM mdb2rn FROM ($query) a WHERE ROWNUM <= $max) WHERE mdb2rn >= $min"; + } else { + $query = "SELECT a.* FROM ($query) a WHERE ROWNUM <= $max"; + } + } + } + return $query; + } + + // }}} + // {{{ _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->getOption('disable_query')) { + if ($is_manip) { + return 0; + } + return null; + } + + if (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + + $query = str_replace("\r\n", "\n", $query); //for fixing end-of-line character in the PL/SQL in windows + $result = @OCIParse($connection, $query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Could not create statement', __FUNCTION__); + return $err; + } + + $mode = $this->in_transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + if (!@OCIExecute($result, $mode)) { + $err = $this->raiseError($result, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + if (is_numeric($this->options['result_prefetching'])) { + @ocisetprefetch($result, $this->options['result_prefetching']); + } + + $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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @OCIRowCount($result); + } + + // }}} + // {{{ 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 = @ociserverversion($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) { + if (!preg_match('/ (\d+)\.(\d+)\.(\d+)\.([\d\.]+) /', $server_info, $tmp)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Could not parse version information:'.$server_info, __FUNCTION__); + } + $server_info = array( + 'major' => $tmp[1], + 'minor' => $tmp[2], + 'patch' => $tmp[3], + 'extra' => $tmp[4], + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared']) { + 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; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (MDB2::isError($result)) { + return $result; + } + $query = $result; + } + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + $parameter = -1; + 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 (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 + } + + if ($query[$position] == $placeholder_type_guess) { + if (null === $placeholder_type) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + if (is_int(key($types))) { + $types_tmp = $types; + $types = array(); + $count = -1; + } + } else { + $types = array_values($types); + } + } + } + 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; + } + // use parameter name in type array + if (isset($count) && isset($types_tmp[++$count])) { + $types[$parameter] = $types_tmp[$count]; + } + $length = strlen($parameter) + 1; + } else { + ++$parameter; + //$length = strlen($parameter); + $length = 1; // strlen('?') + } + if (!in_array($parameter, $positions)) { + $positions[] = $parameter; + } + if (isset($types[$parameter]) + && ($types[$parameter] == 'clob' || $types[$parameter] == 'blob') + ) { + if (!isset($lobs[$parameter])) { + $lobs[$parameter] = $parameter; + } + $value = $this->quote(true, $types[$parameter]); + if (MDB2::isError($value)) { + return $value; + } + $query = substr_replace($query, $value, $p_position, $length); + $position = $p_position + strlen($value) - 1; + } elseif ($placeholder_type == '?') { + $query = substr_replace($query, ':'.$parameter, $p_position, 1); + $position = $p_position + $length; + } else { + $position = $p_position + 1; + } + } else { + $position = $p_position; + } + } + if (is_array($lobs)) { + $columns = $variables = ''; + foreach ($lobs as $parameter => $field) { + $columns.= ($columns ? ', ' : ' RETURNING ').$field; + $variables.= ($variables ? ', ' : ' INTO ').':'.$parameter; + } + $query.= $columns.$variables; + } + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $statement = @OCIParse($connection, $query); + if (!$statement) { + $err = $this->raiseError(null, null, null, + 'Could not create statement', __FUNCTION__); + return $err; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ 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); + $query = "SELECT $sequence_name.nextval FROM DUAL"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->queryOne($query, 'integer'); + $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 $result; + } + return $this->nextId($seq_name, false); + } + } + return $result; + } + + // }}} + // {{{ 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) + { + $old_seq = $table.(empty($field) ? '' : '_'.$field); + $sequence_name = $this->quoteIdentifier($this->getSequenceName($table), true); + $result = $this->queryOne("SELECT $sequence_name.currval", 'integer'); + if (MDB2::isError($result)) { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($old_seq), true); + $result = $this->queryOne("SELECT $sequence_name.currval", 'integer'); + } + + return $result; + } + + // }}} + // {{{ currId() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2_Error or id + * @access public + */ + function currId($seq_name) + { + $sequence_name = $this->getSequenceName($seq_name); + $query = 'SELECT (last_number-1) FROM all_sequences'; + $query.= ' WHERE sequence_name='.$this->quote($sequence_name, 'text'); + $query.= ' OR sequence_name='.$this->quote(strtoupper($sequence_name), 'text'); + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 OCI8 result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_oci8 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 (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 + ) { + @OCIFetchInto($this->result, $row, OCI_ASSOC+OCI_RETURN_NULLS); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + @OCIFetchInto($this->result, $row, OCI_RETURN_NULLS); + } + if (!$row) { + if (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + return null; + } + // remove additional column at the end + if ($this->offset > 0) { + array_pop($row); + } + $mode = 0; + $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 = @OCIColumnName($this->result, $column + 1); + $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 = @OCINumCols($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + if ($this->offset > 0) { + --$cols; + } + 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 = @OCIFreeCursor($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + + } +} + +/** + * MDB2 OCI8 buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_oci8 extends MDB2_Result_oci8 +{ + var $buffer; + var $buffer_rownum = - 1; + + // {{{ _fillBuffer() + + /** + * Fill the row buffer + * + * @param int $rownum row number upto which the buffer should be filled + if the row number is null all rows are ready into the buffer + * @return boolean true on success, false on failure + * @access protected + */ + function _fillBuffer($rownum = null) + { + if (isset($this->buffer) && is_array($this->buffer)) { + if (null === $rownum) { + if (!end($this->buffer)) { + return false; + } + } elseif (isset($this->buffer[$rownum])) { + return (bool)$this->buffer[$rownum]; + } + } + + $row = true; + while (((null === $rownum) || $this->buffer_rownum < $rownum) + && ($row = @OCIFetchInto($this->result, $buffer, OCI_RETURN_NULLS)) + ) { + ++$this->buffer_rownum; + // remove additional column at the end + if ($this->offset > 0) { + array_pop($buffer); + } + if (empty($this->types)) { + foreach (array_keys($buffer) as $key) { + if (is_object($buffer[$key]) && is_a($buffer[$key], 'oci-lob')) { + $buffer[$key] = $buffer[$key]->load(); + } + } + } + $this->buffer[$this->buffer_rownum] = $buffer; + } + + if (!$row) { + ++$this->buffer_rownum; + $this->buffer[$this->buffer_rownum] = false; + return false; + } + return true; + } + + // }}} + // {{{ 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 (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + if (null === $this->result) { + return null; + } + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (MDB2::isError($seek)) { + return $seek; + } + } + $target_rownum = $this->rownum + 1; + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if (!$this->_fillBuffer($target_rownum)) { + return null; + } + $row = $this->buffer[$target_rownum]; + if ( $fetchmode == MDB2_FETCHMODE_ASSOC + || $fetchmode == MDB2_FETCHMODE_OBJECT + ) { + $column_names = $this->getColumnNames(); + foreach ($column_names as $name => $i) { + $column_names[$name] = $row[$i]; + } + $row = $column_names; + } + $mode = 0; + $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; + } + + // }}} + // {{{ 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 (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __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() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return true; + } + if ($this->_fillBuffer($this->rownum + 1)) { + return true; + } + return false; + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + $this->_fillBuffer(); + return $this->buffer_rownum; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with $result. + * + * @return boolean true on success, false if $result is invalid + * @access public + */ + function free() + { + $this->buffer = null; + $this->buffer_rownum = null; + return parent::free(); + } +} + +/** + * MDB2 OCI8 statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_oci8 extends MDB2_Statement_Common +{ + // {{{ Variables (Properties) + + var $type_maxlengths = array(); + + // }}} + // {{{ bindParam() + + /** + * Bind a variable to a parameter of a prepared query. + * + * @param int $parameter the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed &$value variable that is meant to be bound to specified + * parameter. The type of the value depends on the $type argument. + * @param string $type specifies the type of the field + * @param int $maxlength specifies the maximum length of the field; if set to -1, the + * current length of $value is used + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindParam($parameter, &$value, $type = null, $maxlength = -1) + { + if (!is_numeric($parameter)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (MDB2_OK === ($ret = parent::bindParam($parameter, $value, $type))) { + $this->type_maxlengths[$parameter] = $maxlength; + } + + return $ret; + } + + // }}} + // {{{ _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 (null === $this->statement) { + return parent::_execute($result_class, $result_wrap_class); + } + $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; + } + + $result = MDB2_OK; + $lobs = $quoted_values = array(); + $i = 0; + 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__); + } + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if ($type == 'clob' || $type == 'blob') { + $lobs[$i]['file'] = false; + if (is_resource($this->values[$parameter])) { + $fp = $this->values[$parameter]; + $this->values[$parameter] = ''; + while (!feof($fp)) { + $this->values[$parameter] .= fread($fp, 8192); + } + } elseif (is_object($this->values[$parameter]) && is_a($this->values[$parameter], 'OCI-Lob')) { + //do nothing + } elseif ($this->db->getOption('lob_allow_url_include') + && preg_match('/^(\w+:\/\/)(.*)$/', $this->values[$parameter], $match) + ) { + $lobs[$i]['file'] = true; + if ($match[1] == 'file://') { + $this->values[$parameter] = $match[2]; + } + } + $lobs[$i]['value'] = $this->values[$parameter]; + $lobs[$i]['descriptor'] =& $this->values[$parameter]; + // Test to see if descriptor has already been created for this + // variable (i.e. if it has been bound more than once): + if (!(is_object($this->values[$parameter]) && is_a($this->values[$parameter], 'OCI-Lob'))) { + $this->values[$parameter] = @OCINewDescriptor($connection, OCI_D_LOB); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to create descriptor for LOB in parameter: '.$parameter, __FUNCTION__); + break; + } + } + $lob_type = ($type == 'blob' ? OCI_B_BLOB : OCI_B_CLOB); + if (!@OCIBindByName($this->statement, ':'.$parameter, $lobs[$i]['descriptor'], -1, $lob_type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'could not bind LOB parameter', __FUNCTION__); + break; + } + } else if ($type == OCI_B_BFILE) { + // Test to see if descriptor has already been created for this + // variable (i.e. if it has been bound more than once): + if (!(is_object($this->values[$parameter]) && is_a($this->values[$parameter], "OCI-Lob"))) { + $this->values[$parameter] = @OCINewDescriptor($connection, OCI_D_FILE); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to create descriptor for BFILE in parameter: '.$parameter, __FUNCTION__); + break; + } + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $this->values[$parameter], -1, $type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'Could not bind BFILE parameter', __FUNCTION__); + break; + } + } else if ($type == OCI_B_ROWID) { + // Test to see if descriptor has already been created for this + // variable (i.e. if it has been bound more than once): + if (!is_a($this->values[$parameter], "OCI-Lob")) { + $this->values[$parameter] = @OCINewDescriptor($connection, OCI_D_ROWID); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to create descriptor for ROWID in parameter: '.$parameter, __FUNCTION__); + break; + } + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $this->values[$parameter], -1, $type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'Could not bind ROWID parameter', __FUNCTION__); + break; + } + } else if ($type == OCI_B_CURSOR) { + // Test to see if cursor has already been allocated for this + // variable (i.e. if it has been bound more than once): + if (!is_resource($this->values[$parameter]) || !get_resource_type($this->values[$parameter]) == "oci8 statement") { + $this->values[$parameter] = @OCINewCursor($connection); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to allocate cursor for parameter: '.$parameter, __FUNCTION__); + break; + } + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $this->values[$parameter], -1, $type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'Could not bind CURSOR parameter', __FUNCTION__); + break; + } + } else { + $maxlength = array_key_exists($parameter, $this->type_maxlengths) ? $this->type_maxlengths[$parameter] : -1; + $this->values[$parameter] = $this->db->quote($this->values[$parameter], $type, false); + $quoted_values[$i] =& $this->values[$parameter]; + if (MDB2::isError($quoted_values[$i])) { + return $quoted_values[$i]; + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $quoted_values[$i], $maxlength)) { + $result = $this->db->raiseError($this->statement, null, null, + 'could not bind non-abstract parameter', __FUNCTION__); + break; + } + } + ++$i; + } + + $lob_keys = array_keys($lobs); + if (!MDB2::isError($result)) { + $mode = (!empty($lobs) || $this->db->in_transaction) ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + if (!@OCIExecute($this->statement, $mode)) { + $err = $this->db->raiseError($this->statement, null, null, + 'could not execute statement', __FUNCTION__); + return $err; + } + + if (!empty($lobs)) { + foreach ($lob_keys as $i) { + if ((null !== $lobs[$i]['value']) && $lobs[$i]['value'] !== '') { + if (is_object($lobs[$i]['value'])) { + // Probably a NULL LOB + // @see http://bugs.php.net/bug.php?id=27485 + continue; + } + if ($lobs[$i]['file']) { + $result = $lobs[$i]['descriptor']->savefile($lobs[$i]['value']); + } else { + $result = $lobs[$i]['descriptor']->save($lobs[$i]['value']); + } + if (!$result) { + $result = $this->db->raiseError(null, null, null, + 'Unable to save descriptor contents', __FUNCTION__); + break; + } + } + } + + if (!MDB2::isError($result)) { + if (!$this->db->in_transaction) { + if (!@OCICommit($connection)) { + $result = $this->db->raiseError(null, null, null, + 'Unable to commit transaction', __FUNCTION__); + } + } else { + ++$this->db->uncommitedqueries; + } + } + } + } + + if (MDB2::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $this->statement); + return $affected_rows; + } + + $result = $this->db->_wrapResult($this->statement, $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 (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if ((null !== $this->statement) && !@OCIFreeStatement($this->statement)) { + $result = $this->db->raiseError(null, null, null, + 'Could not free statement', __FUNCTION__); + } + + parent::free(); + return $result; + } +} +?> diff --git a/extlib/MDB2/Driver/odbc.php b/extlib/MDB2/Driver/odbc.php new file mode 100644 index 0000000000..9d05476790 --- /dev/null +++ b/extlib/MDB2/Driver/odbc.php @@ -0,0 +1,1142 @@ + rewritten for ODBC by Philipp Schellhaas (mail@pschellhaas.de) + */ +class MDB2_Driver_odbc extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '[', 'end' => ']', 'escape' => ']'); + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'odbc'; + $this->dbsyntax = 'odbc'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = false; + $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'] = 'emulated'; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = false; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['database_device'] = false; + $this->options['database_size'] = false; + $this->options['max_identifiers_length'] = 128; // MS Access: 64 + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null, $connection = null) + { + if (is_null($connection)) { + $connection = $this->connection; + } + + $native_code = null; + if ($connection) { + $result = @odbc_query('select @@ERROR as ErrorCode', $connection); + if ($result) { + $native_code = @odbc_result($result, 0, 0); + @odbc_free_result($result); + } + } + $native_msg = @odbc_errormsg(); + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 102 => MDB2_ERROR_SYNTAX, + 110 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 155 => MDB2_ERROR_NOSUCHFIELD, + 156 => MDB2_ERROR_SYNTAX, + 170 => MDB2_ERROR_SYNTAX, + 207 => MDB2_ERROR_NOSUCHFIELD, + 208 => MDB2_ERROR_NOSUCHTABLE, + 245 => MDB2_ERROR_INVALID_NUMBER, + 319 => MDB2_ERROR_SYNTAX, + 321 => MDB2_ERROR_NOSUCHFIELD, + 325 => MDB2_ERROR_SYNTAX, + 336 => MDB2_ERROR_SYNTAX, + 515 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 547 => MDB2_ERROR_CONSTRAINT, + 911 => MDB2_ERROR_NOT_FOUND, + 1018 => MDB2_ERROR_SYNTAX, + 1035 => MDB2_ERROR_SYNTAX, + 1801 => MDB2_ERROR_ALREADY_EXISTS, + 1913 => MDB2_ERROR_ALREADY_EXISTS, + 2209 => MDB2_ERROR_SYNTAX, + 2223 => MDB2_ERROR_SYNTAX, + 2248 => MDB2_ERROR_SYNTAX, + 2256 => MDB2_ERROR_SYNTAX, + 2257 => MDB2_ERROR_SYNTAX, + 2627 => MDB2_ERROR_CONSTRAINT, + 2714 => MDB2_ERROR_ALREADY_EXISTS, + 3607 => MDB2_ERROR_DIVZERO, + 3701 => MDB2_ERROR_NOSUCHTABLE, + 7630 => MDB2_ERROR_SYNTAX, + 8134 => MDB2_ERROR_DIVZERO, + 9303 => MDB2_ERROR_SYNTAX, + 9317 => MDB2_ERROR_SYNTAX, + 9318 => MDB2_ERROR_SYNTAX, + 9331 => MDB2_ERROR_SYNTAX, + 9332 => MDB2_ERROR_SYNTAX, + 15253 => MDB2_ERROR_SYNTAX, + ); + } + if (isset($ecode_map[$native_code])) { + if ($native_code == 3701 + && preg_match('/Cannot drop the index/i', $native_msg) + ) { + $error = MDB2_ERROR_NOT_FOUND; + } else { + $error = $ecode_map[$native_code]; + } + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ function escapePattern($text) + + /** + * Quotes pattern (% and _) characters in a string) + * + * @param string the input string to quote + * + * @return string quoted string + * + * @access public + */ + function escapePattern($text) + { + $text = str_replace("[", "[ [ ]", $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, '[' . $wildcard . ']', $text); + } + 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)); + if (!is_null($savepoint)) { + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVE TRANSACTION '.$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'); + } + $result =& $this->_doQuery('BEGIN TRANSACTION', 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)) { + return MDB2_OK; + } + + $result =& $this->_doQuery('COMMIT TRANSACTION', 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)) { + $query = 'ROLLBACK TRANSACTION '.$savepoint; + return $this->_doQuery($query, true); + } + + $result =& $this->_doQuery('ROLLBACK TRANSACTION', true); + if (MDB2::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ _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( + $this->dsn['hostspec'] ? $this->dsn['hostspec'] : 'localhost', + $username ? $username : null, + $password ? $password : null, + ); + if ($this->dsn['port']) { + $params[0].= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':').$this->dsn['port']; + } + if (!$persistent) { + if ($this->_isNewLinkSet()) { + $params[] = true; + } else { + $params[] = false; + } + } + + + $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect'; + + + $params = array($this->database_name,$this->dsn[username],$this->dsn[password]); + $connection = @call_user_func_array($connect_function, $params); + + if ($connection <= 0) { + + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__, __FUNCTION__); + } + + //odbc_query('SET ANSI_NULL_DFLT_ON ON', $connection); + + /* + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + */ + + if ((bool)ini_get('odbc.datetimeconvert')) { + @ini_set('odbc.datetimeconvert', '0'); + } + + /* + if (empty($this->dsn['disable_iso_date'])) { + @odbc_query('SET DATEFORMAT ymd', $connection); + } + */ + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + 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 (!odbc_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; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ 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 = odbc_select_db($name, $connection); + $errorInfo = $this->errorInfo(null, $connection); + odbc_close($connection); + if (!$result) { + if ($errorInfo[0] != MDB2_ERROR_NOT_FOUND) { + exit; + $result = $this->raiseError($errorInfo[0], null, null, $errorInfo[2], __FUNCTION__); + return $result; + } + $result = false; + } + + 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 = odbc_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); + } + + odbc_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) + { + // echo "
".$query."
\n"; + $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 (!odbc_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; + } + } + + $result = odbc_query($query, $connection); + if (!$result) { + $err =& $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + //print_r(odbc_fetch_array($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 odbc_rows_affected($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 ($limit > 0) { + $fetch = $offset + $limit; + if (!$is_manip) { + return preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i', + "\\1SELECT\\2 TOP $fetch", $query); + } + } + 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) + { + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $query = 'SELECT @@VERSION'; + $server_info = $this->queryOne($query, 'text'); + if (MDB2::isError($server_info)) { + return $server_info; + } + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native && !MDB2::isError($server_info)) { + if (preg_match('/(\d+)\.(\d+)\.(\d+)/', $server_info, $tmp)) { + $server_info = array( + 'major' => $tmp[1], + 'minor' => $tmp[2], + 'patch' => $tmp[3], + 'extra' => null, + 'native' => $server_info, + ); + } else { + $server_info = array( + 'major' => null, + 'minor' => null, + 'patch' => null, + 'extra' => null, + 'native' => $server_info, + ); + } + } + return $server_info; + } + + // }}} + // {{{ _checkSequence + + /** + * Checks if there's a sequence that exists. + * + * @param string $seq_name The sequence name to verify. + * @return bool $tableExists The value if the table exists or not + * @access private + */ + function _checkSequence($seq_name) + { + $query = "SELECT * FROM $seq_name"; + $tableExists =& $this->_doQuery($query, true); + if (MDB2::isError($tableExists)) { + if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) { + return false; + } + //return $tableExists; + return false; + } + return odbc_result($tableExists, 0, 0); + } + + // }}} + // {{{ 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); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + + $seq_val = $this->_checkSequence($sequence_name); + + if ($seq_val) { + $query = "SET IDENTITY_INSERT $sequence_name OFF ". + "INSERT INTO $sequence_name DEFAULT VALUES"; + } else { + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (0)"; + } + $result =& $this->_doQuery($query, true); + $this->popExpect(); + $this->popErrorHandling(); + if (MDB2::isError($result)) { + if ($ondemand && !$this->_checkSequence($sequence_name)) { + $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 { + /** + * Little off-by-one problem with the sequence emulation + * here being fixed, that instead of re-calling nextID + * and forcing an increment by one, we simply check if it + * exists, then we get the last inserted id if it does. + * + * In theory, $seq_name should be created otherwise there would + * have been an error thrown somewhere up there.. + * + * @todo confirm + */ + if ($this->_checkSequence($seq_name)) { + return $this->lastInsertID($seq_name); + } + + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID($sequence_name); + 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) + { + $server_info = $this->getServerVersion(); + if (is_array($server_info) && !is_null($server_info['major']) + && $server_info['major'] >= 8 + ) { + $query = "SELECT IDENT_CURRENT('$table')"; + } else { + $query = "SELECT @@IDENTITY"; + if (!is_null($table)) { + $query .= ' FROM '.$this->quoteIdentifier($table, true); + } + } + + return $this->queryOne($query, 'integer'); + } + + // }}} +} + +// }}} +// {{{ Class MDB2_Result_odbc + +/** + * MDB2 odbc Server result driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Result_odbc extends MDB2_Result_Common +{ + // {{{ _skipLimitOffset() + + /** + * Skip the first row of a result set. + * + * @param resource $result + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _skipLimitOffset() + { + if ($this->limit) { + if ($this->rownum >= $this->limit) { + return false; + } + } + if ($this->offset) { + while ($this->offset_count < $this->offset) { + ++$this->offset_count; + if (!is_array(odbc_fetch_row_wrapper($this->result))) { + $this->offset_count = $this->limit; + return false; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ 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 (!$this->_skipLimitOffset()) { + $null = null; + return $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 = odbc_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 = odbc_fetch_row_wrapper($this->result); + + + } + if (!$row) { + if ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if ( ( $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 { + $row = new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (MDB2::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = odbc_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 = odbc_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; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + 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 false; + } + return odbc_next_result($this->result); + } + + // }}} + // {{{ 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 = odbc_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; + } + + // }}} +} + +// }}} +// {{{ class MDB2_BufferedResult_odbc + +/** + * MDB2 odbc Server buffered result driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_BufferedResult_odbc extends MDB2_Result_odbc +{ + // {{{ 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) && !odbc_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 = odbc_num_rows($this->result); + + // Hack the Planet + $count = 0; + while ($row = odbc_fetch_row($this->result)) { + $count++; + } + @odbc_fetch_row($this->result, 0); + if($count >= 0) { + $rows = $count; + } + + if (is_null($rows)) { + 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 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + if ($this->limit) { + $rows -= $this->offset; + if ($rows > $this->limit) { + $rows = $this->limit; + } + if ($rows < 0) { + $rows = 0; + } + } + return $rows; + } +} + +// }}} +// {{{ MDB2_Statement_odbc + +/** + * MDB2 odbc Server statement driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Statement_odbc extends MDB2_Statement_Common +{ + +} + +// }}} +?> diff --git a/extlib/MDB2/Driver/pgsql.php b/extlib/MDB2/Driver/pgsql.php new file mode 100644 index 0000000000..4c329422d5 --- /dev/null +++ b/extlib/MDB2/Driver/pgsql.php @@ -0,0 +1,1584 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * MDB2 PostGreSQL driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Driver_pgsql extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'pgsql'; + $this->dbsyntax = 'pgsql'; + + $this->supported['sequences'] = true; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = true; + $this->supported['current_id'] = true; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = 'emulated'; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = true; + $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['multi_query'] = false; + $this->options['disable_smart_seqname'] = true; + $this->options['max_identifiers_length'] = 63; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + // Fall back to MDB2_ERROR if there was no mapping. + $error_code = MDB2_ERROR; + + $native_msg = ''; + if (is_resource($error)) { + $native_msg = @pg_result_error($error); + } elseif ($this->connection) { + $native_msg = @pg_last_error($this->connection); + if (!$native_msg && @pg_connection_status($this->connection) === PGSQL_CONNECTION_BAD) { + $native_msg = 'Database connection has been lost.'; + $error_code = MDB2_ERROR_CONNECT_FAILED; + } + } else { + $native_msg = @pg_last_error(); + } + + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/column .* (of relation .*)?does not exist/i' + => MDB2_ERROR_NOSUCHFIELD, + '/(relation|sequence|table).*does not exist|class .* not found/i' + => MDB2_ERROR_NOSUCHTABLE, + '/database .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/constraint .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/index .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/database .* already exists/i' + => MDB2_ERROR_ALREADY_EXISTS, + '/relation .* already exists/i' + => MDB2_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => MDB2_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => MDB2_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => MDB2_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => MDB2_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => MDB2_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => MDB2_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => MDB2_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => MDB2_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => MDB2_ERROR_SYNTAX, + '/syntax error at/' + => MDB2_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => MDB2_ERROR_SYNTAX, + '/permission denied/' + => MDB2_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => MDB2_ERROR_CONSTRAINT, + '/referential integrity violation/' + => MDB2_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => MDB2_ERROR_VALUE_COUNT_ON_ROW, + ); + } + if (is_numeric($error) && $error < 0) { + $error_code = $error; + } else { + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg)) { + $error_code = $code; + break; + } + } + } + return array($error_code, null, $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; + } + if (is_resource($connection) && version_compare(PHP_VERSION, '5.2.0RC5', '>=')) { + $text = @pg_escape_string($connection, $text); + } else { + $text = @pg_escape_string($text); + } + 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)); + if (null !== $savepoint) { + 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); + } + if ($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'); + } + $result = $this->_doQuery('BEGIN', 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 (null !== $savepoint) { + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $result = $this->_doQuery('COMMIT', 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 (null !== $savepoint) { + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $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)); + 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 CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ _doConnect() + + /** + * Do the grunt work of connecting to the database + * + * @return mixed connection resource on success, MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $database_name, $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__); + } + + if ($database_name == '') { + $database_name = 'template1'; + } + + $protocol = $this->dsn['protocol'] ? $this->dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($this->dsn['hostspec']) { + $params[0].= 'host=' . $this->dsn['hostspec']; + } + if ($this->dsn['port']) { + $params[0].= ' port=' . $this->dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($this->dsn['socket']) { + $params[0].= 'host=' . $this->dsn['socket']; + } + if ($this->dsn['port']) { + $params[0].= ' port=' . $this->dsn['port']; + } + } + if ($database_name) { + $params[0].= ' dbname=\'' . addslashes($database_name) . '\''; + } + if ($username) { + $params[0].= ' user=\'' . addslashes($username) . '\''; + } + if ($password) { + $params[0].= ' password=\'' . addslashes($password) . '\''; + } + if (!empty($this->dsn['options'])) { + $params[0].= ' options=' . $this->dsn['options']; + } + if (!empty($this->dsn['tty'])) { + $params[0].= ' tty=' . $this->dsn['tty']; + } + if (!empty($this->dsn['connect_timeout'])) { + $params[0].= ' connect_timeout=' . $this->dsn['connect_timeout']; + } + if (!empty($this->dsn['sslmode'])) { + $params[0].= ' sslmode=' . $this->dsn['sslmode']; + } + if (!empty($this->dsn['service'])) { + $params[0].= ' service=' . $this->dsn['service']; + } + + if ($this->_isNewLinkSet()) { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + $connection = @call_user_func_array($connect_function, $params); + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if (empty($this->dsn['disable_iso_date'])) { + if (!@pg_query($connection, "SET SESSION DATESTYLE = 'ISO'")) { + return $this->raiseError(null, null, null, + 'Unable to set date style to iso', __FUNCTION__); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + + // Enable extra compatibility settings on 8.2 and later + if (function_exists('pg_parameter_status')) { + $version = pg_parameter_status($connection, 'server_version'); + if ($version == false) { + return $this->raiseError(null, null, null, + 'Unable to retrieve server version', __FUNCTION__); + } + $version = explode ('.', $version); + if ( $version['0'] > 8 + || ($version['0'] == 8 && $version['1'] >= 2) + ) { + if (!@pg_query($connection, "SET SESSION STANDARD_CONFORMING_STRINGS = OFF")) { + return $this->raiseError(null, null, null, + 'Unable to set standard_conforming_strings to off', __FUNCTION__); + } + + if (!@pg_query($connection, "SET SESSION ESCAPE_STRING_WARNING = OFF")) { + return $this->raiseError(null, null, null, + 'Unable to set escape_string_warning to off', __FUNCTION__); + } + } + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true 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->connected_database_name == $this->database_name + && ($this->opened_persistent == $this->options['persistent']) + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if ($this->database_name) { + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->database_name, + $this->options['persistent']); + if (MDB2::isError($connection)) { + return $connection; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $this->database_name; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + } + + return MDB2_OK; + } + + // }}} + // {{{ setCharset() + + /** + * Set the charset on the current connection + * + * @param string charset + * @param resource connection handle + * + * @return true on success, MDB2 Error Object on failure + */ + function setCharset($charset, $connection = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + if (is_array($charset)) { + $charset = array_shift($charset); + $this->warnings[] = 'postgresql does not support setting client collation'; + } + $result = @pg_set_client_encoding($connection, $charset); + if ($result == -1) { + return $this->raiseError(null, null, null, + 'Unable to set client charset: '.$charset, __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ 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) + { + $res = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->escape($name), + $this->options['persistent']); + if (!MDB2::isError($res)) { + return true; + } + + return false; + } + + // }}} + // {{{ 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 = @pg_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->database_name, $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)) { + if ($is_manip) { + $result = $this->_affectedRows($connection, $result); + } else { + $result = $this->_wrapResult($result, $types, true, true, $limit, $offset); + } + } + + @pg_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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + + $function = $this->options['multi_query'] ? 'pg_send_query' : 'pg_query'; + $result = @$function($connection, $query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } elseif ($this->options['multi_query']) { + if (!($result = @pg_get_result($connection))) { + $err = $this->raiseError(null, null, null, + 'Could not get the first result from a multi query', __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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @pg_affected_rows($result); + } + + // }}} + // {{{ _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 ($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); + } + if ($is_manip) { + $query = $this->_modifyManipQuery($query, $limit); + } else { + $query.= " LIMIT $limit OFFSET $offset"; + } + } + return $query; + } + + // }}} + // {{{ _modifyManipQuery() + + /** + * Changes a manip query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param integer $limit limit the number of rows + * @return string modified query + * @access protected + */ + function _modifyManipQuery($query, $limit) + { + $pos = strpos(strtolower($query), 'where'); + $where = $pos ? substr($query, $pos) : ''; + + $manip_clause = '(\bDELETE\b\s+(?:\*\s+)?\bFROM\b|\bUPDATE\b)'; + $from_clause = '([\w\.]+)'; + $where_clause = '(?:(.*)\bWHERE\b\s+(.*))|(.*)'; + $pattern = '/^'. $manip_clause . '\s+' . $from_clause .'(?:\s)*(?:'. $where_clause .')?$/i'; + $matches = preg_match($pattern, $query, $match); + if ($matches) { + $manip = $match[1]; + $from = $match[2]; + $what = (count($matches) == 6) ? $match[5] : $match[3]; + return $manip.' '.$from.' '.$what.' WHERE ctid=(SELECT ctid FROM '.$from.' '.$where.' LIMIT '.$limit.')'; + } + //return error? + 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) + { + $query = 'SHOW SERVER_VERSION'; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = $this->queryOne($query, 'text'); + if (MDB2::isError($server_info)) { + return $server_info; + } + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native && !MDB2::isError($server_info)) { + $tmp = explode('.', $server_info, 3); + if (empty($tmp[2]) + && isset($tmp[1]) + && preg_match('/(\d+)(.*)/', $tmp[1], $tmp2) + ) { + $server_info = array( + 'major' => $tmp[0], + 'minor' => $tmp2[1], + 'patch' => null, + 'extra' => $tmp2[2], + 'native' => $server_info, + ); + } else { + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => null, + 'native' => $server_info, + ); + } + } + return $server_info; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared']) { + 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; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (MDB2::isError($result)) { + return $result; + } + $query = $result; + } + $pgtypes = function_exists('pg_prepare') ? false : array(); + if ($pgtypes !== false && !empty($types)) { + $this->loadModule('Datatype', null, true); + } + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = $parameter = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + //skip "::type" cast ("select id::varchar(20) from sometable where name=?") + $doublecolon_position = strpos($query, '::', $position); + if ($doublecolon_position !== false && $doublecolon_position == $c_position) { + $c_position = strpos($query, $colon, $position+2); + } + 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 (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 + } + + if ($query[$position] == $placeholder_type_guess) { + if (null === $placeholder_type) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + } else { + $types = array_values($types); + } + } + } + if ($placeholder_type_guess == '?') { + $length = 1; + $name = $parameter; + } else { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $param = preg_replace($regexp, '\\1', $query); + if ($param === '') { + $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $length = strlen($param) + 1; + $name = $param; + } + if ($pgtypes !== false) { + if (is_array($types) && array_key_exists($name, $types)) { + $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$name]); + } elseif (is_array($types) && array_key_exists($parameter, $types)) { + $pgtypes[] = $this->datatype->mapPrepareDatatype($types[$parameter]); + } else { + $pgtypes[] = 'text'; + } + } + if (($key_parameter = array_search($name, $positions)) !== false) { + //$next_parameter = 1; + $parameter = $key_parameter + 1; + //foreach ($positions as $key => $value) { + // if ($key_parameter == $key) { + // break; + // } + // ++$next_parameter; + //} + } else { + ++$parameter; + //$next_parameter = $parameter; + $positions[] = $name; + } + $query = substr_replace($query, '$'.$parameter, $position, $length); + $position = $p_position + strlen($parameter); + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + static $prep_statement_counter = 1; + $randomStr = sprintf('%d', $prep_statement_counter++) . sha1(microtime() . sprintf('%d', mt_rand())); + $statement_name = sprintf($this->options['statement_format'], $this->phptype, $randomStr); + $statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']); + if (false === $pgtypes) { + $result = @pg_prepare($connection, $statement_name, $query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Unable to create prepared statement handle', __FUNCTION__); + return $err; + } + } else { + $types_string = ''; + if ($pgtypes) { + $types_string = ' ('.implode(', ', $pgtypes).') '; + } + $query = 'PREPARE '.$statement_name.$types_string.' AS '.$query; + $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; + } + + // }}} + // {{{ function getSequenceName($sqn) + + /** + * adds sequence name formatting to a sequence name + * + * @param string name of the sequence + * + * @return string formatted sequence name + * + * @access public + */ + function getSequenceName($sqn) + { + if (false === $this->options['disable_smart_seqname']) { + if (strpos($sqn, '_') !== false) { + list($table, $field) = explode('_', $sqn, 2); + } + $schema_list = $this->queryOne("SELECT array_to_string(current_schemas(false), ',')"); + if (MDB2::isError($schema_list) || empty($schema_list) || count($schema_list) < 2) { + $order_by = ' a.attnum'; + $schema_clause = ' AND n.nspname=current_schema()'; + } else { + $schemas = explode(',', $schema_list); + $schema_clause = ' AND n.nspname IN ('.$schema_list.')'; + $counter = 1; + $order_by = ' CASE '; + foreach ($schemas as $schema) { + $order_by .= ' WHEN n.nspname='.$schema.' THEN '.$counter++; + } + $order_by .= ' ELSE '.$counter.' END, a.attnum'; + } + + $query = "SELECT substring((SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) + FROM pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) FROM 'nextval[^'']*''([^'']*)') + FROM pg_attribute a + LEFT JOIN pg_class c ON c.oid = a.attrelid + LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef + LEFT JOIN pg_namespace n ON c.relnamespace = n.oid + WHERE (c.relname = ".$this->quote($sqn, 'text'); + if (!empty($field)) { + $query .= " OR (c.relname = ".$this->quote($table, 'text')." AND a.attname = ".$this->quote($field, 'text').")"; + } + $query .= " )" + .$schema_clause." + AND NOT a.attisdropped + AND a.attnum > 0 + AND pg_get_expr(d.adbin, d.adrelid) LIKE 'nextval%' + ORDER BY ".$order_by; + $seqname = $this->queryOne($query); + if (!MDB2::isError($seqname) && !empty($seqname) && is_string($seqname)) { + return $seqname; + } + } + + return parent::getSequenceName($sqn); + } + + // }}} + // {{{ 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); + $query = "SELECT NEXTVAL('$sequence_name')"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->queryOne($query, 'integer'); + $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 could not be created', __FUNCTION__); + } + return $this->nextId($seq_name, false); + } + } + return $result; + } + + // }}} + // {{{ 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) + { + if (empty($table) && empty($field)) { + return $this->queryOne('SELECT lastval()', 'integer'); + } + $seq = $table.(empty($field) ? '' : '_'.$field); + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq), true); + return $this->queryOne("SELECT currval('$sequence_name')", 'integer'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + return $this->queryOne("SELECT last_value FROM $sequence_name", 'integer'); + } +} + +/** + * MDB2 PostGreSQL result driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Result_pgsql 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 (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 = @pg_fetch_array($this->result, null, PGSQL_ASSOC); + 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 = @pg_fetch_row($this->result); + } + if (!$row) { + if (false === $this->result) { + $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 = @pg_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. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + $cols = @pg_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + $connection = $this->db->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + + if (!($this->result = @pg_get_result($connection))) { + return false; + } + return MDB2_OK; + } + + // }}} + // {{{ 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 = @pg_free_result($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 PostGreSQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_BufferedResult_pgsql extends MDB2_Result_pgsql +{ + // {{{ 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) && !@pg_result_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (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 = @pg_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 PostGreSQL statement driver + * + * @package MDB2 + * @category Database + * @author Paul Cooper + */ +class MDB2_Statement_pgsql 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 (null === $this->statement) { + return parent::_execute($result_class, $result_wrap_class); + } + $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 = false; + $parameters = array(); + // todo: disabled until pg_execute() bytea issues are cleared up + if (true || !function_exists('pg_execute')) { + $query = 'EXECUTE '.$this->statement; + } + if (!empty($this->positions)) { + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (is_resource($value) || $type == 'clob' || $type == 'blob' || $this->db->options['lob_allow_url_include']) { + if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + $close = true; + } + if (is_resource($value)) { + $data = ''; + while (!@feof($value)) { + $data.= @fread($value, $this->db->options['lob_buffer_length']); + } + if ($close) { + @fclose($value); + } + $value = $data; + } + } + $quoted = $this->db->quote($value, $type, $query); + if (MDB2::isError($quoted)) { + return $quoted; + } + $parameters[] = $quoted; + } + if ($query) { + $query.= ' ('.implode(', ', $parameters).')'; + } + } + + if (!$query) { + $result = @pg_execute($connection, $this->statement, $parameters); + if (!$result) { + $err = $this->db->raiseError(null, null, null, + 'Unable to execute statement', __FUNCTION__); + return $err; + } + } else { + $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 (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (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; + } + + /** + * 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; + } + + $name = $db->quoteIdentifier($name, true); + $result = $db->exec("DROP TABLE $name"); + + if (MDB2::isError($result)) { + $result = $db->exec("DROP TABLE $name CASCADE"); + } + + return $result; + } +} +?> diff --git a/extlib/MDB2/Driver/querysim.php b/extlib/MDB2/Driver/querysim.php new file mode 100644 index 0000000000..a0551ddfa4 --- /dev/null +++ b/extlib/MDB2/Driver/querysim.php @@ -0,0 +1,824 @@ + | +// | Bert Dawson | +// +----------------------------------------------------------------------+ +// | Original PHP Author: Alan Richmond | +// | David Huyck | +// +----------------------------------------------------------------------+ +// | Special note concerning code documentation: | +// | QuerySim was originally created for use during development of | +// | applications built using the Fusebox framework. (www.fusebox.org) | +// | Fusebox uses an XML style of documentation called Fusedoc. (Which | +// | is admittedly not well suited to documenting classes and functions. | +// | This short-coming is being addressed by the Fusebox community.) PEAR | +// | uses a Javadoc style of documentation called PHPDoc. (www.phpdoc.de) | +// | Since this class extension spans two groups of users, it is asked | +// | that the members of each respect the documentation standard of the | +// | other. So it is a further requirement that both documentation | +// | standards be included and maintained. If assistance is required | +// | please contact Alan Richmond. | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/* + + + I take information and turn it into a recordset that can be accessed + through the PEAR MDB2 API. Based on Hal Helms' QuerySim.cfm ColdFusion + custom tag available at halhelms.com. + + + + + + + + Extensive revision that is backwardly compatible but eliminates the + need for a separate .sim file. + + + Rewrote in PHP as an extention to the PEAR DB API. + Functions supported: + connect, disconnect, query, fetchRow, freeResult, + numCols, numRows, getSpecialQuery + David Huyck (bombusbee.com) added ability to escape special + characters (i.e., delimiters) using a '\'. + Extended PEAR DB options[] for adding incoming parameters. Added + options: columnDelim, dataDelim, eolDelim + + + Added the ability to set the QuerySim options at runtime. + Default options are: + 'columnDelim' => ',', // Commas split the column names + 'dataDelim' => '|', // Pipes split the data fields + 'eolDelim' => chr(13).chr(10) // Carriage returns split the + // lines of data + Affected functions are: + DB_querysim(): set the default options when the + constructor method is called + _parseQuerySim($query): altered the parsing of lines, column + names, and data fields + _empty2null: altered the way this function is called + to simplify calling it + + + Added error catching for malformed QuerySim text. + Bug fix _empty2null(): altered version was returning unmodified + lineData. + Cleanup: + PEAR compliant formatting, finished PHPDocs and added 'out' to + Fusedoc 'io'. + Broke up _parseQuerySim() into _buildResult() and _parseOnDelim() + to containerize duplicate parse code. + + + Edited the _buildResult() and _parseOnDelim() functions to improve + reliability of special character escaping. + Re-introduced a custom setOption() method to throw an error when a + person tries to set one of the delimiters to '\'. + + + Added '/' delimiter param to preg_quote() in _empty2null() and + _parseOnDelim() so '/' can be used as a delimiter. + Added error check for columnDelim == eolDelim or dataDelim == eolDelim. + Renamed some variables for consistancy. + + + Removed protected function _empty2null(). Turns out preg_split() + deals with empty elemants by making them zero length strings, just + what they ended up being anyway. This should speed things up a little. + Affected functions: + _parseOnDelim() perform trim on line here, instead of in + _empty2null(). + _buildResult() remove call to _empty2null(). + _empty2null() removed function. + + + Ported to PEAR MDB2. + Methods supported: + connect, query, getColumnNames, numCols, valid, fetch, + numRows, free, fetchRow, nextResult, setLimit + (inherited). + + + + Changed default eolDelim to a *nix file eol, since we're trimming + the result anyway, it makes no difference for Windows. Now only + Mac file eols should need to be set (and other kinds of chars). + + + Got WAY too long. See querysim_readme.txt for instructions and some + examples. + io section only documents elements of DB_result that DB_querysim uses, + adds or changes; see MDB2 and MDB2_Driver_Common for more info. + io section uses some elements that are not Fusedoc 2.0 compliant: + object and resource. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +/** + * MDB2 QuerySim driver + * + * @package MDB2 + * @category Database + * @author Alan Richmond + */ +class MDB2_Driver_querysim extends MDB2_Driver_Common +{ + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'querysim'; + $this->dbsyntax = 'querysim'; + + // Most of these are dummies to simulate availability if checked + $this->supported['sequences'] = false; + $this->supported['indexes'] = false; + $this->supported['affected_rows'] = false; + $this->supported['summary_functions'] = false; + $this->supported['order_by_text'] = false; + $this->supported['current_id'] = false; + $this->supported['limit_queries'] = true;// this one is real + $this->supported['LOBs'] = true; + $this->supported['replace'] = false; + $this->supported['sub_selects'] = false; + $this->supported['transactions'] = false; + $this->supported['savepoints'] = false; + $this->supported['auto_increment'] = false; + $this->supported['primary_key'] = false; + $this->supported['result_introspection'] = false; // not implemented + $this->supported['prepared_statements'] = false; + $this->supported['identifier_quoting'] = false; + $this->supported['pattern_escaping'] = false; + $this->supported['new_link'] = false; + + $this->options['columnDelim'] = ','; + $this->options['dataDelim'] = '|'; + $this->options['eolDelim'] = "\n"; + } + + // }}} + // {{{ connect() + + /** + * Open a file or simulate a successful database connect + * + * @access public + * + * @return mixed MDB2_OK string on success, a MDB2 error object on failure + */ + function connect() + { + if (is_resource($this->connection)) { + if ($this->connected_database_name == $this->database_name + && ($this->opened_persistent == $this->options['persistent']) + ) { + return MDB2_OK; + } + if ($this->connected_database_name) { + $this->_close($this->connection); + } + $this->disconnect(); + } + + $connection = 1;// sim connect + // if external, check file... + if ($this->database_name) { + $file = $this->database_name; + if (!file_exists($file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'file not found', __FUNCTION__); + } + if (!is_file($file)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'not a file', __FUNCTION__); + } + if (!is_readable($file)) { + return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null, + 'could not open file - check permissions', __FUNCTION__); + } + // ...and open if persistent + if ($this->options['persistent']) { + $connection = @fopen($file, 'r'); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + + $this->connection = $connection; + $this->connected_database_name = $this->database_name; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + return MDB2_OK; + } + + // }}} + // {{{ 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->opened_persistent) { + @fclose($this->connection); + } + } + return parent::disconnect($force); + } + + // }}} + // {{{ _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) + { + if ($this->database_name) { + $query = $this->_readFile(); + } + $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 ($is_manip) { + $err =& $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Manipulation statements are not supported', __FUNCTION__); + return $err; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + $result = $this->_buildResult($query); + if (MDB2::isError($result)) { + return $result; + } + + if ($this->next_limit > 0) { + $result[1] = array_slice($result[1], $this->next_offset, $this->next_limit); + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _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) + { + $this->next_limit = $limit; + $this->next_offset = $offset; + return $query; + } + + // }}} + // {{{ _readFile() + + /** + * Read an external file + * + * @param string filepath/filename + * + * @access protected + * + * @return string the contents of a file + */ + function _readFile() + { + $buffer = ''; + if ($this->opened_persistent) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + while (!feof($connection)) { + $buffer.= fgets($connection, 1024); + } + rewind($connection); + } else { + $connection = @fopen($this->connected_database_name, 'r'); + while (!feof($connection)) { + $buffer.= fgets($connection, 1024); + } + @fclose($connection); + } + return $buffer; + } + + // }}} + // {{{ _buildResult() + + /** + * Convert QuerySim text into an array + * + * @param string Text of simulated query + * + * @access protected + * + * @return multi-dimensional array containing the column names and data + * from the QuerySim + */ + function _buildResult($query) + { + $eolDelim = $this->options['eolDelim']; + $columnDelim = $this->options['columnDelim']; + $dataDelim = $this->options['dataDelim']; + + $columnNames = array(); + $data = array(); + + if ($columnDelim == $eolDelim) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'columnDelim and eolDelim must be different', __FUNCTION__); + } elseif ($dataDelim == $eolDelim){ + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'dataDelim and eolDelim must be different', __FUNCTION__); + } + + $query = trim($query); + //tokenize escaped slashes + $query = str_replace('\\\\', '[$double-slash$]', $query); + + if (!strlen($query)) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'empty querysim text', __FUNCTION__); + } + $lineData = $this->_parseOnDelim($query, $eolDelim); + //kill the empty last row created by final eol char if it exists + if (!strlen(trim($lineData[count($lineData) - 1]))) { + unset($lineData[count($lineData) - 1]); + } + //populate columnNames array + $thisLine = each($lineData); + $columnNames = $this->_parseOnDelim($thisLine[1], $columnDelim); + if ((in_array('', $columnNames)) || (in_array('NULL', $columnNames))) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'all column names must be defined', __FUNCTION__); + } + //replace double-slash tokens with single-slash + $columnNames = str_replace('[$double-slash$]', '\\', $columnNames); + $columnCount = count($columnNames); + $rowNum = 0; + //loop through data lines + if (count($lineData) > 1) { + while ($thisLine = each($lineData)) { + $thisData = $this->_parseOnDelim($thisLine[1], $dataDelim); + $thisDataCount = count($thisData); + if ($thisDataCount != $columnCount) { + $fileLineNo = $rowNum + 2; + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + "number of data elements ($thisDataCount) in line $fileLineNo not equal to number of defined columns ($columnCount)", __FUNCTION__); + } + //loop through data elements in data line + foreach ($thisData as $thisElement) { + if (strtoupper($thisElement) == 'NULL'){ + $thisElement = ''; + } + //replace double-slash tokens with single-slash + $data[$rowNum][] = str_replace('[$double-slash$]', '\\', $thisElement); + }//end foreach + ++$rowNum; + }//end while + }//end if + return array($columnNames, $data); + } + + // }}} + // {{{ _parseOnDelim() + + /** + * Split QuerySim string into an array on a delimiter + * + * @param string $thisLine Text of simulated query + * @param string $delim The delimiter to split on + * + * @access protected + * + * @return array containing parsed string + */ + function _parseOnDelim($thisLine, $delim) + { + $delimQuoted = preg_quote($delim, '/'); + $thisLine = trim($thisLine); + + $parsed = preg_split('/(?options['eolDelim']) { + //replaces escape chars + $parsed = preg_replace('/\\\\/', '', $parsed); + } + return $parsed; + } + + // }}} + // {{{ &exec() + + /** + * Execute a manipulation query to the database and return any the affected rows + * + * @param string $query the SQL query + * @return mixed affected rows on success, a MDB2 error on failure + * @access public + */ + function &exec($query) + { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Querysim only supports reading data', __FUNCTION__); + } + + // }}} + // {{{ 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) + { + $server_info = '@package_version@'; + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + if (!isset($tmp[2]) || !preg_match('/(\d+)(.*)/', $tmp[2], $tmp2)) { + $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' => isset($tmp2[0]) ? $tmp2[0] : null, + 'extra' => isset($tmp2[1]) ? $tmp2[1] : null, + 'native' => $server_info, + ); + } + return $server_info; + } +} + +/** + * MDB2 QuerySim result driver + * + * @package MDB2 + * @category Database + * @author Alan Richmond + */ +class MDB2_Result_querysim 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 (false === $this->result) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + if (null === $this->result) { + return null; + } + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (MDB2::isError($seek)) { + return $seek; + } + } + $target_rownum = $this->rownum + 1; + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if (!isset($this->result[1][$target_rownum])) { + $null = null; + return $null; + } + $row = $this->result[1][$target_rownum]; + // make row associative + if ( $fetchmode == MDB2_FETCHMODE_ASSOC + || $fetchmode == MDB2_FETCHMODE_OBJECT + ) { + $column_names = $this->getColumnNames(); + foreach ($column_names as $name => $i) { + $column_names[$name] = $row[$i]; + } + $row = $column_names; + } + $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 + || $detchmode == 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 { + $row = new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return array(); + } + $columns = array_flip($this->result[0]); + 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. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + $cols = count($this->result[0]); + return $cols; + } +} + +/** + * MDB2 QuerySim buffered result driver + * + * @package MDB2 + * @category Database + * @author Alan Richmond + */ +class MDB2_BufferedResult_querysim extends MDB2_Result_querysim +{ + // }}} + // {{{ 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 (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __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() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + $rows = count($this->result[1]); + return $rows; + } +} + +/** + * MDB2 QuerySim statement driver + * + * @package MDB2 + * @category Database + * @author Alan Richmond + */ +class MDB2_Statement_querysim extends MDB2_Statement_Common +{ + +} + +?> diff --git a/extlib/MDB2/Driver/sqlite.php b/extlib/MDB2/Driver/sqlite.php new file mode 100644 index 0000000000..cf980d8de1 --- /dev/null +++ b/extlib/MDB2/Driver/sqlite.php @@ -0,0 +1,1104 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * MDB2 SQLite driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_sqlite extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + var $_lasterror = ''; + + var $fix_assoc_fields_names = false; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'sqlite'; + $this->dbsyntax = 'sqlite'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $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['transactions'] = true; + $this->supported['savepoints'] = false; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = false; // requires alter table implementation + $this->supported['result_introspection'] = false; // not implemented + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = false; + $this->supported['new_link'] = false; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['base_transaction_name'] = '___php_MDB2_sqlite_auto_commit_off'; + $this->options['fixed_float'] = 0; + $this->options['database_path'] = ''; + $this->options['database_extension'] = ''; + $this->options['server_version'] = ''; + $this->options['max_identifiers_length'] = 128; //no real limit + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + $native_code = null; + if ($this->connection) { + $native_code = @sqlite_last_error($this->connection); + } + $native_msg = $this->_lasterror + ? html_entity_decode($this->_lasterror) : @sqlite_error_string($native_code); + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg); + + if (null === $error) { + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE, + '/^no such index:/' => MDB2_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT, + '/is not unique/' => MDB2_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT, + '/violates .*constraint/' => MDB2_ERROR_CONSTRAINT, + '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD, + '/no column named/' => MDB2_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg)) { + $error = $code; + break; + } + } + } + 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) + { + $text = @sqlite_escape_string($text); + 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)); + if (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if ($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 = 'BEGIN TRANSACTION '.$this->options['base_transaction_name']; + $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 (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name']; + $result = $this->_doQuery($query, true); + if (MDB2::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ + + /** + * 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 (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name']; + $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)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $isolation = 0; + break; + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + $isolation = 1; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "PRAGMA read_uncommitted=$isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ getDatabaseFile() + + /** + * Builds the string with path+dbname+extension + * + * @return string full database path+file + * @access protected + */ + function _getDatabaseFile($database_name) + { + if ($database_name === '' || $database_name === ':memory:') { + return $database_name; + } + return $this->options['database_path'].$database_name.$this->options['database_extension']; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + **/ + function connect() + { + $database_file = $this->_getDatabaseFile($this->database_name); + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->connected_database_name == $database_file + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!extension_loaded($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + if (empty($this->database_name)) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($database_file !== ':memory:') { + if (!file_exists($database_file)) { + if (!touch($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not create database file', __FUNCTION__); + } + if (!isset($this->dsn['mode']) + || !is_numeric($this->dsn['mode']) + ) { + $mode = 0644; + } else { + $mode = octdec($this->dsn['mode']); + } + if (!chmod($database_file, $mode)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be chmodded database file', __FUNCTION__); + } + if (!file_exists($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be found database file', __FUNCTION__); + } + } + if (!is_file($database_file)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Database is a directory name', __FUNCTION__); + } + if (!is_readable($database_file)) { + return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null, + 'Could not read database file', __FUNCTION__); + } + } + + $connect_function = ($this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'); + $php_errormsg = ''; + if (version_compare('5.1.0', PHP_VERSION, '>')) { + @ini_set('track_errors', true); + $connection = @$connect_function($database_file); + @ini_restore('track_errors'); + } else { + $connection = @$connect_function($database_file, 0666, $php_errormsg); + } + $this->_lasterror = $php_errormsg; + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($this->fix_assoc_fields_names || + $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES) + { + @sqlite_query("PRAGMA short_column_names = 1", $connection); + $this->fix_assoc_fields_names = true; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $database_file; + $this->opened_persistent = $this->getoption('persistent'); + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + return MDB2_OK; + } + + // }}} + // {{{ 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) + { + $database_file = $this->_getDatabaseFile($name); + $result = file_exists($database_file); + 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) { + @sqlite_close($this->connection); + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ _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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + + $function = $this->options['result_buffering'] + ? 'sqlite_query' : 'sqlite_unbuffered_query'; + $php_errormsg = ''; + if (version_compare('5.1.0', PHP_VERSION, '>')) { + @ini_set('track_errors', true); + do { + $result = @$function($query.';', $connection); + } while (sqlite_last_error($connection) == SQLITE_SCHEMA); + @ini_restore('track_errors'); + } else { + do { + $result = @$function($query.';', $connection, SQLITE_BOTH, $php_errormsg); + } while (sqlite_last_error($connection) == SQLITE_SCHEMA); + } + $this->_lasterror = $php_errormsg; + + if (!$result) { + $code = null; + if (0 === strpos($this->_lasterror, 'no such table')) { + $code = MDB2_ERROR_NOSUCHTABLE; + } + $err = $this->raiseError($code, 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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @sqlite_changes($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) { + 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); + } + if ($is_manip) { + $query.= " LIMIT $limit"; + } else { + $query.= " LIMIT $offset,$limit"; + } + } + 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) + { + $server_info = false; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } elseif ($this->options['server_version']) { + $server_info = $this->options['server_version']; + } elseif (function_exists('sqlite_libversion')) { + $server_info = @sqlite_libversion(); + } + if (!$server_info) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Requires either the "server_version" option or the sqlite_libversion() function', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => null, + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ 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 SQLite 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 + * + * @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->options['seqcol_name']; + $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) + { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $value = @sqlite_last_insert_rowid($connection); + if (!$value) { + return $this->raiseError(null, null, null, + 'Could not get last insert ID', __FUNCTION__); + } + return $value; + } + + // }}} + // {{{ 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 SQLite result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_sqlite 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 (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 = @sqlite_fetch_array($this->result, SQLITE_ASSOC); + 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 = @sqlite_fetch_array($this->result, SQLITE_NUM); + } + if (!$row) { + if (false === $this->result) { + $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 = @sqlite_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. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + $cols = @sqlite_num_fields($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } +} + +/** + * MDB2 SQLite buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_sqlite extends MDB2_Result_sqlite +{ + // {{{ 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 (!@sqlite_seek($this->result, $rownum)) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (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 = @sqlite_num_rows($this->result); + if (null === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 SQLite statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_sqlite extends MDB2_Statement_Common +{ + +} +?> diff --git a/extlib/MDB2/Driver/sqlite3.php b/extlib/MDB2/Driver/sqlite3.php new file mode 100644 index 0000000000..a4afd538c3 --- /dev/null +++ b/extlib/MDB2/Driver/sqlite3.php @@ -0,0 +1,1112 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * MDB2 SQLite3 driver + * + * @package MDB2 + * @category Database + * @author Lorenzo Alberton + */ +class MDB2_Driver_sqlite3 extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + var $_lasterror = ''; + + var $fix_assoc_fields_names = false; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'sqlite3'; + $this->dbsyntax = 'sqlite3'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $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['transactions'] = true; + $this->supported['savepoints'] = false; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = false; // requires alter table implementation + $this->supported['result_introspection'] = false; // not implemented + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = false; + $this->supported['new_link'] = false; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['base_transaction_name'] = '___php_MDB2_sqlite3_auto_commit_off'; + $this->options['fixed_float'] = 0; + $this->options['database_path'] = ''; + $this->options['database_extension'] = ''; + $this->options['server_version'] = ''; + $this->options['max_identifiers_length'] = 128; //no real limit + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + $native_code = null; + if ($this->connection) { + $native_code = @$this->connection->lastErrorCode(); + } + $native_msg = $this->_lasterror + ? html_entity_decode($this->_lasterror) : '';//@$this->getConnection()->lastErrorMsg(); + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg); + + if (null === $error) { + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE, + '/^no such index:/' => MDB2_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT, + '/is not unique/' => MDB2_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT, + '/violates .*constraint/' => MDB2_ERROR_CONSTRAINT, + '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD, + '/no column named/' => MDB2_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg)) { + $error = $code; + break; + } + } + } + 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) + { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $text = @$connection->escapeString($text); + 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)); + if (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if ($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 = 'BEGIN TRANSACTION '.$this->options['base_transaction_name']; + $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 (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name']; + $result = $this->_doQuery($query, true); + if (MDB2::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ + + /** + * 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 (null !== $savepoint) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name']; + $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)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $isolation = 0; + break; + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + $isolation = 1; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "PRAGMA read_uncommitted=$isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ getDatabaseFile() + + /** + * Builds the string with path+dbname+extension + * + * @return string full database path+file + * @access protected + */ + function _getDatabaseFile($database_name) + { + if ($database_name === '' || $database_name === ':memory:') { + return $database_name; + } + return $this->options['database_path'].$database_name.$this->options['database_extension']; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + **/ + function connect() + { + $database_file = $this->_getDatabaseFile($this->database_name); + if (is_object($this->connection) && ($this->connection instanceof SQLite3)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->connected_database_name == $database_file + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!extension_loaded($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + if (empty($this->database_name)) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($database_file !== ':memory:') { + if (!file_exists($database_file)) { + if (!touch($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not create database file', __FUNCTION__); + } + if (!isset($this->dsn['mode']) + || !is_numeric($this->dsn['mode']) + ) { + $mode = 0644; + } else { + $mode = octdec($this->dsn['mode']); + } + if (!chmod($database_file, $mode)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be chmodded database file', __FUNCTION__); + } + if (!file_exists($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be found database file', __FUNCTION__); + } + } + if (!is_file($database_file)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Database is a directory name', __FUNCTION__); + } + if (!is_readable($database_file)) { + return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null, + 'Could not read database file', __FUNCTION__); + } + } + + $php_errormsg = ''; + @ini_set('track_errors', true); + $connection = new SQLite3($database_file); // persistent connection not supported? + @ini_restore('track_errors'); + + $this->_lasterror = $php_errormsg; + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($this->fix_assoc_fields_names || + $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES) + { + @$connection->query("PRAGMA short_column_names = 1"); + $this->fix_assoc_fields_names = true; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $database_file; + $this->opened_persistent = $this->getoption('persistent'); + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + return MDB2_OK; + } + + // }}} + // {{{ 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) + { + $database_file = $this->_getDatabaseFile($name); + $result = file_exists($database_file); + 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_object($this->connection) && ($this->connection instanceof SQLite3)) { + 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) { + @$this->connection->close(); + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ _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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + + $php_errormsg = ''; + @ini_set('track_errors', true); + do { + $result = @$connection->query($query.';'); // result_buffering unsupported? + } while ($connection->lastErrorCode() == 17); // SQLITE_SCHEMA + @ini_restore('track_errors'); + $this->_lasterror = $php_errormsg; + + if (!$result) { + $code = null; + if (false !== strpos($this->_lasterror, 'no such table')) { + $code = MDB2_ERROR_NOSUCHTABLE; + } + $err = $this->raiseError($code, 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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + return @$connection->changes(); + } + + // }}} + // {{{ _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) { + 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); + } + if ($is_manip) { + $query.= " LIMIT $limit"; + } else { + $query.= " LIMIT $offset,$limit"; + } + } + 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) + { + $server_info = false; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } elseif ($this->options['server_version']) { + $server_info = $this->options['server_version']; + } elseif (method_exists('SQLite3', 'version')) { + $tmp = @SQLite3::version(); + $server_info = $tmp['versionString']; + } + if (!$server_info) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Requires either the "server_version" option or the SQLite3::version() function', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => null, + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ 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 SQLite 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 + * + * @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->options['seqcol_name']; + $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)) { + $conn = $this->getConnection(); + 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__); + } + 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) + { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + $value = @$connection->lastInsertRowID(); + if (!$value) { + return $this->raiseError(null, null, null, + 'Could not get last insert ID', __FUNCTION__); + } + return $value; + } + + // }}} + // {{{ 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 SQLite result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_sqlite3 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 (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 = @$this->result->fetchArray(SQLITE3_ASSOC); + 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 = @$this->result->fetchArray(SQLITE3_NUM); + } + if (!$row) { + if (false === $this->result) { + $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 = @$this->result->columnName($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. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + $cols = @$this->result->numColumns(); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } +} + +/** + * MDB2 SQLite buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_sqlite3 extends MDB2_Result_sqlite3 +{ + // {{{ 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 (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return MDB2_OK; + } + if ($rownum == 0) { + $this->result->reset(); + $this->rownum = -1; + return MDB2_OK; + } + $this->result->reset(); + $this->rownum = -1; + $res = true; + while ($rownum > -1 && $res !== false) { + $res = $this->result->fetchArray(); + $this->rownum++; + $rownum--; + } + if ($res === false) { + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + 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() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + $previous = $this->rownum; // save cursor position + $this->result->reset(); + $nrows = 0; + while ($this->result->fetchArray()) { + $nrows++; + } + $this->seek($previous); // restore cursor position + $this->rownum = $previous; + return $nrows; + } +} + +/** + * MDB2 SQLite statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_sqlite3 extends MDB2_Statement_Common +{ + +} +?> diff --git a/extlib/MDB2/Driver/sqlsrv.php b/extlib/MDB2/Driver/sqlsrv.php new file mode 100644 index 0000000000..13355935b7 --- /dev/null +++ b/extlib/MDB2/Driver/sqlsrv.php @@ -0,0 +1,1174 @@ + | +// +----------------------------------------------------------------------+ +// {{{ Class MDB2_Driver_sqlsrv +/** + * MDB2 MSSQL Server (native) driver + * + * @package MDB2 + * @category Database + */ +class MDB2_Driver_sqlsrv extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + var $identifier_quoting = array('start' => '[', 'end' => ']', 'escape' => ']'); + + var $connection = null; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'sqlsrv'; + $this->dbsyntax = 'sqlsrv'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['summary_functions'] = true; + $this->supported['transactions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['savepoints'] = false; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = 'emulated'; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = 'emulated'; + $this->supported['identifier_quoting'] = false; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['database_device'] = false; + $this->options['database_size'] = false; + $this->options['max_identifiers_length'] = 128; // MS Access: 64 + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null, $connection = null) + { + if (null === $connection) { + $connection = $this->connection; + } + + $native_code = null; + $native_msg = null; + if ($connection) { + $retErrors = sqlsrv_errors(SQLSRV_ERR_ALL); + if ($retErrors !== null) { + foreach ($retErrors as $arrError) { + $native_msg .= "SQLState: ".$arrError[ 'SQLSTATE']."\n"; + $native_msg .= "Error Code: ".$arrError[ 'code']."\n"; + $native_msg .= "Message: ".$arrError[ 'message']."\n"; + $native_code = $arrError[ 'code']; + } + } + } + if (null === $error) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 102 => MDB2_ERROR_SYNTAX, + 110 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 155 => MDB2_ERROR_NOSUCHFIELD, + 156 => MDB2_ERROR_SYNTAX, + 170 => MDB2_ERROR_SYNTAX, + 207 => MDB2_ERROR_NOSUCHFIELD, + 208 => MDB2_ERROR_NOSUCHTABLE, + 245 => MDB2_ERROR_INVALID_NUMBER, + 319 => MDB2_ERROR_SYNTAX, + 321 => MDB2_ERROR_NOSUCHFIELD, + 325 => MDB2_ERROR_SYNTAX, + 336 => MDB2_ERROR_SYNTAX, + 515 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 547 => MDB2_ERROR_CONSTRAINT, + 911 => MDB2_ERROR_NOT_FOUND, + 1018 => MDB2_ERROR_SYNTAX, + 1035 => MDB2_ERROR_SYNTAX, + 1801 => MDB2_ERROR_ALREADY_EXISTS, + 1913 => MDB2_ERROR_ALREADY_EXISTS, + 2209 => MDB2_ERROR_SYNTAX, + 2223 => MDB2_ERROR_SYNTAX, + 2248 => MDB2_ERROR_SYNTAX, + 2256 => MDB2_ERROR_SYNTAX, + 2257 => MDB2_ERROR_SYNTAX, + 2627 => MDB2_ERROR_CONSTRAINT, + 2714 => MDB2_ERROR_ALREADY_EXISTS, + 3607 => MDB2_ERROR_DIVZERO, + 3701 => MDB2_ERROR_NOSUCHTABLE, + 7630 => MDB2_ERROR_SYNTAX, + 8134 => MDB2_ERROR_DIVZERO, + 9303 => MDB2_ERROR_SYNTAX, + 9317 => MDB2_ERROR_SYNTAX, + 9318 => MDB2_ERROR_SYNTAX, + 9331 => MDB2_ERROR_SYNTAX, + 9332 => MDB2_ERROR_SYNTAX, + 15253 => MDB2_ERROR_SYNTAX, + ); + } + if (isset($ecode_map[$native_code])) { + if ($native_code == 3701 + && preg_match('/Cannot drop the index/i', $native_msg) + ) { + $error = MDB2_ERROR_NOT_FOUND; + } else { + $error = $ecode_map[$native_code]; + } + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ function escapePattern($text) + + /** + * Quotes pattern (% and _) characters in a string) + * + * @param string the input string to quote + * + * @return string quoted string + * + * @access public + */ + function escapePattern($text) + { + $text = str_replace("[", "[ [ ]", $text); + foreach ($this->wildcards as $wildcard) { + $text = str_replace($wildcard, '[' . $wildcard . ']', $text); + } + 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)); + if (null !== $savepoint) { + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVE TRANSACTION '.$savepoint; + return $this->_doQuery($query, true); + } + if ($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'); + } + if (MDB2::isError(sqlsrv_begin_transaction($this->connection))) { + return MDB2_ERROR; + } + $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 (null !== $savepoint) { + return MDB2_OK; + } + + if (MDB2::isError(sqlsrv_commit($this->connection))) { + return MDB2_ERROR; + } + $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 (null !== $savepoint) { + $query = 'ROLLBACK TRANSACTION '.$savepoint; + return $this->_doQuery($query, true); + } + + if (MDB2::isError(sqlsrv_rollback($this->connection))) { + return MDB2_ERROR; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ _doConnect() + + /** + * do the grunt work of the connect + * + * @return connection on success or MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $database=null, $persistent = false) + { + if (!extension_loaded($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not installed PHP', __FUNCTION__); + } + + $host = $this->dsn['hostspec'] ? $this->dsn['hostspec'] : '.\\SQLEXPRESS'; + $params = array( + 'UID' => $username ? $username : null, + 'PWD' => $password ? $password : null, + ); + if ($database) { + $params['Database'] = $database; + } + + if ($this->dsn['port'] && $this->dsn['port'] != 1433) { + $host .= ','.$this->dsn['port']; + } + + $connection = @sqlsrv_connect($host, $params); + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__, __FUNCTION__); + } + if (null !== $database) { + $this->connected_database_name = $database; + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (MDB2::isError($result)) { + return $result; + } + } + + if (empty($this->dsn['disable_iso_date'])) { + @sqlsrv_query($connection,'SET DATEFORMAT ymd'); + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + if (is_resource($this->connection)) { + if (MDB2::areEquals($this->connected_dsn, $this->dsn)) { + return MDB2_OK; + } + $this->disconnect(false); + } + + $connection = $this->_doConnect( + $this->dsn['username'], + $this->dsn['password'], + $this->database_name, + $this->options['persistent'] + ); + if (MDB2::isError($connection)) { + return $connection; + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $this->database_name; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + return MDB2_OK; + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name 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']); + if (MDB2::isError($connection)) { + return MDB2_ERROR_CONNECT_FAILED; + } + $result = @sqlsrv_query($connection,'select name from master..sysdatabases where name = \''.strtolower($name).'\''); + if (@sqlsrv_fetch($result)) { + return true; + } + return MDB2_ERROR_NOT_FOUND; + } + + // }}} + // {{{ 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; + } + + @sqlsrv_close($this->connection); + } + 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->database_name, $this->options['persistent']); + if (MDB2::isError($connection)) { + return $connection; + } + + $query = $this->_modifyQuery($query, $is_manip, $this->limit, $this->offset); + $this->offset = $this->limit = 0; + + $result = $this->_doQuery($query, $is_manip, $connection); + if (!MDB2::isError($result)) { + $result = $this->_affectedRows($connection, $result); + } + + @sqlsrv_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 (null === $connection) { + $connection = $this->getConnection(); + if (MDB2::isError($connection)) { + return $connection; + } + } + if (null === $database_name) { + $database_name = $this->database_name; + } + + if ($database_name && $database_name != $this->connected_database_name) { + $connection = $this->_doConnect($this->dsn['username'],$this->dsn['password'],$database_name); + if (MDB2::isError($connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $database_name; + } + + $query = preg_replace('/DATE_FORMAT\((MIN\()?([\w|.]*)(\))?\\Q, \'%Y-%m-%d\')\E/i','CONVERT(varchar(10),$1$2$3,120)',$query); + $query = preg_replace('/DATE_FORMAT\(([\w|.]*)\, \'\%Y\-\%m\-\%d %H\:00\:00\'\)/i','CONVERT(varchar(13),$1,120)+\':00:00\'',$query); + $result = @sqlsrv_query($connection,$query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + $this->result = $result; + $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 (null === $result) { + $result = $this->result; + } + return sqlsrv_rows_affected($this->result); + } + + // }}} + // {{{ _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 ($limit > 0) { + $fetch = $offset + $limit; + if (!$is_manip) { + return preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i', + "\\1SELECT\\2 TOP $fetch", $query); + } + } + 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) + { + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $this->connect(); + $server_info = sqlsrv_server_info($this->connection); + } + // cache server_info + $this->connected_server_info = $server_info; + $version = $server_info['SQLServerVersion']; + if (!$native) { + if (preg_match('/(\d+)\.(\d+)\.(\d+)/', $version, $tmp)) { + $version = array( + 'major' => $tmp[1], + 'minor' => $tmp[2], + 'patch' => $tmp[3], + 'extra' => null, + 'native' => $version, + ); + } else { + $version = array( + 'major' => null, + 'minor' => null, + 'patch' => null, + 'extra' => null, + 'native' => $version, + ); + } + } + return $version; + } + + // }}} + // {{{ _checkSequence + + /** + * Checks if there's a sequence that exists. + * + * @param string $seq_name The sequence name to verify. + * @return bool $tableExists The value if the table exists or not + * @access private + */ + function _checkSequence($seq_name) + { + $query = "SELECT * FROM $seq_name"; + $tableExists =& $this->_doQuery($query, true); + if (MDB2::isError($tableExists)) { + if ($tableExists->getCode() == MDB2_ERROR_NOSUCHTABLE) { + return false; + } + return false; + } + if (@sqlsrv_fetch($tableExists)) { + return true; + } + return false; + } + + // }}} + // {{{ 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); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + + $seq_val = $this->_checkSequence($sequence_name); + + if ($seq_val) { + $query = "SET IDENTITY_INSERT $sequence_name OFF ". + "INSERT INTO $sequence_name DEFAULT VALUES"; + } else { + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (0)"; + } + $result = $this->_doQuery($query, true); + $this->popExpect(); + $this->popErrorHandling(); + if (MDB2::isError($result)) { + if ($ondemand && !$this->_checkSequence($sequence_name)) { + $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 { + /** + * Little off-by-one problem with the sequence emulation + * here being fixed, that instead of re-calling nextID + * and forcing an increment by one, we simply check if it + * exists, then we get the last inserted id if it does. + * + * In theory, $seq_name should be created otherwise there would + * have been an error thrown somewhere up there.. + * + * @todo confirm + */ + if ($this->_checkSequence($seq_name)) { + return $this->lastInsertID($seq_name); + } + + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID($sequence_name); + 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) + { + return $this->queryOne("SELECT IDENT_CURRENT('$table')", 'integer'); + } + + // }}} +} + +// }}} +// {{{ Class MDB2_Result_mssql + +/** + * MDB2 MSSQL Server result driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Result_sqlsrv extends MDB2_Result_Common +{ + // {{{ constructor: function __construct($db, $result, $limit = 0, $offset = 0) + + /** + * Constructor + */ + function __construct($db, $result, $limit = 0, $offset = 0) +{ + $this->db = $db; + $this->result = $result; + $this->offset = $offset; + $this->limit = max(0, $limit - 1); + $this->cursor = 0; + $this->rows = array(); + $this->numFields = sqlsrv_num_fields($result); + $this->fieldMeta = sqlsrv_field_metadata($result); + $this->numRowsAffected = sqlsrv_rows_affected($result); + while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)) { + if ($row !== null) { + if ($this->offset && $this->offset_count < $this->offset) { + $this->offset_count++; + continue; + } + foreach ($row as $k => $v) { + if (is_object($v) && method_exists($v, 'format')) { + //DateTime Object + $row[$k] = $v->format('Y-m-d H:i:s'); + } + } + $this->rows[] = $row; //read results into memory, cursors are not supported + } + } + $this->rowcnt = count($this->rows); + } + + // }}} + // {{{ _skipLimitOffset() + + /** + * Skip the first row of a result set. + * + * @param resource $result + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access protected + */ +/* function _skipLimitOffset() + { + if ($this->limit) { + if ($this->rownum >= $this->limit) { + return false; + } + } + if ($this->offset) { + while ($this->offset_count < $this->offset) { + ++$this->offset_count; + if (!is_array(@sqlsrv_fetch_array($this->result))) { + $this->offset_count = $this->limit; + return false; + } + } + } + return MDB2_OK; + }*/ + + // }}} + function array_to_obj($array, &$obj) { + foreach ($array as $key => $value) { + if (is_array($value)) { + $obj->$key = new stdClass(); + array_to_obj($value, $obj->$key); + } else { + $obj->$key = $value; + } + } + return $obj; + } + // {{{ 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 (!$this->result) { + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, 'no valid statement given', __FUNCTION__); + } + if (($this->limit && $this->rownum >= $this->limit) || ($this->cursor >= $this->rowcnt || $this->rowcnt == 0)) { + return null; + } + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (MDB2::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + + $row = false; + $arrNum = array(); + if ($fetchmode == MDB2_FETCHMODE_ORDERED) { + foreach ($this->rows[$this->cursor] as $key=>$value) { + $arrNum[] = $value; + } + } + switch($fetchmode) { + case MDB2_FETCHMODE_ASSOC: + $row = $this->rows[$this->cursor]; + break; + case MDB2_FETCHMODE_ORDERED: + $row = $arrNum; + break; + case MDB2_FETCHMODE_OBJECT: + $o = new $this->db->options['fetch_class']; + $row = $this->array_to_obj($this->rows[$this->cursor], $o); + break; + } + $this->cursor++; + + /* + if ($fetchmode == MDB2_FETCHMODE_OBJECT) { + $row = sqlsrv_fetch_object($this->result,$this->db->options['fetch_class']); + } else { + switch($fetchmode) { + case MDB2_FETCHMODE_ASSOC: $fetchmode = SQLSRV_FETCH_ASSOC; break; + case MDB2_FETCHMODE_ORDERED: $fetchmode = SQLSRV_FETCH_NUMERIC; break; + case MDB2_FETCHMODE_DEFAULT: + default: + $fetchmode = SQLSRV_FETCH_BOTH; + } + $row = sqlsrv_fetch_array($this->result,$fetchmode); + } + foreach ($row as $key=>$value) { + if (is_object($value) && method_exists($value, 'format')) {//is DateTime object + $row[$key] = $value->format("Y-m-d H:i:s"); + } + }*/ + + /*if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + }*/ + if ($fetchmode == MDB2_FETCHMODE_ASSOC && is_array($row) && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + if (!$row) { + if (false === $this->result) { + $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); + } + ++$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() + { + if (!$this->result) { + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, 'no valid statement given', __FUNCTION__); + } + $columns = array(); + foreach ($this->fieldMeta as $n => $col) { + $columns[$col['Name']] = $n; + } + 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() + { + if (!$this->result) { + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, 'no valid statement given', __FUNCTION__); + } + $cols = $this->numFields; + if (!$cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal result pointer to the next available result + * + * @return true on success, false if there is no more result set or an error object on failure + * @access public + */ + function nextResult() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return false; + } + $ret = sqlsrv_next_result($this->result); + if ($ret) { + $this->cursor = 0; + $this->rows = array(); + $this->numFields = sqlsrv_num_fields($this->result); + $this->fieldMeta = sqlsrv_field_metadata($this->result); + $this->numRowsAffected = sqlsrv_rows_affected($this->result); + while ($row = sqlsrv_fetch_array($this->result, SQLSRV_FETCH_ASSOC)) { + if ($row !== null) { + if ($this->offset && $this->offset_count < $this->offset) { + $this->offset_count++; + continue; + } + foreach ($row as $k => $v) { + if (is_object($v) && method_exists($v, 'format')) {//DateTime Object + //$v->setTimezone(new DateTimeZone('GMT'));//TS_ISO_8601 with a trailing 'Z' is GMT + $row[$k] = $v->format("Y-m-d H:i:s"); + } + } + $this->rows[] = $row;//read results into memory, cursors are not supported + } + } + $this->rowcnt = count($this->rows); + } + return $ret; + } + + // }}} + // {{{ 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) { + if (!@sqlsrv_free_stmt($this->result)) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } + + // }}} + // {{{ function rowCount() + /** + * Returns the actual row number that was last fetched (count from 0) + * @return int + * + * @access public + */ + function rowCount() + { + return $this->cursor; +} + +// }}} + // {{{ function numRows() + +/** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * + * @access public + */ + function numRows() +{ + return $this->rowcnt; + } + + // }}} + // {{{ function seek($rownum = 0) + + /** + * Seek to a specific row in a result set + * + * @param int 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) + { + $this->cursor = min($rownum, $this->rowcnt); + return MDB2_OK; + } + + // }}} + } + + // }}} +// {{{ class MDB2_BufferedResult_mssql + +/** + * MDB2 MSSQL Server buffered result driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_BufferedResult_sqlsrv extends MDB2_Result_sqlsrv +{ + // {{{ 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); + } + + // }}} + +} + +// }}} +// {{{ MDB2_Statement_mssql + +/** + * MDB2 MSSQL Server statement driver + * + * @package MDB2 + * @category Database + * @author Frank M. Kromann + */ +class MDB2_Statement_sqlsrv extends MDB2_Statement_Common +{ + +} + +// }}} + +?> diff --git a/extlib/MDB2/Extended.php b/extlib/MDB2/Extended.php new file mode 100644 index 0000000000..148e71aef2 --- /dev/null +++ b/extlib/MDB2/Extended.php @@ -0,0 +1,723 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * Used by autoPrepare() + */ +define('MDB2_AUTOQUERY_INSERT', 1); +define('MDB2_AUTOQUERY_UPDATE', 2); +define('MDB2_AUTOQUERY_DELETE', 3); +define('MDB2_AUTOQUERY_SELECT', 4); + +/** + * MDB2_Extended: class which adds several high level methods to MDB2 + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Extended extends MDB2_Module_Common +{ + // {{{ autoPrepare() + + /** + * Generate an insert, update or delete query and call prepare() on it + * + * @param string table + * @param array the fields names + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * @param array that contains the types of the placeholders + * @param mixed 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 + * + * @return resource handle for the query + * @see buildManipSQL + * @access public + */ + function autoPrepare($table, $table_fields, $mode = MDB2_AUTOQUERY_INSERT, + $where = false, $types = null, $result_types = MDB2_PREPARE_MANIP) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (MDB2::isError($query)) { + return $query; + } + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + $lobs = array(); + foreach ((array)$types as $param => $type) { + if (($type == 'clob') || ($type == 'blob')) { + $lobs[$param] = $table_fields[$param]; + } + } + return $db->prepare($query, $types, $result_types, $lobs); + } + + // }}} + // {{{ autoExecute() + + /** + * Generate an insert, update or delete query and call prepare() and execute() on it + * + * @param string name of the table + * @param array assoc ($key=>$value) where $key is a field name and $value its value + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * @param array that contains the types of the placeholders + * @param string which specifies which result class to use + * @param mixed 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 + * + * @return bool|MDB2_Error true on success, a MDB2 error on failure + * @see buildManipSQL + * @see autoPrepare + * @access public + */ + function autoExecute($table, $fields_values, $mode = MDB2_AUTOQUERY_INSERT, + $where = false, $types = null, $result_class = true, $result_types = MDB2_PREPARE_MANIP) + { + $fields_values = (array)$fields_values; + if ($mode == MDB2_AUTOQUERY_SELECT) { + if (is_array($result_types)) { + $keys = array_keys($result_types); + } elseif (!empty($fields_values)) { + $keys = $fields_values; + } else { + $keys = array(); + } + } else { + $keys = array_keys($fields_values); + } + $params = array_values($fields_values); + if (empty($params)) { + $query = $this->buildManipSQL($table, $keys, $mode, $where); + + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + if ($mode == MDB2_AUTOQUERY_SELECT) { + $result = $db->query($query, $result_types, $result_class); + } else { + $result = $db->exec($query); + } + } else { + $stmt = $this->autoPrepare($table, $keys, $mode, $where, $types, $result_types); + if (MDB2::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute($params, $result_class); + $stmt->free(); + } + return $result; + } + + // }}} + // {{{ buildManipSQL() + + /** + * Make automaticaly an sql query for prepare() + * + * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), MDB2_AUTOQUERY_INSERT) + * will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * NB : - This belongs more to a SQL Builder class, but this is a simple facility + * - Be carefull ! If you don't give a $where param with an UPDATE/DELETE query, all + * the records of the table will be updated/deleted ! + * + * @param string name of the table + * @param ordered array containing the fields names + * @param int type of query to build + * MDB2_AUTOQUERY_INSERT + * MDB2_AUTOQUERY_UPDATE + * MDB2_AUTOQUERY_DELETE + * MDB2_AUTOQUERY_SELECT + * @param string (in case of update and delete queries, this string will be put after the sql WHERE statement) + * + * @return string sql query for prepare() + * @access public + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if ($db->options['quote_identifier']) { + $table = $db->quoteIdentifier($table); + } + + if (!empty($table_fields) && $db->options['quote_identifier']) { + foreach ($table_fields as $key => $field) { + $table_fields[$key] = $db->quoteIdentifier($field); + } + } + + if ((false !== $where) && (null !== $where)) { + if (is_array($where)) { + $where = implode(' AND ', $where); + } + $where = ' WHERE '.$where; + } + + switch ($mode) { + case MDB2_AUTOQUERY_INSERT: + if (empty($table_fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Insert requires table fields', __FUNCTION__); + } + $cols = implode(', ', $table_fields); + $values = '?'.str_repeat(', ?', (count($table_fields) - 1)); + return 'INSERT INTO '.$table.' ('.$cols.') VALUES ('.$values.')'; + break; + case MDB2_AUTOQUERY_UPDATE: + if (empty($table_fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Update requires table fields', __FUNCTION__); + } + $set = implode(' = ?, ', $table_fields).' = ?'; + $sql = 'UPDATE '.$table.' SET '.$set.$where; + return $sql; + break; + case MDB2_AUTOQUERY_DELETE: + $sql = 'DELETE FROM '.$table.$where; + return $sql; + break; + case MDB2_AUTOQUERY_SELECT: + $cols = !empty($table_fields) ? implode(', ', $table_fields) : '*'; + $sql = 'SELECT '.$cols.' FROM '.$table.$where; + return $sql; + break; + } + return $db->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'Non existant mode', __FUNCTION__); + } + + // }}} + // {{{ limitQuery() + + /** + * Generates a limited query + * + * @param string query + * @param array that contains the types of the columns in the result set + * @param integer the numbers of rows to fetch + * @param integer the row to start to fetching + * @param string which specifies which result class to use + * @param mixed string which specifies which class to wrap results in + * + * @return MDB2_Result|MDB2_Error result set on success, a MDB2 error on failure + * @access public + */ + function limitQuery($query, $types, $limit, $offset = 0, $result_class = true, + $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + $result = $db->setLimit($limit, $offset); + if (MDB2::isError($result)) { + return $result; + } + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ execParam() + + /** + * Execute a parameterized DML statement. + * + * @param string the SQL query + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * + * @return int|MDB2_Error affected rows on success, a MDB2 error on failure + * @access public + */ + function execParam($query, $params = array(), $param_types = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->exec($query); + } + + $stmt = $db->prepare($query, $param_types, MDB2_PREPARE_MANIP); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (MDB2::isError($result)) { + return $result; + } + + $stmt->free(); + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetch the first column of the first row of data returned from a query. + * Takes care of doing the query and freeing the results when finished. + * + * @param string the SQL query + * @param string that contains the type of the column in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int|string which column to return + * + * @return scalar|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getOne($query, $type = null, $params = array(), + $param_types = null, $colnum = 0) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + settype($params, 'array'); + settype($type, 'array'); + if (empty($params)) { + return $db->queryOne($query, $type, $colnum); + } + + $stmt = $db->prepare($query, $param_types, $type); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $one = $result->fetchOne($colnum); + $stmt->free(); + $result->free(); + return $one; + } + + // }}} + // {{{ getRow() + + /** + * Fetch the first row of data returned from a query. Takes care + * of doing the query and freeing the results when finished. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int the fetch mode to use + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getRow($query, $types = null, $params = array(), + $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryRow($query, $types, $fetchmode); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $row = $result->fetchRow($fetchmode); + $stmt->free(); + $result->free(); + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetch a single column from a result set and return it as an + * indexed array. + * + * @param string the SQL query + * @param string that contains the type of the column in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int|string which column to return + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getCol($query, $type = null, $params = array(), + $param_types = null, $colnum = 0) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + settype($params, 'array'); + settype($type, 'array'); + if (empty($params)) { + return $db->queryCol($query, $type, $colnum); + } + + $stmt = $db->prepare($query, $param_types, $type); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $col = $result->fetchCol($colnum); + $stmt->free(); + $result->free(); + return $col; + } + + // }}} + // {{{ getAll() + + /** + * Fetch all the rows returned from a query. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param int the fetch mode to use + * @param bool if set to true, the $all will have the first + * column as its first dimension + * @param bool $force_array used only when the query returns exactly + * two columns. If true, the values of the returned array will be + * one-element arrays instead of scalars. + * @param bool $group if true, the values of the returned array is + * wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getAll($query, $types = null, $params = array(), + $param_types = null, $fetchmode = MDB2_FETCHMODE_DEFAULT, + $rekey = false, $force_array = false, $group = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryAll($query, $types, $fetchmode, $rekey, $force_array, $group); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, $rekey, $force_array, $group); + $stmt->free(); + $result->free(); + return $all; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetch the entire result set of a query and return it as an + * associative array using the first column as the key. + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A MDB2 error code is + * returned on errors. If the result set contains fewer than two + * columns, a MDB2_ERROR_TRUNCATED error is returned. + * + * For example, if the table 'mytable' contains: + *
+     *   ID      TEXT       DATE
+     * --------------------------------
+     *   1       'one'      944679408
+     *   2       'two'      944679408
+     *   3       'three'    944679408
+     * 
+ * Then the call getAssoc('SELECT id,text FROM mytable') returns: + *
+     *    array(
+     *      '1' => 'one',
+     *      '2' => 'two',
+     *      '3' => 'three',
+     *    )
+     * 
+ * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + *
+     *    array(
+     *      '1' => array('one', '944679408'),
+     *      '2' => array('two', '944679408'),
+     *      '3' => array('three', '944679408')
+     *    )
+     * 
+ * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + *
+     * getAssoc('SELECT category,id,name FROM mytable', null, null
+     *           MDB2_FETCHMODE_ASSOC, false, true) returns:
+     *    array(
+     *      '1' => array(array('id' => '4', 'name' => 'number four'),
+     *                   array('id' => '6', 'name' => 'number six')
+     *             ),
+     *      '9' => array(array('id' => '4', 'name' => 'number four'),
+     *                   array('id' => '6', 'name' => 'number six')
+     *             )
+     *    )
+     * 
+ * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string the SQL query + * @param array that contains the types of the columns in the result set + * @param array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param array that contains the types of the values defined in $params + * @param bool $force_array used only when the query returns + * exactly two columns. If TRUE, the values of the returned array + * will be one-element arrays instead of scalars. + * @param bool $group if TRUE, the values of the returned array + * is wrapped in another array. If the same key value (in the first + * column) repeats itself, the values will be appended to this array + * instead of overwriting the existing values. + * + * @return array|MDB2_Error data on success, a MDB2 error on failure + * @access public + */ + function getAssoc($query, $types = null, $params = array(), $param_types = null, + $fetchmode = MDB2_FETCHMODE_DEFAULT, $force_array = false, $group = false) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryAll($query, $types, $fetchmode, true, $force_array, $group); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (MDB2::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (!MDB2::isResultCommon($result)) { + return $result; + } + + $all = $result->fetchAll($fetchmode, true, $force_array, $group); + $stmt->free(); + $result->free(); + return $all; + } + + // }}} + // {{{ executeMultiple() + + /** + * This function does several execute() calls on the same statement handle. + * $params must be an array indexed numerically from 0, one execute call is + * done for every 'row' in the array. + * + * If an error occurs during execute(), executeMultiple() does not execute + * the unfinished rows, but rather returns that error. + * + * @param resource query handle from prepare() + * @param array numeric array containing the data to insert into the query + * + * @return bool|MDB2_Error true on success, a MDB2 error on failure + * @access public + * @see prepare(), execute() + */ + function executeMultiple($stmt, $params = null) + { + if (MDB2::isError($stmt)) { + return $stmt; + } + for ($i = 0, $j = count($params); $i < $j; $i++) { + $result = $stmt->execute($params[$i]); + if (MDB2::isError($result)) { + return $result; + } + } + return MDB2_OK; + } + + // }}} + // {{{ getBeforeID() + + /** + * Returns the next free id of a sequence if the RDBMS + * does not support auto increment + * + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * @param bool when true the sequence is automatic created, if it not exists + * @param bool if the returned value should be quoted + * + * @return int|MDB2_Error id on success, a MDB2 error on failure + * @access public + */ + function getBeforeID($table, $field = null, $ondemand = true, $quote = true) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + $seq = $table.(empty($field) ? '' : '_'.$field); + $id = $db->nextID($seq, $ondemand); + if (!$quote || MDB2::isError($id)) { + return $id; + } + return $db->quote($id, 'integer'); + } elseif (!$quote) { + return null; + } + return 'NULL'; + } + + // }}} + // {{{ getAfterID() + + /** + * Returns the autoincrement ID if supported or $id + * + * @param mixed value as returned by getBeforeId() + * @param string name of the table into which a new row was inserted + * @param string name of the field into which a new row was inserted + * + * @return int|MDB2_Error id on success, a MDB2 error on failure + * @access public + */ + function getAfterID($id, $table, $field = null) + { + $db = $this->getDBInstance(); + if (MDB2::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + return $id; + } + return $db->lastInsertID($table, $field); + } + + // }}} +} +?> diff --git a/extlib/MDB2/Iterator.php b/extlib/MDB2/Iterator.php new file mode 100644 index 0000000000..6de057a820 --- /dev/null +++ b/extlib/MDB2/Iterator.php @@ -0,0 +1,262 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * PHP5 Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Iterator implements Iterator +{ + protected $fetchmode; + /** + * @var MDB2_Result_Common + */ + protected $result; + protected $row; + + // {{{ constructor + + /** + * Constructor + */ + public function __construct(MDB2_Result_Common $result, $fetchmode = MDB2_FETCHMODE_DEFAULT) + { + $this->result = $result; + $this->fetchmode = $fetchmode; + } + // }}} + + // {{{ seek() + + /** + * Seek forward to a specific row in a result set + * + * @param int number of the row where the data can be found + * + * @return void + * @access public + */ + public function seek($rownum) + { + $this->row = null; + if ($this->result) { + $this->result->seek($rownum); + } + } + // }}} + + // {{{ next() + + /** + * Fetch next row of data + * + * @return void + * @access public + */ + public function next() + { + $this->row = null; + } + // }}} + + // {{{ current() + + /** + * return a row of data + * + * @return void + * @access public + */ + public function current() + { + if (null === $this->row) { + $row = $this->result->fetchRow($this->fetchmode); + if (MDB2::isError($row)) { + $row = false; + } + $this->row = $row; + } + return $this->row; + } + // }}} + + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return bool true/false, false is also returned on failure + * @access public + */ + public function valid() + { + return (bool)$this->current(); + } + // }}} + + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function free() + { + if ($this->result) { + return $this->result->free(); + } + $this->result = false; + $this->row = null; + return false; + } + // }}} + + // {{{ key() + + /** + * Returns the row number + * + * @return int|bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function key() + { + if ($this->result) { + return $this->result->rowCount(); + } + return false; + } + // }}} + + // {{{ rewind() + + /** + * Seek to the first row in a result set + * + * @return void + * @access public + */ + public function rewind() + { + } + // }}} + + // {{{ destructor + + /** + * Destructor + */ + public function __destruct() + { + $this->free(); + } + // }}} +} + +/** + * PHP5 buffered Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedIterator extends MDB2_Iterator implements SeekableIterator +{ + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return bool|MDB2_Error true on success, false|MDB2_Error if result is invalid + * @access public + */ + public function valid() + { + if ($this->result) { + return $this->result->valid(); + } + return false; + } + // }}} + + // {{{count() + + /** + * Returns the number of rows in a result object + * + * @return int|MDB2_Error number of rows, false|MDB2_Error if result is invalid + * @access public + */ + public function count() + { + if ($this->result) { + return $this->result->numRows(); + } + return false; + } + // }}} + + // {{{ rewind() + + /** + * Seek to the first row in a result set + * + * @return void + * @access public + */ + public function rewind() + { + $this->seek(0); + } + // }}} +} + +?> diff --git a/extlib/MDB2/LOB.php b/extlib/MDB2/LOB.php new file mode 100644 index 0000000000..537a77e546 --- /dev/null +++ b/extlib/MDB2/LOB.php @@ -0,0 +1,264 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +require_once 'MDB2.php'; + +/** + * MDB2_LOB: user land stream wrapper implementation for LOB support + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_LOB +{ + /** + * contains the key to the global MDB2 instance array of the associated + * MDB2 instance + * + * @var integer + * @access protected + */ + var $db_index; + + /** + * contains the key to the global MDB2_LOB instance array of the associated + * MDB2_LOB instance + * + * @var integer + * @access protected + */ + var $lob_index; + + // {{{ stream_open() + + /** + * open stream + * + * @param string specifies the URL that was passed to fopen() + * @param string the mode used to open the file + * @param int holds additional flags set by the streams API + * @param string not used + * + * @return bool + * @access public + */ + function stream_open($path, $mode, $options, &$opened_path) + { + if (!preg_match('/^rb?\+?$/', $mode)) { + return false; + } + $url = parse_url($path); + if (empty($url['host'])) { + return false; + } + $this->db_index = (int)$url['host']; + if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + return false; + } + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $this->lob_index = (int)$url['user']; + if (!isset($db->datatype->lobs[$this->lob_index])) { + return false; + } + return true; + } + // }}} + + // {{{ stream_read() + + /** + * read stream + * + * @param int number of bytes to read + * + * @return string + * @access public + */ + function stream_read($count) + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $db->datatype->_retrieveLOB($db->datatype->lobs[$this->lob_index]); + + $data = $db->datatype->_readLOB($db->datatype->lobs[$this->lob_index], $count); + $length = strlen($data); + if ($length == 0) { + $db->datatype->lobs[$this->lob_index]['endOfLOB'] = true; + } + $db->datatype->lobs[$this->lob_index]['position'] += $length; + return $data; + } + } + // }}} + + // {{{ stream_write() + + /** + * write stream, note implemented + * + * @param string data + * + * @return int + * @access public + */ + function stream_write($data) + { + return 0; + } + // }}} + + // {{{ stream_tell() + + /** + * return the current position + * + * @return int current position + * @access public + */ + function stream_tell() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + return $db->datatype->lobs[$this->lob_index]['position']; + } + } + // }}} + + // {{{ stream_eof() + + /** + * Check if stream reaches EOF + * + * @return bool + * @access public + */ + function stream_eof() + { + if (!isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + return true; + } + + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + $result = $db->datatype->_endOfLOB($db->datatype->lobs[$this->lob_index]); + if (version_compare(phpversion(), "5.0", ">=") + && version_compare(phpversion(), "5.1", "<") + ) { + return !$result; + } + return $result; + } + // }}} + + // {{{ stream_seek() + + /** + * Seek stream, not implemented + * + * @param int offset + * @param int whence + * + * @return bool + * @access public + */ + function stream_seek($offset, $whence) + { + return false; + } + // }}} + + // {{{ stream_stat() + + /** + * return information about stream + * + * @access public + */ + function stream_stat() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + return array( + 'db_index' => $this->db_index, + 'lob_index' => $this->lob_index, + ); + } + } + // }}} + + // {{{ stream_close() + + /** + * close stream + * + * @access public + */ + function stream_close() + { + if (isset($GLOBALS['_MDB2_databases'][$this->db_index])) { + $db =& $GLOBALS['_MDB2_databases'][$this->db_index]; + if (isset($db->datatype->lobs[$this->lob_index])) { + $db->datatype->_destroyLOB($db->datatype->lobs[$this->lob_index]); + unset($db->datatype->lobs[$this->lob_index]); + } + } + } + // }}} +} + +// register streams wrapper +if (!stream_wrapper_register("MDB2LOB", "MDB2_LOB")) { + MDB2::raiseError(); + return false; +} + +?> diff --git a/lib/database/mysqlschema.php b/lib/database/mysqlschema.php index d27333a392..0d22668261 100644 --- a/lib/database/mysqlschema.php +++ b/lib/database/mysqlschema.php @@ -220,8 +220,8 @@ class MysqlSchema extends Schema */ public function fetchMetaInfo($table, $infoTable, $orderBy = null) { - $schema = $this->conn->dsn['database']; - return $this->fetchQueryData(sprintf( + $schema = $this->conn->getDatabase(); + $info = $this->fetchQueryData(sprintf( <<<'END' SELECT * FROM INFORMATION_SCHEMA.%1$s WHERE TABLE_SCHEMA = '%2$s' AND TABLE_NAME = '%3$s'%4$s; @@ -231,6 +231,10 @@ class MysqlSchema extends Schema $table, ($orderBy ? " ORDER BY {$orderBy}" : '') )); + + return array_map(function (array $cols): array { + return array_change_key_case($cols, CASE_UPPER); + }, $info); } /** @@ -242,7 +246,7 @@ class MysqlSchema extends Schema */ private function fetchKeyInfo(string $table): array { - $schema = $this->conn->dsn['database']; + $schema = $this->conn->getDatabase(); $data = $this->fetchQueryData( <<conn->dsn['database']; + $schema = $this->conn->getDatabase(); $data = $this->fetchQueryData( <<conn->dsn['database']; - return $this->fetchQueryData(sprintf( + $catalog = $this->conn->getDatabase(); + $info = $this->fetchQueryData(sprintf( <<<'END' SELECT * FROM information_schema.%1$s WHERE table_catalog = '%2$s' AND table_name = '%3$s'%4$s; @@ -204,6 +204,10 @@ class PgsqlSchema extends Schema $table, ($orderBy ? " ORDER BY {$orderBy}" : '') )); + + return array_map(function (array $cols): array { + return array_change_key_case($cols, CASE_LOWER); + }, $info); } /** diff --git a/lib/database/schema.php b/lib/database/schema.php index 7feb214956..dc4fddff2a 100644 --- a/lib/database/schema.php +++ b/lib/database/schema.php @@ -72,7 +72,7 @@ class Schema if (is_null($conn)) { $key = 'default'; } else { - $key = md5(serialize($conn->dsn)); + $key = hash('md5', $conn->getDSN('string')); } if (is_null($dbtype)) { @@ -343,7 +343,7 @@ class Schema { global $_PEAR; - $res = $this->conn->query('DROP TABLE ' . $this->quoteIdentifier($name)); + $res = $this->conn->exec('DROP TABLE ' . $this->quoteIdentifier($name)); if ($_PEAR->isError($res)) { PEAR_ErrorToPEAR_Exception($res); @@ -372,7 +372,7 @@ class Schema { global $_PEAR; - $qry = []; + $statements = []; if (!is_array($columnNames)) { $columnNames = [$columnNames]; @@ -382,14 +382,20 @@ class Schema $name = "{$table}_" . implode("_", $columnNames) . "_idx"; } - $this->appendCreateIndex($qry, $table, $name, $columnNames); + $this->appendCreateIndex($statements, $table, $name, $columnNames); - $res = $this->conn->query(implode('; ', $qry)); + $this->conn->beginTransaction(); - if ($_PEAR->isError($res)) { - PEAR_ErrorToPEAR_Exception($res); + foreach ($statements as $sql) { + $res = $this->conn->exec($sql); + + if ($_PEAR->isError($res)) { + $this->conn->rollback(); + PEAR_ErrorToPEAR_Exception($res); + } } + $this->conn->commit(); return true; } @@ -409,12 +415,18 @@ class Schema $statements = []; $this->appendDropIndex($statements, $table, $name); - $res = $this->conn->query(implode(";\n", $statements)); + $this->conn->beginTransaction(); - if ($_PEAR->isError($res)) { - PEAR_ErrorToPEAR_Exception($res); + foreach ($statements as $sql) { + $res = $this->conn->exec($sql); + + if ($_PEAR->isError($res)) { + $this->conn->rollback(); + PEAR_ErrorToPEAR_Exception($res); + } } + $this->conn->commit(); return true; } @@ -435,7 +447,7 @@ class Schema $sql = 'ALTER TABLE ' . $this->quoteIdentifier($table) . ' ADD COLUMN ' . $this->columnSql($name, $columndef); - $res = $this->conn->query($sql); + $res = $this->conn->exec($sql); if ($_PEAR->isError($res)) { PEAR_ErrorToPEAR_Exception($res); @@ -463,7 +475,7 @@ class Schema $sql = 'ALTER TABLE ' . $this->quoteIdentifier($table) . ' MODIFY COLUMN ' . $this->columnSql($name, $columndef); - $res = $this->conn->query($sql); + $res = $this->conn->exec($sql); if ($_PEAR->isError($res)) { PEAR_ErrorToPEAR_Exception($res); @@ -490,7 +502,7 @@ class Schema $sql = 'ALTER TABLE ' . $this->quoteIdentifier($table) . ' DROP COLUMN ' . $columnName; - $res = $this->conn->query($sql); + $res = $this->conn->exec($sql); if ($_PEAR->isError($res)) { PEAR_ErrorToPEAR_Exception($res); @@ -532,19 +544,24 @@ class Schema { global $_PEAR; - $ok = true; + $this->conn->beginTransaction(); + foreach ($statements as $sql) { if (defined('DEBUG_INSTALLER')) { echo "" . htmlspecialchars($sql) . "
\n"; } - $res = $this->conn->query($sql); + + $res = $this->conn->exec($sql); if ($_PEAR->isError($res)) { + $this->conn->rollback(); common_debug('PEAR exception on query: ' . $sql); PEAR_ErrorToPEAR_Exception($res); } } - return $ok; + + $this->conn->commit(); + return true; } /** @@ -849,7 +866,11 @@ class Schema public function quoteValue($val) { - return $this->conn->quoteSmart($val); + // MDB2_Driver_Common::quote changes empty strings to "NULL". + if ($val === '') { + return ''; + } + return $this->conn->quote($val); } /** @@ -1190,7 +1211,7 @@ class Schema * @return array of arrays * @throws PEAR_Exception */ - protected function fetchQueryData($sql) + protected function fetchQueryData(string $sql): array { global $_PEAR; @@ -1200,8 +1221,7 @@ class Schema } $out = []; - $row = []; - while ($res->fetchInto($row, DB_FETCHMODE_ASSOC)) { + while (!empty($row = $res->fetchRow(MDB2_FETCHMODE_ASSOC))) { $out[] = $row; } $res->free(); diff --git a/lib/util/default.php b/lib/util/default.php index a6175e72d0..c20b960d1d 100644 --- a/lib/util/default.php +++ b/lib/util/default.php @@ -72,8 +72,7 @@ $default = 'require_prefix' => 'classes/', 'class_prefix' => '', 'mirror' => null, - 'utf8' => true, - 'db_driver' => 'DB', # XXX: JanRain libs only work with DB + 'db_driver' => 'MDB2', 'disable_null_strings' => true, // 'NULL' can be harmful 'quote_identifiers' => true, 'type' => 'mysql', diff --git a/lib/util/framework.php b/lib/util/framework.php index 6be09e1cca..3849e07c0a 100644 --- a/lib/util/framework.php +++ b/lib/util/framework.php @@ -95,11 +95,9 @@ global $_PEAR; $_PEAR = new PEAR; $_PEAR->setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception'); -require_once 'DB.php'; +require_once 'MDB2.php'; require_once 'DB/DataObject.php'; -require_once 'DB/DataObject/Cast.php'; # for dates -global $_DB; -$_DB = new DB; +require_once 'DB/DataObject/Cast.php'; // for dates require_once INSTALLDIR . '/lib/util/language.php'; @@ -129,13 +127,14 @@ function GNUsocial_class_autoload($cls) $lib_path = INSTALLDIR . '/lib/'; $lib_dirs = array_map(function ($dir) { - return '/lib/' . $dir . '/'; - }, - array_filter(scandir($lib_path), - function ($dir) use ($lib_path) { - // Filter out files and both hidden and implicit folders - return $dir[0] != '.' && is_dir($lib_path . $dir); - })); + return '/lib/' . $dir . '/'; + }, array_filter( + scandir($lib_path), + function ($dir) use ($lib_path) { + // Filter out files and both hidden and implicit directories. + return $dir[0] !== '.' && is_dir($lib_path . $dir); + } + )); $found = false; foreach (array_merge(['/classes/'], $lib_dirs) as $dir) { diff --git a/lib/util/installer.php b/lib/util/installer.php index 1d3b5602af..b4b8f21249 100644 --- a/lib/util/installer.php +++ b/lib/util/installer.php @@ -66,12 +66,12 @@ abstract class Installer 'mysql' => [ 'name' => 'MariaDB 10.3+', 'check_module' => 'mysqli', - 'scheme' => 'mysqli', // DSN prefix for PEAR::DB + 'scheme' => 'mysqli', // DSN prefix for MDB2 ], 'pgsql' => [ 'name' => 'PostgreSQL 11+', 'check_module' => 'pgsql', - 'scheme' => 'pgsql', // DSN prefix for PEAR::DB + 'scheme' => 'pgsql', // DSN prefix for MDB2 ] ]; @@ -283,64 +283,45 @@ abstract class Installer */ public function setupDatabase() { - if ($this->db) { - throw new Exception("Bad order of operations: DB already set up."); + if (!empty($this->db)) { + throw new Exception('Bad order of operations: DB already set up.'); } - $this->updateStatus("Starting installation..."); + $this->updateStatus('Starting installation...'); - if (empty($this->password)) { - $auth = ''; - } else { - $auth = ":$this->password"; + $auth = ''; + if (!empty($this->password)) { + $auth .= ":{$this->password}"; } $scheme = self::$dbModules[$this->dbtype]['scheme']; $dsn = "{$scheme}://{$this->username}{$auth}@{$this->host}/{$this->database}"; - $this->updateStatus("Checking database..."); + $this->updateStatus('Checking database...'); $conn = $this->connectDatabase($dsn); - if (!$conn instanceof DB_common) { - // Is not the right instance - throw new Exception('Cannot connect to database: ' . $conn->getMessage()); + $charset = ($this->dbtype !== 'mysql') ? 'UTF8' : 'utf8mb4'; + $server_charset = $this->getDatabaseCharset($conn, $this->dbtype); + + // Ensure the database server character set is UTF-8. + $conn->exec("SET NAMES '{$charset}'"); + + if ($server_charset !== $charset) { + $this->updateStatus( + 'GNU social requires the "' . $charset . '" character set. ' + . 'Yours is ' . htmlentities($server_charset) + ); + return false; } - switch ($this->dbtype) { - case 'pgsql': - // ensure the timezone is UTC - $conn->query("SET TIME ZONE INTERVAL '+00:00' HOUR TO MINUTE"); - // ensure the database encoding is UTF8 - $conn->query("SET NAMES 'UTF8'"); - $server_encoding = $conn->getRow('SHOW server_encoding')[0]; - if ($server_encoding !== 'UTF8') { - $this->updateStatus( - 'GNU social requires the UTF8 character encoding. Yours is ' . - htmlentities($server_encoding) - ); - return false; - } - break; - case 'mysql': - // ensure the timezone is UTC - $conn->query("SET time_zone = '+0:00'"); - // ensure the database encoding is utf8mb4 - $conn->query("SET NAMES 'utf8mb4'"); - $server_encoding = $conn->getRow("SHOW VARIABLES LIKE 'character_set_server'")[1]; - if ($server_encoding !== 'utf8mb4') { - $this->updateStatus( - 'GNU social requires the utf8mb4 character encoding. Yours is ' . - htmlentities($server_encoding) - ); - return false; - } - break; - default: - $this->updateStatus('Unknown DB type selected: ' . $this->dbtype); - return false; + // Ensure the timezone is UTC. + if ($this->dbtype !== 'mysql') { + $conn->exec("SET TIME ZONE INTERVAL '+00:00' HOUR TO MINUTE"); + } else { + $conn->exec("SET time_zone = '+0:00'"); } - $res = $this->updateStatus("Creating database tables..."); + $res = $this->updateStatus('Creating database tables...'); if (!$this->createCoreTables($conn)) { - $this->updateStatus("Error creating tables.", true); + $this->updateStatus('Error creating tables.', true); return false; } @@ -365,21 +346,67 @@ abstract class Installer * Open a connection to the database. * * @param string $dsn - * @return DB|DB_Error + * @return MDB2_Driver_Common + * @throws Exception */ - public function connectDatabase(string $dsn) + protected function connectDatabase(string $dsn) { - global $_DB; - return $_DB->connect($dsn); + $conn = MDB2::connect($dsn); + if (MDB2::isError($conn)) { + throw new Exception( + 'Cannot connect to database: ' . $conn->getMessage() + ); + } + return $conn; + } + + /** + * Get the database server character set. + * + * @param MDB2_Driver_Common $conn + * @param string $dbtype + * @return string + * @throws Exception + */ + protected function getDatabaseCharset($conn, string $dbtype): string + { + $database = $conn->getDatabase(); + + switch ($dbtype) { + case 'pgsql': + $res = $conn->query('SHOW server_encoding'); + + if (MDB2::isError($res)) { + throw new Exception($res->getMessage()); + } + $ret = $res->fetchOne(); + break; + case 'mysql': + $res = $conn->query( + "SHOW SESSION VARIABLES LIKE 'character_set_server'" + ); + if (MDB2::isError($res)) { + throw new Exception($res->getMessage()); + } + [, $ret] = $res->fetchRow(MDB2_FETCHMODE_ORDERED); + break; + default: + throw new Exception('Unknown DB type selected.'); + } + + if (MDB2::isError($ret)) { + throw new Exception($ret->getMessage()); + } + return $ret; } /** * Create core tables on the given database connection. * - * @param DB_common $conn + * @param MDB2_Driver_Common $conn * @return bool */ - public function createCoreTables(DB_common $conn): bool + public function createCoreTables($conn): bool { $schema = Schema::get($conn, $this->dbtype); $tableDefs = $this->getCoreSchema(); @@ -520,11 +547,11 @@ abstract class Installer * Install schema into the database * * @param string $filename location of database schema file - * @param DB_common $conn connection to database + * @param MDB2_Driver_Common $conn connection to database * * @return bool - indicating success or failure */ - public function runDbScript(string $filename, DB_common $conn): bool + public function runDbScript(string $filename, $conn): bool { $sql = trim(file_get_contents(INSTALLDIR . '/db/' . $filename)); $stmts = explode(';', $sql); diff --git a/plugins/OpenID/lib/openiddbconn.php b/plugins/OpenID/lib/openiddbconn.php new file mode 100644 index 0000000000..514ad6d38e --- /dev/null +++ b/plugins/OpenID/lib/openiddbconn.php @@ -0,0 +1,204 @@ +. + +/** + * @package GNUsocial + * @author Alexei Sorokin + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +defined('GNUSOCIAL') || die(); + +require_once 'Auth/OpenID/DatabaseConnection.php'; + +/** + * A DB abstraction error for OpenID's Auth_OpenID_SQLStore + * + * @package GNUsocial + * @author Alexei Sorokin + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class SQLStore_DB_Connection extends Auth_OpenID_DatabaseConnection +{ + private $conn = null; + private $autocommit = true; + + /** + * @param string|array $dsn + * @param array $options + */ + public function __construct($dsn, array $options = []) + { + if (!is_array($dsn)) { + $dsn = MDB2::parseDSN($dsn); + } + $dsn['new_link'] = true; + + // To create a new Database connection is an absolute must, because + // php-openid code delays its transactions commitment. + // Is a must because our Internal Session Handler uses the database + // and depends on immediate commitment. + $this->conn = MDB2::connect($dsn, $options); + + if (MDB2::isError($this->conn)) { + throw new ServerException($this->conn->getMessage()); + } + } + + public function __destruct() + { + $this->conn->disconnect(); + } + + /** + * Sets auto-commit mode on this database connection. + * + * @param bool $mode + */ + public function autoCommit($mode) + { + $this->autocommit = $mode; + if ($mode && $this->conn->inTransaction()) { + $this->commit(); + } + } + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql + * @param array $params + * @param bool $is_manip + * @return mixed + */ + private function _query(string $sql, array $params = [], bool $is_manip) + { + $stmt_type = $is_manip ? MDB2_PREPARE_MANIP : MDB2_PREPARE_RESULT; + if ($is_manip && !$this->autocommit) { + $this->begin(); + } + + $split = preg_split( + '/((?conn->prepare($sql, null, $stmt_type); + if (MDB2::isError($stmt)) { + // php-openid actually expects PEAR_Error. + return $res; + } + if (count($params) > 0) { + $stmt->bindValueArray($params); + } + $res = $stmt->execute(); + if (MDB2::isError($res)) { + return $res; + } + return $res; + } + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql + * @param array $params + * @return mixed + */ + public function query($sql, $params = []) + { + return $this->_query($sql, $params, true); + } + + public function begin() + { + $this->conn->beginTransaction(); + } + + public function commit() + { + $this->conn->commit(); + } + + public function rollback() + { + $this->conn->rollback(); + } + + /** + * Run an SQL query and return the first column of the first row of the + * result set, if any. + * + * @param string $sql + * @param array $params + * @return string|PEAR_Error + */ + public function getOne($sql, $params = []) + { + $res = $this->_query($sql, $params, false); + if (MDB2::isError($res)) { + return $res; + } + return $res->fetchOne() ?? ''; + } + + /** + * Run an SQL query and return the first row of the result set, if any. + * + * @param string $sql + * @param array $params + * @return array|PEAR_Error + */ + public function getRow($sql, $params = []) + { + $res = $this->_query($sql, $params, false); + if (MDB2::isError($res)) { + return $res; + } + return $res->fetchRow(MDB2_FETCHMODE_ASSOC); + } + + /** + * Run an SQL query with the specified parameters, if any. + * + * @param string $sql + * @param array $params + * @return array|PEAR_Error + */ + public function getAll($sql, $params = []) + { + $res = $this->_query($sql, $params, false); + if (MDB2::isError($res)) { + return $res; + } + return $res->fetchAll(MDB2_FETCHMODE_ASSOC); + } +} diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index 5237cc81ea..ff06feccbe 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -21,11 +21,12 @@ defined('GNUSOCIAL') || die(); -require_once('Auth/OpenID.php'); -require_once('Auth/OpenID/Consumer.php'); -require_once('Auth/OpenID/Server.php'); -require_once('Auth/OpenID/SReg.php'); -require_once('Auth/OpenID/MySQLStore.php'); +require_once __DIR__ . '/lib/openiddbconn.php'; + +require_once 'Auth/OpenID.php'; +require_once 'Auth/OpenID/Consumer.php'; +require_once 'Auth/OpenID/Server.php'; +require_once 'Auth/OpenID/SReg.php'; // About one year cookie expiry @@ -34,31 +35,24 @@ define('OPENID_COOKIE_KEY', 'lastusedopenid'); function oid_store() { + global $_PEAR; static $store = null; + if (is_null($store)) { - // To create a new Database connection is an absolute must - // because database is in transaction (auto-commit = false) - // mode during OpenID operation - // Is a must because our Internal Session Handler uses database - // and depends on auto-commit = true $dsn = common_config('db', 'database'); - $options = PEAR::getStaticProperty('DB', 'options'); + $options = $_PEAR->getStaticProperty('MDB2', 'options'); if (!is_array($options)) { $options = []; } - $db = DB::connect($dsn, $options); - - if ((new PEAR)->isError($db)) { - throw new ServerException($db->getMessage()); - } + $dbconn = new SQLStore_DB_Connection($dsn, $options); switch (common_config('db', 'type')) { case 'pgsql': - $store = new Auth_OpenID_PostgreSQLStore($db); + $store = new Auth_OpenID_PostgreSQLStore($dbconn); break; case 'mysql': - $store = new Auth_OpenID_MySQLStore($db); + $store = new Auth_OpenID_MySQLStore($dbconn); break; default: throw new ServerException('Unknown DB type selected.'); @@ -247,8 +241,12 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) } else { // Generate form markup and render it. $form_id = 'openid_message'; - $form_html = $auth_request->formMarkup($trust_root, $process_url, - $immediate, array('id' => $form_id)); + $form_html = $auth_request->formMarkup( + $trust_root, + $process_url, + $immediate, + ['id' => $form_id] + ); // XXX: This is cheap, but things choke if we don't escape ampersands // in the HTML attributes diff --git a/public/index.php b/public/index.php index 30d584c732..c9b5df30a7 100644 --- a/public/index.php +++ b/public/index.php @@ -121,7 +121,7 @@ function handleError($error) } } if ($error instanceof DB_DataObject_Error - || $error instanceof DB_Error + || $error instanceof MDB2_Error || ($error instanceof PEAR_Exception && $error->getCode() == -24) ) { //If we run into a DB error, assume we can't connect to the DB at all