From edf52c5e1a23ca093123771ea763d2cf72f2140d Mon Sep 17 00:00:00 2001 From: Rodolphe Quiedeville Date: Wed, 30 Jun 2004 16:28:14 +0000 Subject: [PATCH] Nouveaux fichiers --- htdocs/includes/pear/Auth/Auth.php | 2 +- htdocs/includes/pear/Auth/Container.php | 152 +++ htdocs/includes/pear/Auth/Container/DB.php | 394 ++++++ htdocs/includes/pear/DB.php | 948 +++++++++++++ htdocs/includes/pear/DB/common.php | 1398 ++++++++++++++++++++ htdocs/includes/pear/DB/mysql.php | 843 ++++++++++++ htdocs/includes/pear/DB/pgsql.php | 786 +++++++++++ 7 files changed, 4522 insertions(+), 1 deletion(-) create mode 100644 htdocs/includes/pear/Auth/Container.php create mode 100644 htdocs/includes/pear/Auth/Container/DB.php create mode 100644 htdocs/includes/pear/DB.php create mode 100644 htdocs/includes/pear/DB/common.php create mode 100644 htdocs/includes/pear/DB/mysql.php create mode 100644 htdocs/includes/pear/DB/pgsql.php diff --git a/htdocs/includes/pear/Auth/Auth.php b/htdocs/includes/pear/Auth/Auth.php index ed129d70a17..4f9ff737542 100644 --- a/htdocs/includes/pear/Auth/Auth.php +++ b/htdocs/includes/pear/Auth/Auth.php @@ -186,7 +186,7 @@ class DOLIAuth { $storage_path = "Auth/Container/" . $driver . ".php"; $storage_class = "Auth_Container_" . $driver; - require_once $storage_path; + require_once DOL_DOCUMENT_ROOT."/includes/pear/".$storage_path; return new $storage_class($options); } diff --git a/htdocs/includes/pear/Auth/Container.php b/htdocs/includes/pear/Auth/Container.php new file mode 100644 index 00000000000..438a1989022 --- /dev/null +++ b/htdocs/includes/pear/Auth/Container.php @@ -0,0 +1,152 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +define("AUTH_METHOD_NOT_SUPPORTED", -4); + +/** + * Storage class for fetching login data + * + * @author Martin Jansen + * @package Auth + */ +class Auth_Container +{ + + /** + * User that is currently selected from the storage container. + * + * @access public + */ + var $activeUser = ""; + + // {{{ Constructor + + /** + * Constructor + * + * Has to be overwritten by each storage class + * + * @access public + */ + function Auth_Container() + { + } + + // }}} + // {{{ fetchData() + + /** + * Fetch data from storage container + * + * Has to be overwritten by each storage class + * + * @access public + */ + function fetchData() + { + } + + // }}} + // {{{ verifyPassword() + + /** + * Crypt and verfiy the entered password + * + * @param string Entered password + * @param string Password from the data container (usually this password + * is already encrypted. + * @param string Type of algorithm with which the password from + * the container has been crypted. (md5, crypt etc.) + * Defaults to "md5". + * @return bool True, if the passwords match + */ + function verifyPassword($password1, $password2, $cryptType = "md5") + { + switch ($cryptType) { + case "crypt" : + return (($password2 == "**" . $password1) || + (crypt($password1, $password2) == $password2) + ); + break; + + case "none" : + return ($password1 == $password2); + break; + + case "md5" : + return (md5($password1) == $password2); + break; + + default : + if (function_exists($cryptType)) { + return ($cryptType($password1) == $password2); + } else { + return false; + } + break; + } + } + + // }}} + // {{{ listUsers() + + /** + * List all users that are available from the storage container + */ + function listUsers() + { + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + // {{{ addUser() + + /** + * Add a new user to the storage container + * + * @param string Username + * @param string Password + * @param array Additional information + * + * @return boolean + */ + function addUser($username, $password, $additional=null) + { + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @param string Username + */ + function removeUser($username) + { + return AUTH_METHOD_NOT_SUPPORTED; + } + + // }}} + +} +?> diff --git a/htdocs/includes/pear/Auth/Container/DB.php b/htdocs/includes/pear/Auth/Container/DB.php new file mode 100644 index 00000000000..81f3f5ea4fc --- /dev/null +++ b/htdocs/includes/pear/Auth/Container/DB.php @@ -0,0 +1,394 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +//require_once 'Auth/Container.php'; +require_once DOL_DOCUMENT_ROOT."/includes/pear/Auth/Container.php"; +//require_once 'DB.php'; +require_once DOL_DOCUMENT_ROOT."/includes/pear/DB.php"; + +/** + * Storage driver for fetching login data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR DB abstraction layer to fetch login data. + * + * @author Martin Jansen + * @package Auth + * @version $Revision$ + */ +class Auth_Container_DB extends Auth_Container +{ + + /** + * Additional options for the storage container + * @var array + */ + var $options = array(); + + /** + * DB object + * @var object + */ + var $db = null; + var $dsn = ''; + + /** + * User that is currently selected from the DB. + * @var string + */ + var $activeUser = ''; + + // {{{ Constructor + + /** + * Constructor of the container class + * + * Initate connection to the database via PEAR::DB + * + * @param string Connection data or DB object + * @return object Returns an error object if something went wrong + */ + function Auth_Container_DB($dsn) + { + $this->_setDefaults(); + + if (is_array($dsn)) { + $this->_parseOptions($dsn); + + if (empty($this->options['dsn'])) { + PEAR::raiseError('No connection parameters specified!'); + } + } else { + $this->options['dsn'] = $dsn; + } + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @access private + * @param string DSN string + * @return mixed Object on error, otherwise bool + */ + function _connect($dsn) + { + if (is_string($dsn) || is_array($dsn)) { + $this->db = DB::Connect($dsn); + } elseif (get_parent_class($dsn) == "db_common") { + $this->db = $dsn; + } elseif (DB::isError($dsn)) { + return PEAR::raiseError($dsn->getMessage(), $dsn->getCode()); + } else { + return PEAR::raiseError('The given dsn was not valid in file ' . __FILE__ . ' at line ' . __LINE__, + 41, + PEAR_ERROR_RETURN, + null, + null + ); + } + + if (DB::isError($this->db) || PEAR::isError($this->db)) { + return PEAR::raiseError($this->db->getMessage(), $this->db->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ _prepare() + + /** + * Prepare database connection + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * + * @access private + * @return mixed True or a DB error object. + */ + function _prepare() + { + if (!DB::isConnection($this->db)) { + $res = $this->_connect($this->options['dsn']); + if(DB::isError($res) || PEAR::isError($res)){ + return $res; + } + } + return true; + } + + // }}} + // {{{ query() + + /** + * Prepare query to the database + * + * This function checks if we have already opened a connection to + * the database. If that's not the case, a new connection is opened. + * After that the query is passed to the database. + * + * @access public + * @param string Query string + * @return mixed a DB_result object or DB_OK on success, a DB + * or PEAR error on failure + */ + function query($query) + { + $err = $this->_prepare(); + if ($err !== true) { + return $err; + } + return $this->db->query($query); + } + + // }}} + // {{{ _setDefaults() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaults() + { + $this->options['table'] = 'auth'; + $this->options['usernamecol'] = 'username'; + $this->options['passwordcol'] = 'password'; + $this->options['dsn'] = ''; + $this->options['db_fields'] = ''; + $this->options['cryptType'] = 'md5'; + } + + // }}} + // {{{ _parseOptions() + + /** + * Parse options passed to the container class + * + * @access private + * @param array + */ + function _parseOptions($array) + { + foreach ($array as $key => $value) { + if (isset($this->options[$key])) { + $this->options[$key] = $value; + } + } + + /* Include additional fields if they exist */ + if(!empty($this->options['db_fields'])){ + if(is_array($this->options['db_fields'])){ + $this->options['db_fields'] = join($this->options['db_fields'], ', '); + } + $this->options['db_fields'] = ', '.$this->options['db_fields']; + } + } + + // }}} + // {{{ fetchData() + + /** + * Get user information from database + * + * This function uses the given username to fetch + * the corresponding login data from the database + * table. If an account that matches the passed username + * and password is found, the function returns true. + * Otherwise it returns false. + * + * @param string Username + * @param string Password + * @return mixed Error object or boolean + */ + function fetchData($username, $password) + { + // Prepare for a database query + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + // Find if db_fileds contains a *, i so assume all col are selected + if(strstr($this->options['db_fields'], '*')){ + $sql_from = "*"; + } + else{ + $sql_from = $this->options['usernamecol'] . ", ".$this->options['passwordcol'].$this->options['db_fields']; + } + + $query = "SELECT ! FROM ! WHERE ! = ?"; + $query_params = array( + $sql_from, + $this->options['table'], + $this->options['usernamecol'], + $username + ); + $res = $this->db->getRow($query, $query_params, DB_FETCHMODE_ASSOC); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } + if (!is_array($res)) { + $this->activeUser = ''; + return false; + } + if ($this->verifyPassword(trim($password), + trim($res[$this->options['passwordcol']]), + $this->options['cryptType'])) { + // Store additional field values in the session + foreach ($res as $key => $value) { + if ($key == $this->options['passwordcol'] || + $key == $this->options['usernamecol']) { + continue; + } + Auth::setAuthData($key, $value); + } + + return true; + } + + $this->activeUser = $res[$this->options['usernamecol']]; + return false; + } + + // }}} + // {{{ listUsers() + + function listUsers() + { + $err = $this->_prepare(); + if ($err !== true) { + return PEAR::raiseError($err->getMessage(), $err->getCode()); + } + + $retVal = array(); + + // Find if db_fileds contains a *, i so assume all col are selected + if(strstr($this->options['db_fields'], '*') || empty($this->options['db_fields'])){ + $sql_from = "*"; + } + else{ + $sql_from = $this->options['usernamecol'] . ", ".$this->options['passwordcol'].$this->options['db_fields']; + } + + $query = sprintf("SELECT %s FROM %s", + $sql_from, + $this->options['table'] + ); + $res = $this->db->getAll($query, null, DB_FETCHMODE_ASSOC); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + foreach ($res as $user) { + $user['username'] = $user[$this->options['usernamecol']]; + $retVal[] = $user; + } + } + return $retVal; + } + + // }}} + // {{{ addUser() + + /** + * Add user to the storage container + * + * @access public + * @param string Username + * @param string Password + * @param mixed Additional information that are stored in the DB + * + * @return mixed True on success, otherwise error object + */ + function addUser($username, $password, $additional = "") + { + if (function_exists($this->options['cryptType'])) { + $cryptFunction = $this->options['cryptType']; + } else { + $cryptFunction = 'md5'; + } + + $additional_key = ''; + $additional_value = ''; + + if (is_array($additional)) { + foreach ($additional as $key => $value) { + $additional_key .= ', ' . $key; + $additional_value .= ", '" . $value . "'"; + } + } + + $query = sprintf("INSERT INTO %s (%s, %s%s) VALUES ('%s', '%s'%s)", + $this->options['table'], + $this->options['usernamecol'], + $this->options['passwordcol'], + $additional_key, + $username, + $cryptFunction($password), + $additional_value + ); + + $res = $this->query($query); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + return true; + } + } + + // }}} + // {{{ removeUser() + + /** + * Remove user from the storage container + * + * @access public + * @param string Username + * + * @return mixed True on success, otherwise error object + */ + function removeUser($username) + { + $query = sprintf("DELETE FROM %s WHERE %s = '%s'", + $this->options['table'], + $this->options['usernamecol'], + $username + ); + + $res = $this->query($query); + + if (DB::isError($res)) { + return PEAR::raiseError($res->getMessage(), $res->getCode()); + } else { + return true; + } + } + + // }}} +} +?> diff --git a/htdocs/includes/pear/DB.php b/htdocs/includes/pear/DB.php new file mode 100644 index 00000000000..164b8aa3be8 --- /dev/null +++ b/htdocs/includes/pear/DB.php @@ -0,0 +1,948 @@ + | +// | Tomas V.V.Cox | +// +----------------------------------------------------------------------+ +// +// $Id$ +// +// Database independent query interface. +// + +//require_once "PEAR.php"; +require_once DOL_DOCUMENT_ROOT."/includes/pear/PEAR.php"; +// {{{ constants +// {{{ error codes +/* + * The method mapErrorCode in each DB_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 DB::errorMessage(). + */ + +define("DB_OK", 1); +define("DB_ERROR", -1); +define("DB_ERROR_SYNTAX", -2); +define("DB_ERROR_CONSTRAINT", -3); +define("DB_ERROR_NOT_FOUND", -4); +define("DB_ERROR_ALREADY_EXISTS", -5); +define("DB_ERROR_UNSUPPORTED", -6); +define("DB_ERROR_MISMATCH", -7); +define("DB_ERROR_INVALID", -8); +define("DB_ERROR_NOT_CAPABLE", -9); +define("DB_ERROR_TRUNCATED", -10); +define("DB_ERROR_INVALID_NUMBER", -11); +define("DB_ERROR_INVALID_DATE", -12); +define("DB_ERROR_DIVZERO", -13); +define("DB_ERROR_NODBSELECTED", -14); +define("DB_ERROR_CANNOT_CREATE", -15); +define("DB_ERROR_CANNOT_DELETE", -16); +define("DB_ERROR_CANNOT_DROP", -17); +define("DB_ERROR_NOSUCHTABLE", -18); +define("DB_ERROR_NOSUCHFIELD", -19); +define("DB_ERROR_NEED_MORE_DATA", -20); +define("DB_ERROR_NOT_LOCKED", -21); +define("DB_ERROR_VALUE_COUNT_ON_ROW", -22); +define("DB_ERROR_INVALID_DSN", -23); +define("DB_ERROR_CONNECT_FAILED", -24); +define("DB_ERROR_EXTENSION_NOT_FOUND",-25); +define("DB_ERROR_ACCESS_VIOLATION", -26); +define("DB_ERROR_NOSUCHDB", -27); + +// }}} +// {{{ warning codes +/* + * Warnings are not detected as errors by DB::isError(), and are not + * fatal. You can detect whether an error is in fact a warning with + * DB::isWarning(). + * + * @deprecated + */ + +define('DB_WARNING', -1000); +define('DB_WARNING_READ_ONLY', -1001); + +// }}} +// {{{ prepared statement-related +/* + * These constants are used when storing information about prepared + * statements (using the "prepare" method in DB_dbtype). + * + * The prepare/execute model in DB is mostly borrowed from the ODBC + * extension, in a query the "?" character means a scalar parameter. + * There are two extensions though, a "&" character means an opaque + * parameter. An opaque parameter is simply a file name, the real + * data are in that file (useful for putting uploaded files into your + * database and such). The "!" char means a parameter that must be + * left as it is. + * They modify the quote behavoir: + * DB_PARAM_SCALAR (?) => 'original string quoted' + * DB_PARAM_OPAQUE (&) => 'string from file quoted' + * DB_PARAM_MISC (!) => original string + */ + +define('DB_PARAM_SCALAR', 1); +define('DB_PARAM_OPAQUE', 2); +define('DB_PARAM_MISC', 3); + +// }}} +// {{{ binary data-related +/* + * These constants define different ways of returning binary data + * from queries. Again, this model has been borrowed from the ODBC + * extension. + * + * DB_BINMODE_PASSTHRU sends the data directly through to the browser + * when data is fetched from the database. + * DB_BINMODE_RETURN lets you return data as usual. + * DB_BINMODE_CONVERT returns data as well, only it is converted to + * hex format, for example the string "123" would become "313233". + */ + +define('DB_BINMODE_PASSTHRU', 1); +define('DB_BINMODE_RETURN', 2); +define('DB_BINMODE_CONVERT', 3); + +// }}} +// {{{ fetch modes +/** + * This is a special constant that tells DB the user hasn't specified + * any particular get mode, so the default should be used. + */ + +define('DB_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ + +define('DB_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ + +define('DB_FETCHMODE_ASSOC', 2); + +/** + * Column data as object properties + */ + +define('DB_FETCHMODE_OBJECT', 3); + +/** + * For multi-dimensional results: normally the first level of arrays + * is the row number, and the second level indexed by column number or name. + * DB_FETCHMODE_FLIPPED switches this order, so the first level of arrays + * is the column name, and the second level the row number. + */ + +define('DB_FETCHMODE_FLIPPED', 4); + +/* for compatibility */ + +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); +define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED); + +// }}} +// {{{ tableInfo() && autoPrepare()-related +/** + * 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 DB_TABLEINFO_FULL accordingly + */ + +define('DB_TABLEINFO_ORDER', 1); +define('DB_TABLEINFO_ORDERTABLE', 2); +define('DB_TABLEINFO_FULL', 3); + +/* + * Used by autoPrepare() + */ +define('DB_AUTOQUERY_INSERT', 1); +define('DB_AUTOQUERY_UPDATE', 2); + +// }}} +// }}} + +// {{{ class DB +/** + * The main "DB" class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + * The object model of DB is as follows (indentation means inheritance): + * + * DB The main DB class. This is simply a utility class + * with some "static" methods for creating DB objects as + * well as common utility functions for other DB classes. + * + * DB_common The base for each DB implementation. Provides default + * | implementations (in OO lingo virtual methods) for + * | the actual DB implementations as well as a bunch of + * | query utility functions. + * | + * +-DB_mysql The DB implementation for MySQL. Inherits DB_common. + * When calling DB::factory or DB::connect for MySQL + * connections, the object returned is an instance of this + * class. + * + * @package DB + * @author Stig Bakken + * @since PHP 4.0 + */ + +class DB +{ + // {{{ &factory() + /** + * Create a new DB connection object for the specified database + * type + * + * @param string $type database type, for example "mysql" + * + * @return mixed a newly created DB object, or a DB error code on + * error + * + * access public + */ + + function &factory($type) + { + @include_once(DOL_DOCUMENT_ROOT."/includes/pear/DB/${type}.php"); + + $classname = "DB_${type}"; + + if (!class_exists($classname)) { + return PEAR::raiseError(null, DB_ERROR_NOT_FOUND, + null, null, null, 'DB_Error', true); + } + + @$obj =& new $classname; + + return $obj; + } + + // }}} + // {{{ &connect() + /** + * Create a new DB connection object and connect to the specified + * database + * + * @param mixed $dsn "data source name", see the DB::parseDSN + * method for a description of the dsn format. Can also be + * specified as an array of the format returned by DB::parseDSN. + * + * @param mixed $options An associative array of option names and + * their values. For backwards compatibility, this parameter may + * also be a boolean that tells whether the connection should be + * persistent. See DB_common::setOption for more information on + * connection options. + * + * @return mixed a newly created DB connection object, or a DB + * error object on error + * + * @see DB::parseDSN + * @see DB::isError + * @see DB_common::setOption + */ + function &connect($dsn, $options = false) + { + if (is_array($dsn)) { + $dsninfo = $dsn; + } else { + $dsninfo = DB::parseDSN($dsn); + } + $type = $dsninfo["phptype"]; + + if (is_array($options) && isset($options["debug"]) && + $options["debug"] >= 2) { + // expose php errors with sufficient debug level + include_once DOL_DOCUMENT_ROOT."/includes/pear/DB/${type}.php"; + } else { + @include_once DOL_DOCUMENT_ROOT."/includes/pear/DB/${type}.php"; + } + + $classname = "DB_${type}"; + if (!class_exists($classname)) { + return PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null, + "Unable to include the DB/{$type}.php file for `$dsn'", + 'DB_Error', true); + } + + @$obj =& new $classname; + + if (is_array($options)) { + foreach ($options as $option => $value) { + $test = $obj->setOption($option, $value); + if (DB::isError($test)) { + return $test; + } + } + } else { + $obj->setOption('persistent', $options); + } + $err = $obj->connect($dsninfo, $obj->getOption('persistent')); + if (DB::isError($err)) { + $err->addUserInfo($dsn); + return $err; + } + + return $obj; + } + + // }}} + // {{{ apiVersion() + /** + * Return the DB API version + * + * @return int the DB API version number + * + * @access public + */ + function apiVersion() + { + return 2; + } + + // }}} + // {{{ isError() + /** + * Tell whether a result code from a DB method is an error + * + * @param int $value result code + * + * @return bool whether $value is an error + * + * @access public + */ + function isError($value) + { + return (is_object($value) && + (get_class($value) == 'db_error' || + is_subclass_of($value, 'db_error'))); + } + + // }}} + // {{{ isConnection() + /** + * Tell whether a value is a DB connection + * + * @param mixed $value value to test + * + * @return bool whether $value is a DB connection + * + * @access public + */ + function isConnection($value) + { + return (is_object($value) && + is_subclass_of($value, 'db_common') && + method_exists($value, 'simpleQuery')); + } + + // }}} + // {{{ isManip() + /** + * Tell whether a query is a data manipulation query (insert, + * update or delete) or a data definition query (create, drop, + * alter, grant, revoke). + * + * @access public + * + * @param string $query the query + * + * @return boolean whether $query is a data manipulation query + */ + function isManip($query) + { + $manips = 'INSERT|UPDATE|DELETE|'.'REPLACE|CREATE|DROP|'. + 'ALTER|GRANT|REVOKE|'.'LOCK|UNLOCK'; + if (preg_match('/^\s*"?('.$manips.')\s+/i', $query)) { + return true; + } + return false; + } + + // }}} + // {{{ errorMessage() + /** + * Return a textual error message for a DB error code + * + * @param integer $value error code + * + * @return string error message, or false if the error code was + * not recognized + */ + function errorMessage($value) + { + static $errorMessages; + if (!isset($errorMessages)) { + $errorMessages = array( + DB_ERROR => 'unknown error', + DB_ERROR_ALREADY_EXISTS => 'already exists', + DB_ERROR_CANNOT_CREATE => 'can not create', + DB_ERROR_CANNOT_DELETE => 'can not delete', + DB_ERROR_CANNOT_DROP => 'can not drop', + DB_ERROR_CONSTRAINT => 'constraint violation', + DB_ERROR_DIVZERO => 'division by zero', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'invalid date or time', + DB_ERROR_INVALID_NUMBER => 'invalid number', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NODBSELECTED => 'no database selected', + DB_ERROR_NOSUCHFIELD => 'no such field', + DB_ERROR_NOSUCHTABLE => 'no such table', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'not found', + DB_ERROR_NOT_LOCKED => 'not locked', + DB_ERROR_SYNTAX => 'syntax error', + DB_ERROR_UNSUPPORTED => 'not supported', + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + DB_ERROR_INVALID_DSN => 'invalid DSN', + DB_ERROR_CONNECT_FAILED => 'connect failed', + DB_OK => 'no error', + DB_WARNING => 'unknown warning', + DB_WARNING_READ_ONLY => 'read only', + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + DB_ERROR_NOSUCHDB => 'no such database', + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions', + DB_ERROR_TRUNCATED => 'truncated' + ); + } + + if (DB::isError($value)) { + $value = $value->getCode(); + } + + return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[DB_ERROR]; + } + + // }}} + // {{{ parseDSN() + /** + * Parse a data source name + * + * A array with the following keys will be returned: + * 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 + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * @param string $dsn Data Source Name to be parsed + * + * @return array an associative array + * + * @author Tomas V.V.Cox + */ + function parseDSN($dsn) + { + if (is_array($dsn)) { + return $dsn; + } + + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'username' => false, + 'password' => false, + 'protocol' => false, + 'hostspec' => false, + 'port' => false, + 'socket' => false, + 'database' => false + ); + + // 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'] = (empty($arr[2])) ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (empty($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strrpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = rawurldecode(substr($str, 0, $pos)); + $parsed['password'] = rawurldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = rawurldecode($str); + } + } + + // Find protocol and hostspec + + // $dsn => proto(proto_opts)/database + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) { + $proto = $match[1]; + $proto_opts = (!empty($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, '/') !== 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 ($parsed['protocol'] == 'tcp') { + if (strpos($proto_opts, ':') !== false) { + list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts); + } else { + $parsed['hostspec'] = $proto_opts; + } + } elseif ($parsed['protocol'] == 'unix') { + $parsed['socket'] = $proto_opts; + } + + // Get dabase if any + // $dsn => database + if (!empty($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; + } + + // }}} + // {{{ assertExtension() + /** + * Load a PHP database extension if it is not loaded already. + * + * @access public + * + * @param string $name the base name of the extension (without the .so or + * .dll suffix) + * + * @return boolean true if the extension was already or successfully + * loaded, false if it could not be loaded + */ + function assertExtension($name) + { + if (!extension_loaded($name)) { + $dlext = OS_WINDOWS ? '.dll' : '.so'; + $dlprefix = OS_WINDOWS ? 'php_' : ''; + @dl($dlprefix . $name . $dlext); + return extension_loaded($name); + } + return true; + } + // }}} +} +// }}} + +// {{{ class DB_Error +/** + * DB_Error implements a class for reporting portable database error + * messages. + * + * @package DB + * @author Stig Bakken + */ +class DB_Error extends DOLIPEAR_Error +{ + // {{{ constructor + /** + * DB_Error constructor. + * + * @param mixed $code DB error code, or string with error message. + * @param integer $mode what "error mode" to operate in + * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param mixed $debuginfo additional debug info, such as the last query + * + * @access public + * + * @see PEAR_Error + */ + + function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + if (is_int($code)) { + $this->DOLIPEAR_Error('DB Error: ' . DB::errorMessage($code), $code, $mode, $level, $debuginfo); + } else { + $this->DOLIPEAR_Error("DB Error: $code", DB_ERROR, $mode, $level, $debuginfo); + } + } + // }}} +} +// }}} + +// {{{ class DB_Result +/** + * This class implements a wrapper for a DB result set. + * A new instance of this class will be returned by the DB implementation + * after processing a query that returns data. + * + * @package DB + * @author Stig Bakken + */ + +class DB_result +{ + // {{{ properties + + var $dbh; + var $result; + var $row_counter = null; + /** + * for limit queries, the row to start fetching + * @var integer + */ + var $limit_from = null; + + /** + * for limit queries, the number of rows to fetch + * @var integer + */ + var $limit_count = null; + + // }}} + // {{{ constructor + /** + * DB_result constructor. + * @param resource &$dbh DB object reference + * @param resource $result result resource id + * @param array $options assoc array with optional result options + */ + + function DB_result(&$dbh, $result, $options = array()) + { + $this->dbh = &$dbh; + $this->result = $result; + foreach ($options as $key => $value) { + $this->setOption($key, $value); + } + $this->limit_type = $dbh->features['limit']; + $this->autofree = $dbh->options['autofree']; + $this->fetchmode = $dbh->fetchmode; + $this->fetchmode_object_class = $dbh->fetchmode_object_class; + } + + function setOption($key, $value = null) + { + switch ($key) { + case 'limit_from': + $this->limit_from = $value; break; + case 'limit_count'; + $this->limit_count = $value; break; + } + } + + // }}} + // {{{ fetchRow() + /** + * Fetch and return a row of data (it uses driver->fetchInto for that) + * @param int $fetchmode format of fetched row + * @param int $rownum the row number to fetch + * + * @return array a row of data, NULL on no more rows or PEAR_Error on error + * + * @access public + */ + function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->limit_type == false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ( + $this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + return null; + } + if ($this->limit_type == 'emulate') { + $rownum = $this->row_counter; + } + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // default mode specified in DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = &new $object_class($arr); + } + } + return $arr; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ fetchInto() + /** + * Fetch a row of data into an existing variable. + * + * @param mixed &$arr reference to data containing the row + * @param integer $fetchmod format of fetched row + * @param integer $rownum the row number to fetch + * + * @return mixed DB_OK on success, NULL on no more rows or + * a DB_Error object on error + * + * @access public + */ + function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null) + { + if ($fetchmode === DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + if ($fetchmode === DB_FETCHMODE_OBJECT) { + $fetchmode = DB_FETCHMODE_ASSOC; + $object_class = $this->fetchmode_object_class; + } + if ($this->limit_from !== null) { + if ($this->row_counter === null) { + $this->row_counter = $this->limit_from; + // Skip rows + if ($this->limit_type == false) { + $i = 0; + while ($i++ < $this->limit_from) { + $this->dbh->fetchInto($this->result, $arr, $fetchmode); + } + } + } + if ($this->row_counter >= ( + $this->limit_from + $this->limit_count)) + { + if ($this->autofree) { + $this->free(); + } + return null; + } + if ($this->limit_type == 'emulate') { + $rownum = $this->row_counter; + } + + $this->row_counter++; + } + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum); + if ($res === DB_OK) { + if (isset($object_class)) { + // default mode specified in DB_common::fetchmode_object_class property + if ($object_class == 'stdClass') { + $arr = (object) $arr; + } else { + $arr = new $object_class($arr); + } + } + return DB_OK; + } + if ($res == null && $this->autofree) { + $this->free(); + } + return $res; + } + + // }}} + // {{{ numCols() + /** + * Get the the number of columns in a result set. + * + * @return int the number of columns, or a DB error + * + * @access public + */ + function numCols() + { + return $this->dbh->numCols($this->result); + } + + // }}} + // {{{ numRows() + /** + * Get the number of rows in a result set. + * + * @return int the number of rows, or a DB error + * + * @access public + */ + function numRows() + { + return $this->dbh->numRows($this->result); + } + + // }}} + // {{{ nextResult() + /** + * Get the next result if a batch of queries was executed. + * + * @return bool true if a new result is available or false if not. + * + * @access public + */ + function nextResult() + { + return $this->dbh->nextResult($this->result); + } + + // }}} + // {{{ free() + /** + * Frees the resources allocated for this result set. + * @return int error code + * + * @access public + */ + function free() + { + $err = $this->dbh->freeResult($this->result); + if(DB::isError($err)) { + return $err; + } + $this->result = false; + return true; + } + + // }}} + // {{{ tableInfo() + /** + * @deprecated + */ + function tableInfo($mode = null) + { + return $this->dbh->tableInfo($this->result, $mode); + } + + // }}} + // {{{ getRowCounter() + /** + * returns the actual row number + * @return integer + */ + function getRowCounter() + { + return $this->row_counter; + } + // }}} +} +// }}} + +// {{{ class DB_Row +/** +* Pear DB Row Object +* @see DB_common::setFetchMode() +*/ +class DB_row +{ + // {{{ constructor + /** + * constructor + * + * @param resource row data as array + */ + function DB_row(&$arr) + { + for (reset($arr); $key = key($arr); next($arr)) { + $this->$key = &$arr[$key]; + } + } + + // }}} +} +// }}} + +?> diff --git a/htdocs/includes/pear/DB/common.php b/htdocs/includes/pear/DB/common.php new file mode 100644 index 00000000000..a361e8af927 --- /dev/null +++ b/htdocs/includes/pear/DB/common.php @@ -0,0 +1,1398 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// +// Base class for DB implementations. +// + +/** + * DB_common is a base class for DB implementations, and must be + * inherited by all such. + */ + +require_once "PEAR.php"; + +class DB_common extends PEAR +{ + // {{{ properties + /** + * assoc of capabilities for this DB implementation + * $features['limit'] => 'emulate' => emulate with fetch row by number + * 'alter' => alter the query + * false => skip rows + * @var array + */ + var $features; + + /** + * assoc mapping native error codes to DB ones + * @var array + */ + var $errorcode_map; + + /** + * DB type (mysql, oci8, odbc etc.) + * @var string + */ + var $type; + + /** + * @var string + */ + var $prepare_tokens; + + /** + * @var string + */ + var $prepare_types; + + /** + * @var string + */ + var $prepared_queries; + + /** + * @var integer + */ + var $prepare_maxstmt = 0; + + /** + * @var string + */ + var $last_query = ''; + + /** + * @var integer + */ + var $fetchmode = DB_FETCHMODE_ORDERED; + + /** + * @var string + */ + var $fetchmode_object_class = 'stdClass'; + + /** + * $options["persistent"] -> boolean persistent connection true|false? + * $options["optimize"] -> string 'performance' or 'portability' + * $options["debug"] -> integer numeric debug level + * @var array + */ + var $options = array( + 'persistent' => false, + 'optimize' => 'performance', + 'debug' => 0, + 'seqname_format' => '%s_seq', + 'autofree' => false + ); + + /** + * DB handle + * @var resource + */ + var $dbh; + + // }}} + // {{{ toString() + /** + * String conversation + * + * @return string + * @access private + */ + function toString() + { + $info = get_class($this); + $info .= ": (phptype=" . $this->phptype . + ", dbsyntax=" . $this->dbsyntax . + ")"; + + if ($this->connection) { + $info .= " [connected]"; + } + + return $info; + } + + // }}} + // {{{ constructor + /** + * Constructor + */ + function DB_common() + { + $this->PEAR('DB_Error'); + $this->features = array(); + $this->errorcode_map = array(); + $this->fetchmode = DB_FETCHMODE_ORDERED; + } + + // }}} + // {{{ quoteString() + + /** + * Quotes a string so it can be safely used within string delimiters + * in a query (preserved for compatibility issues, quote() is preffered). + * + * @return string quoted string + * @access public + * @see quote() + */ + function quoteString($string) + { + $string = $this->quote($string); + if ($string{0} == "'") { + return substr($string, 1, -1); + } + return $string; + } + + /** + * Quotes a string so it can be safely used in a query. It will return + * the string with single quotes around. Other backend quote styles + * should override this method. + * + * @param string $string the input string to quote + * + * @return string The NULL string or the string quotes + * in magic_quote_sybase style + */ + function quote($string) + { + return ($string === null) ? 'NULL' : "'".str_replace("'", "''", $string)."'"; + } + + // }}} + // {{{ provides() + + /** + * Tell whether a DB implementation or its backend extension + * supports a given feature. + * + * @param array $feature name of the feature (see the DB class doc) + * @return bool whether this DB implementation supports $feature + * @access public + */ + + function provides($feature) + { + return $this->features[$feature]; + } + + // }}} + // {{{ errorCode() + + /** + * Map native error codes to DB's portable ones. Requires that + * the DB implementation's constructor fills in the $errorcode_map + * property. + * + * @param mixed $nativecode the native error code, as returned by the backend + * database extension (string or integer) + * + * @return int a portable DB error code, or FALSE if this DB + * implementation has no mapping for the given error code. + * + * @access public + */ + + function errorCode($nativecode) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ errorMessage() + + /** + * Map a DB error code to a textual message. This is actually + * just a wrapper for DB::errorMessage(). + * + * @param integer $dbcode the DB error code + * + * @return string the corresponding error message, of FALSE + * if the error code was unknown + * + * @access public + */ + + function errorMessage($dbcode) + { + return DB::errorMessage($this->errorcode_map[$dbcode]); + } + + // }}} + // {{{ raiseError() + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * + * @param int error mode, see PEAR_Error docs + * + * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * + * @param string Extra debug information. Defaults to the last + * query and native error code. + * + * @param mixed Native error code, integer or string depending the + * backend. + * + * @return object a PEAR error object + * + * @access public + * @see PEAR_Error + */ + function &raiseError($code = DB_ERROR, $mode = null, $options = null, + $userinfo = null, $nativecode = null) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + return PEAR::raiseError($code, null, $mode, $options, null, null, true); + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= " [nativecode=$nativecode]"; + } + + return PEAR::raiseError(null, $code, $mode, $options, $userinfo, + 'DB_Error', true); + } + + // }}} + // {{{ setFetchMode() + + /** + * Sets which fetch mode should be used by default on queries + * on this connection. + * + * @param integer $fetchmode DB_FETCHMODE_ORDERED or + * DB_FETCHMODE_ASSOC, possibly bit-wise OR'ed with + * DB_FETCHMODE_FLIPPED. + * + * @param string $object_class The class of the object + * to be returned by the fetch methods when + * the DB_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be done. + * There is also the posibility to use and extend the + * 'DB_Row' class. + * + * @see DB_FETCHMODE_ORDERED + * @see DB_FETCHMODE_ASSOC + * @see DB_FETCHMODE_FLIPPED + * @see DB_FETCHMODE_OBJECT + * @see DB_Row::DB_Row() + * @access public + */ + + function setFetchMode($fetchmode, $object_class = null) + { + switch ($fetchmode) { + case DB_FETCHMODE_OBJECT: + if ($object_class) { + $this->fetchmode_object_class = $object_class; + } + case DB_FETCHMODE_ORDERED: + case DB_FETCHMODE_ASSOC: + $this->fetchmode = $fetchmode; + break; + default: + return $this->raiseError('invalid fetchmode mode'); + } + } + + // }}} + // {{{ setOption() + /** + * set the option for the db class + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return mixed DB_OK or DB_Error + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + $this->options[$option] = $value; + return DB_OK; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ getOption() + /** + * returns the value of an option + * + * @param string $option option name + * + * @return mixed the option value + */ + function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ 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(?,?,?)". The ? are wildcards. + * Types of wildcards: + * ? - a quoted scalar value, i.e. strings, integers + * & - requires a file name, the content of the file + * insert into the query (i.e. saving binary data + * in a db) + * ! - value is inserted 'as is' + * + * @param string the query to prepare + * + * @return resource handle for the query + * + * @access public + * @see execute + */ + + function prepare($query) + { + $tokens = split("[\&\?\!]", $query); + $token = 0; + $types = array(); + $qlen = strlen($query); + for ($i = 0; $i < $qlen; $i++) { + switch ($query[$i]) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + break; + } + } + + $this->prepare_tokens[] = &$tokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = &$query; + + return $k; + } + + // }}} + // {{{ autoPrepare() + + /** + * Make automaticaly an insert or update query and call prepare() with it + * + * @param string $table name of the table + * @param array $table_fields ordered array containing the fields names + * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE) + * @param string $where in case of update queries, this string will be put after the sql WHERE statement + * @return resource handle for the query + * @see buildManipSQL + * @access public + */ + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, $where = false) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + return $this->prepare($query); + } + + // {{{ + // }}} autoExecute() + + /** + * Make automaticaly an insert or update query and call prepare() and execute() with it + * + * @param string $table name of the table + * @param array $fields_values assoc ($key=>$value) where $key is a field name and $value its value + * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE) + * @param string $where in case of update queries, this string will be put after the sql WHERE statement + * @return mixed a new DB_Result or a DB_Error when fail + * @see buildManipSQL + * @see autoPrepare + * @access public + */ + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, $where = false) + { + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, $where); + $ret = $this->execute($sth, array_values($fields_values)); + $this->freePrepared($sth); + return $ret; + + } + + // {{{ + // }}} buildManipSQL() + + /** + * Make automaticaly an sql query for prepare() + * + * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), DB_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 query, all + * the records of the table will be updated ! + * + * @param string $table name of the table + * @param array $table_fields ordered array containing the fields names + * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE) + * @param string $where in case of update 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) + { + if (count($table_fields) == 0) { + $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + $first = true; + switch ($mode) { + case DB_AUTOQUERY_INSERT: + $values = ''; + $names = ''; + while (list(, $value) = each($table_fields)) { + if ($first) { + $first = false; + } else { + $names .= ','; + $values .= ','; + } + $names .= $value; + $values .= '?'; + } + return "INSERT INTO $table ($names) VALUES ($values)"; + break; + case DB_AUTOQUERY_UPDATE: + $set = ''; + while (list(, $value) = each($table_fields)) { + if ($first) { + $first = false; + } else { + $set .= ','; + } + $set .= "$value = ?"; + } + $sql = "UPDATE $table SET $set"; + if ($where) { + $sql .= " WHERE $where"; + } + return $sql; + break; + default: + $this->raiseError(DB_ERROR_SYNTAX); + } + } + + // }}} + // {{{ execute() + /** + * Executes a prepared SQL query + * With execute() the generic query of prepare is + * assigned with the given data array. The values + * of the array inserted into the query in the same + * order like the array order + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return mixed a new DB_Result or a DB_Error when fail + * + * @access public + * @see prepare() + */ + function &execute($stmt, $data = false) + { + $realquery = $this->executeEmulateQuery($stmt, $data); + if (DB::isError($realquery)) { + return $realquery; + } + $result = $this->simpleQuery($realquery); + + if (DB::isError($result) || $result === DB_OK) { + return $result; + } else { + return new DB_result($this, $result); + } + } + + // }}} + // {{{ executeEmulateQuery() + + /** + * Emulates the execute statement, when not supported + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return mixed a string containing the real query run when emulating + * prepare/execute. A DB error code is returned on failure. + * + * @access private + * @see execute() + */ + + function executeEmulateQuery($stmt, $data = false) + { + $p = &$this->prepare_tokens; + + if (!isset($this->prepare_tokens[$stmt]) || + !is_array($this->prepare_tokens[$stmt]) || + !sizeof($this->prepare_tokens[$stmt])) + { + return $this->raiseError(DB_ERROR_INVALID); + } + + $qq = &$this->prepare_tokens[$stmt]; + $qp = sizeof($qq) - 1; + + if ((!$data && $qp > 0) || + (!is_array($data) && $qp > 1) || + (is_array($data) && $qp > sizeof($data))) + { + $this->last_query = $this->prepared_queries[$stmt]; + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + + $realquery = $qq[0]; + for ($i = 0; $i < $qp; $i++) { + $type = $this->prepare_types[$stmt][$i]; + if ($type == DB_PARAM_OPAQUE) { + if (is_array($data)) { + $fp = fopen($data[$i], 'r'); + } else { + $fp = fopen($data, 'r'); + } + + $pdata = ''; + + if ($fp) { + while (($buf = fread($fp, 4096)) != false) { + $pdata .= $buf; + } + fclose($fp); + } + } else { + if (is_array($data)) { + $pdata = &$data[$i]; + } else { + $pdata = &$data; + } + } + + $realquery .= ($type != DB_PARAM_MISC) ? $this->quote($pdata) : $pdata; + $realquery .= $qq[$i + 1]; + } + + return $realquery; + } + + // }}} + // {{{ executeMultiple() + + /** + * This function does several execute() calls on the same + * statement handle. $data must be an array indexed numerically + * from 0, one execute call is done for every "row" in the array. + * + * If an error occurs during execute(), executeMultiple() does not + * execute the unfinished rows, but rather returns that error. + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return mixed DB_OK or DB_Error + * + * @access public + * @see prepare(), execute() + */ + + function executeMultiple( $stmt, &$data ) + { + for($i = 0; $i < sizeof( $data ); $i++) { + $res = $this->execute($stmt, $data[$i]); + if (DB::isError($res)) { + return $res; + } + } + return DB_OK; + } + + // }}} + // {{{ freePrepared() + + /* + * Free the resource used in a prepared query + * + * @param $stmt The resurce returned by the prepare() function + * @see prepare() + */ + function freePrepared($stmt) + { + // Free the internal prepared vars + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ modifyQuery() + + /** + * This method is used by backends to alter queries for various + * reasons. It is defined here to assure that all implementations + * have this method defined. + * + * @param string $query query to modify + * + * @return the new (modified) query + * + * @access private + */ + function modifyQuery($query) { + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + /** + * This method is used by backends to alter limited queries + * + * @param string $query query to modify + * @param integer $from the row to start to fetching + * @param integer $count the numbers of rows to fetch + * + * @return the new (modified) query + * + * @access private + */ + + function modifyLimitQuery($query, $from, $count) + { + return $query; + } + + // }}} + // {{{ query() + + /** + * Send a query to the database and return any results with a + * DB_result object. + * + * @access public + * + * @param string $query the SQL query or the statement to prepare + * @param string $params the data to be added to the query + * @return mixed a DB_result object or DB_OK on success, a DB + * error on failure + * + * @see DB::isError + * @see DB_common::prepare + * @see DB_common::execute + */ + function &query($query, $params = array()) { + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $ret = $this->execute($sth, $params); + $this->freePrepared($sth); + return $ret; + } else { + $result = $this->simpleQuery($query); + if (DB::isError($result) || $result === DB_OK) { + return $result; + } else { + return new DB_result($this, $result); + } + } + } + + // }}} + // {{{ limitQuery() + /** + * Generates a limited query + * + * @param string $query query + * @param integer $from the row to start to fetching + * @param integer $count the numbers of rows to fetch + * @param array $params required for a statement + * + * @return mixed a DB_Result object, DB_OK or a DB_Error + * + * @access public + */ + function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count); + $result = $this->query($query, $params); + if (get_class($result) == 'db_result') { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + 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 $query the SQL query + * @param array $params if supplied, prepare/execute will be used + * with this array as execute parameters + * + * @return mixed DB_Error or the returned value of the query + * + * @access public + */ + + function &getOne($query, $params = array()) + { + settype($params, "array"); + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row[0]; + } + + // }}} + // {{{ 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 $query the SQL query + * @param integer $fetchmode the fetch mode to use + * @param array $params array if supplied, prepare/execute will be used + * with this array as execute parameters + * @access public + * @return array the first row of results as an array indexed from + * 0, or a DB error code. + */ + + function &getRow($query, + $params = null, + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + $tmp = $params; + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = null; + } + } + $params = (empty($params)) ? array() : $params; + $fetchmode = (empty($fetchmode)) ? DB_FETCHMODE_DEFAULT : $fetchmode; + settype($params, 'array'); + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, $fetchmode); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetch a single column from a result set and return it as an + * indexed array. + * + * @param string $query the SQL query + * + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * + * @param array $params array if supplied, prepare/execute will be used + * with this array as execute parameters + * @access public + * + * @return array an indexed array with the data from the first + * row at index 0, or a DB error code. + */ + + function &getCol($query, $col = 0, $params = array()) + { + settype($params, "array"); + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + $ret = array(); + + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + + // }}} + // {{{ 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 DB error code is + * returned on errors. If the result set contains fewer than two + * columns, a DB_ERROR_TRUNCATED error is returned. + * + * For example, if the table "mytable" contains: + * + * ID TEXT DATE + * -------------------------------- + * 1 'one' 944679408 + * 2 'two' 944679408 + * 3 'three' 944679408 + * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + * array( + * '1' => 'one', + * '2' => 'two', + * '3' => 'three', + * ) + * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + * array( + * '1' => array('one', '944679408'), + * '2' => array('two', '944679408'), + * '3' => array('three', '944679408') + * ) + * + * If the more than one row occurs with the same value in the + * first column, the last row overwrites all previous ones by + * default. Use the $group parameter if you don't want to + * overwrite like this. Example: + * + * getAssoc('SELECT category,id,name FROM mytable', false, null, + * DB_FETCHMODE_ASSOC, true) returns: + * array( + * '1' => array(array('id' => '4', 'name' => 'number four'), + * array('id' => '6', 'name' => 'number six') + * ), + * '9' => array(array('id' => '4', 'name' => 'number four'), + * array('id' => '6', 'name' => 'number six') + * ) + * ) + * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string $query the SQL query + * + * @param boolean $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 array $params array if supplied, prepare/execute will be used + * with this array as execute parameters + * + * @param boolean $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. + * + * @access public + * + * @return array associative array with results from the query. + */ + + function &getAssoc($query, $force_array = false, $params = array(), + $fetchmode = DB_FETCHMODE_ORDERED, $group = false) + { + settype($params, "array"); + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $cols = $res->numCols(); + + if ($cols < 2) { + return $this->raiseError(DB_ERROR_TRUNCATED); + } + + $results = array(); + + if ($cols > 2 || $force_array) { + // return array values + // XXX this part can be optimized + if ($fetchmode == DB_FETCHMODE_ASSOC) { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { + reset($row); + $key = current($row); + unset($row[key($row)]); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } else { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + // we shift away the first element to get + // indices running from 0 again + $key = array_shift($row); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } + if (DB::isError($row)) { + $results = $row; + } + } else { + // return scalar values + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + if ($group) { + $results[$row[0]][] = $row[1]; + } else { + $results[$row[0]] = $row[1]; + } + } + if (DB::isError($row)) { + $results = $row; + } + } + + $res->free(); + + return $results; + } + + // }}} + // {{{ getAll() + + /** + * Fetch all the rows returned from a query. + * + * @param string $query the SQL query + * + * @param array $params array if supplied, prepare/execute will be used + * with this array as execute parameters + * @param integer $fetchmode the fetch mode to use + * + * @access public + * @return array an nested array, or a DB error + */ + + function &getAll($query, + $params = null, + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + $tmp = $params; + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = null; + } + } + $params = (empty($params)) ? array() : $params; + $fetchmode = (empty($fetchmode)) ? DB_FETCHMODE_DEFAULT : $fetchmode; + settype($params, "array"); + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $results = array(); + while (DB_OK === $res->fetchInto($row, $fetchmode)) { + if ($fetchmode & DB_FETCHMODE_FLIPPED) { + foreach ($row as $key => $val) { + $results[$key][] = $val; + } + } else { + $results[] = $row; + } + } + + $res->free(); + + if (DB::isError($row)) { + return $this->raiseError($row); + } + return $results; + } + + // }}} + // {{{ autoCommit() + /** + * enable automatic Commit + * + * @param boolean $onoff + * @return mixed DB_Error + * + * @access public + */ + function autoCommit($onoff=false) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ commit() + /** + * starts a Commit + * + * @return mixed DB_Error + * + * @access public + */ + function commit() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ rollback() + /** + * starts a rollback + * + * @return mixed DB_Error + * + * @access public + */ + function rollback() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numRows() + /** + * returns the number of rows in a result object + * + * @param object DB_Result the result object to check + * + * @return mixed DB_Error or the number of rows + * + * @access public + */ + function numRows($result) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ affectedRows() + /** + * returns the affected rows of a query + * + * @return mixed DB_Error or number of rows + * + * @access public + */ + function affectedRows() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ errorNative() + /** + * returns an errormessage, provides by the database + * + * @return mixed DB_Error or message + * + * @access public + */ + function errorNative() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ nextId() + /** + * returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the seqence is + * automatic created, if it + * not exists + * + * @return mixed DB_Error or id + */ + function nextId($seq_name, $ondemand = true) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ createSequence() + /** + * creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return mixed DB_Error + * + * @access public + */ + function createSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ dropSequence() + /** + * deletes a sequence + * + * @param string $seq_name name of the sequence + * + * @return mixed DB_Error + * + * @access public + */ + function dropSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ tableInfo() + /** + * returns meta data about the result set + * + * @param object DB_Result $result the result object to analyse + * @param mixed $mode depends on implementation + * + * @return mixed DB_Error + * + * @access public + */ + function tableInfo($result, $mode = null) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getTables() + /** + * @deprecated + */ + function getTables() + { + return $this->getListOf('tables'); + } + + // }}} + // {{{ getListOf() + /** + * list internal DB info + * valid values for $type are db dependent, + * often: databases, users, view, functions + * + * @param string $type type of requested info + * + * @return mixed DB_Error or the requested data + * + * @access public + */ + function getListOf($type) + { + $sql = $this->getSpecialQuery($type); + if ($sql === null) { // No support + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } elseif (is_int($sql) || DB::isError($sql)) { // Previous error + return $this->raiseError($sql); + } elseif (is_array($sql)) { // Already the result + return $sql; + } + return $this->getCol($sql); // Launch this query + } + // }}} + // {{{ getSequenceName() + + function getSequenceName($sqn) + { + return sprintf($this->getOption("seqname_format"), + preg_replace('/[^a-z0-9_]/i', '_', $sqn)); + } + + // }}} +} + +// Used by many drivers +if (!function_exists('array_change_key_case')) { + define('CASE_UPPER', 1); + define('CASE_LOWER', 0); + function &array_change_key_case(&$array, $case) { + $casefunc = ($case == CASE_LOWER) ? 'strtolower' : 'strtoupper'; + $ret = array(); + foreach ($array as $key => $value) { + $ret[$casefunc($key)] = $value; + } + return $ret; + } +} + +?> diff --git a/htdocs/includes/pear/DB/mysql.php b/htdocs/includes/pear/DB/mysql.php new file mode 100644 index 00000000000..a8b92f41834 --- /dev/null +++ b/htdocs/includes/pear/DB/mysql.php @@ -0,0 +1,843 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// +// Database independent query interface definition for PHP's MySQL +// extension. +// + +// +// XXX legend: +// +// XXX ERRORMSG: The error message from the mysql function should +// be registered here. +// + +require_once "DB/common.php"; + +class DB_mysql extends DB_common +{ + // {{{ properties + + var $connection; + var $phptype, $dbsyntax; + var $prepare_tokens = array(); + var $prepare_types = array(); + var $num_rows = array(); + var $transaction_opcount = 0; + var $autocommit = true; + var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */ + var $_db = false; + + // }}} + // {{{ constructor + + /** + * DB_mysql constructor. + * + * @access public + */ + + function DB_mysql() + { + $this->DB_common(); + $this->phptype = 'mysql'; + $this->dbsyntax = 'mysql'; + $this->features = array( + 'prepare' => false, + 'pconnect' => true, + 'transactions' => true, + 'limit' => 'alter' + ); + $this->errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1046 => DB_ERROR_NODBSELECTED, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1146 => DB_ERROR_NOSUCHTABLE, + 1048 => DB_ERROR_CONSTRAINT, + ); + } + + // }}} + + // {{{ connect() + + /** + * Connect to a database and log in as the specified user. + * + * @param $dsn the data source name (see DB::parseDSN for syntax) + * @param $persistent (optional) whether the connection should + * be persistent + * @access public + * @return int DB_OK on success, a DB error on failure + */ + + function connect($dsninfo, $persistent = false) + { + if (!DB::assertExtension('mysql')) + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + + $this->dsn = $dsninfo; + if (isset($dsninfo['protocol']) && $dsninfo['protocol'] == 'unix') { + $dbhost = ':' . $dsninfo['socket']; + } else { + $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost'; + if (!empty($dsninfo['port'])) { + $dbhost .= ':' . $dsninfo['port']; + } + } + $user = $dsninfo['username']; + $pw = $dsninfo['password']; + + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + if ($dbhost && $user && $pw) { + $conn = @$connect_function($dbhost, $user, $pw); + } elseif ($dbhost && $user) { + $conn = @$connect_function($dbhost, $user); + } elseif ($dbhost) { + $conn = @$connect_function($dbhost); + } else { + $conn = false; + } + if (empty($conn)) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null, + null, $err); + } elseif (empty($php_errormsg)) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null, + null, $php_errormsg); + } + } + + if ($dsninfo['database']) { + if (!@mysql_select_db($dsninfo['database'], $conn)) { + switch(mysql_errno($conn)) { + case 1049: + return $this->raiseError(DB_ERROR_NOSUCHDB, null, null, + null, mysql_error($conn)); + break; + case 1044: + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION, null, null, + null, mysql_error($conn)); + break; + default: + return $this->raiseError(DB_ERROR, null, null, + null, mysql_error($conn)); + break; + + } + } + // fix to allow calls to different databases in the same script + $this->_db = $dsninfo['database']; + } + + $this->connection = $conn; + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @access public + * + * @return bool TRUE on success, FALSE if not connected. + */ + function disconnect() + { + $ret = mysql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Send a query to MySQL and return the results as a MySQL resource + * identifier. + * + * @param the SQL query + * + * @access public + * + * @return mixed returns a valid MySQL result for successful SELECT + * queries, DB_OK for other successful queries. A DB error is + * returned on failure. + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); + $result = @mysql_query('BEGIN', $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @mysql_query($query, $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + if (is_resource($result)) { + $numrows = $this->numrows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->num_rows[$result] = $numrows; + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result + * + * This method has not been implemented yet. + * + * @param a valid sql result resource + * + * @access public + * + * @return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param $result MySQL result identifier + * @param $arr (reference) array where data from the row is stored + * @param $fetchmode how the array data should be indexed + * @param $rownum the row number to fetch + * @access public + * + * @return int DB_OK on success, a DB error on failure + */ + function fetchInto($result, &$arr, $fetchmode, $rownum=null) + { + if ($rownum !== null) { + if (!@mysql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysql_fetch_array($result, MYSQL_ASSOC); + } else { + $arr = @mysql_fetch_row($result); + } + if (!$arr) { + // See: http://bugs.php.net/bug.php?id=22328 + // for why we can't check errors on fetching + return null; + /* + $errno = @mysql_errno($this->connection); + if (!$errno) { + return NULL; + } + return $this->mysqlRaiseError($errno); + */ + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Free the internal resources associated with $result. + * + * @param $result MySQL result identifier or DB statement identifier + * + * @access public + * + * @return bool TRUE on success, FALSE if $result is invalid + */ + function freeResult($result) + { + if (is_resource($result)) { + return mysql_free_result($result); + } + + $result = (int)$result; // $result is a prepared query handle + if (!isset($this->prepare_tokens[$result])) { + return false; + } + + + // I fixed the unset thing. + + $this->prepare_types = array(); + $this->prepare_tokens = array(); + + return true; + } + + // }}} + // {{{ numCols() + + /** + * Get the number of columns in a result set. + * + * @param $result MySQL result identifier + * + * @access public + * + * @return int the number of columns per row in $result + */ + function numCols($result) + { + $cols = @mysql_num_fields($result); + + if (!$cols) { + return $this->mysqlRaiseError(); + } + + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Get the number of rows in a result set. + * + * @param $result MySQL result identifier + * + * @access public + * + * @return int the number of rows in $result + */ + function numRows($result) + { + $rows = @mysql_num_rows($result); + if ($rows === null) { + return $this->mysqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enable/disable automatic commits + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the current transaction. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('COMMIT', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Roll back (undo) the current transaction. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysql_query('ROLLBACK', $this->connection); + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Gets the number of rows affected by the data manipulation + * query. For other queries, this function returns 0. + * + * @return number of rows affected by the last query + */ + + function affectedRows() + { + if (DB::isManip($this->last_query)) { + $result = @mysql_affected_rows($this->connection); + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ errorNative() + + /** + * Get the native error code of the last error (if any) that + * occured on the current connection. + * + * @access public + * + * @return int native MySQL error code + */ + + function errorNative() + { + return mysql_errno($this->connection); + } + + // }}} + // {{{ nextId() + + /** + * Get the next value in a sequence. We emulate sequences + * for MySQL. Will create the sequence if it does not exist. + * + * @access public + * + * @param string $seq_name the name of the sequence + * + * @param bool $ondemand whether to create the sequence table on demand + * (default is true) + * + * @return mixed a sequence integer, or a DB error + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("UPDATE ${seqname} ". + 'SET id=LAST_INSERT_ID(id+1)'); + $this->popErrorHandling(); + if ($result == DB_OK) { + /** COMMON CASE **/ + $id = mysql_insert_id($this->connection); + if ($id != 0) { + return $id; + } + /** EMPTY SEQ TABLE **/ + // Sequence table must be empty for some reason, so fill it and return 1 + // Obtain a user-level lock + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + // Failed to get the lock, bail with a DB_ERROR_NOT_LOCKED error + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query("REPLACE INTO ${seqname} VALUES (0)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + /** ONDEMAND TABLE CREATION **/ + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + + /** BACKWARDS COMPAT **/ + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ createSequence() + + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query("CREATE TABLE ${seqname} ". + '(id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'. + ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + $res = $this->query("INSERT INTO ${seqname} VALUES(0)"); + if (DB::isError($res)) { + return $res; + } + // so reset to zero + return $this->query("UPDATE ${seqname} SET id = 0;"); + } + + // }}} + // {{{ dropSequence() + + function dropSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + return $this->query("DROP TABLE ${seqname}"); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname The sequence name to clean up + * @return mixed DB_Error or true + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query("DELETE FROM ${seqname} WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quote() + + /** + * Quote the given string so it can be safely used within string delimiters + * in a query. + * @param $string mixed Data to be quoted + * @return mixed "NULL" string, quoted string or original data + */ + function quote($str = null) + { + switch (strtolower(gettype($str))) { + case 'null': + return 'NULL'; + case 'integer': + case 'double': + return $str; + case 'string': + default: + if(function_exists('mysql_real_escape_string')) { + return "'".mysql_real_escape_string($str, $this->connection)."'"; + } else { + return "'".mysql_escape_string($str)."'"; + } + } + } + + // }}} + // {{{ modifyQuery() + + function modifyQuery($query, $subject = null) + { + if ($this->options['optimize'] == 'portability') { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + function modifyLimitQuery($query, $from, $count) + { + if (DB::isManip($query)) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqlRaiseError() + + function mysqlRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysql_errno($this->connection) . " ** " . + @mysql_error($this->connection)); + } + + // }}} + // {{{ tableInfo() + + function tableInfo($result, $mode = null) { + $count = 0; + $id = 0; + $res = array(); + + /* + * depending on $mode, metadata returns the following values: + * + * - mode is null (default): + * $result[]: + * [0]["table"] table name + * [0]["name"] field name + * [0]["type"] field type + * [0]["len"] field length + * [0]["flags"] field flags + * + * - mode is DB_TABLEINFO_ORDER + * $result[]: + * ["num_fields"] number of metadata records + * [0]["table"] table name + * [0]["name"] field name + * [0]["type"] field type + * [0]["len"] field length + * [0]["flags"] field flags + * ["order"][field name] index of field named "field name" + * The last one is used, if you have a field name, but no index. + * Test: if (isset($result['meta']['myfield'])) { ... + * + * - mode is DB_TABLEINFO_ORDERTABLE + * the same as above. but additionally + * ["ordertable"][table name][field name] index of field + * named "field name" + * + * this is, because if you have fields from different + * tables with the same field name * they override each + * other with DB_TABLEINFO_ORDER + * + * you can combine DB_TABLEINFO_ORDER and + * DB_TABLEINFO_ORDERTABLE with DB_TABLEINFO_ORDER | + * DB_TABLEINFO_ORDERTABLE * or with DB_TABLEINFO_FULL + */ + + // if $result is a string, then we want information about a + // table without a resultset + if (is_string($result)) { + $id = @mysql_list_fields($this->dsn['database'], + $result, $this->connection); + if (empty($id)) { + return $this->mysqlRaiseError(); + } + } else { // else we want information about a resultset + $id = $result; + if (empty($id)) { + return $this->mysqlRaiseError(); + } + } + + $count = @mysql_num_fields($id); + + // made this IF due to performance (one if is faster than $count if's) + if (empty($mode)) { + for ($i=0; $i<$count; $i++) { + $res[$i]['table'] = @mysql_field_table ($id, $i); + $res[$i]['name'] = @mysql_field_name ($id, $i); + $res[$i]['type'] = @mysql_field_type ($id, $i); + $res[$i]['len'] = @mysql_field_len ($id, $i); + $res[$i]['flags'] = @mysql_field_flags ($id, $i); + } + } else { // full + $res['num_fields']= $count; + + for ($i=0; $i<$count; $i++) { + $res[$i]['table'] = @mysql_field_table ($id, $i); + $res[$i]['name'] = @mysql_field_name ($id, $i); + $res[$i]['type'] = @mysql_field_type ($id, $i); + $res[$i]['len'] = @mysql_field_len ($id, $i); + $res[$i]['flags'] = @mysql_field_flags ($id, $i); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + } + + // free the result only if we were called on a table + if (is_string($result)) { + @mysql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Returns the query needed to get some backend info + * @param string $type What kind of info you want to retrieve + * @return string The SQL query string + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + $sql = "SHOW TABLES"; + break; + case 'views': + return DB_ERROR_NOT_CAPABLE; + case 'users': + $sql = "select distinct User from user"; + if($this->dsn['database'] != 'mysql') { + $dsn = $this->dsn; + $dsn['database'] = 'mysql'; + if (DB::isError($db = DB::connect($dsn))) { + return $db; + } + $sql = $db->getCol($sql); + $db->disconnect(); + // XXX Fixme the mysql driver should take care of this + if (!@mysql_select_db($this->dsn['database'], $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + return $sql; + break; + case 'databases': + $sql = "SHOW DATABASES"; + break; + default: + return null; + } + return $sql; + } + + // }}} + + // TODO/wishlist: + // longReadlen + // binmode +} + +?> diff --git a/htdocs/includes/pear/DB/pgsql.php b/htdocs/includes/pear/DB/pgsql.php new file mode 100644 index 00000000000..0a4ec093c36 --- /dev/null +++ b/htdocs/includes/pear/DB/pgsql.php @@ -0,0 +1,786 @@ + | +// | Stig Bakken | +// +----------------------------------------------------------------------+ +// +// $Id$ +// +// Database independent query interface definition for PHP's PostgreSQL +// extension. +// + +require_once 'DB/common.php'; + +class DB_pgsql extends DB_common +{ + // {{{ properties + + var $connection; + var $phptype, $dbsyntax; + var $prepare_tokens = array(); + var $prepare_types = array(); + var $transaction_opcount = 0; + var $dsn = array(); + var $row = array(); + var $num_rows = array(); + var $affected = 0; + var $autocommit = true; + var $fetchmode = DB_FETCHMODE_ORDERED; + + // }}} + // {{{ constructor + + function DB_pgsql() + { + $this->DB_common(); + $this->phptype = 'pgsql'; + $this->dbsyntax = 'pgsql'; + $this->features = array( + 'prepare' => false, + 'pconnect' => true, + 'transactions' => true, + 'limit' => 'alter' + ); + $this->errorcode_map = array( + ); + } + + // }}} + // {{{ connect() + + /** + * Connect to a database and log in as the specified user. + * + * @param $dsn the data source name (see DB::parseDSN for syntax) + * @param $persistent (optional) whether the connection should + * be persistent + * + * @return int DB_OK on success, a DB error code on failure + */ + function connect($dsninfo, $persistent = false) + { + if (!DB::assertExtension('pgsql')) + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + + $this->dsn = $dsninfo; + $protocol = (isset($dsninfo['protocol'])) ? $dsninfo['protocol'] : 'tcp'; + $connstr = ''; + + if ($protocol == 'tcp') { + if (!empty($dsninfo['hostspec'])) { + $connstr = 'host=' . $dsninfo['hostspec']; + } + if (!empty($dsninfo['port'])) { + $connstr .= ' port=' . $dsninfo['port']; + } + } + + if (isset($dsninfo['database'])) { + $connstr .= ' dbname=\'' . addslashes($dsninfo['database']) . '\''; + } + if (!empty($dsninfo['username'])) { + $connstr .= ' user=\'' . addslashes($dsninfo['username']) . '\''; + } + if (!empty($dsninfo['password'])) { + $connstr .= ' password=\'' . addslashes($dsninfo['password']) . '\''; + } + if (!empty($dsninfo['options'])) { + $connstr .= ' options=' . $dsninfo['options']; + } + if (!empty($dsninfo['tty'])) { + $connstr .= ' tty=' . $dsninfo['tty']; + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + // catch error + ob_start(); + $conn = $connect_function($connstr); + $error = ob_get_contents(); + ob_end_clean(); + if ($conn == false) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, + null, null, strip_tags($error)); + } + $this->connection = $conn; + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @return bool TRUE on success, FALSE if not connected. + */ + function disconnect() + { + $ret = @pg_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Send a query to PostgreSQL and return the results as a + * PostgreSQL resource identifier. + * + * @param $query the SQL query + * + * @return int returns a valid PostgreSQL result for successful SELECT + * queries, DB_OK for other successful queries. A DB error code + * is returned on failure. + */ + function simpleQuery($query) + { + $ismanip = DB::isManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @pg_exec($this->connection, "begin;"); + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @pg_exec($this->connection, $query); + if (!$result) { + return $this->pgsqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($ismanip) { + $this->affected = @pg_cmdtuples($result); + return DB_OK; + } elseif (preg_match('/^\s*\(?\s*SELECT\s+/si', $query) && + !preg_match('/^\s*\(?\s*SELECT\s+INTO\s/si', $query)) { + /* PostgreSQL commands: + ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, + CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, + GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, + REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, + UNLISTEN, UPDATE, VACUUM + */ + $this->row[$result] = 0; // reset the row counter. + $numrows = $this->numrows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->num_rows[$result] = $numrows; + $this->affected = 0; + return $result; + } else { + $this->affected = 0; + return DB_OK; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal pgsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ errorCode() + + /** + * Map native error codes to DB's portable ones. Requires that + * the DB implementation's constructor fills in the $errorcode_map + * property. + * + * @param $nativecode the native error code, as returned by the backend + * database extension (string or integer) + * + * @return int a portable DB error code, or FALSE if this DB + * implementation has no mapping for the given error code. + */ + + function errorCode($errormsg) + { + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/(Table does not exist\.|Relation [\"\'].*[\"\'] does not exist|sequence does not exist|class ".+" not found)$/' => DB_ERROR_NOSUCHTABLE, + '/table [\"\'].*[\"\'] does not exist/' => DB_ERROR_NOSUCHTABLE, + '/Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*/' => DB_ERROR_ALREADY_EXISTS, + '/divide by zero$/' => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /' => DB_ERROR_INVALID_NUMBER, + '/ttribute [\"\'].*[\"\'] not found$|Relation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']/' => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near \"/' => DB_ERROR_SYNTAX, + '/referential integrity violation/' => DB_ERROR_CONSTRAINT + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ fetchInto() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param $result PostgreSQL result identifier + * @param $row (reference) array where data from the row is stored + * @param $fetchmode how the array data should be indexed + * @param $rownum the row number to fetch + * + * @return int DB_OK on success, a DB error code on failure + */ + function fetchInto($result, &$row, $fetchmode, $rownum=null) + { + $rownum = ($rownum !== null) ? $rownum : $this->row[$result]; + if ($rownum >= $this->num_rows[$result]) { + return null; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $row = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); + } else { + $row = @pg_fetch_row($result, $rownum); + } + if (!$row) { + $err = pg_errormessage($this->connection); + if (!$err) { + return null; + } + return $this->pgsqlRaiseError(); + } + $this->row[$result] = ++$rownum; + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Free the internal resources associated with $result. + * + * @param $result int PostgreSQL result identifier or DB statement identifier + * + * @return bool TRUE on success, FALSE if $result is invalid + */ + function freeResult($result) + { + if (is_resource($result)) { + return @pg_freeresult($result); + } + if (!isset($this->prepare_tokens[(int)$result])) { + return false; + } + unset($this->prepare_tokens[(int)$result]); + unset($this->prepare_types[(int)$result]); + unset($this->row[(int)$result]); + unset($this->num_rows[(int)$result]); + $this->affected = 0; + return true; + } + + // }}} + // {{{ quote() + /** + * Quote the given string so it can be safely used within string delimiters + * in a query. + * @param $string mixed Data to be quoted + * @return mixed "NULL" string, quoted string or original data + */ + function quote($str = null) + { + switch (strtolower(gettype($str))) { + case 'null': + return 'NULL'; + case 'integer': + case 'double' : + return $str; + case 'boolean': + return $str ? 'TRUE' : 'FALSE'; + case 'string': + default: + $str = str_replace("'", "''", $str); + //PostgreSQL treats a backslash as an escape character. + $str = str_replace('\\', '\\\\', $str); + return "'$str'"; + } + } + // }}} + // {{{ numCols() + + /** + * Get the number of columns in a result set. + * + * @param $result resource PostgreSQL result identifier + * + * @return int the number of columns per row in $result + */ + function numCols($result) + { + $cols = @pg_numfields($result); + if (!$cols) { + return $this->pgsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Get the number of rows in a result set. + * + * @param $result resource PostgreSQL result identifier + * + * @return int the number of rows in $result + */ + function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ errorNative() + + /** + * Get the native error code of the last error (if any) that + * occured on the current connection. + * + * @return int native PostgreSQL error code + */ + function errorNative() + { + return pg_errormessage($this->connection); + } + + // }}} + // {{{ autoCommit() + + /** + * Enable/disable automatic commits + */ + function autoCommit($onoff = false) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the current transaction. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + // (disabled) hack to shut up error messages from libpq.a + //@fclose(@fopen("php://stderr", "w")); + $result = @pg_exec($this->connection, "end;"); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Roll back (undo) the current transaction. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @pg_exec($this->connection, "abort;"); + $this->transaction_opcount = 0; + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Gets the number of rows affected by the last query. + * if the last query was a select, returns 0. + * + * @return int number of rows affected by the last query or DB_ERROR + */ + function affectedRows() + { + return $this->affected; + } + // }}} + // {{{ nextId() + + /** + * Get the next value in a sequence. + * + * We are using native PostgreSQL sequences. If a sequence does + * not exist, it will be created, unless $ondemand is false. + * + * @access public + * @param string $seq_name the name of the sequence + * @param bool $ondemand whether to create the sequence on demand + * @return a sequence integer, or a DB error + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("SELECT NEXTVAL('${seqname}')"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Create the sequence + * + * @param string $seq_name the name of the sequence + * @return mixed DB_OK on success or DB error on error + * @access public + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $result = $this->query("CREATE SEQUENCE ${seqname}"); + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Drop a sequence + * + * @param string $seq_name the name of the sequence + * @return mixed DB_OK on success or DB error on error + * @access public + */ + function dropSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + return $this->query("DROP SEQUENCE ${seqname}"); + } + + // }}} + // {{{ modifyLimitQuery() + + function modifyLimitQuery($query, $from, $count) + { + $query = $query . " LIMIT $count OFFSET $from"; + return $query; + } + + // }}} + // {{{ pgsqlRaiseError() + + function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $err = $this->errorCode($native); + } else { + $err = $errno; + } + return $this->raiseError($err, null, null, null, $native); + } + + // }}} + // {{{ _pgFieldFlags() + + /** + * Flags of a Field + * + * @param int $resource PostgreSQL result identifier + * @param int $num_field the field number + * + * @return string The flags of the field ("not_null", "default_xx", "primary_key", + * "unique" and "multiple_key" are supported) + * @access private + */ + function _pgFieldFlags($resource, $num_field, $table_name) + { + $field_name = @pg_fieldname($resource, $num_field); + + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef + FROM pg_attribute f, pg_class tab, pg_type typ + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + if (@pg_numrows($result) > 0) { + $row = @pg_fetch_row($result, 0); + $flags = ($row[0] == 't') ? 'not_null ' : ''; + + if ($row[1] == 't') { + $result = @pg_exec($this->connection, "SELECT a.adsrc + FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid + AND f.attrelid = a.adrelid AND f.attname = '$field_name' + AND tab.relname = '$table_name' AND f.attnum = a.adnum"); + $row = @pg_fetch_row($result, 0); + $num = str_replace('\'', '', $row[0]); + + $flags .= "default_$num "; + } + } + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey + FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attrelid = i.indrelid + AND f.attname = '$field_name' + AND tab.relname = '$table_name'"); + $count = @pg_numrows($result); + + for ($i = 0; $i < $count ; $i++) { + $row = @pg_fetch_row($result, $i); + $keys = explode(" ", $row[2]); + + if (in_array($num_field + 1, $keys)) { + $flags .= ($row[0] == 't') ? 'unique ' : ''; + $flags .= ($row[1] == 't') ? 'primary ' : ''; + if (count($keys) > 1) + $flags .= 'multiple_key '; + } + } + + return trim($flags); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: doesn't support table name and flags if called from a db_result + * + * @param mixed $resource PostgreSQL result identifier or table name + * @param int $mode A valid tableInfo mode (DB_TABLEINFO_ORDERTABLE or + * DB_TABLEINFO_ORDER) + * + * @return array An array with all the information + */ + function tableInfo($result, $mode = null) + { + $count = 0; + $id = 0; + $res = array(); + + /* + * depending on $mode, metadata returns the following values: + * + * - mode is false (default): + * $result[]: + * [0]["table"] table name + * [0]["name"] field name + * [0]["type"] field type + * [0]["len"] field length + * [0]["flags"] field flags + * + * - mode is DB_TABLEINFO_ORDER + * $result[]: + * ["num_fields"] number of metadata records + * [0]["table"] table name + * [0]["name"] field name + * [0]["type"] field type + * [0]["len"] field length + * [0]["flags"] field flags + * ["order"][field name] index of field named "field name" + * The last one is used, if you have a field name, but no index. + * Test: if (isset($result['meta']['myfield'])) { ... + * + * - mode is DB_TABLEINFO_ORDERTABLE + * the same as above. but additionally + * ["ordertable"][table name][field name] index of field + * named "field name" + * + * this is, because if you have fields from different + * tables with the same field name * they override each + * other with DB_TABLEINFO_ORDER + * + * you can combine DB_TABLEINFO_ORDER and + * DB_TABLEINFO_ORDERTABLE with DB_TABLEINFO_ORDER | + * DB_TABLEINFO_ORDERTABLE * or with DB_TABLEINFO_FULL + */ + + // if $result is a string, then we want information about a + // table without a resultset + + if (is_string($result)) { + $id = @pg_exec($this->connection,"SELECT * FROM $result LIMIT 0"); + if (empty($id)) { + return $this->pgsqlRaiseError(); + } + } else { // else we want information about a resultset + $id = $result; + if (empty($id)) { + return $this->pgsqlRaiseError(); + } + } + + $count = @pg_numfields($id); + + // made this IF due to performance (one if is faster than $count if's) + if (empty($mode)) { + + for ($i=0; $i<$count; $i++) { + $res[$i]['table'] = (is_string($result)) ? $result : ''; + $res[$i]['name'] = @pg_fieldname ($id, $i); + $res[$i]['type'] = @pg_fieldtype ($id, $i); + $res[$i]['len'] = @pg_fieldsize ($id, $i); + $res[$i]['flags'] = (is_string($result)) ? $this->_pgFieldflags($id, $i, $result) : ''; + } + + } else { // full + $res["num_fields"]= $count; + + for ($i=0; $i<$count; $i++) { + $res[$i]['table'] = (is_string($result)) ? $result : ''; + $res[$i]['name'] = @pg_fieldname ($id, $i); + $res[$i]['type'] = @pg_fieldtype ($id, $i); + $res[$i]['len'] = @pg_fieldsize ($id, $i); + $res[$i]['flags'] = (is_string($result)) ? $this->_pgFieldFlags($id, $i, $result) : ''; + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + } + + // free the result only if we were called on a table + if (is_string($result) && is_resource($id)) { + @pg_freeresult($id); + } + return $res; + } + + // }}} + // {{{ getTablesQuery() + + /** + * Returns the query needed to get some backend info + * @param string $type What kind of info you want to retrieve + * @return string The SQL query string + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': { + $sql = "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_' + 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_'"; + break; + } + case 'views': { + // Table cols: viewname | viewowner | definition + $sql = "SELECT viewname FROM pg_views"; + break; + } + case 'users': { + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil + $sql = 'SELECT usename FROM pg_user'; + break; + } + case 'databases': { + $sql = 'SELECT datname FROM pg_database'; + break; + } + case 'functions': { + $sql = 'SELECT proname FROM pg_proc'; + break; + } + default: + return null; + } + return $sql; + } + + // }}} + +} + +// Local variables: +// tab-width: 4 +// c-basic-offset: 4 +// End: +?>