diff options
Diffstat (limited to 'extlib/DB')
-rw-r--r-- | extlib/DB/DataObject/Cast.php | 546 | ||||
-rw-r--r-- | extlib/DB/DataObject/Error.php | 53 | ||||
-rw-r--r-- | extlib/DB/DataObject/Generator.php | 1553 | ||||
-rwxr-xr-x | extlib/DB/DataObject/createTables.php | 59 | ||||
-rw-r--r-- | extlib/DB/common.php | 2262 | ||||
-rw-r--r-- | extlib/DB/dbase.php | 510 | ||||
-rw-r--r-- | extlib/DB/fbsql.php | 769 | ||||
-rw-r--r-- | extlib/DB/ibase.php | 1082 | ||||
-rw-r--r-- | extlib/DB/ifx.php | 683 | ||||
-rw-r--r-- | extlib/DB/msql.php | 831 | ||||
-rw-r--r-- | extlib/DB/mssql.php | 963 | ||||
-rw-r--r-- | extlib/DB/mysql.php | 1045 | ||||
-rw-r--r-- | extlib/DB/mysqli.php | 1092 | ||||
-rw-r--r-- | extlib/DB/oci8.php | 1156 | ||||
-rw-r--r-- | extlib/DB/odbc.php | 883 | ||||
-rw-r--r-- | extlib/DB/pgsql.php | 1135 | ||||
-rw-r--r-- | extlib/DB/sqlite.php | 960 | ||||
-rw-r--r-- | extlib/DB/storage.php | 506 | ||||
-rw-r--r-- | extlib/DB/sybase.php | 942 |
19 files changed, 17030 insertions, 0 deletions
diff --git a/extlib/DB/DataObject/Cast.php b/extlib/DB/DataObject/Cast.php new file mode 100644 index 000000000..616abb55e --- /dev/null +++ b/extlib/DB/DataObject/Cast.php @@ -0,0 +1,546 @@ +<?php +/** + * Prototype Castable Object.. for DataObject queries + * + * Storage for Data that may be cast into a variety of formats. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB_DataObject + * @author Alan Knowles <alan@akbkhome.com> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Cast.php,v 1.15 2005/07/07 05:30:53 alan_k Exp $ + * @link http://pear.php.net/package/DB_DataObject + */ + +/** +* +* Common usages: +* // blobs +* $data = DB_DataObject_Cast::blob($somefile); +* $data = DB_DataObject_Cast::string($somefile); +* $dataObject->someblobfield = $data +* +* // dates? +* $d1 = new DB_DataObject_Cast::date('12/12/2000'); +* $d2 = new DB_DataObject_Cast::date(2000,12,30); +* $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30); +* +* // time, datetime.. ????????? +* +* // raw sql???? +* $data = DB_DataObject_Cast::sql('cast("123123",datetime)'); +* $data = DB_DataObject_Cast::sql('NULL'); +* +* // int's/string etc. are proably pretty pointless..!!!! +* +* +* inside DB_DataObject, +* if (is_a($v,'db_dataobject_class')) { +* $value .= $v->toString(DB_DATAOBJECT_INT,'mysql'); +* } +* +* +* +* + +*/ +class DB_DataObject_Cast { + + /** + * Type of data Stored in the object.. + * + * @var string (date|blob|.....?) + * @access public + */ + var $type; + + /** + * Data For date representation + * + * @var int day/month/year + * @access public + */ + var $day; + var $month; + var $year; + + + /** + * Generic Data.. + * + * @var string + * @access public + */ + + var $value; + + + + /** + * Blob consructor + * + * create a Cast object from some raw data.. (binary) + * + * + * @param string (with binary data!) + * + * @return object DB_DataObject_Cast + * @access public + */ + + function blob($value) { + $r = new DB_DataObject_Cast; + $r->type = 'blob'; + $r->value = $value; + return $r; + } + + + /** + * String consructor (actually use if for ints and everything else!!! + * + * create a Cast object from some string (not binary) + * + * + * @param string (with binary data!) + * + * @return object DB_DataObject_Cast + * @access public + */ + + function string($value) { + $r = new DB_DataObject_Cast; + $r->type = 'string'; + $r->value = $value; + return $r; + } + + /** + * SQL constructor (for raw SQL insert) + * + * create a Cast object from some sql + * + * @param string (with binary data!) + * + * @return object DB_DataObject_Cast + * @access public + */ + + function sql($value) + { + $r = new DB_DataObject_Cast; + $r->type = 'sql'; + $r->value = $value; + return $r; + } + + + /** + * Date Constructor + * + * create a Cast object from some string (not binary) + * NO VALIDATION DONE, although some crappy re-calcing done! + * + * @param vargs... accepts + * dd/mm + * dd/mm/yyyy + * yyyy-mm + * yyyy-mm-dd + * array(yyyy,dd) + * array(yyyy,dd,mm) + * + * + * + * @return object DB_DataObject_Cast + * @access public + */ + + function date() + { + $args = func_get_args(); + switch(count($args)) { + case 0: // no args = today! + $bits = explode('-',date('Y-m-d')); + break; + case 1: // one arg = a string + + if (strpos($args[0],'/') !== false) { + $bits = array_reverse(explode('/',$args[0])); + } else { + $bits = explode('-',$args[0]); + } + break; + default: // 2 or more.. + $bits = $args; + } + if (count($bits) == 1) { // if YYYY set day = 1st.. + $bits[] = 1; + } + + if (count($bits) == 2) { // if YYYY-DD set day = 1st.. + $bits[] = 1; + } + + // if year < 1970 we cant use system tools to check it... + // so we make a few best gueses.... + // basically do date calculations for the year 2000!!! + // fix me if anyone has more time... + if (($bits[0] < 1975) || ($bits[0] > 2030)) { + $oldyear = $bits[0]; + $bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],2000))); + $bits[0] = ($bits[0] - 2000) + $oldyear; + } else { + // now mktime + $bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],$bits[0]))); + } + $r = new DB_DataObject_Cast; + $r->type = 'date'; + list($r->year,$r->month,$r->day) = $bits; + return $r; + } + + + + /** + * Data For time representation ** does not handle timezones!! + * + * @var int hour/minute/second + * @access public + */ + var $hour; + var $minute; + var $second; + + + /** + * DateTime Constructor + * + * create a Cast object from a Date/Time + * Maybe should accept a Date object.! + * NO VALIDATION DONE, although some crappy re-calcing done! + * + * @param vargs... accepts + * noargs (now) + * yyyy-mm-dd HH:MM:SS (Iso) + * array(yyyy,mm,dd,HH,MM,SS) + * + * + * @return object DB_DataObject_Cast + * @access public + * @author therion 5 at hotmail + */ + + function dateTime() + { + $args = func_get_args(); + switch(count($args)) { + case 0: // no args = now! + $datetime = date('Y-m-d G:i:s', mktime()); + + case 1: + // continue on from 0 args. + if (!isset($datetime)) { + $datetime = $args[0]; + } + + $parts = explode(' ', $datetime); + $bits = explode('-', $parts[0]); + $bits = array_merge($bits, explode(':', $parts[1])); + break; + + default: // 2 or more.. + $bits = $args; + + } + + if (count($bits) != 6) { + // PEAR ERROR? + return false; + } + + $r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]); + if (!$r) { + return $r; // pass thru error (False) - doesnt happen at present! + } + // change the type! + $r->type = 'datetime'; + + // should we mathematically sort this out.. + // (or just assume that no-one's dumb enough to enter 26:90:90 as a time! + $r->hour = $bits[3]; + $r->minute = $bits[4]; + $r->second = $bits[5]; + return $r; + + } + + + + /** + * time Constructor + * + * create a Cast object from a Date/Time + * Maybe should accept a Date object.! + * NO VALIDATION DONE, and no-recalcing done! + * + * @param vargs... accepts + * noargs (now) + * HH:MM:SS (Iso) + * array(HH,MM,SS) + * + * + * @return object DB_DataObject_Cast + * @access public + * @author therion 5 at hotmail + */ + function time() + { + $args = func_get_args(); + switch (count($args)) { + case 0: // no args = now! + $time = date('G:i:s', mktime()); + + case 1: + // continue on from 0 args. + if (!isset($time)) { + $time = $args[0]; + } + $bits = explode(':', $time); + break; + + default: // 2 or more.. + $bits = $args; + + } + + if (count($bits) != 3) { + return false; + } + + // now take data from bits into object fields + $r = new DB_DataObject_Cast; + $r->type = 'time'; + $r->hour = $bits[0]; + $r->minute = $bits[1]; + $r->second = $bits[2]; + return $r; + + } + + + + /** + * get the string to use in the SQL statement for this... + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ + + function toString($to=false,$db) + { + // if $this->type is not set, we are in serious trouble!!!! + // values for to: + $method = 'toStringFrom'.$this->type; + return $this->$method($to,$db); + } + + /** + * get the string to use in the SQL statement from a blob of binary data + * ** Suppots only blob->postgres::bytea + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ + function toStringFromBlob($to,$db) + { + // first weed out invalid casts.. + // in blobs can only be cast to blobs.! + + // perhaps we should support TEXT fields??? + + if (!($to & DB_DATAOBJECT_BLOB)) { + return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!'); + } + + switch ($db->dsn["phptype"]) { + case 'pgsql': + return "'".pg_escape_bytea($this->value)."'::bytea"; + + case 'mysql': + return "'".mysql_real_escape_string($this->value,$db->connection)."'"; + + case 'mysqli': + // this is funny - the parameter order is reversed ;) + return "'".mysqli_real_escape_string($db->connection, $this->value)."'"; + + + + default: + return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); + } + + } + + /** + * get the string to use in the SQL statement for a blob from a string! + * ** Suppots only string->postgres::bytea + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ + function toStringFromString($to,$db) + { + // first weed out invalid casts.. + // in blobs can only be cast to blobs.! + + // perhaps we should support TEXT fields??? + // + + if (!($to & DB_DATAOBJECT_BLOB)) { + return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a blob!'. + ' (why not just use native features)'); + } + + switch ($db->dsn['phptype']) { + case 'pgsql': + return "'".pg_escape_string($this->value)."'::bytea"; + + case 'mysql': + return "'".mysql_real_escape_string($this->value,$db->connection)."'"; + + + case 'mysqli': + return "'".mysqli_real_escape_string($db->connection, $this->value)."'"; + + + default: + return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet"); + } + + } + + + /** + * get the string to use in the SQL statement for a date + * + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ + function toStringFromDate($to,$db) + { + // first weed out invalid casts.. + // in blobs can only be cast to blobs.! + // perhaps we should support TEXT fields??? + // + + if (($to !== false) && !($to & DB_DATAOBJECT_DATE)) { + return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!'. + ' (why not just use native features)'); + } + return "'{$this->year}-{$this->month}-{$this->day}'"; + } + + /** + * get the string to use in the SQL statement for a datetime + * + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + * @author therion 5 at hotmail + */ + + function toStringFromDateTime($to,$db) + { + // first weed out invalid casts.. + // in blobs can only be cast to blobs.! + // perhaps we should support TEXT fields??? + if (($to !== false) && + !($to & (DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME))) { + return PEAR::raiseError('Invalid Cast from a ' . + ' DB_DataObject_Cast::dateTime to something other than a datetime!' . + ' (try using native features)'); + } + return "'{$this->year}-{$this->month}-{$this->day} {$this->hour}:{$this->minute}:{$this->second}'"; + } + + /** + * get the string to use in the SQL statement for a time + * + * + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + * @author therion 5 at hotmail + */ + + function toStringFromTime($to,$db) + { + // first weed out invalid casts.. + // in blobs can only be cast to blobs.! + // perhaps we should support TEXT fields??? + if (($to !== false) && !($to & DB_DATAOBJECT_TIME)) { + return PEAR::raiseError('Invalid Cast from a' . + ' DB_DataObject_Cast::time to something other than a time!'. + ' (try using native features)'); + } + return "'{$this->hour}:{$this->minute}:{$this->second}'"; + } + + /** + * get the string to use in the SQL statement for a raw sql statement. + * + * @param int $to Type (DB_DATAOBJECT_* + * @param object $db DB Connection Object + * + * + * @return string + * @access public + */ + function toStringFromSql($to,$db) + { + return $this->value; + } + + + + +} + diff --git a/extlib/DB/DataObject/Error.php b/extlib/DB/DataObject/Error.php new file mode 100644 index 000000000..05a741408 --- /dev/null +++ b/extlib/DB/DataObject/Error.php @@ -0,0 +1,53 @@ +<?php +/** + * DataObjects error handler, loaded on demand... + * + * DB_DataObject_Error is a quick wrapper around pear error, so you can distinguish the + * error code source. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB_DataObject + * @author Alan Knowles <alan@akbkhome.com> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Error.php,v 1.3 2005/03/23 02:35:35 alan_k Exp $ + * @link http://pear.php.net/package/DB_DataObject + */ + + +class DB_DataObject_Error extends PEAR_Error +{ + + /** + * DB_DataObject_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_DataObject_Error($message = '', $code = DB_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE) + { + $this->PEAR_Error('DB_DataObject Error: ' . $message, $code, $mode, $level); + + } + + + // todo : - support code -> message handling, and translated error messages... + + + +} diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php new file mode 100644 index 000000000..de16af692 --- /dev/null +++ b/extlib/DB/DataObject/Generator.php @@ -0,0 +1,1553 @@ +<?php +/** + * Generation tools for DB_DataObject + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB_DataObject + * @author Alan Knowles <alan@akbkhome.com> + * @copyright 1997-2006 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: Generator.php,v 1.141 2008/01/30 02:29:39 alan_k Exp $ + * @link http://pear.php.net/package/DB_DataObject + */ + + /* + * Security Notes: + * This class uses eval to create classes on the fly. + * The table name and database name are used to check the database before writing the + * class definitions, we now check for quotes and semi-colon's in both variables + * so I cant see how it would be possible to generate code even if + * for some crazy reason you took the classname and table name from User Input. + * + * If you consider that wrong, or can prove it.. let me know! + */ + + /** + * + * Config _$ptions + * [DB_DataObject_Generator] + * ; optional default = DB/DataObject.php + * extends_location = + * ; optional default = DB_DataObject + * extends = + * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject) + * generator_class_rewrite = ANY|specific_name // default is DB_DataObject + * + */ + +/** + * Needed classes + * We lazy load here, due to problems with the tests not setting up include path correctly. + * FIXME! + */ +class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php'; +//require_once('Config.php'); + +/** + * Generator class + * + * @package DB_DataObject + */ +class DB_DataObject_Generator extends DB_DataObject +{ + /* =========================================================== */ + /* Utility functions - for building db config files */ + /* =========================================================== */ + + /** + * Array of table names + * + * @var array + * @access private + */ + var $tables; + + /** + * associative array table -> array of table row objects + * + * @var array + * @access private + */ + var $_definitions; + + /** + * active table being output + * + * @var string + * @access private + */ + var $table; // active tablename + + + /** + * The 'starter' = call this to start the process + * + * @access public + * @return none + */ + function start() + { + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + + $databases = array(); + foreach($options as $k=>$v) { + if (substr($k,0,9) == 'database_') { + $databases[substr($k,9)] = $v; + } + } + + if (isset($options['database'])) { + if ($db_driver == 'DB') { + require_once 'DB.php'; + $dsn = DB::parseDSN($options['database']); + } else { + require_once 'MDB2.php'; + $dsn = MDB2::parseDSN($options['database']); + } + + if (!isset($database[$dsn['database']])) { + $databases[$dsn['database']] = $options['database']; + } + } + + foreach($databases as $databasename => $database) { + if (!$database) { + continue; + } + $this->debug("CREATING FOR $databasename\n"); + $class = get_class($this); + $t = new $class; + $t->_database_dsn = $database; + + + $t->_database = $databasename; + if ($db_driver == 'DB') { + require_once 'DB.php'; + $dsn = DB::parseDSN($database); + } else { + require_once 'MDB2.php'; + $dsn = MDB2::parseDSN($database); + } + + if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) { + $t->_database = basename($t->_database); + } + $t->_createTableList(); + + foreach(get_class_methods($class) as $method) { + if (substr($method,0,8 ) != 'generate') { + continue; + } + $this->debug("calling $method"); + $t->$method(); + } + } + $this->debug("DONE\n\n"); + } + + /** + * Output File was config object, now just string + * Used to generate the Tables + * + * @var string outputbuffer for table definitions + * @access private + */ + var $_newConfig; + + /** + * Build a list of tables; + * and store it in $this->tables and $this->_definitions[tablename]; + * + * @access private + * @return none + */ + function _createTableList() + { + $this->_connect(); + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + + $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + $is_MDB2 = ($db_driver != 'DB') ? true : false; + + if (is_a($__DB , 'PEAR_Error')) { + return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE); + } + + if (!$is_MDB2) { + // try getting a list of schema tables first. (postgres) + $__DB->expectError(DB_ERROR_UNSUPPORTED); + $this->tables = $__DB->getListOf('schema.tables'); + $__DB->popExpect(); + } else { + /** + * set portability and some modules to fetch the informations + */ + $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); + $__DB->loadModule('Manager'); + $__DB->loadModule('Reverse'); + } + + if ((empty($this->tables) || is_a($this->tables , 'PEAR_Error'))) { + //if that fails fall back to clasic tables list. + if (!$is_MDB2) { + // try getting a list of schema tables first. (postgres) + $__DB->expectError(DB_ERROR_UNSUPPORTED); + $this->tables = $__DB->getListOf('tables'); + $__DB->popExpect(); + } else { + $this->tables = $__DB->manager->listTables(); + $sequences = $__DB->manager->listSequences(); + foreach ($sequences as $k => $v) { + $this->tables[] = $__DB->getSequenceName($v); + } + } + } + + if (is_a($this->tables , 'PEAR_Error')) { + return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE); + } + + // build views as well if asked to. + if (!empty($options['build_views'])) { + if (!$is_MDB2) { + $views = $__DB->getListOf('views'); + } else { + $views = $__DB->manager->listViews(); + } + if (is_a($views,'PEAR_Error')) { + return PEAR::raiseError( + 'Error getting Views (check the PEAR bug database for the fix to DB), ' . + $views->toString(), + null, + PEAR_ERROR_DIE + ); + } + $this->tables = array_merge ($this->tables, $views); + } + + // declare a temporary table to be filled with matching tables names + $tmp_table = array(); + + + foreach($this->tables as $table) { + if (isset($options['generator_include_regex']) && + !preg_match($options['generator_include_regex'],$table)) { + continue; + } else if (isset($options['generator_exclude_regex']) && + preg_match($options['generator_exclude_regex'],$table)) { + continue; + } + // postgres strip the schema bit from the + if (!empty($options['generator_strip_schema'])) { + $bits = explode('.', $table,2); + $table = $bits[0]; + if (count($bits) > 1) { + $table = $bits[1]; + } + } + $quotedTable = !empty($options['quote_identifiers_tableinfo']) ? + $__DB->quoteIdentifier($table) : $table; + + if (!$is_MDB2) { + + $defs = $__DB->tableInfo($quotedTable); + } else { + $defs = $__DB->reverse->tableInfo($quotedTable); + // rename the length value, so it matches db's return. + foreach ($defs as $k => $v) { + if (!isset($defs[$k]['length'])) { + continue; + } + $defs[$k]['len'] = $defs[$k]['length']; + } + } + + if (is_a($defs,'PEAR_Error')) { + // running in debug mode should pick this up as a big warning.. + $this->raiseError('Error reading tableInfo, '. $defs->toString()); + continue; + } + // cast all definitions to objects - as we deal with that better. + + + + foreach($defs as $def) { + if (!is_array($def)) { + continue; + } + + $this->_definitions[$table][] = (object) $def; + + } + // we find a matching table, just store it into a temporary array + $tmp_table[] = $table; + + + } + // the temporary table array is now the right one (tables names matching + // with regex expressions have been removed) + $this->tables = $tmp_table; + //print_r($this->_definitions); + } + + /** + * Auto generation of table data. + * + * it will output to db_oo_{database} the table definitions + * + * @access private + * @return none + */ + function generateDefinitions() + { + $this->debug("Generating Definitions file: "); + if (!$this->tables) { + $this->debug("-- NO TABLES -- \n"); + return; + } + + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + + + //$this->_newConfig = new Config('IniFile'); + $this->_newConfig = ''; + foreach($this->tables as $this->table) { + $this->_generateDefinitionsTable(); + } + $this->_connect(); + // dont generate a schema if location is not set + // it's created on the fly! + if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) { + return; + } + if (!empty($options['generator_no_ini'])) { // built in ini files.. + return; + } + $base = @$options['schema_location']; + if (isset($options["ini_{$this->_database}"])) { + $file = $options["ini_{$this->_database}"]; + } else { + $file = "{$base}/{$this->_database}.ini"; + } + + if (!file_exists(dirname($file))) { + require_once 'System.php'; + System::mkdir(array('-p','-m',0755,dirname($file))); + } + $this->debug("Writing ini as {$file}\n"); + //touch($file); + $tmpname = tempnam(session_save_path(),'DataObject_'); + //print_r($this->_newConfig); + $fh = fopen($tmpname,'w'); + fwrite($fh,$this->_newConfig); + fclose($fh); + $perms = file_exists($file) ? fileperms($file) : 0755; + // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. + + if (!@rename($tmpname, $file)) { + unlink($file); + rename($tmpname, $file); + } + chmod($file,$perms); + //$ret = $this->_newConfig->writeInput($file,false); + + //if (PEAR::isError($ret) ) { + // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE); + // } + } + + /** + * generate Foreign Keys (for links.ini) + * Currenly only works with mysql / mysqli + * to use, you must set option: generate_links=true + * + * @author Pascal Schöni + */ + function generateForeignKeys() + { + $options = PEAR::getStaticProperty('DB_DataObject','options'); + if (empty($options['generate_links'])) { + return false; + } + $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + if (!in_array($__DB->phptype, array('mysql','mysqli'))) { + echo "WARNING: cant handle non-mysql introspection for defaults."; + return; // cant handle non-mysql introspection for defaults. + } + + $DB = $this->getDatabaseConnection(); + + $fk = array(); + + foreach($this->tables as $this->table) { + $res =& $DB->query('SHOW CREATE TABLE ' . $this->table); + if (PEAR::isError($res)) { + die($res->getMessage()); + } + + $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0); + $treffer = array(); + // Extract FOREIGN KEYS + preg_match_all( + "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i", + $text[1], + $treffer, + PREG_SET_ORDER); + + if (count($treffer) < 1) { + continue; + } + for ($i = 0; $i < count($treffer); $i++) { + $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3]; + } + + } + + $links_ini = ""; + + foreach($fk as $table => $details) { + $links_ini .= "[$table]\n"; + foreach ($details as $col => $ref) { + $links_ini .= "$col = $ref\n"; + } + $links_ini .= "\n"; + } + + // dont generate a schema if location is not set + // it's created on the fly! + $options = PEAR::getStaticProperty('DB_DataObject','options'); + + if (empty($options['schema_location'])) { + return; + } + + + $file = "{$options['schema_location']}/{$this->_database}.links.ini"; + + if (!file_exists(dirname($file))) { + require_once 'System.php'; + System::mkdir(array('-p','-m',0755,dirname($file))); + } + + $this->debug("Writing ini as {$file}\n"); + + //touch($file); // not sure why this is needed? + $tmpname = tempnam(session_save_path(),'DataObject_'); + + $fh = fopen($tmpname,'w'); + fwrite($fh,$links_ini); + fclose($fh); + $perms = file_exists($file) ? fileperms($file) : 0755; + // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. + if (!@rename($tmpname, $file)) { + unlink($file); + rename($tmpname, $file); + } + chmod($file, $perms); + } + + + /** + * The table geneation part + * + * @access private + * @return tabledef and keys array. + */ + function _generateDefinitionsTable() + { + global $_DB_DATAOBJECT; + + $defs = $this->_definitions[$this->table]; + $this->_newConfig .= "\n[{$this->table}]\n"; + $keys_out = "\n[{$this->table}__keys]\n"; + $keys_out_primary = ''; + $keys_out_secondary = ''; + if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { + echo "TABLE STRUCTURE FOR {$this->table}\n"; + print_r($defs); + } + $DB = $this->getDatabaseConnection(); + $dbtype = $DB->phptype; + + $ret = array( + 'table' => array(), + 'keys' => array(), + ); + + $ret_keys_primary = array(); + $ret_keys_secondary = array(); + + + + foreach($defs as $t) { + + $n=0; + $write_ini = true; + + + switch (strtoupper($t->type)) { + + case 'INT': + case 'INT2': // postgres + case 'INT4': // postgres + case 'INT8': // postgres + case 'SERIAL4': // postgres + case 'SERIAL8': // postgres + case 'INTEGER': + case 'TINYINT': + case 'SMALLINT': + case 'MEDIUMINT': + case 'BIGINT': + $type = DB_DATAOBJECT_INT; + if ($t->len == 1) { + $type += DB_DATAOBJECT_BOOL; + } + break; + + case 'REAL': + case 'DOUBLE': + case 'DOUBLE PRECISION': // double precision (firebird) + case 'FLOAT': + case 'FLOAT4': // real (postgres) + case 'FLOAT8': // double precision (postgres) + case 'DECIMAL': + case 'MONEY': // mssql and maybe others + case 'NUMERIC': + case 'NUMBER': // oci8 + $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY... + break; + + case 'YEAR': + $type = DB_DATAOBJECT_INT; + break; + + case 'BIT': + case 'BOOL': + case 'BOOLEAN': + + $type = DB_DATAOBJECT_BOOL; + // postgres needs to quote '0' + if ($dbtype == 'pgsql') { + $type += DB_DATAOBJECT_STR; + } + break; + + case 'STRING': + case 'CHAR': + case 'VARCHAR': + case 'VARCHAR2': + case 'TINYTEXT': + + case 'ENUM': + case 'SET': // not really but oh well + case 'TIMESTAMPTZ': // postgres + case 'BPCHAR': // postgres + case 'INTERVAL': // postgres (eg. '12 days') + + case 'CIDR': // postgres IP net spec + case 'INET': // postgres IP + case 'MACADDR': // postgress network Mac address. + + case 'INTEGER[]': // postgres type + case 'BOOLEAN[]': // postgres type + + $type = DB_DATAOBJECT_STR; + break; + + case 'TEXT': + case 'MEDIUMTEXT': + case 'LONGTEXT': + + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT; + break; + + + case 'DATE': + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE; + break; + + case 'TIME': + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME; + break; + + + case 'DATETIME': + + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; + break; + + case 'TIMESTAMP': // do other databases use this??? + + $type = ($dbtype == 'mysql') ? + DB_DATAOBJECT_MYSQLTIMESTAMP : + DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME; + break; + + + case 'TINYBLOB': + case 'BLOB': /// these should really be ignored!!!??? + case 'MEDIUMBLOB': + case 'LONGBLOB': + case 'BYTEA': // postgres blob support.. + $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB; + break; + default: + echo "*****************************************************************\n". + "** WARNING UNKNOWN TYPE **\n". + "** Found column '{$t->name}', of type '{$t->type}' **\n". + "** Please submit a bug, describe what type you expect this **\n". + "** column to be **\n". + "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n". + "** Try using MDB2 as the backend - eg set the config option **\n". + "** db_driver = MDB2 **\n". + "*****************************************************************\n"; + $write_ini = false; + break; + } + + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) { + echo "*****************************************************************\n". + "** WARNING COLUMN NAME UNUSABLE **\n". + "** Found column '{$t->name}', of type '{$t->type}' **\n". + "** Since this column name can't be converted to a php variable **\n". + "** name, and the whole idea of mapping would result in a mess **\n". + "** This column has been ignored... **\n". + "*****************************************************************\n"; + continue; + } + + if (!strlen(trim($t->name))) { + continue; // is this a bug? + } + + if (preg_match('/not[ _]null/i',$t->flags)) { + $type += DB_DATAOBJECT_NOTNULL; + } + + + if (in_array($t->name,array('null','yes','no','true','false'))) { + echo "*****************************************************************\n". + "** WARNING **\n". + "** Found column '{$t->name}', which is invalid in an .ini file **\n". + "** This line will not be writen to the file - you will have **\n". + "** define the keys()/method manually. **\n". + "*****************************************************************\n"; + $write_ini = false; + } else { + $this->_newConfig .= "{$t->name} = $type\n"; + } + + $ret['table'][$t->name] = $type; + // i've no idea if this will work well on other databases? + // only use primary key or nextval(), cause the setFrom blocks you setting all key items... + // if no keys exist fall back to using unique + //echo "\n{$t->name} => {$t->flags}\n"; + if (preg_match("/(auto_increment|nextval\()/i",rawurldecode($t->flags)) + || (isset($t->autoincrement) && ($t->autoincrement === true))) { + + // native sequences = 2 + if ($write_ini) { + $keys_out_primary .= "{$t->name} = N\n"; + } + $ret_keys_primary[$t->name] = 'N'; + + } else if (preg_match("/(primary|unique)/i",$t->flags)) { + // keys.. = 1 + $key_type = 'K'; + if (!preg_match("/(primary)/i",$t->flags)) { + $key_type = 'U'; + } + + if ($write_ini) { + $keys_out_secondary .= "{$t->name} = {$key_type}\n"; + } + $ret_keys_secondary[$t->name] = $key_type; + } + + + } + + $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary); + $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary; + + if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { + print_r(array("dump for {$this->table}", $ret)); + } + + return $ret; + + + } + + /** + * Convert a table name into a class name -> override this if you want a different mapping + * + * @access public + * @return string class name; + */ + + + function getClassNameFromTableName($table) + { + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; + return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table))); + } + + + /** + * Convert a table name into a file name -> override this if you want a different mapping + * + * @access public + * @return string file name; + */ + + + function getFileNameFromTableName($table) + { + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + $base = $options['class_location']; + if (strpos($base,'%s') !== false) { + $base = dirname($base); + } + if (!file_exists($base)) { + require_once 'System.php'; + System::mkdir(array('-p',$base)); + } + if (strpos($options['class_location'],'%s') !== false) { + $outfilename = sprintf($options['class_location'], + preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table))); + } else { + $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php"; + } + return $outfilename; + + } + + + /** + * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX) + * + * @access public + * @return string method name; + */ + + + function getMethodNameFromColumnName($col) + { + return ucfirst($col); + } + + + + + /* + * building the class files + * for each of the tables output a file! + */ + function generateClasses() + { + //echo "Generating Class files: \n"; + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + + + if ($extends = @$options['extends']) { + $this->_extends = $extends; + $this->_extendsFile = $options['extends_location']; + } + + foreach($this->tables as $this->table) { + $this->table = trim($this->table); + $this->classname = $this->getClassNameFromTableName($this->table); + $i = ''; + $outfilename = $this->getFileNameFromTableName($this->table); + + $oldcontents = ''; + if (file_exists($outfilename)) { + // file_get_contents??? + $oldcontents = implode('',file($outfilename)); + } + + $out = $this->_generateClassTable($oldcontents); + $this->debug( "writing $this->classname\n"); + $tmpname = tempnam(session_save_path(),'DataObject_'); + + $fh = fopen($tmpname, "w"); + fputs($fh,$out); + fclose($fh); + $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755; + + // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy.. + if (!@rename($tmpname, $outfilename)) { + unlink($outfilename); + rename($tmpname, $outfilename); + } + + chmod($outfilename, $perms); + } + //echo $out; + } + + /** + * class being extended (can be overridden by [DB_DataObject_Generator] extends=xxxx + * + * @var string + * @access private + */ + var $_extends = 'DB_DataObject'; + + /** + * line to use for require('DB/DataObject.php'); + * + * @var string + * @access private + */ + var $_extendsFile = "DB/DataObject.php"; + + /** + * class being generated + * + * @var string + * @access private + */ + var $_className; + + /** + * The table class geneation part - single file. + * + * @access private + * @return none + */ + function _generateClassTable($input = '') + { + // title = expand me! + $foot = ""; + $head = "<?php\n/**\n * Table Definition for {$this->table}\n"; + $head .= $this->derivedHookPageLevelDocBlock(); + $head .= " */\n"; + $head .= $this->derivedHookExtendsDocBlock(); + + + // requires + $head .= "require_once '{$this->_extendsFile}';\n\n"; + // add dummy class header in... + // class + $head .= $this->derivedHookClassDocBlock(); + $head .= "class {$this->classname} extends {$this->_extends} \n{"; + + $body = "\n ###START_AUTOCODE\n"; + $body .= " /* the code below is auto generated do not remove the above tag */\n\n"; + // table + $padding = (30 - strlen($this->table)); + $padding = ($padding < 2) ? 2 : $padding; + + $p = str_repeat(' ',$padding) ; + + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + + + $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var'; + $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var; + + + $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n"; + + + // if we are using the option database_{databasename} = dsn + // then we should add var $_database = here + // as database names may not always match.. + + + + + if (isset($options["database_{$this->_database}"])) { + $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n"; + } + + + if (!empty($options['generator_novars'])) { + $var = '//'.$var; + } + + $defs = $this->_definitions[$this->table]; + + // show nice information! + $connections = array(); + $sets = array(); + foreach($defs as $t) { + if (!strlen(trim($t->name))) { + continue; + } + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) { + echo "*****************************************************************\n". + "** WARNING COLUMN NAME UNUSABLE **\n". + "** Found column '{$t->name}', of type '{$t->type}' **\n". + "** Since this column name can't be converted to a php variable **\n". + "** name, and the whole idea of mapping would result in a mess **\n". + "** This column has been ignored... **\n". + "*****************************************************************\n"; + continue; + } + + + $padding = (30 - strlen($t->name)); + if ($padding < 2) $padding =2; + $p = str_repeat(' ',$padding) ; + + $body .=" {$var} \${$t->name}; {$p}// {$t->type}({$t->len}) {$t->flags}\n"; + + // can not do set as PEAR::DB table info doesnt support it. + //if (substr($t->Type,0,3) == "set") + // $sets[$t->Field] = "array".substr($t->Type,3); + $body .= $this->derivedHookVar($t,$padding); + } + + // THIS IS TOTALLY BORKED old FC creation + // IT WILL BE REMOVED!!!!! in DataObjects 1.6 + // grep -r __clone * to find all it's uses + // and replace them with $x = clone($y); + // due to the change in the PHP5 clone design. + + if ( substr(phpversion(),0,1) < 5) { + $body .= "\n"; + $body .= " /* ZE2 compatibility trick*/\n"; + $body .= " function __clone() { return \$this;}\n"; + } + + // simple creation tools ! (static stuff!) + $body .= "\n"; + $body .= " /* Static get */\n"; + $body .= " function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n"; + + // generate getter and setter methods + $body .= $this->_generateGetters($input); + $body .= $this->_generateSetters($input); + + /* + theoretically there is scope here to introduce 'list' methods + based up 'xxxx_up' column!!! for heiracitcal trees.. + */ + + // set methods + //foreach ($sets as $k=>$v) { + // $kk = strtoupper($k); + // $body .=" function getSets{$k}() { return {$v}; }\n"; + //} + + if (!empty($options['generator_no_ini'])) { + $def = $this->_generateDefinitionsTable(); // simplify this!? + $body .= $this->_generateTableFunction($def['table']); + $body .= $this->_generateKeysFunction($def['keys']); + $body .= $this->_generateSequenceKeyFunction($def); + $body .= $this->_generateDefaultsFunction($this->table, $def['table']); + } else if (!empty($options['generator_add_defaults'])) { + // I dont really like doing it this way (adding another option) + // but it helps on older projects. + $def = $this->_generateDefinitionsTable(); // simplify this!? + $body .= $this->_generateDefaultsFunction($this->table,$def['table']); + + } + $body .= $this->derivedHookFunctions($input); + + $body .= "\n /* the code above is auto generated do not remove the tag below */"; + $body .= "\n ###END_AUTOCODE\n"; + + + // stubs.. + + if (!empty($options['generator_add_validate_stubs'])) { + foreach($defs as $t) { + if (!strlen(trim($t->name))) { + continue; + } + $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name); + // dont re-add it.. + if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) { + continue; + } + $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n"; + } + } + + + + + $foot .= "}\n"; + $full = $head . $body . $foot; + + if (!$input) { + return $full; + } + if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) { + return $full; + } + if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) { + return $full; + } + + + /* this will only replace extends DB_DataObject by default, + unless use set generator_class_rewrite to ANY or a name*/ + + $class_rewrite = 'DB_DataObject'; + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) { + $class_rewrite = 'DB_DataObject'; + } + if ($class_rewrite == 'ANY') { + $class_rewrite = '[a-z_]+'; + } + + $input = preg_replace( + '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si', + "\nclass {$this->classname} extends {$this->_extends} \n{\n", + $input); + + $ret = preg_replace( + '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', + $body,$input); + + if (!strlen($ret)) { + return PEAR::raiseError( + "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n". + "pcre.backtrack_limit=1000000\n". + "pcre.recursion_limit=1000000\n" + ,null, PEAR_ERROR_DIE); + } + + return $ret; + } + + /** + * hook to add extra methods to all classes + * + * called once for each class, use with $this->table and + * $this->_definitions[$this->table], to get data out of the current table, + * use it to add extra methods to the default classes. + * + * @access public + * @return string added to class eg. functions. + */ + function derivedHookFunctions($input = "") + { + // This is so derived generator classes can generate functions + // It MUST NOT be changed here!!! + return ""; + } + + /** + * hook for var lines + * called each time a var line is generated, override to add extra var + * lines + * + * @param object t containing type,len,flags etc. from tableInfo call + * @param int padding number of spaces + * @access public + * @return string added to class eg. functions. + */ + function derivedHookVar(&$t,$padding) + { + // This is so derived generator classes can generate variabels + // It MUST NOT be changed here!!! + return ""; + } + + /** + * hook to add extra page-level (in terms of phpDocumentor) DocBlock + * + * called once for each class, use it add extra page-level docs + * @access public + * @return string added to class eg. functions. + */ + function derivedHookPageLevelDocBlock() { + return ''; + } + + /** + * hook to add extra doc block (in terms of phpDocumentor) to extend string + * + * called once for each class, use it add extra comments to extends + * string (require_once...) + * @access public + * @return string added to class eg. functions. + */ + function derivedHookExtendsDocBlock() { + return ''; + } + + /** + * hook to add extra class level DocBlock (in terms of phpDocumentor) + * + * called once for each class, use it add extra comments to class + * string (require_once...) + * @access public + * @return string added to class eg. functions. + */ + function derivedHookClassDocBlock() { + return ''; + } + + /** + + /** + * getProxyFull - create a class definition on the fly and instantate it.. + * + * similar to generated files - but also evals the class definitoin code. + * + * + * @param string database name + * @param string table name of table to create proxy for. + * + * + * @return object Instance of class. or PEAR Error + * @access public + */ + function getProxyFull($database,$table) + { + + if ($err = $this->fillTableSchema($database,$table)) { + return $err; + } + + + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix']; + + if ($extends = @$options['extends']) { + $this->_extends = $extends; + $this->_extendsFile = $options['extends_location']; + } + $classname = $this->classname = $this->getClassNameFromTableName($this->table); + + $out = $this->_generateClassTable(); + //echo $out; + eval('?>'.$out); + return new $classname; + + } + + /** + * fillTableSchema - set the database schema on the fly + * + * + * + * @param string database name + * @param string table name of table to create schema info for + * + * @return none | PEAR::error() + * @access public + */ + function fillTableSchema($database,$table) + { + global $_DB_DATAOBJECT; + // a little bit of sanity testing. + if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) { + return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE); + } + + $this->_database = $database; + + $this->_connect(); + $table = trim($table); + + // a little bit of sanity testing. + if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) { + return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE); + } + $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + + + $options = PEAR::getStaticProperty('DB_DataObject','options'); + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + $is_MDB2 = ($db_driver != 'DB') ? true : false; + + if (!$is_MDB2) { + // try getting a list of schema tables first. (postgres) + $__DB->expectError(DB_ERROR_UNSUPPORTED); + $this->tables = $__DB->getListOf('schema.tables'); + $__DB->popExpect(); + } else { + /** + * set portability and some modules to fetch the informations + */ + $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE); + $__DB->loadModule('Manager'); + $__DB->loadModule('Reverse'); + } + $quotedTable = !empty($options['quote_identifiers']) ? + $__DB->quoteIdentifier($table) : $table; + + if (!$is_MDB2) { + $defs = $__DB->tableInfo($quotedTable); + } else { + $defs = $__DB->reverse->tableInfo($quotedTable); + foreach ($defs as $k => $v) { + if (!isset($defs[$k]['length'])) { + continue; + } + $defs[$k]['len'] = $defs[$k]['length']; + } + } + + + + + if (PEAR::isError($defs)) { + return $defs; + } + if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) { + $this->debug("getting def for $database/$table",'fillTable'); + $this->debug(print_r($defs,true),'defs'); + } + // cast all definitions to objects - as we deal with that better. + + + foreach($defs as $def) { + if (is_array($def)) { + $this->_definitions[$table][] = (object) $def; + } + } + + $this->table = trim($table); + $ret = $this->_generateDefinitionsTable(); + + $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table']; + $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys']; + return false; + + } + + /** + * Generate getter methods for class definition + * + * @param string $input Existing class contents + * @return string + * @access public + */ + function _generateGetters($input) + { + + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + $getters = ''; + + // only generate if option is set to true + if (empty($options['generate_getters'])) { + return ''; + } + + // remove auto-generated code from input to be able to check if the method exists outside of the auto-code + $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); + + $getters .= "\n\n"; + $defs = $this->_definitions[$this->table]; + + // loop through properties and create getter methods + foreach ($defs = $defs as $t) { + + // build mehtod name + $methodName = 'get' . $this->getMethodNameFromColumnName($t->name); + + if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { + continue; + } + + $getters .= " /**\n"; + $getters .= " * Getter for \${$t->name}\n"; + $getters .= " *\n"; + $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n" + : " * @return {$t->type}\n"; + $getters .= " * @access public\n"; + $getters .= " */\n"; + $getters .= (substr(phpversion(),0,1) > 4) ? ' public ' + : ' '; + $getters .= "function $methodName() {\n"; + $getters .= " return \$this->{$t->name};\n"; + $getters .= " }\n\n"; + } + + + return $getters; + } + + + /** + * Generate setter methods for class definition + * + * @param string Existing class contents + * @return string + * @access public + */ + function _generateSetters($input) + { + + $options = &PEAR::getStaticProperty('DB_DataObject','options'); + $setters = ''; + + // only generate if option is set to true + if (empty($options['generate_setters'])) { + return ''; + } + + // remove auto-generated code from input to be able to check if the method exists outside of the auto-code + $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input); + + $setters .= "\n"; + $defs = $this->_definitions[$this->table]; + + // loop through properties and create setter methods + foreach ($defs = $defs as $t) { + + // build mehtod name + $methodName = 'set' . $this->getMethodNameFromColumnName($t->name); + + if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) { + continue; + } + + $setters .= " /**\n"; + $setters .= " * Setter for \${$t->name}\n"; + $setters .= " *\n"; + $setters .= " * @param mixed input value\n"; + $setters .= " * @access public\n"; + $setters .= " */\n"; + $setters .= (substr(phpversion(),0,1) > 4) ? ' public ' + : ' '; + $setters .= "function $methodName(\$value) {\n"; + $setters .= " \$this->{$t->name} = \$value;\n"; + $setters .= " }\n\n"; + } + + + return $setters; + } + /** + * Generate table Function - used when generator_no_ini is set. + * + * @param array table array. + * @return string + * @access public + */ + function _generateTableFunction($def) + { + $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP'); + + $ret = "\n" . + " function table()\n" . + " {\n" . + " return array(\n"; + + foreach($def as $k=>$v) { + $str = '0'; + foreach($defines as $dn) { + if ($v & constant('DB_DATAOBJECT_' . $dn)) { + $str .= ' + DB_DATAOBJECT_' . $dn; + } + } + if (strlen($str) > 1) { + $str = substr($str,3); // strip the 0 + + } + // hopefully addslashes is good enough here!!! + $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n"; + } + return $ret . " );\n" . + " }\n"; + + + + } + /** + * Generate keys Function - used generator_no_ini is set. + * + * @param array keys array. + * @return string + * @access public + */ + function _generateKeysFunction($def) + { + + $ret = "\n" . + " function keys()\n" . + " {\n" . + " return array("; + + foreach($def as $k=>$type) { + // hopefully addslashes is good enough here!!! + $ret .= '\''.addslashes($k).'\', '; + } + $ret = preg_replace('#, $#', '', $ret); + return $ret . ");\n" . + " }\n"; + + + + } + /** + * Generate sequenceKey Function - used generator_no_ini is set. + * + * @param array table and key definition. + * @return string + * @access public + */ + function _generateSequenceKeyFunction($def) + { + + //print_r($def); + // DB_DataObject::debugLevel(5); + global $_DB_DATAOBJECT; + // print_r($def); + + + $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype']; + $realkeys = $def['keys']; + $keys = array_keys($realkeys); + $usekey = isset($keys[0]) ? $keys[0] : false; + $table = $def['table']; + + + $seqname = false; + + + + + $ar = array(false,false,false); + if ($usekey !== false) { + if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) { + $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table]; + if (strpos($usekey,':') !== false) { + list($usekey,$seqname) = explode(':',$usekey); + } + } + + if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) && + ($table[$usekey] & DB_DATAOBJECT_INT) && + isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N') + ) { + // use native sequence keys. + $ar = array($usekey,true,$seqname); + } else { + // use generated sequence keys.. + if ($table[$usekey] & DB_DATAOBJECT_INT) { + $ar = array($usekey,false,$seqname); + } + } + } + + + + + $ret = "\n" . + " function sequenceKey() // keyname, use native, native name\n" . + " {\n" . + " return array("; + foreach($ar as $v) { + switch (gettype($v)) { + case 'boolean': + $ret .= ($v ? 'true' : 'false') . ', '; + break; + + case 'string': + $ret .= "'" . $v . "', "; + break; + + default: // eak + $ret .= "null, "; + + } + } + $ret = preg_replace('#, $#', '', $ret); + return $ret . ");\n" . + " }\n"; + + } + /** + * Generate defaults Function - used generator_add_defaults or generator_no_ini is set. + * Only supports mysql and mysqli ... welcome ideas for more.. + * + * + * @param array table and key definition. + * @return string + * @access public + */ + function _generateDefaultsFunction($table,$defs) + { + $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5]; + if (!in_array($__DB->phptype, array('mysql','mysqli'))) { + return; // cant handle non-mysql introspection for defaults. + } + $options = PEAR::getStaticProperty('DB_DataObject','options'); + $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver']; + $method = $db_driver == 'DB' ? 'getAll' : 'queryAll'; + $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC); + $defaults = array(); + foreach($res as $ar) { + // this is initially very dumb... -> and it may mess up.. + $type = $defs[$ar['Field']]; + + switch (true) { + + case (is_null( $ar['Default'])): + $defaults[$ar['Field']] = 'null'; + break; + + case ($type & DB_DATAOBJECT_DATE): + case ($type & DB_DATAOBJECT_TIME): + case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet.. + break; + + case ($type & DB_DATAOBJECT_BOOL): + $defaults[$ar['Field']] = (int)(boolean) $ar['Default']; + break; + + + case ($type & DB_DATAOBJECT_STR): + $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'"; + break; + + + default: // hopefully eveything else... - numbers etc. + if (!strlen($ar['Default'])) { + continue; + } + if (is_numeric($ar['Default'])) { + $defaults[$ar['Field']] = $ar['Default']; + } + break; + + } + //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']])); + } + if (empty($defaults)) { + return; + } + + $ret = "\n" . + " function defaults() // column default values \n" . + " {\n" . + " return array(\n"; + foreach($defaults as $k=>$v) { + $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n"; + } + return $ret . " );\n" . + " }\n"; + + + + + } + + + + + +} diff --git a/extlib/DB/DataObject/createTables.php b/extlib/DB/DataObject/createTables.php new file mode 100755 index 000000000..c0659574e --- /dev/null +++ b/extlib/DB/DataObject/createTables.php @@ -0,0 +1,59 @@ +#!/usr/bin/php -q +<?php +// +----------------------------------------------------------------------+ +// | PHP Version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2003 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 2.02 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/2_02.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Author: Alan Knowles <alan@akbkhome.com> +// +----------------------------------------------------------------------+ +// +// $Id: createTables.php,v 1.24 2006/01/13 01:27:55 alan_k Exp $ +// + +// since this version doesnt use overload, +// and I assume anyone using custom generators should add this.. + +define('DB_DATAOBJECT_NO_OVERLOAD',1); + +//require_once 'DB/DataObject/Generator.php'; +require_once 'DB/DataObject/Generator.php'; + +if (!ini_get('register_argc_argv')) { + PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE); + exit; +} + +if (!@$_SERVER['argv'][1]) { + PEAR::raiseError("\nERROR: createTable.php usage:\n\nC:\php\pear\DB\DataObjects\createTable.php example.ini\n\n", null, PEAR_ERROR_DIE); + exit; +} + +$config = parse_ini_file($_SERVER['argv'][1], true); +foreach($config as $class=>$values) { + $options = &PEAR::getStaticProperty($class,'options'); + $options = $values; +} + + +$options = &PEAR::getStaticProperty('DB_DataObject','options'); +if (empty($options)) { + PEAR::raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE); + exit; +} +set_time_limit(0); + +// use debug level from file if set.. +DB_DataObject::debugLevel(isset($options['debug']) ? $options['debug'] : 1); + +$generator = new DB_DataObject_Generator; +$generator->start(); + diff --git a/extlib/DB/common.php b/extlib/DB/common.php new file mode 100644 index 000000000..c51323d25 --- /dev/null +++ b/extlib/DB/common.php @@ -0,0 +1,2262 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Contains the DB_common base class + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: common.php,v 1.144 2007/11/26 22:54:03 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the PEAR class so it can be extended from + */ +require_once 'PEAR.php'; + +/** + * DB_common is the base class from which each database driver class extends + * + * All common methods are declared here. If a given DBMS driver contains + * a particular method, that method will overload the one here. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_common extends PEAR +{ + // {{{ properties + + /** + * The current default fetch mode + * @var integer + */ + var $fetchmode = DB_FETCHMODE_ORDERED; + + /** + * The name of the class into which results should be fetched when + * DB_FETCHMODE_OBJECT is in effect + * + * @var string + */ + var $fetchmode_object_class = 'stdClass'; + + /** + * Was a connection present when the object was serialized()? + * @var bool + * @see DB_common::__sleep(), DB_common::__wake() + */ + var $was_connected = null; + + /** + * The most recently executed query + * @var string + */ + var $last_query = ''; + + /** + * Run-time configuration options + * + * The 'optimize' option has been deprecated. Use the 'portability' + * option instead. + * + * @var array + * @see DB_common::setOption() + */ + var $options = array( + 'result_buffering' => 500, + 'persistent' => false, + 'ssl' => false, + 'debug' => 0, + 'seqname_format' => '%s_seq', + 'autofree' => false, + 'portability' => DB_PORTABILITY_NONE, + 'optimize' => 'performance', // Deprecated. Use 'portability'. + ); + + /** + * The parameters from the most recently executed query + * @var array + * @since Property available since Release 1.7.0 + */ + var $last_parameters = array(); + + /** + * The elements from each prepared statement + * @var array + */ + var $prepare_tokens = array(); + + /** + * The data types of the various elements in each prepared statement + * @var array + */ + var $prepare_types = array(); + + /** + * The prepared queries + * @var array + */ + var $prepared_queries = array(); + + /** + * Flag indicating that the last query was a manipulation query. + * @access protected + * @var boolean + */ + var $_last_query_manip = false; + + /** + * Flag indicating that the next query <em>must</em> be a manipulation + * query. + * @access protected + * @var boolean + */ + var $_next_query_manip = false; + + + // }}} + // {{{ DB_common + + /** + * This constructor calls <kbd>$this->PEAR('DB_Error')</kbd> + * + * @return void + */ + function DB_common() + { + $this->PEAR('DB_Error'); + } + + // }}} + // {{{ __sleep() + + /** + * Automatically indicates which properties should be saved + * when PHP's serialize() function is called + * + * @return array the array of properties names that should be saved + */ + function __sleep() + { + if ($this->connection) { + // Don't disconnect(), people use serialize() for many reasons + $this->was_connected = true; + } else { + $this->was_connected = false; + } + if (isset($this->autocommit)) { + return array('autocommit', + 'dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } else { + return array('dbsyntax', + 'dsn', + 'features', + 'fetchmode', + 'fetchmode_object_class', + 'options', + 'was_connected', + ); + } + } + + // }}} + // {{{ __wakeup() + + /** + * Automatically reconnects to the database when PHP's unserialize() + * function is called + * + * The reconnection attempt is only performed if the object was connected + * at the time PHP's serialize() function was run. + * + * @return void + */ + function __wakeup() + { + if ($this->was_connected) { + $this->connect($this->dsn, $this->options); + } + } + + // }}} + // {{{ __toString() + + /** + * Automatic string conversion for PHP 5 + * + * @return string a string describing the current PEAR DB object + * + * @since Method available since Release 1.7.0 + */ + function __toString() + { + $info = strtolower(get_class($this)); + $info .= ': (phptype=' . $this->phptype . + ', dbsyntax=' . $this->dbsyntax . + ')'; + if ($this->connection) { + $info .= ' [connected]'; + } + return $info; + } + + // }}} + // {{{ toString() + + /** + * DEPRECATED: String conversion method + * + * @return string a string describing the current PEAR DB object + * + * @deprecated Method deprecated in Release 1.7.0 + */ + function toString() + { + return $this->__toString(); + } + + // }}} + // {{{ quoteString() + + /** + * DEPRECATED: Quotes a string so it can be safely used within string + * delimiters in a query + * + * @param string $string the string to be quoted + * + * @return string the quoted string + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Method deprecated some time before Release 1.2 + */ + function quoteString($string) + { + $string = $this->quote($string); + if ($string{0} == "'") { + return substr($string, 1, -1); + } + return $string; + } + + // }}} + // {{{ quote() + + /** + * DEPRECATED: Quotes a string so it can be safely used in a query + * + * @param string $string the string to quote + * + * @return string the quoted string or the string <samp>NULL</samp> + * if the value submitted is <kbd>null</kbd>. + * + * @see DB_common::quoteSmart(), DB_common::escapeSimple() + * @deprecated Deprecated in release 1.6.0 + */ + function quote($string = null) + { + return ($string === null) ? 'NULL' + : "'" . str_replace("'", "''", $string) . "'"; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * Delimiting style depends on which database driver is being used. + * + * NOTE: just because you CAN use delimited identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * Portability is broken by using the following characters inside + * delimited identifiers: + * + backtick (<kbd>`</kbd>) -- due to MySQL + * + double quote (<kbd>"</kbd>) -- due to Oracle + * + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access + * + * Delimited identifiers are known to generally work correctly under + * the following drivers: + * + mssql + * + mysql + * + mysqli + * + oci8 + * + odbc(access) + * + odbc(db2) + * + pgsql + * + sqlite + * + sybase (must execute <kbd>set quoted_identifier on</kbd> sometime + * prior to use) + * + * InterBase doesn't seem to be able to use delimited identifiers + * via PHP 4. They work fine under PHP 5. + * + * @param string $str the identifier name to be quoted + * + * @return string the quoted identifier + * + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '"' . str_replace('"', '""', $str) . '"'; + } + + // }}} + // {{{ quoteSmart() + + /** + * Formats input so it can be safely used in a query + * + * The output depends on the PHP data type of input and the database + * type being used. + * + * @param mixed $in the data to be formatted + * + * @return mixed the formatted data. The format depends on the input's + * PHP type: + * <ul> + * <li> + * <kbd>input</kbd> -> <samp>returns</samp> + * </li> + * <li> + * <kbd>null</kbd> -> the string <samp>NULL</samp> + * </li> + * <li> + * <kbd>integer</kbd> or <kbd>double</kbd> -> the unquoted number + * </li> + * <li> + * <kbd>bool</kbd> -> output depends on the driver in use + * Most drivers return integers: <samp>1</samp> if + * <kbd>true</kbd> or <samp>0</samp> if + * <kbd>false</kbd>. + * Some return strings: <samp>TRUE</samp> if + * <kbd>true</kbd> or <samp>FALSE</samp> if + * <kbd>false</kbd>. + * Finally one returns strings: <samp>T</samp> if + * <kbd>true</kbd> or <samp>F</samp> if + * <kbd>false</kbd>. Here is a list of each DBMS, + * the values returned and the suggested column type: + * <ul> + * <li> + * <kbd>dbase</kbd> -> <samp>T/F</samp> + * (<kbd>Logical</kbd>) + * </li> + * <li> + * <kbd>fbase</kbd> -> <samp>TRUE/FALSE</samp> + * (<kbd>BOOLEAN</kbd>) + * </li> + * <li> + * <kbd>ibase</kbd> -> <samp>1/0</samp> + * (<kbd>SMALLINT</kbd>) [1] + * </li> + * <li> + * <kbd>ifx</kbd> -> <samp>1/0</samp> + * (<kbd>SMALLINT</kbd>) [1] + * </li> + * <li> + * <kbd>msql</kbd> -> <samp>1/0</samp> + * (<kbd>INTEGER</kbd>) + * </li> + * <li> + * <kbd>mssql</kbd> -> <samp>1/0</samp> + * (<kbd>BIT</kbd>) + * </li> + * <li> + * <kbd>mysql</kbd> -> <samp>1/0</samp> + * (<kbd>TINYINT(1)</kbd>) + * </li> + * <li> + * <kbd>mysqli</kbd> -> <samp>1/0</samp> + * (<kbd>TINYINT(1)</kbd>) + * </li> + * <li> + * <kbd>oci8</kbd> -> <samp>1/0</samp> + * (<kbd>NUMBER(1)</kbd>) + * </li> + * <li> + * <kbd>odbc</kbd> -> <samp>1/0</samp> + * (<kbd>SMALLINT</kbd>) [1] + * </li> + * <li> + * <kbd>pgsql</kbd> -> <samp>TRUE/FALSE</samp> + * (<kbd>BOOLEAN</kbd>) + * </li> + * <li> + * <kbd>sqlite</kbd> -> <samp>1/0</samp> + * (<kbd>INTEGER</kbd>) + * </li> + * <li> + * <kbd>sybase</kbd> -> <samp>1/0</samp> + * (<kbd>TINYINT(1)</kbd>) + * </li> + * </ul> + * [1] Accommodate the lowest common denominator because not all + * versions of have <kbd>BOOLEAN</kbd>. + * </li> + * <li> + * other (including strings and numeric strings) -> + * the data with single quotes escaped by preceeding + * single quotes, backslashes are escaped by preceeding + * backslashes, then the whole string is encapsulated + * between single quotes + * </li> + * </ul> + * + * @see DB_common::escapeSimple() + * @since Method available since Release 1.6.0 + */ + function quoteSmart($in) + { + if (is_int($in)) { + return $in; + } elseif (is_float($in)) { + return $this->quoteFloat($in); + } elseif (is_bool($in)) { + return $this->quoteBoolean($in); + } elseif (is_null($in)) { + return 'NULL'; + } else { + if ($this->dbsyntax == 'access' + && preg_match('/^#.+#$/', $in)) + { + return $this->escapeSimple($in); + } + return "'" . $this->escapeSimple($in) . "'"; + } + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? '1' : '0'; + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return "'".$this->escapeSimple(str_replace(',', '.', strval(floatval($float))))."'"; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return str_replace("'", "''", $str); + } + + // }}} + // {{{ provides() + + /** + * Tells whether the present driver supports a given feature + * + * @param string $feature the feature you're curious about + * + * @return bool whether this driver supports $feature + */ + function provides($feature) + { + return $this->features[$feature]; + } + + // }}} + // {{{ setFetchMode() + + /** + * Sets the fetch mode that should be used by default for query results + * + * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC + * or DB_FETCHMODE_OBJECT + * @param string $object_class the class name of the object to be returned + * by the fetch methods when the + * DB_FETCHMODE_OBJECT mode is selected. + * If no class is specified by default a cast + * to object from the assoc array row will be + * done. There is also the posibility to use + * and extend the 'DB_row' class. + * + * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT + */ + function setFetchMode($fetchmode, $object_class = 'stdClass') + { + switch ($fetchmode) { + case DB_FETCHMODE_OBJECT: + $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() + + /** + * Sets run-time configuration options for PEAR DB + * + * Options, their data types, default values and description: + * <ul> + * <li> + * <var>autofree</var> <kbd>boolean</kbd> = <samp>false</samp> + * <br />should results be freed automatically when there are no + * more rows? + * </li><li> + * <var>result_buffering</var> <kbd>integer</kbd> = <samp>500</samp> + * <br />how many rows of the result set should be buffered? + * <br />In mysql: mysql_unbuffered_query() is used instead of + * mysql_query() if this value is 0. (Release 1.7.0) + * <br />In oci8: this value is passed to ocisetprefetch(). + * (Release 1.7.0) + * </li><li> + * <var>debug</var> <kbd>integer</kbd> = <samp>0</samp> + * <br />debug level + * </li><li> + * <var>persistent</var> <kbd>boolean</kbd> = <samp>false</samp> + * <br />should the connection be persistent? + * </li><li> + * <var>portability</var> <kbd>integer</kbd> = <samp>DB_PORTABILITY_NONE</samp> + * <br />portability mode constant (see below) + * </li><li> + * <var>seqname_format</var> <kbd>string</kbd> = <samp>%s_seq</samp> + * <br />the sprintf() format string used on sequence names. This + * format is applied to sequence names passed to + * createSequence(), nextID() and dropSequence(). + * </li><li> + * <var>ssl</var> <kbd>boolean</kbd> = <samp>false</samp> + * <br />use ssl to connect? + * </li> + * </ul> + * + * ----------------------------------------- + * + * PORTABILITY MODES + * + * These modes are bitwised, so they can be combined using <kbd>|</kbd> + * and removed using <kbd>^</kbd>. See the examples section below on how + * to do this. + * + * <samp>DB_PORTABILITY_NONE</samp> + * turn off all portability features + * + * This mode gets automatically turned on if the deprecated + * <var>optimize</var> option gets set to <samp>performance</samp>. + * + * + * <samp>DB_PORTABILITY_LOWERCASE</samp> + * convert names of tables and fields to lower case when using + * <kbd>get*()</kbd>, <kbd>fetch*()</kbd> and <kbd>tableInfo()</kbd> + * + * This mode gets automatically turned on in the following databases + * if the deprecated option <var>optimize</var> gets set to + * <samp>portability</samp>: + * + oci8 + * + * + * <samp>DB_PORTABILITY_RTRIM</samp> + * right trim the data output by <kbd>get*()</kbd> <kbd>fetch*()</kbd> + * + * + * <samp>DB_PORTABILITY_DELETE_COUNT</samp> + * force reporting the number of rows deleted + * + * Some DBMS's don't count the number of rows deleted when performing + * simple <kbd>DELETE FROM tablename</kbd> queries. This portability + * mode tricks such DBMS's into telling the count by adding + * <samp>WHERE 1=1</samp> to the end of <kbd>DELETE</kbd> queries. + * + * This mode gets automatically turned on in the following databases + * if the deprecated option <var>optimize</var> gets set to + * <samp>portability</samp>: + * + fbsql + * + mysql + * + mysqli + * + sqlite + * + * + * <samp>DB_PORTABILITY_NUMROWS</samp> + * enable hack that makes <kbd>numRows()</kbd> work in Oracle + * + * This mode gets automatically turned on in the following databases + * if the deprecated option <var>optimize</var> gets set to + * <samp>portability</samp>: + * + oci8 + * + * + * <samp>DB_PORTABILITY_ERRORS</samp> + * makes certain error messages in certain drivers compatible + * with those from other DBMS's + * + * + mysql, mysqli: change unique/primary key constraints + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT + * + * + odbc(access): MS's ODBC driver reports 'no such field' as code + * 07001, which means 'too few parameters.' When this option is on + * that code gets mapped to DB_ERROR_NOSUCHFIELD. + * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD + * + * <samp>DB_PORTABILITY_NULL_TO_EMPTY</samp> + * convert null values to empty strings in data output by get*() and + * fetch*(). Needed because Oracle considers empty strings to be null, + * while most other DBMS's know the difference between empty and null. + * + * + * <samp>DB_PORTABILITY_ALL</samp> + * turn on all portability features + * + * ----------------------------------------- + * + * Example 1. Simple setOption() example + * <code> + * $db->setOption('autofree', true); + * </code> + * + * Example 2. Portability for lowercasing and trimming + * <code> + * $db->setOption('portability', + * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM); + * </code> + * + * Example 3. All portability options except trimming + * <code> + * $db->setOption('portability', + * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM); + * </code> + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::$options + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + $this->options[$option] = $value; + + /* + * Backwards compatibility check for the deprecated 'optimize' + * option. Done here in case settings change after connecting. + */ + if ($option == 'optimize') { + if ($value == 'portability') { + switch ($this->phptype) { + case 'oci8': + $this->options['portability'] = + DB_PORTABILITY_LOWERCASE | + DB_PORTABILITY_NUMROWS; + break; + case 'fbsql': + case 'mysql': + case 'mysqli': + case 'sqlite': + $this->options['portability'] = + DB_PORTABILITY_DELETE_COUNT; + break; + } + } else { + $this->options['portability'] = DB_PORTABILITY_NONE; + } + } + + return DB_OK; + } + return $this->raiseError("unknown option $option"); + } + + // }}} + // {{{ getOption() + + /** + * Returns the value of an option + * + * @param string $option the option name you're curious about + * + * @return mixed the option's 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() + * + * Creates a query that can be run multiple times. Each time it is run, + * the placeholders, if any, will be replaced by the contents of + * execute()'s $data argument. + * + * Three types of placeholders can be used: + * + <kbd>?</kbd> scalar value (i.e. strings, integers). The system + * will automatically quote and escape the data. + * + <kbd>!</kbd> value is inserted 'as is' + * + <kbd>&</kbd> requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Example 1. + * <code> + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * </code> + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders: + * <pre> + * "UPDATE foo SET col=? WHERE col='over \& under'" + * </pre> + * + * With some database backends, this is emulated. + * + * {@internal ibase and oci8 have their own prepare() methods.}} + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. A DB_Error object + * on failure. + * + * @see DB_common::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1, + PREG_SPLIT_DELIM_CAPTURE); + $token = 0; + $types = array(); + $newtokens = array(); + + foreach ($tokens as $val) { + switch ($val) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + break; + default: + $newtokens[] = preg_replace('/\\\([&?!])/', "\\1", $val); + } + } + + $this->prepare_tokens[] = &$newtokens; + end($this->prepare_tokens); + + $k = key($this->prepare_tokens); + $this->prepare_types[$k] = $types; + $this->prepared_queries[$k] = implode(' ', $newtokens); + + return $k; + } + + // }}} + // {{{ autoPrepare() + + /** + * Automaticaly generates an insert or update query and pass it to prepare() + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return resource the query handle + * + * @uses DB_common::prepare(), DB_common::buildManipSQL() + */ + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $query = $this->buildManipSQL($table, $table_fields, $mode, $where); + if (DB::isError($query)) { + return $query; + } + return $this->prepare($query); + } + + // }}} + // {{{ autoExecute() + + /** + * Automaticaly generates an insert or update query and call prepare() + * and execute() with it + * + * @param string $table the table name + * @param array $fields_values the associative array where $key is a + * field name and $value its value + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @uses DB_common::autoPrepare(), DB_common::execute() + */ + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, + $where = false) + { + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, + $where); + if (DB::isError($sth)) { + return $sth; + } + $ret = $this->execute($sth, array_values($fields_values)); + $this->freePrepared($sth); + return $ret; + + } + + // }}} + // {{{ buildManipSQL() + + /** + * Produces an SQL query string for autoPrepare() + * + * Example: + * <pre> + * buildManipSQL('table_sql', array('field1', 'field2', 'field3'), + * DB_AUTOQUERY_INSERT); + * </pre> + * + * That returns + * <samp> + * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?) + * </samp> + * + * NOTES: + * - This belongs more to a SQL Builder class, but this is a simple + * facility. + * - Be carefull! If you don't give a $where param with an UPDATE + * query, all the records of the table will be updated! + * + * @param string $table the table name + * @param array $table_fields the array of field names + * @param int $mode a type of query to make: + * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + * @param string $where for update queries: the WHERE clause to + * append to the SQL statement. Don't + * include the "WHERE" keyword. + * + * @return string the sql query for autoPrepare() + */ + function buildManipSQL($table, $table_fields, $mode, $where = false) + { + if (count($table_fields) == 0) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + $first = true; + switch ($mode) { + case DB_AUTOQUERY_INSERT: + $values = ''; + $names = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $names .= ','; + $values .= ','; + } + $names .= $value; + $values .= '?'; + } + return "INSERT INTO $table ($names) VALUES ($values)"; + case DB_AUTOQUERY_UPDATE: + $set = ''; + foreach ($table_fields as $value) { + if ($first) { + $first = false; + } else { + $set .= ','; + } + $set .= "$value = ?"; + } + $sql = "UPDATE $table SET $set"; + if ($where) { + $sql .= " WHERE $where"; + } + return $sql; + default: + return $this->raiseError(DB_ERROR_SYNTAX); + } + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare() + * + * Example 1. + * <code> + * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)'); + * $data = array( + * "John's text", + * "'it''s good'", + * 'filename.txt' + * ); + * $res = $db->execute($sth, $data); + * </code> + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * {@internal ibase and oci8 have their own execute() methods.}} + * + * @see DB_common::prepare() + */ + function &execute($stmt, $data = array()) + { + $realquery = $this->executeEmulateQuery($stmt, $data); + if (DB::isError($realquery)) { + return $realquery; + } + $result = $this->simpleQuery($realquery); + + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp = new DB_result($this, $result); + return $tmp; + } + } + + // }}} + // {{{ executeEmulateQuery() + + /** + * Emulates executing prepared statements if the DBMS not support them + * + * @param resource $stmt a DB statement resource returned from execute() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a string containing the real query run when emulating + * prepare/execute. A DB_Error object on failure. + * + * @access protected + * @see DB_common::execute() + */ + function executeEmulateQuery($stmt, $data = array()) + { + $stmt = (int)$stmt; + $data = (array)$data; + $this->last_parameters = $data; + + if (count($this->prepare_types[$stmt]) != count($data)) { + $this->last_query = $this->prepared_queries[$stmt]; + return $this->raiseError(DB_ERROR_MISMATCH); + } + + $realquery = $this->prepare_tokens[$stmt][0]; + + $i = 0; + foreach ($data as $value) { + if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) { + $realquery .= $this->quoteSmart($value); + } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($value, 'rb'); + if (!$fp) { + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + } + $realquery .= $this->quoteSmart(fread($fp, filesize($value))); + fclose($fp); + } else { + $realquery .= $value; + } + + $realquery .= $this->prepare_tokens[$stmt][++$i]; + } + + return $realquery; + } + + // }}} + // {{{ executeMultiple() + + /** + * Performs several execute() calls on the same statement handle + * + * $data must be an array indexed numerically + * from 0, one execute call is done for every "row" in the array. + * + * If an error occurs during execute(), executeMultiple() does not + * execute the unfinished rows, but rather returns that error. + * + * @param resource $stmt query handle from prepare() + * @param array $data numeric array containing the + * data to insert into the query + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::prepare(), DB_common::execute() + */ + function executeMultiple($stmt, $data) + { + foreach ($data as $value) { + $res = $this->execute($stmt, $value); + if (DB::isError($res)) { + return $res; + } + } + return DB_OK; + } + + // }}} + // {{{ freePrepared() + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_common::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + $stmt = (int)$stmt; + if (isset($this->prepare_tokens[$stmt])) { + unset($this->prepare_tokens[$stmt]); + unset($this->prepare_types[$stmt]); + unset($this->prepared_queries[$stmt]); + return true; + } + return false; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * It is defined here to ensure all drivers have this method available. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(), + * DB_sqlite::modifyQuery() + */ + function modifyQuery($query) + { + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * It is defined here to assure that all implementations + * have this method defined. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return $query; + } + + // }}} + // {{{ query() + + /** + * Sends a query to the database server + * + * The query string can be either a normal statement to be sent directly + * to the server OR if <var>$params</var> are passed the query can have + * placeholders and it will be passed through prepare() and execute(). + * + * @param string $query the SQL query or the statement to prepare + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + * + * @see DB_result, DB_common::prepare(), DB_common::execute() + */ + 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, false); + return $ret; + } else { + $this->last_parameters = array(); + $result = $this->simpleQuery($query); + if ($result === DB_OK || DB::isError($result)) { + return $result; + } else { + $tmp = new DB_result($this, $result); + return $tmp; + } + } + } + + // }}} + // {{{ limitQuery() + + /** + * Generates and executes a LIMIT query + * + * @param string $query the query + * @param intr $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed a new DB_result object for successful SELECT queries + * or DB_OK for successul data manipulation queries. + * A DB_Error object on failure. + */ + function &limitQuery($query, $from, $count, $params = array()) + { + $query = $this->modifyLimitQuery($query, $from, $count, $params); + if (DB::isError($query)){ + return $query; + } + $result = $this->query($query, $params); + if (is_a($result, 'DB_result')) { + $result->setOption('limit_from', $from); + $result->setOption('limit_count', $count); + } + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Fetches the first column of the first row from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return mixed the returned value of the query. + * A DB_Error object on failure. + */ + function &getOne($query, $params = array()) + { + $params = (array)$params; + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED); + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row[0]; + } + + // }}} + // {{{ getRow() + + /** + * Fetches the first row of data returned from a query result + * + * Takes care of doing the query and freeing the results when finished. + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * + * @return array the first row of results as an array. + * A DB_Error object on failure. + */ + function &getRow($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + // modifyLimitQuery() would be nice here, but it causes BC issues + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + if (DB::isError($sth)) { + return $sth; + } + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $err = $res->fetchInto($row, $fetchmode); + + $res->free(); + + if ($err !== DB_OK) { + return $err; + } + + return $row; + } + + // }}} + // {{{ getCol() + + /** + * Fetches a single column from a query result and returns it as an + * indexed array + * + * @param string $query the SQL query + * @param mixed $col which column to return (integer [column number, + * starting at 0] or string [column name]) + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return array the results as an array. A DB_Error object on failure. + * + * @see DB_common::query() + */ + function &getCol($query, $col = 0, $params = array()) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC; + + if (!is_array($row = $res->fetchRow($fetchmode))) { + $ret = array(); + } else { + if (!array_key_exists($col, $row)) { + $ret = $this->raiseError(DB_ERROR_NOSUCHFIELD); + } else { + $ret = array($row[$col]); + while (is_array($row = $res->fetchRow($fetchmode))) { + $ret[] = $row[$col]; + } + } + } + + $res->free(); + + if (DB::isError($row)) { + $ret = $row; + } + + return $ret; + } + + // }}} + // {{{ getAssoc() + + /** + * Fetches an entire query result and returns it as an + * associative array using the first column as the key + * + * If the result set contains more than two columns, the value + * will be an array of the values from column 2-n. If the result + * set contains only two columns, the returned value will be a + * scalar with the value of the second column (unless forced to an + * array with the $force_array parameter). A DB error code is + * returned on errors. If the result set contains fewer than two + * columns, a DB_ERROR_TRUNCATED error is returned. + * + * For example, if the table "mytable" contains: + * + * <pre> + * ID TEXT DATE + * -------------------------------- + * 1 'one' 944679408 + * 2 'two' 944679408 + * 3 'three' 944679408 + * </pre> + * + * Then the call getAssoc('SELECT id,text FROM mytable') returns: + * <pre> + * array( + * '1' => 'one', + * '2' => 'two', + * '3' => 'three', + * ) + * </pre> + * + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns: + * <pre> + * array( + * '1' => array('one', '944679408'), + * '2' => array('two', '944679408'), + * '3' => array('three', '944679408') + * ) + * </pre> + * + * 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: + * + * <pre> + * 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') + * ) + * ) + * </pre> + * + * Keep in mind that database functions in PHP usually return string + * values for results regardless of the database's internal type. + * + * @param string $query the SQL query + * @param bool $force_array used only when the query returns + * exactly two columns. If true, the values + * of the returned array will be one-element + * arrays instead of scalars. + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use + * @param bool $group if true, the values of the returned array + * is wrapped in another array. If the same + * key value (in the first column) repeats + * itself, the values will be appended to + * this array instead of overwriting the + * existing values. + * + * @return array the associative array containing the query results. + * A DB_Error object on failure. + */ + function &getAssoc($query, $force_array = false, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT, $group = false) + { + $params = (array)$params; + if (sizeof($params) > 0) { + $sth = $this->prepare($query); + + if (DB::isError($sth)) { + return $sth; + } + + $res = $this->execute($sth, $params); + $this->freePrepared($sth); + } else { + $res = $this->query($query); + } + + if (DB::isError($res)) { + return $res; + } + if ($fetchmode == DB_FETCHMODE_DEFAULT) { + $fetchmode = $this->fetchmode; + } + $cols = $res->numCols(); + + if ($cols < 2) { + $tmp = $this->raiseError(DB_ERROR_TRUNCATED); + return $tmp; + } + + $results = array(); + + if ($cols > 2 || $force_array) { + // return array values + // XXX this part can be optimized + if ($fetchmode == DB_FETCHMODE_ASSOC) { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) { + reset($row); + $key = current($row); + unset($row[key($row)]); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } elseif ($fetchmode == DB_FETCHMODE_OBJECT) { + while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) { + $arr = get_object_vars($row); + $key = current($arr); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } else { + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + // we shift away the first element to get + // indices running from 0 again + $key = array_shift($row); + if ($group) { + $results[$key][] = $row; + } else { + $results[$key] = $row; + } + } + } + if (DB::isError($row)) { + $results = $row; + } + } else { + // return scalar values + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) { + if ($group) { + $results[$row[0]][] = $row[1]; + } else { + $results[$row[0]] = $row[1]; + } + } + if (DB::isError($row)) { + $results = $row; + } + } + + $res->free(); + + return $results; + } + + // }}} + // {{{ getAll() + + /** + * Fetches all of the rows from a query result + * + * @param string $query the SQL query + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of + * items passed must match quantity of + * placeholders in query: meaning 1 + * placeholder for non-array parameters or + * 1 placeholder per array element. + * @param int $fetchmode the fetch mode to use: + * + DB_FETCHMODE_ORDERED + * + DB_FETCHMODE_ASSOC + * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED + * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED + * + * @return array the nested array. A DB_Error object on failure. + */ + function &getAll($query, $params = array(), + $fetchmode = DB_FETCHMODE_DEFAULT) + { + // compat check, the params and fetchmode parameters used to + // have the opposite order + if (!is_array($params)) { + if (is_array($fetchmode)) { + if ($params === null) { + $tmp = DB_FETCHMODE_DEFAULT; + } else { + $tmp = $params; + } + $params = $fetchmode; + $fetchmode = $tmp; + } elseif ($params !== null) { + $fetchmode = $params; + $params = array(); + } + } + + 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 ($res === DB_OK || DB::isError($res)) { + return $res; + } + + $results = array(); + while (DB_OK === $res->fetchInto($row, $fetchmode)) { + if ($fetchmode & DB_FETCHMODE_FLIPPED) { + foreach ($row as $key => $val) { + $results[$key][] = $val; + } + } else { + $results[] = $row; + } + } + + $res->free(); + + if (DB::isError($row)) { + $tmp = $this->raiseError($row); + return $tmp; + } + return $results; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numRows() + + /** + * Determines the number of rows in a query result + * + * @param resource $result the query result idenifier produced by PHP + * + * @return int the number of rows. A DB_Error object on failure. + */ + function numRows($result) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getSequenceName() + + /** + * Generates the name used inside the database for a sequence + * + * The createSequence() docblock contains notes about storing sequence + * names. + * + * @param string $sqn the sequence's public name + * + * @return string the sequence's name in the backend + * + * @access protected + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::nextID(), DB_common::setOption() + */ + function getSequenceName($sqn) + { + return sprintf($this->getOption('seqname_format'), + preg_replace('/[^a-z0-9_.]/i', '_', $sqn)); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::dropSequence(), + * DB_common::getSequenceName() + */ + function nextId($seq_name, $ondemand = true) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * The name of a given sequence is determined by passing the string + * provided in the <var>$seq_name</var> argument through PHP's sprintf() + * function using the value from the <var>seqname_format</var> option as + * the sprintf()'s format argument. + * + * <var>seqname_format</var> is set via setOption(). + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function createSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_common::nextID() + */ + function dropSequence($seq_name) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ raiseError() + + /** + * Communicates an error and invoke error callbacks, etc + * + * Basically a wrapper for PEAR::raiseError without the message string. + * + * @param mixed integer error code, or a PEAR error object (all + * other parameters are ignored if this parameter is + * an object + * @param int error mode, see PEAR_Error docs + * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string extra debug information. Defaults to the last + * query and native error code. + * @param mixed native error code, integer or string depending the + * backend + * @param mixed dummy parameter for E_STRICT compatibility with + * PEAR::raiseError + * @param mixed dummy parameter for E_STRICT compatibility with + * PEAR::raiseError + * + * @return object the PEAR_Error object + * + * @see PEAR_Error + */ + function &raiseError($code = DB_ERROR, $mode = null, $options = null, + $userinfo = null, $nativecode = null, $dummy1 = null, + $dummy2 = null) + { + // The error is yet a DB error object + if (is_object($code)) { + // because we the static PEAR::raiseError, our global + // handler should be used if it is set + if ($mode === null && !empty($this->_default_error_mode)) { + $mode = $this->_default_error_mode; + $options = $this->_default_error_options; + } + $tmp = PEAR::raiseError($code, null, $mode, $options, + null, null, true); + return $tmp; + } + + if ($userinfo === null) { + $userinfo = $this->last_query; + } + + if ($nativecode) { + $userinfo .= ' [nativecode=' . trim($nativecode) . ']'; + } else { + $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']'; + } + + $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo, + 'DB_Error', true); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return mixed the DBMS' error code. A DB_Error object on failure. + */ + function errorNative() + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * Uses the <var>$errorcode_map</var> property defined in each driver. + * + * @param string|int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + */ + 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() + + /** + * Maps a DB error code to a textual message + * + * @param integer $dbcode the DB error code + * + * @return string the error message corresponding to the error code + * submitted. FALSE if the error code is unknown. + * + * @see DB::errorMessage() + */ + function errorMessage($dbcode) + { + return DB::errorMessage($this->errorcode_map[$dbcode]); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * The format of the resulting array depends on which <var>$mode</var> + * you select. The sample output below is based on this query: + * <pre> + * SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId + * FROM tblFoo + * JOIN tblBar ON tblFoo.fldId = tblBar.fldId + * </pre> + * + * <ul> + * <li> + * + * <kbd>null</kbd> (default) + * <pre> + * [0] => Array ( + * [table] => tblFoo + * [name] => fldId + * [type] => int + * [len] => 11 + * [flags] => primary_key not_null + * ) + * [1] => Array ( + * [table] => tblFoo + * [name] => fldPhone + * [type] => string + * [len] => 20 + * [flags] => + * ) + * [2] => Array ( + * [table] => tblBar + * [name] => fldId + * [type] => int + * [len] => 11 + * [flags] => primary_key not_null + * ) + * </pre> + * + * </li><li> + * + * <kbd>DB_TABLEINFO_ORDER</kbd> + * + * <p>In addition to the information found in the default output, + * a notation of the number of columns is provided by the + * <samp>num_fields</samp> element while the <samp>order</samp> + * element provides an array with the column names as the keys and + * their location index number (corresponding to the keys in the + * the default output) as the values.</p> + * + * <p>If a result set has identical field names, the last one is + * used.</p> + * + * <pre> + * [num_fields] => 3 + * [order] => Array ( + * [fldId] => 2 + * [fldTrans] => 1 + * ) + * </pre> + * + * </li><li> + * + * <kbd>DB_TABLEINFO_ORDERTABLE</kbd> + * + * <p>Similar to <kbd>DB_TABLEINFO_ORDER</kbd> but adds more + * dimensions to the array in which the table names are keys and + * the field names are sub-keys. This is helpful for queries that + * join tables which have identical field names.</p> + * + * <pre> + * [num_fields] => 3 + * [ordertable] => Array ( + * [tblFoo] => Array ( + * [fldId] => 0 + * [fldPhone] => 1 + * ) + * [tblBar] => Array ( + * [fldId] => 2 + * ) + * ) + * </pre> + * + * </li> + * </ul> + * + * The <samp>flags</samp> element contains a space separated list + * of extra information about the field. This data is inconsistent + * between DBMS's due to the way each DBMS works. + * + <samp>primary_key</samp> + * + <samp>unique_key</samp> + * + <samp>multiple_key</samp> + * + <samp>not_null</samp> + * + * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp> + * elements if <var>$result</var> is a table name. The following DBMS's + * provide full information from queries: + * + fbsql + * + mysql + * + * If the 'portability' option has <samp>DB_PORTABILITY_LOWERCASE</samp> + * turned on, the names of tables and fields will be lowercased. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode either unused or one of the tableInfo modes: + * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>, + * <kbd>DB_TABLEINFO_ORDER</kbd> or + * <kbd>DB_TABLEINFO_FULL</kbd> (which does both). + * These are bitwise, so the first two can be + * combined using <kbd>|</kbd>. + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + /* + * If the DB_<driver> class has a tableInfo() method, that one + * overrides this one. But, if the driver doesn't have one, + * this method runs and tells users about that fact. + */ + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ getTables() + + /** + * Lists the tables in the current database + * + * @return array the list of tables. A DB_Error object on failure. + * + * @deprecated Method deprecated some time before Release 1.2 + */ + function getTables() + { + return $this->getListOf('tables'); + } + + // }}} + // {{{ getListOf() + + /** + * Lists internal database information + * + * @param string $type type of information being sought. + * Common items being sought are: + * tables, databases, users, views, functions + * Each DBMS's has its own capabilities. + * + * @return array an array listing the items sought. + * A DB DB_Error object on failure. + */ + function getListOf($type) + { + $sql = $this->getSpecialQuery($type); + if ($sql === null) { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } elseif (is_int($sql) || DB::isError($sql)) { + // Previous error + return $this->raiseError($sql); + } elseif (is_array($sql)) { + // Already the result + return $sql; + } + // Launch this query + return $this->getCol($sql); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ nextQueryIsManip() + + /** + * Sets (or unsets) a flag indicating that the next query will be a + * manipulation query, regardless of the usual DB::isManip() heuristics. + * + * @param boolean true to set the flag overriding the isManip() behaviour, + * false to clear it and fall back onto isManip() + * + * @return void + * + * @access public + */ + function nextQueryIsManip($manip) + { + $this->_next_query_manip = $manip; + } + + // }}} + // {{{ _checkManip() + + /** + * Checks if the given query is a manipulation query. This also takes into + * account the _next_query_manip flag and sets the _last_query_manip flag + * (and resets _next_query_manip) according to the result. + * + * @param string The query to check. + * + * @return boolean true if the query is a manipulation query, false + * otherwise + * + * @access protected + */ + function _checkManip($query) + { + if ($this->_next_query_manip || DB::isManip($query)) { + $this->_last_query_manip = true; + } else { + $this->_last_query_manip = false; + } + $this->_next_query_manip = false; + return $this->_last_query_manip; + $manip = $this->_next_query_manip; + } + + // }}} + // {{{ _rtrimArrayValues() + + /** + * Right-trims all strings in an array + * + * @param array $array the array to be trimmed (passed by reference) + * + * @return void + * + * @access protected + */ + function _rtrimArrayValues(&$array) + { + foreach ($array as $key => $value) { + if (is_string($value)) { + $array[$key] = rtrim($value); + } + } + } + + // }}} + // {{{ _convertNullArrayValuesToEmpty() + + /** + * Converts all null values in an array to empty strings + * + * @param array $array the array to be de-nullified (passed by reference) + * + * @return void + * + * @access protected + */ + function _convertNullArrayValuesToEmpty(&$array) + { + foreach ($array as $key => $value) { + if (is_null($value)) { + $array[$key] = ''; + } + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/dbase.php b/extlib/DB/dbase.php new file mode 100644 index 000000000..67afc897d --- /dev/null +++ b/extlib/DB/dbase.php @@ -0,0 +1,510 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's dbase extension + * for interacting with dBase databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: dbase.php,v 1.45 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's dbase extension + * for interacting with dBase databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Tomas V.V. Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_dbase extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'dbase'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'dbase'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => false, + 'new_link' => false, + 'numrows' => true, + 'pconnect' => false, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * A means of emulating result resources + * @var array + */ + var $res_row = array(); + + /** + * The quantity of results so far + * + * For emulating result resources. + * + * @var integer + */ + var $result = 0; + + /** + * Maps dbase data type id's to human readable strings + * + * The human readable values are based on the output of PHP's + * dbase_get_header_info() function. + * + * @var array + * @since Property available since Release 1.7.0 + */ + var $types = array( + 'C' => 'character', + 'D' => 'date', + 'L' => 'boolean', + 'M' => 'memo', + 'N' => 'number', + ); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_dbase() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database and create it if it doesn't exist + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's dbase driver supports the following extra DSN options: + * + mode An integer specifying the read/write mode to use + * (0 = read only, 1 = write only, 2 = read/write). + * Available since PEAR DB 1.7.0. + * + fields An array of arrays that PHP's dbase_create() function needs + * to create a new database. This information is used if the + * dBase file specified in the "database" segment of the DSN + * does not exist. For more info, see the PHP manual's + * {@link http://php.net/dbase_create dbase_create()} page. + * Available since PEAR DB 1.7.0. + * + * Example of how to connect and establish a new dBase file if necessary: + * <code> + * require_once 'DB.php'; + * + * $dsn = array( + * 'phptype' => 'dbase', + * 'database' => '/path/and/name/of/dbase/file', + * 'mode' => 2, + * 'fields' => array( + * array('a', 'N', 5, 0), + * array('b', 'C', 40), + * array('c', 'C', 255), + * array('d', 'C', 20), + * ), + * ); + * $options = array( + * 'debug' => 2, + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('dbase')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + /* + * Turn track_errors on for entire script since $php_errormsg + * is the only way to find errors from the dbase extension. + */ + @ini_set('track_errors', 1); + $php_errormsg = ''; + + if (!file_exists($dsn['database'])) { + $this->dsn['mode'] = 2; + if (empty($dsn['fields']) || !is_array($dsn['fields'])) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + 'the dbase file does not exist and ' + . 'it could not be created because ' + . 'the "fields" element of the DSN ' + . 'is not properly set'); + } + $this->connection = @dbase_create($dsn['database'], + $dsn['fields']); + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + 'the dbase file does not exist and ' + . 'the attempt to create it failed: ' + . $php_errormsg); + } + } else { + if (!isset($this->dsn['mode'])) { + $this->dsn['mode'] = 0; + } + $this->connection = @dbase_open($dsn['database'], + $this->dsn['mode']); + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @dbase_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ &query() + + function &query($query = null) + { + // emulate result resources + $this->res_row[(int)$this->result] = 0; + $tmp = new DB_result($this, $this->result++); + return $tmp; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum === null) { + $rownum = $this->res_row[(int)$result]++; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @dbase_get_record_with_names($this->connection, $rownum); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @dbase_get_record($this->connection, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set. + * + * This method is a no-op for dbase, as there aren't result resources in + * the same sense as most other database backends. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return true; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($foo) + { + return @dbase_numfields($this->connection); + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($foo) + { + return @dbase_numrecords($this->connection); + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? 'T' : 'F'; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about the current database + * + * @param mixed $result THIS IS UNUSED IN DBASE. The current database + * is examined regardless of what is provided here. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result = null, $mode = null) + { + if (function_exists('dbase_get_header_info')) { + $id = @dbase_get_header_info($this->connection); + if (!$id && $php_errormsg) { + return $this->raiseError(DB_ERROR, + null, null, null, + $php_errormsg); + } + } else { + /* + * This segment for PHP 4 is loosely based on code by + * Hadi Rusiah <deegos@yahoo.com> in the comments on + * the dBase reference page in the PHP manual. + */ + $db = @fopen($this->dsn['database'], 'r'); + if (!$db) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + + $id = array(); + $i = 0; + + $line = fread($db, 32); + while (!feof($db)) { + $line = fread($db, 32); + if (substr($line, 0, 1) == chr(13)) { + break; + } else { + $pos = strpos(substr($line, 0, 10), chr(0)); + $pos = ($pos == 0 ? 10 : $pos); + $id[$i] = array( + 'name' => substr($line, 0, $pos), + 'type' => $this->types[substr($line, 11, 1)], + 'length' => ord(substr($line, 16, 1)), + 'precision' => ord(substr($line, 17, 1)), + ); + } + $i++; + } + + fclose($db); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $res = array(); + $count = count($id); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $this->dsn['database'], + 'name' => $case_func($id[$i]['name']), + 'type' => $id[$i]['type'], + 'len' => $id[$i]['length'], + 'flags' => '' + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/fbsql.php b/extlib/DB/fbsql.php new file mode 100644 index 000000000..4de4078f7 --- /dev/null +++ b/extlib/DB/fbsql.php @@ -0,0 +1,769 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's fbsql extension + * for interacting with FrontBase databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Frank M. Kromann <frank@frontbase.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: fbsql.php,v 1.88 2007/07/06 05:19:21 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's fbsql extension + * for interacting with FrontBase databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Frank M. Kromann <frank@frontbase.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + * @since Class functional since Release 1.7.0 + */ +class DB_fbsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'fbsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'fbsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 22 => DB_ERROR_SYNTAX, + 85 => DB_ERROR_ALREADY_EXISTS, + 108 => DB_ERROR_SYNTAX, + 116 => DB_ERROR_NOSUCHTABLE, + 124 => DB_ERROR_VALUE_COUNT_ON_ROW, + 215 => DB_ERROR_NOSUCHFIELD, + 217 => DB_ERROR_INVALID_NUMBER, + 226 => DB_ERROR_NOSUCHFIELD, + 231 => DB_ERROR_INVALID, + 239 => DB_ERROR_TRUNCATED, + 251 => DB_ERROR_SYNTAX, + 266 => DB_ERROR_NOT_FOUND, + 357 => DB_ERROR_CONSTRAINT_NOT_NULL, + 358 => DB_ERROR_CONSTRAINT, + 360 => DB_ERROR_CONSTRAINT, + 361 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_fbsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('fbsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array( + $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', + $dsn['username'] ? $dsn['username'] : null, + $dsn['password'] ? $dsn['password'] : null, + ); + + $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + + if ($dsn['database']) { + if (!@fbsql_select_db($dsn['database'], $this->connection)) { + return $this->fbsqlRaiseError(); + } + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @fbsql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @fbsql_query("$query;", $this->connection); + if (!$result) { + return $this->fbsqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($this->_checkManip($query)) { + return DB_OK; + } + return $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal fbsql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return @fbsql_next_result($result); + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@fbsql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @fbsql_fetch_array($result, FBSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @fbsql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? fbsql_free_result($result) : false; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff=false) + { + if ($onoff) { + $this->query("SET COMMIT TRUE"); + } else { + $this->query("SET COMMIT FALSE"); + } + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + @fbsql_commit($this->connection); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + @fbsql_rollback($this->connection); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @fbsql_num_fields($result); + if (!$cols) { + return $this->fbsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @fbsql_num_rows($result); + if ($rows === null) { + return $this->fbsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + $result = @fbsql_affected_rows($this->connection); + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_fbsql::createSequence(), DB_fbsql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query('SELECT UNIQUE FROM ' . $seqname); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $result; + } + } else { + $repeat = 0; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->fbsqlRaiseError(); + } + $result->fetchInto($tmp, DB_FETCHMODE_ORDERED); + return $tmp[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_fbsql::nextID(), DB_fbsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER NOT NULL,' + . ' PRIMARY KEY(id))'); + if ($res) { + $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname); + } + return $res; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_fbsql::nextID(), DB_fbsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name) + . ' RESTRICT'); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query) || $this->_next_query_manip) { + return preg_replace('/^([\s(])*SELECT/i', + "\\1SELECT TOP($count)", $query); + } else { + return preg_replace('/([\s(])*SELECT/i', + "\\1SELECT TOP($from, $count)", $query); + } + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? 'TRUE' : 'FALSE'; + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + // {{{ fbsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_fbsql::errorNative(), DB_common::errorCode() + */ + function fbsqlRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(fbsql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @fbsql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @fbsql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @fbsql_list_fields($this->dsn['database'], + $result, $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @fbsql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@fbsql_field_table($id, $i)), + 'name' => $case_func(@fbsql_field_name($id, $i)), + 'type' => @fbsql_field_type($id, $i), + 'len' => @fbsql_field_len($id, $i), + 'flags' => @fbsql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @fbsql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT "table_name" FROM information_schema.tables' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'BASE TABLE\'' + . ' AND "schema_name" = current_schema'; + case 'views': + return 'SELECT "table_name" FROM information_schema.tables' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk AND' + . ' "table_type" = \'VIEW\'' + . ' AND "schema_name" = current_schema'; + case 'users': + return 'SELECT "user_name" from information_schema.users'; + case 'functions': + return 'SELECT "routine_name" FROM' + . ' information_schema.psm_routines' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk' + . ' AND "routine_kind"=\'FUNCTION\'' + . ' AND "schema_name" = current_schema'; + case 'procedures': + return 'SELECT "routine_name" FROM' + . ' information_schema.psm_routines' + . ' t0, information_schema.schemata t1' + . ' WHERE t0.schema_pk=t1.schema_pk' + . ' AND "routine_kind"=\'PROCEDURE\'' + . ' AND "schema_name" = current_schema'; + default: + return null; + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/ibase.php b/extlib/DB/ibase.php new file mode 100644 index 000000000..ee19c5589 --- /dev/null +++ b/extlib/DB/ibase.php @@ -0,0 +1,1082 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's interbase extension + * for interacting with Interbase and Firebird databases + * + * While this class works with PHP 4, PHP's InterBase extension is + * unstable in PHP 4. Use PHP 5. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Sterling Hughes <sterling@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: ibase.php,v 1.116 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's interbase extension + * for interacting with Interbase and Firebird databases + * + * These methods overload the ones declared in DB_common. + * + * While this class works with PHP 4, PHP's InterBase extension is + * unstable in PHP 4. Use PHP 5. + * + * NOTICE: limitQuery() only works for Firebird. + * + * @category Database + * @package DB + * @author Sterling Hughes <sterling@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + * @since Class became stable in Release 1.7.0 + */ +class DB_ibase extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'ibase'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'ibase'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * NOTE: only firebird supports limit. + * + * @var array + */ + var $features = array( + 'limit' => false, + 'new_link' => false, + 'numrows' => 'emulate', + 'pconnect' => true, + 'prepare' => true, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + -104 => DB_ERROR_SYNTAX, + -150 => DB_ERROR_ACCESS_VIOLATION, + -151 => DB_ERROR_ACCESS_VIOLATION, + -155 => DB_ERROR_NOSUCHTABLE, + -157 => DB_ERROR_NOSUCHFIELD, + -158 => DB_ERROR_VALUE_COUNT_ON_ROW, + -170 => DB_ERROR_MISMATCH, + -171 => DB_ERROR_MISMATCH, + -172 => DB_ERROR_INVALID, + // -204 => // Covers too many errors, need to use regex on msg + -205 => DB_ERROR_NOSUCHFIELD, + -206 => DB_ERROR_NOSUCHFIELD, + -208 => DB_ERROR_INVALID, + -219 => DB_ERROR_NOSUCHTABLE, + -297 => DB_ERROR_CONSTRAINT, + -303 => DB_ERROR_INVALID, + -413 => DB_ERROR_INVALID_NUMBER, + -530 => DB_ERROR_CONSTRAINT, + -551 => DB_ERROR_ACCESS_VIOLATION, + -552 => DB_ERROR_ACCESS_VIOLATION, + // -607 => // Covers too many errors, need to use regex on msg + -625 => DB_ERROR_CONSTRAINT_NOT_NULL, + -803 => DB_ERROR_CONSTRAINT, + -804 => DB_ERROR_VALUE_COUNT_ON_ROW, + // -902 => // Covers too many errors, need to use regex on msg + -904 => DB_ERROR_CONNECT_FAILED, + -922 => DB_ERROR_NOSUCHDB, + -923 => DB_ERROR_CONNECT_FAILED, + -924 => DB_ERROR_CONNECT_FAILED + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * The number of rows affected by a data manipulation query + * @var integer + * @access private + */ + var $affected = 0; + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The prepared statement handle from the most recently executed statement + * + * {@internal Mainly here because the InterBase/Firebird API is only + * able to retrieve data from result sets if the statemnt handle is + * still in scope.}} + * + * @var resource + */ + var $last_stmt; + + /** + * Is the given prepared statement a data manipulation query? + * @var array + * @access private + */ + var $manip_query = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_ibase() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's ibase driver supports the following extra DSN options: + * + buffers The number of database buffers to allocate for the + * server-side cache. + * + charset The default character set for a database. + * + dialect The default SQL dialect for any statement + * executed within a connection. Defaults to the + * highest one supported by client libraries. + * Functional only with InterBase 6 and up. + * + role Functional only with InterBase 5 and up. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('interbase')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + if ($this->dbsyntax == 'firebird') { + $this->features['limit'] = 'alter'; + } + + $params = array( + $dsn['hostspec'] + ? ($dsn['hostspec'] . ':' . $dsn['database']) + : $dsn['database'], + $dsn['username'] ? $dsn['username'] : null, + $dsn['password'] ? $dsn['password'] : null, + isset($dsn['charset']) ? $dsn['charset'] : null, + isset($dsn['buffers']) ? $dsn['buffers'] : null, + isset($dsn['dialect']) ? $dsn['dialect'] : null, + isset($dsn['role']) ? $dsn['role'] : null, + ); + + $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect'; + + $this->connection = @call_user_func_array($connect_function, $params); + if (!$this->connection) { + return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @ibase_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @ibase_query($this->connection, $query); + + if (!$result) { + return $this->ibaseRaiseError(); + } + if ($this->autocommit && $ismanip) { + @ibase_commit($this->connection); + } + if ($ismanip) { + $this->affected = $result; + return DB_OK; + } else { + $this->affected = 0; + return $result; + } + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * Only works with Firebird. + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if ($this->dsn['dbsyntax'] == 'firebird') { + $query = preg_replace('/^([\s(])*SELECT/i', + "SELECT FIRST $count SKIP $from", $query); + } + return $query; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal ibase result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + if (function_exists('ibase_fetch_assoc')) { + $arr = @ibase_fetch_assoc($result); + } else { + $arr = get_object_vars(ibase_fetch_object($result)); + } + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @ibase_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? ibase_free_result($result) : false; + } + + // }}} + // {{{ freeQuery() + + function freeQuery($query) + { + return is_resource($query) ? ibase_free_query($query) : false; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (is_integer($this->affected)) { + return $this->affected; + } + return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @ibase_num_fields($result); + if (!$cols) { + return $this->ibaseRaiseError(); + } + return $cols; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * + * prepare() requires a generic query as string like <code> + * INSERT INTO numbers VALUES (?, ?, ?) + * </code>. The <kbd>?</kbd> characters are placeholders. + * + * Three types of placeholders can be used: + * + <kbd>?</kbd> a quoted scalar value, i.e. strings, integers + * + <kbd>!</kbd> value is inserted 'as is' + * + <kbd>&</kbd> requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders. Example: <code> + * "UPDATE foo SET col=? WHERE col='over \& under'" + * </code> + * + * @param string $query query to be prepared + * @return mixed DB statement resource on success. DB_Error on failure. + */ + function prepare($query) + { + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1, + PREG_SPLIT_DELIM_CAPTURE); + $token = 0; + $types = array(); + $newquery = ''; + + foreach ($tokens as $key => $val) { + switch ($val) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + break; + default: + $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val); + $newquery .= $tokens[$key] . '?'; + } + } + + $newquery = substr($newquery, 0, -1); + $this->last_query = $query; + $newquery = $this->modifyQuery($newquery); + $stmt = @ibase_prepare($this->connection, $newquery); + + if ($stmt === false) { + $stmt = $this->ibaseRaiseError(); + } else { + $this->prepare_types[(int)$stmt] = $types; + $this->manip_query[(int)$stmt] = DB::isManip($query); + } + + return $stmt; + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare(). + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 for non-array items or the + * quantity of elements in the array. + * @return object a new DB_Result or a DB_Error when fail + * @see DB_ibase::prepare() + * @access public + */ + function &execute($stmt, $data = array()) + { + $data = (array)$data; + $this->last_parameters = $data; + + $types = $this->prepare_types[(int)$stmt]; + if (count($types) != count($data)) { + $tmp = $this->raiseError(DB_ERROR_MISMATCH); + return $tmp; + } + + $i = 0; + foreach ($data as $key => $value) { + if ($types[$i] == DB_PARAM_MISC) { + /* + * ibase doesn't seem to have the ability to pass a + * parameter along unchanged, so strip off quotes from start + * and end, plus turn two single quotes to one single quote, + * in order to avoid the quotes getting escaped by + * ibase and ending up in the database. + */ + $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]); + $data[$key] = str_replace("''", "'", $data[$key]); + } elseif ($types[$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($data[$key], 'rb'); + if (!$fp) { + $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + return $tmp; + } + $data[$key] = fread($fp, filesize($data[$key])); + fclose($fp); + } + $i++; + } + + array_unshift($data, $stmt); + + $res = call_user_func_array('ibase_execute', $data); + if (!$res) { + $tmp = $this->ibaseRaiseError(); + return $tmp; + } + /* XXX need this? + if ($this->autocommit && $this->manip_query[(int)$stmt]) { + @ibase_commit($this->connection); + }*/ + $this->last_stmt = $stmt; + if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) { + $this->_last_query_manip = true; + $this->_next_query_manip = false; + $tmp = DB_OK; + } else { + $this->_last_query_manip = false; + $tmp = new DB_result($this, $res); + } + return $tmp; + } + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's PHP resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_ibase::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + if (!is_resource($stmt)) { + return false; + } + if ($free_resource) { + @ibase_free_query($stmt); + } + unset($this->prepare_tokens[(int)$stmt]); + unset($this->prepare_types[(int)$stmt]); + unset($this->manip_query[(int)$stmt]); + return true; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + $this->autocommit = $onoff ? 1 : 0; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + return @ibase_commit($this->connection); + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + return @ibase_rollback($this->connection); + } + + // }}} + // {{{ transactionInit() + + function transactionInit($trans_args = 0) + { + return $trans_args + ? @ibase_trans($trans_args, $this->connection) + : @ibase_trans(); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_ibase::createSequence(), DB_ibase::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $sqn = strtoupper($this->getSequenceName($seq_name)); + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("SELECT GEN_ID(${sqn}, 1) " + . 'FROM RDB$GENERATORS ' + . "WHERE RDB\$GENERATOR_NAME='${sqn}'"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result)) { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $result; + } + } else { + $repeat = 0; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_ibase::nextID(), DB_ibase::dropSequence() + */ + function createSequence($seq_name) + { + $sqn = strtoupper($this->getSequenceName($seq_name)); + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("CREATE GENERATOR ${sqn}"); + $this->popErrorHandling(); + + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_ibase::nextID(), DB_ibase::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DELETE FROM RDB$GENERATORS ' + . "WHERE RDB\$GENERATOR_NAME='" + . strtoupper($this->getSequenceName($seq_name)) + . "'"); + } + + // }}} + // {{{ _ibaseFieldFlags() + + /** + * Get the column's flags + * + * Supports "primary_key", "unique_key", "not_null", "default", + * "computed" and "blob". + * + * @param string $field_name the name of the field + * @param string $table_name the name of the table + * + * @return string the flags + * + * @access private + */ + function _ibaseFieldFlags($field_name, $table_name) + { + $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE' + .' FROM RDB$INDEX_SEGMENTS I' + .' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME' + .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\'' + .' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''; + + $result = @ibase_query($this->connection, $sql); + if (!$result) { + return $this->ibaseRaiseError(); + } + + $flags = ''; + if ($obj = @ibase_fetch_object($result)) { + @ibase_free_result($result); + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') { + $flags .= 'primary_key '; + } + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') { + $flags .= 'unique_key '; + } + } + + $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,' + .' R.RDB$DEFAULT_SOURCE AS DSOURCE,' + .' F.RDB$FIELD_TYPE AS FTYPE,' + .' F.RDB$COMPUTED_SOURCE AS CSOURCE' + .' FROM RDB$RELATION_FIELDS R ' + .' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME' + .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'' + .' AND R.RDB$FIELD_NAME=\'' . $field_name . '\''; + + $result = @ibase_query($this->connection, $sql); + if (!$result) { + return $this->ibaseRaiseError(); + } + if ($obj = @ibase_fetch_object($result)) { + @ibase_free_result($result); + if (isset($obj->NFLAG)) { + $flags .= 'not_null '; + } + if (isset($obj->DSOURCE)) { + $flags .= 'default '; + } + if (isset($obj->CSOURCE)) { + $flags .= 'computed '; + } + if (isset($obj->FTYPE) && $obj->FTYPE == 261) { + $flags .= 'blob '; + } + } + + return trim($flags); + } + + // }}} + // {{{ ibaseRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_ibase::errorNative(), DB_ibase::errorCode() + */ + function &ibaseRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode($this->errorNative()); + } + $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg()); + return $tmp; + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code. NULL if there is no error code. + * + * @since Method available since Release 1.7.0 + */ + function errorNative() + { + if (function_exists('ibase_errcode')) { + return @ibase_errcode(); + } + if (preg_match('/^Dynamic SQL Error SQL error code = ([0-9-]+)/i', + @ibase_errmsg(), $m)) { + return (int)$m[1]; + } + return null; + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones + * + * @param int $nativecode the error code returned by the DBMS + * + * @return int the portable DB error code. Return DB_ERROR if the + * current driver doesn't have a mapping for the + * $nativecode submitted. + * + * @since Method available since Release 1.7.0 + */ + function errorCode($nativecode = null) + { + if (isset($this->errorcode_map[$nativecode])) { + return $this->errorcode_map[$nativecode]; + } + + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/generator .* is not defined/' + => DB_ERROR_SYNTAX, // for compat. w ibase_errcode() + '/table.*(not exist|not found|unknown)/i' + => DB_ERROR_NOSUCHTABLE, + '/table .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* failed attempt to store duplicate value/i' + => DB_ERROR_ALREADY_EXISTS, + '/unsuccessful metadata update .* not found/i' + => DB_ERROR_NOT_FOUND, + '/validation error for column .* value "\*\*\* null/i' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violation of [\w ]+ constraint/i' + => DB_ERROR_CONSTRAINT, + '/conversion error from string/i' + => DB_ERROR_INVALID_NUMBER, + '/no permission for/i' + => DB_ERROR_ACCESS_VIOLATION, + '/arithmetic exception, numeric overflow, or string truncation/i' + => DB_ERROR_INVALID, + '/feature is not supported/i' + => DB_ERROR_NOT_CAPABLE, + ); + } + + $errormsg = @ibase_errmsg(); + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @ibase_query($this->connection, + "SELECT * FROM $result WHERE 1=0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @ibase_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $info = @ibase_field_info($id, $i); + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func($info['name']), + 'type' => $info['type'], + 'len' => $info['length'], + 'flags' => ($got_string) + ? $this->_ibaseFieldFlags($info['name'], $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @ibase_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM ' + . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0'; + case 'views': + return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS'; + case 'users': + return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/ifx.php b/extlib/DB/ifx.php new file mode 100644 index 000000000..baa6f2867 --- /dev/null +++ b/extlib/DB/ifx.php @@ -0,0 +1,683 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's ifx extension + * for interacting with Informix databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: ifx.php,v 1.75 2007/07/06 05:19:21 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's ifx extension + * for interacting with Informix databases + * + * These methods overload the ones declared in DB_common. + * + * More info on Informix errors can be found at: + * http://www.informix.com/answers/english/ierrors.htm + * + * TODO: + * - set needed env Informix vars on connect + * - implement native prepare/execute + * + * @category Database + * @package DB + * @author Tomas V.V.Cox <cox@idecnet.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_ifx extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'ifx'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'ifx'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => 'emulate', + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + '-201' => DB_ERROR_SYNTAX, + '-206' => DB_ERROR_NOSUCHTABLE, + '-217' => DB_ERROR_NOSUCHFIELD, + '-236' => DB_ERROR_VALUE_COUNT_ON_ROW, + '-239' => DB_ERROR_CONSTRAINT, + '-253' => DB_ERROR_SYNTAX, + '-268' => DB_ERROR_CONSTRAINT, + '-292' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-310' => DB_ERROR_ALREADY_EXISTS, + '-316' => DB_ERROR_ALREADY_EXISTS, + '-319' => DB_ERROR_NOT_FOUND, + '-329' => DB_ERROR_NODBSELECTED, + '-346' => DB_ERROR_CONSTRAINT, + '-386' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-391' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-554' => DB_ERROR_SYNTAX, + '-691' => DB_ERROR_CONSTRAINT, + '-692' => DB_ERROR_CONSTRAINT, + '-703' => DB_ERROR_CONSTRAINT_NOT_NULL, + '-1202' => DB_ERROR_DIVZERO, + '-1204' => DB_ERROR_INVALID_DATE, + '-1205' => DB_ERROR_INVALID_DATE, + '-1206' => DB_ERROR_INVALID_DATE, + '-1209' => DB_ERROR_INVALID_DATE, + '-1210' => DB_ERROR_INVALID_DATE, + '-1212' => DB_ERROR_INVALID_DATE, + '-1213' => DB_ERROR_INVALID_NUMBER, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + * @access private + */ + var $affected = 0; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_ifx() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('informix') && + !PEAR::loadExtension('Informix')) + { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : ''; + $dbname = $dsn['database'] ? $dsn['database'] . $dbhost : ''; + $user = $dsn['username'] ? $dsn['username'] : ''; + $pw = $dsn['password'] ? $dsn['password'] : ''; + + $connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect'; + + $this->connection = @$connect_function($dbname, $user, $pw); + if (!is_resource($this->connection)) { + return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @ifx_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $this->affected = null; + if (preg_match('/(SELECT|EXECUTE)/i', $query)) { //TESTME: Use !DB::isManip()? + // the scroll is needed for fetching absolute row numbers + // in a select query result + $result = @ifx_query($query, $this->connection, IFX_SCROLL); + } else { + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @ifx_query('BEGIN WORK', $this->connection); + if (!$result) { + return $this->ifxRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @ifx_query($query, $this->connection); + } + if (!$result) { + return $this->ifxRaiseError(); + } + $this->affected = @ifx_affected_rows($result); + // Determine which queries should return data, and which + // should return an error code only. + if (preg_match('/(SELECT|EXECUTE)/i', $query)) { + return $result; + } + // XXX Testme: free results inside a transaction + // may cause to stop it and commit the work? + + // Result has to be freed even with a insert or update + @ifx_free_result($result); + + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal ifx result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + return $this->affected; + } else { + return 0; + } + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if (($rownum !== null) && ($rownum < 0)) { + return null; + } + if ($rownum === null) { + /* + * Even though fetch_row() should return the next row if + * $rownum is null, it doesn't in all cases. Bug 598. + */ + $rownum = 'NEXT'; + } else { + // Index starts at row 1, unlike most DBMS's starting at 0. + $rownum++; + } + if (!$arr = @ifx_fetch_row($result, $rownum)) { + return null; + } + if ($fetchmode !== DB_FETCHMODE_ASSOC) { + $i=0; + $order = array(); + foreach ($arr as $val) { + $order[$i++] = $val; + } + $arr = $order; + } elseif ($fetchmode == DB_FETCHMODE_ASSOC && + $this->options['portability'] & DB_PORTABILITY_LOWERCASE) + { + $arr = array_change_key_case($arr, CASE_LOWER); + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + if (!$cols = @ifx_num_fields($result)) { + return $this->ifxRaiseError(); + } + return $cols; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? ifx_free_result($result) : false; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = true) + { + // XXX if $this->transaction_opcount > 0, we should probably + // issue a warning here. + $this->autocommit = $onoff ? true : false; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + $result = @ifx_query('COMMIT WORK', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->ifxRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + $result = @ifx_query('ROLLBACK WORK', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->ifxRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ ifxRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_ifx::errorNative(), DB_ifx::errorCode() + */ + function ifxRaiseError($errno = null) + { + if ($errno === null) { + $errno = $this->errorCode(ifx_error()); + } + return $this->raiseError($errno, null, null, null, + $this->errorNative()); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code and message produced by the last query + * + * @return string the DBMS' error code and message + */ + function errorNative() + { + return @ifx_error() . ' ' . @ifx_errormsg(); + } + + // }}} + // {{{ errorCode() + + /** + * Maps native error codes to DB's portable ones. + * + * Requires that the DB implementation's constructor fills + * in the <var>$errorcode_map</var> property. + * + * @param string $nativecode error code returned by the database + * @return int a portable DB error code, or DB_ERROR if this DB + * implementation has no mapping for the given error code. + */ + function errorCode($nativecode) + { + if (ereg('SQLCODE=(.*)]', $nativecode, $match)) { + $code = $match[1]; + if (isset($this->errorcode_map[$code])) { + return $this->errorcode_map[$code]; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' if <var>$result</var> is a table name. + * + * If analyzing a query result and the result has duplicate field names, + * an error will be raised saying + * <samp>can't distinguish duplicate field names</samp>. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.6.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @ifx_query("SELECT * FROM $result WHERE 1=0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + $flds = @ifx_fieldproperties($id); + $count = @ifx_num_fields($id); + + if (count($flds) != $count) { + return $this->raiseError("can't distinguish duplicate field names"); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $i = 0; + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + foreach ($flds as $key => $value) { + $props = explode(';', $value); + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func($key), + 'type' => $props[0], + 'len' => $props[1], + 'flags' => $props[4] == 'N' ? 'not_null' : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + $i++; + } + + // free the result only if we were called on a table + if ($got_string) { + @ifx_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT tabname FROM systables WHERE tabid >= 100'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/msql.php b/extlib/DB/msql.php new file mode 100644 index 000000000..34854f472 --- /dev/null +++ b/extlib/DB/msql.php @@ -0,0 +1,831 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's msql extension + * for interacting with Mini SQL databases + * + * PHP's mSQL extension did weird things with NULL values prior to PHP + * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds + * those versions. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: msql.php,v 1.64 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's msql extension + * for interacting with Mini SQL databases + * + * These methods overload the ones declared in DB_common. + * + * PHP's mSQL extension did weird things with NULL values prior to PHP + * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds + * those versions. + * + * @category Database + * @package DB + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + * @since Class not functional until Release 1.7.0 + */ +class DB_msql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'msql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'msql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * The query result resource created by PHP + * + * Used to make affectedRows() work. Only contains the result for + * data manipulation queries. Contains false for other queries. + * + * @var resource + * @access private + */ + var $_result; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_msql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * Example of how to connect: + * <code> + * require_once 'DB.php'; + * + * // $dsn = 'msql://hostname/dbname'; // use a TCP connection + * $dsn = 'msql:///dbname'; // use a socket + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('msql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['hostspec']) { + $params[] = $dsn['port'] + ? $dsn['hostspec'] . ',' . $dsn['port'] + : $dsn['hostspec']; + } + + $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @msql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if (!@msql_select_db($dsn['database'], $this->connection)) { + return $this->msqlRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @msql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @msql_query($query, $this->connection); + if (!$result) { + return $this->msqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($this->_checkManip($query)) { + $this->_result = $result; + return DB_OK; + } else { + $this->_result = false; + return $result; + } + } + + + // }}} + // {{{ nextResult() + + /** + * Move the internal msql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * PHP's mSQL extension did weird things with NULL values prior to PHP + * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds + * those versions. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@msql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @msql_fetch_array($result, MSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @msql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? msql_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @msql_num_fields($result); + if (!$cols) { + return $this->msqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @msql_num_rows($result); + if ($rows === false) { + return $this->msqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affected() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (!$this->_result) { + return 0; + } + return msql_affected_rows($this->_result); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_msql::createSequence(), DB_msql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = false; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("SELECT _seq FROM ${seqname}"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = true; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + $result->free(); + return $arr[0]; + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * Also creates a new table to associate the sequence with. Uses + * a separate table to ensure portability with other drivers. + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_msql::nextID(), DB_msql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER NOT NULL)'); + if (DB::isError($res)) { + return $res; + } + $res = $this->query("CREATE SEQUENCE ON ${seqname}"); + return $res; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_msql::nextID(), DB_msql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ quoteIdentifier() + + /** + * mSQL does not support delimited identifiers + * + * @param string $str the identifier name to be quoted + * + * @return object a DB_Error object + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.7.0 + */ + function quoteIdentifier($str) + { + return $this->raiseError(DB_ERROR_UNSUPPORTED); + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.7.0 + */ + function escapeSimple($str) + { + return addslashes($str); + } + + // }}} + // {{{ msqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_msql::errorNative(), DB_msql::errorCode() + */ + function msqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @msql_error(); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message + * + * @param string $errormsg the error message returned from the database + * + * @return integer the error number from a DB_ERROR* constant + */ + function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/^Access to database denied/i' + => DB_ERROR_ACCESS_VIOLATION, + '/^Bad index name/i' + => DB_ERROR_ALREADY_EXISTS, + '/^Bad order field/i' + => DB_ERROR_SYNTAX, + '/^Bad type for comparison/i' + => DB_ERROR_SYNTAX, + '/^Can\'t perform LIKE on/i' + => DB_ERROR_SYNTAX, + '/^Can\'t use TEXT fields in LIKE comparison/i' + => DB_ERROR_SYNTAX, + '/^Couldn\'t create temporary table/i' + => DB_ERROR_CANNOT_CREATE, + '/^Error creating table file/i' + => DB_ERROR_CANNOT_CREATE, + '/^Field .* cannot be null$/i' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/^Index (field|condition) .* cannot be null$/i' + => DB_ERROR_SYNTAX, + '/^Invalid date format/i' + => DB_ERROR_INVALID_DATE, + '/^Invalid time format/i' + => DB_ERROR_INVALID, + '/^Literal value for .* is wrong type$/i' + => DB_ERROR_INVALID_NUMBER, + '/^No Database Selected/i' + => DB_ERROR_NODBSELECTED, + '/^No value specified for field/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + '/^Non unique value for unique index/i' + => DB_ERROR_CONSTRAINT, + '/^Out of memory for temporary table/i' + => DB_ERROR_CANNOT_CREATE, + '/^Permission denied/i' + => DB_ERROR_ACCESS_VIOLATION, + '/^Reference to un-selected table/i' + => DB_ERROR_SYNTAX, + '/^syntax error/i' + => DB_ERROR_SYNTAX, + '/^Table .* exists$/i' + => DB_ERROR_ALREADY_EXISTS, + '/^Unknown database/i' + => DB_ERROR_NOSUCHDB, + '/^Unknown field/i' + => DB_ERROR_NOSUCHFIELD, + '/^Unknown (index|system variable)/i' + => DB_ERROR_NOT_FOUND, + '/^Unknown table/i' + => DB_ERROR_NOSUCHTABLE, + '/^Unqualified field/i' + => DB_ERROR_SYNTAX, + ); + } + + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @msql_query("SELECT * FROM $result", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->raiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @msql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $tmp = @msql_fetch_field($id); + + $flags = ''; + if ($tmp->not_null) { + $flags .= 'not_null '; + } + if ($tmp->unique) { + $flags .= 'unique_key '; + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($tmp->table), + 'name' => $case_func($tmp->name), + 'type' => $tmp->type, + 'len' => msql_field_len($id, $i), + 'flags' => $flags, + ); + + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @msql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtain a list of a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return array the array containing the list of objects requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'databases': + $id = @msql_list_dbs($this->connection); + break; + case 'tables': + $id = @msql_list_tables($this->dsn['database'], + $this->connection); + break; + default: + return null; + } + if (!$id) { + return $this->msqlRaiseError(); + } + $out = array(); + while ($row = @msql_fetch_row($id)) { + $out[] = $row[0]; + } + return $out; + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/mssql.php b/extlib/DB/mssql.php new file mode 100644 index 000000000..511a2b686 --- /dev/null +++ b/extlib/DB/mssql.php @@ -0,0 +1,963 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's mssql extension + * for interacting with Microsoft SQL Server databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Sterling Hughes <sterling@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mssql.php,v 1.92 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mssql extension + * for interacting with Microsoft SQL Server databases + * + * These methods overload the ones declared in DB_common. + * + * DB's mssql driver is only for Microsfoft SQL Server databases. + * + * If you're connecting to a Sybase database, you MUST specify "sybase" + * as the "phptype" in the DSN. + * + * This class only works correctly if you have compiled PHP using + * --with-mssql=[dir_to_FreeTDS]. + * + * @category Database + * @package DB + * @author Sterling Hughes <sterling@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_mssql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mssql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mssql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX + var $errorcode_map = array( + 102 => DB_ERROR_SYNTAX, + 110 => DB_ERROR_VALUE_COUNT_ON_ROW, + 155 => DB_ERROR_NOSUCHFIELD, + 156 => DB_ERROR_SYNTAX, + 170 => DB_ERROR_SYNTAX, + 207 => DB_ERROR_NOSUCHFIELD, + 208 => DB_ERROR_NOSUCHTABLE, + 245 => DB_ERROR_INVALID_NUMBER, + 319 => DB_ERROR_SYNTAX, + 321 => DB_ERROR_NOSUCHFIELD, + 325 => DB_ERROR_SYNTAX, + 336 => DB_ERROR_SYNTAX, + 515 => DB_ERROR_CONSTRAINT_NOT_NULL, + 547 => DB_ERROR_CONSTRAINT, + 1018 => DB_ERROR_SYNTAX, + 1035 => DB_ERROR_SYNTAX, + 1913 => DB_ERROR_ALREADY_EXISTS, + 2209 => DB_ERROR_SYNTAX, + 2223 => DB_ERROR_SYNTAX, + 2248 => DB_ERROR_SYNTAX, + 2256 => DB_ERROR_SYNTAX, + 2257 => DB_ERROR_SYNTAX, + 2627 => DB_ERROR_CONSTRAINT, + 2714 => DB_ERROR_ALREADY_EXISTS, + 3607 => DB_ERROR_DIVZERO, + 3701 => DB_ERROR_NOSUCHTABLE, + 7630 => DB_ERROR_SYNTAX, + 8134 => DB_ERROR_DIVZERO, + 9303 => DB_ERROR_SYNTAX, + 9317 => DB_ERROR_SYNTAX, + 9318 => DB_ERROR_SYNTAX, + 9331 => DB_ERROR_SYNTAX, + 9332 => DB_ERROR_SYNTAX, + 15253 => DB_ERROR_SYNTAX, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = null; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_mssql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase') + && !PEAR::loadExtension('sybase_ct')) + { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array( + $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', + $dsn['username'] ? $dsn['username'] : null, + $dsn['password'] ? $dsn['password'] : null, + ); + if ($dsn['port']) { + $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':') + . $dsn['port']; + } + + $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect'; + + $this->connection = @call_user_func_array($connect_function, $params); + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + @mssql_get_last_message()); + } + if ($dsn['database']) { + if (!@mssql_select_db($dsn['database'], $this->connection)) { + return $this->raiseError(DB_ERROR_NODBSELECTED, + null, null, null, + @mssql_get_last_message()); + } + $this->_db = $dsn['database']; + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mssql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mssql_query('BEGIN TRAN', $this->connection); + if (!$result) { + return $this->mssqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @mssql_query($query, $this->connection); + if (!$result) { + return $this->mssqlRaiseError(); + } + // Determine which queries that should return data, and which + // should return an error code only. + return $ismanip ? DB_OK : $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mssql result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return @mssql_next_result($result); + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mssql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mssql_fetch_assoc($result); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mssql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? mssql_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mssql_num_fields($result); + if (!$cols) { + return $this->mssqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mssql_num_rows($result); + if ($rows === false) { + return $this->mssqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + 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() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @mssql_query('COMMIT TRAN', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mssqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @mssql_query('ROLLBACK TRAN', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mssqlRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + $res = @mssql_query('select @@rowcount', $this->connection); + if (!$res) { + return $this->mssqlRaiseError(); + } + $ar = @mssql_fetch_row($res); + if (!$ar) { + $result = 0; + } else { + @mssql_free_result($res); + $result = $ar[0]; + } + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mssql::createSequence(), DB_mssql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) + { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } elseif (!DB::isError($result)) { + $result = $this->query("SELECT IDENT_CURRENT('$seqname')"); + if (DB::isError($result)) { + /* Fallback code for MS SQL Server 7.0, which doesn't have + * IDENT_CURRENT. This is *not* safe for concurrent + * requests, and really, if you're using it, you're in a + * world of hurt. Nevertheless, it's here to ensure BC. See + * bug #181 for the gory details.*/ + $result = $this->query("SELECT @@IDENTITY FROM $seqname"); + } + $repeat = 0; + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $result = $result->fetchRow(DB_FETCHMODE_ORDERED); + return $result[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mssql::nextID(), DB_mssql::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE TABLE ' + . $this->getSequenceName($seq_name) + . ' ([id] [int] IDENTITY (1, 1) NOT NULL,' + . ' [vapor] [int] NULL)'); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mssql::nextID(), DB_mssql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '[' . str_replace(']', ']]', $str) . ']'; + } + + // }}} + // {{{ mssqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mssql::errorNative(), DB_mssql::errorCode() + */ + function mssqlRaiseError($code = null) + { + $message = @mssql_get_last_message(); + if (!$code) { + $code = $this->errorNative(); + } + return $this->raiseError($this->errorCode($code, $message), + null, null, null, "$code - $message"); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection); + if (!$res) { + return DB_ERROR; + } + $row = @mssql_fetch_row($res); + return $row[0]; + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from mssql's native codes. + * + * If <var>$nativecode</var> isn't known yet, it will be looked up. + * + * @param mixed $nativecode mssql error code, if known + * @return integer an error number from a DB error constant + * @see errorNative() + */ + function errorCode($nativecode = null, $msg = '') + { + if (!$nativecode) { + $nativecode = $this->errorNative(); + } + if (isset($this->errorcode_map[$nativecode])) { + if ($nativecode == 3701 + && preg_match('/Cannot drop the index/i', $msg)) + { + return DB_ERROR_NOT_FOUND; + } + return $this->errorcode_map[$nativecode]; + } else { + return DB_ERROR; + } + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + if (!@mssql_select_db($this->_db, $this->connection)) { + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED); + } + $id = @mssql_query("SELECT * FROM $result WHERE 1=0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mssql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + if ($got_string) { + $flags = $this->_mssql_field_flags($result, + @mssql_field_name($id, $i)); + if (DB::isError($flags)) { + return $flags; + } + } else { + $flags = ''; + } + + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@mssql_field_name($id, $i)), + 'type' => @mssql_field_type($id, $i), + 'len' => @mssql_field_length($id, $i), + 'flags' => $flags, + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mssql_free_result($id); + } + return $res; + } + + // }}} + // {{{ _mssql_field_flags() + + /** + * Get a column's flags + * + * Supports "not_null", "primary_key", + * "auto_increment" (mssql identity), "timestamp" (mssql timestamp), + * "unique_key" (mssql unique index, unique check or primary_key) and + * "multiple_key" (multikey index) + * + * mssql timestamp is NOT similar to the mysql timestamp so this is maybe + * not useful at all - is the behaviour of mysql_field_flags that primary + * keys are alway unique? is the interpretation of multiple_key correct? + * + * @param string $table the table name + * @param string $column the field name + * + * @return string the flags + * + * @access private + * @author Joern Barthel <j_barthel@web.de> + */ + function _mssql_field_flags($table, $column) + { + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + + $flags = array(); + $tableName = $table; + + // get unique and primary keys + $res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC); + if (DB::isError($res)) { + return $res; + } + + foreach ($res as $val) { + $keys = explode(', ', $val['index_keys']); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'primary key')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'primary_key'); + } + } elseif (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + // get auto_increment, not_null and timestamp + $res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC); + if (DB::isError($res)) { + return $res; + } + + foreach ($res as $val) { + $val = array_change_key_case($val, CASE_LOWER); + if ($val['nullable'] == '0') { + $this->_add_flag($flags[$val['column_name']], 'not_null'); + } + if (strpos($val['type_name'], 'identity')) { + $this->_add_flag($flags[$val['column_name']], 'auto_increment'); + } + if (strpos($val['type_name'], 'timestamp')) { + $this->_add_flag($flags[$val['column_name']], 'timestamp'); + } + } + } + + if (array_key_exists($column, $flags)) { + return(implode(' ', $flags[$column])); + } + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array &$array the reference to the flag-array + * @param string $value the flag value + * + * @return void + * + * @access private + * @author Joern Barthel <j_barthel@web.de> + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return "SELECT name FROM sysobjects WHERE type = 'U'" + . ' ORDER BY name'; + case 'views': + return "SELECT name FROM sysobjects WHERE type = 'V'"; + default: + return null; + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/mysql.php b/extlib/DB/mysql.php new file mode 100644 index 000000000..c67254520 --- /dev/null +++ b/extlib/DB/mysql.php @@ -0,0 +1,1045 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's mysql extension + * for interacting with MySQL databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysql.php,v 1.126 2007/09/21 13:32:52 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysql extension + * for interacting with MySQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_mysql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.2.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + 1356 => DB_ERROR_DIVZERO, + 1451 => DB_ERROR_CONSTRAINT, + 1452 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_mysql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysql driver supports the following extra DSN options: + * + new_link If set to true, causes subsequent calls to connect() + * to return a new connection link instead of the + * existing one. WARNING: this is not portable to + * other DBMS's. Available since PEAR DB 1.7.0. + * + client_flags Any combination of MYSQL_CLIENT_* constants. + * Only used if PHP is at version 4.3.0 or greater. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $params = array(); + if ($dsn['protocol'] && $dsn['protocol'] == 'unix') { + $params[0] = ':' . $dsn['socket']; + } else { + $params[0] = $dsn['hostspec'] ? $dsn['hostspec'] + : 'localhost'; + if ($dsn['port']) { + $params[0] .= ':' . $dsn['port']; + } + } + $params[] = $dsn['username'] ? $dsn['username'] : null; + $params[] = $dsn['password'] ? $dsn['password'] : null; + + if (!$persistent) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($dsn['client_flags']) + ? $dsn['client_flags'] : null; + } + + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + if (!@mysql_select_db($dsn['database'], $this->connection)) { + return $this->mysqlRaiseError(); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysql_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * Generally uses mysql_query(). If you want to use + * mysql_unbuffered_query() set the "result_buffering" option to 0 using + * setOptions(). This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection); + $result = @mysql_query('BEGIN', $this->connection); + if (!$result) { + return $this->mysqlRaiseError(); + } + } + $this->transaction_opcount++; + } + if (!$this->options['result_buffering']) { + $result = @mysql_unbuffered_query($query, $this->connection); + } else { + $result = @mysql_query($query, $this->connection); + } + if (!$result) { + return $this->mysqlRaiseError(); + } + if (is_resource($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ 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 + * + * @return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysql_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysql_fetch_array($result, MYSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysql_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? mysql_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysql_num_fields($result); + if (!$cols) { + return $this->mysqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysql_num_rows($result); + if ($rows === null) { + return $this->mysqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + 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() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + 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() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + 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() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + return @mysql_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysql::createSequence(), DB_mysql::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("UPDATE ${seqname} ". + 'SET id=LAST_INSERT_ID(id+1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysql_insert_id($this->connection); + if ($id != 0) { + return $id; + } + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, so fill + // it and return 1 and obtain a user-level lock + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + // Failed to get the lock + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ createSequence() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + if (DB::isError($res)) { + return $res; + } + // so reset to zero + return $this->query("UPDATE ${seqname} SET id = 0"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + 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; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * (WARNING: using names that require this is a REALLY BAD IDEA) + * + * WARNING: Older versions of MySQL can't handle the backtick + * character (<kbd>`</kbd>) in table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . str_replace('`', '``', $str) . '`'; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($str, $this->connection); + } else { + return @mysql_escape_string($str); + } + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query) || $this->_next_query_manip) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysql::errorNative(), DB_common::errorCode() + */ + function mysqlRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysql_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysql_errno($this->connection) . ' ** ' . + @mysql_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysql_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + // Fix for bug #11580. + if ($this->_db) { + if (!@mysql_select_db($this->_db, $this->connection)) { + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED); + } + } + + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysql_query("SELECT * FROM $result LIMIT 0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysql_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/mysqli.php b/extlib/DB/mysqli.php new file mode 100644 index 000000000..c6941b170 --- /dev/null +++ b/extlib/DB/mysqli.php @@ -0,0 +1,1092 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's mysqli extension + * for interacting with MySQL databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: mysqli.php,v 1.82 2007/09/21 13:40:41 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's mysqli extension + * for interacting with MySQL databases + * + * This is for MySQL versions 4.1 and above. Requires PHP 5. + * + * Note that persistent connections no longer exist. + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + * @since Class functional since Release 1.6.3 + */ +class DB_mysqli extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'mysqli'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'mysqli'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => false, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1022 => DB_ERROR_ALREADY_EXISTS, + 1044 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1048 => DB_ERROR_CONSTRAINT, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1061 => DB_ERROR_ALREADY_EXISTS, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1091 => DB_ERROR_NOT_FOUND, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1142 => DB_ERROR_ACCESS_VIOLATION, + 1146 => DB_ERROR_NOSUCHTABLE, + 1216 => DB_ERROR_CONSTRAINT, + 1217 => DB_ERROR_CONSTRAINT, + 1356 => DB_ERROR_DIVZERO, + 1451 => DB_ERROR_CONSTRAINT, + 1452 => DB_ERROR_CONSTRAINT, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + /** + * Array for converting MYSQLI_*_FLAG constants to text values + * @var array + * @access public + * @since Property available since Release 1.6.5 + */ + var $mysqli_flags = array( + MYSQLI_NOT_NULL_FLAG => 'not_null', + MYSQLI_PRI_KEY_FLAG => 'primary_key', + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key', + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key', + MYSQLI_BLOB_FLAG => 'blob', + MYSQLI_UNSIGNED_FLAG => 'unsigned', + MYSQLI_ZEROFILL_FLAG => 'zerofill', + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment', + MYSQLI_TIMESTAMP_FLAG => 'timestamp', + MYSQLI_SET_FLAG => 'set', + // MYSQLI_NUM_FLAG => 'numeric', // unnecessary + // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie + MYSQLI_GROUP_FLAG => 'group_by' + ); + + /** + * Array for converting MYSQLI_TYPE_* constants to text values + * @var array + * @access public + * @since Property available since Release 1.6.5 + */ + var $mysqli_types = array( + MYSQLI_TYPE_DECIMAL => 'decimal', + MYSQLI_TYPE_TINY => 'tinyint', + MYSQLI_TYPE_SHORT => 'int', + MYSQLI_TYPE_LONG => 'int', + MYSQLI_TYPE_FLOAT => 'float', + MYSQLI_TYPE_DOUBLE => 'double', + // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it + MYSQLI_TYPE_TIMESTAMP => 'timestamp', + MYSQLI_TYPE_LONGLONG => 'bigint', + MYSQLI_TYPE_INT24 => 'mediumint', + MYSQLI_TYPE_DATE => 'date', + MYSQLI_TYPE_TIME => 'time', + MYSQLI_TYPE_DATETIME => 'datetime', + MYSQLI_TYPE_YEAR => 'year', + MYSQLI_TYPE_NEWDATE => 'date', + MYSQLI_TYPE_ENUM => 'enum', + MYSQLI_TYPE_SET => 'set', + MYSQLI_TYPE_TINY_BLOB => 'tinyblob', + MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob', + MYSQLI_TYPE_LONG_BLOB => 'longblob', + MYSQLI_TYPE_BLOB => 'blob', + MYSQLI_TYPE_VAR_STRING => 'varchar', + MYSQLI_TYPE_STRING => 'char', + MYSQLI_TYPE_GEOMETRY => 'geometry', + /* These constants are conditionally compiled in ext/mysqli, so we'll + * define them by number rather than constant. */ + 16 => 'bit', + 246 => 'decimal', + ); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_mysqli() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's mysqli driver supports the following extra DSN options: + * + When the 'ssl' $option passed to DB::connect() is true: + * + key The path to the key file. + * + cert The path to the certificate file. + * + ca The path to the certificate authority file. + * + capath The path to a directory that contains trusted SSL + * CA certificates in pem format. + * + cipher The list of allowable ciphers for SSL encryption. + * + * Example of how to connect using SSL: + * <code> + * require_once 'DB.php'; + * + * $dsn = array( + * 'phptype' => 'mysqli', + * 'username' => 'someuser', + * 'password' => 'apasswd', + * 'hostspec' => 'localhost', + * 'database' => 'thedb', + * 'key' => 'client-key.pem', + * 'cert' => 'client-cert.pem', + * 'ca' => 'cacert.pem', + * 'capath' => '/path/to/ca/dir', + * 'cipher' => 'AES', + * ); + * + * $options = array( + * 'ssl' => true, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('mysqli')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $ini = ini_get('track_errors'); + @ini_set('track_errors', 1); + $php_errormsg = ''; + + if (((int) $this->getOption('ssl')) === 1) { + $init = mysqli_init(); + mysqli_ssl_set( + $init, + empty($dsn['key']) ? null : $dsn['key'], + empty($dsn['cert']) ? null : $dsn['cert'], + empty($dsn['ca']) ? null : $dsn['ca'], + empty($dsn['capath']) ? null : $dsn['capath'], + empty($dsn['cipher']) ? null : $dsn['cipher'] + ); + if ($this->connection = @mysqli_real_connect( + $init, + $dsn['hostspec'], + $dsn['username'], + $dsn['password'], + $dsn['database'], + $dsn['port'], + $dsn['socket'])) + { + $this->connection = $init; + } + } else { + $this->connection = @mysqli_connect( + $dsn['hostspec'], + $dsn['username'], + $dsn['password'], + $dsn['database'], + $dsn['port'], + $dsn['socket'] + ); + } + + @ini_set('track_errors', $ini); + + if (!$this->connection) { + if (($err = @mysqli_connect_error()) != '') { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $err); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + } + + if ($dsn['database']) { + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @mysqli_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=0'); + $result = @mysqli_query($this->connection, 'BEGIN'); + if (!$result) { + return $this->mysqliRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @mysqli_query($this->connection, $query); + if (!$result) { + return $this->mysqliRaiseError(); + } + if (is_object($result)) { + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal mysql result pointer to the next available result. + * + * This method has not been implemented yet. + * + * @param resource $result a valid sql result resource + * @return false + * @access public + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@mysqli_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @mysqli_fetch_array($result, MYSQLI_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @mysqli_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? mysqli_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @mysqli_num_fields($result); + if (!$cols) { + return $this->mysqliRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @mysqli_num_rows($result); + if ($rows === null) { + return $this->mysqliRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + 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() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysqli_query($this->connection, 'COMMIT'); + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqliRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + $result = @mysqli_query($this->connection, 'ROLLBACK'); + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1'); + $this->transaction_opcount = 0; + if (!$result) { + return $this->mysqliRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + return @mysqli_affected_rows($this->connection); + } else { + return 0; + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_mysqli::createSequence(), DB_mysqli::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query('UPDATE ' . $seqname + . ' SET id = LAST_INSERT_ID(id + 1)'); + $this->popErrorHandling(); + if ($result === DB_OK) { + // COMMON CASE + $id = @mysqli_insert_id($this->connection); + if ($id != 0) { + return $id; + } + + // EMPTY SEQ TABLE + // Sequence table must be empty for some reason, + // so fill it and return 1 + // Obtain a user-level lock + $result = $this->getOne('SELECT GET_LOCK(' + . "'${seqname}_lock', 10)"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + if ($result == 0) { + return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED); + } + + // add the default value + $result = $this->query('REPLACE INTO ' . $seqname + . ' (id) VALUES (0)'); + if (DB::isError($result)) { + return $this->raiseError($result); + } + + // Release the lock + $result = $this->getOne('SELECT RELEASE_LOCK(' + . "'${seqname}_lock')"); + if (DB::isError($result)) { + return $this->raiseError($result); + } + // We know what the result will be, so no need to try again + return 1; + + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + // ONDEMAND TABLE CREATION + $result = $this->createSequence($seq_name); + + // Since createSequence initializes the ID to be 1, + // we do not need to retrieve the ID again (or we will get 2) + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + // First ID of a newly created sequence is 1 + return 1; + } + + } elseif (DB::isError($result) && + $result->getCode() == DB_ERROR_ALREADY_EXISTS) + { + // BACKWARDS COMPAT + // see _BCsequence() comment + $result = $this->_BCsequence($seqname); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $repeat = 1; + } + } while ($repeat); + + return $this->raiseError($result); + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_mysqli::nextID(), DB_mysqli::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $res = $this->query('CREATE TABLE ' . $seqname + . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,' + . ' PRIMARY KEY(id))'); + if (DB::isError($res)) { + return $res; + } + // insert yields value 1, nextId call will generate ID 2 + return $this->query("INSERT INTO ${seqname} (id) VALUES (0)"); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_mysql::nextID(), DB_mysql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ _BCsequence() + + /** + * Backwards compatibility with old sequence emulation implementation + * (clean up the dupes) + * + * @param string $seqname the sequence name to clean up + * + * @return bool true on success. A DB_Error object on failure. + * + * @access private + */ + function _BCsequence($seqname) + { + // Obtain a user-level lock... this will release any previous + // application locks, but unlike LOCK TABLES, it does not abort + // the current transaction and is much less frequently used. + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)"); + if (DB::isError($result)) { + return $result; + } + if ($result == 0) { + // Failed to get the lock, can't do the conversion, bail + // with a DB_ERROR_NOT_LOCKED error + return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED); + } + + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}"); + if (DB::isError($highest_id)) { + return $highest_id; + } + + // This should kill all rows except the highest + // We should probably do something if $highest_id isn't + // numeric, but I'm at a loss as how to handle that... + $result = $this->query('DELETE FROM ' . $seqname + . " WHERE id <> $highest_id"); + if (DB::isError($result)) { + return $result; + } + + // If another thread has been waiting for this lock, + // it will go thru the above procedure, but will have no + // real effect + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')"); + if (DB::isError($result)) { + return $result; + } + return true; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * (WARNING: using names that require this is a REALLY BAD IDEA) + * + * WARNING: Older versions of MySQL can't handle the backtick + * character (<kbd>`</kbd>) in table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + return '`' . str_replace('`', '``', $str) . '`'; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + return @mysqli_real_escape_string($this->connection, $str); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + if (DB::isManip($query) || $this->_next_query_manip) { + return $query . " LIMIT $count"; + } else { + return $query . " LIMIT $from, $count"; + } + } + + // }}} + // {{{ mysqliRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_mysqli::errorNative(), DB_common::errorCode() + */ + function mysqliRaiseError($errno = null) + { + if ($errno === null) { + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL; + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS; + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT; + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS; + } + $errno = $this->errorCode(mysqli_errno($this->connection)); + } + return $this->raiseError($errno, null, null, null, + @mysqli_errno($this->connection) . ' ** ' . + @mysqli_error($this->connection)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code + */ + function errorNative() + { + return @mysqli_errno($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::setOption() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + // Fix for bug #11580. + if ($this->_db) { + if (!@mysqli_select_db($this->connection, $this->_db)) { + return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED); + } + } + + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @mysqli_query($this->connection, + "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_a($id, 'mysqli_result')) { + return $this->mysqliRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @mysqli_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $tmp = @mysqli_fetch_field($id); + + $flags = ''; + foreach ($this->mysqli_flags as $const => $means) { + if ($tmp->flags & $const) { + $flags .= $means . ' '; + } + } + if ($tmp->def) { + $flags .= 'default_' . rawurlencode($tmp->def); + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($tmp->table), + 'name' => $case_func($tmp->name), + 'type' => isset($this->mysqli_types[$tmp->type]) + ? $this->mysqli_types[$tmp->type] + : 'unknown', + // http://bugs.php.net/?id=36579 + 'len' => $tmp->length, + 'flags' => $flags, + ); + + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysqli_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SHOW TABLES'; + case 'users': + return 'SELECT DISTINCT User FROM mysql.user'; + case 'databases': + return 'SHOW DATABASES'; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/oci8.php b/extlib/DB/oci8.php new file mode 100644 index 000000000..d30794871 --- /dev/null +++ b/extlib/DB/oci8.php @@ -0,0 +1,1156 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's oci8 extension + * for interacting with Oracle databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author James L. Pine <jlp@valinux.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: oci8.php,v 1.116 2007/11/28 02:22:39 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's oci8 extension + * for interacting with Oracle databases + * + * Definitely works with versions 8 and 9 of Oracle. + * + * These methods overload the ones declared in DB_common. + * + * Be aware... OCIError() only appears to return anything when given a + * statement, so functions return the generic DB_ERROR instead of more + * useful errors that have to do with feedback from the database. + * + * @category Database + * @package DB + * @author James L. Pine <jlp@valinux.com> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_oci8 extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'oci8'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'oci8'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '5.0.0', + 'numrows' => 'subquery', + 'pconnect' => true, + 'prepare' => true, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + 1 => DB_ERROR_CONSTRAINT, + 900 => DB_ERROR_SYNTAX, + 904 => DB_ERROR_NOSUCHFIELD, + 913 => DB_ERROR_VALUE_COUNT_ON_ROW, + 921 => DB_ERROR_SYNTAX, + 923 => DB_ERROR_SYNTAX, + 942 => DB_ERROR_NOSUCHTABLE, + 955 => DB_ERROR_ALREADY_EXISTS, + 1400 => DB_ERROR_CONSTRAINT_NOT_NULL, + 1401 => DB_ERROR_INVALID, + 1407 => DB_ERROR_CONSTRAINT_NOT_NULL, + 1418 => DB_ERROR_NOT_FOUND, + 1476 => DB_ERROR_DIVZERO, + 1722 => DB_ERROR_INVALID_NUMBER, + 2289 => DB_ERROR_NOSUCHTABLE, + 2291 => DB_ERROR_CONSTRAINT, + 2292 => DB_ERROR_CONSTRAINT, + 2449 => DB_ERROR_CONSTRAINT, + 12899 => DB_ERROR_INVALID, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * Stores the $data passed to execute() in the oci8 driver + * + * Gets reset to array() when simpleQuery() is run. + * + * Needed in case user wants to call numRows() after prepare/execute + * was used. + * + * @var array + * @access private + */ + var $_data = array(); + + /** + * The result or statement handle from the most recently executed query + * @var resource + */ + var $last_stmt; + + /** + * Is the given prepared statement a data manipulation query? + * @var array + * @access private + */ + var $manip_query = array(); + + /** + * Store of prepared SQL queries. + * @var array + * @access private + */ + var $_prepared_queries = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_oci8() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * If PHP is at version 5.0.0 or greater: + * + Generally, oci_connect() or oci_pconnect() are used. + * + But if the new_link DSN option is set to true, oci_new_connect() + * is used. + * + * When using PHP version 4.x, OCILogon() or OCIPLogon() are used. + * + * PEAR DB's oci8 driver supports the following extra DSN options: + * + charset The character set to be used on the connection. + * Only used if PHP is at version 5.0.0 or greater + * and the Oracle server is at 9.2 or greater. + * Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('oci8')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + // Backwards compatibility with DB < 1.7.0 + if (empty($dsn['database']) && !empty($dsn['hostspec'])) { + $db = $dsn['hostspec']; + } else { + $db = $dsn['database']; + } + + if (function_exists('oci_connect')) { + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + $connect_function = 'oci_new_connect'; + } else { + $connect_function = $persistent ? 'oci_pconnect' + : 'oci_connect'; + } + if (isset($this->dsn['port']) && $this->dsn['port']) { + $db = '//'.$db.':'.$this->dsn['port']; + } + + $char = empty($dsn['charset']) ? null : $dsn['charset']; + $this->connection = @$connect_function($dsn['username'], + $dsn['password'], + $db, + $char); + $error = OCIError(); + if (!empty($error) && $error['code'] == 12541) { + // Couldn't find TNS listener. Try direct connection. + $this->connection = @$connect_function($dsn['username'], + $dsn['password'], + null, + $char); + } + } else { + $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon'; + if ($db) { + $this->connection = @$connect_function($dsn['username'], + $dsn['password'], + $db); + } elseif ($dsn['username'] || $dsn['password']) { + $this->connection = @$connect_function($dsn['username'], + $dsn['password']); + } + } + + if (!$this->connection) { + $error = OCIError(); + $error = (is_array($error)) ? $error['message'] : null; + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $error); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + if (function_exists('oci_close')) { + $ret = @oci_close($this->connection); + } else { + $ret = @OCILogOff($this->connection); + } + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * To determine how many rows of a result set get buffered using + * ocisetprefetch(), see the "result_buffering" option in setOptions(). + * This option was added in Release 1.7.0. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->_data = array(); + $this->last_parameters = array(); + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @OCIParse($this->connection, $query); + if (!$result) { + return $this->oci8RaiseError(); + } + if ($this->autocommit) { + $success = @OCIExecute($result,OCI_COMMIT_ON_SUCCESS); + } else { + $success = @OCIExecute($result,OCI_DEFAULT); + } + if (!$success) { + return $this->oci8RaiseError($result); + } + $this->last_stmt = $result; + if ($this->_checkManip($query)) { + return DB_OK; + } else { + @ocisetprefetch($result, $this->options['result_buffering']); + return $result; + } + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal oracle result pointer to the next available result + * + * @param a valid oci8 result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $moredata = @OCIFetchInto($result,$arr,OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && + $moredata) + { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $moredata = OCIFetchInto($result,$arr,OCI_RETURN_NULLS+OCI_RETURN_LOBS); + } + if (!$moredata) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? OCIFreeStatement($result) : false; + } + + /** + * Frees the internal resources associated with a prepared query + * + * @param resource $stmt the prepared statement's resource + * @param bool $free_resource should the PHP resource be freed too? + * Use false if you need to get data + * from the result set later. + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_oci8::prepare() + */ + function freePrepared($stmt, $free_resource = true) + { + if (!is_resource($stmt)) { + return false; + } + if ($free_resource) { + @ocifreestatement($stmt); + } + if (isset($this->prepare_types[(int)$stmt])) { + unset($this->prepare_types[(int)$stmt]); + unset($this->manip_query[(int)$stmt]); + } else { + return false; + } + return true; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * Only works if the DB_PORTABILITY_NUMROWS portability option + * is turned on. + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows(), DB_common::setOption() + */ + function numRows($result) + { + // emulate numRows for Oracle. yuck. + if ($this->options['portability'] & DB_PORTABILITY_NUMROWS && + $result === $this->last_stmt) + { + $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')'; + $save_query = $this->last_query; + $save_stmt = $this->last_stmt; + + $count = $this->query($countquery); + + // Restore the last query and statement. + $this->last_query = $save_query; + $this->last_stmt = $save_stmt; + + if (DB::isError($count) || + DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED))) + { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + return $row[0]; + } + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @OCINumCols($result); + if (!$cols) { + return $this->oci8RaiseError($result); + } + return $cols; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * + * With oci8, this is emulated. + * + * prepare() requires a generic query as string like <code> + * INSERT INTO numbers VALUES (?, ?, ?) + * </code>. The <kbd>?</kbd> characters are placeholders. + * + * Three types of placeholders can be used: + * + <kbd>?</kbd> a quoted scalar value, i.e. strings, integers + * + <kbd>!</kbd> value is inserted 'as is' + * + <kbd>&</kbd> requires a file name. The file's contents get + * inserted into the query (i.e. saving binary + * data in a db) + * + * Use backslashes to escape placeholder characters if you don't want + * them to be interpreted as placeholders. Example: <code> + * "UPDATE foo SET col=? WHERE col='over \& under'" + * </code> + * + * @param string $query the query to be prepared + * + * @return mixed DB statement resource on success. DB_Error on failure. + * + * @see DB_oci8::execute() + */ + function prepare($query) + { + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1, + PREG_SPLIT_DELIM_CAPTURE); + $binds = count($tokens) - 1; + $token = 0; + $types = array(); + $newquery = ''; + + foreach ($tokens as $key => $val) { + switch ($val) { + case '?': + $types[$token++] = DB_PARAM_SCALAR; + unset($tokens[$key]); + break; + case '&': + $types[$token++] = DB_PARAM_OPAQUE; + unset($tokens[$key]); + break; + case '!': + $types[$token++] = DB_PARAM_MISC; + unset($tokens[$key]); + break; + default: + $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val); + if ($key != $binds) { + $newquery .= $tokens[$key] . ':bind' . $token; + } else { + $newquery .= $tokens[$key]; + } + } + } + + $this->last_query = $query; + $newquery = $this->modifyQuery($newquery); + if (!$stmt = @OCIParse($this->connection, $newquery)) { + return $this->oci8RaiseError(); + } + $this->prepare_types[(int)$stmt] = $types; + $this->manip_query[(int)$stmt] = DB::isManip($query); + $this->_prepared_queries[(int)$stmt] = $newquery; + return $stmt; + } + + // }}} + // {{{ execute() + + /** + * Executes a DB statement prepared with prepare(). + * + * To determine how many rows of a result set get buffered using + * ocisetprefetch(), see the "result_buffering" option in setOptions(). + * This option was added in Release 1.7.0. + * + * @param resource $stmt a DB statement resource returned from prepare() + * @param mixed $data array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 for non-array items or the + * quantity of elements in the array. + * + * @return mixed returns an oic8 result resource for successful SELECT + * queries, DB_OK for other successful queries. + * A DB error object is returned on failure. + * + * @see DB_oci8::prepare() + */ + function &execute($stmt, $data = array()) + { + $data = (array)$data; + $this->last_parameters = $data; + $this->last_query = $this->_prepared_queries[(int)$stmt]; + $this->_data = $data; + + $types = $this->prepare_types[(int)$stmt]; + if (count($types) != count($data)) { + $tmp = $this->raiseError(DB_ERROR_MISMATCH); + return $tmp; + } + + $i = 0; + foreach ($data as $key => $value) { + if ($types[$i] == DB_PARAM_MISC) { + /* + * Oracle doesn't seem to have the ability to pass a + * parameter along unchanged, so strip off quotes from start + * and end, plus turn two single quotes to one single quote, + * in order to avoid the quotes getting escaped by + * Oracle and ending up in the database. + */ + $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]); + $data[$key] = str_replace("''", "'", $data[$key]); + } elseif ($types[$i] == DB_PARAM_OPAQUE) { + $fp = @fopen($data[$key], 'rb'); + if (!$fp) { + $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION); + return $tmp; + } + $data[$key] = fread($fp, filesize($data[$key])); + fclose($fp); + } elseif ($types[$i] == DB_PARAM_SCALAR) { + // Floats have to be converted to a locale-neutral + // representation. + if (is_float($data[$key])) { + $data[$key] = $this->quoteFloat($data[$key]); + } + } + if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) { + $tmp = $this->oci8RaiseError($stmt); + return $tmp; + } + $this->last_query = preg_replace("/:bind$i/",$this->quoteSmart($data[$key]),$this->last_query,1); + $i++; + } + if ($this->autocommit) { + $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS); + } else { + $success = @OCIExecute($stmt, OCI_DEFAULT); + } + if (!$success) { + $tmp = $this->oci8RaiseError($stmt); + return $tmp; + } + $this->last_stmt = $stmt; + if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) { + $this->_last_query_manip = true; + $this->_next_query_manip = false; + $tmp = DB_OK; + } else { + $this->_last_query_manip = false; + @ocisetprefetch($stmt, $this->options['result_buffering']); + $tmp = new DB_result($this, $stmt); + } + return $tmp; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + $this->autocommit = (bool)$onoff;; + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + $result = @OCICommit($this->connection); + if (!$result) { + return $this->oci8RaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + $result = @OCIRollback($this->connection); + if (!$result) { + return $this->oci8RaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->last_stmt === false) { + return $this->oci8RaiseError(); + } + $result = @OCIRowCount($this->last_stmt); + if ($result === false) { + return $this->oci8RaiseError($this->last_stmt); + } + return $result; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + */ + function modifyQuery($query) + { + if (preg_match('/^\s*SELECT/i', $query) && + !preg_match('/\sFROM\s/i', $query)) { + $query .= ' FROM dual'; + } + return $query; + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + // Let Oracle return the name of the columns instead of + // coding a "home" SQL parser + + if (count($params)) { + $result = $this->prepare("SELECT * FROM ($query) " + . 'WHERE NULL = NULL'); + $tmp = $this->execute($result, $params); + } else { + $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL"; + + if (!$result = @OCIParse($this->connection, $q_fields)) { + $this->last_query = $q_fields; + return $this->oci8RaiseError(); + } + if (!@OCIExecute($result, OCI_DEFAULT)) { + $this->last_query = $q_fields; + return $this->oci8RaiseError($result); + } + } + + $ncols = OCINumCols($result); + $cols = array(); + for ( $i = 1; $i <= $ncols; $i++ ) { + $cols[] = '"' . OCIColumnName($result, $i) . '"'; + } + $fields = implode(', ', $cols); + // XXX Test that (tip by John Lim) + //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) { + // // Introduce the FIRST_ROWS Oracle query optimizer + // $query = substr($query, strlen($match[0]), strlen($query)); + // $query = "SELECT /* +FIRST_ROWS */ " . $query; + //} + + // Construct the query + // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2 + // Perhaps this could be optimized with the use of Unions + $query = "SELECT $fields FROM". + " (SELECT rownum as linenum, $fields FROM". + " ($query)". + ' WHERE rownum <= '. ($from + $count) . + ') WHERE linenum >= ' . ++$from; + return $query; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_oci8::createSequence(), DB_oci8::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = 0; + do { + $this->expectError(DB_ERROR_NOSUCHTABLE); + $result = $this->query("SELECT ${seqname}.nextval FROM dual"); + $this->popExpect(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } else { + $repeat = 0; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED); + return $arr[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_oci8::nextID(), DB_oci8::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_oci8::nextID(), DB_oci8::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ oci8RaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_oci8::errorNative(), DB_oci8::errorCode() + */ + function oci8RaiseError($errno = null) + { + if ($errno === null) { + $error = @OCIError($this->connection); + return $this->raiseError($this->errorCode($error['code']), + null, null, null, $error['message']); + } elseif (is_resource($errno)) { + $error = @OCIError($errno); + return $this->raiseError($this->errorCode($error['code']), + null, null, null, $error['message']); + } + return $this->raiseError($this->errorCode($errno)); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code produced by the last query + * + * @return int the DBMS' error code. FALSE if the code could not be + * determined + */ + function errorNative() + { + if (is_resource($this->last_stmt)) { + $error = @OCIError($this->last_stmt); + } else { + $error = @OCIError($this->connection); + } + if (is_array($error)) { + return $error['code']; + } + return false; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * NOTE: flags won't contain index information. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $res = array(); + + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $result = strtoupper($result); + $q_fields = 'SELECT column_name, data_type, data_length, ' + . 'nullable ' + . 'FROM user_tab_columns ' + . "WHERE table_name='$result' ORDER BY column_id"; + + $this->last_query = $q_fields; + + if (!$stmt = @OCIParse($this->connection, $q_fields)) { + return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA); + } + if (!@OCIExecute($stmt, OCI_DEFAULT)) { + return $this->oci8RaiseError($stmt); + } + + $i = 0; + while (@OCIFetch($stmt)) { + $res[$i] = array( + 'table' => $case_func($result), + 'name' => $case_func(@OCIResult($stmt, 1)), + 'type' => @OCIResult($stmt, 2), + 'len' => @OCIResult($stmt, 3), + 'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + $i++; + } + + if ($mode) { + $res['num_fields'] = $i; + } + @OCIFreeStatement($stmt); + + } else { + if (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $result = $result->result; + } + + $res = array(); + + if ($result === $this->last_stmt) { + $count = @OCINumCols($result); + if ($mode) { + $res['num_fields'] = $count; + } + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => '', + 'name' => $case_func(@OCIColumnName($result, $i+1)), + 'type' => @OCIColumnType($result, $i+1), + 'len' => @OCIColumnSize($result, $i+1), + 'flags' => '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + } else { + return $this->raiseError(DB_ERROR_NOT_CAPABLE); + } + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT table_name FROM user_tables'; + case 'synonyms': + return 'SELECT synonym_name FROM user_synonyms'; + case 'views': + return 'SELECT view_name FROM user_views'; + default: + return null; + } + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/odbc.php b/extlib/DB/odbc.php new file mode 100644 index 000000000..eba43659a --- /dev/null +++ b/extlib/DB/odbc.php @@ -0,0 +1,883 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's odbc extension + * for interacting with databases via ODBC connections + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: odbc.php,v 1.81 2007/07/06 05:19:21 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's odbc extension + * for interacting with databases via ODBC connections + * + * These methods overload the ones declared in DB_common. + * + * More info on ODBC errors could be found here: + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp + * + * @category Database + * @package DB + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_odbc extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'odbc'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'sql92'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * NOTE: The feature set of the following drivers are different than + * the default: + * + solid: 'transactions' = true + * + navision: 'limit' = false + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + '01004' => DB_ERROR_TRUNCATED, + '07001' => DB_ERROR_MISMATCH, + '21S01' => DB_ERROR_VALUE_COUNT_ON_ROW, + '21S02' => DB_ERROR_MISMATCH, + '22001' => DB_ERROR_INVALID, + '22003' => DB_ERROR_INVALID_NUMBER, + '22005' => DB_ERROR_INVALID_NUMBER, + '22008' => DB_ERROR_INVALID_DATE, + '22012' => DB_ERROR_DIVZERO, + '23000' => DB_ERROR_CONSTRAINT, + '23502' => DB_ERROR_CONSTRAINT_NOT_NULL, + '23503' => DB_ERROR_CONSTRAINT, + '23504' => DB_ERROR_CONSTRAINT, + '23505' => DB_ERROR_CONSTRAINT, + '24000' => DB_ERROR_INVALID, + '34000' => DB_ERROR_INVALID, + '37000' => DB_ERROR_SYNTAX, + '42000' => DB_ERROR_SYNTAX, + '42601' => DB_ERROR_SYNTAX, + 'IM001' => DB_ERROR_UNSUPPORTED, + 'S0000' => DB_ERROR_NOSUCHTABLE, + 'S0001' => DB_ERROR_ALREADY_EXISTS, + 'S0002' => DB_ERROR_NOSUCHTABLE, + 'S0011' => DB_ERROR_ALREADY_EXISTS, + 'S0012' => DB_ERROR_NOT_FOUND, + 'S0021' => DB_ERROR_ALREADY_EXISTS, + 'S0022' => DB_ERROR_NOSUCHFIELD, + 'S1009' => DB_ERROR_INVALID, + 'S1090' => DB_ERROR_INVALID, + 'S1C00' => DB_ERROR_NOT_CAPABLE, + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * The number of rows affected by a data manipulation query + * @var integer + * @access private + */ + var $affected = 0; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_odbc() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's odbc driver supports the following extra DSN options: + * + cursor The type of cursor to be used for this connection. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('odbc')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + switch ($this->dbsyntax) { + case 'access': + case 'db2': + case 'solid': + $this->features['transactions'] = true; + break; + case 'navision': + $this->features['limit'] = false; + } + + /* + * This is hear for backwards compatibility. Should have been using + * 'database' all along, but prior to 1.6.0RC3 'hostspec' was used. + */ + if ($dsn['database']) { + $odbcdsn = $dsn['database']; + } elseif ($dsn['hostspec']) { + $odbcdsn = $dsn['hostspec']; + } else { + $odbcdsn = 'localhost'; + } + + $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect'; + + if (empty($dsn['cursor'])) { + $this->connection = @$connect_function($odbcdsn, $dsn['username'], + $dsn['password']); + } else { + $this->connection = @$connect_function($odbcdsn, $dsn['username'], + $dsn['password'], + $dsn['cursor']); + } + + if (!is_resource($this->connection)) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $this->errorNative()); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $err = @odbc_close($this->connection); + $this->connection = null; + return $err; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $this->last_query = $query; + $query = $this->modifyQuery($query); + $result = @odbc_exec($this->connection, $query); + if (!$result) { + return $this->odbcRaiseError(); // XXX ERRORMSG + } + // Determine which queries that should return data, and which + // should return an error code only. + if ($this->_checkManip($query)) { + $this->affected = $result; // For affectedRows() + return DB_OK; + } + $this->affected = 0; + return $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal odbc result pointer to the next available result + * + * @param a valid fbsql result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return @odbc_next_result($result); + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $arr = array(); + if ($rownum !== null) { + $rownum++; // ODBC first row is 1 + if (version_compare(phpversion(), '4.2.0', 'ge')) { + $cols = @odbc_fetch_into($result, $arr, $rownum); + } else { + $cols = @odbc_fetch_into($result, $rownum, $arr); + } + } else { + $cols = @odbc_fetch_into($result, $arr); + } + if (!$cols) { + return null; + } + if ($fetchmode !== DB_FETCHMODE_ORDERED) { + for ($i = 0; $i < count($arr); $i++) { + $colName = @odbc_field_name($result, $i+1); + $a[$colName] = $arr[$i]; + } + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $a = array_change_key_case($a, CASE_LOWER); + } + $arr = $a; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? odbc_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @odbc_num_fields($result); + if (!$cols) { + return $this->odbcRaiseError(); + } + return $cols; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if (empty($this->affected)) { // In case of SELECT stms + return 0; + } + $nrows = @odbc_num_rows($this->affected); + if ($nrows == -1) { + return $this->odbcRaiseError(); + } + return $nrows; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * Not all ODBC drivers support this functionality. If they don't + * a DB_Error object for DB_ERROR_UNSUPPORTED is returned. + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $nrows = @odbc_num_rows($result); + if ($nrows == -1) { + return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED); + } + if ($nrows === false) { + return $this->odbcRaiseError(); + } + return $nrows; + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quotes a string so it can be safely used as a table or column name + * + * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked + * "Use ANSI quoted identifiers" when setting up the ODBC data source. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @see DB_common::quoteIdentifier() + * @since Method available since Release 1.6.0 + */ + function quoteIdentifier($str) + { + switch ($this->dsn['dbsyntax']) { + case 'access': + return '[' . $str . ']'; + case 'mssql': + case 'sybase': + return '[' . str_replace(']', ']]', $str) . ']'; + case 'mysql': + case 'mysqli': + return '`' . $str . '`'; + default: + return '"' . str_replace('"', '""', $str) . '"'; + } + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_odbc::createSequence(), DB_odbc::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("update ${seqname} set id = id + 1"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) { + $repeat = 1; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->createSequence($seq_name); + $this->popErrorHandling(); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $result = $this->query("insert into ${seqname} (id) values(0)"); + } else { + $repeat = 0; + } + } while ($repeat); + + if (DB::isError($result)) { + return $this->raiseError($result); + } + + $result = $this->query("select id from ${seqname}"); + if (DB::isError($result)) { + return $result; + } + + $row = $result->fetchRow(DB_FETCHMODE_ORDERED); + if (DB::isError($row || !$row)) { + return $row; + } + + return $row[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_odbc::nextID(), DB_odbc::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE TABLE ' + . $this->getSequenceName($seq_name) + . ' (id integer NOT NULL,' + . ' PRIMARY KEY(id))'); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_odbc::nextID(), DB_odbc::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + function autoCommit($onoff = false) + { + if (!@odbc_autocommit($this->connection, $onoff)) { + return $this->odbcRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ commit() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if (!@odbc_commit($this->connection)) { + return $this->odbcRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if (!@odbc_rollback($this->connection)) { + return $this->odbcRaiseError(); + } + return DB_OK; + } + + // }}} + // {{{ odbcRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_odbc::errorNative(), DB_common::errorCode() + */ + function odbcRaiseError($errno = null) + { + if ($errno === null) { + switch ($this->dbsyntax) { + case 'access': + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) { + $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD; + } else { + // Doing this in case mode changes during runtime. + $this->errorcode_map['07001'] = DB_ERROR_MISMATCH; + } + + $native_code = odbc_error($this->connection); + + // S1000 is for "General Error." Let's be more specific. + if ($native_code == 'S1000') { + $errormsg = odbc_errormsg($this->connection); + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/includes related records.$/i' => DB_ERROR_CONSTRAINT, + '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $this->raiseError($code, + null, null, null, + $native_code . ' ' . $errormsg); + } + } + $errno = DB_ERROR; + } else { + $errno = $this->errorCode($native_code); + } + break; + default: + $errno = $this->errorCode(odbc_error($this->connection)); + } + } + return $this->raiseError($errno, null, null, null, + $this->errorNative()); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error code and message produced by the last query + * + * @return string the DBMS' error code and message + */ + function errorNative() + { + if (!is_resource($this->connection)) { + return @odbc_error() . ' ' . @odbc_errormsg(); + } + return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection); + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @odbc_exec($this->connection, "SELECT * FROM $result"); + if (!$id) { + return $this->odbcRaiseError(); + } + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @odbc_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $col = $i + 1; + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@odbc_field_name($id, $col)), + 'type' => @odbc_field_type($id, $col), + 'len' => @odbc_field_len($id, $col), + 'flags' => '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @odbc_free_result($id); + } + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com. + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the list of objects requested + * + * @access protected + * @see DB_common::getListOf() + * @since Method available since Release 1.7.0 + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'databases': + if (!function_exists('odbc_data_source')) { + return null; + } + $res = @odbc_data_source($this->connection, SQL_FETCH_FIRST); + if (is_array($res)) { + $out = array($res['server']); + while($res = @odbc_data_source($this->connection, + SQL_FETCH_NEXT)) + { + $out[] = $res['server']; + } + return $out; + } else { + return $this->odbcRaiseError(); + } + break; + case 'tables': + case 'schema.tables': + $keep = 'TABLE'; + break; + case 'views': + $keep = 'VIEW'; + break; + default: + return null; + } + + /* + * Removing non-conforming items in the while loop rather than + * in the odbc_tables() call because some backends choke on this: + * odbc_tables($this->connection, '', '', '', 'TABLE') + */ + $res = @odbc_tables($this->connection); + if (!$res) { + return $this->odbcRaiseError(); + } + $out = array(); + while ($row = odbc_fetch_array($res)) { + if ($row['TABLE_TYPE'] != $keep) { + continue; + } + if ($type == 'schema.tables') { + $out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME']; + } else { + $out[] = $row['TABLE_NAME']; + } + } + return $out; + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/pgsql.php b/extlib/DB/pgsql.php new file mode 100644 index 000000000..6030bb4c1 --- /dev/null +++ b/extlib/DB/pgsql.php @@ -0,0 +1,1135 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Rui Hirokawa <hirokawa@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: pgsql.php,v 1.139 2007/11/28 02:19:44 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's pgsql extension + * for interacting with PostgreSQL databases + * + * These methods overload the ones declared in DB_common. + * + * @category Database + * @package DB + * @author Rui Hirokawa <hirokawa@php.net> + * @author Stig Bakken <ssb@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_pgsql extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'pgsql'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'pgsql'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => '4.3.0', + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => true, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The number of rows affected by a data manipulation query + * @var integer + */ + var $affected = 0; + + /** + * The current row being looked at in fetchInto() + * @var array + * @access private + */ + var $row = array(); + + /** + * The number of rows in a given result set + * @var array + * @access private + */ + var $_num_rows = array(); + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_pgsql() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's pgsql driver supports the following extra DSN options: + * + connect_timeout How many seconds to wait for a connection to + * be established. Available since PEAR DB 1.7.0. + * + new_link If set to true, causes subsequent calls to + * connect() to return a new connection link + * instead of the existing one. WARNING: this is + * not portable to other DBMS's. Available only + * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0. + * + options Command line options to be sent to the server. + * Available since PEAR DB 1.6.4. + * + service Specifies a service name in pg_service.conf that + * holds additional connection parameters. + * Available since PEAR DB 1.7.0. + * + sslmode How should SSL be used when connecting? Values: + * disable, allow, prefer or require. + * Available since PEAR DB 1.7.0. + * + tty This was used to specify where to send server + * debug output. Available since PEAR DB 1.6.4. + * + * Example of connecting to a new link via a socket: + * <code> + * require_once 'DB.php'; + * + * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('pgsql')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp'; + + $params = array(''); + if ($protocol == 'tcp') { + if ($dsn['hostspec']) { + $params[0] .= 'host=' . $dsn['hostspec']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } elseif ($protocol == 'unix') { + // Allow for pg socket in non-standard locations. + if ($dsn['socket']) { + $params[0] .= 'host=' . $dsn['socket']; + } + if ($dsn['port']) { + $params[0] .= ' port=' . $dsn['port']; + } + } + if ($dsn['database']) { + $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\''; + } + if ($dsn['username']) { + $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\''; + } + if ($dsn['password']) { + $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\''; + } + if (!empty($dsn['options'])) { + $params[0] .= ' options=' . $dsn['options']; + } + if (!empty($dsn['tty'])) { + $params[0] .= ' tty=' . $dsn['tty']; + } + if (!empty($dsn['connect_timeout'])) { + $params[0] .= ' connect_timeout=' . $dsn['connect_timeout']; + } + if (!empty($dsn['sslmode'])) { + $params[0] .= ' sslmode=' . $dsn['sslmode']; + } + if (!empty($dsn['service'])) { + $params[0] .= ' service=' . $dsn['service']; + } + + if (isset($dsn['new_link']) + && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true)) + { + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = PGSQL_CONNECT_FORCE_NEW; + } + } + + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect'; + + $ini = ini_get('track_errors'); + $php_errormsg = ''; + if ($ini) { + $this->connection = @call_user_func_array($connect_function, + $params); + } else { + @ini_set('track_errors', 1); + $this->connection = @call_user_func_array($connect_function, + $params); + @ini_set('track_errors', $ini); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @pg_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @pg_exec($this->connection, 'begin;'); + if (!$result) { + return $this->pgsqlRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @pg_exec($this->connection, $query); + if (!$result) { + return $this->pgsqlRaiseError(); + } + + /* + * Determine whether queries produce affected rows, result or nothing. + * + * This logic was introduced in version 1.1 of the file by ssb, + * though the regex has been modified slightly since then. + * + * PostgreSQL commands: + * ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY, + * CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH, + * GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET, + * REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW, + * UNLISTEN, UPDATE, VACUUM + */ + if ($ismanip) { + $this->affected = @pg_affected_rows($result); + return DB_OK; + } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|FETCH|SHOW)\s/si', + $query)) + { + $this->row[(int)$result] = 0; // reset the row counter. + $numrows = $this->numRows($result); + if (is_object($numrows)) { + return $numrows; + } + $this->_num_rows[(int)$result] = $numrows; + $this->affected = 0; + return $result; + } else { + $this->affected = 0; + return DB_OK; + } + } + + // }}} + // {{{ nextResult() + + /** + * 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; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + $result_int = (int)$result; + $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int]; + if ($rownum >= $this->_num_rows[$result_int]) { + return null; + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @pg_fetch_row($result, $rownum); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + $this->row[$result_int] = ++$rownum; + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + if (is_resource($result)) { + unset($this->row[(int)$result]); + unset($this->_num_rows[(int)$result]); + $this->affected = 0; + return @pg_freeresult($result); + } + return false; + } + + // }}} + // {{{ quote() + + /** + * @deprecated Deprecated in release 1.6.0 + * @internal + */ + function quote($str) + { + return $this->quoteSmart($str); + } + + // }}} + // {{{ quoteBoolean() + + /** + * Formats a boolean value for use within a query in a locale-independent + * manner. + * + * @param boolean the boolean value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteBoolean($boolean) { + return $boolean ? 'TRUE' : 'FALSE'; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * {@internal PostgreSQL treats a backslash as an escape character, + * so they are escaped as well. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @see DB_common::quoteSmart() + * @since Method available since Release 1.6.0 + */ + function escapeSimple($str) + { + if (function_exists('pg_escape_string')) { + /* This fixes an undocumented BC break in PHP 5.2.0 which changed + * the prototype of pg_escape_string. I'm not thrilled about having + * to sniff the PHP version, quite frankly, but it's the only way + * to deal with the problem. Revision 1.331.2.13.2.10 on + * php-src/ext/pgsql/pgsql.c (PHP_5_2 branch) is to blame, for the + * record. */ + if (version_compare(PHP_VERSION, '5.2.0', '>=')) { + return pg_escape_string($this->connection, $str); + } else { + return pg_escape_string($str); + } + } else { + return str_replace("'", "''", str_replace('\\', '\\\\', $str)); + } + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @pg_numfields($result); + if (!$cols) { + return $this->pgsqlRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @pg_numrows($result); + if ($rows === null) { + return $this->pgsqlRaiseError(); + } + return $rows; + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + 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() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + 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() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + 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() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return $this->affected; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_pgsql::createSequence(), DB_pgsql::dropSequence() + */ + 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() + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $result = $this->query("CREATE SEQUENCE ${seqname}"); + return $result; + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_pgsql::nextID(), DB_pgsql::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP SEQUENCE ' + . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ pgsqlRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_pgsql::errorNative(), DB_pgsql::errorCode() + */ + function pgsqlRaiseError($errno = null) + { + $native = $this->errorNative(); + if (!$native) { + $native = 'Database connection has been lost.'; + $errno = DB_ERROR_CONNECT_FAILED; + } + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal Error messages are used instead of error codes + * in order to support older versions of PostgreSQL.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @pg_errormessage($this->connection); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + if (!isset($error_regexps)) { + $error_regexps = array( + '/column .* (of relation .*)?does not exist/i' + => DB_ERROR_NOSUCHFIELD, + '/(relation|sequence|table).*does not exist|class .* not found/i' + => DB_ERROR_NOSUCHTABLE, + '/index .* does not exist/' + => DB_ERROR_NOT_FOUND, + '/relation .* already exists/i' + => DB_ERROR_ALREADY_EXISTS, + '/(divide|division) by zero$/i' + => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /i' + => DB_ERROR_INVALID_NUMBER, + '/invalid input syntax for( type)? (integer|numeric)/i' + => DB_ERROR_INVALID_NUMBER, + '/value .* is out of range for type \w*int/i' + => DB_ERROR_INVALID_NUMBER, + '/integer out of range/i' + => DB_ERROR_INVALID_NUMBER, + '/value too long for type character/i' + => DB_ERROR_INVALID, + '/attribute .* not found|relation .* does not have attribute/i' + => DB_ERROR_NOSUCHFIELD, + '/column .* specified in USING clause does not exist in (left|right) table/i' + => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near/i' + => DB_ERROR_SYNTAX, + '/syntax error at/' + => DB_ERROR_SYNTAX, + '/column reference .* is ambiguous/i' + => DB_ERROR_SYNTAX, + '/permission denied/' + => DB_ERROR_ACCESS_VIOLATION, + '/violates not-null constraint/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/violates [\w ]+ constraint/' + => DB_ERROR_CONSTRAINT, + '/referential integrity violation/' + => DB_ERROR_CONSTRAINT, + '/more expressions than target columns/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0"); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @pg_numfields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $got_string ? $case_func($result) : '', + 'name' => $case_func(@pg_fieldname($id, $i)), + 'type' => @pg_fieldtype($id, $i), + 'len' => @pg_fieldsize($id, $i), + 'flags' => $got_string + ? $this->_pgFieldFlags($id, $i, $result) + : '', + ); + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @pg_freeresult($id); + } + return $res; + } + + // }}} + // {{{ _pgFieldFlags() + + /** + * Get a column's flags + * + * Supports "not_null", "default_value", "primary_key", "unique_key" + * and "multiple_key". The default value is passed through + * rawurlencode() in case there are spaces in it. + * + * @param int $resource the PostgreSQL result identifier + * @param int $num_field the field number + * + * @return string the flags + * + * @access private + */ + function _pgFieldFlags($resource, $num_field, $table_name) + { + $field_name = @pg_fieldname($resource, $num_field); + + // Check if there's a schema in $table_name and update things + // accordingly. + $from = 'pg_attribute f, pg_class tab, pg_type typ'; + if (strpos($table_name, '.') !== false) { + $from .= ', pg_namespace nsp'; + list($schema, $table) = explode('.', $table_name); + $tableWhere = "tab.relname = '$table' AND tab.relnamespace = nsp.oid AND nsp.nspname = '$schema'"; + } else { + $tableWhere = "tab.relname = '$table_name'"; + } + + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef + FROM $from + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attname = '$field_name' + AND $tableWhere"); + if (@pg_numrows($result) > 0) { + $row = @pg_fetch_row($result, 0); + $flags = ($row[0] == 't') ? 'not_null ' : ''; + + if ($row[1] == 't') { + $result = @pg_exec($this->connection, "SELECT a.adsrc + FROM $from, pg_attrdef a + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid + AND f.attrelid = a.adrelid AND f.attname = '$field_name' + AND $tableWhere AND f.attnum = a.adnum"); + $row = @pg_fetch_row($result, 0); + $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]); + $flags .= 'default_' . rawurlencode($num) . ' '; + } + } else { + $flags = ''; + } + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey + FROM $from, pg_index i + WHERE tab.relname = typ.typname + AND typ.typrelid = f.attrelid + AND f.attrelid = i.indrelid + AND f.attname = '$field_name' + AND $tableWhere"); + $count = @pg_numrows($result); + + for ($i = 0; $i < $count ; $i++) { + $row = @pg_fetch_row($result, $i); + $keys = explode(' ', $row[2]); + + if (in_array($num_field + 1, $keys)) { + $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : ''; + $flags .= ($row[1] == 't') ? 'primary_key ' : ''; + if (count($keys) > 1) + $flags .= 'multiple_key '; + } + } + + return trim($flags); + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return 'SELECT c.relname AS "Name"' + . ' FROM pg_class c, pg_user u' + . ' WHERE c.relowner = u.usesysid' + . " AND c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . " AND c.relname !~ '^(pg_|sql_)'" + . ' UNION' + . ' SELECT c.relname AS "Name"' + . ' FROM pg_class c' + . " WHERE c.relkind = 'r'" + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_views' + . ' WHERE viewname = c.relname)' + . ' AND NOT EXISTS' + . ' (SELECT 1 FROM pg_user' + . ' WHERE usesysid = c.relowner)' + . " AND c.relname !~ '^pg_'"; + case 'schema.tables': + return "SELECT schemaname || '.' || tablename" + . ' AS "Name"' + . ' FROM pg_catalog.pg_tables' + . ' WHERE schemaname NOT IN' + . " ('pg_catalog', 'information_schema', 'pg_toast')"; + case 'schema.views': + return "SELECT schemaname || '.' || viewname from pg_views WHERE schemaname" + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'views': + // Table cols: viewname | viewowner | definition + return 'SELECT viewname from pg_views WHERE schemaname' + . " NOT IN ('information_schema', 'pg_catalog')"; + case 'users': + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil + return 'SELECT usename FROM pg_user'; + case 'databases': + return 'SELECT datname FROM pg_database'; + case 'functions': + case 'procedures': + return 'SELECT proname FROM pg_proc WHERE proowner <> 1'; + default: + return null; + } + } + + // }}} + // {{{ _checkManip() + + /** + * Checks if the given query is a manipulation query. This also takes into + * account the _next_query_manip flag and sets the _last_query_manip flag + * (and resets _next_query_manip) according to the result. + * + * @param string The query to check. + * + * @return boolean true if the query is a manipulation query, false + * otherwise + * + * @access protected + */ + function _checkManip($query) + { + return (preg_match('/^\s*(SAVEPOINT|RELEASE)\s+/i', $query) + || parent::_checkManip($query)); + } + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/sqlite.php b/extlib/DB/sqlite.php new file mode 100644 index 000000000..5c4b396e5 --- /dev/null +++ b/extlib/DB/sqlite.php @@ -0,0 +1,960 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's sqlite extension + * for interacting with SQLite databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Urs Gehrig <urs@circle.ch> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 + * @version CVS: $Id: sqlite.php,v 1.118 2007/11/26 22:57:18 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's sqlite extension + * for interacting with SQLite databases + * + * These methods overload the ones declared in DB_common. + * + * NOTICE: This driver needs PHP's track_errors ini setting to be on. + * It is automatically turned on when connecting to the database. + * Make sure your scripts don't turn it off. + * + * @category Database + * @package DB + * @author Urs Gehrig <urs@circle.ch> + * @author Mika Tuupola <tuupola@appelsiini.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_sqlite extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'sqlite'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'sqlite'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'alter', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => false, + ); + + /** + * A mapping of native error codes to DB error codes + * + * {@internal Error codes according to sqlite_exec. See the online + * manual at http://sqlite.org/c_interface.html for info. + * This error handling based on sqlite_exec is not yet implemented.}} + * + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * SQLite data types + * + * @link http://www.sqlite.org/datatypes.html + * + * @var array + */ + var $keywords = array ( + 'BLOB' => '', + 'BOOLEAN' => '', + 'CHARACTER' => '', + 'CLOB' => '', + 'FLOAT' => '', + 'INTEGER' => '', + 'KEY' => '', + 'NATIONAL' => '', + 'NUMERIC' => '', + 'NVARCHAR' => '', + 'PRIMARY' => '', + 'TEXT' => '', + 'TIMESTAMP' => '', + 'UNIQUE' => '', + 'VARCHAR' => '', + 'VARYING' => '', + ); + + /** + * The most recent error message from $php_errormsg + * @var string + * @access private + */ + var $_lasterror = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_sqlite() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's sqlite driver supports the following extra DSN options: + * + mode The permissions for the database file, in four digit + * chmod octal format (eg "0600"). + * + * Example of connecting to a database in read-only mode: + * <code> + * require_once 'DB.php'; + * + * $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400'; + * $options = array( + * 'portability' => DB_PORTABILITY_ALL, + * ); + * + * $db = DB::connect($dsn, $options); + * if (PEAR::isError($db)) { + * die($db->getMessage()); + * } + * </code> + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('sqlite')) { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + if (!$dsn['database']) { + return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION); + } + + if ($dsn['database'] !== ':memory:') { + if (!file_exists($dsn['database'])) { + if (!touch($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); + } + if (!isset($dsn['mode']) || + !is_numeric($dsn['mode'])) + { + $mode = 0644; + } else { + $mode = octdec($dsn['mode']); + } + if (!chmod($dsn['database'], $mode)) { + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); + } + if (!file_exists($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND); + } + } + if (!is_file($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_INVALID); + } + if (!is_readable($dsn['database'])) { + return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION); + } + } + + $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open'; + + // track_errors must remain on for simpleQuery() + @ini_set('track_errors', 1); + $php_errormsg = ''; + + if (!$this->connection = @$connect_function($dsn['database'])) { + return $this->raiseError(DB_ERROR_NODBSELECTED, + null, null, null, + $php_errormsg); + } + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @sqlite_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * NOTICE: This method needs PHP's track_errors ini setting to be on. + * It is automatically turned on when connecting to the database. + * Make sure your scripts don't turn it off. + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + $query = $this->modifyQuery($query); + + $php_errormsg = ''; + + $result = @sqlite_query($query, $this->connection); + $this->_lasterror = $php_errormsg ? $php_errormsg : ''; + + $this->result = $result; + if (!$this->result) { + return $this->sqliteRaiseError(null); + } + + // sqlite_query() seems to allways return a resource + // so cant use that. Using $ismanip instead + if (!$ismanip) { + $numRows = $this->numRows($result); + if (is_object($numRows)) { + // we've got PEAR_Error + return $numRows; + } + return $result; + } + return DB_OK; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal sqlite result pointer to the next available result + * + * @param resource $result the valid sqlite result resource + * + * @return bool true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@sqlite_seek($this->result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + $arr = @sqlite_fetch_array($result, SQLITE_ASSOC); + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + + /* Remove extraneous " characters from the fields in the result. + * Fixes bug #11716. */ + if (is_array($arr) && count($arr) > 0) { + $strippedArr = array(); + foreach ($arr as $field => $value) { + $strippedArr[trim($field, '"')] = $value; + } + $arr = $strippedArr; + } + } else { + $arr = @sqlite_fetch_array($result, SQLITE_NUM); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + /* + * Even though this DBMS already trims output, we do this because + * a field might have intentional whitespace at the end that + * gets removed by DB_PORTABILITY_RTRIM under another driver. + */ + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult(&$result) + { + // XXX No native free? + if (!is_resource($result)) { + return false; + } + $result = null; + return true; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @sqlite_num_fields($result); + if (!$cols) { + return $this->sqliteRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @sqlite_num_rows($result); + if ($rows === null) { + return $this->sqliteRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affected() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + return @sqlite_changes($this->connection); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_sqlite::nextID(), DB_sqlite::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_sqlite::nextID(), DB_sqlite::dropSequence() + */ + function createSequence($seq_name) + { + $seqname = $this->getSequenceName($seq_name); + $query = 'CREATE TABLE ' . $seqname . + ' (id INTEGER UNSIGNED PRIMARY KEY) '; + $result = $this->query($query); + if (DB::isError($result)) { + return($result); + } + $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname + BEGIN + DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID(); + END "; + $result = $this->query($query); + if (DB::isError($result)) { + return($result); + } + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_sqlite::createSequence(), DB_sqlite::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + + do { + $repeat = 0; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)"); + $this->popErrorHandling(); + if ($result === DB_OK) { + $id = @sqlite_last_insert_rowid($this->connection); + if ($id != 0) { + return $id; + } + } elseif ($ondemand && DB::isError($result) && + $result->getCode() == DB_ERROR_NOSUCHTABLE) + { + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } else { + $repeat = 1; + } + } + } while ($repeat); + + return $this->raiseError($result); + } + + // }}} + // {{{ getDbFileStats() + + /** + * Get the file stats for the current database + * + * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size, + * atime, mtime, ctime, blksize, blocks or a numeric key between + * 0 and 12. + * + * @param string $arg the array key for stats() + * + * @return mixed an array on an unspecified key, integer on a passed + * arg and false at a stats error + */ + function getDbFileStats($arg = '') + { + $stats = stat($this->dsn['database']); + if ($stats == false) { + return false; + } + if (is_array($stats)) { + if (is_numeric($arg)) { + if (((int)$arg <= 12) & ((int)$arg >= 0)) { + return false; + } + return $stats[$arg ]; + } + if (array_key_exists(trim($arg), $stats)) { + return $stats[$arg ]; + } + } + return $stats; + } + + // }}} + // {{{ escapeSimple() + + /** + * Escapes a string according to the current DBMS's standards + * + * In SQLite, this makes things safe for inserts/updates, but may + * cause problems when performing text comparisons against columns + * containing binary data. See the + * {@link http://php.net/sqlite_escape_string PHP manual} for more info. + * + * @param string $str the string to be escaped + * + * @return string the escaped string + * + * @since Method available since Release 1.6.1 + * @see DB_common::escapeSimple() + */ + function escapeSimple($str) + { + return @sqlite_escape_string($str); + } + + // }}} + // {{{ modifyLimitQuery() + + /** + * Adds LIMIT clauses to a query string according to current DBMS standards + * + * @param string $query the query to modify + * @param int $from the row to start to fetching (0 = the first row) + * @param int $count the numbers of rows to fetch + * @param mixed $params array, string or numeric data to be used in + * execution of the statement. Quantity of items + * passed must match quantity of placeholders in + * query: meaning 1 placeholder for non-array + * parameters or 1 placeholder per array element. + * + * @return string the query string with LIMIT clauses added + * + * @access protected + */ + function modifyLimitQuery($query, $from, $count, $params = array()) + { + return "$query LIMIT $count OFFSET $from"; + } + + // }}} + // {{{ modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * This little hack lets you know how many rows were deleted + * when running a "DELETE FROM table" query. Only implemented + * if the DB_PORTABILITY_DELETE_COUNT portability option is on. + * + * @param string $query the query string to modify + * + * @return string the modified query string + * + * @access protected + * @see DB_common::setOption() + */ + function modifyQuery($query) + { + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + return $query; + } + + // }}} + // {{{ sqliteRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_sqlite::errorNative(), DB_sqlite::errorCode() + */ + function sqliteRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + + $errorcode = @sqlite_last_error($this->connection); + $userinfo = "$errorcode ** $this->last_query"; + + return $this->raiseError($errno, null, null, $userinfo, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * {@internal This is used to retrieve more meaningfull error messages + * because sqlite_last_error() does not provide adequate info.}} + * + * @return string the DBMS' error message + */ + function errorNative() + { + return $this->_lasterror; + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message + * + * @param string $errormsg the error message returned from the database + * + * @return integer the DB error number + */ + function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => DB_ERROR_NOSUCHTABLE, + '/^no such index:/' => DB_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT, + '/is not unique/' => DB_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT, + '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => DB_ERROR_NOSUCHFIELD, + '/no column named/' => DB_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table + * + * @param string $result a string containing the name of a table + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $id = @sqlite_array_query($this->connection, + "PRAGMA table_info('$result');", + SQLITE_ASSOC); + $got_string = true; + } else { + $this->last_query = ''; + return $this->raiseError(DB_ERROR_NOT_CAPABLE, null, null, null, + 'This DBMS can not obtain tableInfo' . + ' from result sets'); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = count($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + if (strpos($id[$i]['type'], '(') !== false) { + $bits = explode('(', $id[$i]['type']); + $type = $bits[0]; + $len = rtrim($bits[1],')'); + } else { + $type = $id[$i]['type']; + $len = 0; + } + + $flags = ''; + if ($id[$i]['pk']) { + $flags .= 'primary_key '; + } + if ($id[$i]['notnull']) { + $flags .= 'not_null '; + } + if ($id[$i]['dflt_value'] !== null) { + $flags .= 'default_' . rawurlencode($id[$i]['dflt_value']); + } + $flags = trim($flags); + + $res[$i] = array( + 'table' => $case_func($result), + 'name' => $case_func($id[$i]['name']), + 'type' => $type, + 'len' => $len, + 'flags' => $flags, + ); + + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + return $res; + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * @param array $args SQLITE DRIVER ONLY: a private array of arguments + * used by the getSpecialQuery(). Do not use + * this directly. + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type, $args = array()) + { + if (!is_array($args)) { + return $this->raiseError('no key specified', null, null, null, + 'Argument has to be an array.'); + } + + switch ($type) { + case 'master': + return 'SELECT * FROM sqlite_master;'; + case 'tables': + return "SELECT name FROM sqlite_master WHERE type='table' " + . 'UNION ALL SELECT name FROM sqlite_temp_master ' + . "WHERE type='table' ORDER BY name;"; + case 'schema': + return 'SELECT sql FROM (SELECT * FROM sqlite_master ' + . 'UNION ALL SELECT * FROM sqlite_temp_master) ' + . "WHERE type!='meta' " + . 'ORDER BY tbl_name, type DESC, name;'; + case 'schemax': + case 'schema_x': + /* + * Use like: + * $res = $db->query($db->getSpecialQuery('schema_x', + * array('table' => 'table3'))); + */ + return 'SELECT sql FROM (SELECT * FROM sqlite_master ' + . 'UNION ALL SELECT * FROM sqlite_temp_master) ' + . "WHERE tbl_name LIKE '{$args['table']}' " + . "AND type!='meta' " + . 'ORDER BY type DESC, name;'; + case 'alter': + /* + * SQLite does not support ALTER TABLE; this is a helper query + * to handle this. 'table' represents the table name, 'rows' + * the news rows to create, 'save' the row(s) to keep _with_ + * the data. + * + * Use like: + * $args = array( + * 'table' => $table, + * 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT", + * 'save' => "NULL, titel, content, datetime" + * ); + * $res = $db->query( $db->getSpecialQuery('alter', $args)); + */ + $rows = strtr($args['rows'], $this->keywords); + + $q = array( + 'BEGIN TRANSACTION', + "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})", + "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}", + "DROP TABLE {$args['table']}", + "CREATE TABLE {$args['table']} ({$args['rows']})", + "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup", + "DROP TABLE {$args['table']}_backup", + 'COMMIT', + ); + + /* + * This is a dirty hack, since the above query will not get + * executed with a single query call so here the query method + * will be called directly and return a select instead. + */ + foreach ($q as $query) { + $this->query($query); + } + return "SELECT * FROM {$args['table']};"; + default: + return null; + } + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/storage.php b/extlib/DB/storage.php new file mode 100644 index 000000000..ffa2d9447 --- /dev/null +++ b/extlib/DB/storage.php @@ -0,0 +1,506 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Provides an object interface to a table row + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Stig Bakken <stig@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: storage.php,v 1.24 2007/08/12 05:27:25 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB class so it can be extended from + */ +require_once 'DB.php'; + +/** + * Provides an object interface to a table row + * + * It lets you add, delete and change rows using objects rather than SQL + * statements. + * + * @category Database + * @package DB + * @author Stig Bakken <stig@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_storage extends PEAR +{ + // {{{ properties + + /** the name of the table (or view, if the backend database supports + updates in views) we hold data from */ + var $_table = null; + + /** which column(s) in the table contains primary keys, can be a + string for single-column primary keys, or an array of strings + for multiple-column primary keys */ + var $_keycolumn = null; + + /** DB connection handle used for all transactions */ + var $_dbh = null; + + /** an assoc with the names of database fields stored as properties + in this object */ + var $_properties = array(); + + /** an assoc with the names of the properties in this object that + have been changed since they were fetched from the database */ + var $_changes = array(); + + /** flag that decides if data in this object can be changed. + objects that don't have their table's key column in their + property lists will be flagged as read-only. */ + var $_readonly = false; + + /** function or method that implements a validator for fields that + are set, this validator function returns true if the field is + valid, false if not */ + var $_validator = null; + + // }}} + // {{{ constructor + + /** + * Constructor + * + * @param $table string the name of the database table + * + * @param $keycolumn mixed string with name of key column, or array of + * strings if the table has a primary key of more than one column + * + * @param $dbh object database connection object + * + * @param $validator mixed function or method used to validate + * each new value, called with three parameters: the name of the + * field/column that is changing, a reference to the new value and + * a reference to this object + * + */ + function DB_storage($table, $keycolumn, &$dbh, $validator = null) + { + $this->PEAR('DB_Error'); + $this->_table = $table; + $this->_keycolumn = $keycolumn; + $this->_dbh = $dbh; + $this->_readonly = false; + $this->_validator = $validator; + } + + // }}} + // {{{ _makeWhere() + + /** + * Utility method to build a "WHERE" clause to locate ourselves in + * the table. + * + * XXX future improvement: use rowids? + * + * @access private + */ + function _makeWhere($keyval = null) + { + if (is_array($this->_keycolumn)) { + if ($keyval === null) { + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + $keyval[] = $this->{$this->_keycolumn[$i]}; + } + } + $whereclause = ''; + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) { + if ($i > 0) { + $whereclause .= ' AND '; + } + $whereclause .= $this->_keycolumn[$i]; + if (is_null($keyval[$i])) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]); + } + } + } else { + if ($keyval === null) { + $keyval = @$this->{$this->_keycolumn}; + } + $whereclause = $this->_keycolumn; + if (is_null($keyval)) { + // there's not much point in having a NULL key, + // but we support it anyway + $whereclause .= ' IS NULL'; + } else { + $whereclause .= ' = ' . $this->_dbh->quote($keyval); + } + } + return $whereclause; + } + + // }}} + // {{{ setup() + + /** + * Method used to initialize a DB_storage object from the + * configured table. + * + * @param $keyval mixed the key[s] of the row to fetch (string or array) + * + * @return int DB_OK on success, a DB error if not + */ + function setup($keyval) + { + $whereclause = $this->_makeWhere($keyval); + $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause; + $sth = $this->_dbh->query($query); + if (DB::isError($sth)) { + return $sth; + } + $row = $sth->fetchRow(DB_FETCHMODE_ASSOC); + if (DB::isError($row)) { + return $row; + } + if (!$row) { + return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null, + $query, null, true); + } + foreach ($row as $key => $value) { + $this->_properties[$key] = true; + $this->$key = $value; + } + return DB_OK; + } + + // }}} + // {{{ insert() + + /** + * Create a new (empty) row in the configured table for this + * object. + */ + function insert($newpk) + { + if (is_array($this->_keycolumn)) { + $primarykey = $this->_keycolumn; + } else { + $primarykey = array($this->_keycolumn); + } + settype($newpk, "array"); + for ($i = 0; $i < sizeof($primarykey); $i++) { + $pkvals[] = $this->_dbh->quote($newpk[$i]); + } + + $sth = $this->_dbh->query("INSERT INTO $this->_table (" . + implode(",", $primarykey) . ") VALUES(" . + implode(",", $pkvals) . ")"); + if (DB::isError($sth)) { + return $sth; + } + if (sizeof($newpk) == 1) { + $newpk = $newpk[0]; + } + $this->setup($newpk); + } + + // }}} + // {{{ toString() + + /** + * Output a simple description of this DB_storage object. + * @return string object description + */ + function toString() + { + $info = strtolower(get_class($this)); + $info .= " (table="; + $info .= $this->_table; + $info .= ", keycolumn="; + if (is_array($this->_keycolumn)) { + $info .= "(" . implode(",", $this->_keycolumn) . ")"; + } else { + $info .= $this->_keycolumn; + } + $info .= ", dbh="; + if (is_object($this->_dbh)) { + $info .= $this->_dbh->toString(); + } else { + $info .= "null"; + } + $info .= ")"; + if (sizeof($this->_properties)) { + $info .= " [loaded, key="; + $keyname = $this->_keycolumn; + if (is_array($keyname)) { + $info .= "("; + for ($i = 0; $i < sizeof($keyname); $i++) { + if ($i > 0) { + $info .= ","; + } + $info .= $this->$keyname[$i]; + } + $info .= ")"; + } else { + $info .= $this->$keyname; + } + $info .= "]"; + } + if (sizeof($this->_changes)) { + $info .= " [modified]"; + } + return $info; + } + + // }}} + // {{{ dump() + + /** + * Dump the contents of this object to "standard output". + */ + function dump() + { + foreach ($this->_properties as $prop => $foo) { + print "$prop = "; + print htmlentities($this->$prop); + print "<br />\n"; + } + } + + // }}} + // {{{ &create() + + /** + * Static method used to create new DB storage objects. + * @param $data assoc. array where the keys are the names + * of properties/columns + * @return object a new instance of DB_storage or a subclass of it + */ + function &create($table, &$data) + { + $classname = strtolower(get_class($this)); + $obj = new $classname($table); + foreach ($data as $name => $value) { + $obj->_properties[$name] = true; + $obj->$name = &$value; + } + return $obj; + } + + // }}} + // {{{ loadFromQuery() + + /** + * Loads data into this object from the given query. If this + * object already contains table data, changes will be saved and + * the object re-initialized first. + * + * @param $query SQL query + * + * @param $params parameter list in case you want to use + * prepare/execute mode + * + * @return int DB_OK on success, DB_WARNING_READ_ONLY if the + * returned object is read-only (because the object's specified + * key column was not found among the columns returned by $query), + * or another DB error code in case of errors. + */ +// XXX commented out for now +/* + function loadFromQuery($query, $params = null) + { + if (sizeof($this->_properties)) { + if (sizeof($this->_changes)) { + $this->store(); + $this->_changes = array(); + } + $this->_properties = array(); + } + $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params); + if (DB::isError($rowdata)) { + return $rowdata; + } + reset($rowdata); + $found_keycolumn = false; + while (list($key, $value) = each($rowdata)) { + if ($key == $this->_keycolumn) { + $found_keycolumn = true; + } + $this->_properties[$key] = true; + $this->$key = &$value; + unset($value); // have to unset, or all properties will + // refer to the same value + } + if (!$found_keycolumn) { + $this->_readonly = true; + return DB_WARNING_READ_ONLY; + } + return DB_OK; + } + */ + + // }}} + // {{{ set() + + /** + * Modify an attriute value. + */ + function set($property, $newvalue) + { + // only change if $property is known and object is not + // read-only + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + if (@isset($this->_properties[$property])) { + if (empty($this->_validator)) { + $valid = true; + } else { + $valid = @call_user_func($this->_validator, + $this->_table, + $property, + $newvalue, + $this->$property, + $this); + } + if ($valid) { + $this->$property = $newvalue; + if (empty($this->_changes[$property])) { + $this->_changes[$property] = 0; + } else { + $this->_changes[$property]++; + } + } else { + return $this->raiseError(null, DB_ERROR_INVALID, null, + null, "invalid field: $property", + null, true); + } + return true; + } + return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null, + null, "unknown field: $property", + null, true); + } + + // }}} + // {{{ &get() + + /** + * Fetch an attribute value. + * + * @param string attribute name + * + * @return attribute contents, or null if the attribute name is + * unknown + */ + function &get($property) + { + // only return if $property is known + if (isset($this->_properties[$property])) { + return $this->$property; + } + $tmp = null; + return $tmp; + } + + // }}} + // {{{ _DB_storage() + + /** + * Destructor, calls DB_storage::store() if there are changes + * that are to be kept. + */ + function _DB_storage() + { + if (sizeof($this->_changes)) { + $this->store(); + } + $this->_properties = array(); + $this->_changes = array(); + $this->_table = null; + } + + // }}} + // {{{ store() + + /** + * Stores changes to this object in the database. + * + * @return DB_OK or a DB error + */ + function store() + { + $params = array(); + $vars = array(); + foreach ($this->_changes as $name => $foo) { + $params[] = &$this->$name; + $vars[] = $name . ' = ?'; + } + if ($vars) { + $query = 'UPDATE ' . $this->_table . ' SET ' . + implode(', ', $vars) . ' WHERE ' . + $this->_makeWhere(); + $stmt = $this->_dbh->prepare($query); + $res = $this->_dbh->execute($stmt, $params); + if (DB::isError($res)) { + return $res; + } + $this->_changes = array(); + } + return DB_OK; + } + + // }}} + // {{{ remove() + + /** + * Remove the row represented by this object from the database. + * + * @return mixed DB_OK or a DB error + */ + function remove() + { + if ($this->_readonly) { + return $this->raiseError(null, DB_WARNING_READ_ONLY, null, + null, null, null, true); + } + $query = 'DELETE FROM ' . $this->_table .' WHERE '. + $this->_makeWhere(); + $res = $this->_dbh->query($query); + if (DB::isError($res)) { + return $res; + } + foreach ($this->_properties as $prop => $foo) { + unset($this->$prop); + } + $this->_properties = array(); + $this->_changes = array(); + return DB_OK; + } + + // }}} +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> diff --git a/extlib/DB/sybase.php b/extlib/DB/sybase.php new file mode 100644 index 000000000..3befbf6ea --- /dev/null +++ b/extlib/DB/sybase.php @@ -0,0 +1,942 @@ +<?php + +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * The PEAR DB driver for PHP's sybase extension + * for interacting with Sybase databases + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Database + * @package DB + * @author Sterling Hughes <sterling@php.net> + * @author Antônio Carlos Venâncio Júnior <floripa@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id: sybase.php,v 1.87 2007/09/21 13:40:42 aharvey Exp $ + * @link http://pear.php.net/package/DB + */ + +/** + * Obtain the DB_common class so it can be extended from + */ +require_once 'DB/common.php'; + +/** + * The methods PEAR DB uses to interact with PHP's sybase extension + * for interacting with Sybase databases + * + * These methods overload the ones declared in DB_common. + * + * WARNING: This driver may fail with multiple connections under the + * same user/pass/host and different databases. + * + * @category Database + * @package DB + * @author Sterling Hughes <sterling@php.net> + * @author Antônio Carlos Venâncio Júnior <floripa@php.net> + * @author Daniel Convissor <danielc@php.net> + * @copyright 1997-2007 The PHP Group + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version Release: 1.7.14RC1 + * @link http://pear.php.net/package/DB + */ +class DB_sybase extends DB_common +{ + // {{{ properties + + /** + * The DB driver type (mysql, oci8, odbc, etc.) + * @var string + */ + var $phptype = 'sybase'; + + /** + * The database syntax variant to be used (db2, access, etc.), if any + * @var string + */ + var $dbsyntax = 'sybase'; + + /** + * The capabilities of this DB implementation + * + * The 'new_link' element contains the PHP version that first provided + * new_link support for this DBMS. Contains false if it's unsupported. + * + * Meaning of the 'limit' element: + * + 'emulate' = emulate with fetch row by number + * + 'alter' = alter the query + * + false = skip rows + * + * @var array + */ + var $features = array( + 'limit' => 'emulate', + 'new_link' => false, + 'numrows' => true, + 'pconnect' => true, + 'prepare' => false, + 'ssl' => false, + 'transactions' => true, + ); + + /** + * A mapping of native error codes to DB error codes + * @var array + */ + var $errorcode_map = array( + ); + + /** + * The raw database connection created by PHP + * @var resource + */ + var $connection; + + /** + * The DSN information for connecting to a database + * @var array + */ + var $dsn = array(); + + + /** + * Should data manipulation queries be committed automatically? + * @var bool + * @access private + */ + var $autocommit = true; + + /** + * The quantity of transactions begun + * + * {@internal While this is private, it can't actually be designated + * private in PHP 5 because it is directly accessed in the test suite.}} + * + * @var integer + * @access private + */ + var $transaction_opcount = 0; + + /** + * The database specified in the DSN + * + * It's a fix to allow calls to different databases in the same script. + * + * @var string + * @access private + */ + var $_db = ''; + + + // }}} + // {{{ constructor + + /** + * This constructor calls <kbd>$this->DB_common()</kbd> + * + * @return void + */ + function DB_sybase() + { + $this->DB_common(); + } + + // }}} + // {{{ connect() + + /** + * Connect to the database server, log in and open the database + * + * Don't call this method directly. Use DB::connect() instead. + * + * PEAR DB's sybase driver supports the following extra DSN options: + * + appname The application name to use on this connection. + * Available since PEAR DB 1.7.0. + * + charset The character set to use on this connection. + * Available since PEAR DB 1.7.0. + * + * @param array $dsn the data source name + * @param bool $persistent should the connection be persistent? + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function connect($dsn, $persistent = false) + { + if (!PEAR::loadExtension('sybase') && + !PEAR::loadExtension('sybase_ct')) + { + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); + } + + $this->dsn = $dsn; + if ($dsn['dbsyntax']) { + $this->dbsyntax = $dsn['dbsyntax']; + } + + $dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost'; + $dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false; + $dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false; + $dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false; + + $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect'; + + if ($dsn['username']) { + $this->connection = @$connect_function($dsn['hostspec'], + $dsn['username'], + $dsn['password'], + $dsn['charset'], + $dsn['appname']); + } else { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + 'The DSN did not contain a username.'); + } + + if (!$this->connection) { + return $this->raiseError(DB_ERROR_CONNECT_FAILED, + null, null, null, + @sybase_get_last_message()); + } + + if ($dsn['database']) { + if (!@sybase_select_db($dsn['database'], $this->connection)) { + return $this->raiseError(DB_ERROR_NODBSELECTED, + null, null, null, + @sybase_get_last_message()); + } + $this->_db = $dsn['database']; + } + + return DB_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Disconnects from the database server + * + * @return bool TRUE on success, FALSE on failure + */ + function disconnect() + { + $ret = @sybase_close($this->connection); + $this->connection = null; + return $ret; + } + + // }}} + // {{{ simpleQuery() + + /** + * Sends a query to the database server + * + * @param string the SQL query string + * + * @return mixed + a PHP result resrouce for successful SELECT queries + * + the DB_OK constant for other successful queries + * + a DB_Error object on failure + */ + function simpleQuery($query) + { + $ismanip = $this->_checkManip($query); + $this->last_query = $query; + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $query = $this->modifyQuery($query); + if (!$this->autocommit && $ismanip) { + if ($this->transaction_opcount == 0) { + $result = @sybase_query('BEGIN TRANSACTION', $this->connection); + if (!$result) { + return $this->sybaseRaiseError(); + } + } + $this->transaction_opcount++; + } + $result = @sybase_query($query, $this->connection); + if (!$result) { + return $this->sybaseRaiseError(); + } + if (is_resource($result)) { + return $result; + } + // Determine which queries that should return data, and which + // should return an error code only. + return $ismanip ? DB_OK : $result; + } + + // }}} + // {{{ nextResult() + + /** + * Move the internal sybase result pointer to the next available result + * + * @param a valid sybase result resource + * + * @access public + * + * @return true if a result is available otherwise return false + */ + function nextResult($result) + { + return false; + } + + // }}} + // {{{ fetchInto() + + /** + * Places a row from the result set into the given array + * + * Formating of the array and the data therein are configurable. + * See DB_result::fetchInto() for more information. + * + * This method is not meant to be called directly. Use + * DB_result::fetchInto() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result the query result resource + * @param array $arr the referenced array to put the data in + * @param int $fetchmode how the resulting array should be indexed + * @param int $rownum the row number to fetch (0 = first row) + * + * @return mixed DB_OK on success, NULL when the end of a result set is + * reached or on failure + * + * @see DB_result::fetchInto() + */ + function fetchInto($result, &$arr, $fetchmode, $rownum = null) + { + if ($rownum !== null) { + if (!@sybase_data_seek($result, $rownum)) { + return null; + } + } + if ($fetchmode & DB_FETCHMODE_ASSOC) { + if (function_exists('sybase_fetch_assoc')) { + $arr = @sybase_fetch_assoc($result); + } else { + if ($arr = @sybase_fetch_array($result)) { + foreach ($arr as $key => $value) { + if (is_int($key)) { + unset($arr[$key]); + } + } + } + } + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { + $arr = array_change_key_case($arr, CASE_LOWER); + } + } else { + $arr = @sybase_fetch_row($result); + } + if (!$arr) { + return null; + } + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { + $this->_rtrimArrayValues($arr); + } + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { + $this->_convertNullArrayValuesToEmpty($arr); + } + return DB_OK; + } + + // }}} + // {{{ freeResult() + + /** + * Deletes the result set and frees the memory occupied by the result set + * + * This method is not meant to be called directly. Use + * DB_result::free() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return bool TRUE on success, FALSE if $result is invalid + * + * @see DB_result::free() + */ + function freeResult($result) + { + return is_resource($result) ? sybase_free_result($result) : false; + } + + // }}} + // {{{ numCols() + + /** + * Gets the number of columns in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numCols() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of columns. A DB_Error object on failure. + * + * @see DB_result::numCols() + */ + function numCols($result) + { + $cols = @sybase_num_fields($result); + if (!$cols) { + return $this->sybaseRaiseError(); + } + return $cols; + } + + // }}} + // {{{ numRows() + + /** + * Gets the number of rows in a result set + * + * This method is not meant to be called directly. Use + * DB_result::numRows() instead. It can't be declared "protected" + * because DB_result is a separate object. + * + * @param resource $result PHP's query result resource + * + * @return int the number of rows. A DB_Error object on failure. + * + * @see DB_result::numRows() + */ + function numRows($result) + { + $rows = @sybase_num_rows($result); + if ($rows === false) { + return $this->sybaseRaiseError(); + } + return $rows; + } + + // }}} + // {{{ affectedRows() + + /** + * Determines the number of rows affected by a data maniuplation query + * + * 0 is returned for queries that don't manipulate data. + * + * @return int the number of rows. A DB_Error object on failure. + */ + function affectedRows() + { + if ($this->_last_query_manip) { + $result = @sybase_affected_rows($this->connection); + } else { + $result = 0; + } + return $result; + } + + // }}} + // {{{ nextId() + + /** + * Returns the next free id in a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true, the seqence is automatically + * created if it does not exist + * + * @return int the next id number in the sequence. + * A DB_Error object on failure. + * + * @see DB_common::nextID(), DB_common::getSequenceName(), + * DB_sybase::createSequence(), DB_sybase::dropSequence() + */ + function nextId($seq_name, $ondemand = true) + { + $seqname = $this->getSequenceName($seq_name); + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $repeat = 0; + do { + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)"); + $this->popErrorHandling(); + if ($ondemand && DB::isError($result) && + ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE)) + { + $repeat = 1; + $result = $this->createSequence($seq_name); + if (DB::isError($result)) { + return $this->raiseError($result); + } + } elseif (!DB::isError($result)) { + $result = $this->query("SELECT @@IDENTITY FROM $seqname"); + $repeat = 0; + } else { + $repeat = false; + } + } while ($repeat); + if (DB::isError($result)) { + return $this->raiseError($result); + } + $result = $result->fetchRow(DB_FETCHMODE_ORDERED); + return $result[0]; + } + + /** + * Creates a new sequence + * + * @param string $seq_name name of the new sequence + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::createSequence(), DB_common::getSequenceName(), + * DB_sybase::nextID(), DB_sybase::dropSequence() + */ + function createSequence($seq_name) + { + return $this->query('CREATE TABLE ' + . $this->getSequenceName($seq_name) + . ' (id numeric(10, 0) IDENTITY NOT NULL,' + . ' vapor int NULL)'); + } + + // }}} + // {{{ dropSequence() + + /** + * Deletes a sequence + * + * @param string $seq_name name of the sequence to be deleted + * + * @return int DB_OK on success. A DB_Error object on failure. + * + * @see DB_common::dropSequence(), DB_common::getSequenceName(), + * DB_sybase::nextID(), DB_sybase::createSequence() + */ + function dropSequence($seq_name) + { + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)); + } + + // }}} + // {{{ quoteFloat() + + /** + * Formats a float value for use within a query in a locale-independent + * manner. + * + * @param float the float value to be quoted. + * @return string the quoted string. + * @see DB_common::quoteSmart() + * @since Method available since release 1.7.8. + */ + function quoteFloat($float) { + return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); + } + + // }}} + // {{{ autoCommit() + + /** + * Enables or disables automatic commits + * + * @param bool $onoff true turns it on, false turns it off + * + * @return int DB_OK on success. A DB_Error object if the driver + * doesn't support auto-committing transactions. + */ + 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() + + /** + * Commits the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function commit() + { + if ($this->transaction_opcount > 0) { + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @sybase_query('COMMIT', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->sybaseRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ rollback() + + /** + * Reverts the current transaction + * + * @return int DB_OK on success. A DB_Error object on failure. + */ + function rollback() + { + if ($this->transaction_opcount > 0) { + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $result = @sybase_query('ROLLBACK', $this->connection); + $this->transaction_opcount = 0; + if (!$result) { + return $this->sybaseRaiseError(); + } + } + return DB_OK; + } + + // }}} + // {{{ sybaseRaiseError() + + /** + * Produces a DB_Error object regarding the current problem + * + * @param int $errno if the error is being manually raised pass a + * DB_ERROR* constant here. If this isn't passed + * the error information gathered from the DBMS. + * + * @return object the DB_Error object + * + * @see DB_common::raiseError(), + * DB_sybase::errorNative(), DB_sybase::errorCode() + */ + function sybaseRaiseError($errno = null) + { + $native = $this->errorNative(); + if ($errno === null) { + $errno = $this->errorCode($native); + } + return $this->raiseError($errno, null, null, null, $native); + } + + // }}} + // {{{ errorNative() + + /** + * Gets the DBMS' native error message produced by the last query + * + * @return string the DBMS' error message + */ + function errorNative() + { + return @sybase_get_last_message(); + } + + // }}} + // {{{ errorCode() + + /** + * Determines PEAR::DB error code from the database's text error message. + * + * @param string $errormsg error message returned from the database + * @return integer an error number from a DB error constant + */ + function errorCode($errormsg) + { + static $error_regexps; + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg); + + if (!isset($error_regexps)) { + $error_regexps = array( + '/Incorrect syntax near/' + => DB_ERROR_SYNTAX, + '/^Unclosed quote before the character string [\"\'].*[\"\']\./' + => DB_ERROR_SYNTAX, + '/Implicit conversion (from datatype|of NUMERIC value)/i' + => DB_ERROR_INVALID_NUMBER, + '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./' + => DB_ERROR_NOSUCHTABLE, + '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./' + => DB_ERROR_ACCESS_VIOLATION, + '/^.+ permission denied on object .+, database .+, owner .+/' + => DB_ERROR_ACCESS_VIOLATION, + '/^.* permission denied, database .+, owner .+/' + => DB_ERROR_ACCESS_VIOLATION, + '/[^.*] not found\./' + => DB_ERROR_NOSUCHTABLE, + '/There is already an object named/' + => DB_ERROR_ALREADY_EXISTS, + '/Invalid column name/' + => DB_ERROR_NOSUCHFIELD, + '/does not allow null values/' + => DB_ERROR_CONSTRAINT_NOT_NULL, + '/Command has been aborted/' + => DB_ERROR_CONSTRAINT, + '/^Cannot drop the index .* because it doesn\'t exist/i' + => DB_ERROR_NOT_FOUND, + '/^There is already an index/i' + => DB_ERROR_ALREADY_EXISTS, + '/^There are fewer columns in the INSERT statement than values specified/i' + => DB_ERROR_VALUE_COUNT_ON_ROW, + '/Divide by zero/i' + => DB_ERROR_DIVZERO, + ); + } + + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + return DB_ERROR; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * @param object|string $result DB_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A DB_Error object on failure. + * + * @see DB_common::tableInfo() + * @since Method available since Release 1.6.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) { + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED); + } + $id = @sybase_query("SELECT * FROM $result WHERE 1=0", + $this->connection); + $got_string = true; + } elseif (isset($result->result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->result; + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA); + } + + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { + $case_func = 'strtolower'; + } else { + $case_func = 'strval'; + } + + $count = @sybase_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $f = @sybase_fetch_field($id, $i); + // column_source is often blank + $res[$i] = array( + 'table' => $got_string + ? $case_func($result) + : $case_func($f->column_source), + 'name' => $case_func($f->name), + 'type' => $f->type, + 'len' => $f->max_length, + 'flags' => '', + ); + if ($res[$i]['table']) { + $res[$i]['flags'] = $this->_sybase_field_flags( + $res[$i]['table'], $res[$i]['name']); + } + if ($mode & DB_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & DB_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @sybase_free_result($id); + } + return $res; + } + + // }}} + // {{{ _sybase_field_flags() + + /** + * Get the flags for a field + * + * Currently supports: + * + <samp>unique_key</samp> (unique index, unique check or primary_key) + * + <samp>multiple_key</samp> (multi-key index) + * + * @param string $table the table name + * @param string $column the field name + * + * @return string space delimited string of flags. Empty string if none. + * + * @access private + */ + function _sybase_field_flags($table, $column) + { + static $tableName = null; + static $flags = array(); + + if ($table != $tableName) { + $flags = array(); + $tableName = $table; + + /* We're running sp_helpindex directly because it doesn't exist in + * older versions of ASE -- unfortunately, we can't just use + * DB::isError() because the user may be using callback error + * handling. */ + $res = @sybase_query("sp_helpindex $table", $this->connection); + + if ($res === false || $res === true) { + // Fake a valid response for BC reasons. + return ''; + } + + while (($val = sybase_fetch_assoc($res)) !== false) { + if (!isset($val['index_keys'])) { + /* No useful information returned. Break and be done with + * it, which preserves the pre-1.7.9 behaviour. */ + break; + } + + $keys = explode(', ', trim($val['index_keys'])); + + if (sizeof($keys) > 1) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'multiple_key'); + } + } + + if (strpos($val['index_description'], 'unique')) { + foreach ($keys as $key) { + $this->_add_flag($flags[$key], 'unique_key'); + } + } + } + + sybase_free_result($res); + + } + + if (array_key_exists($column, $flags)) { + return(implode(' ', $flags[$column])); + } + + return ''; + } + + // }}} + // {{{ _add_flag() + + /** + * Adds a string to the flags array if the flag is not yet in there + * - if there is no flag present the array is created + * + * @param array $array reference of flags array to add a value to + * @param mixed $value value to add to the flag array + * + * @return void + * + * @access private + */ + function _add_flag(&$array, $value) + { + if (!is_array($array)) { + $array = array($value); + } elseif (!in_array($value, $array)) { + array_push($array, $value); + } + } + + // }}} + // {{{ getSpecialQuery() + + /** + * Obtains the query string needed for listing a given type of objects + * + * @param string $type the kind of objects you want to retrieve + * + * @return string the SQL query string or null if the driver doesn't + * support the object type requested + * + * @access protected + * @see DB_common::getListOf() + */ + function getSpecialQuery($type) + { + switch ($type) { + case 'tables': + return "SELECT name FROM sysobjects WHERE type = 'U'" + . ' ORDER BY name'; + case 'views': + return "SELECT name FROM sysobjects WHERE type = 'V'"; + default: + return null; + } + } + + // }}} + +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ + +?> |