From 95ebbc985a0e64a364e880de89dbc1ae7952d91e Mon Sep 17 00:00:00 2001 From: till Date: Sun, 3 Feb 2008 12:58:34 +0000 Subject: * putting latest MDB2 into SVN * adding MDB2 drivers for mssql, mysql, mysqli, pgsql, sqlite --- program/lib/MDB2.php | 4295 +++++++++++++++++++++++++++ program/lib/MDB2/Date.php | 183 ++ program/lib/MDB2/Driver/Datatype/Common.php | 1830 ++++++++++++ program/lib/MDB2/Driver/Datatype/mssql.php | 498 ++++ program/lib/MDB2/Driver/Datatype/mysql.php | 466 +++ program/lib/MDB2/Driver/Datatype/mysqli.php | 504 ++++ program/lib/MDB2/Driver/Datatype/pgsql.php | 555 ++++ program/lib/MDB2/Driver/Datatype/sqlite.php | 412 +++ program/lib/MDB2/Driver/Function/Common.php | 249 ++ program/lib/MDB2/Driver/Function/mssql.php | 178 ++ program/lib/MDB2/Driver/Function/mysql.php | 120 + program/lib/MDB2/Driver/Function/mysqli.php | 128 + program/lib/MDB2/Driver/Function/pgsql.php | 99 + program/lib/MDB2/Driver/Function/sqlite.php | 125 + program/lib/MDB2/Driver/Manager/Common.php | 935 ++++++ program/lib/MDB2/Driver/Manager/mssql.php | 815 +++++ program/lib/MDB2/Driver/Manager/mysql.php | 1064 +++++++ program/lib/MDB2/Driver/Manager/mysqli.php | 1067 +++++++ program/lib/MDB2/Driver/Manager/pgsql.php | 765 +++++ program/lib/MDB2/Driver/Manager/sqlite.php | 1325 +++++++++ program/lib/MDB2/Driver/Native/Common.php | 61 + program/lib/MDB2/Driver/Native/mssql.php | 60 + program/lib/MDB2/Driver/Native/mysql.php | 60 + program/lib/MDB2/Driver/Native/mysqli.php | 60 + program/lib/MDB2/Driver/Native/pgsql.php | 88 + program/lib/MDB2/Driver/Native/sqlite.php | 60 + program/lib/MDB2/Driver/Reverse/Common.php | 511 ++++ program/lib/MDB2/Driver/Reverse/mssql.php | 653 ++++ program/lib/MDB2/Driver/Reverse/mysql.php | 514 ++++ program/lib/MDB2/Driver/Reverse/mysqli.php | 578 ++++ program/lib/MDB2/Driver/Reverse/pgsql.php | 527 ++++ program/lib/MDB2/Driver/Reverse/sqlite.php | 573 ++++ program/lib/MDB2/Driver/mssql.php | 1010 +++++++ program/lib/MDB2/Driver/mysql.php | 1535 ++++++++++ program/lib/MDB2/Driver/mysqli.php | 1697 +++++++++++ program/lib/MDB2/Driver/pgsql.php | 1470 +++++++++ program/lib/MDB2/Driver/sqlite.php | 1078 +++++++ program/lib/MDB2/Extended.php | 721 +++++ program/lib/MDB2/Iterator.php | 259 ++ program/lib/MDB2/LOB.php | 264 ++ 40 files changed, 27392 insertions(+) create mode 100644 program/lib/MDB2.php create mode 100644 program/lib/MDB2/Date.php create mode 100644 program/lib/MDB2/Driver/Datatype/Common.php create mode 100644 program/lib/MDB2/Driver/Datatype/mssql.php create mode 100644 program/lib/MDB2/Driver/Datatype/mysql.php create mode 100644 program/lib/MDB2/Driver/Datatype/mysqli.php create mode 100644 program/lib/MDB2/Driver/Datatype/pgsql.php create mode 100644 program/lib/MDB2/Driver/Datatype/sqlite.php create mode 100644 program/lib/MDB2/Driver/Function/Common.php create mode 100644 program/lib/MDB2/Driver/Function/mssql.php create mode 100644 program/lib/MDB2/Driver/Function/mysql.php create mode 100644 program/lib/MDB2/Driver/Function/mysqli.php create mode 100644 program/lib/MDB2/Driver/Function/pgsql.php create mode 100644 program/lib/MDB2/Driver/Function/sqlite.php create mode 100644 program/lib/MDB2/Driver/Manager/Common.php create mode 100644 program/lib/MDB2/Driver/Manager/mssql.php create mode 100644 program/lib/MDB2/Driver/Manager/mysql.php create mode 100644 program/lib/MDB2/Driver/Manager/mysqli.php create mode 100644 program/lib/MDB2/Driver/Manager/pgsql.php create mode 100644 program/lib/MDB2/Driver/Manager/sqlite.php create mode 100644 program/lib/MDB2/Driver/Native/Common.php create mode 100644 program/lib/MDB2/Driver/Native/mssql.php create mode 100644 program/lib/MDB2/Driver/Native/mysql.php create mode 100644 program/lib/MDB2/Driver/Native/mysqli.php create mode 100644 program/lib/MDB2/Driver/Native/pgsql.php create mode 100644 program/lib/MDB2/Driver/Native/sqlite.php create mode 100644 program/lib/MDB2/Driver/Reverse/Common.php create mode 100644 program/lib/MDB2/Driver/Reverse/mssql.php create mode 100644 program/lib/MDB2/Driver/Reverse/mysql.php create mode 100644 program/lib/MDB2/Driver/Reverse/mysqli.php create mode 100644 program/lib/MDB2/Driver/Reverse/pgsql.php create mode 100644 program/lib/MDB2/Driver/Reverse/sqlite.php create mode 100644 program/lib/MDB2/Driver/mssql.php create mode 100644 program/lib/MDB2/Driver/mysql.php create mode 100644 program/lib/MDB2/Driver/mysqli.php create mode 100644 program/lib/MDB2/Driver/pgsql.php create mode 100644 program/lib/MDB2/Driver/sqlite.php create mode 100644 program/lib/MDB2/Extended.php create mode 100644 program/lib/MDB2/Iterator.php create mode 100644 program/lib/MDB2/LOB.php (limited to 'program') diff --git a/program/lib/MDB2.php b/program/lib/MDB2.php new file mode 100644 index 000000000..a8bbb0fb4 --- /dev/null +++ b/program/lib/MDB2.php @@ -0,0 +1,4295 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: MDB2.php,v 1.307 2007/11/10 13:29:05 quipo Exp $ +// + +/** + * @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); +// }}} +// {{{ 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 + */ + function setOptions(&$db, $options) + { + if (is_array($options)) { + foreach ($options as $option => $value) { + $test = $db->setOption($option, $value); + if (PEAR::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 + */ + function classExists($classname) + { + if (version_compare(phpversion(), "5.0", ">=")) { + return class_exists($classname, false); + } + return class_exists($classname); + } + + // }}} + // {{{ 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 + */ + 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; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function &factory($dsn, $options = false) + + /** + * Create a new MDB2 object for the specified database type + * + * IMPORTANT: In order for MDB2 to work properly it is necessary that + * you make sure that you work with a reference of the original + * object instead of a copy (this is a PHP4 quirk). + * + * For example: + * $db =& MDB2::factory($dsn); + * ^^ + * And not: + * $db = MDB2::factory($dsn); + * + * @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 + */ + 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 (PEAR::isError($err)) { + return $err; + } + + $db =& new $class_name(); + $db->setDSN($dsninfo); + $err = MDB2::setOptions($db, $options); + if (PEAR::isError($err)) { + return $err; + } + + return $db; + } + + // }}} + // {{{ function &connect($dsn, $options = false) + + /** + * Create a new MDB2 connection object and connect to the specified + * database + * + * IMPORTANT: In order for MDB2 to work properly it is necessary that + * you make sure that you work with a reference of the original + * object instead of a copy (this is a PHP4 quirk). + * + * For example: + * $db =& MDB2::connect($dsn); + * ^^ + * And not: + * $db = MDB2::connect($dsn); + * ^^ + * + * @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 + */ + function &connect($dsn, $options = false) + { + $db =& MDB2::factory($dsn, $options); + if (PEAR::isError($db)) { + return $db; + } + + $err = $db->connect(); + if (PEAR::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. + * + * IMPORTANT: In order for MDB2 to work properly it is necessary that + * you make sure that you work with a reference of the original + * object instead of a copy (this is a PHP4 quirk). + * + * For example: + * $db =& MDB2::singleton($dsn); + * ^^ + * And not: + * $db = MDB2::singleton($dsn); + * ^^ + * + * @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 + */ + 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'])) { + $db =& $GLOBALS['_MDB2_databases'][key($GLOBALS['_MDB2_databases'])]; + return $db; + } + $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 + */ + 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 name of the file in the MDB2 directory (without '.php') + * + * @return string name of the file that was included + * + * @access public + */ + 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 + */ + function apiVersion() + { + return '2.5.0a2'; + } + + // }}} + // {{{ 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 + */ + function &raiseError($code = null, + $mode = null, + $options = null, + $userinfo = null, + $dummy1 = null, + $dummy2 = null, + $dummy3 = false) + { + $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 + */ + function isError($data, $code = null) + { + if (is_a($data, 'MDB2_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() === $code; + } else { + $code = (array)$code; + return in_array($data->getCode(), $code); + } + } + return false; + } + + // }}} + // {{{ 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 + */ + function isConnection($value) + { + return is_a($value, 'MDB2_Driver_Common'); + } + + // }}} + // {{{ function isResult($value) + + /** + * Tell whether a value is a MDB2 result + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 result + * + * @access public + */ + function isResult($value) + { + return is_a($value, 'MDB2_Result'); + } + + // }}} + // {{{ function isResultCommon($value) + + /** + * Tell whether a value is a MDB2 result implementing the common interface + * + * @param mixed value to test + * + * @return bool whether $value is a MDB2 result implementing the common interface + * + * @access public + */ + function isResultCommon($value) + { + return is_a($value, '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 + */ + function isStatement($value) + { + return is_a($value, '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 + */ + 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', + ); + } + + if (is_null($value)) { + return $errorMessages; + } + + if (PEAR::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 + */ + 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; + } + + if (!count($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; + $dsn = null; + } 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'] = $dsn; + // /database?param1=value1¶m2=value2 + } else { + $parsed['database'] = 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; + } + + // }}} + // {{{ 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 + */ + 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 MDB2_Error($code = MDB2_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null, $dummy = null) + { + if (is_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 extends PEAR +{ + // {{{ Variables (Properties) + + /** + * index of the MDB2 object within the $GLOBALS['_MDB2_databases'] array + * @var int + * @access public + */ + var $db_index = 0; + + /** + * DSN used for the next query + * @var array + * @access protected + */ + var $dsn = array(); + + /** + * DSN that was used to create the current connection + * @var array + * @access protected + */ + var $connected_dsn = array(); + + /** + * connection resource + * @var mixed + * @access protected + */ + var $connection = 0; + + /** + * if the current opened connection is a persistent connection + * @var bool + * @access protected + */ + var $opened_persistent; + + /** + * the name of the database for the next query + * @var string + * @access protected + */ + var $database_name = ''; + + /** + * the name of the database currently selected + * @var string + * @access protected + */ + var $connected_database_name = ''; + + /** + * server version information + * @var string + * @access protected + */ + var $connected_server_info = ''; + + /** + * list of all supported features of the given driver + * @var array + * @access public + */ + var $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, + '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 + *
+ * + * @var array + * @access public + * @see MDB2::connect() + * @see MDB2::factory() + * @see MDB2::singleton() + * @see MDB2_Driver_Common::setOption() + */ + var $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_]*)', + ); + + /** + * string array + * @var string + * @access protected + */ + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => false, 'escape_pattern' => false); + + /** + * identifier quoting + * @var array + * @access protected + */ + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + /** + * sql comments + * @var array + * @access protected + */ + var $sql_comments = array( + array('start' => '--', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + /** + * comparision wildcards + * @var array + * @access protected + */ + var $wildcards = array('%', '_'); + + /** + * column alias keyword + * @var string + * @access protected + */ + var $as_keyword = ' AS '; + + /** + * warnings + * @var array + * @access protected + */ + var $warnings = array(); + + /** + * string with the debugging information + * @var string + * @access public + */ + var $debug_output = ''; + + /** + * determine if there is an open transaction + * @var bool + * @access protected + */ + var $in_transaction = false; + + /** + * the smart transaction nesting depth + * @var int + * @access protected + */ + var $nested_transaction_counter = null; + + /** + * the first error that occured inside a nested transaction + * @var MDB2_Error|bool + * @access protected + */ + var $has_transaction_error = false; + + /** + * result offset used in the next query + * @var int + * @access protected + */ + var $offset = 0; + + /** + * result limit used in the next query + * @var int + * @access protected + */ + var $limit = 0; + + /** + * Database backend used in PHP (mysql, odbc etc.) + * @var string + * @access public + */ + var $phptype; + + /** + * Database used with regards to SQL syntax etc. + * @var string + * @access public + */ + var $dbsyntax; + + /** + * the last query sent to the driver + * @var string + * @access public + */ + var $last_query; + + /** + * the default fetchmode used + * @var int + * @access protected + */ + var $fetchmode = MDB2_FETCHMODE_ORDERED; + + /** + * array of module instances + * @var array + * @access protected + */ + var $modules = array(); + + /** + * determines of the PHP4 destructor emulation has been enabled yet + * @var array + * @access protected + */ + var $destructor_registered = true; + + // }}} + // {{{ 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; + } + + // }}} + // {{{ function MDB2_Driver_Common() + + /** + * PHP 4 Constructor + */ + function MDB2_Driver_Common() + { + $this->destructor_registered = false; + $this->__construct(); + } + + // }}} + // {{{ 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 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 string name of the method that triggered the error + * + * @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) + { + $userinfo = "[Error message: $userinfo]\n"; + // The error is yet a MDB2 error object + if (PEAR::isError($code)) { + // because we use the static PEAR::raiseError, our global + // handler should be used if it is set + if (is_null($mode) && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + if (is_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 (!is_null($native_errno) && $native_errno !== '') { + $userinfo.= "[Native code: $native_errno]\n"; + } + if (!is_null($native_msg) && $native_msg !== '') { + $userinfo.= "[Native message: ". strip_tags($native_msg) ."]\n"; + } + if (!is_null($method)) { + $userinfo = $method.': '.$userinfo; + } + } + + $err =& 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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (!is_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 (!PEAR::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 (!PEAR::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 (is_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__); + } + + // }}} + // {{{ 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 bool 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) + { + $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; + $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; + } + + // }}} + // {{{ 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 (PEAR::isError($connection)) { + return $connection; + } + + $result =& $this->_doQuery($query, $is_manip, $connection, false); + if (PEAR::isError($result)) { + return $result; + } + + if ($is_manip) { + $affected_rows = $this->_affectedRows($connection, $result); + return $affected_rows; + } + $result =& $this->_wrapResult($result, $types, true, false, $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 (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $err =& $this->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 $this->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 (PEAR::isError($connection)) { + return $connection; + } + + $result =& $this->_doQuery($query, true, $connection, $this->database_name); + if (PEAR::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 = false) + { + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, false, $limit, $offset); + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result =& $this->_doQuery($query, false, $connection, $this->database_name); + if (PEAR::isError($result)) { + return $result; + } + + $result =& $this->_wrapResult($result, $types, $result_class, $result_wrap_class, $limit, $offset); + return $result; + } + + // }}} + // {{{ function &_wrapResult($result, $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, $types = array(), $result_class = true, + $result_wrap_class = false, $limit = null, $offset = null) + { + if ($types === true) { + if ($this->supports('result_introspection')) { + $this->loadModule('Reverse', null, true); + $tableInfo = $this->reverse->tableInfo($result); + if (PEAR::isError($tableInfo)) { + return $tableInfo; + } + $types = array(); + foreach ($tableInfo as $field) { + $types[] = $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 =& $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'result class does not exist '.$class_name, __FUNCTION__); + return $err; + } + $result =& new $class_name($this, $result, $limit, $offset); + if (!MDB2::isResultCommon($result)) { + $err =& $this->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 (PEAR::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 =& $this->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; + } + + // }}} + // {{{ 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 $this->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 $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'limit is not supported by this driver', __FUNCTION__); + } + $limit = (int)$limit; + if ($limit < 0) { + return $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'it was not specified a valid selected range row limit', __FUNCTION__); + } + $this->limit = $limit; + if (!is_null($offset)) { + $offset = (int)$offset; + if ($offset < 0) { + return $this->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 $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $col = $this->queryCol($query, $type); + if (PEAR::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 REPLACE query just updates its values instead of + * inserting a new row. + * + * 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 $this->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 $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $condition[] = $name . '=' . $value; + } + } + if (empty($condition)) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $result = null; + $in_transaction = $this->in_transaction; + if (!$in_transaction && PEAR::isError($result = $this->beginTransaction())) { + return $result; + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $condition = ' WHERE '.implode(' AND ', $condition); + $query = "DELETE FROM $table$condition"; + $result =& $this->_doQuery($query, true, $connection); + if (!PEAR::isError($result)) { + $affected_rows = $this->_affectedRows($connection, $result); + $insert = implode(', ', array_keys($values)); + $values = implode(', ', $values); + $query = "INSERT INTO $table ($insert) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (!PEAR::isError($result)) { + $affected_rows += $this->_affectedRows($connection, $result);; + } + } + + if (!$in_transaction) { + if (PEAR::isError($result)) { + $this->rollback(); + } else { + $result = $this->commit(); + } + } + + if (PEAR::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 (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + $ignores = $this->sql_comments; + $ignores[] = $this->string_quoting; + $ignores[] = $this->identifier_quoting; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (is_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; + } + $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 = $this->sql_comments; + $ignores[] = $this->string_quoting; + $ignores[] = $this->identifier_quoting; + + 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 =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'query with an unterminated text string specified', __FUNCTION__); + return $err; + } + } + } while ($ignore['escape'] && $query[($end_quote - 1)] == $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 (PEAR::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 (PEAR::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 (PEAR::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 $this->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 $this->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 $this->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 the SELECT query statement to be executed. + * @param string 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 int the column number 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 the SELECT query statement to be executed. + * @param string 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 int the row number 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; + } + + // }}} +} + +// }}} +// {{{ 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) + + var $db; + var $result; + var $rownum = -1; + var $types = array(); + var $values = array(); + var $offset; + var $offset_count = 0; + var $limit; + var $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 MDB2_Result_Common(&$db, &$result, $limit = 0, $offset = 0) + + /** + * PHP 4 Constructor + */ + function MDB2_Result_Common(&$db, &$result, $limit = 0, $offset = 0) + { + $this->__construct($db, $result, $limit, $offset); + } + + // }}} + // {{{ 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 (PEAR::isError($load)) { + return $load; + } + $types = $this->db->datatype->checkResultTypes($types); + if (PEAR::isError($types)) { + return $types; + } + $this->types = $types; + 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 $this->db->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 =& $this->db->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 the column number 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) || PEAR::isError($row)) { + return $row; + } + if (!array_key_exists($colnum, $row)) { + return $this->db->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 the column number 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 $this->db->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 (PEAR::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 (PEAR::isError($row)) { + return $row; + } elseif (!$row) { + return $all; + } + + $shift_array = $rekey ? false : null; + if (!is_null($shift_array)) { + if (is_object($row)) { + $colnum = count(get_object_vars($row)); + } else { + $colnum = count($row); + } + if ($colnum < 2) { + return $this->db->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) { + $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 $this->db->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 $this->db->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 (PEAR::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 $this->db->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 $this->db->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 (!is_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]; + } + } + + // }}} + // {{{ function MDB2_Row(&$row) + + /** + * PHP 4 Constructor + * + * @param resource row data as array + */ + function MDB2_Row(&$row) + { + $this->__construct($row); + } + + // }}} +} + +// }}} +// {{{ 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 MDB2_Statement_Common(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + + /** + * PHP 4 Constructor + */ + function MDB2_Statement_Common(&$db, &$statement, $positions, $query, $types, $result_types, $is_manip = false, $limit = null, $offset = null) + { + $this->__construct($db, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $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)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (!in_array($parameter, $this->positions)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] = $value; + if (!is_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); + foreach ($parameters as $key => $parameter) { + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + $err = $this->bindValue($parameter, $values[$parameter], $types[$key]); + $this->db->popExpect(); + if (PEAR::isError($err)) { + if ($err->getCode() == MDB2_ERROR_NOT_FOUND) { + //ignore (extra value for missing placeholder) + continue; + } + return $err; + } + } + 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)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (!in_array($parameter, $this->positions)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $this->values[$parameter] =& $value; + if (!is_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 (PEAR::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 a result handle or MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function &execute($values = null, $result_class = true, $result_wrap_class = false) + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + + $values = (array)$values; + if (!empty($values)) { + $err = $this->bindValueArray($values); + if (PEAR::isError($err)) { + return $this->db->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 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 $this->db->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 (PEAR::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 (is_null($this->positions)) { + return $this->db->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 + */ + var $db_index; + + // }}} + // {{{ constructor: function __construct($db_index) + + /** + * Constructor + */ + function __construct($db_index) + { + $this->db_index = $db_index; + } + + // }}} + // {{{ function MDB2_Module_Common($db_index) + + /** + * PHP 4 Constructor + */ + function MDB2_Module_Common($db_index) + { + $this->__construct($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; +} + +// }}} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Date.php b/program/lib/MDB2/Date.php new file mode 100644 index 000000000..ce846543a --- /dev/null +++ b/program/lib/MDB2/Date.php @@ -0,0 +1,183 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Date.php,v 1.10 2006/03/01 12:15:32 lsmith Exp $ +// + +/** + * @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 + */ + 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 + */ + function mdbToday() + { + return date('Y-m-d'); + } + // }}} + + // {{{ mdbTime() + + /** + * return the current time + * + * @return string current time in the MDB2 format + * @access public + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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/program/lib/MDB2/Driver/Datatype/Common.php b/program/lib/MDB2/Driver/Datatype/Common.php new file mode 100644 index 000000000..01423e44c --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/Common.php @@ -0,0 +1,1830 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.128 2007/11/09 20:54:58 quipo Exp $ + +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 (PEAR::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 (PEAR::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 (PEAR::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 (is_null($value)) { + return null; + } + $db =& $this->getDBInstance(); + if (PEAR::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); + foreach ($row as $key => $value) { + if (empty($types[$key])) { + continue; + } + $value = $this->convertResult($row[$key], $types[$key], $rtrim); + if (PEAR::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 (is_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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $declaration_options = $db->datatype->_getDeclarationOptions($field); + if (PEAR::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']); + + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + if (empty($field['notnull'])) { + $field['default'] = null; + } else { + $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'] = ' '; + } + } + $default = ' DEFAULT '.$this->quote($field['default'], $field['type']); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + + $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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + if (is_null($value) + || ($value === '' && $db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) + ) { + if (!$quote) { + return null; + } + return 'NULL'; + } + + if (is_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 (PEAR::isError($db)) { + return $db; + } + + $value = $db->escape($value, $escape_wildcards); + if (PEAR::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 ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + } + + if (is_resource($value)) { + $db =& $this->getDBInstance(); + if (PEAR::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) + { + $value = $this->_readFile($value); + if (PEAR::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 (PEAR::isError($db)) { + return $db; + } + if (isset($db->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 (PEAR::isError($db)) { + return $db; + } + if (isset($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 (PEAR::isError($db)) { + return $db; + } + if (isset($db->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('/[^.0-9]/', $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 (PEAR::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 (is_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 (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + if (is_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 sensitive + case 'LIKE': + $match = is_null($field) ? 'LIKE ' : $field.' 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 { + if ($operator === 'ILIKE') { + $value = strtolower($value); + } + $escaped = $db->escape($value); + if (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Datatype/mssql.php b/program/lib/MDB2/Driver/Datatype/mssql.php new file mode 100644 index 000000000..4bdad8c57 --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/mssql.php @@ -0,0 +1,498 @@ + | +// | Daniel Convissor | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.59 2007/12/03 20:59:50 quipo Exp $ +// + +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 (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 (PEAR::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 ''; + } + + // }}} + // {{{ _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. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDeclarationOptions($field) + { + $charset = empty($field['charset']) ? '' : + ' '.$this->_getCharsetFieldDeclaration($field['charset']); + + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $field['default'] = empty($field['notnull']) + ? null : $this->valid_default_values[$field['type']]; + if ($field['default'] === '' + && ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) + ) { + $field['default'] = ' '; + } + } + $default = ' DEFAULT '.$this->quote($field['default'], $field['type']); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + if ($default == ' DEFAULT NULL' && $notnull == ' NULL') { + $notnull = ''; + } + + $collation = empty($field['collation']) ? '' : + ' '.$this->_getCollationFieldDeclaration($field['collation']); + return $charset.$default.$notnull.$collation; + } + + // }}} + // {{{ _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 (PEAR::isError($db)) { + return $db; + } + + $default = $autoinc = '';; + if (!empty($field['autoincrement'])) { + $autoinc = ' IDENTITY 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'); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + if ($default == ' DEFAULT NULL' && $notnull == ' NULL') { + $notnull = ''; + } + 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).$default.$notnull.$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 (PEAR::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 (PEAR::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 = bin2hex("0x".$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 'int': + $type[0] = 'integer'; + $length = 4; + break; + case 'bigint': + $type[0] = 'integer'; + $length = 8; + break; + 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': + $type[] = 'blob'; + $length = null; + break; + default: + $db =& $this->getDBInstance(); + if (PEAR::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); + } + // }}} +} + +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Datatype/mysql.php b/program/lib/MDB2/Driver/Datatype/mysql.php new file mode 100644 index 000000000..388d0b2b4 --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/mysql.php @@ -0,0 +1,466 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.62 2007/11/09 20:54:58 quipo Exp $ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common +{ + // {{{ _getCharsetFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset name of the charset + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + function _getCharsetFieldDeclaration($charset) + { + return 'CHARACTER SET '.$charset; + } + + // }}} + // {{{ _getCollationFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + function _getCollationFieldDeclaration($collation) + { + return 'COLLATE '.$collation; + } + + // }}} + // {{{ 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 (PEAR::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': + return 'DOUBLE'; + 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 (PEAR::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'); + } 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.$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 (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $field = is_null($field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'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']); + 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 (PEAR::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); + } + + // }}} +} + +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Datatype/mysqli.php b/program/lib/MDB2/Driver/Datatype/mysqli.php new file mode 100644 index 000000000..40f70f24a --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/mysqli.php @@ -0,0 +1,504 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysqli.php,v 1.61 2007/11/09 20:54:58 quipo Exp $ +// + +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; + } + + // }}} + // {{{ 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 (PEAR::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': + return 'DOUBLE'; + 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 (PEAR::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'); + } 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.$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 (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $field = is_null($field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'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 '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']); + 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 (PEAR::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 (PEAR::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 'i'; + case 'float': + return 'd'; + case 'blob': + return 'b'; + default: + break; + } + return 's'; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Datatype/pgsql.php b/program/lib/MDB2/Driver/Datatype/pgsql.php new file mode 100644 index 000000000..8a7e3ff33 --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/pgsql.php @@ -0,0 +1,555 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.88 2007/11/09 20:54:58 quipo Exp $ + +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 (is_null($value)) { + return null; + } + switch ($type) { + case 'boolean': + return $value == 't'; + 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 (PEAR::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 '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 (PEAR::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'); + } elseif (empty($field['notnull'])) { + $default = ' DEFAULT NULL'; + } + + $notnull = empty($field['notnull']) ? '' : ' NOT 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) + { + 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; + } + if (version_compare(PHP_VERSION, '5.2.0RC6', '>=')) { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $connection = $db->getConnection(); + if (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $field = is_null($field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'ILIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'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 (PEAR::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': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + 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 (PEAR::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 (PEAR::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; + } + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Datatype/sqlite.php b/program/lib/MDB2/Driver/Datatype/sqlite.php new file mode 100644 index 000000000..e518e45fa --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/sqlite.php @@ -0,0 +1,412 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.65 2007/12/03 20:59:51 quipo Exp $ +// + +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 (PEAR::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 (PEAR::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'); + } 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.$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 (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $field = is_null($field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'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 (PEAR::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); + } + + // }}} +} + +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Function/Common.php b/program/lib/MDB2/Driver/Function/Common.php new file mode 100644 index 000000000..74971e2bb --- /dev/null +++ b/program/lib/MDB2/Driver/Function/Common.php @@ -0,0 +1,249 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.19 2007/09/09 13:47:36 quipo Exp $ +// + +/** + * @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 (PEAR::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) + * + * @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'; + } + } + + // }}} + // {{{ 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 (!is_null($length)) { + return "SUBSTRING($value FROM $position FOR $length)"; + } + return "SUBSTRING($value FROM $position)"; + } + + // }}} + // {{{ 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 (PEAR::isError($db)) { + return $db; + } + + $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Function/mssql.php b/program/lib/MDB2/Driver/Function/mssql.php new file mode 100644 index 000000000..4ee028169 --- /dev/null +++ b/program/lib/MDB2/Driver/Function/mssql.php @@ -0,0 +1,178 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.15 2007/08/11 16:02:22 quipo Exp $ +// + +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 (PEAR::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()'; + } + } + + // }}} + // {{{ 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 (!is_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()'; + } + + // }}} +} +// }}} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Function/mysql.php b/program/lib/MDB2/Driver/Function/mysql.php new file mode 100644 index 000000000..90dea8174 --- /dev/null +++ b/program/lib/MDB2/Driver/Function/mysql.php @@ -0,0 +1,120 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.11 2007/01/12 11:29:12 quipo Exp $ +// + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_mysql extends MDB2_Driver_Function_Common +{ + // }}} + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'CALL '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ 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()'; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Function/mysqli.php b/program/lib/MDB2/Driver/Function/mysqli.php new file mode 100644 index 000000000..5af55c1fd --- /dev/null +++ b/program/lib/MDB2/Driver/Function/mysqli.php @@ -0,0 +1,128 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysqli.php,v 1.13 2007/01/12 11:29:12 quipo Exp $ +// + +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 (PEAR::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; + } + + // }}} + // {{{ 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()'; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Function/pgsql.php b/program/lib/MDB2/Driver/Function/pgsql.php new file mode 100644 index 000000000..df1d455f3 --- /dev/null +++ b/program/lib/MDB2/Driver/Function/pgsql.php @@ -0,0 +1,99 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.9 2006/06/12 21:48:43 lsmith Exp $ + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 MySQL driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Function_pgsql extends MDB2_Driver_Function_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function &executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT * FROM '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + // }}} + // {{{ 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/program/lib/MDB2/Driver/Function/sqlite.php b/program/lib/MDB2/Driver/Function/sqlite.php new file mode 100644 index 000000000..3a4960bea --- /dev/null +++ b/program/lib/MDB2/Driver/Function/sqlite.php @@ -0,0 +1,125 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.8 2006/06/13 22:55:55 lsmith Exp $ +// + +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\')'; + } + } + + // }}} + // {{{ 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 (!is_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)'; + } + + // }}} +} +?> diff --git a/program/lib/MDB2/Driver/Manager/Common.php b/program/lib/MDB2/Driver/Manager/Common.php new file mode 100644 index 000000000..da2e329ef --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/Common.php @@ -0,0 +1,935 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.68 2007/12/03 20:59:15 quipo Exp $ +// + +/** + * @package MDB2 + * @category Database + * @author Lukas Smith + */ + +/** + * 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 + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getFieldDeclarationList() + + /** + * Get declaration of a number of field in bulk + * + * @param array $fields a multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Boolean value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * + * @return mixed string on success, a MDB2 error on failure + * @access public + */ + function getFieldDeclarationList($fields) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_array($fields) || empty($fields)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'missing any fields', __FUNCTION__); + } + foreach ($fields as $field_name => $field) { + $query = $db->getDeclaration($field['type'], $field_name, $field); + if (PEAR::isError($query)) { + return $query; + } + $query_fields[] = $query; + } + return implode(', ', $query_fields); + } + + // }}} + // {{{ _fixSequenceName() + + /** + * Removes any formatting in an sequence name using the 'seqname_format' option + * + * @param string $sqn string that containts name of a potential sequence + * @param bool $check if only formatted sequences should be returned + * @return string name of the sequence with possible formatting removed + * @access protected + */ + function _fixSequenceName($sqn, $check = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $seq_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['seqname_format']).'$/i'; + $seq_name = preg_replace($seq_pattern, '\\1', $sqn); + if ($seq_name && !strcasecmp($sqn, $db->getSequenceName($seq_name))) { + return $seq_name; + } + if ($check) { + return false; + } + return $sqn; + } + + // }}} + // {{{ _fixIndexName() + + /** + * Removes any formatting in an index name using the 'idxname_format' option + * + * @param string $idx string that containts name of anl index + * @return string name of the index with eventual formatting removed + * @access protected + */ + function _fixIndexName($idx) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $idx_pattern = '/^'.preg_replace('/%s/', '([a-z0-9_]+)', $db->options['idxname_format']).'$/i'; + $idx_name = preg_replace($idx_pattern, '\\1', $idx); + if ($idx_name && !strcasecmp($idx, $db->getIndexName($idx_name))) { + return $idx_name; + } + return $idx; + } + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($database, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($database) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * @param array $options An associative array of table options + * + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'TEMPORARY'; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP TABLE $name"); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implementedd', __FUNCTION__); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the view names from + * a database other than the current one + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default. + * NB: not all the drivers can get the table names from + * a database other than the current one + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + return ''; + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * The full structure of the array looks like this: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table ADD CONSTRAINT $name"; + if (!empty($definition['primary'])) { + $query.= ' PRIMARY KEY'; + } elseif (!empty($definition['unique'])) { + $query.= ' UNIQUE'; + } elseif (!empty($definition['foreign'])) { + $query.= ' FOREIGN KEY'; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("ALTER TABLE $table DROP CONSTRAINT $name"); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * NB: not all the drivers can get the sequence names from + * a database other than the current one + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Manager/mssql.php b/program/lib/MDB2/Driver/Manager/mssql.php new file mode 100644 index 000000000..9cc66f3b7 --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/mssql.php @@ -0,0 +1,815 @@ + | +// | David Coallier | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.93 2007/12/03 20:59:15 quipo 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_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 (PEAR::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); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + 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); + } + + // }}} + // {{{ 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 (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + break; + case 'remove': + break; + case 'name': + case 'rename': + case 'change': + 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.= ', '; + } else { + $query.= 'ADD COLUMN '; + } + $query.= $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 COLUMN ' . $field_name; + } + } + + if (!$query) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("ALTER TABLE $name $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 (PEAR::isError($db)) { + return $db; + } + + $query = 'EXEC sp_tables @table_type = "\'TABLE\'"'; + $table_names = $db->queryCol($query, null, 2); + if (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SELECT name FROM sys.databases'); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SELECT DISTINCT loginame FROM master..sysprocesses'); + if (PEAR::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 (PEAR::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 (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * + * @return mixed array of trigger names on success, 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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $table.$name"); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $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 (PEAR::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 (PEAR::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 (PEAR::isError($res)) { + return $res; + } + + $query = "SET IDENTITY_INSERT $sequence_name ON ". + "INSERT INTO $sequence_name ($seqcol_name) VALUES ($start)"; + $res = $db->exec($query); + + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * 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 (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sysobjects WHERE xtype = 'U'"; + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? + 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} + +// }}} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Manager/mysql.php b/program/lib/MDB2/Driver/Manager/mysql.php new file mode 100644 index 000000000..84109be27 --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/mysql.php @@ -0,0 +1,1064 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.100 2007/12/03 20:59:15 quipo Exp $ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common +{ + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = 'CREATE DATABASE ' . $name; + if (!empty($options['charset'])) { + $query .= ' DEFAULT CHARACTER SET ' . $options['charset']; + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + 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 (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "DROP DATABASE $name"; + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + 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'])) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete'])) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + return $query; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $query = ''; + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $query .= 'RENAME TO ' . $change_name; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'DROP ' . $field_name; + } + } + + $rename = array(); + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $rename[$field['name']] = $field_name; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$field_name])) { + $old_field_name = $rename[$field_name]; + unset($rename[$field_name]); + } else { + $old_field_name = $field_name; + } + $old_field_name = $db->quoteIdentifier($old_field_name, true); + $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + } + + if (!empty($rename) && is_array($rename)) { + foreach ($rename as $rename_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamed_field]; + $renamed_field = $db->quoteIdentifier($renamed_field, true); + $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); + } + } + + if (!$query) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("ALTER TABLE $name $query"); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SHOW DATABASES'); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER'); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM mysql.proc"; + /* + SELECT ROUTINE_NAME + FROM INFORMATION_SCHEMA.ROUTINES + WHERE ROUTINE_TYPE = 'FUNCTION' + */ + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW TRIGGERS'; + if (!is_null($table)) { + $table = $db->quote($table, 'text'); + $query .= " LIKE $table"; + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW /*!50002 FULL*/ TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; + + $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table) { + if (!$this->_fixSequenceName($table[0], true)) { + $result[] = $table[0]; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW FULL TABLES'; + if (!is_null($database)) { + $query.= " FROM $database"; + } + $query.= " WHERE Table_type = 'VIEW'"; + + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $result = $db->queryCol("SHOW COLUMNS FROM $table"); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @author Leoncx + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * 'length' => 10 + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + if (!empty($fieldinfo['length'])) { + $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; + } else { + $fields[] = $db->quoteIdentifier($field, true); + } + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name ON $table"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { + $result[$index] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $type = ''; + $name = $db->quoteIdentifier($db->getIndexName($name), true); + if (!empty($definition['primary'])) { + $type = 'PRIMARY'; + $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 = $db->quoteIdentifier($table, true); + $query = "ALTER TABLE $table ADD $type $name"; + if (!empty($definition['foreign'])) { + $query .= ' FOREIGN KEY '; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + if ($primary || strtolower($name) == 'primary') { + $query = "ALTER TABLE $table DROP PRIMARY KEY"; + } else { + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP INDEX $name"; + } + return $db->exec($query); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true); + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if (!$index_data[$non_unique]) { + if ($index_data[$key_name] !== 'PRIMARY') { + $index = $this->_fixIndexName($index_data[$key_name]); + } else { + $index = 'PRIMARY'; + } + if (!empty($index)) { + $result[$index] = true; + } + } + } + + //list FOREIGN KEY constraints... + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $definition = $db->queryOne($query, 'text', 1); + if (!PEAR::isError($definition) && !empty($definition)) { + $pattern = '/\bCONSTRAINT\s+([^\s]+)\s+FOREIGN KEY\b/i'; + if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 1) { + foreach ($matches[1] as $constraint) { + $result[$constraint] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $res = $db->exec($query); + + if (PEAR::isError($res)) { + return $res; + } + + if ($start == 1) { + return MDB2_OK; + } + + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; + $res = $db->exec($query); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Manager/mysqli.php b/program/lib/MDB2/Driver/Manager/mysqli.php new file mode 100644 index 000000000..42782fca5 --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/mysqli.php @@ -0,0 +1,1067 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysqli.php,v 1.87 2007/12/03 20:59:15 quipo Exp $ +// + +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 (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = 'CREATE DATABASE ' . $name; + if (!empty($options['charset'])) { + $query .= ' DEFAULT CHARACTER SET ' . $options['charset']; + } + if (!empty($options['collation'])) { + $query .= ' COLLATE ' . $options['collation']; + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + 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 (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + $query = "DROP DATABASE $name"; + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + 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'])) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + if (!empty($definition['ondelete'])) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + return $query; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * array( + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = $this->_getCreateTableQuery($name, $fields, $options); + if (PEAR::isError($query)) { + return $query; + } + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $query = ''; + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $query .= 'RENAME TO ' . $change_name; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $db->getDeclaration($field['type'], $field_name, $field); + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $field_name = $db->quoteIdentifier($field_name, true); + $query.= 'DROP ' . $field_name; + } + } + + $rename = array(); + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $rename[$field['name']] = $field_name; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$field_name])) { + $old_field_name = $rename[$field_name]; + unset($rename[$field_name]); + } else { + $old_field_name = $field_name; + } + $old_field_name = $db->quoteIdentifier($old_field_name, true); + $query.= "CHANGE $old_field_name " . $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + } + + if (!empty($rename) && is_array($rename)) { + foreach ($rename as $rename_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $field = $changes['rename'][$renamed_field]; + $renamed_field = $db->quoteIdentifier($renamed_field, true); + $query.= 'CHANGE ' . $renamed_field . ' ' . $db->getDeclaration($field['definition']['type'], $field['name'], $field['definition']); + } + } + + if (!$query) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("ALTER TABLE $name $query"); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->queryCol('SHOW DATABASES'); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->queryCol('SELECT DISTINCT USER FROM mysql.USER'); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM mysql.proc"; + /* + SELECT ROUTINE_NAME + FROM INFORMATION_SCHEMA.ROUTINES + WHERE ROUTINE_TYPE = 'FUNCTION' + */ + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW TRIGGERS'; + if (!is_null($table)) { + $table = $db->quote($table, 'text'); + $query .= " LIKE $table"; + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @param string database, the current is default + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW /*!50002 FULL*/ TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $query.= "/*!50002 WHERE Table_type = 'BASE TABLE'*/"; + + $table_names = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table) { + if (!$this->_fixSequenceName($table[0], true)) { + $result[] = $table[0]; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string database, the current is default + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SHOW FULL TABLES'; + if (!is_null($database)) { + $query.= " FROM $database"; + } + $query.= " WHERE Table_type = 'VIEW'"; + + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $result = $db->queryCol("SHOW COLUMNS FROM $table"); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @author Leoncx + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * 'length' => 10 + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field => $fieldinfo) { + if (!empty($fieldinfo['length'])) { + $fields[] = $db->quoteIdentifier($field, true) . '(' . $fieldinfo['length'] . ')'; + } else { + $fields[] = $db->quoteIdentifier($field, true); + } + } + $query .= ' ('. implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($db->getIndexName($name), true); + return $db->exec("DROP INDEX $name ON $table"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $table = $db->quoteIdentifier($table, true); + $query = "SHOW INDEX FROM $table"; + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if ($index_data[$non_unique] && ($index = $this->_fixIndexName($index_data[$key_name]))) { + $result[$index] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $type = ''; + $name = $db->quoteIdentifier($db->getIndexName($name), true); + if (!empty($definition['primary'])) { + $type = 'PRIMARY'; + $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 = $db->quoteIdentifier($table, true); + $query = "ALTER TABLE $table ADD $type $name"; + if (!empty($definition['foreign'])) { + $query .= ' FOREIGN KEY '; + } + $fields = array(); + foreach (array_keys($definition['fields']) as $field) { + $fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $fields) . ')'; + if (!empty($definition['foreign'])) { + $query.= ' REFERENCES ' . $db->quoteIdentifier($definition['references']['table'], true); + $referenced_fields = array(); + foreach (array_keys($definition['references']['fields']) as $field) { + $referenced_fields[] = $db->quoteIdentifier($field, true); + } + $query .= ' ('. implode(', ', $referenced_fields) . ')'; + $query .= $this->_getAdvancedFKOptions($definition); + } + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + if ($primary || strtolower($name) == 'primary') { + $query = "ALTER TABLE $table DROP PRIMARY KEY"; + } else { + $name = $db->quoteIdentifier($db->getIndexName($name), true); + $query = "ALTER TABLE $table DROP INDEX $name"; + } + return $db->exec($query); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + $non_unique = 'Non_unique'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + $non_unique = strtolower($non_unique); + } else { + $key_name = strtoupper($key_name); + $non_unique = strtoupper($non_unique); + } + } + + $query = 'SHOW INDEX FROM ' . $db->quoteIdentifier($table, true); + $indexes = $db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $index_data) { + if (!$index_data[$non_unique]) { + if ($index_data[$key_name] !== 'PRIMARY') { + $index = $this->_fixIndexName($index_data[$key_name]); + } else { + $index = 'PRIMARY'; + } + if (!empty($index)) { + $result[$index] = true; + } + } + } + + //list FOREIGN KEY constraints... + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $definition = $db->queryOne($query, 'text', 1); + if (!PEAR::isError($definition) && !empty($definition)) { + $pattern = '/\bCONSTRAINT\s+([^\s]+)\s+FOREIGN KEY\b/i'; + if (preg_match_all($pattern, str_replace('`', '', $definition), $matches) > 1) { + foreach ($matches[1] as $constraint) { + $result[$constraint] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'charset' => 'utf8', + * 'collate' => 'utf8_unicode_ci', + * 'type' => 'innodb', + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1, $options = array()) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + + $options_strings = array(); + + if (!empty($options['comment'])) { + $options_strings['comment'] = 'COMMENT = '.$db->quote($options['comment'], 'text'); + } + + if (!empty($options['charset'])) { + $options_strings['charset'] = 'DEFAULT CHARACTER SET '.$options['charset']; + if (!empty($options['collate'])) { + $options_strings['charset'].= ' COLLATE '.$options['collate']; + } + } + + $type = false; + if (!empty($options['type'])) { + $type = $options['type']; + } elseif ($db->options['default_table_type']) { + $type = $db->options['default_table_type']; + } + if ($type) { + $options_strings[] = "ENGINE = $type"; + } + + if (!empty($options_strings)) { + $query.= ' '.implode(' ', $options_strings); + } + + $query = "CREATE TABLE $sequence_name ($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))"; + if (!empty($options_strings)) { + $query .= ' '.implode(' ', $options_strings); + } + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + + if ($start == 1) { + return MDB2_OK; + } + + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'; + $res = $db->exec($query); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string database, the current is default + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($database = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SHOW TABLES"; + if (!is_null($database)) { + $query .= " FROM $database"; + } + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Manager/pgsql.php b/program/lib/MDB2/Driver/Manager/pgsql.php new file mode 100644 index 000000000..d5ebbc840 --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/pgsql.php @@ -0,0 +1,765 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.74 2007/12/03 20:59:15 quipo Exp $ + +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 (PEAR::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); + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->standaloneQuery("DROP DATABASE $name", 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; + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case '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; + } + + 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 (PEAR::isError($result)) { + return $result; + } + } + } + + 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 (PEAR::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 (PEAR::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); + $query = "ALTER $field_name TYPE ".$db->datatype->getTypeDeclaration($field['definition']); + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::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 (PEAR::isError($result)) { + return $result; + } + } + if (!empty($field['definition']['notnull'])) { + $query = "ALTER $field_name ".($field['definition']['notnull'] ? 'SET' : 'DROP').' NOT NULL'; + $result = $db->exec("ALTER TABLE $name $query"); + if (PEAR::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 (PEAR::isError($result)) { + return $result; + } + } + } + + $name = $db->quoteIdentifier($name, true); + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("ALTER TABLE $name RENAME TO ".$change_name); + if (PEAR::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 (PEAR::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 (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $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 (PEAR::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 (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT viewname FROM pg_views NATURAL JOIN pg_tables'; + $query.= ' WHERE tablename ='.$db->quote($table, 'text'); + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = " + SELECT + 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 (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT trg.tgname AS trigger_name + FROM pg_trigger trg, + pg_class tbl + WHERE trg.tgrelid = tbl.oid'; + if (!is_null($table)) { + $table = $db->quote(strtoupper($table), 'text'); + $query .= " AND tbl.relname = $table"; + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db =& $this->getDBInstance(); + if (PEAR::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 (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $db->setLimit(1); + $result2 = $db->query("SELECT * FROM $table"); + if (PEAR::isError($result2)) { + return $result2; + } + $result = $result2->getColumnNames(); + $result2->free(); + if (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $subquery = "SELECT indexrelid FROM pg_index, pg_class"; + $subquery.= " WHERE pg_class.relname=$table AND pg_class.oid=pg_index.indrelid AND indisunique != 't' AND indisprimary != 't'"; + $query = "SELECT relname FROM pg_class WHERE oid IN ($subquery)"; + $indexes = $db->queryCol($query, 'text'); + if (PEAR::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); + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quote($table, 'text'); + $subquery = "SELECT indexrelid FROM pg_index, pg_class"; + $subquery.= " WHERE pg_class.relname=$table AND pg_class.oid=pg_index.indrelid AND (indisunique = 't' OR indisprimary = 't')"; + $query = "SELECT relname FROM pg_class WHERE oid IN ($subquery)"; + $constraints = $db->queryCol($query); + if (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("CREATE SEQUENCE $sequence_name INCREMENT 1". + ($start < 1 ? " MINVALUE $start" : '')." START $start"); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP SEQUENCE $sequence_name"); + } + + // }}} + // {{{ 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 (PEAR::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 (PEAR::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; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Manager/sqlite.php b/program/lib/MDB2/Driver/Manager/sqlite.php new file mode 100644 index 000000000..f126ff146 --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/sqlite.php @@ -0,0 +1,1325 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.72 2007/12/03 20:59:15 quipo Exp $ +// + +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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + 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 (PEAR::isError($result)) { + return $result; + } + // create triggers to enforce FOREIGN KEY constraints + if (!empty($options['foreign_keys'])) { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + //set actions to 'RESTRICT' if not set + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? 'RESTRICT' : strtoupper($fkdef['onupdate']); + $fkdef['ondelete'] = empty($fkdef['ondelete']) ? 'RESTRICT' : 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 (PEAR::isError($result)) { + return $result; + } + + $result = $db->exec(sprintf($query, $trigger_names['update'], 'UPDATE', 'update')); + if (PEAR::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; $iloadModule('Reverse', null, true); + $default_values = array(); + foreach ($table_fields as $table_field) { + $field_definition = $db->reverse->getTableFieldDefinition($name, $field); + if (PEAR::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;'; + } else { + //'RESTRICT' + $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;'; + } else { + //'RESTRICT' + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . '; END;'; + } + + if (PEAR::isError($result)) { + return $result; + } + $result = $db->exec($sql_delete); + if (PEAR::isError($result)) { + return $result; + } + $result = $db->exec($sql_update); + if (PEAR::isError($result)) { + return $result; + } + } + } + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + //delete the triggers associated to existing FK constraints + $constraints = $this->listTableConstraints($name); + if (!PEAR::isError($constraints) && !empty($constraints)) { + $db->loadModule('Reverse', null, true); + foreach ($constraints as $constraint) { + $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP TABLE $name"); + } + + // }}} + // {{{ 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 (PEAR::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 (PEAR::isError($fields)) { + return $fields; + } + + $fields = array_flip($fields); + foreach ($fields as $field => $value) { + $definition = $db->reverse->getTableFieldDefinition($name, $field); + if (PEAR::isError($definition)) { + return $definition; + } + $fields[$field] = $definition[0]; + } + + $indexes = $db->manager->listTableIndexes($name); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $indexes = array_flip($indexes); + foreach ($indexes as $index => $value) { + $definition = $db->reverse->getTableIndexDefinition($name, $index); + if (PEAR::isError($definition)) { + return $definition; + } + $indexes[$index] = $definition; + } + + $constraints = $db->manager->listTableConstraints($name); + if (PEAR::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 (PEAR::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 (PEAR::isError($result)) { + return $result; + } + + $result = $this->createTable($name_new, $fields, $options); + if (PEAR::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 (PEAR::isError($stmt)) { + return $stmt; + } + foreach ($data as $row) { + $result = $stmt->execute($row); + if (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Reverse', null, true); + if (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='trigger' AND sql NOT NULL"; + if (!is_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 (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @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 (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE 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) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->getIndexName($name); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $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 (PEAR::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 (PEAR::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) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + if ($primary || $name == 'PRIMARY') { + return $this->alterTable($table, array(), false, array('primary' => null)); + } + + $db =& $this->getDBInstance(); + if (PEAR::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 (!PEAR::isError($definition) && !empty($definition['foreign'])) { + //first drop the FK enforcing triggers + $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + //then drop the constraint itself + return $this->alterTable($table, array(), false, array('foreign_keys' => array($name => null))); + } + + $name = $db->getIndexName($name); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ _dropFKTriggers() + + /** + * Drop the triggers created to enforce the FOREIGN KEY constraint on the table + * + * @param string $table table name + * @param string $fkname FOREIGN KEY constraint name + * @param string $referenced_table referenced table name + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropFKTriggers($table, $fkname, $referenced_table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $triggers = $this->listTableTriggers($table); + $triggers2 = $this->listTableTriggers($referenced_table); + if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { + $triggers = array_merge($triggers, $triggers2); + $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/i'; + foreach ($triggers as $trigger) { + if (preg_match($pattern, $trigger)) { + $result = $db->exec('DROP TRIGGER '.$trigger); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + return MDB2_OK; + } + + // }]] + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $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 (PEAR::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 (PEAR::isError($table_def)) { + return $table_def; + } + if (preg_match("/\bPRIMARY\s+KEY\b\s*\(([^)]+)/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 (PEAR::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 (PEAR::isError($res)) { + return $res; + } + if ($start == 1) { + return MDB2_OK; + } + $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db =& $this->getDBInstance(); + if (PEAR::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 (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Native/Common.php b/program/lib/MDB2/Driver/Native/Common.php new file mode 100644 index 000000000..c01caa35b --- /dev/null +++ b/program/lib/MDB2/Driver/Native/Common.php @@ -0,0 +1,61 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.2 2007/09/09 13:47:36 quipo Exp $ +// + +/** + * 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/program/lib/MDB2/Driver/Native/mssql.php b/program/lib/MDB2/Driver/Native/mssql.php new file mode 100644 index 000000000..e72faba92 --- /dev/null +++ b/program/lib/MDB2/Driver/Native/mssql.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.9 2006/06/18 21:59:05 lsmith Exp $ +// + +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/program/lib/MDB2/Driver/Native/mysql.php b/program/lib/MDB2/Driver/Native/mysql.php new file mode 100644 index 000000000..90ff068e6 --- /dev/null +++ b/program/lib/MDB2/Driver/Native/mysql.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.9 2006/06/18 21:59:05 lsmith Exp $ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 MySQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_mysql extends MDB2_Driver_Native_Common +{ +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Native/mysqli.php b/program/lib/MDB2/Driver/Native/mysqli.php new file mode 100644 index 000000000..326c1d87e --- /dev/null +++ b/program/lib/MDB2/Driver/Native/mysqli.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysqli.php,v 1.8 2006/06/18 21:59:05 lsmith Exp $ +// + +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/program/lib/MDB2/Driver/Native/pgsql.php b/program/lib/MDB2/Driver/Native/pgsql.php new file mode 100644 index 000000000..acab8389c --- /dev/null +++ b/program/lib/MDB2/Driver/Native/pgsql.php @@ -0,0 +1,88 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.12 2006/07/15 13:07:15 lsmith Exp $ + +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 (PEAR::isError($db)) { + return $db; + } + + $connection = $db->getConnection(); + if (PEAR::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; + } + +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Native/sqlite.php b/program/lib/MDB2/Driver/Native/sqlite.php new file mode 100644 index 000000000..0213293a5 --- /dev/null +++ b/program/lib/MDB2/Driver/Native/sqlite.php @@ -0,0 +1,60 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.9 2006/06/18 21:59:05 lsmith Exp $ +// + +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/program/lib/MDB2/Driver/Reverse/Common.php b/program/lib/MDB2/Driver/Reverse/Common.php new file mode 100644 index 000000000..43a2f251b --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/Common.php @@ -0,0 +1,511 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Common.php,v 1.41 2007/09/09 13:47:36 quipo Exp $ +// + +/** + * @package MDB2 + * @category Database + */ + +/** + * These are constants for the tableInfo-function + * they are bitwised or'ed. so if there are more constants to be defined + * in the future, adjust MDB2_TABLEINFO_FULL accordingly + */ + +define('MDB2_TABLEINFO_ORDER', 1); +define('MDB2_TABLEINFO_ORDERTABLE', 2); +define('MDB2_TABLEINFO_FULL', 3); + +/** + * Base class for the schema reverse engineering module that is extended by each MDB2 driver + * + * To load this module in the MDB2 object: + * $mdb->loadModule('Reverse'); + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_Common extends MDB2_Module_Common +{ + // {{{ splitTableSchema() + + /** + * Split the "[owner|schema].table" notation into an array + * @access private + */ + function splitTableSchema($table) + { + $ret = array(); + if (strpos($table, '.') !== false) { + return explode('.', $table); + } + return array(null, $table); + } + + // }}} + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $field name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure. + * The returned array contains an array for each field definition, + * with all or some of these indices, depending on the field data type: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + * + * array ( + * [fields] => array ( + * [field1name] => array() // one entry per each field covered + * [field2name] => array() // by the index + * [field3name] => array( + * [sorting] => ascending + * ) + * ) + * ); + * + * @access public + */ + function getTableIndexDefinition($table, $index) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of an constraints into an array + * + * @param string $table name of table that should be used in method + * @param string $index name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [primary] => 0
+     *              [unique]  => 0
+     *              [foreign] => 1
+     *              [check]   => 0
+     *              [fields] => array (
+     *                  [field1name] => array() // one entry per each field covered
+     *                  [field2name] => array() // by the index
+     *                  [field3name] => array(
+     *                      [sorting]  => ascending
+     *                      [position] => 3
+     *                  )
+     *              )
+     *              [references] => array(
+     *                  [table] => name
+     *                  [fields] => array(
+     *                      [field1name] => array(  //one entry per each referenced field
+     *                           [position] => 1
+     *                      )
+     *                  )
+     *              )
+     *              [deferrable] => 0
+     *              [initiallydeferred] => 0
+     *              [onupdate] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [ondelete] => CASCADE|RESTRICT|SET NULL|SET DEFAULT|NO ACTION
+     *              [match] => SIMPLE|PARTIAL|FULL
+     *          );
+     *          
+ * @access public + */ + function getTableConstraintDefinition($table, $index) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the structure of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [start] => n
+     *          );
+     *          
+ * @access public + */ + function getSequenceDefinition($sequence) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $start = $db->currId($sequence); + if (PEAR::isError($start)) { + return $start; + } + if ($db->supports('current_id')) { + $start++; + } else { + $db->warnings[] = 'database does not support getting current + sequence value, the sequence value was incremented'; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * The returned array has this structure: + *
+     *          array (
+     *              [trigger_name]    => 'trigger name',
+     *              [table_name]      => 'table name',
+     *              [trigger_body]    => 'trigger body definition',
+     *              [trigger_type]    => 'BEFORE' | 'AFTER',
+     *              [trigger_event]   => 'INSERT' | 'UPDATE' | 'DELETE'
+     *                  //or comma separated list of multiple events, when supported
+     *              [trigger_enabled] => true|false
+     *              [trigger_comment] => 'trigger comment',
+     *          );
+     *          
+ * The oci8 driver also returns a [when_clause] index. + * @access public + */ + function getTriggerDefinition($trigger) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which $mode + * you select. The sample output below is based on this query: + *
+     *    SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+     *    FROM tblFoo
+     *    JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+     * 
+ * + *
    + *
  • + * + * null (default) + *
    +     *   [0] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   [1] => Array (
    +     *       [table] => tblFoo
    +     *       [name] => fldPhone
    +     *       [type] => string
    +     *       [len] => 20
    +     *       [flags] =>
    +     *   )
    +     *   [2] => Array (
    +     *       [table] => tblBar
    +     *       [name] => fldId
    +     *       [type] => int
    +     *       [len] => 11
    +     *       [flags] => primary_key not_null
    +     *   )
    +     *   
    + * + *
  • + * + * MDB2_TABLEINFO_ORDER + * + *

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

    + * + *

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

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

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

    + * + *
    +     *   [num_fields] => 3
    +     *   [ordertable] => Array (
    +     *       [tblFoo] => Array (
    +     *           [fldId] => 0
    +     *           [fldPhone] => 1
    +     *       )
    +     *       [tblBar] => Array (
    +     *           [fldId] => 2
    +     *       )
    +     *   )
    +     *   
    + * + *
  • + *
+ * + * The flags element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + primary_key + * + unique_key + * + multiple_key + * + not_null + * + * Most DBMS's only provide the table and flags + * elements if $result is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has MDB2_PORTABILITY_FIX_CASE + * turned on, the names of tables and fields will be lower or upper cased. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * MDB2_TABLEINFO_ORDERTABLE, + * MDB2_TABLEINFO_ORDER or + * MDB2_TABLEINFO_FULL (which does both). + * These are bitwise, so the first two can be + * combined using |. + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!is_string($result)) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + } + + $db->loadModule('Manager', null, true); + $fields = $db->manager->listTableFields($result); + if (PEAR::isError($fields)) { + return $fields; + } + + $flags = array(); + + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + + $indexes = $db->manager->listTableIndexes($result); + if (PEAR::isError($indexes)) { + $db->setOption('idxname_format', $idxname_format); + return $indexes; + } + + foreach ($indexes as $index) { + $definition = $this->getTableIndexDefinition($result, $index); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + if (count($definition['fields']) > 1) { + foreach ($definition['fields'] as $field => $sort) { + $flags[$field] = 'multiple_key'; + } + } + } + + $constraints = $db->manager->listTableConstraints($result); + if (PEAR::isError($constraints)) { + return $constraints; + } + + foreach ($constraints as $constraint) { + $definition = $this->getTableConstraintDefinition($result, $constraint); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $flag = !empty($definition['primary']) + ? 'primary_key' : (!empty($definition['unique']) + ? 'unique_key' : false); + if ($flag) { + foreach ($definition['fields'] as $field => $sort) { + if (empty($flags[$field]) || $flags[$field] != 'primary_key') { + $flags[$field] = $flag; + } + } + } + } + + if ($mode) { + $res['num_fields'] = count($fields); + } + + foreach ($fields as $i => $field) { + $definition = $this->getTableFieldDefinition($result, $field); + if (PEAR::isError($definition)) { + $db->setOption('idxname_format', $idxname_format); + return $definition; + } + $res[$i] = $definition[0]; + $res[$i]['name'] = $field; + $res[$i]['table'] = $result; + $res[$i]['type'] = preg_replace('/^([a-z]+).*$/i', '\\1', trim($definition[0]['nativetype'])); + // 'primary_key', 'unique_key', 'multiple_key' + $res[$i]['flags'] = empty($flags[$field]) ? '' : $flags[$field]; + // not_null', 'unsigned', 'auto_increment', 'default_[rawencodedvalue]' + if (!empty($res[$i]['notnull'])) { + $res[$i]['flags'].= ' not_null'; + } + if (!empty($res[$i]['unsigned'])) { + $res[$i]['flags'].= ' unsigned'; + } + if (!empty($res[$i]['auto_increment'])) { + $res[$i]['flags'].= ' autoincrement'; + } + if (!empty($res[$i]['default'])) { + $res[$i]['flags'].= ' default_'.rawurlencode($res[$i]['default']); + } + + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + $db->setOption('idxname_format', $idxname_format); + return $res; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Reverse/mssql.php b/program/lib/MDB2/Driver/Reverse/mssql.php new file mode 100644 index 000000000..a27c95d57 --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/mssql.php @@ -0,0 +1,653 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.48 2007/11/25 13:38:29 quipo Exp $ +// + +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 (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::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 (PEAR::isError($column)) { + return $column; + } + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + 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 (PEAR::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 (PEAR::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 (!PEAR::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 (PEAR::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 (PEAR::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 (!PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($def)) { + return $def; + } + $trg_body = $db->queryCol('EXEC sp_helptext '. $db->quote($trigger, 'text'), 'text'); + if (!PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @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 (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } + + // }}} + // {{{ _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 (PEAR::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); + } + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Reverse/mysql.php b/program/lib/MDB2/Driver/Reverse/mysql.php new file mode 100644 index 000000000..ac6abedbc --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/mysql.php @@ -0,0 +1,514 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.79 2007/11/25 13:38:29 quipo Exp $ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MySQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_mysql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::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 (PEAR::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 (PEAR::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 (is_null($default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['extra']) && $column['extra'] == 'auto_increment') { + $autoincrement = true; + } + $collate = null; + if (!empty($column['collation'])) { + $collate = $column['collation']; + $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate); + } + + $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; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + if (!is_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 (PEAR::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 (!PEAR::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, $db->quote($index_name))); + if (PEAR::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 (PEAR::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 (!PEAR::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, $db->quote($constraint_name))); + if (PEAR::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? + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $constraint = $db->queryOne($query, 'text', 1); + if (!PEAR::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); + } + } + $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^ ]+) \(([^\)]+)\)/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 ([^ ]+) \(([^\)]+)\)/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['onupdate'] = 'NO ACTION'; + $definition['ondelete'] = 'NO ACTION'; + $definition['match'] = 'SIMPLE'; + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + 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 $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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($resource); + $res = array(); + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($resource, $i)), + 'name' => $case_func(@mysql_field_name($resource, $i)), + 'type' => @mysql_field_type($resource, $i), + 'length' => @mysql_field_len($resource, $i), + 'flags' => @mysql_field_flags($resource, $i), + ); + if ($res[$i]['type'] == 'string') { + $res[$i]['type'] = 'char'; + } elseif ($res[$i]['type'] == 'unknown') { + $res[$i]['type'] = 'decimal'; + } + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Reverse/mysqli.php b/program/lib/MDB2/Driver/Reverse/mysqli.php new file mode 100644 index 000000000..741fac059 --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/mysqli.php @@ -0,0 +1,578 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysqli.php,v 1.69 2007/11/25 13:38:29 quipo Exp $ +// + +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 (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::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 (PEAR::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 (PEAR::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 (is_null($default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['extra']) && $column['extra'] == 'auto_increment') { + $autoincrement = true; + } + $collate = null; + if (!empty($column['collation'])) { + $collate = $column['collation']; + $charset = preg_replace('/(.+?)(_.+)?/', '$1', $collate); + } + + $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; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + if (!is_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 (PEAR::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 (!PEAR::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, $db->quote($index_name))); + if (PEAR::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 (PEAR::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 (!PEAR::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, $db->quote($constraint_name))); + if (PEAR::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? + $query = 'SHOW CREATE TABLE '. $db->escape($table); + $constraint = $db->queryOne($query, 'text', 1); + if (!PEAR::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); + } + } + $pattern = '/\bCONSTRAINT\s+'.$constraint_name.'\s+FOREIGN KEY\s+\(([^\)]+)\) \bREFERENCES\b ([^ ]+) \(([^\)]+)\)/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 ([^ ]+) \(([^\)]+)\)/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['onupdate'] = 'NO ACTION'; + $definition['ondelete'] = 'NO ACTION'; + $definition['match'] = 'SIMPLE'; + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + 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 $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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Reverse/pgsql.php b/program/lib/MDB2/Driver/Reverse/pgsql.php new file mode 100644 index 000000000..930eea93f --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/pgsql.php @@ -0,0 +1,527 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.68 2007/11/25 13:38:29 quipo Exp $ + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 PostGreSQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Paul Cooper + * @author Lorenzo Alberton + */ +class MDB2_Driver_Reverse_pgsql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT a.attname AS name, + t.typname AS type, + CASE a.attlen + WHEN -1 THEN + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod / 65536) + WHEN 'decimal' THEN (a.atttypmod / 65536) + WHEN 'money' THEN (a.atttypmod / 65536) + ELSE CASE a.atttypmod + WHEN -1 THEN NULL + ELSE a.atttypmod - 4 + END + END + ELSE a.attlen + END AS length, + CASE t.typname + WHEN 'numeric' THEN (a.atttypmod % 65536) - 4 + WHEN 'decimal' THEN (a.atttypmod % 65536) - 4 + WHEN 'money' THEN (a.atttypmod % 65536) - 4 + ELSE 0 + END AS scale, + a.attnotnull, + a.atttypmod, + a.atthasdef, + (SELECT substring(pg_get_expr(d.adbin, d.adrelid) for 128) + FROM pg_attrdef d + WHERE d.adrelid = a.attrelid + AND d.adnum = a.attnum + AND a.atthasdef + ) as default + FROM pg_attribute a, + pg_class c, + pg_type t + WHERE c.relname = ".$db->quote($table, 'text')." + AND a.atttypid = t.oid + AND c.oid = a.attrelid + AND NOT a.attisdropped + AND a.attnum > 0 + AND a.attname = ".$db->quote($field_name, 'text')." + ORDER BY a.attnum"; + $column = $db->queryRow($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($column)) { + return $column; + } + + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + $column = array_change_key_case($column, CASE_LOWER); + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (PEAR::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (!empty($column['attnotnull']) && $column['attnotnull'] == 't') { + $notnull = true; + } + $default = null; + if ($column['atthasdef'] === 't' + && !preg_match("/nextval\('([^']+)'/", $column['default']) + ) { + $pattern = '/(\'.*\')::[\w ]+$/i'; + $default = $column['default'];#substr($column['adsrc'], 1, -1); + if (is_null($default) && $notnull) { + $default = ''; + } elseif (!empty($default) && preg_match($pattern, $default)) { + //remove data type cast + $default = preg_replace ($pattern, '\\1', $default); + } + } + $autoincrement = false; + if (preg_match("/nextval\('([^']+)'/", $column['default'], $nextvals)) { + $autoincrement = true; + } + $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); + if (!is_null($length)) { + $definition[0]['length'] = $length; + } + if (!is_null($unsigned)) { + $definition[0]['unsigned'] = $unsigned; + } + if (!is_null($fixed)) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = 'SELECT relname, indkey FROM pg_index, pg_class'; + $query.= ' WHERE pg_class.oid = pg_index.indexrelid'; + $query.= " AND indisunique != 't' AND indisprimary != 't'"; + $query.= ' AND pg_class.relname = %s'; + $index_name_mdb2 = $db->getIndexName($index_name); + $row = $db->queryRow(sprintf($query, $db->quote($index_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $row = $db->queryRow(sprintf($query, $db->quote($index_name, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $row = array_change_key_case($row, CASE_LOWER); + + $db->loadModule('Manager', null, true); + $columns = $db->manager->listTableFields($table_name); + + $definition = array(); + + $index_column_numbers = explode(' ', $row['indkey']); + + $colpos = 1; + foreach ($index_column_numbers as $number) { + $definition['fields'][$columns[($number - 1)]] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT c.oid, + c.conname AS constraint_name, + CASE WHEN c.contype = 'c' THEN 1 ELSE 0 END AS \"check\", + CASE WHEN c.contype = 'f' THEN 1 ELSE 0 END AS \"foreign\", + CASE WHEN c.contype = 'p' THEN 1 ELSE 0 END AS \"primary\", + CASE WHEN c.contype = 'u' THEN 1 ELSE 0 END AS \"unique\", + CASE WHEN c.condeferrable = 'f' THEN 0 ELSE 1 END AS deferrable, + CASE WHEN c.condeferred = 'f' THEN 0 ELSE 1 END AS initiallydeferred, + --array_to_string(c.conkey, ' ') AS constraint_key, + t.relname AS table_name, + t2.relname AS references_table, + CASE confupdtype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS onupdate, + CASE confdeltype + WHEN 'a' THEN 'NO ACTION' + WHEN 'r' THEN 'RESTRICT' + WHEN 'c' THEN 'CASCADE' + WHEN 'n' THEN 'SET NULL' + WHEN 'd' THEN 'SET DEFAULT' + END AS ondelete, + CASE confmatchtype + WHEN 'u' THEN 'UNSPECIFIED' + WHEN 'f' THEN 'FULL' + WHEN 'p' THEN 'PARTIAL' + END AS match, + --array_to_string(c.confkey, ' ') AS fk_constraint_key, + consrc + FROM pg_constraint c + LEFT JOIN pg_class t ON c.conrelid = t.oid + LEFT JOIN pg_class t2 ON c.confrelid = t2.oid + WHERE c.conname = %s + AND t.relname = " . $db->quote($table, 'text'); + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row) || empty($row)) { + // fallback to the given $index_name, without transformation + $constraint_name_mdb2 = $constraint_name; + $row = $db->queryRow(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null, MDB2_FETCHMODE_ASSOC); + } + if (PEAR::isError($row)) { + return $row; + } + + if (empty($row)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $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'], + ); + + $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'); + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (PEAR::isError($fields)) { + return $fields; + } + $colpos = 1; + foreach ($fields as $field) { + $definition['fields'][$field] = array( + 'position' => $colpos++, + 'sorting' => 'ascending', + ); + } + + if ($definition['foreign']) { + $query = 'SELECT a.attname + FROM pg_constraint c + LEFT JOIN pg_class t ON c.confrelid = t.oid + LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(c.conkey) + WHERE c.conname = %s + AND t.relname = ' . $db->quote($definition['references']['table'], 'text'); + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + $foreign_fields = $db->queryCol(sprintf($query, $db->quote($constraint_name_mdb2, 'text')), null); + if (PEAR::isError($foreign_fields)) { + return $foreign_fields; + } + $colpos = 1; + foreach ($foreign_fields as $foreign_field) { + $definition['references']['fields'][$foreign_field] = array( + 'position' => $colpos++, + ); + } + } + + if ($definition['check']) { + $check_def = $db->queryOne("SELECT pg_get_constraintdef(" . $row['oid'] . ", 't')"); + // ... + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + * + * @TODO: add support for plsql functions and functions with args + */ + function getTriggerDefinition($trigger) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT trg.tgname AS trigger_name, + tbl.relname AS table_name, + CASE + WHEN p.proname IS NOT NULL THEN 'EXECUTE PROCEDURE ' || p.proname || '();' + ELSE '' + END AS trigger_body, + CASE trg.tgtype & cast(2 as int2) + WHEN 0 THEN 'AFTER' + ELSE 'BEFORE' + END AS trigger_type, + CASE trg.tgtype & cast(28 as int2) + WHEN 16 THEN 'UPDATE' + WHEN 8 THEN 'DELETE' + WHEN 4 THEN 'INSERT' + WHEN 20 THEN 'INSERT, UPDATE' + WHEN 28 THEN 'INSERT, UPDATE, DELETE' + WHEN 24 THEN 'UPDATE, DELETE' + WHEN 12 THEN 'INSERT, DELETE' + END AS trigger_event, + trg.tgenabled AS trigger_enabled, + obj_description(trg.oid, 'pg_trigger') AS trigger_comment + FROM pg_trigger trg, + pg_class tbl, + pg_proc p + WHERE trg.tgrelid = tbl.oid + AND trg.tgfoid = p.oid + AND trg.tgname = ". $db->quote($trigger, 'text'); + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + return $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if $result + * is a table name. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @pg_num_fields($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => function_exists('pg_field_table') ? @pg_field_table($resource, $i) : '', + 'name' => $case_func(@pg_field_name($resource, $i)), + 'type' => @pg_field_type($resource, $i), + 'length' => @pg_field_size($resource, $i), + 'flags' => '', + ); + $mdb2type_info = $db->datatype->mapNativeDatatype($res[$i]); + if (PEAR::isError($mdb2type_info)) { + return $mdb2type_info; + } + $res[$i]['mdb2type'] = $mdb2type_info[0][0]; + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Reverse/sqlite.php b/program/lib/MDB2/Driver/Reverse/sqlite.php new file mode 100644 index 000000000..2ba81c7c5 --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/sqlite.php @@ -0,0 +1,573 @@ + | +// | Lorenzo Alberton | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.78 2007/12/01 10:46:13 quipo Exp $ +// + +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 +{ + function _getTableColumns($sql) + { + $db =& $this->getDBInstance(); + if (PEAR::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_sql = split(',', $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|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( UNSIGNED)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?$/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[7]) && strlen($matches[7])) { + $columns[$j]['unsigned'] = true; + } + if (isset($matches[8]) && strlen($matches[8])) { + $columns[$j]['autoincrement'] = true; + } + if (isset($matches[10]) && strlen($matches[10])) { + $default = $matches[10]; + if (strlen($default) && $default[0]=="'") { + $default = str_replace("''", "'", substr($default, 1, strlen($default)-2)); + } + if ($default === 'NULL') { + $default = null; + } + $columns[$j]['default'] = $default; + } + if (isset($matches[11]) && strlen($matches[11])) { + $columns[$j]['notnull'] = ($matches[11] === ' 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 (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::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 (PEAR::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 (PEAR::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 (is_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 (!is_null($length)) { + $definition[0]['length'] = $length; + } + if (!is_null($unsigned)) { + $definition[0]['unsigned'] = $unsigned; + } + if (!is_null($fixed)) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + return $definition; + } + } + + 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 (PEAR::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 (PEAR::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 (PEAR::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 = split(',', $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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 = split(',', $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 = split(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $referenced_cols = split(',', $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 = split(',', $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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, + 'This DBMS can not obtain tableInfo from result sets', __FUNCTION__); + } +} + +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/mssql.php b/program/lib/MDB2/Driver/mssql.php new file mode 100644 index 000000000..f74acfea4 --- /dev/null +++ b/program/lib/MDB2/Driver/mssql.php @@ -0,0 +1,1010 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mssql.php,v 1.161 2007/11/18 17:52:00 quipo Exp $ +// +// {{{ 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['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['database_device'] = false; + $this->options['database_size'] = false; + } + + // }}} + // {{{ 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) { + $result = @mssql_query('select @@ERROR as ErrorCode', $this->connection); + if ($result) { + $native_code = @mssql_result($result, 0, 0); + @mssql_free_result($result); + } + } + $native_msg = @mssql_get_last_message(); + 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, + 1018 => MDB2_ERROR_SYNTAX, + 1035 => MDB2_ERROR_SYNTAX, + 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 (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + return MDB2_OK; + } + + $result =& $this->_doQuery('COMMIT TRANSACTION', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + $query = 'ROLLBACK TRANSACTION '.$savepoint; + return $this->_doQuery($query, true); + } + + $result =& $this->_doQuery('ROLLBACK TRANSACTION', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ 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); + } + + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $params = array( + $this->dsn['hostspec'] ? $this->dsn['hostspec'] : 'localhost', + $this->dsn['username'] ? $this->dsn['username'] : null, + $this->dsn['password'] ? $this->dsn['password'] : null, + ); + if ($this->dsn['port']) { + $params[0].= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':').$this->dsn['port']; + } + if (!$this->options['persistent']) { + if (isset($this->dsn['new_link']) + && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true) + ) { + $params[] = true; + } else { + $params[] = false; + } + } + + $connect_function = $this->options['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__); + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + return $result; + } + } + + if ((bool)ini_get('mssql.datetimeconvert')) { + @ini_set('mssql.datetimeconvert', '0'); + } + + if (empty($this->dsn['disable_iso_date'])) { + @mssql_query('SET DATEFORMAT ymd', $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; + } + + // }}} + // {{{ 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) { + @mssql_close($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) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@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 (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::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 (PEAR::isError($server_info)) { + return $server_info; + } + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native && !PEAR::isError($server_info)) { + if (preg_match('/([0-9]+)\.([0-9]+)\.([0-9]+)/', $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 (PEAR::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->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(); + if (PEAR::isError($result)) { + if ($ondemand && !$this->_checkSequence($sequence_name)) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + /** + * 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 (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + $server_info = $this->getServerVersion(); + if (is_array($server_info) && !is_null($server_info['major']) + && $server_info['major'] >= 8 + ) { + $query = "SELECT SCOPE_IDENTITY()"; + } else { + $query = "SELECT @@IDENTITY"; + } + + 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()) { + $null = null; + return $null; + } + if (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @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 ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @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 (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 @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 ($free === false) { + 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 ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mssql_num_rows($this->result); + 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->limit -1 + $this->offset; + 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 +{ + +} + +// }}} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/mysql.php b/program/lib/MDB2/Driver/mysql.php new file mode 100644 index 000000000..1e0453936 --- /dev/null +++ b/program/lib/MDB2/Driver/mysql.php @@ -0,0 +1,1535 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.195 2007/11/10 13:27:03 quipo Exp $ +// + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysql extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`'); + + var $sql_comments = array( + array('start' => '-- ', 'end' => "\n", 'escape' => false), + array('start' => '#', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + var $server_capabilities_checked = false; + + var $start_transaction = false; + + var $varchar_max_length = 255; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'mysql'; + $this->dbsyntax = 'mysql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = false; + $this->supported['savepoints'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['sub_selects'] = 'emulated'; + $this->supported['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['default_table_type'] = ''; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if ($this->connection) { + $native_code = @mysql_errno($this->connection); + $native_msg = @mysql_error($this->connection); + } else { + $native_code = @mysql_errno(); + $native_msg = @mysql_error(); + } + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1000 => MDB2_ERROR_INVALID, //hashchk + 1001 => MDB2_ERROR_INVALID, //isamchk + 1004 => MDB2_ERROR_CANNOT_CREATE, + 1005 => MDB2_ERROR_CANNOT_CREATE, + 1006 => MDB2_ERROR_CANNOT_CREATE, + 1007 => MDB2_ERROR_ALREADY_EXISTS, + 1008 => MDB2_ERROR_CANNOT_DROP, + 1009 => MDB2_ERROR_CANNOT_DROP, + 1010 => MDB2_ERROR_CANNOT_DROP, + 1011 => MDB2_ERROR_CANNOT_DELETE, + 1022 => MDB2_ERROR_ALREADY_EXISTS, + 1029 => MDB2_ERROR_NOT_FOUND, + 1032 => MDB2_ERROR_NOT_FOUND, + 1044 => MDB2_ERROR_ACCESS_VIOLATION, + 1045 => MDB2_ERROR_ACCESS_VIOLATION, + 1046 => MDB2_ERROR_NODBSELECTED, + 1048 => MDB2_ERROR_CONSTRAINT, + 1049 => MDB2_ERROR_NOSUCHDB, + 1050 => MDB2_ERROR_ALREADY_EXISTS, + 1051 => MDB2_ERROR_NOSUCHTABLE, + 1054 => MDB2_ERROR_NOSUCHFIELD, + 1060 => MDB2_ERROR_ALREADY_EXISTS, + 1061 => MDB2_ERROR_ALREADY_EXISTS, + 1062 => MDB2_ERROR_ALREADY_EXISTS, + 1064 => MDB2_ERROR_SYNTAX, + 1067 => MDB2_ERROR_INVALID, + 1072 => MDB2_ERROR_NOT_FOUND, + 1086 => MDB2_ERROR_ALREADY_EXISTS, + 1091 => MDB2_ERROR_NOT_FOUND, + 1100 => MDB2_ERROR_NOT_LOCKED, + 1109 => MDB2_ERROR_NOT_FOUND, + 1125 => MDB2_ERROR_ALREADY_EXISTS, + 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 1138 => MDB2_ERROR_INVALID, + 1142 => MDB2_ERROR_ACCESS_VIOLATION, + 1143 => MDB2_ERROR_ACCESS_VIOLATION, + 1146 => MDB2_ERROR_NOSUCHTABLE, + 1149 => MDB2_ERROR_SYNTAX, + 1169 => MDB2_ERROR_CONSTRAINT, + 1176 => MDB2_ERROR_NOT_FOUND, + 1177 => MDB2_ERROR_NOSUCHTABLE, + 1213 => MDB2_ERROR_DEADLOCK, + 1216 => MDB2_ERROR_CONSTRAINT, + 1217 => MDB2_ERROR_CONSTRAINT, + 1227 => MDB2_ERROR_ACCESS_VIOLATION, + 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, + ); + } + if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { + $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; + $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $text = @mysql_real_escape_string($text, $connection); + return $text; + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + $this->_getServerCapabilities(); + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 1'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $server_info = $this->getServerVersion(); + if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + return MDB2_OK; + } + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + $result =& $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ 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'] + && $this->connected_database_name == $this->database_name + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $params = array(); + if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') { + $params[0] = ':' . $this->dsn['socket']; + } else { + $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec'] + : 'localhost'; + if ($this->dsn['port']) { + $params[0].= ':' . $this->dsn['port']; + } + } + $params[] = $this->dsn['username'] ? $this->dsn['username'] : null; + $params[] = $this->dsn['password'] ? $this->dsn['password'] : null; + if (!$this->options['persistent']) { + if (isset($this->dsn['new_link']) + && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true) + ) { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($this->dsn['client_flags']) + ? $this->dsn['client_flags'] : null; + } + $connect_function = $this->options['persistent'] ? 'mysql_pconnect' : 'mysql_connect'; + + $connection = @call_user_func_array($connect_function, $params); + if (!$connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + $err, __FUNCTION__); + } else { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + } + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + return $result; + } + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = ''; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + if ($this->database_name) { + if ($this->database_name != $this->connected_database_name) { + if (!@mysql_select_db($this->database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$this->database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $this->database_name; + } + } + + $this->supported['transactions'] = $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['transactions'] = false; + $this->warnings[] = $this->options['default_table_type'] . + ' is not a supported default table type'; + break; + } + } + + $this->_getServerCapabilities(); + + 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 (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + $query = "SET NAMES '".mysql_real_escape_string($charset, $connection)."'"; + return $this->_doQuery($query, true, $connection); + } + + // }}} + // {{{ 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) { + @mysql_close($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) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mysql_select_db($database_name, $connection)) { + $err = $this->raiseError(null, null, null, + 'Could not select the database: '.$database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $database_name; + } + } + + $function = $this->options['result_buffering'] + ? 'mysql_query' : 'mysql_unbuffered_query'; + $result = @$function($query, $connection); + if (!$result) { + $err =& $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @mysql_affected_rows($connection); + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + + // LIMIT doesn't always come last in the query + // @see http://dev.mysql.com/doc/refman/5.0/en/select.html + $after = ''; + if (preg_match('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+INTO\s+(?:OUT|DUMP)FILE\s.*)$/ims', '', $query); + } elseif (preg_match('/(\s+FOR\s+UPDATE\s*)$/i', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+FOR\s+UPDATE\s*)$/im', '', $query); + } elseif (preg_match('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', $query, $matches)) { + $after = $matches[0]; + $query = preg_replace('/(\s+LOCK\s+IN\s+SHARE\s+MODE\s*)$/im', '', $query); + } + + if ($is_manip) { + return $query . " LIMIT $limit" . $after; + } else { + return $query . " LIMIT $offset, $limit" . $after; + } + } + return $query; + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param bool $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = @mysql_get_server_info($connection); + } + if (!$server_info) { + return $this->raiseError(null, null, null, + 'Could not get server information', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + if (isset($tmp[2]) && strpos($tmp[2], '-')) { + $tmp2 = explode('-', @$tmp[2], 2); + } else { + $tmp2[0] = isset($tmp[2]) ? $tmp[2] : null; + $tmp2[1] = null; + } + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => $tmp2[0], + 'extra' => $tmp2[1], + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ _getServerCapabilities() + + /** + * Fetch some information about the server capabilities + * (transactions, subselects, prepared statements, etc). + * + * @access private + */ + function _getServerCapabilities() + { + if (!$this->server_capabilities_checked) { + $this->server_capabilities_checked = true; + + //set defaults + $this->supported['sub_selects'] = 'emulated'; + $this->supported['prepared_statements'] = 'emulated'; + $this->start_transaction = false; + $this->varchar_max_length = 255; + + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.0', '<')) { + $this->supported['sub_selects'] = true; + $this->supported['prepared_statements'] = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.14', '<') + || !version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.1', '<') + ) { + $this->supported['savepoints'] = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.11', '<')) { + $this->start_transaction = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + $this->varchar_max_length = 65532; + } + } + } + } + + // }}} + // {{{ function _skipUserDefinedVariable($query, $position) + + /** + * Utility method, used by prepare() to avoid misinterpreting MySQL user + * defined variables (SELECT @x:=5) for placeholders. + * Check if the placeholder is a false positive, i.e. if it is an user defined + * variable instead. If so, skip it and advance the position, otherwise + * return the current position, which is valid + * + * @param string $query + * @param integer $position current string cursor position + * @return integer $new_position + * @access protected + */ + function _skipUserDefinedVariable($query, $position) + { + $found = strpos(strrev(substr($query, 0, $position)), '@'); + if ($found === false) { + return $position; + } + $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1; + $substring = substr($query, $pos, $position - $pos + 2); + if (preg_match('/^@\w+\s*:=$/', $substring)) { + return $position + 1; //found an user defined variable: skip it + } + return $position; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function &prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared'] + || $this->supported['prepared_statements'] !== true + ) { + $obj =& parent::prepare($query, $types, $result_types, $lobs); + return $obj; + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + //make sure this is not part of an user defined variable + $new_pos = $this->_skipUserDefinedVariable($query, $position); + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $positions[$p_position] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + static $prep_statement_counter = 1; + $statement_name = sprintf($this->options['statement_format'], $this->phptype, sha1(microtime() + mt_rand())) . $prep_statement_counter++; + $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); + $statement =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($statement)) { + return $statement; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the REPLACE query just updates its values instead of + * inserting a new row. + * + * 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 + * + * @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.= $name; + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result =& $this->_doQuery($query, true); + $this->popExpect(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 + return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 MySQL result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Result_mysql extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @mysql_fetch_assoc($this->result); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @mysql_fetch_row($this->result); + } + + if (!$row) { + if ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @mysql_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * @access public + */ + function numCols() + { + $cols = @mysql_num_fields($this->result); + if (is_null($cols)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + return $cols; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + if (is_resource($this->result) && $this->db->connection) { + $free = @mysql_free_result($this->result); + if ($free === false) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 MySQL buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_BufferedResult_mysql extends MDB2_Result_mysql +{ + // }}} + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { + if ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mysql_num_rows($this->result); + if (false === $rows) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return 0; + } + return $this->db->raiseError(null, null, null, + 'Could not get row count', __FUNCTION__); + } + return $rows; + } +} + +/** + * MDB2 MySQL statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_mysql extends MDB2_Statement_Common +{ + // {{{ _execute() + + /** + * Execute a prepared query statement helper method. + * + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function &_execute($result_class = true, $result_wrap_class = false) + { + if (is_null($this->statement)) { + $result =& parent::_execute($result_class, $result_wrap_class); + return $result; + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 'EXECUTE '.$this->statement; + if (!empty($this->positions)) { + $parameters = array(); + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $value = $this->values[$parameter]; + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if (is_resource($value) || $type == 'clob' || $type == 'blob' && $this->db->options['lob_allow_url_include']) { + if (!is_resource($value) && preg_match('/^(\w+:\/\/)(.*)$/', $value, $match)) { + if ($match[1] == 'file://') { + $value = $match[2]; + } + $value = @fopen($value, 'r'); + $close = true; + } + if (is_resource($value)) { + $data = ''; + while (!@feof($value)) { + $data.= @fread($value, $this->db->options['lob_buffer_length']); + } + if ($close) { + @fclose($value); + } + $value = $data; + } + } + $quoted = $this->db->quote($value, $type); + if (PEAR::isError($quoted)) { + return $quoted; + } + $param_query = 'SET @'.$parameter.' = '.$quoted; + $result = $this->db->_doQuery($param_query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + } + $query.= ' USING @'.implode(', @', array_values($this->positions)); + } + + $result = $this->db->_doQuery($query, $this->is_manip, $connection); + if (PEAR::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result =& $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (!is_null($this->statement)) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/mysqli.php b/program/lib/MDB2/Driver/mysqli.php new file mode 100644 index 000000000..5dc53d624 --- /dev/null +++ b/program/lib/MDB2/Driver/mysqli.php @@ -0,0 +1,1697 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysqli.php,v 1.176 2007/11/10 13:27:03 quipo Exp $ +// + +/** + * MDB2 MySQLi driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysqli extends MDB2_Driver_Common +{ + // {{{ properties + + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => '\\', 'escape_pattern' => '\\'); + + var $identifier_quoting = array('start' => '`', 'end' => '`', 'escape' => '`'); + + var $sql_comments = array( + array('start' => '-- ', 'end' => "\n", 'escape' => false), + array('start' => '#', 'end' => "\n", 'escape' => false), + array('start' => '/*', 'end' => '*/', 'escape' => false), + ); + + var $server_capabilities_checked = false; + + var $start_transaction = false; + + var $varchar_max_length = 255; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = '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['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['default_table_type'] = ''; + $this->options['multi_query'] = 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 = @mysqli_errno($this->connection); + $native_msg = @mysqli_error($this->connection); + } else { + $native_code = @mysqli_connect_errno(); + $native_msg = @mysqli_connect_error(); + } + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1000 => MDB2_ERROR_INVALID, //hashchk + 1001 => MDB2_ERROR_INVALID, //isamchk + 1004 => MDB2_ERROR_CANNOT_CREATE, + 1005 => MDB2_ERROR_CANNOT_CREATE, + 1006 => MDB2_ERROR_CANNOT_CREATE, + 1007 => MDB2_ERROR_ALREADY_EXISTS, + 1008 => MDB2_ERROR_CANNOT_DROP, + 1009 => MDB2_ERROR_CANNOT_DROP, + 1010 => MDB2_ERROR_CANNOT_DROP, + 1011 => MDB2_ERROR_CANNOT_DELETE, + 1022 => MDB2_ERROR_ALREADY_EXISTS, + 1029 => MDB2_ERROR_NOT_FOUND, + 1032 => MDB2_ERROR_NOT_FOUND, + 1044 => MDB2_ERROR_ACCESS_VIOLATION, + 1045 => MDB2_ERROR_ACCESS_VIOLATION, + 1046 => MDB2_ERROR_NODBSELECTED, + 1048 => MDB2_ERROR_CONSTRAINT, + 1049 => MDB2_ERROR_NOSUCHDB, + 1050 => MDB2_ERROR_ALREADY_EXISTS, + 1051 => MDB2_ERROR_NOSUCHTABLE, + 1054 => MDB2_ERROR_NOSUCHFIELD, + 1060 => MDB2_ERROR_ALREADY_EXISTS, + 1061 => MDB2_ERROR_ALREADY_EXISTS, + 1062 => MDB2_ERROR_ALREADY_EXISTS, + 1064 => MDB2_ERROR_SYNTAX, + 1067 => MDB2_ERROR_INVALID, + 1072 => MDB2_ERROR_NOT_FOUND, + 1086 => MDB2_ERROR_ALREADY_EXISTS, + 1091 => MDB2_ERROR_NOT_FOUND, + 1100 => MDB2_ERROR_NOT_LOCKED, + 1109 => MDB2_ERROR_NOT_FOUND, + 1125 => MDB2_ERROR_ALREADY_EXISTS, + 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 1138 => MDB2_ERROR_INVALID, + 1142 => MDB2_ERROR_ACCESS_VIOLATION, + 1143 => MDB2_ERROR_ACCESS_VIOLATION, + 1146 => MDB2_ERROR_NOSUCHTABLE, + 1149 => MDB2_ERROR_SYNTAX, + 1169 => MDB2_ERROR_CONSTRAINT, + 1176 => MDB2_ERROR_NOT_FOUND, + 1177 => MDB2_ERROR_NOSUCHTABLE, + 1213 => MDB2_ERROR_DEADLOCK, + 1216 => MDB2_ERROR_CONSTRAINT, + 1217 => MDB2_ERROR_CONSTRAINT, + 1227 => MDB2_ERROR_ACCESS_VIOLATION, + 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, + 2019 => MDB2_ERROR_INVALID, + ); + } + if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { + $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; + $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + function escape($text, $escape_wildcards = false) + { + if ($escape_wildcards) { + $text = $this->escapePattern($text); + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $text = @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 (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + $query = $this->start_transaction ? 'START TRANSACTION' : 'SET AUTOCOMMIT = 1'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $server_info = $this->getServerVersion(); + if (version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + return MDB2_OK; + } + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + + $result =& $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + if (!$this->supports('savepoints')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + if (!$this->start_transaction) { + $query = 'SET AUTOCOMMIT = 0'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'transactions are not supported', __FUNCTION__); + } + switch ($isolation) { + case 'READ UNCOMMITTED': + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "SET SESSION TRANSACTION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ 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; + } + + if (!PEAR::loadExtension($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'], + $this->dsn['username'], + $this->dsn['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 (PEAR::isError($result)) { + return $result; + } + } + + $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->supported['transactions'] = $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['transactions'] = false; + $this->warnings[] = $this->options['default_table_type'] . + ' is not a supported default table type'; + break; + } + } + + $this->_getServerCapabilities(); + + 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 (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + $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)."'"; + 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; + } + + // }}} + // {{{ 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) { + @mysqli_close($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) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@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) { + $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 (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::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 (PEAR::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->start_transaction = false; + $this->varchar_max_length = 255; + + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.0', '<')) { + $this->supported['sub_selects'] = true; + $this->supported['prepared_statements'] = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.14', '<') + || !version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.1.1', '<') + ) { + $this->supported['savepoints'] = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '4.0.11', '<')) { + $this->start_transaction = true; + } + + if (!version_compare($server_info['major'].'.'.$server_info['minor'].'.'.$server_info['patch'], '5.0.3', '<')) { + $this->varchar_max_length = 65532; + } + } + } + } + + // }}} + // {{{ function _skipUserDefinedVariable($query, $position) + + /** + * Utility method, used by prepare() to avoid misinterpreting MySQL user + * defined variables (SELECT @x:=5) for placeholders. + * Check if the placeholder is a false positive, i.e. if it is an user defined + * variable instead. If so, skip it and advance the position, otherwise + * return the current position, which is valid + * + * @param string $query + * @param integer $position current string cursor position + * @return integer $new_position + * @access protected + */ + function _skipUserDefinedVariable($query, $position) + { + $found = strpos(strrev(substr($query, 0, $position)), '@'); + if ($found === false) { + return $position; + } + $pos = strlen($query) - strlen(substr($query, $position)) - $found - 1; + $substring = substr($query, $pos, $position - $pos + 2); + if (preg_match('/^@\w+\s*:=$/', $substring)) { + return $position + 1; //found an user defined variable: skip it + } + return $position; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function &prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared'] + || $this->supported['prepared_statements'] !== true + ) { + $obj =& parent::prepare($query, $types, $result_types, $lobs); + return $obj; + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + //make sure this is not part of an user defined variable + $new_pos = $this->_skipUserDefinedVariable($query, $position); + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err =& $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + $positions[$p_position] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + if (!$is_manip) { + static $prep_statement_counter = 1; + $statement_name = sprintf($this->options['statement_format'], $this->phptype, sha1(microtime() + mt_rand())) . $prep_statement_counter++; + $query = "PREPARE $statement_name FROM ".$this->quote($query, 'text'); + + $statement =& $this->_doQuery($query, true, $connection); + if (PEAR::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 REPLACE query just updates its values instead of + * inserting a new row. + * + * 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 + * + * @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.= $name; + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result =& $this->_doQuery($query, true); + $this->popExpect(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + // not using mysql_insert_id() due to http://pear.php.net/bugs/bug.php?id=8051 + return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 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 (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @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 ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_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 (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() + { + $connection = $this->db->getConnection(); + if (PEAR::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() + { + if (is_object($this->result) && $this->db->connection) { + $free = @mysqli_free_result($this->result); + if ($free === false) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + } +} + +/** + * MDB2 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 ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mysqli_num_rows($this->result); + 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__); + } + 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 (PEAR::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 a result handle or MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function &_execute($result_class = true, $result_wrap_class = false) + { + if (is_null($this->statement)) { + $result =& parent::_execute($result_class, $result_wrap_class); + return $result; + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + if (!is_object($this->statement)) { + $query = 'EXECUTE '.$this->statement; + } + if (!empty($this->positions)) { + $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 (PEAR::isError($quoted)) { + return $quoted; + } + $param_query = 'SET @'.$parameter.' = '.$quoted; + $result = $this->db->_doQuery($param_query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + } else { + if (is_resource($value) || $type == 'clob' || $type == 'blob') { + $parameters[] = null; + $parameters[1].= 'b'; + $lobs[$i] = $parameter; + } else { + $quoted = $this->db->quote($value, $type, false); + if (PEAR::isError($quoted)) { + return $quoted; + } + $parameters[] = $quoted; + $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 ($result === false) { + $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 (PEAR::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result =& $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + } 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 (is_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 (!is_null($this->statement)) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/pgsql.php b/program/lib/MDB2/Driver/pgsql.php new file mode 100644 index 000000000..49ab1218a --- /dev/null +++ b/program/lib/MDB2/Driver/pgsql.php @@ -0,0 +1,1470 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: pgsql.php,v 1.186 2007/11/10 13:27:03 quipo Exp $ + +/** + * 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['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['multi_query'] = false; + $this->options['disable_smart_seqname'] = false; + } + + // }}} + // {{{ 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; + } + } + + 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, + '/index .* does not exist/' + => MDB2_ERROR_NOT_FOUND, + '/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 (PEAR::isError($connection)) { + return $connection; + } + if (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 (!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 = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $result =& $this->_doQuery('BEGIN', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + $query = 'RELEASE SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $result =& $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + $query = 'ROLLBACK'; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + 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() + + /** + * Does the grunt work of connecting to the database + * + * @return mixed connection resource on success, MDB2 Error Object on failure + * @access protected + **/ + function _doConnect($database_name, $persistent = false) + { + 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 ($this->dsn['username']) { + $params[0].= ' user=\'' . addslashes($this->dsn['username']) . '\''; + } + if ($this->dsn['password']) { + $params[0].= ' password=\'' . addslashes($this->dsn['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 (!empty($this->dsn['new_link']) + && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true)) + { + 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 (PEAR::isError($result)) { + return $result; + } + } + + 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 (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + if ($this->database_name) { + $connection = $this->_doConnect($this->database_name, $this->options['persistent']); + if (PEAR::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 (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $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; + } + + // }}} + // {{{ 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) { + @pg_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) + { + $connection = $this->_doConnect('template1', false); + if (PEAR::isError($connection)) { + $err =& $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'Cannot connect to template1', __FUNCTION__); + return $err; + } + + $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); + @pg_close($connection); + if (PEAR::isError($result)) { + return $result; + } + + if ($is_manip) { + $affected_rows = $this->_affectedRows($connection, $result); + return $affected_rows; + } + $result =& $this->_wrapResult($result, $types, true, false, $limit, $offset); + return $result; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $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 (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::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 (PEAR::isError($server_info)) { + return $server_info; + } + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native && !PEAR::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']) { + $obj =& parent::prepare($query, $types, $result_types, $lobs); + return $obj; + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::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 (is_null($placeholder_type)) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (is_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))) { + $next_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 (PEAR::isError($connection)) { + return $connection; + } + static $prep_statement_counter = 1; + $statement_name = sprintf($this->options['statement_format'], $this->phptype, sha1(microtime() + mt_rand())) . $prep_statement_counter++; + $statement_name = strtolower($statement_name); + if ($pgtypes === false) { + $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 (PEAR::isError($statement)) { + return $statement; + } + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement_name, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ 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 (PEAR::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 (!PEAR::isError($seqname) && !empty($seqname) && is_string($seqname)) { + return $seqname; + } + } + + return sprintf($this->options['seqname_format'], + preg_replace('/[^\w\$.]/i', '_', $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->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->queryOne($query, 'integer'); + $this->popExpect(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence 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->getSequenceName($seq); + 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 (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @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 ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @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 (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() + { + $connection = $this->db->getConnection(); + if (PEAR::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 ($free === false) { + 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 ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @pg_num_rows($this->result); + 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__); + } + 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 a result handle or MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function &_execute($result_class = true, $result_wrap_class = false) + { + if (is_null($this->statement)) { + $result =& parent::_execute($result_class, $result_wrap_class); + return $result; + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 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 (PEAR::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 (PEAR::isError($result)) { + return $result; + } + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result =& $this->db->_wrapResult($result, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if (!is_null($this->statement)) { + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $query = 'DEALLOCATE PREPARE '.$this->statement; + $result = $this->db->_doQuery($query, true, $connection); + } + + parent::free(); + return $result; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/sqlite.php b/program/lib/MDB2/Driver/sqlite.php new file mode 100644 index 000000000..d61255347 --- /dev/null +++ b/program/lib/MDB2/Driver/sqlite.php @@ -0,0 +1,1078 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.152 2007/11/27 07:44:41 quipo Exp $ +// + +/** + * 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['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['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'] = ''; + } + + // }}} + // {{{ 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 (is_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, + '/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 (!is_null($savepoint)) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } elseif ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name']; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (!is_null($savepoint)) { + 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 (PEAR::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 (!is_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 (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + 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 (!PEAR::loadExtension($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)) { + 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 (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + return $result; + } + } + + $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; + } + + // }}} + // {{{ 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); + } + } + return parent::disconnect($force); + } + + // }}} + // {{{ getConnection() + + /** + * Returns a native connection + * + * @return mixed a valid MDB2 connection object, + * or a MDB2 error object on error + * @access public + */ + function getConnection() + { + $connection = parent::getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $fix_assoc_fields_names = $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES; + if ($fix_assoc_fields_names !== $this->fix_assoc_fields_names) { + @sqlite_query("PRAGMA short_column_names = $fix_assoc_fields_names;", $connection); + $this->fix_assoc_fields_names = $fix_assoc_fields_names; + } + + return $connection; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function &_doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $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) { + $err =& $this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (is_null($connection)) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @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 REPLACE query just updates its values instead of + * inserting a new row. + * + * 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.= $name; + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result =& $this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->options['seqcol_name']; + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result =& $this->_doQuery($query, true); + $this->popExpect(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result =& $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + $connection = $this->getConnection(); + if (PEAR::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 (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @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 ($this->result === false) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @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 (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; + } +} + +/** + * 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 ($this->result === false) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } elseif (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'tried to seek to an invalid row number ('.$rownum.')', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @sqlite_num_rows($this->result); + 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__); + } + return $rows; + } +} + +/** + * MDB2 SQLite statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Statement_sqlite extends MDB2_Statement_Common +{ + +} + +?> \ No newline at end of file diff --git a/program/lib/MDB2/Extended.php b/program/lib/MDB2/Extended.php new file mode 100644 index 000000000..7772c58b6 --- /dev/null +++ b/program/lib/MDB2/Extended.php @@ -0,0 +1,721 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Extended.php,v 1.60 2007/11/28 19:49:34 quipo Exp $ + +/** + * @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 (PEAR::isError($query)) { + return $query; + } + $db =& $this->getDBInstance(); + if (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 ($where !== false && !is_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 (PEAR::isError($db)) { + return $db; + } + + $result = $db->setLimit($limit, $offset); + if (PEAR::isError($result)) { + return $result; + } + $result =& $db->query($query, $types, $result_class, $result_wrap_class); + return $result; + } + + // }}} + // {{{ 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 (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->exec($query); + } + + $stmt = $db->prepare($query, $param_types, MDB2_PREPARE_MANIP); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $result = $stmt->execute($params); + if (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + settype($params, 'array'); + if (empty($params)) { + return $db->queryRow($query, $types, $fetchmode); + } + + $stmt = $db->prepare($query, $param_types, $types); + if (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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 (PEAR::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) + { + for ($i = 0, $j = count($params); $i < $j; $i++) { + $result = $stmt->execute($params[$i]); + if (PEAR::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 (PEAR::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + $seq = $table.(empty($field) ? '' : '_'.$field); + $id = $db->nextID($seq, $ondemand); + if (!$quote || PEAR::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 (PEAR::isError($db)) { + return $db; + } + + if ($db->supports('auto_increment') !== true) { + return $id; + } + return $db->lastInsertID($table, $field); + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Iterator.php b/program/lib/MDB2/Iterator.php new file mode 100644 index 000000000..ca5e7b69c --- /dev/null +++ b/program/lib/MDB2/Iterator.php @@ -0,0 +1,259 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Iterator.php,v 1.22 2006/05/06 14:03:41 lsmith Exp $ + +/** + * PHP5 Iterator + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Iterator implements Iterator +{ + protected $fetchmode; + protected $result; + protected $row; + + // {{{ constructor + + /** + * Constructor + */ + public function __construct($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 (is_null($this->row)) { + $row = $this->result->fetchRow($this->fetchmode); + if (PEAR::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); + } + // }}} +} + +?> \ No newline at end of file diff --git a/program/lib/MDB2/LOB.php b/program/lib/MDB2/LOB.php new file mode 100644 index 000000000..69db267d6 --- /dev/null +++ b/program/lib/MDB2/LOB.php @@ -0,0 +1,264 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: LOB.php,v 1.34 2006/10/25 11:52:21 lsmith Exp $ + +/** + * @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; +} + +?> -- cgit v1.2.3