summaryrefslogtreecommitdiff
path: root/program
diff options
context:
space:
mode:
authorsvncommit <devs@roundcube.net>2005-10-02 11:36:35 +0000
committersvncommit <devs@roundcube.net>2005-10-02 11:36:35 +0000
commit1676e1ebda38922b609c501b3c3c34b881302122 (patch)
treeaf115b449e30bac37d7cf0459ed95d88d32f1282 /program
parent968bdc7c90f718560b84eb3b7e8e1d02cf4270f3 (diff)
Added PEAR:DB support plus database replication support
Diffstat (limited to 'program')
-rw-r--r--program/include/main.inc17
-rwxr-xr-xprogram/include/rcube_db.inc202
-rw-r--r--program/include/rcube_mysql.inc186
-rw-r--r--program/lib/DB.php1388
-rw-r--r--program/lib/DB/common.php2157
-rw-r--r--program/lib/DB/dbase.php510
-rw-r--r--program/lib/DB/fbsql.php770
-rw-r--r--program/lib/DB/ibase.php1071
-rw-r--r--program/lib/DB/ifx.php681
-rw-r--r--program/lib/DB/msql.php810
-rw-r--r--program/lib/DB/mssql.php914
-rw-r--r--program/lib/DB/mysql.php1034
-rw-r--r--program/lib/DB/mysqli.php1076
-rw-r--r--program/lib/DB/oci8.php1117
-rw-r--r--program/lib/DB/odbc.php883
-rw-r--r--program/lib/DB/pgsql.php1097
-rw-r--r--program/lib/DB/sqlite.php942
-rw-r--r--program/lib/DB/storage.php504
-rw-r--r--program/lib/DB/sybase.php907
19 files changed, 16065 insertions, 201 deletions
diff --git a/program/include/main.inc b/program/include/main.inc
index 83421768a..ebf2f2685 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -54,23 +54,10 @@ function rcmail_startup($task='mail')
// prepare DB connection
- if (strtolower($CONFIG['db_type'])=='mysql')
- $DB = new rcube_mysql($CONFIG['db_name'], $CONFIG['db_user'], $CONFIG['db_pass'], $CONFIG['db_host']);
-
- // database not supported
- else
- {
- raise_error(array('code' => 500,
- 'type' => 'php',
- 'line' => __LINE__,
- 'file' => __FILE__,
- 'message' => "Database not supported"), TRUE, TRUE);
- return;
- }
-
+ $DB = new rcube_db($CONFIG['db_dsnw'], $CONFIG['db_dsnr']);
// we can use the database for storing session data
- if (is_object($DB) && $DB->connect())
+ if (is_object($DB))
include_once('include/session.inc');
diff --git a/program/include/rcube_db.inc b/program/include/rcube_db.inc
new file mode 100755
index 000000000..f4dc222a5
--- /dev/null
+++ b/program/include/rcube_db.inc
@@ -0,0 +1,202 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_db.inc |
+ | |
+ | This file is part of the RoundCube Webmail client |
+ | Copyright (C) 2005, RoundCube Dev. - Switzerland |
+ | Licensed under the GNU GPL |
+ | |
+ | PURPOSE: |
+ | PEAR:DB wrapper class that implements PEAR DB functions |
+ | See http://pear.php.net/package/DB |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+require_once('DB.php');
+
+class rcube_db
+{
+ var $db_dsnw; // DSN for write operations
+ var $db_dsnr; // DSN for read operations
+ var $db_connected=false; // Already connected ?
+ var $db_mode=''; // Connection mode
+ var $db_handle=0; // Connection handle
+
+ var $a_query_results = array('dummy');
+ var $last_res_id = 0;
+
+ // PHP 5 constructor
+ function __construct($db_dsnw,$db_dsnr='')
+ {
+ if ($db_dsnr=='') $db_dsnr=$db_dsnw;
+
+ $this->db_dsnw = $db_dsnw;
+ $this->db_dsnr = $db_dsnr;
+ }
+
+ // PHP 4 compatibility
+ function rcube_db($db_dsnw,$db_dsnr='')
+ {
+ $this->__construct($db_dsnw,$db_dsnr);
+ }
+
+ // Connect to specific database
+ function dsn_connect($dsn)
+ {
+ $dbh = DB::connect($dsn);
+
+ if (DB::isError($dbh))
+ raise_error(array('code' => 500,
+ 'type' => 'db',
+ 'line' => __LINE__,
+ 'file' => __FILE__,
+ 'message' => $dbh->getMessage()), TRUE, FALSE);
+ return $dbh;
+ }
+
+ // Connect to appropiate databse
+ function db_connect ($mode)
+ {
+ if ($this->db_connected && $this->db_mode=='w') return;
+
+ if ($this->db_connected && $this->db_mode==$mode) return;
+
+ if ($mode=='r')
+ $dsn=$this->db_dsnr;
+ else
+ $dsn=$this->db_dsnw;
+
+ $this->db_handle = $this->dsn_connect($dsn);
+ $this->db_connected = true;
+ $this->db_mode = $mode;
+ }
+
+ // Query database (read operations)
+
+ function query($query)
+ {
+ // Read or write ?
+
+ if (strtolower(trim(substr($query,0,6)))=='select')
+ $mode='r';
+ else
+ {
+ $mode='w';
+ }
+
+ $this->db_connect($mode);
+
+ $result = $this->db_handle->query($query);
+
+ if (DB::isError($result))
+ raise_error( array('code' => 500, 'type' => 'db', 'line' => __LINE__,
+ 'file' => __FILE__,
+ 'message' => $result->getMessage()), TRUE, FALSE);
+
+ return $this->_add_result($result, $query);
+ }
+
+ function db_execute ($query)
+ {
+ db_connect('w');
+
+ $result = $this->db_handle->query($query);
+
+ }
+
+ function num_rows($res_id=NULL)
+ {
+ if (!$this->db_handle)
+ return FALSE;
+
+ $result = $this->_get_result($res_id);
+
+ if ($result)
+ return $result->numRows();
+ else
+ return FALSE;
+ }
+
+ function affected_rows($res_id=NULL)
+ {
+ if (!$this->db_handle)
+ return FALSE;
+
+ return $this->last_res_id->affectedRows();
+ }
+
+ function insert_id($sequence = '')
+ {
+ if (!$this->db_link || $this->db_mode=='r')
+ return FALSE;
+
+ switch($this->db_provider)
+ {
+ case 'pgsql':
+ // PostgreSQL uses sequences
+ $result =& $this->db_handle->getOne("SELECT CURRVAL('$sequence')");
+ if (DB::isError($result))
+ raise_error( array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $result->getMessage()), TRUE, TRUE);
+ return $result;
+
+ case 'mysql': // This is unfortuneate
+ return mysql_insert_id($this->db_handle);
+
+ default:
+ die("portability issue with this database, please have the developer fix");
+ }
+ }
+
+
+ function fetch_assoc($res_id=NULL)
+ {
+ $result = $this->_get_result($res_id);
+
+ if (DB::isError($result))
+ raise_error( array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $this->db_link->getMessage()), TRUE, TRUE);
+
+ return $result->fetchRow(DB_FETCHMODE_ASSOC);
+ }
+
+ function _add_result($res, $query)
+ {
+ // sql error occured
+ if (DB::isError($res))
+ {
+ raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, 'message' => $res->getMessage() . " Query: " . preg_replace('/[\r\n]+\s*/', ' ', $query)), TRUE, FALSE);
+ return FALSE;
+ }
+ else
+ {
+ $res_id = sizeof($this->a_query_results);
+ $this->a_query_results[$res_id] = $res;
+ $this->last_res_id = $res_id;
+ return $res_id;
+ }
+ }
+
+
+ function _get_result($res_id)
+ {
+ if ($res_id==NULL)
+ $res_id = $this->last_res_id;
+
+ if ($res_id && isset($this->a_query_results[$res_id]))
+ return $this->a_query_results[$res_id];
+ else
+ return FALSE;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/program/include/rcube_mysql.inc b/program/include/rcube_mysql.inc
deleted file mode 100644
index c5955489a..000000000
--- a/program/include/rcube_mysql.inc
+++ /dev/null
@@ -1,186 +0,0 @@
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | program/include/rcube_mysql.inc |
- | |
- | This file is part of the RoundCube Webmail client |
- | Copyright (C) 2005, RoundCube Dev. - Switzerland |
- | Licensed under the GNU GPL |
- | |
- | PURPOSE: |
- | MySQL wrapper class that implements PHP MySQL functions |
- | See http://www.php.net/manual/en/ref.mysql.php |
- | |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com> |
- +-----------------------------------------------------------------------+
-
- $Id$
-
-*/
-
-
-class rcube_mysql
- {
- var $db_link;
- var $db_host = 'localhost';
- var $db_name = '';
- var $db_user = '';
- var $db_pass = '';
- var $a_query_results = array('dummy');
- var $last_res_id = 0;
-
-
- // PHP 5 constructor
- function __construct($db_name='', $user='', $pass='', $host='localhost')
- {
- $this->db_host = $host;
- $this->db_name = $db_name;
- $this->db_user = $user;
- $this->db_pass = $pass;
- }
-
- // PHP 4 compatibility
- function rcube_mysql($db_name='', $user='', $pass='', $host='localhost')
- {
- $this->__construct($db_name, $user, $pass, $host);
- }
-
-
- function connect()
- {
- $this->db_link = mysql_connect($this->db_host, $this->db_user, $this->db_pass);
-
- if (!$this->db_link)
- {
- raise_error(array('code' => 500,
- 'type' => 'mysql',
- 'line' => __LINE__,
- 'file' => __FILE__,
- 'message' => "Can't connect to database"), TRUE, FALSE);
- return FALSE;
- }
-
- return TRUE;
- }
-
-
- function select_db($name)
- {
- $this->db_name = $name;
-
- if ($this->db_link)
- mysql_select_db($name, $this->db_link);
- }
-
-
- function query($query)
- {
- // establish a connection
- if (!$this->db_link)
- {
- if (!$this->connect())
- return FALSE;
- }
-
- $sql_result = mysql_db_query($this->db_name, $query, $this->db_link);
- return $this->_add_result($sql_result, $query);
- }
-
-
- function num_rows($res_id=NULL)
- {
- if (!$this->db_link)
- return FALSE;
-
- $sql_result = $this->_get_result($res_id);
-
- if ($sql_result)
- return mysql_num_rows($sql_result);
- else
- return FALSE;
- }
-
-
- function affected_rows()
- {
- if (!$this->db_link)
- return FALSE;
-
- return mysql_affected_rows($this->db_link);
- }
-
-
- function insert_id()
- {
- if (!$this->db_link)
- return FALSE;
-
- return mysql_insert_id($this->db_link);
- }
-
-
- function fetch_assoc($res_id=NULL)
- {
- $sql_result = $this->_get_result($res_id);
-
- if ($sql_result)
- return mysql_fetch_assoc($sql_result);
- else
- return FALSE;
- }
-
-
- function seek($res_id=NULL, $row=0)
- {
- $sql_result = $this->_get_result($res_id);
-
- if ($sql_result)
- return mysql_data_seek($sql_result, $row);
- else
- return FALSE;
- }
-
-
-
- function _add_result($res, $query)
- {
- // sql error occured
- if ($res===FALSE)
- {
- $sql_error = mysql_error($this->db_link);
- raise_error(array('code' => 500,
- 'type' => 'mysql',
- 'line' => __LINE__,
- 'file' => __FILE__,
- 'message' => $sql_error."; QUERY: ".preg_replace('/[\r\n]+\s*/', ' ', $query)), TRUE, FALSE);
-
- return FALSE;
- }
- else
- {
- $res_id = sizeof($this->a_query_results);
- $this->a_query_results[$res_id] = $res;
- $this->last_res_id = $res_id;
-
- return $res_id;
- }
- }
-
-
- function _get_result($res_id)
- {
- if ($res_id===NULL)
- $res_id = $this->last_res_id;
-
- if ($res_id && isset($this->a_query_results[$res_id]))
- return $this->a_query_results[$res_id];
- else
- return FALSE;
- }
-
- }
-
-
-?> \ No newline at end of file
diff --git a/program/lib/DB.php b/program/lib/DB.php
new file mode 100644
index 000000000..e2beecaf2
--- /dev/null
+++ b/program/lib/DB.php
@@ -0,0 +1,1388 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Database independent query interface
+ *
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+
+// {{{ constants
+// {{{ error codes
+
+/**#@+
+ * One of PEAR DB's portable error codes.
+ * @see DB_common::errorCode(), DB::errorMessage()
+ *
+ * {@internal If you add an error code here, make sure you also add a textual
+ * version of it in DB::errorMessage().}}
+ */
+
+/**
+ * The code returned by many methods upon success
+ */
+define('DB_OK', 1);
+
+/**
+ * Unkown error
+ */
+define('DB_ERROR', -1);
+
+/**
+ * Syntax error
+ */
+define('DB_ERROR_SYNTAX', -2);
+
+/**
+ * Tried to insert a duplicate value into a primary or unique index
+ */
+define('DB_ERROR_CONSTRAINT', -3);
+
+/**
+ * An identifier in the query refers to a non-existant object
+ */
+define('DB_ERROR_NOT_FOUND', -4);
+
+/**
+ * Tried to create a duplicate object
+ */
+define('DB_ERROR_ALREADY_EXISTS', -5);
+
+/**
+ * The current driver does not support the action you attempted
+ */
+define('DB_ERROR_UNSUPPORTED', -6);
+
+/**
+ * The number of parameters does not match the number of placeholders
+ */
+define('DB_ERROR_MISMATCH', -7);
+
+/**
+ * A literal submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID', -8);
+
+/**
+ * The current DBMS does not support the action you attempted
+ */
+define('DB_ERROR_NOT_CAPABLE', -9);
+
+/**
+ * A literal submitted was too long so the end of it was removed
+ */
+define('DB_ERROR_TRUNCATED', -10);
+
+/**
+ * A literal number submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_NUMBER', -11);
+
+/**
+ * A literal date submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_DATE', -12);
+
+/**
+ * Attempt to divide something by zero
+ */
+define('DB_ERROR_DIVZERO', -13);
+
+/**
+ * A database needs to be selected
+ */
+define('DB_ERROR_NODBSELECTED', -14);
+
+/**
+ * Could not create the object requested
+ */
+define('DB_ERROR_CANNOT_CREATE', -15);
+
+/**
+ * Could not drop the database requested because it does not exist
+ */
+define('DB_ERROR_CANNOT_DROP', -17);
+
+/**
+ * An identifier in the query refers to a non-existant table
+ */
+define('DB_ERROR_NOSUCHTABLE', -18);
+
+/**
+ * An identifier in the query refers to a non-existant column
+ */
+define('DB_ERROR_NOSUCHFIELD', -19);
+
+/**
+ * The data submitted to the method was inappropriate
+ */
+define('DB_ERROR_NEED_MORE_DATA', -20);
+
+/**
+ * The attempt to lock the table failed
+ */
+define('DB_ERROR_NOT_LOCKED', -21);
+
+/**
+ * The number of columns doesn't match the number of values
+ */
+define('DB_ERROR_VALUE_COUNT_ON_ROW', -22);
+
+/**
+ * The DSN submitted has problems
+ */
+define('DB_ERROR_INVALID_DSN', -23);
+
+/**
+ * Could not connect to the database
+ */
+define('DB_ERROR_CONNECT_FAILED', -24);
+
+/**
+ * The PHP extension needed for this DBMS could not be found
+ */
+define('DB_ERROR_EXTENSION_NOT_FOUND',-25);
+
+/**
+ * The present user has inadequate permissions to perform the task requestd
+ */
+define('DB_ERROR_ACCESS_VIOLATION', -26);
+
+/**
+ * The database requested does not exist
+ */
+define('DB_ERROR_NOSUCHDB', -27);
+
+/**
+ * Tried to insert a null value into a column that doesn't allow nulls
+ */
+define('DB_ERROR_CONSTRAINT_NOT_NULL',-29);
+/**#@-*/
+
+
+// }}}
+// {{{ prepared statement-related
+
+
+/**#@+
+ * Identifiers for the placeholders used in prepared statements.
+ * @see DB_common::prepare()
+ */
+
+/**
+ * Indicates a scalar (<kbd>?</kbd>) placeholder was used
+ *
+ * Quote and escape the value as necessary.
+ */
+define('DB_PARAM_SCALAR', 1);
+
+/**
+ * Indicates an opaque (<kbd>&</kbd>) placeholder was used
+ *
+ * The value presented is a file name. Extract the contents of that file
+ * and place them in this column.
+ */
+define('DB_PARAM_OPAQUE', 2);
+
+/**
+ * Indicates a misc (<kbd>!</kbd>) placeholder was used
+ *
+ * The value should not be quoted or escaped.
+ */
+define('DB_PARAM_MISC', 3);
+/**#@-*/
+
+
+// }}}
+// {{{ binary data-related
+
+
+/**#@+
+ * The different ways of returning binary data from queries.
+ */
+
+/**
+ * Sends the fetched data straight through to output
+ */
+define('DB_BINMODE_PASSTHRU', 1);
+
+/**
+ * Lets you return data as usual
+ */
+define('DB_BINMODE_RETURN', 2);
+
+/**
+ * Converts the data to hex format before returning it
+ *
+ * For example the string "123" would become "313233".
+ */
+define('DB_BINMODE_CONVERT', 3);
+/**#@-*/
+
+
+// }}}
+// {{{ fetch modes
+
+
+/**#@+
+ * Fetch Modes.
+ * @see DB_common::setFetchMode()
+ */
+
+/**
+ * Indicates the current default fetch mode should be used
+ * @see DB_common::$fetchmode
+ */
+define('DB_FETCHMODE_DEFAULT', 0);
+
+/**
+ * Column data indexed by numbers, ordered from 0 and up
+ */
+define('DB_FETCHMODE_ORDERED', 1);
+
+/**
+ * Column data indexed by column names
+ */
+define('DB_FETCHMODE_ASSOC', 2);
+
+/**
+ * Column data as object properties
+ */
+define('DB_FETCHMODE_OBJECT', 3);
+
+/**
+ * For multi-dimensional results, make the column name the first level
+ * of the array and put the row number in the second level of the array
+ *
+ * This is flipped from the normal behavior, which puts the row numbers
+ * in the first level of the array and the column names in the second level.
+ */
+define('DB_FETCHMODE_FLIPPED', 4);
+/**#@-*/
+
+/**#@+
+ * Old fetch modes. Left here for compatibility.
+ */
+define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
+define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC);
+define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED);
+/**#@-*/
+
+
+// }}}
+// {{{ tableInfo() && autoPrepare()-related
+
+
+/**#@+
+ * The type of information to return from the tableInfo() method.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::tableInfo()
+ *
+ * {@internal Since the TABLEINFO constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}}
+ */
+define('DB_TABLEINFO_ORDER', 1);
+define('DB_TABLEINFO_ORDERTABLE', 2);
+define('DB_TABLEINFO_FULL', 3);
+/**#@-*/
+
+
+/**#@+
+ * The type of query to create with the automatic query building methods.
+ * @see DB_common::autoPrepare(), DB_common::autoExecute()
+ */
+define('DB_AUTOQUERY_INSERT', 1);
+define('DB_AUTOQUERY_UPDATE', 2);
+/**#@-*/
+
+
+// }}}
+// {{{ portability modes
+
+
+/**#@+
+ * Portability Modes.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::setOption()
+ *
+ * {@internal Since the PORTABILITY constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}}
+ */
+
+/**
+ * Turn off all portability features
+ */
+define('DB_PORTABILITY_NONE', 0);
+
+/**
+ * Convert names of tables and fields to lower case
+ * when using the get*(), fetch*() and tableInfo() methods
+ */
+define('DB_PORTABILITY_LOWERCASE', 1);
+
+/**
+ * Right trim the data output by get*() and fetch*()
+ */
+define('DB_PORTABILITY_RTRIM', 2);
+
+/**
+ * Force reporting the number of rows deleted
+ */
+define('DB_PORTABILITY_DELETE_COUNT', 4);
+
+/**
+ * Enable hack that makes numRows() work in Oracle
+ */
+define('DB_PORTABILITY_NUMROWS', 8);
+
+/**
+ * Makes certain error messages in certain drivers compatible
+ * with those from other DBMS's
+ *
+ * + mysql, mysqli: change unique/primary key constraints
+ * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+ *
+ * + odbc(access): MS's ODBC driver reports 'no such field' as code
+ * 07001, which means 'too few parameters.' When this option is on
+ * that code gets mapped to DB_ERROR_NOSUCHFIELD.
+ */
+define('DB_PORTABILITY_ERRORS', 16);
+
+/**
+ * Convert null values to empty strings in data output by
+ * get*() and fetch*()
+ */
+define('DB_PORTABILITY_NULL_TO_EMPTY', 32);
+
+/**
+ * Turn on all portability features
+ */
+define('DB_PORTABILITY_ALL', 63);
+/**#@-*/
+
+// }}}
+
+
+// }}}
+// {{{ class DB
+
+/**
+ * Database independent query interface
+ *
+ * The main "DB" class is simply a container class with some static
+ * methods for creating DB objects as well as some utility functions
+ * common to all parts of DB.
+ *
+ * The object model of DB is as follows (indentation means inheritance):
+ * <pre>
+ * DB The main DB class. This is simply a utility class
+ * with some "static" methods for creating DB objects as
+ * well as common utility functions for other DB classes.
+ *
+ * DB_common The base for each DB implementation. Provides default
+ * | implementations (in OO lingo virtual methods) for
+ * | the actual DB implementations as well as a bunch of
+ * | query utility functions.
+ * |
+ * +-DB_mysql The DB implementation for MySQL. Inherits DB_common.
+ * When calling DB::factory or DB::connect for MySQL
+ * connections, the object returned is an instance of this
+ * class.
+ * </pre>
+ *
+ * @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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/DB
+ */
+class DB
+{
+ // {{{ &factory()
+
+ /**
+ * Create a new DB object for the specified database type but don't
+ * connect to the database
+ *
+ * @param string $type the database type (eg "mysql")
+ * @param array $options an associative array of option names and values
+ *
+ * @return object a new DB object. A DB_Error object on failure.
+ *
+ * @see DB_common::setOption()
+ */
+ function &factory($type, $options = false)
+ {
+ if (!is_array($options)) {
+ $options = array('persistent' => $options);
+ }
+
+ if (isset($options['debug']) && $options['debug'] >= 2) {
+ // expose php errors with sufficient debug level
+ include_once "DB/{$type}.php";
+ } else {
+ @include_once "DB/{$type}.php";
+ }
+
+ $classname = "DB_${type}";
+
+ if (!class_exists($classname)) {
+ $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+ "Unable to include the DB/{$type}.php"
+ . " file for '$dsn'",
+ 'DB_Error', true);
+ return $tmp;
+ }
+
+ @$obj =& new $classname;
+
+ foreach ($options as $option => $value) {
+ $test = $obj->setOption($option, $value);
+ if (DB::isError($test)) {
+ return $test;
+ }
+ }
+
+ return $obj;
+ }
+
+ // }}}
+ // {{{ &connect()
+
+ /**
+ * Create a new DB object including a connection to the specified database
+ *
+ * Example 1.
+ * <code>
+ * require_once 'DB.php';
+ *
+ * $dsn = 'pgsql://user:password@host/database';
+ * $options = array(
+ * 'debug' => 2,
+ * 'portability' => DB_PORTABILITY_ALL,
+ * );
+ *
+ * $db =& DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param mixed $dsn the string "data source name" or array in the
+ * format returned by DB::parseDSN()
+ * @param array $options an associative array of option names and values
+ *
+ * @return object a new DB object. A DB_Error object on failure.
+ *
+ * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(),
+ * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(),
+ * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(),
+ * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(),
+ * DB_sybase::connect()
+ *
+ * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError()
+ */
+ function &connect($dsn, $options = array())
+ {
+ $dsninfo = DB::parseDSN($dsn);
+ $type = $dsninfo['phptype'];
+
+ if (!is_array($options)) {
+ /*
+ * For backwards compatibility. $options used to be boolean,
+ * indicating whether the connection should be persistent.
+ */
+ $options = array('persistent' => $options);
+ }
+
+ if (isset($options['debug']) && $options['debug'] >= 2) {
+ // expose php errors with sufficient debug level
+ include_once "DB/${type}.php";
+ } else {
+ @include_once "DB/${type}.php";
+ }
+
+ $classname = "DB_${type}";
+ if (!class_exists($classname)) {
+ $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+ "Unable to include the DB/{$type}.php"
+ . " file for '$dsn'",
+ 'DB_Error', true);
+ return $tmp;
+ }
+
+ @$obj =& new $classname;
+
+ foreach ($options as $option => $value) {
+ $test = $obj->setOption($option, $value);
+ if (DB::isError($test)) {
+ return $test;
+ }
+ }
+
+ $err = $obj->connect($dsninfo, $obj->getOption('persistent'));
+ if (DB::isError($err)) {
+ $err->addUserInfo($dsn);
+ return $err;
+ }
+
+ return $obj;
+ }
+
+ // }}}
+ // {{{ apiVersion()
+
+ /**
+ * Return the DB API version
+ *
+ * @return string the DB API version number
+ */
+ function apiVersion()
+ {
+ return '@package_version@';
+ }
+
+ // }}}
+ // {{{ isError()
+
+ /**
+ * Determines if a variable is a DB_Error object
+ *
+ * @param mixed $value the variable to check
+ *
+ * @return bool whether $value is DB_Error object
+ */
+ function isError($value)
+ {
+ return is_a($value, 'DB_Error');
+ }
+
+ // }}}
+ // {{{ isConnection()
+
+ /**
+ * Determines if a value is a DB_<driver> object
+ *
+ * @param mixed $value the value to test
+ *
+ * @return bool whether $value is a DB_<driver> object
+ */
+ function isConnection($value)
+ {
+ return (is_object($value) &&
+ is_subclass_of($value, 'db_common') &&
+ method_exists($value, 'simpleQuery'));
+ }
+
+ // }}}
+ // {{{ isManip()
+
+ /**
+ * Tell whether a query is a data manipulation or data definition query
+ *
+ * Examples of data manipulation queries are INSERT, UPDATE and DELETE.
+ * Examples of data definition queries are CREATE, DROP, ALTER, GRANT,
+ * REVOKE.
+ *
+ * @param string $query the query
+ *
+ * @return boolean whether $query is a data manipulation query
+ */
+ function isManip($query)
+ {
+ $manips = 'INSERT|UPDATE|DELETE|REPLACE|'
+ . 'CREATE|DROP|'
+ . 'LOAD DATA|SELECT .* INTO|COPY|'
+ . 'ALTER|GRANT|REVOKE|'
+ . 'LOCK|UNLOCK';
+ if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) {
+ return true;
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ errorMessage()
+
+ /**
+ * Return a textual error message for a DB error code
+ *
+ * @param integer $value the DB error code
+ *
+ * @return string the error message or false if the error code was
+ * not recognized
+ */
+ function errorMessage($value)
+ {
+ static $errorMessages;
+ if (!isset($errorMessages)) {
+ $errorMessages = array(
+ DB_ERROR => 'unknown error',
+ DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions',
+ DB_ERROR_ALREADY_EXISTS => 'already exists',
+ DB_ERROR_CANNOT_CREATE => 'can not create',
+ DB_ERROR_CANNOT_DROP => 'can not drop',
+ DB_ERROR_CONNECT_FAILED => 'connect failed',
+ DB_ERROR_CONSTRAINT => 'constraint violation',
+ DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+ DB_ERROR_DIVZERO => 'division by zero',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
+ DB_ERROR_INVALID => 'invalid',
+ DB_ERROR_INVALID_DATE => 'invalid date or time',
+ DB_ERROR_INVALID_DSN => 'invalid DSN',
+ DB_ERROR_INVALID_NUMBER => 'invalid number',
+ DB_ERROR_MISMATCH => 'mismatch',
+ DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied',
+ DB_ERROR_NODBSELECTED => 'no database selected',
+ DB_ERROR_NOSUCHDB => 'no such database',
+ DB_ERROR_NOSUCHFIELD => 'no such field',
+ DB_ERROR_NOSUCHTABLE => 'no such table',
+ DB_ERROR_NOT_CAPABLE => 'DB backend not capable',
+ DB_ERROR_NOT_FOUND => 'not found',
+ DB_ERROR_NOT_LOCKED => 'not locked',
+ DB_ERROR_SYNTAX => 'syntax error',
+ DB_ERROR_UNSUPPORTED => 'not supported',
+ DB_ERROR_TRUNCATED => 'truncated',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+ DB_OK => 'no error',
+ );
+ }
+
+ if (DB::isError($value)) {
+ $value = $value->getCode();
+ }
+
+ return isset($errorMessages[$value]) ? $errorMessages[$value]
+ : $errorMessages[DB_ERROR];
+ }
+
+ // }}}
+ // {{{ parseDSN()
+
+ /**
+ * Parse a data source name
+ *
+ * Additional keys can be added by appending a URI query string to the
+ * end of the DSN.
+ *
+ * The format of the supplied DSN is in its fullest form:
+ * <code>
+ * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
+ * </code>
+ *
+ * Most variations are allowed:
+ * <code>
+ * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
+ * phptype://username:password@hostspec/database_name
+ * phptype://username:password@hostspec
+ * phptype://username@hostspec
+ * phptype://hostspec/database
+ * phptype://hostspec
+ * phptype(dbsyntax)
+ * phptype
+ * </code>
+ *
+ * @param string $dsn Data Source Name to be parsed
+ *
+ * @return array an associative array with the following keys:
+ * + phptype: Database backend used in PHP (mysql, odbc etc.)
+ * + dbsyntax: Database used with regards to SQL syntax etc.
+ * + protocol: Communication protocol to use (tcp, unix etc.)
+ * + hostspec: Host specification (hostname[:port])
+ * + database: Database to use on the DBMS server
+ * + username: User name for login
+ * + password: Password for login
+ */
+ function parseDSN($dsn)
+ {
+ $parsed = array(
+ 'phptype' => false,
+ 'dbsyntax' => false,
+ 'username' => false,
+ 'password' => false,
+ 'protocol' => false,
+ 'hostspec' => false,
+ 'port' => false,
+ 'socket' => false,
+ 'database' => false,
+ );
+
+ if (is_array($dsn)) {
+ $dsn = array_merge($parsed, $dsn);
+ if (!$dsn['dbsyntax']) {
+ $dsn['dbsyntax'] = $dsn['phptype'];
+ }
+ return $dsn;
+ }
+
+ // Find phptype and dbsyntax
+ if (($pos = strpos($dsn, '://')) !== false) {
+ $str = substr($dsn, 0, $pos);
+ $dsn = substr($dsn, $pos + 3);
+ } else {
+ $str = $dsn;
+ $dsn = null;
+ }
+
+ // Get phptype and dbsyntax
+ // $str => phptype(dbsyntax)
+ if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+ $parsed['phptype'] = $arr[1];
+ $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
+ } else {
+ $parsed['phptype'] = $str;
+ $parsed['dbsyntax'] = $str;
+ }
+
+ if (!count($dsn)) {
+ return $parsed;
+ }
+
+ // Get (if found): username and password
+ // $dsn => username:password@protocol+hostspec/database
+ if (($at = strrpos($dsn,'@')) !== false) {
+ $str = substr($dsn, 0, $at);
+ $dsn = substr($dsn, $at + 1);
+ if (($pos = strpos($str, ':')) !== false) {
+ $parsed['username'] = rawurldecode(substr($str, 0, $pos));
+ $parsed['password'] = rawurldecode(substr($str, $pos + 1));
+ } else {
+ $parsed['username'] = rawurldecode($str);
+ }
+ }
+
+ // Find protocol and hostspec
+
+ if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
+ // $dsn => proto(proto_opts)/database
+ $proto = $match[1];
+ $proto_opts = $match[2] ? $match[2] : false;
+ $dsn = $match[3];
+
+ } else {
+ // $dsn => protocol+hostspec/database (old format)
+ if (strpos($dsn, '+') !== false) {
+ list($proto, $dsn) = explode('+', $dsn, 2);
+ }
+ if (strpos($dsn, '/') !== false) {
+ list($proto_opts, $dsn) = explode('/', $dsn, 2);
+ } else {
+ $proto_opts = $dsn;
+ $dsn = null;
+ }
+ }
+
+ // process the different protocol options
+ $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
+ $proto_opts = rawurldecode($proto_opts);
+ if ($parsed['protocol'] == 'tcp') {
+ if (strpos($proto_opts, ':') !== false) {
+ list($parsed['hostspec'],
+ $parsed['port']) = explode(':', $proto_opts);
+ } else {
+ $parsed['hostspec'] = $proto_opts;
+ }
+ } elseif ($parsed['protocol'] == 'unix') {
+ $parsed['socket'] = $proto_opts;
+ }
+
+ // Get dabase if any
+ // $dsn => database
+ if ($dsn) {
+ if (($pos = strpos($dsn, '?')) === false) {
+ // /database
+ $parsed['database'] = rawurldecode($dsn);
+ } else {
+ // /database?param1=value1&param2=value2
+ $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
+ $dsn = substr($dsn, $pos + 1);
+ if (strpos($dsn, '&') !== false) {
+ $opts = explode('&', $dsn);
+ } else { // database?param1=value1
+ $opts = array($dsn);
+ }
+ foreach ($opts as $opt) {
+ list($key, $value) = explode('=', $opt);
+ if (!isset($parsed[$key])) {
+ // don't allow params overwrite
+ $parsed[$key] = rawurldecode($value);
+ }
+ }
+ }
+ }
+
+ return $parsed;
+ }
+
+ // }}}
+}
+
+// }}}
+// {{{ class DB_Error
+
+/**
+ * DB_Error implements a class for reporting portable database error
+ * messages
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/DB
+ */
+class DB_Error extends PEAR_Error
+{
+ // {{{ constructor
+
+ /**
+ * DB_Error constructor
+ *
+ * @param mixed $code DB error code, or string with error message
+ * @param int $mode what "error mode" to operate in
+ * @param int $level what error level to use for $mode &
+ * PEAR_ERROR_TRIGGER
+ * @param mixed $debuginfo additional debug info, such as the last query
+ *
+ * @see PEAR_Error
+ */
+ function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
+ $level = E_USER_NOTICE, $debuginfo = null)
+ {
+ if (is_int($code)) {
+ $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code,
+ $mode, $level, $debuginfo);
+ } else {
+ $this->PEAR_Error("DB Error: $code", DB_ERROR,
+ $mode, $level, $debuginfo);
+ }
+ }
+
+ // }}}
+}
+
+// }}}
+// {{{ class DB_result
+
+/**
+ * This class implements a wrapper for a DB result set
+ *
+ * A new instance of this class will be returned by the DB implementation
+ * after processing a query that returns data.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/DB
+ */
+class DB_result
+{
+ // {{{ properties
+
+ /**
+ * Should results be freed automatically when there are no more rows?
+ * @var boolean
+ * @see DB_common::$options
+ */
+ var $autofree;
+
+ /**
+ * A reference to the DB_<driver> object
+ * @var object
+ */
+ var $dbh;
+
+ /**
+ * The current default fetch mode
+ * @var integer
+ * @see DB_common::$fetchmode
+ */
+ var $fetchmode;
+
+ /**
+ * The name of the class into which results should be fetched when
+ * DB_FETCHMODE_OBJECT is in effect
+ *
+ * @var string
+ * @see DB_common::$fetchmode_object_class
+ */
+ var $fetchmode_object_class;
+
+ /**
+ * The number of rows to fetch from a limit query
+ * @var integer
+ */
+ var $limit_count = null;
+
+ /**
+ * The row to start fetching from in limit queries
+ * @var integer
+ */
+ var $limit_from = null;
+
+ /**
+ * The execute parameters that created this result
+ * @var array
+ * @since Property available since Release 1.7.0
+ */
+ var $parameters;
+
+ /**
+ * The query string that created this result
+ *
+ * Copied here incase it changes in $dbh, which is referenced
+ *
+ * @var string
+ * @since Property available since Release 1.7.0
+ */
+ var $query;
+
+ /**
+ * The query result resource id created by PHP
+ * @var resource
+ */
+ var $result;
+
+ /**
+ * The present row being dealt with
+ * @var integer
+ */
+ var $row_counter = null;
+
+ /**
+ * The prepared statement resource id created by PHP in $dbh
+ *
+ * This resource is only available when the result set was created using
+ * a driver's native execute() method, not PEAR DB's emulated one.
+ *
+ * Copied here incase it changes in $dbh, which is referenced
+ *
+ * {@internal Mainly here because the InterBase/Firebird API is only
+ * able to retrieve data from result sets if the statemnt handle is
+ * still in scope.}}
+ *
+ * @var resource
+ * @since Property available since Release 1.7.0
+ */
+ var $statement;
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor sets the object's properties
+ *
+ * @param object &$dbh the DB object reference
+ * @param resource $result the result resource id
+ * @param array $options an associative array with result options
+ *
+ * @return void
+ */
+ function DB_result(&$dbh, $result, $options = array())
+ {
+ $this->autofree = $dbh->options['autofree'];
+ $this->dbh = &$dbh;
+ $this->fetchmode = $dbh->fetchmode;
+ $this->fetchmode_object_class = $dbh->fetchmode_object_class;
+ $this->parameters = $dbh->last_parameters;
+ $this->query = $dbh->last_query;
+ $this->result = $result;
+ $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt;
+ foreach ($options as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+
+ /**
+ * Set options for the DB_result object
+ *
+ * @param string $key the option to set
+ * @param mixed $value the value to set the option to
+ *
+ * @return void
+ */
+ function setOption($key, $value = null)
+ {
+ switch ($key) {
+ case 'limit_from':
+ $this->limit_from = $value;
+ break;
+ case 'limit_count':
+ $this->limit_count = $value;
+ }
+ }
+
+ // }}}
+ // {{{ fetchRow()
+
+ /**
+ * Fetch a row of data and return it by reference into an array
+ *
+ * The type of array returned can be controlled either by setting this
+ * method's <var>$fetchmode</var> parameter or by changing the default
+ * fetch mode setFetchMode() before calling this method.
+ *
+ * There are two options for standardizing the information returned
+ * from databases, ensuring their values are consistent when changing
+ * DBMS's. These portability options can be turned on when creating a
+ * new DB object or by using setOption().
+ *
+ * + <var>DB_PORTABILITY_LOWERCASE</var>
+ * convert names of fields to lower case
+ *
+ * + <var>DB_PORTABILITY_RTRIM</var>
+ * right trim the data
+ *
+ * @param int $fetchmode the constant indicating how to format the data
+ * @param int $rownum the row number to fetch (index starts at 0)
+ *
+ * @return mixed an array or object containing the row's data,
+ * NULL when the end of the result set is reached
+ * or a DB_Error object on failure.
+ *
+ * @see DB_common::setOption(), DB_common::setFetchMode()
+ */
+ function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+ {
+ if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+ $fetchmode = $this->fetchmode;
+ }
+ if ($fetchmode === DB_FETCHMODE_OBJECT) {
+ $fetchmode = DB_FETCHMODE_ASSOC;
+ $object_class = $this->fetchmode_object_class;
+ }
+ if ($this->limit_from !== null) {
+ if ($this->row_counter === null) {
+ $this->row_counter = $this->limit_from;
+ // Skip rows
+ if ($this->dbh->features['limit'] === false) {
+ $i = 0;
+ while ($i++ < $this->limit_from) {
+ $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+ }
+ }
+ }
+ if ($this->row_counter >= ($this->limit_from + $this->limit_count))
+ {
+ if ($this->autofree) {
+ $this->free();
+ }
+ $tmp = null;
+ return $tmp;
+ }
+ if ($this->dbh->features['limit'] === 'emulate') {
+ $rownum = $this->row_counter;
+ }
+ $this->row_counter++;
+ }
+ $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+ if ($res === DB_OK) {
+ if (isset($object_class)) {
+ // The default mode is specified in the
+ // DB_common::fetchmode_object_class property
+ if ($object_class == 'stdClass') {
+ $arr = (object) $arr;
+ } else {
+ $arr = &new $object_class($arr);
+ }
+ }
+ return $arr;
+ }
+ if ($res == null && $this->autofree) {
+ $this->free();
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Fetch a row of data into an array which is passed by reference
+ *
+ * The type of array returned can be controlled either by setting this
+ * method's <var>$fetchmode</var> parameter or by changing the default
+ * fetch mode setFetchMode() before calling this method.
+ *
+ * There are two options for standardizing the information returned
+ * from databases, ensuring their values are consistent when changing
+ * DBMS's. These portability options can be turned on when creating a
+ * new DB object or by using setOption().
+ *
+ * + <var>DB_PORTABILITY_LOWERCASE</var>
+ * convert names of fields to lower case
+ *
+ * + <var>DB_PORTABILITY_RTRIM</var>
+ * right trim the data
+ *
+ * @param array &$arr the variable where the data should be placed
+ * @param int $fetchmode the constant indicating how to format the data
+ * @param int $rownum the row number to fetch (index starts at 0)
+ *
+ * @return mixed DB_OK if a row is processed, NULL when the end of the
+ * result set is reached or a DB_Error object on failure
+ *
+ * @see DB_common::setOption(), DB_common::setFetchMode()
+ */
+ function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+ {
+ if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+ $fetchmode = $this->fetchmode;
+ }
+ if ($fetchmode === DB_FETCHMODE_OBJECT) {
+ $fetchmode = DB_FETCHMODE_ASSOC;
+ $object_class = $this->fetchmode_object_class;
+ }
+ if ($this->limit_from !== null) {
+ if ($this->row_counter === null) {
+ $this->row_counter = $this->limit_from;
+ // Skip rows
+ if ($this->dbh->features['limit'] === false) {
+ $i = 0;
+ while ($i++ < $this->limit_from) {
+ $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+ }
+ }
+ }
+ if ($this->row_counter >= (
+ $this->limit_from + $this->limit_count))
+ {
+ if ($this->autofree) {
+ $this->free();
+ }
+ return null;
+ }
+ if ($this->dbh->features['limit'] === 'emulate') {
+ $rownum = $this->row_counter;
+ }
+
+ $this->row_counter++;
+ }
+ $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+ if ($res === DB_OK) {
+ if (isset($object_class)) {
+ // default mode specified in the
+ // DB_common::fetchmode_object_class property
+ if ($object_class == 'stdClass') {
+ $arr = (object) $arr;
+ } else {
+ $arr = new $object_class($arr);
+ }
+ }
+ return DB_OK;
+ }
+ if ($res == null && $this->autofree) {
+ $this->free();
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Get the the number of columns in a result set
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ */
+ function numCols()
+ {
+ return $this->dbh->numCols($this->result);
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Get the number of rows in a result set
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function numRows()
+ {
+ if ($this->dbh->features['numrows'] === 'emulate'
+ && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS)
+ {
+ if ($this->dbh->features['prepare']) {
+ $res = $this->dbh->query($this->query, $this->parameters);
+ } else {
+ $res = $this->dbh->query($this->query);
+ }
+ if (DB::isError($res)) {
+ return $res;
+ }
+ $i = 0;
+ while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) {
+ $i++;
+ }
+ return $i;
+ } else {
+ return $this->dbh->numRows($this->result);
+ }
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Get the next result if a batch of queries was executed
+ *
+ * @return bool true if a new result is available or false if not
+ */
+ function nextResult()
+ {
+ return $this->dbh->nextResult($this->result);
+ }
+
+ // }}}
+ // {{{ free()
+
+ /**
+ * Frees the resources allocated for this result set
+ *
+ * @return bool true on success. A DB_Error object on failure.
+ */
+ function free()
+ {
+ $err = $this->dbh->freeResult($this->result);
+ if (DB::isError($err)) {
+ return $err;
+ }
+ $this->result = false;
+ $this->statement = false;
+ return true;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * @see DB_common::tableInfo()
+ * @deprecated Method deprecated some time before Release 1.2
+ */
+ function tableInfo($mode = null)
+ {
+ if (is_string($mode)) {
+ return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+ return $this->dbh->tableInfo($this, $mode);
+ }
+
+ // }}}
+ // {{{ getQuery()
+
+ /**
+ * Determine the query string that created this result
+ *
+ * @return string the query string
+ *
+ * @since Method available since Release 1.7.0
+ */
+ function getQuery()
+ {
+ return $this->query;
+ }
+
+ // }}}
+ // {{{ getRowCounter()
+
+ /**
+ * Tells which row number is currently being processed
+ *
+ * @return integer the current row being looked at. Starts at 1.
+ */
+ function getRowCounter()
+ {
+ return $this->row_counter;
+ }
+
+ // }}}
+}
+
+// }}}
+// {{{ class DB_row
+
+/**
+ * PEAR DB Row Object
+ *
+ * The object contains a row of data from a result set. Each column's data
+ * is placed in a property named for the column.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/DB
+ * @see DB_common::setFetchMode()
+ */
+class DB_row
+{
+ // {{{ constructor
+
+ /**
+ * The constructor places a row's data into properties of this object
+ *
+ * @param array the array containing the row's data
+ *
+ * @return void
+ */
+ function DB_row(&$arr)
+ {
+ foreach ($arr as $key => $value) {
+ $this->$key = &$arr[$key];
+ }
+ }
+
+ // }}}
+}
+
+// }}}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/common.php b/program/lib/DB/common.php
new file mode 100644
index 000000000..7988e7194
--- /dev/null
+++ b/program/lib/DB/common.php
@@ -0,0 +1,2157 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+/**
+ * DB_common is the base class from which each database driver class extends
+ *
+ * All common methods are declared here. If a given DBMS driver contains
+ * a particular method, that method will overload the one here.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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();
+
+
+ // }}}
+ // {{{ 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) || is_double($in)) {
+ return $in;
+ } elseif (is_bool($in)) {
+ return $in ? 1 : 0;
+ } elseif (is_null($in)) {
+ return 'NULL';
+ } else {
+ return "'" . $this->escapeSimple($in) . "'";
+ }
+ }
+
+ // }}}
+ // {{{ 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
+ *
+ * @return object the PEAR_Error object
+ *
+ * @see PEAR_Error
+ */
+ function &raiseError($code = DB_ERROR, $mode = null, $options = null,
+ $userinfo = null, $nativecode = null)
+ {
+ // The error is yet a DB error object
+ if (is_object($code)) {
+ // because we the static PEAR::raiseError, our global
+ // handler should be used if it is set
+ if ($mode === null && !empty($this->_default_error_mode)) {
+ $mode = $this->_default_error_mode;
+ $options = $this->_default_error_options;
+ }
+ $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);
+ }
+
+ // }}}
+ // {{{ _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/program/lib/DB/dbase.php b/program/lib/DB/dbase.php
new file mode 100644
index 000000000..6026f15ce
--- /dev/null
+++ b/program/lib/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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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;
+ }
+
+ // }}}
+ // {{{ 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);
+ }
+
+ // }}}
+ // {{{ quoteSmart()
+
+ /**
+ * Formats input so it can be safely used in a query
+ *
+ * @param mixed $in the data to be formatted
+ *
+ * @return mixed the formatted data. The format depends on the input's
+ * PHP type:
+ * + null = the string <samp>NULL</samp>
+ * + boolean = <samp>T</samp> if true or
+ * <samp>F</samp> if false. Use the <kbd>Logical</kbd>
+ * data type.
+ * + integer or double = the unquoted number
+ * + other (including strings and numeric strings) =
+ * the data with single quotes escaped by preceeding
+ * single quotes then the whole string is encapsulated
+ * between single quotes
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteSmart($in)
+ {
+ if (is_int($in) || is_double($in)) {
+ return $in;
+ } elseif (is_bool($in)) {
+ return $in ? 'T' : 'F';
+ } elseif (is_null($in)) {
+ return 'NULL';
+ } else {
+ return "'" . $this->escapeSimple($in) . "'";
+ }
+ }
+
+ // }}}
+ // {{{ 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/program/lib/DB/fbsql.php b/program/lib/DB/fbsql.php
new file mode 100644
index 000000000..b25868dc1
--- /dev/null
+++ b/program/lib/DB/fbsql.php
@@ -0,0 +1,770 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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 (DB::isManip($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 @fbsql_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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();
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ @fbsql_rollback();
+ }
+
+ // }}}
+ // {{{ 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 (DB::isManip($this->last_query)) {
+ $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)) {
+ return preg_replace('/^([\s(])*SELECT/i',
+ "\\1SELECT TOP($count)", $query);
+ } else {
+ return preg_replace('/([\s(])*SELECT/i',
+ "\\1SELECT TOP($from, $count)", $query);
+ }
+ }
+
+ // }}}
+ // {{{ quoteSmart()
+
+ /**
+ * Formats input so it can be safely used in a query
+ *
+ * @param mixed $in the data to be formatted
+ *
+ * @return mixed the formatted data. The format depends on the input's
+ * PHP type:
+ * + null = the string <samp>NULL</samp>
+ * + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
+ * + integer or double = the unquoted number
+ * + other (including strings and numeric strings) =
+ * the data escaped according to FrontBase's settings
+ * then encapsulated between single quotes
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteSmart($in)
+ {
+ if (is_int($in) || is_double($in)) {
+ return $in;
+ } elseif (is_bool($in)) {
+ return $in ? 'TRUE' : 'FALSE';
+ } elseif (is_null($in)) {
+ return 'NULL';
+ } else {
+ return "'" . $this->escapeSimple($in) . "'";
+ }
+ }
+
+ // }}}
+ // {{{ 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/program/lib/DB/ibase.php b/program/lib/DB/ibase.php
new file mode 100644
index 000000000..b5e2386a0
--- /dev/null
+++ b/program/lib/DB/ibase.php
@@ -0,0 +1,1071 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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,
+ -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 = DB::isManip($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 @ibase_free_result($result);
+ }
+
+ // }}}
+ // {{{ freeQuery()
+
+ function freeQuery($query)
+ {
+ @ibase_free_query($query);
+ return true;
+ }
+
+ // }}}
+ // {{{ 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);
+ $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]) {
+ $tmp = DB_OK;
+ } else {
+ $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,
+ );
+ }
+
+ $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/program/lib/DB/ifx.php b/program/lib/DB/ifx.php
new file mode 100644
index 000000000..22575c746
--- /dev/null
+++ b/program/lib/DB/ifx.php
@@ -0,0 +1,681 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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,
+ '-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,
+ '-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 = DB::isManip($query);
+ $this->last_query = $query;
+ $this->affected = null;
+ if (preg_match('/(SELECT)/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)/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 (DB::isManip($this->last_query)) {
+ 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 @ifx_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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/program/lib/DB/msql.php b/program/lib/DB/msql.php
new file mode 100644
index 000000000..81ca8553b
--- /dev/null
+++ b/program/lib/DB/msql.php
@@ -0,0 +1,810 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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 (DB::isManip($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 @msql_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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);
+ }
+
+ // }}}
+ // {{{ 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;
+ 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/program/lib/DB/mssql.php b/program/lib/DB/mssql.php
new file mode 100644
index 000000000..a84db89f6
--- /dev/null
+++ b/program/lib/DB/mssql.php
@@ -0,0 +1,914 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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.
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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(
+ 110 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 155 => DB_ERROR_NOSUCHFIELD,
+ 170 => DB_ERROR_SYNTAX,
+ 207 => DB_ERROR_NOSUCHFIELD,
+ 208 => DB_ERROR_NOSUCHTABLE,
+ 245 => DB_ERROR_INVALID_NUMBER,
+ 515 => DB_ERROR_CONSTRAINT_NOT_NULL,
+ 547 => DB_ERROR_CONSTRAINT,
+ 1913 => DB_ERROR_ALREADY_EXISTS,
+ 2627 => DB_ERROR_CONSTRAINT,
+ 2714 => DB_ERROR_ALREADY_EXISTS,
+ 3701 => DB_ERROR_NOSUCHTABLE,
+ 8134 => DB_ERROR_DIVZERO,
+ );
+
+ /**
+ * 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 = DB::isManip($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_array($result, MSSQL_ASSOC);
+ 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 @mssql_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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 (DB::isManip($this->last_query)) {
+ $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 @@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++) {
+ $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),
+ // We only support flags for table
+ 'flags' => $got_string
+ ? $this->_mssql_field_flags($result,
+ @mssql_field_name($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) {
+ @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);
+
+ 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);
+
+ 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/program/lib/DB/mysql.php b/program/lib/DB/mysql.php
new file mode 100644
index 000000000..5b737b606
--- /dev/null
+++ b/program/lib/DB/mysql.php
@@ -0,0 +1,1034 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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,
+ );
+
+ /**
+ * 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 = DB::isManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ if ($this->_db) {
+ if (!@mysql_select_db($this->_db, $this->connection)) {
+ return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection);
+ $result = @mysql_query('BEGIN', $this->connection);
+ if (!$result) {
+ return $this->mysqlRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ 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 @mysql_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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 (DB::isManip($this->last_query)) {
+ 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
+ *
+ * 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 . '`';
+ }
+
+ // }}}
+ // {{{ 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)) {
+ 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)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @mysql_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->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/program/lib/DB/mysqli.php b/program/lib/DB/mysqli.php
new file mode 100644
index 000000000..bf742e27f
--- /dev/null
+++ b/program/lib/DB/mysqli.php
@@ -0,0 +1,1076 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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,
+ );
+
+ /**
+ * 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',
+ );
+
+
+ // }}}
+ // {{{ 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 ($this->getOption('ssl') === true) {
+ $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 = DB::isManip($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 @mysqli_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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 (DB::isManip($this->last_query)) {
+ 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
+ *
+ * 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 . '`';
+ }
+
+ // }}}
+ // {{{ 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)) {
+ 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)) {
+ /*
+ * 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',
+ 'len' => $tmp->max_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/program/lib/DB/oci8.php b/program/lib/DB/oci8.php
new file mode 100644
index 000000000..c610a04e8
--- /dev/null
+++ b/program/lib/DB/oci8.php
@@ -0,0 +1,1117 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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,
+ );
+
+ /**
+ * 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();
+
+
+ // }}}
+ // {{{ 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'];
+ }
+
+ 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';
+ }
+
+ // Backwards compatibility with DB < 1.7.0
+ if (empty($dsn['database']) && !empty($dsn['hostspec'])) {
+ $db = $dsn['hostspec'];
+ } else {
+ $db = $dsn['database'];
+ }
+
+ $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 ($dsn['hostspec']) {
+ $this->connection = @$connect_function($dsn['username'],
+ $dsn['password'],
+ $dsn['hostspec']);
+ } 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 (DB::isManip($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 @OCIFreeStatement($result);
+ }
+
+ /**
+ * 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;
+
+ if (count($this->_data)) {
+ $smt = $this->prepare('SELECT COUNT(*) FROM ('.$this->last_query.')');
+ $count = $this->execute($smt, $this->_data);
+ } else {
+ $count =& $this->query($countquery);
+ }
+
+ if (DB::isError($count) ||
+ DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED)))
+ {
+ $this->last_query = $save_query;
+ $this->last_stmt = $save_stmt;
+ 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);
+ 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->_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);
+ }
+ if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) {
+ $tmp = $this->oci8RaiseError($stmt);
+ return $tmp;
+ }
+ $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]) {
+ $tmp = DB_OK;
+ } else {
+ @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';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/odbc.php b/program/lib/DB/odbc.php
new file mode 100644
index 000000000..c920d7a63
--- /dev/null
+++ b/program/lib/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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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 (DB::isManip($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 @odbc_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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/program/lib/DB/pgsql.php b/program/lib/DB/pgsql.php
new file mode 100644
index 000000000..ae229afa1
--- /dev/null
+++ b/program/lib/DB/pgsql.php
@@ -0,0 +1,1097 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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 = DB::isManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @pg_exec($this->connection, 'begin;');
+ if (!$result) {
+ return $this->pgsqlRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ $result = @pg_exec($this->connection, $query);
+ if (!$result) {
+ return $this->pgsqlRaiseError();
+ }
+ // Determine which queries that should return data, and which
+ // should return an error code only.
+ if ($ismanip) {
+ $this->affected = @pg_affected_rows($result);
+ return DB_OK;
+ } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|SHOW)\s/si', $query)) {
+ /* PostgreSQL commands:
+ ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
+ CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
+ GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
+ REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
+ UNLISTEN, UPDATE, VACUUM
+ */
+ $this->row[(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);
+ }
+
+ // }}}
+ // {{{ quoteSmart()
+
+ /**
+ * Formats input so it can be safely used in a query
+ *
+ * @param mixed $in the data to be formatted
+ *
+ * @return mixed the formatted data. The format depends on the input's
+ * PHP type:
+ * + null = the string <samp>NULL</samp>
+ * + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
+ * + integer or double = the unquoted number
+ * + other (including strings and numeric strings) =
+ * the data escaped according to MySQL's settings
+ * then encapsulated between single quotes
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteSmart($in)
+ {
+ if (is_int($in) || is_double($in)) {
+ return $in;
+ } elseif (is_bool($in)) {
+ return $in ? 'TRUE' : 'FALSE';
+ } elseif (is_null($in)) {
+ return 'NULL';
+ } else {
+ return "'" . $this->escapeSimple($in) . "'";
+ }
+ }
+
+ // }}}
+ // {{{ 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.
+ *
+ * Not using pg_escape_string() yet because it requires PostgreSQL
+ * to be at version 7.2 or greater.}}
+ *
+ * @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_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 ($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(
+ '/(relation|sequence|table).*does not exist|class .* not found/i'
+ => DB_ERROR_NOSUCHTABLE,
+ '/index .* does not exist/'
+ => DB_ERROR_NOT_FOUND,
+ '/column .* does not exist/i'
+ => DB_ERROR_NOSUCHFIELD,
+ '/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);
+
+ $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
+ FROM pg_attribute f, pg_class tab, pg_type typ
+ WHERE tab.relname = typ.typname
+ AND typ.typrelid = f.attrelid
+ AND f.attname = '$field_name'
+ AND tab.relname = '$table_name'");
+ if (@pg_numrows($result) > 0) {
+ $row = @pg_fetch_row($result, 0);
+ $flags = ($row[0] == 't') ? 'not_null ' : '';
+
+ if ($row[1] == 't') {
+ $result = @pg_exec($this->connection, "SELECT a.adsrc
+ FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a
+ WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
+ AND f.attrelid = a.adrelid AND f.attname = '$field_name'
+ AND tab.relname = '$table_name' AND f.attnum = a.adnum");
+ $row = @pg_fetch_row($result, 0);
+ $num = 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 pg_attribute f, pg_class tab, pg_type typ, pg_index i
+ WHERE tab.relname = typ.typname
+ AND typ.typrelid = f.attrelid
+ AND f.attrelid = i.indrelid
+ AND f.attname = '$field_name'
+ AND tab.relname = '$table_name'");
+ $count = @pg_numrows($result);
+
+ for ($i = 0; $i < $count ; $i++) {
+ $row = @pg_fetch_row($result, $i);
+ $keys = explode(' ', $row[2]);
+
+ if (in_array($num_field + 1, $keys)) {
+ $flags .= ($row[0] == 't' && $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 'views':
+ // Table cols: viewname | viewowner | definition
+ return 'SELECT viewname from pg_views WHERE schemaname'
+ . " NOT IN ('information_schema', 'pg_catalog')";
+ case 'users':
+ // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil
+ return 'SELECT usename FROM pg_user';
+ case 'databases':
+ return 'SELECT datname FROM pg_database';
+ case 'functions':
+ case 'procedures':
+ return 'SELECT proname FROM pg_proc WHERE proowner <> 1';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/program/lib/DB/sqlite.php b/program/lib/DB/sqlite.php
new file mode 100644
index 000000000..42f599e5d
--- /dev/null
+++ b/program/lib/DB/sqlite.php
@@ -0,0 +1,942 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
+ * @version Release: @package_version@
+ * @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']) {
+ 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);
+ }
+ } else {
+ 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 = DB::isManip($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);
+ }
+ } 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;
+ 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,
+ '/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/program/lib/DB/storage.php b/program/lib/DB/storage.php
new file mode 100644
index 000000000..17bffd155
--- /dev/null
+++ b/program/lib/DB/storage.php
@@ -0,0 +1,504 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB class so it can be extended from
+ */
+require_once 'DB.php';
+
+/**
+ * Provides an object interface to a table row
+ *
+ * It lets you add, delete and change rows using objects rather than SQL
+ * statements.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <stig@php.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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()
+ {
+ 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/program/lib/DB/sybase.php b/program/lib/DB/sybase.php
new file mode 100644
index 000000000..893178213
--- /dev/null
+++ b/program/lib/DB/sybase.php
@@ -0,0 +1,907 @@
+<?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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id$
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * 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-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: @package_version@
+ * @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 = DB::isManip($query);
+ $this->last_query = $query;
+ if (!@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 @sybase_free_result($result);
+ }
+
+ // }}}
+ // {{{ 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 (DB::isManip($this->last_query)) {
+ $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 (!@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));
+ }
+
+ // }}}
+ // {{{ 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 (!@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 (!@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;
+ 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,
+ );
+ }
+
+ 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 (!@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;
+
+ // get unique/primary keys
+ $res = $this->getAll("sp_helpindex $table", DB_FETCHMODE_ASSOC);
+
+ if (!isset($res[0]['index_description'])) {
+ return '';
+ }
+
+ foreach ($res as $val) {
+ $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');
+ }
+ }
+ }
+
+ }
+
+ 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:
+ */
+
+?>