Commit affd3e45e87cfcc7beaf030688792a03086453ae

Authored by nbm
1 parent e7bbeef1

PEAR DB from DB-1.6.8


git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/trunk@3009 c91229c3-7414-0410-bfa2-8a42b809f60b
pear/DB.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Authors: Stig Bakken <ssb@php.net> |
  17 +// | Tomas V.V.Cox <cox@idecnet.com> |
  18 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  19 +// +----------------------------------------------------------------------+
  20 +//
  21 +// $Id$
  22 +//
  23 +// Database independent query interface.
  24 +
  25 +
  26 +require_once 'PEAR.php';
  27 +
  28 +// {{{ constants
  29 +// {{{ error codes
  30 +
  31 +/*
  32 + * The method mapErrorCode in each DB_dbtype implementation maps
  33 + * native error codes to one of these.
  34 + *
  35 + * If you add an error code here, make sure you also add a textual
  36 + * version of it in DB::errorMessage().
  37 + */
  38 +define('DB_OK', 1);
  39 +define('DB_ERROR', -1);
  40 +define('DB_ERROR_SYNTAX', -2);
  41 +define('DB_ERROR_CONSTRAINT', -3);
  42 +define('DB_ERROR_NOT_FOUND', -4);
  43 +define('DB_ERROR_ALREADY_EXISTS', -5);
  44 +define('DB_ERROR_UNSUPPORTED', -6);
  45 +define('DB_ERROR_MISMATCH', -7);
  46 +define('DB_ERROR_INVALID', -8);
  47 +define('DB_ERROR_NOT_CAPABLE', -9);
  48 +define('DB_ERROR_TRUNCATED', -10);
  49 +define('DB_ERROR_INVALID_NUMBER', -11);
  50 +define('DB_ERROR_INVALID_DATE', -12);
  51 +define('DB_ERROR_DIVZERO', -13);
  52 +define('DB_ERROR_NODBSELECTED', -14);
  53 +define('DB_ERROR_CANNOT_CREATE', -15);
  54 +define('DB_ERROR_CANNOT_DELETE', -16);
  55 +define('DB_ERROR_CANNOT_DROP', -17);
  56 +define('DB_ERROR_NOSUCHTABLE', -18);
  57 +define('DB_ERROR_NOSUCHFIELD', -19);
  58 +define('DB_ERROR_NEED_MORE_DATA', -20);
  59 +define('DB_ERROR_NOT_LOCKED', -21);
  60 +define('DB_ERROR_VALUE_COUNT_ON_ROW', -22);
  61 +define('DB_ERROR_INVALID_DSN', -23);
  62 +define('DB_ERROR_CONNECT_FAILED', -24);
  63 +define('DB_ERROR_EXTENSION_NOT_FOUND',-25);
  64 +define('DB_ERROR_ACCESS_VIOLATION', -26);
  65 +define('DB_ERROR_NOSUCHDB', -27);
  66 +define('DB_ERROR_CONSTRAINT_NOT_NULL',-29);
  67 +
  68 +
  69 +// }}}
  70 +// {{{ prepared statement-related
  71 +
  72 +
  73 +/*
  74 + * These constants are used when storing information about prepared
  75 + * statements (using the "prepare" method in DB_dbtype).
  76 + *
  77 + * The prepare/execute model in DB is mostly borrowed from the ODBC
  78 + * extension, in a query the "?" character means a scalar parameter.
  79 + * There are two extensions though, a "&" character means an opaque
  80 + * parameter. An opaque parameter is simply a file name, the real
  81 + * data are in that file (useful for putting uploaded files into your
  82 + * database and such). The "!" char means a parameter that must be
  83 + * left as it is.
  84 + * They modify the quote behavoir:
  85 + * DB_PARAM_SCALAR (?) => 'original string quoted'
  86 + * DB_PARAM_OPAQUE (&) => 'string from file quoted'
  87 + * DB_PARAM_MISC (!) => original string
  88 + */
  89 +define('DB_PARAM_SCALAR', 1);
  90 +define('DB_PARAM_OPAQUE', 2);
  91 +define('DB_PARAM_MISC', 3);
  92 +
  93 +
  94 +// }}}
  95 +// {{{ binary data-related
  96 +
  97 +
  98 +/*
  99 + * These constants define different ways of returning binary data
  100 + * from queries. Again, this model has been borrowed from the ODBC
  101 + * extension.
  102 + *
  103 + * DB_BINMODE_PASSTHRU sends the data directly through to the browser
  104 + * when data is fetched from the database.
  105 + * DB_BINMODE_RETURN lets you return data as usual.
  106 + * DB_BINMODE_CONVERT returns data as well, only it is converted to
  107 + * hex format, for example the string "123" would become "313233".
  108 + */
  109 +define('DB_BINMODE_PASSTHRU', 1);
  110 +define('DB_BINMODE_RETURN', 2);
  111 +define('DB_BINMODE_CONVERT', 3);
  112 +
  113 +
  114 +// }}}
  115 +// {{{ fetch modes
  116 +
  117 +
  118 +/**
  119 + * This is a special constant that tells DB the user hasn't specified
  120 + * any particular get mode, so the default should be used.
  121 + */
  122 +define('DB_FETCHMODE_DEFAULT', 0);
  123 +
  124 +/**
  125 + * Column data indexed by numbers, ordered from 0 and up
  126 + */
  127 +define('DB_FETCHMODE_ORDERED', 1);
  128 +
  129 +/**
  130 + * Column data indexed by column names
  131 + */
  132 +define('DB_FETCHMODE_ASSOC', 2);
  133 +
  134 +/**
  135 + * Column data as object properties
  136 + */
  137 +define('DB_FETCHMODE_OBJECT', 3);
  138 +
  139 +/**
  140 + * For multi-dimensional results: normally the first level of arrays
  141 + * is the row number, and the second level indexed by column number or name.
  142 + * DB_FETCHMODE_FLIPPED switches this order, so the first level of arrays
  143 + * is the column name, and the second level the row number.
  144 + */
  145 +define('DB_FETCHMODE_FLIPPED', 4);
  146 +
  147 +/* for compatibility */
  148 +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
  149 +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC);
  150 +define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED);
  151 +
  152 +
  153 +// }}}
  154 +// {{{ tableInfo() && autoPrepare()-related
  155 +
  156 +
  157 +/**
  158 + * these are constants for the tableInfo-function
  159 + * they are bitwised or'ed. so if there are more constants to be defined
  160 + * in the future, adjust DB_TABLEINFO_FULL accordingly
  161 + */
  162 +define('DB_TABLEINFO_ORDER', 1);
  163 +define('DB_TABLEINFO_ORDERTABLE', 2);
  164 +define('DB_TABLEINFO_FULL', 3);
  165 +
  166 +/*
  167 + * Used by autoPrepare()
  168 + */
  169 +define('DB_AUTOQUERY_INSERT', 1);
  170 +define('DB_AUTOQUERY_UPDATE', 2);
  171 +
  172 +
  173 +// }}}
  174 +// {{{ portability modes
  175 +
  176 +
  177 +/**
  178 + * Portability: turn off all portability features.
  179 + * @see DB_common::setOption()
  180 + */
  181 +define('DB_PORTABILITY_NONE', 0);
  182 +
  183 +/**
  184 + * Portability: convert names of tables and fields to lower case
  185 + * when using the get*(), fetch*() and tableInfo() methods.
  186 + * @see DB_common::setOption()
  187 + */
  188 +define('DB_PORTABILITY_LOWERCASE', 1);
  189 +
  190 +/**
  191 + * Portability: right trim the data output by get*() and fetch*().
  192 + * @see DB_common::setOption()
  193 + */
  194 +define('DB_PORTABILITY_RTRIM', 2);
  195 +
  196 +/**
  197 + * Portability: force reporting the number of rows deleted.
  198 + * @see DB_common::setOption()
  199 + */
  200 +define('DB_PORTABILITY_DELETE_COUNT', 4);
  201 +
  202 +/**
  203 + * Portability: enable hack that makes numRows() work in Oracle.
  204 + * @see DB_common::setOption()
  205 + */
  206 +define('DB_PORTABILITY_NUMROWS', 8);
  207 +
  208 +/**
  209 + * Portability: makes certain error messages in certain drivers compatible
  210 + * with those from other DBMS's.
  211 + *
  212 + * + mysql, mysqli: change unique/primary key constraints
  213 + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
  214 + *
  215 + * + odbc(access): MS's ODBC driver reports 'no such field' as code
  216 + * 07001, which means 'too few parameters.' When this option is on
  217 + * that code gets mapped to DB_ERROR_NOSUCHFIELD.
  218 + *
  219 + * @see DB_common::setOption()
  220 + */
  221 +define('DB_PORTABILITY_ERRORS', 16);
  222 +
  223 +/**
  224 + * Portability: convert null values to empty strings in data output by
  225 + * get*() and fetch*().
  226 + * @see DB_common::setOption()
  227 + */
  228 +define('DB_PORTABILITY_NULL_TO_EMPTY', 32);
  229 +
  230 +/**
  231 + * Portability: turn on all portability features.
  232 + * @see DB_common::setOption()
  233 + */
  234 +define('DB_PORTABILITY_ALL', 63);
  235 +
  236 +// }}}
  237 +
  238 +
  239 +// }}}
  240 +// {{{ class DB
  241 +
  242 +/**
  243 + * The main "DB" class is simply a container class with some static
  244 + * methods for creating DB objects as well as some utility functions
  245 + * common to all parts of DB.
  246 + *
  247 + * The object model of DB is as follows (indentation means inheritance):
  248 + *
  249 + * DB The main DB class. This is simply a utility class
  250 + * with some "static" methods for creating DB objects as
  251 + * well as common utility functions for other DB classes.
  252 + *
  253 + * DB_common The base for each DB implementation. Provides default
  254 + * | implementations (in OO lingo virtual methods) for
  255 + * | the actual DB implementations as well as a bunch of
  256 + * | query utility functions.
  257 + * |
  258 + * +-DB_mysql The DB implementation for MySQL. Inherits DB_common.
  259 + * When calling DB::factory or DB::connect for MySQL
  260 + * connections, the object returned is an instance of this
  261 + * class.
  262 + *
  263 + * @package DB
  264 + * @author Stig Bakken <ssb@php.net>
  265 + * @author Tomas V.V.Cox <cox@idecnet.com>
  266 + * @since PHP 4.0
  267 + * @version $Id$
  268 + * @category Database
  269 + */
  270 +class DB
  271 +{
  272 + // {{{ &factory()
  273 +
  274 + /**
  275 + * Create a new DB object for the specified database type.
  276 + *
  277 + * Allows creation of a DB_<driver> object from which the object's
  278 + * methods can be utilized without actually connecting to a database.
  279 + *
  280 + * @param string $type database type, for example "mysql"
  281 + * @param array $options associative array of option names and values
  282 + *
  283 + * @return object a new DB object. On error, an error object.
  284 + *
  285 + * @see DB_common::setOption()
  286 + * @access public
  287 + */
  288 + function &factory($type, $options = false)
  289 + {
  290 + if (!is_array($options)) {
  291 + $options = array('persistent' => $options);
  292 + }
  293 +
  294 + if (isset($options['debug']) && $options['debug'] >= 2) {
  295 + // expose php errors with sufficient debug level
  296 + include_once "DB/{$type}.php";
  297 + } else {
  298 + @include_once "DB/{$type}.php";
  299 + }
  300 +
  301 + $classname = "DB_${type}";
  302 +
  303 + if (!class_exists($classname)) {
  304 + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
  305 + "Unable to include the DB/{$type}.php file",
  306 + 'DB_Error', true);
  307 + return $tmp;
  308 + }
  309 +
  310 + @$obj =& new $classname;
  311 +
  312 + foreach ($options as $option => $value) {
  313 + $test = $obj->setOption($option, $value);
  314 + if (DB::isError($test)) {
  315 + return $test;
  316 + }
  317 + }
  318 +
  319 + return $obj;
  320 + }
  321 +
  322 + // }}}
  323 + // {{{ &connect()
  324 +
  325 + /**
  326 + * Create a new DB object and connect to the specified database.
  327 + *
  328 + * Example 1.
  329 + * <code> <?php
  330 + * require_once 'DB.php';
  331 + *
  332 + * $dsn = 'mysql://user:password@host/database'
  333 + * $options = array(
  334 + * 'debug' => 2,
  335 + * 'portability' => DB_PORTABILITY_ALL,
  336 + * );
  337 + *
  338 + * $dbh =& DB::connect($dsn, $options);
  339 + * if (DB::isError($dbh)) {
  340 + * die($dbh->getMessage());
  341 + * }
  342 + * ?></code>
  343 + *
  344 + * @param mixed $dsn string "data source name" or an array in the
  345 + * format returned by DB::parseDSN()
  346 + *
  347 + * @param array $options an associative array of option names and
  348 + * their values
  349 + *
  350 + * @return object a newly created DB connection object, or a DB
  351 + * error object on error
  352 + *
  353 + * @see DB::parseDSN(), DB_common::setOption(), DB::isError()
  354 + * @access public
  355 + */
  356 + function &connect($dsn, $options = array())
  357 + {
  358 + $dsninfo = DB::parseDSN($dsn);
  359 + $type = $dsninfo['phptype'];
  360 +
  361 + if (!is_array($options)) {
  362 + /*
  363 + * For backwards compatibility. $options used to be boolean,
  364 + * indicating whether the connection should be persistent.
  365 + */
  366 + $options = array('persistent' => $options);
  367 + }
  368 +
  369 + if (isset($options['debug']) && $options['debug'] >= 2) {
  370 + // expose php errors with sufficient debug level
  371 + include_once "DB/${type}.php";
  372 + } else {
  373 + @include_once "DB/${type}.php";
  374 + }
  375 +
  376 + $classname = "DB_${type}";
  377 + if (!class_exists($classname)) {
  378 + $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
  379 + "Unable to include the DB/{$type}.php file for `$dsn'",
  380 + 'DB_Error', true);
  381 + return $tmp;
  382 + }
  383 +
  384 + @$obj =& new $classname;
  385 +
  386 + foreach ($options as $option => $value) {
  387 + $test = $obj->setOption($option, $value);
  388 + if (DB::isError($test)) {
  389 + return $test;
  390 + }
  391 + }
  392 +
  393 + $err = $obj->connect($dsninfo, $obj->getOption('persistent'));
  394 + if (DB::isError($err)) {
  395 + $err->addUserInfo($dsn);
  396 + return $err;
  397 + }
  398 +
  399 + return $obj;
  400 + }
  401 +
  402 + // }}}
  403 + // {{{ apiVersion()
  404 +
  405 + /**
  406 + * Return the DB API version
  407 + *
  408 + * @return int the DB API version number
  409 + *
  410 + * @access public
  411 + */
  412 + function apiVersion()
  413 + {
  414 + return 2;
  415 + }
  416 +
  417 + // }}}
  418 + // {{{ isError()
  419 +
  420 + /**
  421 + * Tell whether a result code from a DB method is an error
  422 + *
  423 + * @param int $value result code
  424 + *
  425 + * @return bool whether $value is an error
  426 + *
  427 + * @access public
  428 + */
  429 + function isError($value)
  430 + {
  431 + return is_a($value, 'DB_Error');
  432 + }
  433 +
  434 + // }}}
  435 + // {{{ isConnection()
  436 +
  437 + /**
  438 + * Tell whether a value is a DB connection
  439 + *
  440 + * @param mixed $value value to test
  441 + *
  442 + * @return bool whether $value is a DB connection
  443 + *
  444 + * @access public
  445 + */
  446 + function isConnection($value)
  447 + {
  448 + return (is_object($value) &&
  449 + is_subclass_of($value, 'db_common') &&
  450 + method_exists($value, 'simpleQuery'));
  451 + }
  452 +
  453 + // }}}
  454 + // {{{ isManip()
  455 +
  456 + /**
  457 + * Tell whether a query is a data manipulation query (insert,
  458 + * update or delete) or a data definition query (create, drop,
  459 + * alter, grant, revoke).
  460 + *
  461 + * @access public
  462 + *
  463 + * @param string $query the query
  464 + *
  465 + * @return boolean whether $query is a data manipulation query
  466 + */
  467 + function isManip($query)
  468 + {
  469 + $manips = 'INSERT|UPDATE|DELETE|LOAD DATA|'.'REPLACE|CREATE|DROP|'.
  470 + 'ALTER|GRANT|REVOKE|'.'LOCK|UNLOCK';
  471 + if (preg_match('/^\s*"?('.$manips.')\s+/i', $query)) {
  472 + return true;
  473 + }
  474 + return false;
  475 + }
  476 +
  477 + // }}}
  478 + // {{{ errorMessage()
  479 +
  480 + /**
  481 + * Return a textual error message for a DB error code
  482 + *
  483 + * @param integer $value error code
  484 + *
  485 + * @return string error message, or false if the error code was
  486 + * not recognized
  487 + */
  488 + function errorMessage($value)
  489 + {
  490 + static $errorMessages;
  491 + if (!isset($errorMessages)) {
  492 + $errorMessages = array(
  493 + DB_ERROR => 'unknown error',
  494 + DB_ERROR_ALREADY_EXISTS => 'already exists',
  495 + DB_ERROR_CANNOT_CREATE => 'can not create',
  496 + DB_ERROR_CANNOT_DELETE => 'can not delete',
  497 + DB_ERROR_CANNOT_DROP => 'can not drop',
  498 + DB_ERROR_CONSTRAINT => 'constraint violation',
  499 + DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
  500 + DB_ERROR_DIVZERO => 'division by zero',
  501 + DB_ERROR_INVALID => 'invalid',
  502 + DB_ERROR_INVALID_DATE => 'invalid date or time',
  503 + DB_ERROR_INVALID_NUMBER => 'invalid number',
  504 + DB_ERROR_MISMATCH => 'mismatch',
  505 + DB_ERROR_NODBSELECTED => 'no database selected',
  506 + DB_ERROR_NOSUCHFIELD => 'no such field',
  507 + DB_ERROR_NOSUCHTABLE => 'no such table',
  508 + DB_ERROR_NOT_CAPABLE => 'DB backend not capable',
  509 + DB_ERROR_NOT_FOUND => 'not found',
  510 + DB_ERROR_NOT_LOCKED => 'not locked',
  511 + DB_ERROR_SYNTAX => 'syntax error',
  512 + DB_ERROR_UNSUPPORTED => 'not supported',
  513 + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
  514 + DB_ERROR_INVALID_DSN => 'invalid DSN',
  515 + DB_ERROR_CONNECT_FAILED => 'connect failed',
  516 + DB_OK => 'no error',
  517 + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied',
  518 + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
  519 + DB_ERROR_NOSUCHDB => 'no such database',
  520 + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions',
  521 + DB_ERROR_TRUNCATED => 'truncated'
  522 + );
  523 + }
  524 +
  525 + if (DB::isError($value)) {
  526 + $value = $value->getCode();
  527 + }
  528 +
  529 + return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[DB_ERROR];
  530 + }
  531 +
  532 + // }}}
  533 + // {{{ parseDSN()
  534 +
  535 + /**
  536 + * Parse a data source name.
  537 + *
  538 + * Additional keys can be added by appending a URI query string to the
  539 + * end of the DSN.
  540 + *
  541 + * The format of the supplied DSN is in its fullest form:
  542 + * <code>
  543 + * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
  544 + * </code>
  545 + *
  546 + * Most variations are allowed:
  547 + * <code>
  548 + * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
  549 + * phptype://username:password@hostspec/database_name
  550 + * phptype://username:password@hostspec
  551 + * phptype://username@hostspec
  552 + * phptype://hostspec/database
  553 + * phptype://hostspec
  554 + * phptype(dbsyntax)
  555 + * phptype
  556 + * </code>
  557 + *
  558 + * @param string $dsn Data Source Name to be parsed
  559 + *
  560 + * @return array an associative array with the following keys:
  561 + * + phptype: Database backend used in PHP (mysql, odbc etc.)
  562 + * + dbsyntax: Database used with regards to SQL syntax etc.
  563 + * + protocol: Communication protocol to use (tcp, unix etc.)
  564 + * + hostspec: Host specification (hostname[:port])
  565 + * + database: Database to use on the DBMS server
  566 + * + username: User name for login
  567 + * + password: Password for login
  568 + *
  569 + * @author Tomas V.V.Cox <cox@idecnet.com>
  570 + */
  571 + function parseDSN($dsn)
  572 + {
  573 + $parsed = array(
  574 + 'phptype' => false,
  575 + 'dbsyntax' => false,
  576 + 'username' => false,
  577 + 'password' => false,
  578 + 'protocol' => false,
  579 + 'hostspec' => false,
  580 + 'port' => false,
  581 + 'socket' => false,
  582 + 'database' => false,
  583 + );
  584 +
  585 + if (is_array($dsn)) {
  586 + $dsn = array_merge($parsed, $dsn);
  587 + if (!$dsn['dbsyntax']) {
  588 + $dsn['dbsyntax'] = $dsn['phptype'];
  589 + }
  590 + return $dsn;
  591 + }
  592 +
  593 + // Find phptype and dbsyntax
  594 + if (($pos = strpos($dsn, '://')) !== false) {
  595 + $str = substr($dsn, 0, $pos);
  596 + $dsn = substr($dsn, $pos + 3);
  597 + } else {
  598 + $str = $dsn;
  599 + $dsn = null;
  600 + }
  601 +
  602 + // Get phptype and dbsyntax
  603 + // $str => phptype(dbsyntax)
  604 + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
  605 + $parsed['phptype'] = $arr[1];
  606 + $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
  607 + } else {
  608 + $parsed['phptype'] = $str;
  609 + $parsed['dbsyntax'] = $str;
  610 + }
  611 +
  612 + if (!count($dsn)) {
  613 + return $parsed;
  614 + }
  615 +
  616 + // Get (if found): username and password
  617 + // $dsn => username:password@protocol+hostspec/database
  618 + if (($at = strrpos($dsn,'@')) !== false) {
  619 + $str = substr($dsn, 0, $at);
  620 + $dsn = substr($dsn, $at + 1);
  621 + if (($pos = strpos($str, ':')) !== false) {
  622 + $parsed['username'] = rawurldecode(substr($str, 0, $pos));
  623 + $parsed['password'] = rawurldecode(substr($str, $pos + 1));
  624 + } else {
  625 + $parsed['username'] = rawurldecode($str);
  626 + }
  627 + }
  628 +
  629 + // Find protocol and hostspec
  630 +
  631 + // $dsn => proto(proto_opts)/database
  632 + if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
  633 + $proto = $match[1];
  634 + $proto_opts = $match[2] ? $match[2] : false;
  635 + $dsn = $match[3];
  636 +
  637 + // $dsn => protocol+hostspec/database (old format)
  638 + } else {
  639 + if (strpos($dsn, '+') !== false) {
  640 + list($proto, $dsn) = explode('+', $dsn, 2);
  641 + }
  642 + if (strpos($dsn, '/') !== false) {
  643 + list($proto_opts, $dsn) = explode('/', $dsn, 2);
  644 + } else {
  645 + $proto_opts = $dsn;
  646 + $dsn = null;
  647 + }
  648 + }
  649 +
  650 + // process the different protocol options
  651 + $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
  652 + $proto_opts = rawurldecode($proto_opts);
  653 + if ($parsed['protocol'] == 'tcp') {
  654 + if (strpos($proto_opts, ':') !== false) {
  655 + list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts);
  656 + } else {
  657 + $parsed['hostspec'] = $proto_opts;
  658 + }
  659 + } elseif ($parsed['protocol'] == 'unix') {
  660 + $parsed['socket'] = $proto_opts;
  661 + }
  662 +
  663 + // Get dabase if any
  664 + // $dsn => database
  665 + if ($dsn) {
  666 + // /database
  667 + if (($pos = strpos($dsn, '?')) === false) {
  668 + $parsed['database'] = rawurldecode($dsn);
  669 + // /database?param1=value1&param2=value2
  670 + } else {
  671 + $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
  672 + $dsn = substr($dsn, $pos + 1);
  673 + if (strpos($dsn, '&') !== false) {
  674 + $opts = explode('&', $dsn);
  675 + } else { // database?param1=value1
  676 + $opts = array($dsn);
  677 + }
  678 + foreach ($opts as $opt) {
  679 + list($key, $value) = explode('=', $opt);
  680 + if (!isset($parsed[$key])) {
  681 + // don't allow params overwrite
  682 + $parsed[$key] = rawurldecode($value);
  683 + }
  684 + }
  685 + }
  686 + }
  687 +
  688 + return $parsed;
  689 + }
  690 +
  691 + // }}}
  692 + // {{{ assertExtension()
  693 +
  694 + /**
  695 + * Load a PHP database extension if it is not loaded already.
  696 + *
  697 + * @access public
  698 + *
  699 + * @param string $name the base name of the extension (without the .so or
  700 + * .dll suffix)
  701 + *
  702 + * @return boolean true if the extension was already or successfully
  703 + * loaded, false if it could not be loaded
  704 + */
  705 + function assertExtension($name)
  706 + {
  707 + if (!extension_loaded($name)) {
  708 + $dlext = OS_WINDOWS ? '.dll' : '.so';
  709 + $dlprefix = OS_WINDOWS ? 'php_' : '';
  710 + @dl($dlprefix . $name . $dlext);
  711 + return extension_loaded($name);
  712 + }
  713 + return true;
  714 + }
  715 + // }}}
  716 +}
  717 +
  718 +// }}}
  719 +// {{{ class DB_Error
  720 +
  721 +/**
  722 + * DB_Error implements a class for reporting portable database error
  723 + * messages.
  724 + *
  725 + * @package DB
  726 + * @author Stig Bakken <ssb@php.net>
  727 + */
  728 +class DB_Error extends PEAR_Error
  729 +{
  730 + // {{{ constructor
  731 +
  732 + /**
  733 + * DB_Error constructor.
  734 + *
  735 + * @param mixed $code DB error code, or string with error message.
  736 + * @param integer $mode what "error mode" to operate in
  737 + * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER
  738 + * @param mixed $debuginfo additional debug info, such as the last query
  739 + *
  740 + * @access public
  741 + *
  742 + * @see PEAR_Error
  743 + */
  744 + function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
  745 + $level = E_USER_NOTICE, $debuginfo = null)
  746 + {
  747 + if (is_int($code)) {
  748 + $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code, $mode, $level, $debuginfo);
  749 + } else {
  750 + $this->PEAR_Error("DB Error: $code", DB_ERROR, $mode, $level, $debuginfo);
  751 + }
  752 + }
  753 + // }}}
  754 +}
  755 +
  756 +// }}}
  757 +// {{{ class DB_result
  758 +
  759 +/**
  760 + * This class implements a wrapper for a DB result set.
  761 + * A new instance of this class will be returned by the DB implementation
  762 + * after processing a query that returns data.
  763 + *
  764 + * @package DB
  765 + * @author Stig Bakken <ssb@php.net>
  766 + */
  767 +class DB_result
  768 +{
  769 + // {{{ properties
  770 +
  771 + var $dbh;
  772 + var $result;
  773 + var $row_counter = null;
  774 +
  775 + /**
  776 + * for limit queries, the row to start fetching
  777 + * @var integer
  778 + */
  779 + var $limit_from = null;
  780 +
  781 + /**
  782 + * for limit queries, the number of rows to fetch
  783 + * @var integer
  784 + */
  785 + var $limit_count = null;
  786 +
  787 + // }}}
  788 + // {{{ constructor
  789 +
  790 + /**
  791 + * DB_result constructor.
  792 + * @param resource &$dbh DB object reference
  793 + * @param resource $result result resource id
  794 + * @param array $options assoc array with optional result options
  795 + */
  796 + function DB_result(&$dbh, $result, $options = array())
  797 + {
  798 + $this->dbh = &$dbh;
  799 + $this->result = $result;
  800 + foreach ($options as $key => $value) {
  801 + $this->setOption($key, $value);
  802 + }
  803 + $this->limit_type = $dbh->features['limit'];
  804 + $this->autofree = $dbh->options['autofree'];
  805 + $this->fetchmode = $dbh->fetchmode;
  806 + $this->fetchmode_object_class = $dbh->fetchmode_object_class;
  807 + }
  808 +
  809 + function setOption($key, $value = null)
  810 + {
  811 + switch ($key) {
  812 + case 'limit_from':
  813 + $this->limit_from = $value; break;
  814 + case 'limit_count':
  815 + $this->limit_count = $value; break;
  816 + }
  817 + }
  818 +
  819 + // }}}
  820 + // {{{ fetchRow()
  821 +
  822 + /**
  823 + * Fetch a row of data and return it by reference into an array.
  824 + *
  825 + * The type of array returned can be controlled either by setting this
  826 + * method's <var>$fetchmode</var> parameter or by changing the default
  827 + * fetch mode setFetchMode() before calling this method.
  828 + *
  829 + * There are two options for standardizing the information returned
  830 + * from databases, ensuring their values are consistent when changing
  831 + * DBMS's. These portability options can be turned on when creating a
  832 + * new DB object or by using setOption().
  833 + *
  834 + * + <samp>DB_PORTABILITY_LOWERCASE</samp>
  835 + * convert names of fields to lower case
  836 + *
  837 + * + <samp>DB_PORTABILITY_RTRIM</samp>
  838 + * right trim the data
  839 + *
  840 + * @param int $fetchmode how the resulting array should be indexed
  841 + * @param int $rownum the row number to fetch
  842 + *
  843 + * @return array a row of data, null on no more rows or PEAR_Error
  844 + * object on error
  845 + *
  846 + * @see DB_common::setOption(), DB_common::setFetchMode()
  847 + * @access public
  848 + */
  849 + function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
  850 + {
  851 + if ($fetchmode === DB_FETCHMODE_DEFAULT) {
  852 + $fetchmode = $this->fetchmode;
  853 + }
  854 + if ($fetchmode === DB_FETCHMODE_OBJECT) {
  855 + $fetchmode = DB_FETCHMODE_ASSOC;
  856 + $object_class = $this->fetchmode_object_class;
  857 + }
  858 + if ($this->limit_from !== null) {
  859 + if ($this->row_counter === null) {
  860 + $this->row_counter = $this->limit_from;
  861 + // Skip rows
  862 + if ($this->limit_type == false) {
  863 + $i = 0;
  864 + while ($i++ < $this->limit_from) {
  865 + $this->dbh->fetchInto($this->result, $arr, $fetchmode);
  866 + }
  867 + }
  868 + }
  869 + if ($this->row_counter >= (
  870 + $this->limit_from + $this->limit_count))
  871 + {
  872 + if ($this->autofree) {
  873 + $this->free();
  874 + }
  875 + $tmp = null;
  876 + return $tmp;
  877 + }
  878 + if ($this->limit_type == 'emulate') {
  879 + $rownum = $this->row_counter;
  880 + }
  881 + $this->row_counter++;
  882 + }
  883 + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
  884 + if ($res === DB_OK) {
  885 + if (isset($object_class)) {
  886 + // default mode specified in DB_common::fetchmode_object_class property
  887 + if ($object_class == 'stdClass') {
  888 + $arr = (object) $arr;
  889 + } else {
  890 + $arr = &new $object_class($arr);
  891 + }
  892 + }
  893 + return $arr;
  894 + }
  895 + if ($res == null && $this->autofree) {
  896 + $this->free();
  897 + }
  898 + return $res;
  899 + }
  900 +
  901 + // }}}
  902 + // {{{ fetchInto()
  903 +
  904 + /**
  905 + * Fetch a row of data into an array which is passed by reference.
  906 + *
  907 + * The type of array returned can be controlled either by setting this
  908 + * method's <var>$fetchmode</var> parameter or by changing the default
  909 + * fetch mode setFetchMode() before calling this method.
  910 + *
  911 + * There are two options for standardizing the information returned
  912 + * from databases, ensuring their values are consistent when changing
  913 + * DBMS's. These portability options can be turned on when creating a
  914 + * new DB object or by using setOption().
  915 + *
  916 + * + <samp>DB_PORTABILITY_LOWERCASE</samp>
  917 + * convert names of fields to lower case
  918 + *
  919 + * + <samp>DB_PORTABILITY_RTRIM</samp>
  920 + * right trim the data
  921 + *
  922 + * @param array &$arr (reference) array where data from the row
  923 + * should be placed
  924 + * @param int $fetchmode how the resulting array should be indexed
  925 + * @param int $rownum the row number to fetch
  926 + *
  927 + * @return mixed DB_OK on success, null on no more rows or
  928 + * a DB_Error object on error
  929 + *
  930 + * @see DB_common::setOption(), DB_common::setFetchMode()
  931 + * @access public
  932 + */
  933 + function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum=null)
  934 + {
  935 + if ($fetchmode === DB_FETCHMODE_DEFAULT) {
  936 + $fetchmode = $this->fetchmode;
  937 + }
  938 + if ($fetchmode === DB_FETCHMODE_OBJECT) {
  939 + $fetchmode = DB_FETCHMODE_ASSOC;
  940 + $object_class = $this->fetchmode_object_class;
  941 + }
  942 + if ($this->limit_from !== null) {
  943 + if ($this->row_counter === null) {
  944 + $this->row_counter = $this->limit_from;
  945 + // Skip rows
  946 + if ($this->limit_type == false) {
  947 + $i = 0;
  948 + while ($i++ < $this->limit_from) {
  949 + $this->dbh->fetchInto($this->result, $arr, $fetchmode);
  950 + }
  951 + }
  952 + }
  953 + if ($this->row_counter >= (
  954 + $this->limit_from + $this->limit_count))
  955 + {
  956 + if ($this->autofree) {
  957 + $this->free();
  958 + }
  959 + return null;
  960 + }
  961 + if ($this->limit_type == 'emulate') {
  962 + $rownum = $this->row_counter;
  963 + }
  964 +
  965 + $this->row_counter++;
  966 + }
  967 + $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
  968 + if ($res === DB_OK) {
  969 + if (isset($object_class)) {
  970 + // default mode specified in DB_common::fetchmode_object_class property
  971 + if ($object_class == 'stdClass') {
  972 + $arr = (object) $arr;
  973 + } else {
  974 + $arr = new $object_class($arr);
  975 + }
  976 + }
  977 + return DB_OK;
  978 + }
  979 + if ($res == null && $this->autofree) {
  980 + $this->free();
  981 + }
  982 + return $res;
  983 + }
  984 +
  985 + // }}}
  986 + // {{{ numCols()
  987 +
  988 + /**
  989 + * Get the the number of columns in a result set.
  990 + *
  991 + * @return int the number of columns, or a DB error
  992 + *
  993 + * @access public
  994 + */
  995 + function numCols()
  996 + {
  997 + return $this->dbh->numCols($this->result);
  998 + }
  999 +
  1000 + // }}}
  1001 + // {{{ numRows()
  1002 +
  1003 + /**
  1004 + * Get the number of rows in a result set.
  1005 + *
  1006 + * @return int the number of rows, or a DB error
  1007 + *
  1008 + * @access public
  1009 + */
  1010 + function numRows()
  1011 + {
  1012 + return $this->dbh->numRows($this->result);
  1013 + }
  1014 +
  1015 + // }}}
  1016 + // {{{ nextResult()
  1017 +
  1018 + /**
  1019 + * Get the next result if a batch of queries was executed.
  1020 + *
  1021 + * @return bool true if a new result is available or false if not.
  1022 + *
  1023 + * @access public
  1024 + */
  1025 + function nextResult()
  1026 + {
  1027 + return $this->dbh->nextResult($this->result);
  1028 + }
  1029 +
  1030 + // }}}
  1031 + // {{{ free()
  1032 +
  1033 + /**
  1034 + * Frees the resources allocated for this result set.
  1035 + * @return int error code
  1036 + *
  1037 + * @access public
  1038 + */
  1039 + function free()
  1040 + {
  1041 + $err = $this->dbh->freeResult($this->result);
  1042 + if (DB::isError($err)) {
  1043 + return $err;
  1044 + }
  1045 + $this->result = false;
  1046 + return true;
  1047 + }
  1048 +
  1049 + // }}}
  1050 + // {{{ tableInfo()
  1051 +
  1052 + /**
  1053 + * @deprecated
  1054 + * @internal
  1055 + * @see DB_common::tableInfo()
  1056 + */
  1057 + function tableInfo($mode = null)
  1058 + {
  1059 + if (is_string($mode)) {
  1060 + return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA);
  1061 + }
  1062 + return $this->dbh->tableInfo($this, $mode);
  1063 + }
  1064 +
  1065 + // }}}
  1066 + // {{{ getRowCounter()
  1067 +
  1068 + /**
  1069 + * returns the actual row number
  1070 + * @return integer
  1071 + */
  1072 + function getRowCounter()
  1073 + {
  1074 + return $this->row_counter;
  1075 + }
  1076 + // }}}
  1077 +}
  1078 +
  1079 +// }}}
  1080 +// {{{ class DB_row
  1081 +
  1082 +/**
  1083 + * Pear DB Row Object
  1084 + * @see DB_common::setFetchMode()
  1085 + */
  1086 +class DB_row
  1087 +{
  1088 + // {{{ constructor
  1089 +
  1090 + /**
  1091 + * constructor
  1092 + *
  1093 + * @param resource row data as array
  1094 + */
  1095 + function DB_row(&$arr)
  1096 + {
  1097 + foreach ($arr as $key => $value) {
  1098 + $this->$key = &$arr[$key];
  1099 + }
  1100 + }
  1101 +
  1102 + // }}}
  1103 +}
  1104 +
  1105 +// }}}
  1106 +
  1107 +/*
  1108 + * Local variables:
  1109 + * tab-width: 4
  1110 + * c-basic-offset: 4
  1111 + * End:
  1112 + */
  1113 +
  1114 +?>
... ...
pear/DB/common.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Stig Bakken <ssb@php.net> |
  17 +// | Tomas V.V.Cox <cox@idecnet.com> |
  18 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  19 +// +----------------------------------------------------------------------+
  20 +//
  21 +// $Id$
  22 +
  23 +require_once 'PEAR.php';
  24 +
  25 +/**
  26 + * DB_common is a base class for DB implementations, and must be
  27 + * inherited by all such
  28 + *
  29 + * @package DB
  30 + * @version $Id$
  31 + * @category Database
  32 + * @author Stig Bakken <ssb@php.net>
  33 + * @author Tomas V.V.Cox <cox@idecnet.com>
  34 + */
  35 +class DB_common extends PEAR
  36 +{
  37 + // {{{ properties
  38 +
  39 + /**
  40 + * assoc of capabilities for this DB implementation
  41 + * $features['limit'] => 'emulate' => emulate with fetch row by number
  42 + * 'alter' => alter the query
  43 + * false => skip rows
  44 + * @var array
  45 + */
  46 + var $features = array();
  47 +
  48 + /**
  49 + * assoc mapping native error codes to DB ones
  50 + * @var array
  51 + */
  52 + var $errorcode_map = array();
  53 +
  54 + /**
  55 + * DB type (mysql, oci8, odbc etc.)
  56 + * @var string
  57 + */
  58 + var $phptype;
  59 +
  60 + /**
  61 + * @var string
  62 + */
  63 + var $prepare_tokens;
  64 +
  65 + /**
  66 + * @var string
  67 + */
  68 + var $prepare_types;
  69 +
  70 + /**
  71 + * @var string
  72 + */
  73 + var $prepared_queries;
  74 +
  75 + /**
  76 + * @var integer
  77 + */
  78 + var $prepare_maxstmt = 0;
  79 +
  80 + /**
  81 + * @var string
  82 + */
  83 + var $last_query = '';
  84 +
  85 + /**
  86 + * @var integer
  87 + */
  88 + var $fetchmode = DB_FETCHMODE_ORDERED;
  89 +
  90 + /**
  91 + * @var string
  92 + */
  93 + var $fetchmode_object_class = 'stdClass';
  94 +
  95 + /**
  96 + * Run-time configuration options.
  97 + *
  98 + * The 'optimize' option has been deprecated. Use the 'portability'
  99 + * option instead.
  100 + *
  101 + * @see DB_common::setOption()
  102 + * @var array
  103 + */
  104 + var $options = array(
  105 + 'persistent' => false,
  106 + 'ssl' => false,
  107 + 'debug' => 0,
  108 + 'seqname_format' => '%s_seq',
  109 + 'autofree' => false,
  110 + 'portability' => DB_PORTABILITY_NONE,
  111 + 'optimize' => 'performance', // Deprecated. Use 'portability'.
  112 + );
  113 +
  114 + /**
  115 + * DB handle
  116 + * @var resource
  117 + */
  118 + var $dbh;
  119 +
  120 + // }}}
  121 + // {{{ toString()
  122 +
  123 + /**
  124 + * String conversation
  125 + *
  126 + * @return string
  127 + * @access private
  128 + */
  129 + function toString()
  130 + {
  131 + $info = strtolower(get_class($this));
  132 + $info .= ': (phptype=' . $this->phptype .
  133 + ', dbsyntax=' . $this->dbsyntax .
  134 + ')';
  135 +
  136 + if ($this->connection) {
  137 + $info .= ' [connected]';
  138 + }
  139 +
  140 + return $info;
  141 + }
  142 +
  143 + // }}}
  144 + // {{{ constructor
  145 +
  146 + /**
  147 + * Constructor
  148 + */
  149 + function DB_common()
  150 + {
  151 + $this->PEAR('DB_Error');
  152 + }
  153 +
  154 + // }}}
  155 + // {{{ quoteString()
  156 +
  157 + /**
  158 + * DEPRECATED: Quotes a string so it can be safely used within string
  159 + * delimiters in a query
  160 + *
  161 + * @return string quoted string
  162 + *
  163 + * @see DB_common::quoteSmart(), DB_common::escapeSimple()
  164 + * @deprecated Deprecated in release 1.2 or lower
  165 + * @internal
  166 + */
  167 + function quoteString($string)
  168 + {
  169 + $string = $this->quote($string);
  170 + if ($string{0} == "'") {
  171 + return substr($string, 1, -1);
  172 + }
  173 + return $string;
  174 + }
  175 +
  176 + // }}}
  177 + // {{{ quote()
  178 +
  179 + /**
  180 + * DEPRECATED: Quotes a string so it can be safely used in a query
  181 + *
  182 + * @param string $string the input string to quote
  183 + *
  184 + * @return string The NULL string or the string quotes
  185 + * in magic_quote_sybase style
  186 + *
  187 + * @see DB_common::quoteSmart(), DB_common::escapeSimple()
  188 + * @deprecated Deprecated in release 1.6.0
  189 + * @internal
  190 + */
  191 + function quote($string = null)
  192 + {
  193 + return ($string === null) ? 'NULL' : "'".str_replace("'", "''", $string)."'";
  194 + }
  195 +
  196 + // }}}
  197 + // {{{ quoteIdentifier()
  198 +
  199 + /**
  200 + * Quote a string so it can be safely used as a table or column name
  201 + *
  202 + * Delimiting style depends on which database driver is being used.
  203 + *
  204 + * NOTE: just because you CAN use delimited identifiers doesn't mean
  205 + * you SHOULD use them. In general, they end up causing way more
  206 + * problems than they solve.
  207 + *
  208 + * Portability is broken by using the following characters inside
  209 + * delimited identifiers:
  210 + * + backtick (<kbd>`</kbd>) -- due to MySQL
  211 + * + double quote (<kbd>"</kbd>) -- due to Oracle
  212 + * + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
  213 + *
  214 + * Delimited identifiers are known to generally work correctly under
  215 + * the following drivers:
  216 + * + mssql
  217 + * + mysql
  218 + * + mysqli
  219 + * + oci8
  220 + * + odbc(access)
  221 + * + odbc(db2)
  222 + * + pgsql
  223 + * + sqlite
  224 + * + sybase
  225 + *
  226 + * InterBase doesn't seem to be able to use delimited identifiers
  227 + * via PHP 4. They work fine under PHP 5.
  228 + *
  229 + * @param string $str identifier name to be quoted
  230 + *
  231 + * @return string quoted identifier string
  232 + *
  233 + * @since 1.6.0
  234 + * @access public
  235 + */
  236 + function quoteIdentifier($str)
  237 + {
  238 + return '"' . str_replace('"', '""', $str) . '"';
  239 + }
  240 +
  241 + // }}}
  242 + // {{{ quoteSmart()
  243 +
  244 + /**
  245 + * Format input so it can be safely used in a query
  246 + *
  247 + * The output depends on the PHP data type of input and the database
  248 + * type being used.
  249 + *
  250 + * @param mixed $in data to be quoted
  251 + *
  252 + * @return mixed the format of the results depends on the input's
  253 + * PHP type:
  254 + *
  255 + * <ul>
  256 + * <li>
  257 + * <kbd>input</kbd> -> <samp>returns</samp>
  258 + * </li>
  259 + * <li>
  260 + * <kbd>null</kbd> -> the string <samp>NULL</samp>
  261 + * </li>
  262 + * <li>
  263 + * <kbd>integer</kbd> or <kbd>double</kbd> -> the unquoted number
  264 + * </li>
  265 + * <li>
  266 + * &type.bool; -> output depends on the driver in use
  267 + * Most drivers return integers: <samp>1</samp> if
  268 + * <kbd>true</kbd> or <samp>0</samp> if
  269 + * <kbd>false</kbd>.
  270 + * Some return strings: <samp>TRUE</samp> if
  271 + * <kbd>true</kbd> or <samp>FALSE</samp> if
  272 + * <kbd>false</kbd>.
  273 + * Finally one returns strings: <samp>T</samp> if
  274 + * <kbd>true</kbd> or <samp>F</samp> if
  275 + * <kbd>false</kbd>. Here is a list of each DBMS,
  276 + * the values returned and the suggested column type:
  277 + * <ul>
  278 + * <li>
  279 + * <kbd>dbase</kbd> -> <samp>T/F</samp>
  280 + * (<kbd>Logical</kbd>)
  281 + * </li>
  282 + * <li>
  283 + * <kbd>fbase</kbd> -> <samp>TRUE/FALSE</samp>
  284 + * (<kbd>BOOLEAN</kbd>)
  285 + * </li>
  286 + * <li>
  287 + * <kbd>ibase</kbd> -> <samp>1/0</samp>
  288 + * (<kbd>SMALLINT</kbd>) [1]
  289 + * </li>
  290 + * <li>
  291 + * <kbd>ifx</kbd> -> <samp>1/0</samp>
  292 + * (<kbd>SMALLINT</kbd>) [1]
  293 + * </li>
  294 + * <li>
  295 + * <kbd>msql</kbd> -> <samp>1/0</samp>
  296 + * (<kbd>INTEGER</kbd>)
  297 + * </li>
  298 + * <li>
  299 + * <kbd>mssql</kbd> -> <samp>1/0</samp>
  300 + * (<kbd>BIT</kbd>)
  301 + * </li>
  302 + * <li>
  303 + * <kbd>mysql</kbd> -> <samp>1/0</samp>
  304 + * (<kbd>TINYINT(1)</kbd>)
  305 + * </li>
  306 + * <li>
  307 + * <kbd>mysqli</kbd> -> <samp>1/0</samp>
  308 + * (<kbd>TINYINT(1)</kbd>)
  309 + * </li>
  310 + * <li>
  311 + * <kbd>oci8</kbd> -> <samp>1/0</samp>
  312 + * (<kbd>NUMBER(1)</kbd>)
  313 + * </li>
  314 + * <li>
  315 + * <kbd>odbc</kbd> -> <samp>1/0</samp>
  316 + * (<kbd>SMALLINT</kbd>) [1]
  317 + * </li>
  318 + * <li>
  319 + * <kbd>pgsql</kbd> -> <samp>TRUE/FALSE</samp>
  320 + * (<kbd>BOOLEAN</kbd>)
  321 + * </li>
  322 + * <li>
  323 + * <kbd>sqlite</kbd> -> <samp>1/0</samp>
  324 + * (<kbd>INTEGER</kbd>)
  325 + * </li>
  326 + * <li>
  327 + * <kbd>sybase</kbd> -> <samp>1/0</samp>
  328 + * (<kbd>TINYINT(1)</kbd>)
  329 + * </li>
  330 + * </ul>
  331 + * [1] Accommodate the lowest common denominator because not all
  332 + * versions of have <kbd>BOOLEAN</kbd>.
  333 + * </li>
  334 + * <li>
  335 + * other (including strings and numeric strings) ->
  336 + * the data with single quotes escaped by preceeding
  337 + * single quotes, backslashes are escaped by preceeding
  338 + * backslashes, then the whole string is encapsulated
  339 + * between single quotes
  340 + * </li>
  341 + * </ul>
  342 + *
  343 + * @since 1.6.0
  344 + * @see DB_common::escapeSimple()
  345 + * @access public
  346 + */
  347 + function quoteSmart($in)
  348 + {
  349 + if (is_int($in) || is_double($in)) {
  350 + return $in;
  351 + } elseif (is_bool($in)) {
  352 + return $in ? 1 : 0;
  353 + } elseif (is_null($in)) {
  354 + return 'NULL';
  355 + } else {
  356 + return "'" . $this->escapeSimple($in) . "'";
  357 + }
  358 + }
  359 +
  360 + // }}}
  361 + // {{{ escapeSimple()
  362 +
  363 + /**
  364 + * Escape a string according to the current DBMS's standards
  365 + *
  366 + * In SQLite, this makes things safe for inserts/updates, but may
  367 + * cause problems when performing text comparisons against columns
  368 + * containing binary data. See the
  369 + * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
  370 + *
  371 + * @param string $str the string to be escaped
  372 + *
  373 + * @return string the escaped string
  374 + *
  375 + * @since 1.6.0
  376 + * @see DB_common::quoteSmart()
  377 + * @access public
  378 + */
  379 + function escapeSimple($str) {
  380 + return str_replace("'", "''", $str);
  381 + }
  382 +
  383 + // }}}
  384 + // {{{ provides()
  385 +
  386 + /**
  387 + * Tell whether a DB implementation or its backend extension
  388 + * supports a given feature
  389 + *
  390 + * @param array $feature name of the feature (see the DB class doc)
  391 + * @return bool whether this DB implementation supports $feature
  392 + * @access public
  393 + */
  394 + function provides($feature)
  395 + {
  396 + return $this->features[$feature];
  397 + }
  398 +
  399 + // }}}
  400 + // {{{ errorCode()
  401 +
  402 + /**
  403 + * Map native error codes to DB's portable ones
  404 + *
  405 + * Requires that the DB implementation's constructor fills
  406 + * in the <var>$errorcode_map</var> property.
  407 + *
  408 + * @param mixed $nativecode the native error code, as returned by the
  409 + * backend database extension (string or integer)
  410 + *
  411 + * @return int a portable DB error code, or DB_ERROR if this DB
  412 + * implementation has no mapping for the given error code.
  413 + *
  414 + * @access public
  415 + */
  416 + function errorCode($nativecode)
  417 + {
  418 + if (isset($this->errorcode_map[$nativecode])) {
  419 + return $this->errorcode_map[$nativecode];
  420 + }
  421 + // Fall back to DB_ERROR if there was no mapping.
  422 + return DB_ERROR;
  423 + }
  424 +
  425 + // }}}
  426 + // {{{ errorMessage()
  427 +
  428 + /**
  429 + * Map a DB error code to a textual message. This is actually
  430 + * just a wrapper for DB::errorMessage()
  431 + *
  432 + * @param integer $dbcode the DB error code
  433 + *
  434 + * @return string the corresponding error message, of false
  435 + * if the error code was unknown
  436 + *
  437 + * @access public
  438 + */
  439 + function errorMessage($dbcode)
  440 + {
  441 + return DB::errorMessage($this->errorcode_map[$dbcode]);
  442 + }
  443 +
  444 + // }}}
  445 + // {{{ raiseError()
  446 +
  447 + /**
  448 + * Communicate an error and invoke error callbacks, etc
  449 + *
  450 + * Basically a wrapper for PEAR::raiseError without the message string.
  451 + *
  452 + * @param mixed integer error code, or a PEAR error object (all
  453 + * other parameters are ignored if this parameter is
  454 + * an object
  455 + *
  456 + * @param int error mode, see PEAR_Error docs
  457 + *
  458 + * @param mixed If error mode is PEAR_ERROR_TRIGGER, this is the
  459 + * error level (E_USER_NOTICE etc). If error mode is
  460 + * PEAR_ERROR_CALLBACK, this is the callback function,
  461 + * either as a function name, or as an array of an
  462 + * object and method name. For other error modes this
  463 + * parameter is ignored.
  464 + *
  465 + * @param string Extra debug information. Defaults to the last
  466 + * query and native error code.
  467 + *
  468 + * @param mixed Native error code, integer or string depending the
  469 + * backend.
  470 + *
  471 + * @return object a PEAR error object
  472 + *
  473 + * @access public
  474 + * @see PEAR_Error
  475 + */
  476 + function &raiseError($code = DB_ERROR, $mode = null, $options = null,
  477 + $userinfo = null, $nativecode = null)
  478 + {
  479 + // The error is yet a DB error object
  480 + if (is_object($code)) {
  481 + // because we the static PEAR::raiseError, our global
  482 + // handler should be used if it is set
  483 + if ($mode === null && !empty($this->_default_error_mode)) {
  484 + $mode = $this->_default_error_mode;
  485 + $options = $this->_default_error_options;
  486 + }
  487 + $tmp = PEAR::raiseError($code, null, $mode, $options, null, null, true);
  488 + return $tmp;
  489 + }
  490 +
  491 + if ($userinfo === null) {
  492 + $userinfo = $this->last_query;
  493 + }
  494 +
  495 + if ($nativecode) {
  496 + $userinfo .= ' [nativecode=' . trim($nativecode) . ']';
  497 + }
  498 +
  499 + $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo,
  500 + 'DB_Error', true);
  501 + return $tmp;
  502 + }
  503 +
  504 + // }}}
  505 + // {{{ setFetchMode()
  506 +
  507 + /**
  508 + * Sets which fetch mode should be used by default on queries
  509 + * on this connection
  510 + *
  511 + * @param integer $fetchmode DB_FETCHMODE_ORDERED or
  512 + * DB_FETCHMODE_ASSOC, possibly bit-wise OR'ed with
  513 + * DB_FETCHMODE_FLIPPED.
  514 + *
  515 + * @param string $object_class The class of the object
  516 + * to be returned by the fetch methods when
  517 + * the DB_FETCHMODE_OBJECT mode is selected.
  518 + * If no class is specified by default a cast
  519 + * to object from the assoc array row will be done.
  520 + * There is also the posibility to use and extend the
  521 + * 'DB_row' class.
  522 + *
  523 + * @see DB_FETCHMODE_ORDERED
  524 + * @see DB_FETCHMODE_ASSOC
  525 + * @see DB_FETCHMODE_FLIPPED
  526 + * @see DB_FETCHMODE_OBJECT
  527 + * @see DB_row::DB_row()
  528 + * @access public
  529 + */
  530 + function setFetchMode($fetchmode, $object_class = 'stdClass')
  531 + {
  532 + switch ($fetchmode) {
  533 + case DB_FETCHMODE_OBJECT:
  534 + $this->fetchmode_object_class = $object_class;
  535 + case DB_FETCHMODE_ORDERED:
  536 + case DB_FETCHMODE_ASSOC:
  537 + $this->fetchmode = $fetchmode;
  538 + break;
  539 + default:
  540 + return $this->raiseError('invalid fetchmode mode');
  541 + }
  542 + }
  543 +
  544 + // }}}
  545 + // {{{ setOption()
  546 +
  547 + /**
  548 + * Set run-time configuration options for PEAR DB
  549 + *
  550 + * Options, their data types, default values and description:
  551 + * <ul>
  552 + * <li>
  553 + * <var>autofree</var> <kbd>boolean</kbd> = <samp>false</samp>
  554 + * <br />should results be freed automatically when there are no
  555 + * more rows?
  556 + * </li><li>
  557 + * <var>debug</var> <kbd>integer</kbd> = <samp>0</samp>
  558 + * <br />debug level
  559 + * </li><li>
  560 + * <var>persistent</var> <kbd>boolean</kbd> = <samp>false</samp>
  561 + * <br />should the connection be persistent?
  562 + * </li><li>
  563 + * <var>portability</var> <kbd>integer</kbd> = <samp>DB_PORTABILITY_NONE</samp>
  564 + * <br />portability mode constant (see below)
  565 + * </li><li>
  566 + * <var>seqname_format</var> <kbd>string</kbd> = <samp>%s_seq</samp>
  567 + * <br />the sprintf() format string used on sequence names. This
  568 + * format is applied to sequence names passed to
  569 + * createSequence(), nextID() and dropSequence().
  570 + * </li><li>
  571 + * <var>ssl</var> <kbd>boolean</kbd> = <samp>false</samp>
  572 + * <br />use ssl to connect?
  573 + * </li>
  574 + * </ul>
  575 + *
  576 + * -----------------------------------------
  577 + *
  578 + * PORTABILITY MODES
  579 + *
  580 + * These modes are bitwised, so they can be combined using <kbd>|</kbd>
  581 + * and removed using <kbd>^</kbd>. See the examples section below on how
  582 + * to do this.
  583 + *
  584 + * <samp>DB_PORTABILITY_NONE</samp>
  585 + * turn off all portability features
  586 + *
  587 + * This mode gets automatically turned on if the deprecated
  588 + * <var>optimize</var> option gets set to <samp>performance</samp>.
  589 + *
  590 + *
  591 + * <samp>DB_PORTABILITY_LOWERCASE</samp>
  592 + * convert names of tables and fields to lower case when using
  593 + * <kbd>get*()</kbd>, <kbd>fetch*()</kbd> and <kbd>tableInfo()</kbd>
  594 + *
  595 + * This mode gets automatically turned on in the following databases
  596 + * if the deprecated option <var>optimize</var> gets set to
  597 + * <samp>portability</samp>:
  598 + * + oci8
  599 + *
  600 + *
  601 + * <samp>DB_PORTABILITY_RTRIM</samp>
  602 + * right trim the data output by <kbd>get*()</kbd> <kbd>fetch*()</kbd>
  603 + *
  604 + *
  605 + * <samp>DB_PORTABILITY_DELETE_COUNT</samp>
  606 + * force reporting the number of rows deleted
  607 + *
  608 + * Some DBMS's don't count the number of rows deleted when performing
  609 + * simple <kbd>DELETE FROM tablename</kbd> queries. This portability
  610 + * mode tricks such DBMS's into telling the count by adding
  611 + * <samp>WHERE 1=1</samp> to the end of <kbd>DELETE</kbd> queries.
  612 + *
  613 + * This mode gets automatically turned on in the following databases
  614 + * if the deprecated option <var>optimize</var> gets set to
  615 + * <samp>portability</samp>:
  616 + * + fbsql
  617 + * + mysql
  618 + * + mysqli
  619 + * + sqlite
  620 + *
  621 + *
  622 + * <samp>DB_PORTABILITY_NUMROWS</samp>
  623 + * enable hack that makes <kbd>numRows()</kbd> work in Oracle
  624 + *
  625 + * This mode gets automatically turned on in the following databases
  626 + * if the deprecated option <var>optimize</var> gets set to
  627 + * <samp>portability</samp>:
  628 + * + oci8
  629 + *
  630 + *
  631 + * <samp>DB_PORTABILITY_ERRORS</samp>
  632 + * makes certain error messages in certain drivers compatible
  633 + * with those from other DBMS's
  634 + *
  635 + * + mysql, mysqli: change unique/primary key constraints
  636 + * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
  637 + *
  638 + * + odbc(access): MS's ODBC driver reports 'no such field' as code
  639 + * 07001, which means 'too few parameters.' When this option is on
  640 + * that code gets mapped to DB_ERROR_NOSUCHFIELD.
  641 + * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD
  642 + *
  643 + *
  644 + * <samp>DB_PORTABILITY_NULL_TO_EMPTY</samp>
  645 + * convert null values to empty strings in data output by get*() and
  646 + * fetch*(). Needed because Oracle considers empty strings to be null,
  647 + * while most other DBMS's know the difference between empty and null.
  648 + *
  649 + *
  650 + * <samp>DB_PORTABILITY_ALL</samp>
  651 + * turn on all portability features
  652 + *
  653 + * -----------------------------------------
  654 + *
  655 + * Example 1. Simple setOption() example
  656 + * <code> <?php
  657 + * $dbh->setOption('autofree', true);
  658 + * ?></code>
  659 + *
  660 + * Example 2. Portability for lowercasing and trimming
  661 + * <code> <?php
  662 + * $dbh->setOption('portability',
  663 + * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM);
  664 + * ?></code>
  665 + *
  666 + * Example 3. All portability options except trimming
  667 + * <code> <?php
  668 + * $dbh->setOption('portability',
  669 + * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM);
  670 + * ?></code>
  671 + *
  672 + * @param string $option option name
  673 + * @param mixed $value value for the option
  674 + *
  675 + * @return int DB_OK on success. DB_Error object on failure.
  676 + *
  677 + * @see DB_common::$options
  678 + */
  679 + function setOption($option, $value)
  680 + {
  681 + if (isset($this->options[$option])) {
  682 + $this->options[$option] = $value;
  683 +
  684 + /*
  685 + * Backwards compatibility check for the deprecated 'optimize'
  686 + * option. Done here in case settings change after connecting.
  687 + */
  688 + if ($option == 'optimize') {
  689 + if ($value == 'portability') {
  690 + switch ($this->phptype) {
  691 + case 'oci8':
  692 + $this->options['portability'] =
  693 + DB_PORTABILITY_LOWERCASE |
  694 + DB_PORTABILITY_NUMROWS;
  695 + break;
  696 + case 'fbsql':
  697 + case 'mysql':
  698 + case 'mysqli':
  699 + case 'sqlite':
  700 + $this->options['portability'] =
  701 + DB_PORTABILITY_DELETE_COUNT;
  702 + break;
  703 + }
  704 + } else {
  705 + $this->options['portability'] = DB_PORTABILITY_NONE;
  706 + }
  707 + }
  708 +
  709 + return DB_OK;
  710 + }
  711 + return $this->raiseError("unknown option $option");
  712 + }
  713 +
  714 + // }}}
  715 + // {{{ getOption()
  716 +
  717 + /**
  718 + * Returns the value of an option
  719 + *
  720 + * @param string $option option name
  721 + *
  722 + * @return mixed the option value
  723 + */
  724 + function getOption($option)
  725 + {
  726 + if (isset($this->options[$option])) {
  727 + return $this->options[$option];
  728 + }
  729 + return $this->raiseError("unknown option $option");
  730 + }
  731 +
  732 + // }}}
  733 + // {{{ prepare()
  734 +
  735 + /**
  736 + * Prepares a query for multiple execution with execute()
  737 + *
  738 + * Creates a query that can be run multiple times. Each time it is run,
  739 + * the placeholders, if any, will be replaced by the contents of
  740 + * execute()'s $data argument.
  741 + *
  742 + * Three types of placeholders can be used:
  743 + * + <kbd>?</kbd> scalar value (i.e. strings, integers). The system
  744 + * will automatically quote and escape the data.
  745 + * + <kbd>!</kbd> value is inserted 'as is'
  746 + * + <kbd>&</kbd> requires a file name. The file's contents get
  747 + * inserted into the query (i.e. saving binary
  748 + * data in a db)
  749 + *
  750 + * Example 1.
  751 + * <code> <?php
  752 + * $sth = $dbh->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
  753 + * $data = array(
  754 + * "John's text",
  755 + * "'it''s good'",
  756 + * 'filename.txt'
  757 + * );
  758 + * $res = $dbh->execute($sth, $data);
  759 + * ?></code>
  760 + *
  761 + * Use backslashes to escape placeholder characters if you don't want
  762 + * them to be interpreted as placeholders:
  763 + * <pre>
  764 + * "UPDATE foo SET col=? WHERE col='over \& under'"
  765 + * </pre>
  766 + *
  767 + * With some database backends, this is emulated.
  768 + *
  769 + * {@internal ibase and oci8 have their own prepare() methods.}}
  770 + *
  771 + * @param string $query query to be prepared
  772 + *
  773 + * @return mixed DB statement resource on success. DB_Error on failure.
  774 + *
  775 + * @see DB_common::execute()
  776 + * @access public
  777 + */
  778 + function prepare($query)
  779 + {
  780 + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1,
  781 + PREG_SPLIT_DELIM_CAPTURE);
  782 + $token = 0;
  783 + $types = array();
  784 + $newtokens = array();
  785 +
  786 + foreach ($tokens as $val) {
  787 + switch ($val) {
  788 + case '?':
  789 + $types[$token++] = DB_PARAM_SCALAR;
  790 + break;
  791 + case '&':
  792 + $types[$token++] = DB_PARAM_OPAQUE;
  793 + break;
  794 + case '!':
  795 + $types[$token++] = DB_PARAM_MISC;
  796 + break;
  797 + default:
  798 + $newtokens[] = preg_replace('/\\\([&?!])/', "\\1", $val);
  799 + }
  800 + }
  801 +
  802 + $this->prepare_tokens[] = &$newtokens;
  803 + end($this->prepare_tokens);
  804 +
  805 + $k = key($this->prepare_tokens);
  806 + $this->prepare_types[$k] = $types;
  807 + $this->prepared_queries[$k] = implode(' ', $newtokens);
  808 +
  809 + return $k;
  810 + }
  811 +
  812 + // }}}
  813 + // {{{ autoPrepare()
  814 +
  815 + /**
  816 + * Automaticaly generate an insert or update query and pass it to prepare()
  817 + *
  818 + * @param string $table name of the table
  819 + * @param array $table_fields ordered array containing the fields names
  820 + * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE)
  821 + * @param string $where in case of update queries, this string will be put after the sql WHERE statement
  822 + * @return resource handle for the query
  823 + * @see DB_common::prepare(), DB_common::buildManipSQL()
  824 + * @access public
  825 + */
  826 + function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT, $where = false)
  827 + {
  828 + $query = $this->buildManipSQL($table, $table_fields, $mode, $where);
  829 + return $this->prepare($query);
  830 + }
  831 +
  832 + // }}}
  833 + // {{{ autoExecute()
  834 +
  835 + /**
  836 + * Automaticaly generate an insert or update query and call prepare()
  837 + * and execute() with it
  838 + *
  839 + * @param string $table name of the table
  840 + * @param array $fields_values assoc ($key=>$value) where $key is a field name and $value its value
  841 + * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE)
  842 + * @param string $where in case of update queries, this string will be put after the sql WHERE statement
  843 + * @return mixed a new DB_Result or a DB_Error when fail
  844 + * @see DB_common::autoPrepare(), DB_common::buildManipSQL()
  845 + * @access public
  846 + */
  847 + function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT, $where = false)
  848 + {
  849 + $sth = $this->autoPrepare($table, array_keys($fields_values), $mode, $where);
  850 + $ret =& $this->execute($sth, array_values($fields_values));
  851 + $this->freePrepared($sth);
  852 + return $ret;
  853 +
  854 + }
  855 +
  856 + // }}}
  857 + // {{{ buildManipSQL()
  858 +
  859 + /**
  860 + * Make automaticaly an sql query for prepare()
  861 + *
  862 + * Example : buildManipSQL('table_sql', array('field1', 'field2', 'field3'), DB_AUTOQUERY_INSERT)
  863 + * will return the string : INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?)
  864 + * NB : - This belongs more to a SQL Builder class, but this is a simple facility
  865 + * - Be carefull ! If you don't give a $where param with an UPDATE query, all
  866 + * the records of the table will be updated !
  867 + *
  868 + * @param string $table name of the table
  869 + * @param array $table_fields ordered array containing the fields names
  870 + * @param int $mode type of query to make (DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE)
  871 + * @param string $where in case of update queries, this string will be put after the sql WHERE statement
  872 + * @return string sql query for prepare()
  873 + * @access public
  874 + */
  875 + function buildManipSQL($table, $table_fields, $mode, $where = false)
  876 + {
  877 + if (count($table_fields) == 0) {
  878 + $this->raiseError(DB_ERROR_NEED_MORE_DATA);
  879 + }
  880 + $first = true;
  881 + switch ($mode) {
  882 + case DB_AUTOQUERY_INSERT:
  883 + $values = '';
  884 + $names = '';
  885 + foreach ($table_fields as $value) {
  886 + if ($first) {
  887 + $first = false;
  888 + } else {
  889 + $names .= ',';
  890 + $values .= ',';
  891 + }
  892 + $names .= $value;
  893 + $values .= '?';
  894 + }
  895 + return "INSERT INTO $table ($names) VALUES ($values)";
  896 + case DB_AUTOQUERY_UPDATE:
  897 + $set = '';
  898 + foreach ($table_fields as $value) {
  899 + if ($first) {
  900 + $first = false;
  901 + } else {
  902 + $set .= ',';
  903 + }
  904 + $set .= "$value = ?";
  905 + }
  906 + $sql = "UPDATE $table SET $set";
  907 + if ($where) {
  908 + $sql .= " WHERE $where";
  909 + }
  910 + return $sql;
  911 + default:
  912 + $this->raiseError(DB_ERROR_SYNTAX);
  913 + }
  914 + }
  915 +
  916 + // }}}
  917 + // {{{ execute()
  918 +
  919 + /**
  920 + * Executes a DB statement prepared with prepare()
  921 + *
  922 + * Example 1.
  923 + * <code> <?php
  924 + * $sth = $dbh->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
  925 + * $data = array(
  926 + * "John's text",
  927 + * "'it''s good'",
  928 + * 'filename.txt'
  929 + * );
  930 + * $res =& $dbh->execute($sth, $data);
  931 + * ?></code>
  932 + *
  933 + * @param resource $stmt a DB statement resource returned from prepare()
  934 + * @param mixed $data array, string or numeric data to be used in
  935 + * execution of the statement. Quantity of items
  936 + * passed must match quantity of placeholders in
  937 + * query: meaning 1 placeholder for non-array
  938 + * parameters or 1 placeholder per array element.
  939 + *
  940 + * @return object a new DB_Result or a DB_Error when fail
  941 + *
  942 + * {@internal ibase and oci8 have their own execute() methods.}}
  943 + *
  944 + * @see DB_common::prepare()
  945 + * @access public
  946 + */
  947 + function &execute($stmt, $data = array())
  948 + {
  949 + $realquery = $this->executeEmulateQuery($stmt, $data);
  950 + if (DB::isError($realquery)) {
  951 + return $realquery;
  952 + }
  953 + $result = $this->simpleQuery($realquery);
  954 +
  955 + if (DB::isError($result) || $result === DB_OK) {
  956 + return $result;
  957 + } else {
  958 + $tmp =& new DB_result($this, $result);
  959 + return $tmp;
  960 + }
  961 + }
  962 +
  963 + // }}}
  964 + // {{{ executeEmulateQuery()
  965 +
  966 + /**
  967 + * Emulates the execute statement, when not supported
  968 + *
  969 + * @param resource $stmt a DB statement resource returned from execute()
  970 + * @param mixed $data array, string or numeric data to be used in
  971 + * execution of the statement. Quantity of items
  972 + * passed must match quantity of placeholders in
  973 + * query: meaning 1 placeholder for non-array
  974 + * parameters or 1 placeholder per array element.
  975 + *
  976 + * @return mixed a string containing the real query run when emulating
  977 + * prepare/execute. A DB error code is returned on failure.
  978 + *
  979 + * @see DB_common::execute()
  980 + * @access private
  981 + */
  982 + function executeEmulateQuery($stmt, $data = array())
  983 + {
  984 + $stmt = (int)$stmt;
  985 + if (!is_array($data)) {
  986 + $data = array($data);
  987 + }
  988 +
  989 + if (count($this->prepare_types[$stmt]) != count($data)) {
  990 + $this->last_query = $this->prepared_queries[$stmt];
  991 + return $this->raiseError(DB_ERROR_MISMATCH);
  992 + }
  993 +
  994 + $realquery = $this->prepare_tokens[$stmt][0];
  995 +
  996 + $i = 0;
  997 + foreach ($data as $value) {
  998 + if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) {
  999 + $realquery .= $this->quoteSmart($value);
  1000 + } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) {
  1001 + $fp = @fopen($value, 'rb');
  1002 + if (!$fp) {
  1003 + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
  1004 + }
  1005 + $realquery .= $this->quoteSmart(fread($fp, filesize($value)));
  1006 + fclose($fp);
  1007 + } else {
  1008 + $realquery .= $value;
  1009 + }
  1010 +
  1011 + $realquery .= $this->prepare_tokens[$stmt][++$i];
  1012 + }
  1013 +
  1014 + return $realquery;
  1015 + }
  1016 +
  1017 + // }}}
  1018 + // {{{ executeMultiple()
  1019 +
  1020 + /**
  1021 + * This function does several execute() calls on the same
  1022 + * statement handle
  1023 + *
  1024 + * $data must be an array indexed numerically
  1025 + * from 0, one execute call is done for every "row" in the array.
  1026 + *
  1027 + * If an error occurs during execute(), executeMultiple() does not
  1028 + * execute the unfinished rows, but rather returns that error.
  1029 + *
  1030 + * @param resource $stmt query handle from prepare()
  1031 + * @param array $data numeric array containing the
  1032 + * data to insert into the query
  1033 + *
  1034 + * @return mixed DB_OK or DB_Error
  1035 + *
  1036 + * @see DB_common::prepare(), DB_common::execute()
  1037 + * @access public
  1038 + */
  1039 + function executeMultiple($stmt, $data)
  1040 + {
  1041 + foreach ($data as $value) {
  1042 + $res =& $this->execute($stmt, $value);
  1043 + if (DB::isError($res)) {
  1044 + return $res;
  1045 + }
  1046 + }
  1047 + return DB_OK;
  1048 + }
  1049 +
  1050 + // }}}
  1051 + // {{{ freePrepared()
  1052 +
  1053 + /**
  1054 + * Free the resource used in a prepared query
  1055 + *
  1056 + * @param $stmt The resurce returned by the prepare() function
  1057 + * @see DB_common::prepare()
  1058 + */
  1059 + function freePrepared($stmt)
  1060 + {
  1061 + $stmt = (int)$stmt;
  1062 + // Free the internal prepared vars
  1063 + if (isset($this->prepare_tokens[$stmt])) {
  1064 + unset($this->prepare_tokens[$stmt]);
  1065 + unset($this->prepare_types[$stmt]);
  1066 + unset($this->prepared_queries[$stmt]);
  1067 + return true;
  1068 + }
  1069 + return false;
  1070 + }
  1071 +
  1072 + // }}}
  1073 + // {{{ modifyQuery()
  1074 +
  1075 + /**
  1076 + * This method is used by backends to alter queries for various
  1077 + * reasons
  1078 + *
  1079 + * It is defined here to assure that all implementations
  1080 + * have this method defined.
  1081 + *
  1082 + * @param string $query query to modify
  1083 + *
  1084 + * @return the new (modified) query
  1085 + *
  1086 + * @access private
  1087 + */
  1088 + function modifyQuery($query) {
  1089 + return $query;
  1090 + }
  1091 +
  1092 + // }}}
  1093 + // {{{ modifyLimitQuery()
  1094 +
  1095 + /**
  1096 + * This method is used by backends to alter limited queries
  1097 + *
  1098 + * @param string $query query to modify
  1099 + * @param integer $from the row to start to fetching
  1100 + * @param integer $count the numbers of rows to fetch
  1101 + *
  1102 + * @return the new (modified) query
  1103 + *
  1104 + * @access private
  1105 + */
  1106 + function modifyLimitQuery($query, $from, $count, $params = array())
  1107 + {
  1108 + return $query;
  1109 + }
  1110 +
  1111 + // }}}
  1112 + // {{{ query()
  1113 +
  1114 + /**
  1115 + * Send a query to the database and return any results with a
  1116 + * DB_result object
  1117 + *
  1118 + * The query string can be either a normal statement to be sent directly
  1119 + * to the server OR if <var>$params</var> are passed the query can have
  1120 + * placeholders and it will be passed through prepare() and execute().
  1121 + *
  1122 + * @param string $query the SQL query or the statement to prepare
  1123 + * @param mixed $params array, string or numeric data to be used in
  1124 + * execution of the statement. Quantity of items
  1125 + * passed must match quantity of placeholders in
  1126 + * query: meaning 1 placeholder for non-array
  1127 + * parameters or 1 placeholder per array element.
  1128 + *
  1129 + * @return mixed a DB_result object or DB_OK on success, a DB
  1130 + * error on failure
  1131 + *
  1132 + * @see DB_result, DB_common::prepare(), DB_common::execute()
  1133 + * @access public
  1134 + */
  1135 + function &query($query, $params = array())
  1136 + {
  1137 + if (sizeof($params) > 0) {
  1138 + $sth = $this->prepare($query);
  1139 + if (DB::isError($sth)) {
  1140 + return $sth;
  1141 + }
  1142 + $ret =& $this->execute($sth, $params);
  1143 + $this->freePrepared($sth);
  1144 + return $ret;
  1145 + } else {
  1146 + $result = $this->simpleQuery($query);
  1147 + if (DB::isError($result) || $result === DB_OK) {
  1148 + return $result;
  1149 + } else {
  1150 + $tmp =& new DB_result($this, $result);
  1151 + return $tmp;
  1152 + }
  1153 + }
  1154 + }
  1155 +
  1156 + // }}}
  1157 + // {{{ limitQuery()
  1158 +
  1159 + /**
  1160 + * Generates a limited query
  1161 + *
  1162 + * @param string $query query
  1163 + * @param integer $from the row to start to fetching
  1164 + * @param integer $count the numbers of rows to fetch
  1165 + * @param mixed $params array, string or numeric data to be used in
  1166 + * execution of the statement. Quantity of items
  1167 + * passed must match quantity of placeholders in
  1168 + * query: meaning 1 placeholder for non-array
  1169 + * parameters or 1 placeholder per array element.
  1170 + *
  1171 + * @return mixed a DB_Result object, DB_OK or a DB_Error
  1172 + *
  1173 + * @access public
  1174 + */
  1175 + function &limitQuery($query, $from, $count, $params = array())
  1176 + {
  1177 + $query = $this->modifyLimitQuery($query, $from, $count, $params);
  1178 + if (DB::isError($query)){
  1179 + return $query;
  1180 + }
  1181 + $result =& $this->query($query, $params);
  1182 + if (is_a($result, 'DB_result')) {
  1183 + $result->setOption('limit_from', $from);
  1184 + $result->setOption('limit_count', $count);
  1185 + }
  1186 + return $result;
  1187 + }
  1188 +
  1189 + // }}}
  1190 + // {{{ getOne()
  1191 +
  1192 + /**
  1193 + * Fetch the first column of the first row of data returned from
  1194 + * a query
  1195 + *
  1196 + * Takes care of doing the query and freeing the results when finished.
  1197 + *
  1198 + * @param string $query the SQL query
  1199 + * @param mixed $params array, string or numeric data to be used in
  1200 + * execution of the statement. Quantity of items
  1201 + * passed must match quantity of placeholders in
  1202 + * query: meaning 1 placeholder for non-array
  1203 + * parameters or 1 placeholder per array element.
  1204 + *
  1205 + * @return mixed the returned value of the query. DB_Error on failure.
  1206 + *
  1207 + * @access public
  1208 + */
  1209 + function &getOne($query, $params = array())
  1210 + {
  1211 + settype($params, 'array');
  1212 + if (sizeof($params) > 0) {
  1213 + $sth = $this->prepare($query);
  1214 + if (DB::isError($sth)) {
  1215 + return $sth;
  1216 + }
  1217 + $res =& $this->execute($sth, $params);
  1218 + $this->freePrepared($sth);
  1219 + } else {
  1220 + $res =& $this->query($query);
  1221 + }
  1222 +
  1223 + if (DB::isError($res)) {
  1224 + return $res;
  1225 + }
  1226 +
  1227 + $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED);
  1228 + $res->free();
  1229 +
  1230 + if ($err !== DB_OK) {
  1231 + return $err;
  1232 + }
  1233 +
  1234 + return $row[0];
  1235 + }
  1236 +
  1237 + // }}}
  1238 + // {{{ getRow()
  1239 +
  1240 + /**
  1241 + * Fetch the first row of data returned from a query
  1242 + *
  1243 + * Takes care of doing the query and freeing the results when finished.
  1244 + *
  1245 + * @param string $query the SQL query
  1246 + * @param array $params array to be used in execution of the statement.
  1247 + * Quantity of array elements must match quantity
  1248 + * of placeholders in query. This function does
  1249 + * NOT support scalars.
  1250 + * @param int $fetchmode the fetch mode to use
  1251 + *
  1252 + * @return array the first row of results as an array indexed from
  1253 + * 0, or a DB error code.
  1254 + *
  1255 + * @access public
  1256 + */
  1257 + function &getRow($query,
  1258 + $params = array(),
  1259 + $fetchmode = DB_FETCHMODE_DEFAULT)
  1260 + {
  1261 + // compat check, the params and fetchmode parameters used to
  1262 + // have the opposite order
  1263 + if (!is_array($params)) {
  1264 + if (is_array($fetchmode)) {
  1265 + if ($params === null) {
  1266 + $tmp = DB_FETCHMODE_DEFAULT;
  1267 + } else {
  1268 + $tmp = $params;
  1269 + }
  1270 + $params = $fetchmode;
  1271 + $fetchmode = $tmp;
  1272 + } elseif ($params !== null) {
  1273 + $fetchmode = $params;
  1274 + $params = array();
  1275 + }
  1276 + }
  1277 +
  1278 + if (sizeof($params) > 0) {
  1279 + $sth = $this->prepare($query);
  1280 + if (DB::isError($sth)) {
  1281 + return $sth;
  1282 + }
  1283 + $res =& $this->execute($sth, $params);
  1284 + $this->freePrepared($sth);
  1285 + } else {
  1286 + $res =& $this->query($query);
  1287 + }
  1288 +
  1289 + if (DB::isError($res)) {
  1290 + return $res;
  1291 + }
  1292 +
  1293 + $err = $res->fetchInto($row, $fetchmode);
  1294 +
  1295 + $res->free();
  1296 +
  1297 + if ($err !== DB_OK) {
  1298 + return $err;
  1299 + }
  1300 +
  1301 + return $row;
  1302 + }
  1303 +
  1304 + // }}}
  1305 + // {{{ getCol()
  1306 +
  1307 + /**
  1308 + * Fetch a single column from a result set and return it as an
  1309 + * indexed array
  1310 + *
  1311 + * @param string $query the SQL query
  1312 + * @param mixed $col which column to return (integer [column number,
  1313 + * starting at 0] or string [column name])
  1314 + * @param mixed $params array, string or numeric data to be used in
  1315 + * execution of the statement. Quantity of items
  1316 + * passed must match quantity of placeholders in
  1317 + * query: meaning 1 placeholder for non-array
  1318 + * parameters or 1 placeholder per array element.
  1319 + *
  1320 + * @return array an indexed array with the data from the first
  1321 + * row at index 0, or a DB error code
  1322 + *
  1323 + * @see DB_common::query()
  1324 + * @access public
  1325 + */
  1326 + function &getCol($query, $col = 0, $params = array())
  1327 + {
  1328 + settype($params, 'array');
  1329 + if (sizeof($params) > 0) {
  1330 + $sth = $this->prepare($query);
  1331 +
  1332 + if (DB::isError($sth)) {
  1333 + return $sth;
  1334 + }
  1335 +
  1336 + $res =& $this->execute($sth, $params);
  1337 + $this->freePrepared($sth);
  1338 + } else {
  1339 + $res =& $this->query($query);
  1340 + }
  1341 +
  1342 + if (DB::isError($res)) {
  1343 + return $res;
  1344 + }
  1345 +
  1346 + $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC;
  1347 +
  1348 + if (!is_array($row = $res->fetchRow($fetchmode))) {
  1349 + $ret = array();
  1350 + } else {
  1351 + if (!array_key_exists($col, $row)) {
  1352 + $ret =& $this->raiseError(DB_ERROR_NOSUCHFIELD);
  1353 + } else {
  1354 + $ret = array($row[$col]);
  1355 + while (is_array($row = $res->fetchRow($fetchmode))) {
  1356 + $ret[] = $row[$col];
  1357 + }
  1358 + }
  1359 + }
  1360 +
  1361 + $res->free();
  1362 +
  1363 + if (DB::isError($row)) {
  1364 + $ret = $row;
  1365 + }
  1366 +
  1367 + return $ret;
  1368 + }
  1369 +
  1370 + // }}}
  1371 + // {{{ getAssoc()
  1372 +
  1373 + /**
  1374 + * Fetch the entire result set of a query and return it as an
  1375 + * associative array using the first column as the key
  1376 + *
  1377 + * If the result set contains more than two columns, the value
  1378 + * will be an array of the values from column 2-n. If the result
  1379 + * set contains only two columns, the returned value will be a
  1380 + * scalar with the value of the second column (unless forced to an
  1381 + * array with the $force_array parameter). A DB error code is
  1382 + * returned on errors. If the result set contains fewer than two
  1383 + * columns, a DB_ERROR_TRUNCATED error is returned.
  1384 + *
  1385 + * For example, if the table "mytable" contains:
  1386 + *
  1387 + * <pre>
  1388 + * ID TEXT DATE
  1389 + * --------------------------------
  1390 + * 1 'one' 944679408
  1391 + * 2 'two' 944679408
  1392 + * 3 'three' 944679408
  1393 + * </pre>
  1394 + *
  1395 + * Then the call getAssoc('SELECT id,text FROM mytable') returns:
  1396 + * <pre>
  1397 + * array(
  1398 + * '1' => 'one',
  1399 + * '2' => 'two',
  1400 + * '3' => 'three',
  1401 + * )
  1402 + * </pre>
  1403 + *
  1404 + * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns:
  1405 + * <pre>
  1406 + * array(
  1407 + * '1' => array('one', '944679408'),
  1408 + * '2' => array('two', '944679408'),
  1409 + * '3' => array('three', '944679408')
  1410 + * )
  1411 + * </pre>
  1412 + *
  1413 + * If the more than one row occurs with the same value in the
  1414 + * first column, the last row overwrites all previous ones by
  1415 + * default. Use the $group parameter if you don't want to
  1416 + * overwrite like this. Example:
  1417 + *
  1418 + * <pre>
  1419 + * getAssoc('SELECT category,id,name FROM mytable', false, null,
  1420 + * DB_FETCHMODE_ASSOC, true) returns:
  1421 + *
  1422 + * array(
  1423 + * '1' => array(array('id' => '4', 'name' => 'number four'),
  1424 + * array('id' => '6', 'name' => 'number six')
  1425 + * ),
  1426 + * '9' => array(array('id' => '4', 'name' => 'number four'),
  1427 + * array('id' => '6', 'name' => 'number six')
  1428 + * )
  1429 + * )
  1430 + * </pre>
  1431 + *
  1432 + * Keep in mind that database functions in PHP usually return string
  1433 + * values for results regardless of the database's internal type.
  1434 + *
  1435 + * @param string $query the SQL query
  1436 + * @param boolean $force_array used only when the query returns
  1437 + * exactly two columns. If true, the values
  1438 + * of the returned array will be one-element
  1439 + * arrays instead of scalars.
  1440 + * @param mixed $params array, string or numeric data to be used in
  1441 + * execution of the statement. Quantity of items
  1442 + * passed must match quantity of placeholders in
  1443 + * query: meaning 1 placeholder for non-array
  1444 + * parameters or 1 placeholder per array element.
  1445 + * @param int $fetchmode the fetch mode to use
  1446 + * @param boolean $group if true, the values of the returned array
  1447 + * is wrapped in another array. If the same
  1448 + * key value (in the first column) repeats
  1449 + * itself, the values will be appended to
  1450 + * this array instead of overwriting the
  1451 + * existing values.
  1452 + *
  1453 + * @return array associative array with results from the query.
  1454 + * DB Error on failure.
  1455 + *
  1456 + * @access public
  1457 + */
  1458 + function &getAssoc($query, $force_array = false, $params = array(),
  1459 + $fetchmode = DB_FETCHMODE_DEFAULT, $group = false)
  1460 + {
  1461 + settype($params, 'array');
  1462 + if (sizeof($params) > 0) {
  1463 + $sth = $this->prepare($query);
  1464 +
  1465 + if (DB::isError($sth)) {
  1466 + return $sth;
  1467 + }
  1468 +
  1469 + $res =& $this->execute($sth, $params);
  1470 + $this->freePrepared($sth);
  1471 + } else {
  1472 + $res =& $this->query($query);
  1473 + }
  1474 +
  1475 + if (DB::isError($res)) {
  1476 + return $res;
  1477 + }
  1478 + if ($fetchmode == DB_FETCHMODE_DEFAULT) {
  1479 + $fetchmode = $this->fetchmode;
  1480 + }
  1481 + $cols = $res->numCols();
  1482 +
  1483 + if ($cols < 2) {
  1484 + $tmp =& $this->raiseError(DB_ERROR_TRUNCATED);
  1485 + return $tmp;
  1486 + }
  1487 +
  1488 + $results = array();
  1489 +
  1490 + if ($cols > 2 || $force_array) {
  1491 + // return array values
  1492 + // XXX this part can be optimized
  1493 + if ($fetchmode == DB_FETCHMODE_ASSOC) {
  1494 + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) {
  1495 + reset($row);
  1496 + $key = current($row);
  1497 + unset($row[key($row)]);
  1498 + if ($group) {
  1499 + $results[$key][] = $row;
  1500 + } else {
  1501 + $results[$key] = $row;
  1502 + }
  1503 + }
  1504 + } elseif ($fetchmode == DB_FETCHMODE_OBJECT) {
  1505 + while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) {
  1506 + $arr = get_object_vars($row);
  1507 + $key = current($arr);
  1508 + if ($group) {
  1509 + $results[$key][] = $row;
  1510 + } else {
  1511 + $results[$key] = $row;
  1512 + }
  1513 + }
  1514 + } else {
  1515 + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
  1516 + // we shift away the first element to get
  1517 + // indices running from 0 again
  1518 + $key = array_shift($row);
  1519 + if ($group) {
  1520 + $results[$key][] = $row;
  1521 + } else {
  1522 + $results[$key] = $row;
  1523 + }
  1524 + }
  1525 + }
  1526 + if (DB::isError($row)) {
  1527 + $results = $row;
  1528 + }
  1529 + } else {
  1530 + // return scalar values
  1531 + while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
  1532 + if ($group) {
  1533 + $results[$row[0]][] = $row[1];
  1534 + } else {
  1535 + $results[$row[0]] = $row[1];
  1536 + }
  1537 + }
  1538 + if (DB::isError($row)) {
  1539 + $results = $row;
  1540 + }
  1541 + }
  1542 +
  1543 + $res->free();
  1544 +
  1545 + return $results;
  1546 + }
  1547 +
  1548 + // }}}
  1549 + // {{{ getAll()
  1550 +
  1551 + /**
  1552 + * Fetch all the rows returned from a query
  1553 + *
  1554 + * @param string $query the SQL query
  1555 + * @param array $params array to be used in execution of the statement.
  1556 + * Quantity of array elements must match quantity
  1557 + * of placeholders in query. This function does
  1558 + * NOT support scalars.
  1559 + * @param int $fetchmode the fetch mode to use
  1560 + *
  1561 + * @return array an nested array. DB error on failure.
  1562 + *
  1563 + * @access public
  1564 + */
  1565 + function &getAll($query,
  1566 + $params = array(),
  1567 + $fetchmode = DB_FETCHMODE_DEFAULT)
  1568 + {
  1569 + // compat check, the params and fetchmode parameters used to
  1570 + // have the opposite order
  1571 + if (!is_array($params)) {
  1572 + if (is_array($fetchmode)) {
  1573 + if ($params === null) {
  1574 + $tmp = DB_FETCHMODE_DEFAULT;
  1575 + } else {
  1576 + $tmp = $params;
  1577 + }
  1578 + $params = $fetchmode;
  1579 + $fetchmode = $tmp;
  1580 + } elseif ($params !== null) {
  1581 + $fetchmode = $params;
  1582 + $params = array();
  1583 + }
  1584 + }
  1585 +
  1586 + if (sizeof($params) > 0) {
  1587 + $sth = $this->prepare($query);
  1588 +
  1589 + if (DB::isError($sth)) {
  1590 + return $sth;
  1591 + }
  1592 +
  1593 + $res =& $this->execute($sth, $params);
  1594 + $this->freePrepared($sth);
  1595 + } else {
  1596 + $res =& $this->query($query);
  1597 + }
  1598 +
  1599 + if (DB::isError($res) || $res === DB_OK) {
  1600 + return $res;
  1601 + }
  1602 +
  1603 + $results = array();
  1604 + while (DB_OK === $res->fetchInto($row, $fetchmode)) {
  1605 + if ($fetchmode & DB_FETCHMODE_FLIPPED) {
  1606 + foreach ($row as $key => $val) {
  1607 + $results[$key][] = $val;
  1608 + }
  1609 + } else {
  1610 + $results[] = $row;
  1611 + }
  1612 + }
  1613 +
  1614 + $res->free();
  1615 +
  1616 + if (DB::isError($row)) {
  1617 + $tmp =& $this->raiseError($row);
  1618 + return $tmp;
  1619 + }
  1620 + return $results;
  1621 + }
  1622 +
  1623 + // }}}
  1624 + // {{{ autoCommit()
  1625 +
  1626 + /**
  1627 + * enable automatic Commit
  1628 + *
  1629 + * @param boolean $onoff
  1630 + * @return mixed DB_Error
  1631 + *
  1632 + * @access public
  1633 + */
  1634 + function autoCommit($onoff=false)
  1635 + {
  1636 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1637 + }
  1638 +
  1639 + // }}}
  1640 + // {{{ commit()
  1641 +
  1642 + /**
  1643 + * starts a Commit
  1644 + *
  1645 + * @return mixed DB_Error
  1646 + *
  1647 + * @access public
  1648 + */
  1649 + function commit()
  1650 + {
  1651 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1652 + }
  1653 +
  1654 + // }}}
  1655 + // {{{ rollback()
  1656 +
  1657 + /**
  1658 + * starts a rollback
  1659 + *
  1660 + * @return mixed DB_Error
  1661 + *
  1662 + * @access public
  1663 + */
  1664 + function rollback()
  1665 + {
  1666 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1667 + }
  1668 +
  1669 + // }}}
  1670 + // {{{ numRows()
  1671 +
  1672 + /**
  1673 + * Returns the number of rows in a result object
  1674 + *
  1675 + * @param object DB_Result the result object to check
  1676 + *
  1677 + * @return mixed DB_Error or the number of rows
  1678 + *
  1679 + * @access public
  1680 + */
  1681 + function numRows($result)
  1682 + {
  1683 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1684 + }
  1685 +
  1686 + // }}}
  1687 + // {{{ affectedRows()
  1688 +
  1689 + /**
  1690 + * Returns the affected rows of a query
  1691 + *
  1692 + * @return mixed DB_Error or number of rows
  1693 + *
  1694 + * @access public
  1695 + */
  1696 + function affectedRows()
  1697 + {
  1698 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1699 + }
  1700 +
  1701 + // }}}
  1702 + // {{{ errorNative()
  1703 +
  1704 + /**
  1705 + * Returns an errormessage, provides by the database
  1706 + *
  1707 + * @return mixed DB_Error or message
  1708 + *
  1709 + * @access public
  1710 + */
  1711 + function errorNative()
  1712 + {
  1713 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1714 + }
  1715 +
  1716 + // }}}
  1717 + // {{{ getSequenceName()
  1718 +
  1719 + /**
  1720 + * Generate the name used inside the database for a sequence
  1721 + *
  1722 + * The createSequence() docblock contains notes about storing sequence
  1723 + * names.
  1724 + *
  1725 + * @param string $sqn the sequence's public name
  1726 + *
  1727 + * @return string the sequence's name in the backend
  1728 + *
  1729 + * @see DB_common::createSequence(), DB_common::dropSequence(),
  1730 + * DB_common::nextID(), DB_common::setOption()
  1731 + * @access private
  1732 + */
  1733 + function getSequenceName($sqn)
  1734 + {
  1735 + return sprintf($this->getOption('seqname_format'),
  1736 + preg_replace('/[^a-z0-9_.]/i', '_', $sqn));
  1737 + }
  1738 +
  1739 + // }}}
  1740 + // {{{ nextId()
  1741 +
  1742 + /**
  1743 + * Returns the next free id in a sequence
  1744 + *
  1745 + * @param string $seq_name name of the sequence
  1746 + * @param boolean $ondemand when true, the seqence is automatically
  1747 + * created if it does not exist
  1748 + *
  1749 + * @return int the next id number in the sequence. DB_Error if problem.
  1750 + *
  1751 + * @see DB_common::createSequence(), DB_common::dropSequence(),
  1752 + * DB_common::getSequenceName()
  1753 + * @access public
  1754 + */
  1755 + function nextId($seq_name, $ondemand = true)
  1756 + {
  1757 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1758 + }
  1759 +
  1760 + // }}}
  1761 + // {{{ createSequence()
  1762 +
  1763 + /**
  1764 + * Creates a new sequence
  1765 + *
  1766 + * The name of a given sequence is determined by passing the string
  1767 + * provided in the <var>$seq_name</var> argument through PHP's sprintf()
  1768 + * function using the value from the <var>seqname_format</var> option as
  1769 + * the sprintf()'s format argument.
  1770 + *
  1771 + * <var>seqname_format</var> is set via setOption().
  1772 + *
  1773 + * @param string $seq_name name of the new sequence
  1774 + *
  1775 + * @return int DB_OK on success. A DB_Error object is returned if
  1776 + * problems arise.
  1777 + *
  1778 + * @see DB_common::dropSequence(), DB_common::getSequenceName(),
  1779 + * DB_common::nextID()
  1780 + * @access public
  1781 + */
  1782 + function createSequence($seq_name)
  1783 + {
  1784 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1785 + }
  1786 +
  1787 + // }}}
  1788 + // {{{ dropSequence()
  1789 +
  1790 + /**
  1791 + * Deletes a sequence
  1792 + *
  1793 + * @param string $seq_name name of the sequence to be deleted
  1794 + *
  1795 + * @return int DB_OK on success. DB_Error if problems.
  1796 + *
  1797 + * @see DB_common::createSequence(), DB_common::getSequenceName(),
  1798 + * DB_common::nextID()
  1799 + * @access public
  1800 + */
  1801 + function dropSequence($seq_name)
  1802 + {
  1803 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1804 + }
  1805 +
  1806 + // }}}
  1807 + // {{{ tableInfo()
  1808 +
  1809 + /**
  1810 + * Returns information about a table or a result set
  1811 + *
  1812 + * The format of the resulting array depends on which <var>$mode</var>
  1813 + * you select. The sample output below is based on this query:
  1814 + * <pre>
  1815 + * SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
  1816 + * FROM tblFoo
  1817 + * JOIN tblBar ON tblFoo.fldId = tblBar.fldId
  1818 + * </pre>
  1819 + *
  1820 + * <ul>
  1821 + * <li>
  1822 + *
  1823 + * <kbd>null</kbd> (default)
  1824 + * <pre>
  1825 + * [0] => Array (
  1826 + * [table] => tblFoo
  1827 + * [name] => fldId
  1828 + * [type] => int
  1829 + * [len] => 11
  1830 + * [flags] => primary_key not_null
  1831 + * )
  1832 + * [1] => Array (
  1833 + * [table] => tblFoo
  1834 + * [name] => fldPhone
  1835 + * [type] => string
  1836 + * [len] => 20
  1837 + * [flags] =>
  1838 + * )
  1839 + * [2] => Array (
  1840 + * [table] => tblBar
  1841 + * [name] => fldId
  1842 + * [type] => int
  1843 + * [len] => 11
  1844 + * [flags] => primary_key not_null
  1845 + * )
  1846 + * </pre>
  1847 + *
  1848 + * </li><li>
  1849 + *
  1850 + * <kbd>DB_TABLEINFO_ORDER</kbd>
  1851 + *
  1852 + * <p>In addition to the information found in the default output,
  1853 + * a notation of the number of columns is provided by the
  1854 + * <samp>num_fields</samp> element while the <samp>order</samp>
  1855 + * element provides an array with the column names as the keys and
  1856 + * their location index number (corresponding to the keys in the
  1857 + * the default output) as the values.</p>
  1858 + *
  1859 + * <p>If a result set has identical field names, the last one is
  1860 + * used.</p>
  1861 + *
  1862 + * <pre>
  1863 + * [num_fields] => 3
  1864 + * [order] => Array (
  1865 + * [fldId] => 2
  1866 + * [fldTrans] => 1
  1867 + * )
  1868 + * </pre>
  1869 + *
  1870 + * </li><li>
  1871 + *
  1872 + * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>
  1873 + *
  1874 + * <p>Similar to <kbd>DB_TABLEINFO_ORDER</kbd> but adds more
  1875 + * dimensions to the array in which the table names are keys and
  1876 + * the field names are sub-keys. This is helpful for queries that
  1877 + * join tables which have identical field names.</p>
  1878 + *
  1879 + * <pre>
  1880 + * [num_fields] => 3
  1881 + * [ordertable] => Array (
  1882 + * [tblFoo] => Array (
  1883 + * [fldId] => 0
  1884 + * [fldPhone] => 1
  1885 + * )
  1886 + * [tblBar] => Array (
  1887 + * [fldId] => 2
  1888 + * )
  1889 + * )
  1890 + * </pre>
  1891 + *
  1892 + * </li>
  1893 + * </ul>
  1894 + *
  1895 + * The <samp>flags</samp> element contains a space separated list
  1896 + * of extra information about the field. This data is inconsistent
  1897 + * between DBMS's due to the way each DBMS works.
  1898 + * + <samp>primary_key</samp>
  1899 + * + <samp>unique_key</samp>
  1900 + * + <samp>multiple_key</samp>
  1901 + * + <samp>not_null</samp>
  1902 + *
  1903 + * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp>
  1904 + * elements if <var>$result</var> is a table name. The following DBMS's
  1905 + * provide full information from queries:
  1906 + * + fbsql
  1907 + * + mysql
  1908 + *
  1909 + * If the 'portability' option has <samp>DB_PORTABILITY_LOWERCASE</samp>
  1910 + * turned on, the names of tables and fields will be lowercased.
  1911 + *
  1912 + * @param object|string $result DB_result object from a query or a
  1913 + * string containing the name of a table.
  1914 + * While this also accepts a query result
  1915 + * resource identifier, this behavior is
  1916 + * deprecated.
  1917 + * @param int $mode either unused or one of the tableInfo modes:
  1918 + * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>,
  1919 + * <kbd>DB_TABLEINFO_ORDER</kbd> or
  1920 + * <kbd>DB_TABLEINFO_FULL</kbd> (which does both).
  1921 + * These are bitwise, so the first two can be
  1922 + * combined using <kbd>|</kbd>.
  1923 + * @return array an associative array with the information requested.
  1924 + * If something goes wrong an error object is returned.
  1925 + *
  1926 + * @see DB_common::setOption()
  1927 + * @access public
  1928 + */
  1929 + function tableInfo($result, $mode = null)
  1930 + {
  1931 + /*
  1932 + * If the DB_<driver> class has a tableInfo() method, that one
  1933 + * overrides this one. But, if the driver doesn't have one,
  1934 + * this method runs and tells users about that fact.
  1935 + */
  1936 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  1937 + }
  1938 +
  1939 + // }}}
  1940 + // {{{ getTables()
  1941 +
  1942 + /**
  1943 + * @deprecated Deprecated in release 1.2 or lower
  1944 + */
  1945 + function getTables()
  1946 + {
  1947 + return $this->getListOf('tables');
  1948 + }
  1949 +
  1950 + // }}}
  1951 + // {{{ getListOf()
  1952 +
  1953 + /**
  1954 + * list internal DB info
  1955 + * valid values for $type are db dependent,
  1956 + * often: databases, users, view, functions
  1957 + *
  1958 + * @param string $type type of requested info
  1959 + *
  1960 + * @return mixed DB_Error or the requested data
  1961 + *
  1962 + * @access public
  1963 + */
  1964 + function getListOf($type)
  1965 + {
  1966 + $sql = $this->getSpecialQuery($type);
  1967 + if ($sql === null) { // No support
  1968 + return $this->raiseError(DB_ERROR_UNSUPPORTED);
  1969 + } elseif (is_int($sql) || DB::isError($sql)) { // Previous error
  1970 + return $this->raiseError($sql);
  1971 + } elseif (is_array($sql)) { // Already the result
  1972 + return $sql;
  1973 + }
  1974 + return $this->getCol($sql); // Launch this query
  1975 + }
  1976 +
  1977 + // }}}
  1978 + // {{{ getSpecialQuery()
  1979 +
  1980 + /**
  1981 + * Returns the query needed to get some backend info
  1982 + *
  1983 + * @param string $type What kind of info you want to retrieve
  1984 + *
  1985 + * @return string The SQL query string
  1986 + *
  1987 + * @access public
  1988 + */
  1989 + function getSpecialQuery($type)
  1990 + {
  1991 + return $this->raiseError(DB_ERROR_UNSUPPORTED);
  1992 + }
  1993 +
  1994 + // }}}
  1995 + // {{{ _rtrimArrayValues()
  1996 +
  1997 + /**
  1998 + * Right trim all strings in an array
  1999 + *
  2000 + * @param array $array the array to be trimmed (passed by reference)
  2001 + * @return void
  2002 + * @access private
  2003 + */
  2004 + function _rtrimArrayValues(&$array)
  2005 + {
  2006 + foreach ($array as $key => $value) {
  2007 + if (is_string($value)) {
  2008 + $array[$key] = rtrim($value);
  2009 + }
  2010 + }
  2011 + }
  2012 +
  2013 + // }}}
  2014 + // {{{ _convertNullArrayValuesToEmpty()
  2015 +
  2016 + /**
  2017 + * Convert all null values in an array to empty strings
  2018 + *
  2019 + * @param array $array the array to be de-nullified (passed by reference)
  2020 + * @return void
  2021 + * @access private
  2022 + */
  2023 + function _convertNullArrayValuesToEmpty(&$array)
  2024 + {
  2025 + foreach ($array as $key => $value) {
  2026 + if (is_null($value)) {
  2027 + $array[$key] = '';
  2028 + }
  2029 + }
  2030 + }
  2031 +
  2032 + // }}}
  2033 +}
  2034 +
  2035 +/*
  2036 + * Local variables:
  2037 + * tab-width: 4
  2038 + * c-basic-offset: 4
  2039 + * End:
  2040 + */
  2041 +
  2042 +?>
... ...
pear/DB/dbase.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Tomas V.V.Cox <cox@idecnet.com> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// XXX legend:
  24 +// You have to compile your PHP with the --enable-dbase option
  25 +
  26 +
  27 +require_once 'DB/common.php';
  28 +
  29 +/**
  30 + * Database independent query interface definition for PHP's dbase
  31 + * extension.
  32 + *
  33 + * @package DB
  34 + * @version $Id$
  35 + * @category Database
  36 + * @author Stig Bakken <ssb@php.net>
  37 + */
  38 +class DB_dbase extends DB_common
  39 +{
  40 + // {{{ properties
  41 +
  42 + var $connection;
  43 + var $phptype, $dbsyntax;
  44 + var $prepare_tokens = array();
  45 + var $prepare_types = array();
  46 + var $res_row = array();
  47 + var $result = 0;
  48 +
  49 + // }}}
  50 + // {{{ constructor
  51 +
  52 + /**
  53 + * DB_mysql constructor.
  54 + *
  55 + * @access public
  56 + */
  57 + function DB_dbase()
  58 + {
  59 + $this->DB_common();
  60 + $this->phptype = 'dbase';
  61 + $this->dbsyntax = 'dbase';
  62 + $this->features = array(
  63 + 'prepare' => false,
  64 + 'pconnect' => false,
  65 + 'transactions' => false,
  66 + 'limit' => false
  67 + );
  68 + $this->errorcode_map = array();
  69 + }
  70 +
  71 + // }}}
  72 + // {{{ connect()
  73 +
  74 + function connect($dsninfo, $persistent = false)
  75 + {
  76 + if (!DB::assertExtension('dbase')) {
  77 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  78 + }
  79 + $this->dsn = $dsninfo;
  80 +
  81 + $ini = ini_get('track_errors');
  82 + if ($ini) {
  83 + $conn = @dbase_open($dsninfo['database'], 0);
  84 + } else {
  85 + ini_set('track_errors', 1);
  86 + $conn = @dbase_open($dsninfo['database'], 0);
  87 + ini_set('track_errors', $ini);
  88 + }
  89 + if (!$conn) {
  90 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null,
  91 + null, null, strip_tags($php_errormsg));
  92 + }
  93 + $this->connection = $conn;
  94 + return DB_OK;
  95 + }
  96 +
  97 + // }}}
  98 + // {{{ disconnect()
  99 +
  100 + function disconnect()
  101 + {
  102 + $ret = @dbase_close($this->connection);
  103 + $this->connection = null;
  104 + return $ret;
  105 + }
  106 +
  107 + // }}}
  108 + // {{{ &query()
  109 +
  110 + function &query($query = null)
  111 + {
  112 + // emulate result resources
  113 + $this->res_row[(int)$this->result] = 0;
  114 + $tmp =& new DB_result($this, $this->result++);
  115 + return $tmp;
  116 + }
  117 +
  118 + // }}}
  119 + // {{{ fetchInto()
  120 +
  121 + /**
  122 + * Fetch a row and insert the data into an existing array.
  123 + *
  124 + * Formating of the array and the data therein are configurable.
  125 + * See DB_result::fetchInto() for more information.
  126 + *
  127 + * @param resource $result query result identifier
  128 + * @param array $arr (reference) array where data from the row
  129 + * should be placed
  130 + * @param int $fetchmode how the resulting array should be indexed
  131 + * @param int $rownum the row number to fetch
  132 + *
  133 + * @return mixed DB_OK on success, null when end of result set is
  134 + * reached or on failure
  135 + *
  136 + * @see DB_result::fetchInto()
  137 + * @access private
  138 + */
  139 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  140 + {
  141 + if ($rownum === null) {
  142 + $rownum = $this->res_row[(int)$result]++;
  143 + }
  144 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  145 + $arr = @dbase_get_record_with_names($this->connection, $rownum);
  146 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  147 + $arr = array_change_key_case($arr, CASE_LOWER);
  148 + }
  149 + } else {
  150 + $arr = @dbase_get_record($this->connection, $rownum);
  151 + }
  152 + if (!$arr) {
  153 + return null;
  154 + }
  155 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  156 + $this->_rtrimArrayValues($arr);
  157 + }
  158 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  159 + $this->_convertNullArrayValuesToEmpty($arr);
  160 + }
  161 + return DB_OK;
  162 + }
  163 +
  164 + // }}}
  165 + // {{{ numCols()
  166 +
  167 + function numCols($foo)
  168 + {
  169 + return @dbase_numfields($this->connection);
  170 + }
  171 +
  172 + // }}}
  173 + // {{{ numRows()
  174 +
  175 + function numRows($foo)
  176 + {
  177 + return @dbase_numrecords($this->connection);
  178 + }
  179 +
  180 + // }}}
  181 + // {{{ quoteSmart()
  182 +
  183 + /**
  184 + * Format input so it can be safely used in a query
  185 + *
  186 + * @param mixed $in data to be quoted
  187 + *
  188 + * @return mixed Submitted variable's type = returned value:
  189 + * + null = the string <samp>NULL</samp>
  190 + * + boolean = <samp>T</samp> if true or
  191 + * <samp>F</samp> if false. Use the <kbd>Logical</kbd>
  192 + * data type.
  193 + * + integer or double = the unquoted number
  194 + * + other (including strings and numeric strings) =
  195 + * the data with single quotes escaped by preceeding
  196 + * single quotes then the whole string is encapsulated
  197 + * between single quotes
  198 + *
  199 + * @internal
  200 + */
  201 + function quoteSmart($in)
  202 + {
  203 + if (is_int($in) || is_double($in)) {
  204 + return $in;
  205 + } elseif (is_bool($in)) {
  206 + return $in ? 'T' : 'F';
  207 + } elseif (is_null($in)) {
  208 + return 'NULL';
  209 + } else {
  210 + return "'" . $this->escapeSimple($in) . "'";
  211 + }
  212 + }
  213 +
  214 + // }}}
  215 +
  216 +}
  217 +
  218 +/*
  219 + * Local variables:
  220 + * tab-width: 4
  221 + * c-basic-offset: 4
  222 + * End:
  223 + */
  224 +
  225 +?>
... ...
pear/DB/fbsql.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Frank M. Kromann <frank@frontbase.com> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// XXX legend:
  24 +//
  25 +// XXX ERRORMSG: The error message from the fbsql function should
  26 +// be registered here.
  27 +//
  28 +// TODO/wishlist:
  29 +// longReadlen
  30 +// binmode
  31 +
  32 +
  33 +require_once 'DB/common.php';
  34 +
  35 +/**
  36 + * Database independent query interface definition for PHP's FrontBase
  37 + * extension.
  38 + *
  39 + * @package DB
  40 + * @version $Id$
  41 + * @category Database
  42 + * @author Frank M. Kromann <frank@frontbase.com>
  43 + */
  44 +class DB_fbsql extends DB_common
  45 +{
  46 + // {{{ properties
  47 +
  48 + var $connection;
  49 + var $phptype, $dbsyntax;
  50 + var $prepare_tokens = array();
  51 + var $prepare_types = array();
  52 + var $num_rows = array();
  53 + var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */
  54 +
  55 + // }}}
  56 + // {{{ constructor
  57 +
  58 + /**
  59 + * DB_fbsql constructor.
  60 + *
  61 + * @access public
  62 + */
  63 + function DB_fbsql()
  64 + {
  65 + $this->DB_common();
  66 + $this->phptype = 'fbsql';
  67 + $this->dbsyntax = 'fbsql';
  68 + $this->features = array(
  69 + 'prepare' => false,
  70 + 'pconnect' => true,
  71 + 'transactions' => true,
  72 + 'limit' => 'emulate'
  73 + );
  74 + $this->errorcode_map = array(
  75 + 1004 => DB_ERROR_CANNOT_CREATE,
  76 + 1005 => DB_ERROR_CANNOT_CREATE,
  77 + 1006 => DB_ERROR_CANNOT_CREATE,
  78 + 1007 => DB_ERROR_ALREADY_EXISTS,
  79 + 1008 => DB_ERROR_CANNOT_DROP,
  80 + 1046 => DB_ERROR_NODBSELECTED,
  81 + 1050 => DB_ERROR_ALREADY_EXISTS,
  82 + 1051 => DB_ERROR_NOSUCHTABLE,
  83 + 1054 => DB_ERROR_NOSUCHFIELD,
  84 + 1062 => DB_ERROR_ALREADY_EXISTS,
  85 + 1064 => DB_ERROR_SYNTAX,
  86 + 1100 => DB_ERROR_NOT_LOCKED,
  87 + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
  88 + 1146 => DB_ERROR_NOSUCHTABLE,
  89 + );
  90 + }
  91 +
  92 + // }}}
  93 + // {{{ connect()
  94 +
  95 + /**
  96 + * Connect to a database and log in as the specified user.
  97 + *
  98 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  99 + * @param $persistent (optional) whether the connection should
  100 + * be persistent
  101 + * @access public
  102 + * @return int DB_OK on success, a DB error on failure
  103 + */
  104 + function connect($dsninfo, $persistent = false)
  105 + {
  106 + if (!DB::assertExtension('fbsql')) {
  107 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  108 + }
  109 +
  110 + $this->dsn = $dsninfo;
  111 + $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
  112 +
  113 + $php_errormsg = '';
  114 + $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect';
  115 +
  116 + if ($dbhost && $dsninfo['username'] && $dsninfo['password']) {
  117 + $conn = @$connect_function($dbhost, $dsninfo['username'],
  118 + $dsninfo['password']);
  119 + } elseif ($dbhost && $dsninfo['username']) {
  120 + $conn = @$connect_function($dbhost, $dsninfo['username']);
  121 + } elseif ($dbhost) {
  122 + $conn = @$connect_function($dbhost);
  123 + } else {
  124 + $conn = false;
  125 + }
  126 + if (!$conn) {
  127 + if (empty($php_errormsg)) {
  128 + return $this->raiseError(DB_ERROR_CONNECT_FAILED);
  129 + } else {
  130 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  131 + null, $php_errormsg);
  132 + }
  133 + }
  134 +
  135 + if ($dsninfo['database']) {
  136 + if (!fbsql_select_db($dsninfo['database'], $conn)) {
  137 + return $this->fbsqlRaiseError();
  138 + }
  139 + }
  140 +
  141 + $this->connection = $conn;
  142 + return DB_OK;
  143 + }
  144 +
  145 + // }}}
  146 + // {{{ disconnect()
  147 +
  148 + /**
  149 + * Log out and disconnect from the database.
  150 + *
  151 + * @access public
  152 + *
  153 + * @return bool true on success, false if not connected.
  154 + */
  155 + function disconnect()
  156 + {
  157 + $ret = @fbsql_close($this->connection);
  158 + $this->connection = null;
  159 + return $ret;
  160 + }
  161 +
  162 + // }}}
  163 + // {{{ simpleQuery()
  164 +
  165 + /**
  166 + * Send a query to fbsql and return the results as a fbsql resource
  167 + * identifier.
  168 + *
  169 + * @param the SQL query
  170 + *
  171 + * @access public
  172 + *
  173 + * @return mixed returns a valid fbsql result for successful SELECT
  174 + * queries, DB_OK for other successful queries. A DB error is
  175 + * returned on failure.
  176 + */
  177 + function simpleQuery($query)
  178 + {
  179 + $this->last_query = $query;
  180 + $query = $this->modifyQuery($query);
  181 + $result = @fbsql_query("$query;", $this->connection);
  182 + if (!$result) {
  183 + return $this->fbsqlRaiseError();
  184 + }
  185 + // Determine which queries that should return data, and which
  186 + // should return an error code only.
  187 + if (DB::isManip($query)) {
  188 + return DB_OK;
  189 + }
  190 + $numrows = $this->numrows($result);
  191 + if (is_object($numrows)) {
  192 + return $numrows;
  193 + }
  194 + $this->num_rows[(int)$result] = $numrows;
  195 + return $result;
  196 + }
  197 +
  198 + // }}}
  199 + // {{{ nextResult()
  200 +
  201 + /**
  202 + * Move the internal fbsql result pointer to the next available result
  203 + *
  204 + * @param a valid fbsql result resource
  205 + *
  206 + * @access public
  207 + *
  208 + * @return true if a result is available otherwise return false
  209 + */
  210 + function nextResult($result)
  211 + {
  212 + return @fbsql_next_result($result);
  213 + }
  214 +
  215 + // }}}
  216 + // {{{ fetchInto()
  217 +
  218 + /**
  219 + * Fetch a row and insert the data into an existing array.
  220 + *
  221 + * Formating of the array and the data therein are configurable.
  222 + * See DB_result::fetchInto() for more information.
  223 + *
  224 + * @param resource $result query result identifier
  225 + * @param array $arr (reference) array where data from the row
  226 + * should be placed
  227 + * @param int $fetchmode how the resulting array should be indexed
  228 + * @param int $rownum the row number to fetch
  229 + *
  230 + * @return mixed DB_OK on success, null when end of result set is
  231 + * reached or on failure
  232 + *
  233 + * @see DB_result::fetchInto()
  234 + * @access private
  235 + */
  236 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  237 + {
  238 + if ($rownum !== null) {
  239 + if (!@fbsql_data_seek($result, $rownum)) {
  240 + return null;
  241 + }
  242 + }
  243 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  244 + $arr = @fbsql_fetch_array($result, FBSQL_ASSOC);
  245 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  246 + $arr = array_change_key_case($arr, CASE_LOWER);
  247 + }
  248 + } else {
  249 + $arr = @fbsql_fetch_row($result);
  250 + }
  251 + if (!$arr) {
  252 + $errno = @fbsql_errno($this->connection);
  253 + if (!$errno) {
  254 + return null;
  255 + }
  256 + return $this->fbsqlRaiseError($errno);
  257 + }
  258 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  259 + $this->_rtrimArrayValues($arr);
  260 + }
  261 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  262 + $this->_convertNullArrayValuesToEmpty($arr);
  263 + }
  264 + return DB_OK;
  265 + }
  266 +
  267 + // }}}
  268 + // {{{ freeResult()
  269 +
  270 + /**
  271 + * Free the internal resources associated with $result.
  272 + *
  273 + * @param $result fbsql result identifier
  274 + *
  275 + * @access public
  276 + *
  277 + * @return bool true on success, false if $result is invalid
  278 + */
  279 + function freeResult($result)
  280 + {
  281 + return @fbsql_free_result($result);
  282 + }
  283 +
  284 + // }}}
  285 + // {{{ autoCommit()
  286 +
  287 + function autoCommit($onoff=false)
  288 + {
  289 + if ($onoff) {
  290 + $this->query("SET COMMIT TRUE");
  291 + } else {
  292 + $this->query("SET COMMIT FALSE");
  293 + }
  294 + }
  295 +
  296 + // }}}
  297 + // {{{ commit()
  298 +
  299 + function commit()
  300 + {
  301 + @fbsql_commit();
  302 + }
  303 +
  304 + // }}}
  305 + // {{{ rollback()
  306 +
  307 + function rollback()
  308 + {
  309 + @fbsql_rollback();
  310 + }
  311 +
  312 + // }}}
  313 + // {{{ numCols()
  314 +
  315 + /**
  316 + * Get the number of columns in a result set.
  317 + *
  318 + * @param $result fbsql result identifier
  319 + *
  320 + * @access public
  321 + *
  322 + * @return int the number of columns per row in $result
  323 + */
  324 + function numCols($result)
  325 + {
  326 + $cols = @fbsql_num_fields($result);
  327 +
  328 + if (!$cols) {
  329 + return $this->fbsqlRaiseError();
  330 + }
  331 +
  332 + return $cols;
  333 + }
  334 +
  335 + // }}}
  336 + // {{{ numRows()
  337 +
  338 + /**
  339 + * Get the number of rows in a result set.
  340 + *
  341 + * @param $result fbsql result identifier
  342 + *
  343 + * @access public
  344 + *
  345 + * @return int the number of rows in $result
  346 + */
  347 + function numRows($result)
  348 + {
  349 + $rows = @fbsql_num_rows($result);
  350 + if ($rows === null) {
  351 + return $this->fbsqlRaiseError();
  352 + }
  353 + return $rows;
  354 + }
  355 +
  356 + // }}}
  357 + // {{{ affectedRows()
  358 +
  359 + /**
  360 + * Gets the number of rows affected by the data manipulation
  361 + * query. For other queries, this function returns 0.
  362 + *
  363 + * @return number of rows affected by the last query
  364 + */
  365 + function affectedRows()
  366 + {
  367 + if (DB::isManip($this->last_query)) {
  368 + $result = @fbsql_affected_rows($this->connection);
  369 + } else {
  370 + $result = 0;
  371 + }
  372 + return $result;
  373 + }
  374 +
  375 + // }}}
  376 + // {{{ errorNative()
  377 +
  378 + /**
  379 + * Get the native error code of the last error (if any) that
  380 + * occured on the current connection.
  381 + *
  382 + * @access public
  383 + *
  384 + * @return int native fbsql error code
  385 + */
  386 + function errorNative()
  387 + {
  388 + return @fbsql_errno($this->connection);
  389 + }
  390 +
  391 + // }}}
  392 + // {{{ nextId()
  393 +
  394 + /**
  395 + * Returns the next free id in a sequence
  396 + *
  397 + * @param string $seq_name name of the sequence
  398 + * @param boolean $ondemand when true, the seqence is automatically
  399 + * created if it does not exist
  400 + *
  401 + * @return int the next id number in the sequence. DB_Error if problem.
  402 + *
  403 + * @internal
  404 + * @see DB_common::nextID()
  405 + * @access public
  406 + */
  407 + function nextId($seq_name, $ondemand = true)
  408 + {
  409 + $seqname = $this->getSequenceName($seq_name);
  410 + $repeat = 0;
  411 + do {
  412 + $result = $this->query("INSERT INTO ${seqname} (id) VALUES (NULL)");
  413 + if ($ondemand && DB::isError($result) &&
  414 + $result->getCode() == DB_ERROR_NOSUCHTABLE) {
  415 + $repeat = 1;
  416 + $result = $this->createSequence($seq_name);
  417 + if (DB::isError($result)) {
  418 + return $result;
  419 + }
  420 + } else {
  421 + $repeat = 0;
  422 + }
  423 + } while ($repeat);
  424 + if (DB::isError($result)) {
  425 + return $result;
  426 + }
  427 + return @fbsql_insert_id($this->connection);
  428 + }
  429 +
  430 + /**
  431 + * Creates a new sequence
  432 + *
  433 + * @param string $seq_name name of the new sequence
  434 + *
  435 + * @return int DB_OK on success. A DB_Error object is returned if
  436 + * problems arise.
  437 + *
  438 + * @internal
  439 + * @see DB_common::createSequence()
  440 + * @access public
  441 + */
  442 + function createSequence($seq_name)
  443 + {
  444 + $seqname = $this->getSequenceName($seq_name);
  445 + return $this->query("CREATE TABLE ${seqname} ".
  446 + '(id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'.
  447 + ' PRIMARY KEY(id))');
  448 + }
  449 +
  450 + // }}}
  451 + // {{{ dropSequence()
  452 +
  453 + /**
  454 + * Deletes a sequence
  455 + *
  456 + * @param string $seq_name name of the sequence to be deleted
  457 + *
  458 + * @return int DB_OK on success. DB_Error if problems.
  459 + *
  460 + * @internal
  461 + * @see DB_common::dropSequence()
  462 + * @access public
  463 + */
  464 + function dropSequence($seq_name)
  465 + {
  466 + $seqname = $this->getSequenceName($seq_name);
  467 + return $this->query("DROP TABLE ${seqname} RESTRICT");
  468 + }
  469 +
  470 + // }}}
  471 + // {{{ modifyQuery()
  472 +
  473 + function modifyQuery($query)
  474 + {
  475 + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
  476 + // "DELETE FROM table" gives 0 affected rows in fbsql.
  477 + // This little hack lets you know how many rows were deleted.
  478 + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
  479 + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
  480 + 'DELETE FROM \1 WHERE 1=1', $query);
  481 + }
  482 + }
  483 + return $query;
  484 + }
  485 +
  486 + // }}}
  487 + // {{{ quoteSmart()
  488 +
  489 + /**
  490 + * Format input so it can be safely used in a query
  491 + *
  492 + * @param mixed $in data to be quoted
  493 + *
  494 + * @return mixed Submitted variable's type = returned value:
  495 + * + null = the string <samp>NULL</samp>
  496 + * + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
  497 + * + integer or double = the unquoted number
  498 + * + other (including strings and numeric strings) =
  499 + * the data escaped according to MySQL's settings
  500 + * then encapsulated between single quotes
  501 + *
  502 + * @internal
  503 + */
  504 + function quoteSmart($in)
  505 + {
  506 + if (is_int($in) || is_double($in)) {
  507 + return $in;
  508 + } elseif (is_bool($in)) {
  509 + return $in ? 'TRUE' : 'FALSE';
  510 + } elseif (is_null($in)) {
  511 + return 'NULL';
  512 + } else {
  513 + return "'" . $this->escapeSimple($in) . "'";
  514 + }
  515 + }
  516 +
  517 + // }}}
  518 + // {{{ fbsqlRaiseError()
  519 +
  520 + /**
  521 + * Gather information about an error, then use that info to create a
  522 + * DB error object and finally return that object.
  523 + *
  524 + * @param integer $errno PEAR error number (usually a DB constant) if
  525 + * manually raising an error
  526 + * @return object DB error object
  527 + * @see DB_common::errorCode()
  528 + * @see DB_common::raiseError()
  529 + */
  530 + function fbsqlRaiseError($errno = null)
  531 + {
  532 + if ($errno === null) {
  533 + $errno = $this->errorCode(fbsql_errno($this->connection));
  534 + }
  535 + return $this->raiseError($errno, null, null, null,
  536 + @fbsql_error($this->connection));
  537 + }
  538 +
  539 + // }}}
  540 + // {{{ tableInfo()
  541 +
  542 + /**
  543 + * Returns information about a table or a result set.
  544 + *
  545 + * @param object|string $result DB_result object from a query or a
  546 + * string containing the name of a table
  547 + * @param int $mode a valid tableInfo mode
  548 + * @return array an associative array with the information requested
  549 + * or an error object if something is wrong
  550 + * @access public
  551 + * @internal
  552 + * @see DB_common::tableInfo()
  553 + */
  554 + function tableInfo($result, $mode = null) {
  555 + if (isset($result->result)) {
  556 + /*
  557 + * Probably received a result object.
  558 + * Extract the result resource identifier.
  559 + */
  560 + $id = $result->result;
  561 + $got_string = false;
  562 + } elseif (is_string($result)) {
  563 + /*
  564 + * Probably received a table name.
  565 + * Create a result resource identifier.
  566 + */
  567 + $id = @fbsql_list_fields($this->dsn['database'],
  568 + $result, $this->connection);
  569 + $got_string = true;
  570 + } else {
  571 + /*
  572 + * Probably received a result resource identifier.
  573 + * Copy it.
  574 + * Deprecated. Here for compatibility only.
  575 + */
  576 + $id = $result;
  577 + $got_string = false;
  578 + }
  579 +
  580 + if (!is_resource($id)) {
  581 + return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
  582 + }
  583 +
  584 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  585 + $case_func = 'strtolower';
  586 + } else {
  587 + $case_func = 'strval';
  588 + }
  589 +
  590 + $count = @fbsql_num_fields($id);
  591 +
  592 + // made this IF due to performance (one if is faster than $count if's)
  593 + if (!$mode) {
  594 + for ($i=0; $i<$count; $i++) {
  595 + $res[$i]['table'] = $case_func(@fbsql_field_table($id, $i));
  596 + $res[$i]['name'] = $case_func(@fbsql_field_name($id, $i));
  597 + $res[$i]['type'] = @fbsql_field_type($id, $i);
  598 + $res[$i]['len'] = @fbsql_field_len($id, $i);
  599 + $res[$i]['flags'] = @fbsql_field_flags($id, $i);
  600 + }
  601 + } else { // full
  602 + $res["num_fields"]= $count;
  603 +
  604 + for ($i=0; $i<$count; $i++) {
  605 + $res[$i]['table'] = $case_func(@fbsql_field_table($id, $i));
  606 + $res[$i]['name'] = $case_func(@fbsql_field_name($id, $i));
  607 + $res[$i]['type'] = @fbsql_field_type($id, $i);
  608 + $res[$i]['len'] = @fbsql_field_len($id, $i);
  609 + $res[$i]['flags'] = @fbsql_field_flags($id, $i);
  610 +
  611 + if ($mode & DB_TABLEINFO_ORDER) {
  612 + $res['order'][$res[$i]['name']] = $i;
  613 + }
  614 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  615 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  616 + }
  617 + }
  618 + }
  619 +
  620 + // free the result only if we were called on a table
  621 + if ($got_string) {
  622 + @fbsql_free_result($id);
  623 + }
  624 + return $res;
  625 + }
  626 +
  627 + // }}}
  628 + // {{{ getSpecialQuery()
  629 +
  630 + /**
  631 + * Returns the query needed to get some backend info
  632 + * @param string $type What kind of info you want to retrieve
  633 + * @return string The SQL query string
  634 + */
  635 + function getSpecialQuery($type)
  636 + {
  637 + switch ($type) {
  638 + case 'tables':
  639 + return 'select "table_name" from information_schema.tables';
  640 + default:
  641 + return null;
  642 + }
  643 + }
  644 +
  645 + // }}}
  646 +}
  647 +
  648 +/*
  649 + * Local variables:
  650 + * tab-width: 4
  651 + * c-basic-offset: 4
  652 + * End:
  653 + */
  654 +
  655 +?>
... ...
pear/DB/ibase.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Sterling Hughes <sterling@php.net> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// Bugs:
  24 +// - If dbsyntax is not firebird, the limitQuery may fail
  25 +
  26 +
  27 +require_once 'DB/common.php';
  28 +
  29 +/**
  30 + * Database independent query interface definition for PHP's Interbase
  31 + * extension.
  32 + *
  33 + * @package DB
  34 + * @version $Id$
  35 + * @category Database
  36 + * @author Sterling Hughes <sterling@php.net>
  37 + */
  38 +class DB_ibase extends DB_common
  39 +{
  40 +
  41 + // {{{ properties
  42 +
  43 + var $connection;
  44 + var $phptype, $dbsyntax;
  45 + var $autocommit = 1;
  46 + var $manip_query = array();
  47 +
  48 + // }}}
  49 + // {{{ constructor
  50 +
  51 + function DB_ibase()
  52 + {
  53 + $this->DB_common();
  54 + $this->phptype = 'ibase';
  55 + $this->dbsyntax = 'ibase';
  56 + $this->features = array(
  57 + 'prepare' => true,
  58 + 'pconnect' => true,
  59 + 'transactions' => true,
  60 + 'limit' => false
  61 + );
  62 + // just a few of the tons of Interbase error codes listed in the
  63 + // Language Reference section of the Interbase manual
  64 + $this->errorcode_map = array(
  65 + -104 => DB_ERROR_SYNTAX,
  66 + -150 => DB_ERROR_ACCESS_VIOLATION,
  67 + -151 => DB_ERROR_ACCESS_VIOLATION,
  68 + -155 => DB_ERROR_NOSUCHTABLE,
  69 + 88 => DB_ERROR_NOSUCHTABLE,
  70 + -157 => DB_ERROR_NOSUCHFIELD,
  71 + -158 => DB_ERROR_VALUE_COUNT_ON_ROW,
  72 + -170 => DB_ERROR_MISMATCH,
  73 + -171 => DB_ERROR_MISMATCH,
  74 + -172 => DB_ERROR_INVALID,
  75 + -204 => DB_ERROR_INVALID,
  76 + -205 => DB_ERROR_NOSUCHFIELD,
  77 + -206 => DB_ERROR_NOSUCHFIELD,
  78 + -208 => DB_ERROR_INVALID,
  79 + -219 => DB_ERROR_NOSUCHTABLE,
  80 + -297 => DB_ERROR_CONSTRAINT,
  81 + -530 => DB_ERROR_CONSTRAINT,
  82 + -607 => DB_ERROR_NOSUCHTABLE,
  83 + -803 => DB_ERROR_CONSTRAINT,
  84 + -551 => DB_ERROR_ACCESS_VIOLATION,
  85 + -552 => DB_ERROR_ACCESS_VIOLATION,
  86 + -922 => DB_ERROR_NOSUCHDB,
  87 + -923 => DB_ERROR_CONNECT_FAILED,
  88 + -924 => DB_ERROR_CONNECT_FAILED
  89 + );
  90 + }
  91 +
  92 + // }}}
  93 + // {{{ connect()
  94 +
  95 + function connect($dsninfo, $persistent = false)
  96 + {
  97 + if (!DB::assertExtension('interbase')) {
  98 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  99 + }
  100 + $this->dsn = $dsninfo;
  101 + $dbhost = $dsninfo['hostspec'] ?
  102 + ($dsninfo['hostspec'] . ':' . $dsninfo['database']) :
  103 + $dsninfo['database'];
  104 +
  105 + $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect';
  106 +
  107 + $params = array();
  108 + $params[] = $dbhost;
  109 + $params[] = $dsninfo['username'] ? $dsninfo['username'] : null;
  110 + $params[] = $dsninfo['password'] ? $dsninfo['password'] : null;
  111 + $params[] = isset($dsninfo['charset']) ? $dsninfo['charset'] : null;
  112 + $params[] = isset($dsninfo['buffers']) ? $dsninfo['buffers'] : null;
  113 + $params[] = isset($dsninfo['dialect']) ? $dsninfo['dialect'] : null;
  114 + $params[] = isset($dsninfo['role']) ? $dsninfo['role'] : null;
  115 +
  116 + $conn = @call_user_func_array($connect_function, $params);
  117 + if (!$conn) {
  118 + return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED);
  119 + }
  120 + $this->connection = $conn;
  121 + if ($this->dsn['dbsyntax'] == 'firebird') {
  122 + $this->features['limit'] = 'alter';
  123 + }
  124 + return DB_OK;
  125 + }
  126 +
  127 + // }}}
  128 + // {{{ disconnect()
  129 +
  130 + function disconnect()
  131 + {
  132 + $ret = @ibase_close($this->connection);
  133 + $this->connection = null;
  134 + return $ret;
  135 + }
  136 +
  137 + // }}}
  138 + // {{{ simpleQuery()
  139 +
  140 + function simpleQuery($query)
  141 + {
  142 + $ismanip = DB::isManip($query);
  143 + $this->last_query = $query;
  144 + $query = $this->modifyQuery($query);
  145 + $result = @ibase_query($this->connection, $query);
  146 + if (!$result) {
  147 + return $this->ibaseRaiseError();
  148 + }
  149 + if ($this->autocommit && $ismanip) {
  150 + @ibase_commit($this->connection);
  151 + }
  152 + // Determine which queries that should return data, and which
  153 + // should return an error code only.
  154 + return $ismanip ? DB_OK : $result;
  155 + }
  156 +
  157 + // }}}
  158 + // {{{ modifyLimitQuery()
  159 +
  160 + /**
  161 + * This method is used by backends to alter limited queries
  162 + * Uses the new FIRST n SKIP n Firebird 1.0 syntax, so it is
  163 + * only compatible with Firebird 1.x
  164 + *
  165 + * @param string $query query to modify
  166 + * @param integer $from the row to start to fetching
  167 + * @param integer $count the numbers of rows to fetch
  168 + *
  169 + * @return the new (modified) query
  170 + * @author Ludovico Magnocavallo <ludo@sumatrasolutions.com>
  171 + * @access private
  172 + */
  173 + function modifyLimitQuery($query, $from, $count, $params = array())
  174 + {
  175 + if ($this->dsn['dbsyntax'] == 'firebird') {
  176 + //$from++; // SKIP starts from 1, ie SKIP 1 starts from the first record
  177 + // (cox) Seems that SKIP starts in 0
  178 + $query = preg_replace('/^\s*select\s(.*)$/is',
  179 + "SELECT FIRST $count SKIP $from $1", $query);
  180 + }
  181 + return $query;
  182 + }
  183 +
  184 + // }}}
  185 + // {{{ nextResult()
  186 +
  187 + /**
  188 + * Move the internal ibase result pointer to the next available result
  189 + *
  190 + * @param a valid fbsql result resource
  191 + *
  192 + * @access public
  193 + *
  194 + * @return true if a result is available otherwise return false
  195 + */
  196 + function nextResult($result)
  197 + {
  198 + return false;
  199 + }
  200 +
  201 + // }}}
  202 + // {{{ fetchInto()
  203 +
  204 + /**
  205 + * Fetch a row and insert the data into an existing array.
  206 + *
  207 + * Formating of the array and the data therein are configurable.
  208 + * See DB_result::fetchInto() for more information.
  209 + *
  210 + * @param resource $result query result identifier
  211 + * @param array $arr (reference) array where data from the row
  212 + * should be placed
  213 + * @param int $fetchmode how the resulting array should be indexed
  214 + * @param int $rownum the row number to fetch
  215 + *
  216 + * @return mixed DB_OK on success, null when end of result set is
  217 + * reached or on failure
  218 + *
  219 + * @see DB_result::fetchInto()
  220 + * @access private
  221 + */
  222 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  223 + {
  224 + if ($rownum !== null) {
  225 + return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
  226 + }
  227 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  228 + if (function_exists('ibase_fetch_assoc')) {
  229 + $arr = @ibase_fetch_assoc($result);
  230 + } else {
  231 + $arr = get_object_vars(ibase_fetch_object($result));
  232 + }
  233 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  234 + $arr = array_change_key_case($arr, CASE_LOWER);
  235 + }
  236 + } else {
  237 + $arr = @ibase_fetch_row($result);
  238 + }
  239 + if (!$arr) {
  240 + if ($errmsg = @ibase_errmsg()) {
  241 + return $this->ibaseRaiseError(null, $errmsg);
  242 + } else {
  243 + return null;
  244 + }
  245 + }
  246 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  247 + $this->_rtrimArrayValues($arr);
  248 + }
  249 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  250 + $this->_convertNullArrayValuesToEmpty($arr);
  251 + }
  252 + return DB_OK;
  253 + }
  254 +
  255 + // }}}
  256 + // {{{ freeResult()
  257 +
  258 + function freeResult($result)
  259 + {
  260 + return @ibase_free_result($result);
  261 + }
  262 +
  263 + // }}}
  264 + // {{{ freeQuery()
  265 +
  266 + function freeQuery($query)
  267 + {
  268 + @ibase_free_query($query);
  269 + return true;
  270 + }
  271 +
  272 + // }}}
  273 + // {{{ numCols()
  274 +
  275 + function numCols($result)
  276 + {
  277 + $cols = @ibase_num_fields($result);
  278 + if (!$cols) {
  279 + return $this->ibaseRaiseError();
  280 + }
  281 + return $cols;
  282 + }
  283 +
  284 + // }}}
  285 + // {{{ prepare()
  286 +
  287 + /**
  288 + * Prepares a query for multiple execution with execute().
  289 + *
  290 + * prepare() requires a generic query as string like <code>
  291 + * INSERT INTO numbers VALUES (?, ?, ?)
  292 + * </code>. The <kbd>?</kbd> characters are placeholders.
  293 + *
  294 + * Three types of placeholders can be used:
  295 + * + <kbd>?</kbd> a quoted scalar value, i.e. strings, integers
  296 + * + <kbd>!</kbd> value is inserted 'as is'
  297 + * + <kbd>&</kbd> requires a file name. The file's contents get
  298 + * inserted into the query (i.e. saving binary
  299 + * data in a db)
  300 + *
  301 + * Use backslashes to escape placeholder characters if you don't want
  302 + * them to be interpreted as placeholders. Example: <code>
  303 + * "UPDATE foo SET col=? WHERE col='over \& under'"
  304 + * </code>
  305 + *
  306 + * @param string $query query to be prepared
  307 + * @return mixed DB statement resource on success. DB_Error on failure.
  308 + */
  309 + function prepare($query)
  310 + {
  311 + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1,
  312 + PREG_SPLIT_DELIM_CAPTURE);
  313 + $token = 0;
  314 + $types = array();
  315 + $newquery = '';
  316 +
  317 + foreach ($tokens as $key => $val) {
  318 + switch ($val) {
  319 + case '?':
  320 + $types[$token++] = DB_PARAM_SCALAR;
  321 + break;
  322 + case '&':
  323 + $types[$token++] = DB_PARAM_OPAQUE;
  324 + break;
  325 + case '!':
  326 + $types[$token++] = DB_PARAM_MISC;
  327 + break;
  328 + default:
  329 + $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
  330 + $newquery .= $tokens[$key] . '?';
  331 + }
  332 + }
  333 +
  334 + $newquery = substr($newquery, 0, -1);
  335 + $this->last_query = $query;
  336 + $newquery = $this->modifyQuery($newquery);
  337 + $stmt = @ibase_prepare($this->connection, $newquery);
  338 + $this->prepare_types[(int)$stmt] = $types;
  339 + $this->manip_query[(int)$stmt] = DB::isManip($query);
  340 + return $stmt;
  341 + }
  342 +
  343 + // }}}
  344 + // {{{ execute()
  345 +
  346 + /**
  347 + * Executes a DB statement prepared with prepare().
  348 + *
  349 + * @param resource $stmt a DB statement resource returned from prepare()
  350 + * @param mixed $data array, string or numeric data to be used in
  351 + * execution of the statement. Quantity of items
  352 + * passed must match quantity of placeholders in
  353 + * query: meaning 1 for non-array items or the
  354 + * quantity of elements in the array.
  355 + * @return object a new DB_Result or a DB_Error when fail
  356 + * @see DB_ibase::prepare()
  357 + * @access public
  358 + */
  359 + function &execute($stmt, $data = array())
  360 + {
  361 + if (!is_array($data)) {
  362 + $data = array($data);
  363 + }
  364 +
  365 + $types =& $this->prepare_types[(int)$stmt];
  366 + if (count($types) != count($data)) {
  367 + $tmp =& $this->raiseError(DB_ERROR_MISMATCH);
  368 + return $tmp;
  369 + }
  370 +
  371 + $i = 0;
  372 + foreach ($data as $key => $value) {
  373 + if ($types[$i] == DB_PARAM_MISC) {
  374 + /*
  375 + * ibase doesn't seem to have the ability to pass a
  376 + * parameter along unchanged, so strip off quotes from start
  377 + * and end, plus turn two single quotes to one single quote,
  378 + * in order to avoid the quotes getting escaped by
  379 + * ibase and ending up in the database.
  380 + */
  381 + $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
  382 + $data[$key] = str_replace("''", "'", $data[$key]);
  383 + } elseif ($types[$i] == DB_PARAM_OPAQUE) {
  384 + $fp = @fopen($data[$key], 'rb');
  385 + if (!$fp) {
  386 + $tmp =& $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
  387 + return $tmp;
  388 + }
  389 + $data[$key] = fread($fp, filesize($data[$key]));
  390 + fclose($fp);
  391 + }
  392 + $i++;
  393 + }
  394 +
  395 + array_unshift($data, $stmt);
  396 +
  397 + $res = call_user_func_array('ibase_execute', $data);
  398 + if (!$res) {
  399 + $tmp =& $this->ibaseRaiseError();
  400 + return $tmp;
  401 + }
  402 + /* XXX need this?
  403 + if ($this->autocommit && $this->manip_query[(int)$stmt]) {
  404 + @ibase_commit($this->connection);
  405 + }*/
  406 + if ($this->manip_query[(int)$stmt]) {
  407 + $tmp = DB_OK;
  408 + } else {
  409 + $tmp =& new DB_result($this, $res);
  410 + }
  411 + return $tmp;
  412 + }
  413 +
  414 + /**
  415 + * Free the internal resources associated with a prepared query.
  416 + *
  417 + * @param $stmt The interbase_query resource type
  418 + *
  419 + * @return bool true on success, false if $result is invalid
  420 + */
  421 + function freePrepared($stmt)
  422 + {
  423 + if (!is_resource($stmt)) {
  424 + return false;
  425 + }
  426 + @ibase_free_query($stmt);
  427 + unset($this->prepare_tokens[(int)$stmt]);
  428 + unset($this->prepare_types[(int)$stmt]);
  429 + unset($this->manip_query[(int)$stmt]);
  430 + return true;
  431 + }
  432 +
  433 + // }}}
  434 + // {{{ autoCommit()
  435 +
  436 + function autoCommit($onoff = false)
  437 + {
  438 + $this->autocommit = $onoff ? 1 : 0;
  439 + return DB_OK;
  440 + }
  441 +
  442 + // }}}
  443 + // {{{ commit()
  444 +
  445 + function commit()
  446 + {
  447 + return @ibase_commit($this->connection);
  448 + }
  449 +
  450 + // }}}
  451 + // {{{ rollback()
  452 +
  453 + function rollback()
  454 + {
  455 + return @ibase_rollback($this->connection);
  456 + }
  457 +
  458 + // }}}
  459 + // {{{ transactionInit()
  460 +
  461 + function transactionInit($trans_args = 0)
  462 + {
  463 + return $trans_args ? @ibase_trans($trans_args, $this->connection) : @ibase_trans();
  464 + }
  465 +
  466 + // }}}
  467 + // {{{ nextId()
  468 +
  469 + /**
  470 + * Returns the next free id in a sequence
  471 + *
  472 + * @param string $seq_name name of the sequence
  473 + * @param boolean $ondemand when true, the seqence is automatically
  474 + * created if it does not exist
  475 + *
  476 + * @return int the next id number in the sequence. DB_Error if problem.
  477 + *
  478 + * @internal
  479 + * @see DB_common::nextID()
  480 + * @access public
  481 + */
  482 + function nextId($seq_name, $ondemand = true)
  483 + {
  484 + $sqn = strtoupper($this->getSequenceName($seq_name));
  485 + $repeat = 0;
  486 + do {
  487 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  488 + $result =& $this->query("SELECT GEN_ID(${sqn}, 1) "
  489 + . 'FROM RDB$GENERATORS '
  490 + . "WHERE RDB\$GENERATOR_NAME='${sqn}'");
  491 + $this->popErrorHandling();
  492 + if ($ondemand && DB::isError($result)) {
  493 + $repeat = 1;
  494 + $result = $this->createSequence($seq_name);
  495 + if (DB::isError($result)) {
  496 + return $result;
  497 + }
  498 + } else {
  499 + $repeat = 0;
  500 + }
  501 + } while ($repeat);
  502 + if (DB::isError($result)) {
  503 + return $this->raiseError($result);
  504 + }
  505 + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
  506 + $result->free();
  507 + return $arr[0];
  508 + }
  509 +
  510 + // }}}
  511 + // {{{ createSequence()
  512 +
  513 + /**
  514 + * Create the sequence
  515 + *
  516 + * @param string $seq_name the name of the sequence
  517 + * @return mixed DB_OK on success or DB error on error
  518 + * @access public
  519 + */
  520 + function createSequence($seq_name)
  521 + {
  522 + $sqn = strtoupper($this->getSequenceName($seq_name));
  523 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  524 + $result = $this->query("CREATE GENERATOR ${sqn}");
  525 + $this->popErrorHandling();
  526 +
  527 + return $result;
  528 + }
  529 +
  530 + // }}}
  531 + // {{{ dropSequence()
  532 +
  533 + /**
  534 + * Drop a sequence
  535 + *
  536 + * @param string $seq_name the name of the sequence
  537 + * @return mixed DB_OK on success or DB error on error
  538 + * @access public
  539 + */
  540 + function dropSequence($seq_name)
  541 + {
  542 + $sqn = strtoupper($this->getSequenceName($seq_name));
  543 + return $this->query('DELETE FROM RDB$GENERATORS '
  544 + . "WHERE RDB\$GENERATOR_NAME='${sqn}'");
  545 + }
  546 +
  547 + // }}}
  548 + // {{{ _ibaseFieldFlags()
  549 +
  550 + /**
  551 + * get the Flags of a Field
  552 + *
  553 + * @param string $field_name the name of the field
  554 + * @param string $table_name the name of the table
  555 + *
  556 + * @return string The flags of the field ("primary_key", "unique_key", "not_null"
  557 + * "default", "computed" and "blob" are supported)
  558 + * @access private
  559 + */
  560 + function _ibaseFieldFlags($field_name, $table_name)
  561 + {
  562 + $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE'
  563 + .' FROM RDB$INDEX_SEGMENTS I'
  564 + .' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME'
  565 + .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\''
  566 + .' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'';
  567 +
  568 + $result = @ibase_query($this->connection, $sql);
  569 + if (!$result) {
  570 + return $this->ibaseRaiseError();
  571 + }
  572 +
  573 + $flags = '';
  574 + if ($obj = @ibase_fetch_object($result)) {
  575 + @ibase_free_result($result);
  576 + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') {
  577 + $flags .= 'primary_key ';
  578 + }
  579 + if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') {
  580 + $flags .= 'unique_key ';
  581 + }
  582 + }
  583 +
  584 + $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,'
  585 + .' R.RDB$DEFAULT_SOURCE AS DSOURCE,'
  586 + .' F.RDB$FIELD_TYPE AS FTYPE,'
  587 + .' F.RDB$COMPUTED_SOURCE AS CSOURCE'
  588 + .' FROM RDB$RELATION_FIELDS R '
  589 + .' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME'
  590 + .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''
  591 + .' AND R.RDB$FIELD_NAME=\'' . $field_name . '\'';
  592 +
  593 + $result = @ibase_query($this->connection, $sql);
  594 + if (!$result) {
  595 + return $this->ibaseRaiseError();
  596 + }
  597 + if ($obj = @ibase_fetch_object($result)) {
  598 + @ibase_free_result($result);
  599 + if (isset($obj->NFLAG)) {
  600 + $flags .= 'not_null ';
  601 + }
  602 + if (isset($obj->DSOURCE)) {
  603 + $flags .= 'default ';
  604 + }
  605 + if (isset($obj->CSOURCE)) {
  606 + $flags .= 'computed ';
  607 + }
  608 + if (isset($obj->FTYPE) && $obj->FTYPE == 261) {
  609 + $flags .= 'blob ';
  610 + }
  611 + }
  612 +
  613 + return trim($flags);
  614 + }
  615 +
  616 + // }}}
  617 + // {{{ tableInfo()
  618 +
  619 + /**
  620 + * Returns information about a table or a result set.
  621 + *
  622 + * NOTE: only supports 'table' and 'flags' if <var>$result</var>
  623 + * is a table name.
  624 + *
  625 + * @param object|string $result DB_result object from a query or a
  626 + * string containing the name of a table
  627 + * @param int $mode a valid tableInfo mode
  628 + * @return array an associative array with the information requested
  629 + * or an error object if something is wrong
  630 + * @access public
  631 + * @internal
  632 + * @see DB_common::tableInfo()
  633 + */
  634 + function tableInfo($result, $mode = null)
  635 + {
  636 + if (isset($result->result)) {
  637 + /*
  638 + * Probably received a result object.
  639 + * Extract the result resource identifier.
  640 + */
  641 + $id = $result->result;
  642 + $got_string = false;
  643 + } elseif (is_string($result)) {
  644 + /*
  645 + * Probably received a table name.
  646 + * Create a result resource identifier.
  647 + */
  648 + $id = @ibase_query($this->connection,
  649 + "SELECT * FROM $result WHERE 1=0");
  650 + $got_string = true;
  651 + } else {
  652 + /*
  653 + * Probably received a result resource identifier.
  654 + * Copy it.
  655 + * Deprecated. Here for compatibility only.
  656 + */
  657 + $id = $result;
  658 + $got_string = false;
  659 + }
  660 +
  661 + if (!is_resource($id)) {
  662 + return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA);
  663 + }
  664 +
  665 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  666 + $case_func = 'strtolower';
  667 + } else {
  668 + $case_func = 'strval';
  669 + }
  670 +
  671 + $count = @ibase_num_fields($id);
  672 +
  673 + // made this IF due to performance (one if is faster than $count if's)
  674 + if (!$mode) {
  675 + for ($i=0; $i<$count; $i++) {
  676 + $info = @ibase_field_info($id, $i);
  677 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  678 + $res[$i]['name'] = $case_func($info['name']);
  679 + $res[$i]['type'] = $info['type'];
  680 + $res[$i]['len'] = $info['length'];
  681 + $res[$i]['flags'] = ($got_string) ? $this->_ibaseFieldFlags($info['name'], $result) : '';
  682 + }
  683 + } else { // full
  684 + $res['num_fields']= $count;
  685 +
  686 + for ($i=0; $i<$count; $i++) {
  687 + $info = @ibase_field_info($id, $i);
  688 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  689 + $res[$i]['name'] = $case_func($info['name']);
  690 + $res[$i]['type'] = $info['type'];
  691 + $res[$i]['len'] = $info['length'];
  692 + $res[$i]['flags'] = ($got_string) ? $this->_ibaseFieldFlags($info['name'], $result) : '';
  693 +
  694 + if ($mode & DB_TABLEINFO_ORDER) {
  695 + $res['order'][$res[$i]['name']] = $i;
  696 + }
  697 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  698 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  699 + }
  700 + }
  701 + }
  702 +
  703 + // free the result only if we were called on a table
  704 + if ($got_string) {
  705 + @ibase_free_result($id);
  706 + }
  707 + return $res;
  708 + }
  709 +
  710 + // }}}
  711 + // {{{ ibaseRaiseError()
  712 +
  713 + /**
  714 + * Gather information about an error, then use that info to create a
  715 + * DB error object and finally return that object.
  716 + *
  717 + * @param integer $db_errno PEAR error number (usually a DB constant) if
  718 + * manually raising an error
  719 + * @param string $native_errmsg text of error message if known
  720 + * @return object DB error object
  721 + * @see DB_common::errorCode()
  722 + * @see DB_common::raiseError()
  723 + */
  724 + function &ibaseRaiseError($db_errno = null, $native_errmsg = null)
  725 + {
  726 + if ($native_errmsg === null) {
  727 + $native_errmsg = @ibase_errmsg();
  728 + }
  729 + // memo for the interbase php module hackers: we need something similar
  730 + // to mysql_errno() to retrieve error codes instead of this ugly hack
  731 + if (preg_match('/^([^0-9\-]+)([0-9\-]+)\s+(.*)$/', $native_errmsg, $m)) {
  732 + $native_errno = (int)$m[2];
  733 + } else {
  734 + $native_errno = null;
  735 + }
  736 + // try to map the native error to the DB one
  737 + if ($db_errno === null) {
  738 + if ($native_errno) {
  739 + // try to interpret Interbase error code (that's why we need ibase_errno()
  740 + // in the interbase module to return the real error code)
  741 + switch ($native_errno) {
  742 + case -204:
  743 + if (is_int(strpos($m[3], 'Table unknown'))) {
  744 + $db_errno = DB_ERROR_NOSUCHTABLE;
  745 + }
  746 + break;
  747 + default:
  748 + $db_errno = $this->errorCode($native_errno);
  749 + }
  750 + } else {
  751 + $error_regexps = array(
  752 + '/[tT]able not found/' => DB_ERROR_NOSUCHTABLE,
  753 + '/[tT]able .* already exists/' => DB_ERROR_ALREADY_EXISTS,
  754 + '/validation error for column .* value "\*\*\* null/' => DB_ERROR_CONSTRAINT_NOT_NULL,
  755 + '/violation of [\w ]+ constraint/' => DB_ERROR_CONSTRAINT,
  756 + '/conversion error from string/' => DB_ERROR_INVALID_NUMBER,
  757 + '/no permission for/' => DB_ERROR_ACCESS_VIOLATION,
  758 + '/arithmetic exception, numeric overflow, or string truncation/' => DB_ERROR_DIVZERO
  759 + );
  760 + foreach ($error_regexps as $regexp => $code) {
  761 + if (preg_match($regexp, $native_errmsg)) {
  762 + $db_errno = $code;
  763 + $native_errno = null;
  764 + break;
  765 + }
  766 + }
  767 + }
  768 + }
  769 + $tmp =& $this->raiseError($db_errno, null, null, null, $native_errmsg);
  770 + return $tmp;
  771 + }
  772 +
  773 + // }}}
  774 +
  775 +}
  776 +
  777 +/*
  778 + * Local variables:
  779 + * tab-width: 4
  780 + * c-basic-offset: 4
  781 + * End:
  782 + */
  783 +
  784 +?>
... ...
pear/DB/ifx.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Tomas V.V.Cox <cox@idecnet.com> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// Legend:
  24 +// For more info on Informix errors see:
  25 +// http://www.informix.com/answers/english/ierrors.htm
  26 +//
  27 +// TODO:
  28 +// - set needed env Informix vars on connect
  29 +// - implement native prepare/execute
  30 +
  31 +
  32 +require_once 'DB/common.php';
  33 +
  34 +/**
  35 + * Database independent query interface definition for PHP's Informix
  36 + * extension.
  37 + *
  38 + * @package DB
  39 + * @version $Id$
  40 + * @category Database
  41 + * @author Tomas V.V.Cox <cox@idecnet.com>
  42 + */
  43 +class DB_ifx extends DB_common
  44 +{
  45 + // {{{ properties
  46 +
  47 + var $connection;
  48 + var $affected = 0;
  49 + var $dsn = array();
  50 + var $transaction_opcount = 0;
  51 + var $autocommit = true;
  52 + var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */
  53 +
  54 + // }}}
  55 + // {{{ constructor
  56 +
  57 + function DB_ifx()
  58 + {
  59 + $this->phptype = 'ifx';
  60 + $this->dbsyntax = 'ifx';
  61 + $this->features = array(
  62 + 'prepare' => false,
  63 + 'pconnect' => true,
  64 + 'transactions' => true,
  65 + 'limit' => 'emulate'
  66 + );
  67 + $this->errorcode_map = array(
  68 + '-201' => DB_ERROR_SYNTAX,
  69 + '-206' => DB_ERROR_NOSUCHTABLE,
  70 + '-217' => DB_ERROR_NOSUCHFIELD,
  71 + '-239' => DB_ERROR_CONSTRAINT,
  72 + '-253' => DB_ERROR_SYNTAX,
  73 + '-292' => DB_ERROR_CONSTRAINT_NOT_NULL,
  74 + '-310' => DB_ERROR_ALREADY_EXISTS,
  75 + '-329' => DB_ERROR_NODBSELECTED,
  76 + '-346' => DB_ERROR_CONSTRAINT,
  77 + '-386' => DB_ERROR_CONSTRAINT_NOT_NULL,
  78 + '-391' => DB_ERROR_CONSTRAINT_NOT_NULL,
  79 + '-554' => DB_ERROR_SYNTAX,
  80 + '-691' => DB_ERROR_CONSTRAINT,
  81 + '-703' => DB_ERROR_CONSTRAINT_NOT_NULL,
  82 + '-1204' => DB_ERROR_INVALID_DATE,
  83 + '-1205' => DB_ERROR_INVALID_DATE,
  84 + '-1206' => DB_ERROR_INVALID_DATE,
  85 + '-1209' => DB_ERROR_INVALID_DATE,
  86 + '-1210' => DB_ERROR_INVALID_DATE,
  87 + '-1212' => DB_ERROR_INVALID_DATE,
  88 + '-1213' => DB_ERROR_INVALID_NUMBER,
  89 + );
  90 + }
  91 +
  92 + // }}}
  93 + // {{{ connect()
  94 +
  95 + /**
  96 + * Connect to a database and log in as the specified user.
  97 + *
  98 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  99 + * @param $persistent (optional) whether the connection should
  100 + * be persistent
  101 + *
  102 + * @return int DB_OK on success, a DB error code on failure
  103 + */
  104 + function connect($dsninfo, $persistent = false)
  105 + {
  106 + if (!DB::assertExtension('informix') &&
  107 + !DB::assertExtension('Informix'))
  108 + {
  109 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  110 + }
  111 + $this->dsn = $dsninfo;
  112 + $dbhost = $dsninfo['hostspec'] ? '@' . $dsninfo['hostspec'] : '';
  113 + $dbname = $dsninfo['database'] ? $dsninfo['database'] . $dbhost : '';
  114 + $user = $dsninfo['username'] ? $dsninfo['username'] : '';
  115 + $pw = $dsninfo['password'] ? $dsninfo['password'] : '';
  116 +
  117 + $connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect';
  118 +
  119 + $this->connection = @$connect_function($dbname, $user, $pw);
  120 + if (!is_resource($this->connection)) {
  121 + return $this->ifxraiseError(DB_ERROR_CONNECT_FAILED);
  122 + }
  123 + return DB_OK;
  124 + }
  125 +
  126 + // }}}
  127 + // {{{ disconnect()
  128 +
  129 + /**
  130 + * Log out and disconnect from the database.
  131 + *
  132 + * @return bool true on success, false if not connected.
  133 + */
  134 + function disconnect()
  135 + {
  136 + $ret = @ifx_close($this->connection);
  137 + $this->connection = null;
  138 + return $ret;
  139 + }
  140 +
  141 + // }}}
  142 + // {{{ simpleQuery()
  143 +
  144 + /**
  145 + * Send a query to Informix and return the results as a
  146 + * Informix resource identifier.
  147 + *
  148 + * @param $query the SQL query
  149 + *
  150 + * @return int returns a valid Informix result for successful SELECT
  151 + * queries, DB_OK for other successful queries. A DB error code
  152 + * is returned on failure.
  153 + */
  154 + function simpleQuery($query)
  155 + {
  156 + $ismanip = DB::isManip($query);
  157 + $this->last_query = $query;
  158 + $this->affected = null;
  159 + if (preg_match('/(SELECT)/i', $query)) { //TESTME: Use !DB::isManip()?
  160 + // the scroll is needed for fetching absolute row numbers
  161 + // in a select query result
  162 + $result = @ifx_query($query, $this->connection, IFX_SCROLL);
  163 + } else {
  164 + if (!$this->autocommit && $ismanip) {
  165 + if ($this->transaction_opcount == 0) {
  166 + $result = @ifx_query('BEGIN WORK', $this->connection);
  167 + if (!$result) {
  168 + return $this->ifxraiseError();
  169 + }
  170 + }
  171 + $this->transaction_opcount++;
  172 + }
  173 + $result = @ifx_query($query, $this->connection);
  174 + }
  175 + if (!$result) {
  176 + return $this->ifxraiseError();
  177 + }
  178 + $this->affected = @ifx_affected_rows($result);
  179 + // Determine which queries should return data, and which
  180 + // should return an error code only.
  181 + if (preg_match('/(SELECT)/i', $query)) {
  182 + return $result;
  183 + }
  184 + // XXX Testme: free results inside a transaction
  185 + // may cause to stop it and commit the work?
  186 +
  187 + // Result has to be freed even with a insert or update
  188 + @ifx_free_result($result);
  189 +
  190 + return DB_OK;
  191 + }
  192 +
  193 + // }}}
  194 + // {{{ nextResult()
  195 +
  196 + /**
  197 + * Move the internal ifx result pointer to the next available result
  198 + *
  199 + * @param a valid fbsql result resource
  200 + *
  201 + * @access public
  202 + *
  203 + * @return true if a result is available otherwise return false
  204 + */
  205 + function nextResult($result)
  206 + {
  207 + return false;
  208 + }
  209 +
  210 + // }}}
  211 + // {{{ affectedRows()
  212 +
  213 + /**
  214 + * Gets the number of rows affected by the last query.
  215 + * if the last query was a select, returns 0.
  216 + *
  217 + * @return number of rows affected by the last query
  218 + */
  219 + function affectedRows()
  220 + {
  221 + if (DB::isManip($this->last_query)) {
  222 + return $this->affected;
  223 + } else {
  224 + return 0;
  225 + }
  226 +
  227 + }
  228 +
  229 + // }}}
  230 + // {{{ fetchInto()
  231 +
  232 + /**
  233 + * Fetch a row and insert the data into an existing array.
  234 + *
  235 + * Formating of the array and the data therein are configurable.
  236 + * See DB_result::fetchInto() for more information.
  237 + *
  238 + * @param resource $result query result identifier
  239 + * @param array $arr (reference) array where data from the row
  240 + * should be placed
  241 + * @param int $fetchmode how the resulting array should be indexed
  242 + * @param int $rownum the row number to fetch
  243 + *
  244 + * @return mixed DB_OK on success, null when end of result set is
  245 + * reached or on failure
  246 + *
  247 + * @see DB_result::fetchInto()
  248 + * @access private
  249 + */
  250 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  251 + {
  252 + if (($rownum !== null) && ($rownum < 0)) {
  253 + return null;
  254 + }
  255 + if ($rownum === null) {
  256 + /*
  257 + * Even though fetch_row() should return the next row if
  258 + * $rownum is null, it doesn't in all cases. Bug 598.
  259 + */
  260 + $rownum = 'NEXT';
  261 + } else {
  262 + // Index starts at row 1, unlike most DBMS's starting at 0.
  263 + $rownum++;
  264 + }
  265 + if (!$arr = @ifx_fetch_row($result, $rownum)) {
  266 + return null;
  267 + }
  268 + if ($fetchmode !== DB_FETCHMODE_ASSOC) {
  269 + $i=0;
  270 + $order = array();
  271 + foreach ($arr as $val) {
  272 + $order[$i++] = $val;
  273 + }
  274 + $arr = $order;
  275 + } elseif ($fetchmode == DB_FETCHMODE_ASSOC &&
  276 + $this->options['portability'] & DB_PORTABILITY_LOWERCASE)
  277 + {
  278 + $arr = array_change_key_case($arr, CASE_LOWER);
  279 + }
  280 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  281 + $this->_rtrimArrayValues($arr);
  282 + }
  283 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  284 + $this->_convertNullArrayValuesToEmpty($arr);
  285 + }
  286 + return DB_OK;
  287 + }
  288 +
  289 + // }}}
  290 + // {{{ numRows()
  291 +
  292 + function numRows($result)
  293 + {
  294 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  295 + }
  296 +
  297 + // }}}
  298 + // {{{ numCols()
  299 +
  300 + /**
  301 + * Get the number of columns in a result set.
  302 + *
  303 + * @param $result Informix result identifier
  304 + *
  305 + * @return int the number of columns per row in $result
  306 + */
  307 + function numCols($result)
  308 + {
  309 + if (!$cols = @ifx_num_fields($result)) {
  310 + return $this->ifxraiseError();
  311 + }
  312 + return $cols;
  313 + }
  314 +
  315 + // }}}
  316 + // {{{ freeResult()
  317 +
  318 + /**
  319 + * Free the internal resources associated with $result.
  320 + *
  321 + * @param $result Informix result identifier
  322 + *
  323 + * @return bool true on success, false if $result is invalid
  324 + */
  325 + function freeResult($result)
  326 + {
  327 + return @ifx_free_result($result);
  328 + }
  329 +
  330 + // }}}
  331 + // {{{ autoCommit()
  332 +
  333 + /**
  334 + * Enable/disable automatic commits
  335 + */
  336 + function autoCommit($onoff = true)
  337 + {
  338 + // XXX if $this->transaction_opcount > 0, we should probably
  339 + // issue a warning here.
  340 + $this->autocommit = $onoff ? true : false;
  341 + return DB_OK;
  342 + }
  343 +
  344 + // }}}
  345 + // {{{ commit()
  346 +
  347 + /**
  348 + * Commit the current transaction.
  349 + */
  350 + function commit()
  351 + {
  352 + if ($this->transaction_opcount > 0) {
  353 + $result = @ifx_query('COMMIT WORK', $this->connection);
  354 + $this->transaction_opcount = 0;
  355 + if (!$result) {
  356 + return $this->ifxRaiseError();
  357 + }
  358 + }
  359 + return DB_OK;
  360 + }
  361 +
  362 + // }}}
  363 + // {{{ rollback()
  364 +
  365 + /**
  366 + * Roll back (undo) the current transaction.
  367 + */
  368 + function rollback()
  369 + {
  370 + if ($this->transaction_opcount > 0) {
  371 + $result = @ifx_query('ROLLBACK WORK', $this->connection);
  372 + $this->transaction_opcount = 0;
  373 + if (!$result) {
  374 + return $this->ifxRaiseError();
  375 + }
  376 + }
  377 + return DB_OK;
  378 + }
  379 +
  380 + // }}}
  381 + // {{{ ifxraiseError()
  382 +
  383 + /**
  384 + * Gather information about an error, then use that info to create a
  385 + * DB error object and finally return that object.
  386 + *
  387 + * @param integer $errno PEAR error number (usually a DB constant) if
  388 + * manually raising an error
  389 + * @return object DB error object
  390 + * @see errorNative()
  391 + * @see errorCode()
  392 + * @see DB_common::raiseError()
  393 + */
  394 + function ifxraiseError($errno = null)
  395 + {
  396 + if ($errno === null) {
  397 + $errno = $this->errorCode(ifx_error());
  398 + }
  399 +
  400 + return $this->raiseError($errno, null, null, null,
  401 + $this->errorNative());
  402 + }
  403 +
  404 + // }}}
  405 + // {{{ errorCode()
  406 +
  407 + /**
  408 + * Map native error codes to DB's portable ones.
  409 + *
  410 + * Requires that the DB implementation's constructor fills
  411 + * in the <var>$errorcode_map</var> property.
  412 + *
  413 + * @param string $nativecode error code returned by the database
  414 + * @return int a portable DB error code, or DB_ERROR if this DB
  415 + * implementation has no mapping for the given error code.
  416 + */
  417 + function errorCode($nativecode)
  418 + {
  419 + if (ereg('SQLCODE=(.*)]', $nativecode, $match)) {
  420 + $code = $match[1];
  421 + if (isset($this->errorcode_map[$code])) {
  422 + return $this->errorcode_map[$code];
  423 + }
  424 + }
  425 + return DB_ERROR;
  426 + }
  427 +
  428 + // }}}
  429 + // {{{ errorNative()
  430 +
  431 + /**
  432 + * Get the native error message of the last error (if any) that
  433 + * occured on the current connection.
  434 + *
  435 + * @return int native Informix error code
  436 + */
  437 + function errorNative()
  438 + {
  439 + return @ifx_error() . ' ' . @ifx_errormsg();
  440 + }
  441 +
  442 + // }}}
  443 + // {{{ getSpecialQuery()
  444 +
  445 + /**
  446 + * Returns the query needed to get some backend info
  447 + * @param string $type What kind of info you want to retrieve
  448 + * @return string The SQL query string
  449 + */
  450 + function getSpecialQuery($type)
  451 + {
  452 + switch ($type) {
  453 + case 'tables':
  454 + return 'select tabname from systables where tabid >= 100';
  455 + default:
  456 + return null;
  457 + }
  458 + }
  459 +
  460 + // }}}
  461 + // {{{ tableInfo()
  462 +
  463 + /**
  464 + * Returns information about a table or a result set.
  465 + *
  466 + * NOTE: only supports 'table' if <var>$result</var> is a table name.
  467 + *
  468 + * If analyzing a query result and the result has duplicate field names,
  469 + * an error will be raised saying
  470 + * <samp>can't distinguish duplicate field names</samp>.
  471 + *
  472 + * @param object|string $result DB_result object from a query or a
  473 + * string containing the name of a table
  474 + * @param int $mode a valid tableInfo mode
  475 + * @return array an associative array with the information requested
  476 + * or an error object if something is wrong
  477 + * @access public
  478 + * @internal
  479 + * @since 1.6.0
  480 + * @see DB_common::tableInfo()
  481 + */
  482 + function tableInfo($result, $mode = null)
  483 + {
  484 + if (isset($result->result)) {
  485 + /*
  486 + * Probably received a result object.
  487 + * Extract the result resource identifier.
  488 + */
  489 + $id = $result->result;
  490 + $got_string = false;
  491 + } elseif (is_string($result)) {
  492 + /*
  493 + * Probably received a table name.
  494 + * Create a result resource identifier.
  495 + */
  496 + $id = @ifx_query("SELECT * FROM $result WHERE 1=0",
  497 + $this->connection);
  498 + $got_string = true;
  499 + } else {
  500 + /*
  501 + * Probably received a result resource identifier.
  502 + * Copy it.
  503 + */
  504 + $id = $result;
  505 + $got_string = false;
  506 + }
  507 +
  508 + if (!is_resource($id)) {
  509 + return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA);
  510 + }
  511 +
  512 + $flds = @ifx_fieldproperties($id);
  513 + $count = @ifx_num_fields($id);
  514 +
  515 + if (count($flds) != $count) {
  516 + return $this->raiseError("can't distinguish duplicate field names");
  517 + }
  518 +
  519 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  520 + $case_func = 'strtolower';
  521 + } else {
  522 + $case_func = 'strval';
  523 + }
  524 +
  525 + $i = 0;
  526 + // made this IF due to performance (one if is faster than $count if's)
  527 + if (!$mode) {
  528 + foreach ($flds as $key => $value) {
  529 + $props = explode(';', $value);
  530 +
  531 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  532 + $res[$i]['name'] = $case_func($key);
  533 + $res[$i]['type'] = $props[0];
  534 + $res[$i]['len'] = $props[1];
  535 + $res[$i]['flags'] = $props[4] == 'N' ? 'not_null' : '';
  536 + $i++;
  537 + }
  538 +
  539 + } else { // full
  540 + $res['num_fields'] = $count;
  541 +
  542 + foreach ($flds as $key => $value) {
  543 + $props = explode(';', $value);
  544 +
  545 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  546 + $res[$i]['name'] = $case_func($key);
  547 + $res[$i]['type'] = $props[0];
  548 + $res[$i]['len'] = $props[1];
  549 + $res[$i]['flags'] = $props[4] == 'N' ? 'not_null' : '';
  550 +
  551 + if ($mode & DB_TABLEINFO_ORDER) {
  552 + $res['order'][$res[$i]['name']] = $i;
  553 + }
  554 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  555 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  556 + }
  557 + $i++;
  558 + }
  559 + }
  560 +
  561 + // free the result only if we were called on a table
  562 + if ($got_string) {
  563 + @ifx_free_result($id);
  564 + }
  565 + return $res;
  566 + }
  567 +
  568 + // }}}
  569 +
  570 +}
  571 +
  572 +/*
  573 + * Local variables:
  574 + * tab-width: 4
  575 + * c-basic-offset: 4
  576 + * End:
  577 + */
  578 +
  579 +?>
... ...
pear/DB/msql.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Sterling Hughes <sterling@php.net> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +require_once 'DB/common.php';
  23 +
  24 +/**
  25 + * Database independent query interface definition for PHP's Mini-SQL
  26 + * extension.
  27 + *
  28 + * @package DB
  29 + * @version $Id$
  30 + * @category Database
  31 + * @author Sterling Hughes <sterling@php.net>
  32 + */
  33 +class DB_msql extends DB_common
  34 +{
  35 + // {{{ properties
  36 +
  37 + var $connection;
  38 + var $phptype, $dbsyntax;
  39 + var $prepare_tokens = array();
  40 + var $prepare_types = array();
  41 +
  42 + // }}}
  43 + // {{{ constructor
  44 +
  45 + function DB_msql()
  46 + {
  47 + $this->DB_common();
  48 + $this->phptype = 'msql';
  49 + $this->dbsyntax = 'msql';
  50 + $this->features = array(
  51 + 'prepare' => false,
  52 + 'pconnect' => true,
  53 + 'transactions' => false,
  54 + 'limit' => 'emulate'
  55 + );
  56 + }
  57 +
  58 + // }}}
  59 + // {{{ connect()
  60 +
  61 + function connect($dsninfo, $persistent = false)
  62 + {
  63 + if (!DB::assertExtension('msql')) {
  64 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  65 + }
  66 +
  67 + $this->dsn = $dsninfo;
  68 + $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
  69 +
  70 + $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect';
  71 +
  72 + if ($dbhost && $dsninfo['username'] && $dsninfo['password']) {
  73 + $conn = $connect_function($dbhost, $dsninfo['username'],
  74 + $dsninfo['password']);
  75 + } elseif ($dbhost && $dsninfo['username']) {
  76 + $conn = $connect_function($dbhost, $dsninfo['username']);
  77 + } else {
  78 + $conn = $connect_function($dbhost);
  79 + }
  80 + if (!$conn) {
  81 + $this->raiseError(DB_ERROR_CONNECT_FAILED);
  82 + }
  83 + if (!@msql_select_db($dsninfo['database'], $conn)){
  84 + return $this->raiseError(DB_ERROR_NODBSELECTED);
  85 + }
  86 + $this->connection = $conn;
  87 + return DB_OK;
  88 + }
  89 +
  90 + // }}}
  91 + // {{{ disconnect()
  92 +
  93 + function disconnect()
  94 + {
  95 + $ret = @msql_close($this->connection);
  96 + $this->connection = null;
  97 + return $ret;
  98 + }
  99 +
  100 + // }}}
  101 + // {{{ simpleQuery()
  102 +
  103 + function simpleQuery($query)
  104 + {
  105 + $this->last_query = $query;
  106 + $query = $this->modifyQuery($query);
  107 + $result = @msql_query($query, $this->connection);
  108 + if (!$result) {
  109 + return $this->raiseError();
  110 + }
  111 + // Determine which queries that should return data, and which
  112 + // should return an error code only.
  113 + return DB::isManip($query) ? DB_OK : $result;
  114 + }
  115 +
  116 +
  117 + // }}}
  118 + // {{{ nextResult()
  119 +
  120 + /**
  121 + * Move the internal msql result pointer to the next available result
  122 + *
  123 + * @param a valid fbsql result resource
  124 + *
  125 + * @access public
  126 + *
  127 + * @return true if a result is available otherwise return false
  128 + */
  129 + function nextResult($result)
  130 + {
  131 + return false;
  132 + }
  133 +
  134 + // }}}
  135 + // {{{ fetchInto()
  136 +
  137 + /**
  138 + * Fetch a row and insert the data into an existing array.
  139 + *
  140 + * Formating of the array and the data therein are configurable.
  141 + * See DB_result::fetchInto() for more information.
  142 + *
  143 + * @param resource $result query result identifier
  144 + * @param array $arr (reference) array where data from the row
  145 + * should be placed
  146 + * @param int $fetchmode how the resulting array should be indexed
  147 + * @param int $rownum the row number to fetch
  148 + *
  149 + * @return mixed DB_OK on success, null when end of result set is
  150 + * reached or on failure
  151 + *
  152 + * @see DB_result::fetchInto()
  153 + * @access private
  154 + */
  155 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  156 + {
  157 + if ($rownum !== null) {
  158 + if (!@msql_data_seek($result, $rownum)) {
  159 + return null;
  160 + }
  161 + }
  162 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  163 + $arr = @msql_fetch_array($result, MSQL_ASSOC);
  164 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  165 + $arr = array_change_key_case($arr, CASE_LOWER);
  166 + }
  167 + } else {
  168 + $arr = @msql_fetch_row($result);
  169 + }
  170 + if (!$arr) {
  171 + if ($error = @msql_error()) {
  172 + return $this->raiseError($error);
  173 + } else {
  174 + return null;
  175 + }
  176 + }
  177 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  178 + $this->_rtrimArrayValues($arr);
  179 + }
  180 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  181 + $this->_convertNullArrayValuesToEmpty($arr);
  182 + }
  183 + return DB_OK;
  184 + }
  185 +
  186 + // }}}
  187 + // {{{ freeResult()
  188 +
  189 + function freeResult($result)
  190 + {
  191 + return @msql_free_result($result);
  192 + }
  193 +
  194 + // }}}
  195 + // {{{ numCols()
  196 +
  197 + function numCols($result)
  198 + {
  199 + $cols = @msql_num_fields($result);
  200 + if (!$cols) {
  201 + return $this->raiseError();
  202 + }
  203 + return $cols;
  204 + }
  205 +
  206 + // }}}
  207 + // {{{ numRows()
  208 +
  209 + function numRows($result)
  210 + {
  211 + $rows = @msql_num_rows($result);
  212 + if (!$rows) {
  213 + return $this->raiseError();
  214 + }
  215 + return $rows;
  216 + }
  217 +
  218 + // }}}
  219 + // {{{ affected()
  220 +
  221 + /**
  222 + * Gets the number of rows affected by a query.
  223 + *
  224 + * @return number of rows affected by the last query
  225 + */
  226 + function affectedRows()
  227 + {
  228 + return @msql_affected_rows($this->connection);
  229 + }
  230 +
  231 + // }}}
  232 +
  233 +}
  234 +
  235 +/*
  236 + * Local variables:
  237 + * tab-width: 4
  238 + * c-basic-offset: 4
  239 + * End:
  240 + */
  241 +
  242 +?>
... ...
pear/DB/mssql.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Sterling Hughes <sterling@php.net> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +require_once 'DB/common.php';
  23 +
  24 +/**
  25 + * Database independent query interface definition for PHP's Microsoft SQL Server
  26 + * extension.
  27 + *
  28 + * @package DB
  29 + * @version $Id$
  30 + * @category Database
  31 + * @author Sterling Hughes <sterling@php.net>
  32 + */
  33 +class DB_mssql extends DB_common
  34 +{
  35 + // {{{ properties
  36 +
  37 + var $connection;
  38 + var $phptype, $dbsyntax;
  39 + var $prepare_tokens = array();
  40 + var $prepare_types = array();
  41 + var $transaction_opcount = 0;
  42 + var $autocommit = true;
  43 + var $_db = null;
  44 +
  45 + // }}}
  46 + // {{{ constructor
  47 +
  48 + function DB_mssql()
  49 + {
  50 + $this->DB_common();
  51 + $this->phptype = 'mssql';
  52 + $this->dbsyntax = 'mssql';
  53 + $this->features = array(
  54 + 'prepare' => false,
  55 + 'pconnect' => true,
  56 + 'transactions' => true,
  57 + 'limit' => 'emulate'
  58 + );
  59 + // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX
  60 + $this->errorcode_map = array(
  61 + 170 => DB_ERROR_SYNTAX,
  62 + 207 => DB_ERROR_NOSUCHFIELD,
  63 + 208 => DB_ERROR_NOSUCHTABLE,
  64 + 245 => DB_ERROR_INVALID_NUMBER,
  65 + 515 => DB_ERROR_CONSTRAINT_NOT_NULL,
  66 + 547 => DB_ERROR_CONSTRAINT,
  67 + 2627 => DB_ERROR_CONSTRAINT,
  68 + 2714 => DB_ERROR_ALREADY_EXISTS,
  69 + 3701 => DB_ERROR_NOSUCHTABLE,
  70 + 8134 => DB_ERROR_DIVZERO,
  71 + );
  72 + }
  73 +
  74 + // }}}
  75 + // {{{ connect()
  76 +
  77 + function connect($dsninfo, $persistent = false)
  78 + {
  79 + if (!DB::assertExtension('mssql') && !DB::assertExtension('sybase')
  80 + && !DB::assertExtension('sybase_ct'))
  81 + {
  82 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  83 + }
  84 + $this->dsn = $dsninfo;
  85 + $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
  86 + $dbhost .= $dsninfo['port'] ? ',' . $dsninfo['port'] : '';
  87 +
  88 + $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
  89 +
  90 + if ($dbhost && $dsninfo['username'] && $dsninfo['password']) {
  91 + $conn = @$connect_function($dbhost, $dsninfo['username'],
  92 + $dsninfo['password']);
  93 + } elseif ($dbhost && $dsninfo['username']) {
  94 + $conn = @$connect_function($dbhost, $dsninfo['username']);
  95 + } else {
  96 + $conn = @$connect_function($dbhost);
  97 + }
  98 + if (!$conn) {
  99 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  100 + null, @mssql_get_last_message());
  101 + }
  102 + if ($dsninfo['database']) {
  103 + if (!@mssql_select_db($dsninfo['database'], $conn)) {
  104 + return $this->raiseError(DB_ERROR_NODBSELECTED, null, null,
  105 + null, @mssql_get_last_message());
  106 + }
  107 + $this->_db = $dsninfo['database'];
  108 + }
  109 + $this->connection = $conn;
  110 + return DB_OK;
  111 + }
  112 +
  113 + // }}}
  114 + // {{{ disconnect()
  115 +
  116 + function disconnect()
  117 + {
  118 + $ret = @mssql_close($this->connection);
  119 + $this->connection = null;
  120 + return $ret;
  121 + }
  122 +
  123 + // }}}
  124 + // {{{ simpleQuery()
  125 +
  126 + function simpleQuery($query)
  127 + {
  128 + $ismanip = DB::isManip($query);
  129 + $this->last_query = $query;
  130 + if (!@mssql_select_db($this->_db, $this->connection)) {
  131 + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  132 + }
  133 + $query = $this->modifyQuery($query);
  134 + if (!$this->autocommit && $ismanip) {
  135 + if ($this->transaction_opcount == 0) {
  136 + $result = @mssql_query('BEGIN TRAN', $this->connection);
  137 + if (!$result) {
  138 + return $this->mssqlRaiseError();
  139 + }
  140 + }
  141 + $this->transaction_opcount++;
  142 + }
  143 + $result = @mssql_query($query, $this->connection);
  144 + if (!$result) {
  145 + return $this->mssqlRaiseError();
  146 + }
  147 + // Determine which queries that should return data, and which
  148 + // should return an error code only.
  149 + return $ismanip ? DB_OK : $result;
  150 + }
  151 +
  152 + // }}}
  153 + // {{{ nextResult()
  154 +
  155 + /**
  156 + * Move the internal mssql result pointer to the next available result
  157 + *
  158 + * @param a valid fbsql result resource
  159 + *
  160 + * @access public
  161 + *
  162 + * @return true if a result is available otherwise return false
  163 + */
  164 + function nextResult($result)
  165 + {
  166 + return @mssql_next_result($result);
  167 + }
  168 +
  169 + // }}}
  170 + // {{{ fetchInto()
  171 +
  172 + /**
  173 + * Fetch a row and insert the data into an existing array.
  174 + *
  175 + * Formating of the array and the data therein are configurable.
  176 + * See DB_result::fetchInto() for more information.
  177 + *
  178 + * @param resource $result query result identifier
  179 + * @param array $arr (reference) array where data from the row
  180 + * should be placed
  181 + * @param int $fetchmode how the resulting array should be indexed
  182 + * @param int $rownum the row number to fetch
  183 + *
  184 + * @return mixed DB_OK on success, null when end of result set is
  185 + * reached or on failure
  186 + *
  187 + * @see DB_result::fetchInto()
  188 + * @access private
  189 + */
  190 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  191 + {
  192 + if ($rownum !== null) {
  193 + if (!@mssql_data_seek($result, $rownum)) {
  194 + return null;
  195 + }
  196 + }
  197 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  198 + $arr = @mssql_fetch_array($result, MSSQL_ASSOC);
  199 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  200 + $arr = array_change_key_case($arr, CASE_LOWER);
  201 + }
  202 + } else {
  203 + $arr = @mssql_fetch_row($result);
  204 + }
  205 + if (!$arr) {
  206 + /* This throws informative error messages,
  207 + don't use it for now
  208 + if ($msg = @mssql_get_last_message()) {
  209 + return $this->raiseError($msg);
  210 + }
  211 + */
  212 + return null;
  213 + }
  214 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  215 + $this->_rtrimArrayValues($arr);
  216 + }
  217 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  218 + $this->_convertNullArrayValuesToEmpty($arr);
  219 + }
  220 + return DB_OK;
  221 + }
  222 +
  223 + // }}}
  224 + // {{{ freeResult()
  225 +
  226 + function freeResult($result)
  227 + {
  228 + return @mssql_free_result($result);
  229 + }
  230 +
  231 + // }}}
  232 + // {{{ numCols()
  233 +
  234 + function numCols($result)
  235 + {
  236 + $cols = @mssql_num_fields($result);
  237 + if (!$cols) {
  238 + return $this->mssqlRaiseError();
  239 + }
  240 + return $cols;
  241 + }
  242 +
  243 + // }}}
  244 + // {{{ numRows()
  245 +
  246 + function numRows($result)
  247 + {
  248 + $rows = @mssql_num_rows($result);
  249 + if ($rows === false) {
  250 + return $this->mssqlRaiseError();
  251 + }
  252 + return $rows;
  253 + }
  254 +
  255 + // }}}
  256 + // {{{ autoCommit()
  257 +
  258 + /**
  259 + * Enable/disable automatic commits
  260 + */
  261 + function autoCommit($onoff = false)
  262 + {
  263 + // XXX if $this->transaction_opcount > 0, we should probably
  264 + // issue a warning here.
  265 + $this->autocommit = $onoff ? true : false;
  266 + return DB_OK;
  267 + }
  268 +
  269 + // }}}
  270 + // {{{ commit()
  271 +
  272 + /**
  273 + * Commit the current transaction.
  274 + */
  275 + function commit()
  276 + {
  277 + if ($this->transaction_opcount > 0) {
  278 + if (!@mssql_select_db($this->_db, $this->connection)) {
  279 + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  280 + }
  281 + $result = @mssql_query('COMMIT TRAN', $this->connection);
  282 + $this->transaction_opcount = 0;
  283 + if (!$result) {
  284 + return $this->mssqlRaiseError();
  285 + }
  286 + }
  287 + return DB_OK;
  288 + }
  289 +
  290 + // }}}
  291 + // {{{ rollback()
  292 +
  293 + /**
  294 + * Roll back (undo) the current transaction.
  295 + */
  296 + function rollback()
  297 + {
  298 + if ($this->transaction_opcount > 0) {
  299 + if (!@mssql_select_db($this->_db, $this->connection)) {
  300 + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  301 + }
  302 + $result = @mssql_query('ROLLBACK TRAN', $this->connection);
  303 + $this->transaction_opcount = 0;
  304 + if (!$result) {
  305 + return $this->mssqlRaiseError();
  306 + }
  307 + }
  308 + return DB_OK;
  309 + }
  310 +
  311 + // }}}
  312 + // {{{ affectedRows()
  313 +
  314 + /**
  315 + * Gets the number of rows affected by the last query.
  316 + * if the last query was a select, returns 0.
  317 + *
  318 + * @return number of rows affected by the last query or DB_ERROR
  319 + */
  320 + function affectedRows()
  321 + {
  322 + if (DB::isManip($this->last_query)) {
  323 + $res = @mssql_query('select @@rowcount', $this->connection);
  324 + if (!$res) {
  325 + return $this->mssqlRaiseError();
  326 + }
  327 + $ar = @mssql_fetch_row($res);
  328 + if (!$ar) {
  329 + $result = 0;
  330 + } else {
  331 + @mssql_free_result($res);
  332 + $result = $ar[0];
  333 + }
  334 + } else {
  335 + $result = 0;
  336 + }
  337 + return $result;
  338 + }
  339 +
  340 + // }}}
  341 + // {{{ nextId()
  342 +
  343 + /**
  344 + * Returns the next free id in a sequence
  345 + *
  346 + * @param string $seq_name name of the sequence
  347 + * @param boolean $ondemand when true, the seqence is automatically
  348 + * created if it does not exist
  349 + *
  350 + * @return int the next id number in the sequence. DB_Error if problem.
  351 + *
  352 + * @internal
  353 + * @see DB_common::nextID()
  354 + * @access public
  355 + */
  356 + function nextId($seq_name, $ondemand = true)
  357 + {
  358 + $seqname = $this->getSequenceName($seq_name);
  359 + if (!@mssql_select_db($this->_db, $this->connection)) {
  360 + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  361 + }
  362 + $repeat = 0;
  363 + do {
  364 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  365 + $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
  366 + $this->popErrorHandling();
  367 + if ($ondemand && DB::isError($result) &&
  368 + ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
  369 + {
  370 + $repeat = 1;
  371 + $result = $this->createSequence($seq_name);
  372 + if (DB::isError($result)) {
  373 + return $this->raiseError($result);
  374 + }
  375 + } elseif (!DB::isError($result)) {
  376 + $result =& $this->query("SELECT @@IDENTITY FROM $seqname");
  377 + $repeat = 0;
  378 + } else {
  379 + $repeat = false;
  380 + }
  381 + } while ($repeat);
  382 + if (DB::isError($result)) {
  383 + return $this->raiseError($result);
  384 + }
  385 + $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
  386 + return $result[0];
  387 + }
  388 +
  389 + /**
  390 + * Creates a new sequence
  391 + *
  392 + * @param string $seq_name name of the new sequence
  393 + *
  394 + * @return int DB_OK on success. A DB_Error object is returned if
  395 + * problems arise.
  396 + *
  397 + * @internal
  398 + * @see DB_common::createSequence()
  399 + * @access public
  400 + */
  401 + function createSequence($seq_name)
  402 + {
  403 + $seqname = $this->getSequenceName($seq_name);
  404 + return $this->query("CREATE TABLE $seqname ".
  405 + '([id] [int] IDENTITY (1, 1) NOT NULL ,' .
  406 + '[vapor] [int] NULL)');
  407 + }
  408 +
  409 + // }}}
  410 + // {{{ dropSequence()
  411 +
  412 + /**
  413 + * Deletes a sequence
  414 + *
  415 + * @param string $seq_name name of the sequence to be deleted
  416 + *
  417 + * @return int DB_OK on success. DB_Error if problems.
  418 + *
  419 + * @internal
  420 + * @see DB_common::dropSequence()
  421 + * @access public
  422 + */
  423 + function dropSequence($seq_name)
  424 + {
  425 + $seqname = $this->getSequenceName($seq_name);
  426 + return $this->query("DROP TABLE $seqname");
  427 + }
  428 +
  429 + // }}}
  430 + // {{{ errorNative()
  431 +
  432 + /**
  433 + * Determine MS SQL Server error code by querying @@ERROR.
  434 + *
  435 + * @return mixed mssql's native error code or DB_ERROR if unknown.
  436 + */
  437 + function errorNative()
  438 + {
  439 + $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
  440 + if (!$res) {
  441 + return DB_ERROR;
  442 + }
  443 + $row = @mssql_fetch_row($res);
  444 + return $row[0];
  445 + }
  446 +
  447 + // }}}
  448 + // {{{ errorCode()
  449 +
  450 + /**
  451 + * Determine PEAR::DB error code from mssql's native codes.
  452 + *
  453 + * If <var>$nativecode</var> isn't known yet, it will be looked up.
  454 + *
  455 + * @param mixed $nativecode mssql error code, if known
  456 + * @return integer an error number from a DB error constant
  457 + * @see errorNative()
  458 + */
  459 + function errorCode($nativecode = null)
  460 + {
  461 + if (!$nativecode) {
  462 + $nativecode = $this->errorNative();
  463 + }
  464 + if (isset($this->errorcode_map[$nativecode])) {
  465 + return $this->errorcode_map[$nativecode];
  466 + } else {
  467 + return DB_ERROR;
  468 + }
  469 + }
  470 +
  471 + // }}}
  472 + // {{{ mssqlRaiseError()
  473 +
  474 + /**
  475 + * Gather information about an error, then use that info to create a
  476 + * DB error object and finally return that object.
  477 + *
  478 + * @param integer $code PEAR error number (usually a DB constant) if
  479 + * manually raising an error
  480 + * @return object DB error object
  481 + * @see errorCode()
  482 + * @see errorNative()
  483 + * @see DB_common::raiseError()
  484 + */
  485 + function mssqlRaiseError($code = null)
  486 + {
  487 + $message = @mssql_get_last_message();
  488 + if (!$code) {
  489 + $code = $this->errorNative();
  490 + }
  491 + return $this->raiseError($this->errorCode($code), null, null, null,
  492 + "$code - $message");
  493 + }
  494 +
  495 + // }}}
  496 + // {{{ tableInfo()
  497 +
  498 + /**
  499 + * Returns information about a table or a result set.
  500 + *
  501 + * NOTE: only supports 'table' and 'flags' if <var>$result</var>
  502 + * is a table name.
  503 + *
  504 + * @param object|string $result DB_result object from a query or a
  505 + * string containing the name of a table
  506 + * @param int $mode a valid tableInfo mode
  507 + * @return array an associative array with the information requested
  508 + * or an error object if something is wrong
  509 + * @access public
  510 + * @internal
  511 + * @see DB_common::tableInfo()
  512 + */
  513 + function tableInfo($result, $mode = null)
  514 + {
  515 + if (isset($result->result)) {
  516 + /*
  517 + * Probably received a result object.
  518 + * Extract the result resource identifier.
  519 + */
  520 + $id = $result->result;
  521 + $got_string = false;
  522 + } elseif (is_string($result)) {
  523 + /*
  524 + * Probably received a table name.
  525 + * Create a result resource identifier.
  526 + */
  527 + if (!@mssql_select_db($this->_db, $this->connection)) {
  528 + return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
  529 + }
  530 + $id = @mssql_query("SELECT * FROM $result WHERE 1=0",
  531 + $this->connection);
  532 + $got_string = true;
  533 + } else {
  534 + /*
  535 + * Probably received a result resource identifier.
  536 + * Copy it.
  537 + * Deprecated. Here for compatibility only.
  538 + */
  539 + $id = $result;
  540 + $got_string = false;
  541 + }
  542 +
  543 + if (!is_resource($id)) {
  544 + return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA);
  545 + }
  546 +
  547 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  548 + $case_func = 'strtolower';
  549 + } else {
  550 + $case_func = 'strval';
  551 + }
  552 +
  553 + $count = @mssql_num_fields($id);
  554 +
  555 + // made this IF due to performance (one if is faster than $count if's)
  556 + if (!$mode) {
  557 + for ($i=0; $i<$count; $i++) {
  558 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  559 + $res[$i]['name'] = $case_func(@mssql_field_name($id, $i));
  560 + $res[$i]['type'] = @mssql_field_type($id, $i);
  561 + $res[$i]['len'] = @mssql_field_length($id, $i);
  562 + // We only support flags for tables
  563 + $res[$i]['flags'] = $got_string ? $this->_mssql_field_flags($result, $res[$i]['name']) : '';
  564 + }
  565 +
  566 + } else { // full
  567 + $res['num_fields']= $count;
  568 +
  569 + for ($i=0; $i<$count; $i++) {
  570 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  571 + $res[$i]['name'] = $case_func(@mssql_field_name($id, $i));
  572 + $res[$i]['type'] = @mssql_field_type($id, $i);
  573 + $res[$i]['len'] = @mssql_field_length($id, $i);
  574 + // We only support flags for tables
  575 + $res[$i]['flags'] = $got_string ? $this->_mssql_field_flags($result, $res[$i]['name']) : '';
  576 +
  577 + if ($mode & DB_TABLEINFO_ORDER) {
  578 + $res['order'][$res[$i]['name']] = $i;
  579 + }
  580 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  581 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  582 + }
  583 + }
  584 + }
  585 +
  586 + // free the result only if we were called on a table
  587 + if ($got_string) {
  588 + @mssql_free_result($id);
  589 + }
  590 + return $res;
  591 + }
  592 +
  593 + // }}}
  594 + // {{{ getSpecialQuery()
  595 +
  596 + /**
  597 + * Returns the query needed to get some backend info
  598 + * @param string $type What kind of info you want to retrieve
  599 + * @return string The SQL query string
  600 + */
  601 + function getSpecialQuery($type)
  602 + {
  603 + switch ($type) {
  604 + case 'tables':
  605 + return "select name from sysobjects where type = 'U' order by name";
  606 + case 'views':
  607 + return "select name from sysobjects where type = 'V'";
  608 + default:
  609 + return null;
  610 + }
  611 + }
  612 +
  613 + // }}}
  614 + // {{{ _mssql_field_flags()
  615 +
  616 + /**
  617 + * Get the flags for a field, currently supports "not_null", "primary_key",
  618 + * "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
  619 + * "unique_key" (mssql unique index, unique check or primary_key) and
  620 + * "multiple_key" (multikey index)
  621 + *
  622 + * mssql timestamp is NOT similar to the mysql timestamp so this is maybe
  623 + * not useful at all - is the behaviour of mysql_field_flags that primary
  624 + * keys are alway unique? is the interpretation of multiple_key correct?
  625 + *
  626 + * @param string The table name
  627 + * @param string The field
  628 + * @author Joern Barthel <j_barthel@web.de>
  629 + * @access private
  630 + */
  631 + function _mssql_field_flags($table, $column)
  632 + {
  633 + static $tableName = null;
  634 + static $flags = array();
  635 +
  636 + if ($table != $tableName) {
  637 +
  638 + $flags = array();
  639 + $tableName = $table;
  640 +
  641 + // get unique and primary keys
  642 + $res = $this->getAll("EXEC SP_HELPINDEX[$table]", DB_FETCHMODE_ASSOC);
  643 +
  644 + foreach ($res as $val) {
  645 + $keys = explode(', ', $val['index_keys']);
  646 +
  647 + if (sizeof($keys) > 1) {
  648 + foreach ($keys as $key) {
  649 + $this->_add_flag($flags[$key], 'multiple_key');
  650 + }
  651 + }
  652 +
  653 + if (strpos($val['index_description'], 'primary key')) {
  654 + foreach ($keys as $key) {
  655 + $this->_add_flag($flags[$key], 'primary_key');
  656 + }
  657 + } elseif (strpos($val['index_description'], 'unique')) {
  658 + foreach ($keys as $key) {
  659 + $this->_add_flag($flags[$key], 'unique_key');
  660 + }
  661 + }
  662 + }
  663 +
  664 + // get auto_increment, not_null and timestamp
  665 + $res = $this->getAll("EXEC SP_COLUMNS[$table]", DB_FETCHMODE_ASSOC);
  666 +
  667 + foreach ($res as $val) {
  668 + $val = array_change_key_case($val, CASE_LOWER);
  669 + if ($val['nullable'] == '0') {
  670 + $this->_add_flag($flags[$val['column_name']], 'not_null');
  671 + }
  672 + if (strpos($val['type_name'], 'identity')) {
  673 + $this->_add_flag($flags[$val['column_name']], 'auto_increment');
  674 + }
  675 + if (strpos($val['type_name'], 'timestamp')) {
  676 + $this->_add_flag($flags[$val['column_name']], 'timestamp');
  677 + }
  678 + }
  679 + }
  680 +
  681 + if (array_key_exists($column, $flags)) {
  682 + return(implode(' ', $flags[$column]));
  683 + }
  684 + return '';
  685 + }
  686 +
  687 + // }}}
  688 + // {{{ _add_flag()
  689 +
  690 + /**
  691 + * Adds a string to the flags array if the flag is not yet in there
  692 + * - if there is no flag present the array is created.
  693 + *
  694 + * @param reference Reference to the flag-array
  695 + * @param value The flag value
  696 + * @access private
  697 + * @author Joern Barthel <j_barthel@web.de>
  698 + */
  699 + function _add_flag(&$array, $value)
  700 + {
  701 + if (!is_array($array)) {
  702 + $array = array($value);
  703 + } elseif (!in_array($value, $array)) {
  704 + array_push($array, $value);
  705 + }
  706 + }
  707 +
  708 + // }}}
  709 + // {{{ quoteIdentifier()
  710 +
  711 + /**
  712 + * Quote a string so it can be safely used as a table / column name
  713 + *
  714 + * Quoting style depends on which database driver is being used.
  715 + *
  716 + * @param string $str identifier name to be quoted
  717 + *
  718 + * @return string quoted identifier string
  719 + *
  720 + * @since 1.6.0
  721 + * @access public
  722 + */
  723 + function quoteIdentifier($str)
  724 + {
  725 + return '[' . str_replace(']', ']]', $str) . ']';
  726 + }
  727 +
  728 + // }}}
  729 +}
  730 +
  731 +/*
  732 + * Local variables:
  733 + * tab-width: 4
  734 + * c-basic-offset: 4
  735 + * End:
  736 + */
  737 +
  738 +?>
... ...
pear/DB/mysql.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Stig Bakken <ssb@php.net> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// XXX legend:
  24 +//
  25 +// XXX ERRORMSG: The error message from the mysql function should
  26 +// be registered here.
  27 +//
  28 +// TODO/wishlist:
  29 +// longReadlen
  30 +// binmode
  31 +
  32 +
  33 +require_once 'DB/common.php';
  34 +
  35 +/**
  36 + * Database independent query interface definition for PHP's MySQL
  37 + * extension.
  38 + *
  39 + * This is for MySQL versions 4.0 and below.
  40 + *
  41 + * @package DB
  42 + * @version $Id$
  43 + * @category Database
  44 + * @author Stig Bakken <ssb@php.net>
  45 + */
  46 +class DB_mysql extends DB_common
  47 +{
  48 + // {{{ properties
  49 +
  50 + var $connection;
  51 + var $phptype, $dbsyntax;
  52 + var $prepare_tokens = array();
  53 + var $prepare_types = array();
  54 + var $num_rows = array();
  55 + var $transaction_opcount = 0;
  56 + var $autocommit = true;
  57 + var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */
  58 + var $_db = false;
  59 +
  60 + // }}}
  61 + // {{{ constructor
  62 +
  63 + /**
  64 + * DB_mysql constructor.
  65 + *
  66 + * @access public
  67 + */
  68 + function DB_mysql()
  69 + {
  70 + $this->DB_common();
  71 + $this->phptype = 'mysql';
  72 + $this->dbsyntax = 'mysql';
  73 + $this->features = array(
  74 + 'prepare' => false,
  75 + 'pconnect' => true,
  76 + 'transactions' => true,
  77 + 'limit' => 'alter'
  78 + );
  79 + $this->errorcode_map = array(
  80 + 1004 => DB_ERROR_CANNOT_CREATE,
  81 + 1005 => DB_ERROR_CANNOT_CREATE,
  82 + 1006 => DB_ERROR_CANNOT_CREATE,
  83 + 1007 => DB_ERROR_ALREADY_EXISTS,
  84 + 1008 => DB_ERROR_CANNOT_DROP,
  85 + 1022 => DB_ERROR_ALREADY_EXISTS,
  86 + 1046 => DB_ERROR_NODBSELECTED,
  87 + 1048 => DB_ERROR_CONSTRAINT,
  88 + 1050 => DB_ERROR_ALREADY_EXISTS,
  89 + 1051 => DB_ERROR_NOSUCHTABLE,
  90 + 1054 => DB_ERROR_NOSUCHFIELD,
  91 + 1062 => DB_ERROR_ALREADY_EXISTS,
  92 + 1064 => DB_ERROR_SYNTAX,
  93 + 1100 => DB_ERROR_NOT_LOCKED,
  94 + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
  95 + 1146 => DB_ERROR_NOSUCHTABLE,
  96 + 1216 => DB_ERROR_CONSTRAINT,
  97 + 1217 => DB_ERROR_CONSTRAINT,
  98 + );
  99 + }
  100 +
  101 + // }}}
  102 + // {{{ connect()
  103 +
  104 + /**
  105 + * Connect to a database and log in as the specified user.
  106 + *
  107 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  108 + * @param $persistent (optional) whether the connection should
  109 + * be persistent
  110 + * @access public
  111 + * @return int DB_OK on success, a DB error on failure
  112 + */
  113 + function connect($dsninfo, $persistent = false)
  114 + {
  115 + if (!DB::assertExtension('mysql')) {
  116 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  117 + }
  118 + $this->dsn = $dsninfo;
  119 + if ($dsninfo['protocol'] && $dsninfo['protocol'] == 'unix') {
  120 + $dbhost = ':' . $dsninfo['socket'];
  121 + } else {
  122 + $dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
  123 + if ($dsninfo['port']) {
  124 + $dbhost .= ':' . $dsninfo['port'];
  125 + }
  126 + }
  127 +
  128 + $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
  129 +
  130 + if ($dbhost && $dsninfo['username'] && isset($dsninfo['password'])) {
  131 + $conn = @$connect_function($dbhost, $dsninfo['username'],
  132 + $dsninfo['password']);
  133 + } elseif ($dbhost && $dsninfo['username']) {
  134 + $conn = @$connect_function($dbhost, $dsninfo['username']);
  135 + } elseif ($dbhost) {
  136 + $conn = @$connect_function($dbhost);
  137 + } else {
  138 + $conn = false;
  139 + }
  140 + if (!$conn) {
  141 + if (($err = @mysql_error()) != '') {
  142 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  143 + null, $err);
  144 + } elseif (empty($php_errormsg)) {
  145 + return $this->raiseError(DB_ERROR_CONNECT_FAILED);
  146 + } else {
  147 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  148 + null, $php_errormsg);
  149 + }
  150 + }
  151 +
  152 + if ($dsninfo['database']) {
  153 + if (!@mysql_select_db($dsninfo['database'], $conn)) {
  154 + switch(mysql_errno($conn)) {
  155 + case 1049:
  156 + return $this->raiseError(DB_ERROR_NOSUCHDB, null, null,
  157 + null, @mysql_error($conn));
  158 + case 1044:
  159 + return $this->raiseError(DB_ERROR_ACCESS_VIOLATION, null, null,
  160 + null, @mysql_error($conn));
  161 + default:
  162 + return $this->raiseError(DB_ERROR, null, null,
  163 + null, @mysql_error($conn));
  164 + }
  165 + }
  166 + // fix to allow calls to different databases in the same script
  167 + $this->_db = $dsninfo['database'];
  168 + }
  169 +
  170 + $this->connection = $conn;
  171 + return DB_OK;
  172 + }
  173 +
  174 + // }}}
  175 + // {{{ disconnect()
  176 +
  177 + /**
  178 + * Log out and disconnect from the database.
  179 + *
  180 + * @access public
  181 + *
  182 + * @return bool true on success, false if not connected.
  183 + */
  184 + function disconnect()
  185 + {
  186 + $ret = @mysql_close($this->connection);
  187 + $this->connection = null;
  188 + return $ret;
  189 + }
  190 +
  191 + // }}}
  192 + // {{{ simpleQuery()
  193 +
  194 + /**
  195 + * Send a query to MySQL and return the results as a MySQL resource
  196 + * identifier.
  197 + *
  198 + * @param the SQL query
  199 + *
  200 + * @access public
  201 + *
  202 + * @return mixed returns a valid MySQL result for successful SELECT
  203 + * queries, DB_OK for other successful queries. A DB error is
  204 + * returned on failure.
  205 + */
  206 + function simpleQuery($query)
  207 + {
  208 + $ismanip = DB::isManip($query);
  209 + $this->last_query = $query;
  210 + $query = $this->modifyQuery($query);
  211 + if ($this->_db) {
  212 + if (!@mysql_select_db($this->_db, $this->connection)) {
  213 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  214 + }
  215 + }
  216 + if (!$this->autocommit && $ismanip) {
  217 + if ($this->transaction_opcount == 0) {
  218 + $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection);
  219 + $result = @mysql_query('BEGIN', $this->connection);
  220 + if (!$result) {
  221 + return $this->mysqlRaiseError();
  222 + }
  223 + }
  224 + $this->transaction_opcount++;
  225 + }
  226 + $result = @mysql_query($query, $this->connection);
  227 + if (!$result) {
  228 + return $this->mysqlRaiseError();
  229 + }
  230 + if (is_resource($result)) {
  231 + $numrows = $this->numrows($result);
  232 + if (is_object($numrows)) {
  233 + return $numrows;
  234 + }
  235 + $this->num_rows[(int)$result] = $numrows;
  236 + return $result;
  237 + }
  238 + return DB_OK;
  239 + }
  240 +
  241 + // }}}
  242 + // {{{ nextResult()
  243 +
  244 + /**
  245 + * Move the internal mysql result pointer to the next available result
  246 + *
  247 + * This method has not been implemented yet.
  248 + *
  249 + * @param a valid sql result resource
  250 + *
  251 + * @access public
  252 + *
  253 + * @return false
  254 + */
  255 + function nextResult($result)
  256 + {
  257 + return false;
  258 + }
  259 +
  260 + // }}}
  261 + // {{{ fetchInto()
  262 +
  263 + /**
  264 + * Fetch a row and insert the data into an existing array.
  265 + *
  266 + * Formating of the array and the data therein are configurable.
  267 + * See DB_result::fetchInto() for more information.
  268 + *
  269 + * @param resource $result query result identifier
  270 + * @param array $arr (reference) array where data from the row
  271 + * should be placed
  272 + * @param int $fetchmode how the resulting array should be indexed
  273 + * @param int $rownum the row number to fetch
  274 + *
  275 + * @return mixed DB_OK on success, null when end of result set is
  276 + * reached or on failure
  277 + *
  278 + * @see DB_result::fetchInto()
  279 + * @access private
  280 + */
  281 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  282 + {
  283 + if ($rownum !== null) {
  284 + if (!@mysql_data_seek($result, $rownum)) {
  285 + return null;
  286 + }
  287 + }
  288 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  289 + $arr = @mysql_fetch_array($result, MYSQL_ASSOC);
  290 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  291 + $arr = array_change_key_case($arr, CASE_LOWER);
  292 + }
  293 + } else {
  294 + $arr = @mysql_fetch_row($result);
  295 + }
  296 + if (!$arr) {
  297 + // See: http://bugs.php.net/bug.php?id=22328
  298 + // for why we can't check errors on fetching
  299 + return null;
  300 + /*
  301 + $errno = @mysql_errno($this->connection);
  302 + if (!$errno) {
  303 + return null;
  304 + }
  305 + return $this->mysqlRaiseError($errno);
  306 + */
  307 + }
  308 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  309 + /*
  310 + * Even though this DBMS already trims output, we do this because
  311 + * a field might have intentional whitespace at the end that
  312 + * gets removed by DB_PORTABILITY_RTRIM under another driver.
  313 + */
  314 + $this->_rtrimArrayValues($arr);
  315 + }
  316 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  317 + $this->_convertNullArrayValuesToEmpty($arr);
  318 + }
  319 + return DB_OK;
  320 + }
  321 +
  322 + // }}}
  323 + // {{{ freeResult()
  324 +
  325 + /**
  326 + * Free the internal resources associated with $result.
  327 + *
  328 + * @param $result MySQL result identifier
  329 + *
  330 + * @access public
  331 + *
  332 + * @return bool true on success, false if $result is invalid
  333 + */
  334 + function freeResult($result)
  335 + {
  336 + unset($this->num_rows[(int)$result]);
  337 + return @mysql_free_result($result);
  338 + }
  339 +
  340 + // }}}
  341 + // {{{ numCols()
  342 +
  343 + /**
  344 + * Get the number of columns in a result set.
  345 + *
  346 + * @param $result MySQL result identifier
  347 + *
  348 + * @access public
  349 + *
  350 + * @return int the number of columns per row in $result
  351 + */
  352 + function numCols($result)
  353 + {
  354 + $cols = @mysql_num_fields($result);
  355 +
  356 + if (!$cols) {
  357 + return $this->mysqlRaiseError();
  358 + }
  359 +
  360 + return $cols;
  361 + }
  362 +
  363 + // }}}
  364 + // {{{ numRows()
  365 +
  366 + /**
  367 + * Get the number of rows in a result set.
  368 + *
  369 + * @param $result MySQL result identifier
  370 + *
  371 + * @access public
  372 + *
  373 + * @return int the number of rows in $result
  374 + */
  375 + function numRows($result)
  376 + {
  377 + $rows = @mysql_num_rows($result);
  378 + if ($rows === null) {
  379 + return $this->mysqlRaiseError();
  380 + }
  381 + return $rows;
  382 + }
  383 +
  384 + // }}}
  385 + // {{{ autoCommit()
  386 +
  387 + /**
  388 + * Enable/disable automatic commits
  389 + */
  390 + function autoCommit($onoff = false)
  391 + {
  392 + // XXX if $this->transaction_opcount > 0, we should probably
  393 + // issue a warning here.
  394 + $this->autocommit = $onoff ? true : false;
  395 + return DB_OK;
  396 + }
  397 +
  398 + // }}}
  399 + // {{{ commit()
  400 +
  401 + /**
  402 + * Commit the current transaction.
  403 + */
  404 + function commit()
  405 + {
  406 + if ($this->transaction_opcount > 0) {
  407 + if ($this->_db) {
  408 + if (!@mysql_select_db($this->_db, $this->connection)) {
  409 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  410 + }
  411 + }
  412 + $result = @mysql_query('COMMIT', $this->connection);
  413 + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
  414 + $this->transaction_opcount = 0;
  415 + if (!$result) {
  416 + return $this->mysqlRaiseError();
  417 + }
  418 + }
  419 + return DB_OK;
  420 + }
  421 +
  422 + // }}}
  423 + // {{{ rollback()
  424 +
  425 + /**
  426 + * Roll back (undo) the current transaction.
  427 + */
  428 + function rollback()
  429 + {
  430 + if ($this->transaction_opcount > 0) {
  431 + if ($this->_db) {
  432 + if (!@mysql_select_db($this->_db, $this->connection)) {
  433 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  434 + }
  435 + }
  436 + $result = @mysql_query('ROLLBACK', $this->connection);
  437 + $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
  438 + $this->transaction_opcount = 0;
  439 + if (!$result) {
  440 + return $this->mysqlRaiseError();
  441 + }
  442 + }
  443 + return DB_OK;
  444 + }
  445 +
  446 + // }}}
  447 + // {{{ affectedRows()
  448 +
  449 + /**
  450 + * Gets the number of rows affected by the data manipulation
  451 + * query. For other queries, this function returns 0.
  452 + *
  453 + * @return number of rows affected by the last query
  454 + */
  455 + function affectedRows()
  456 + {
  457 + if (DB::isManip($this->last_query)) {
  458 + return @mysql_affected_rows($this->connection);
  459 + } else {
  460 + return 0;
  461 + }
  462 + }
  463 +
  464 + // }}}
  465 + // {{{ errorNative()
  466 +
  467 + /**
  468 + * Get the native error code of the last error (if any) that
  469 + * occured on the current connection.
  470 + *
  471 + * @access public
  472 + *
  473 + * @return int native MySQL error code
  474 + */
  475 + function errorNative()
  476 + {
  477 + return @mysql_errno($this->connection);
  478 + }
  479 +
  480 + // }}}
  481 + // {{{ nextId()
  482 +
  483 + /**
  484 + * Returns the next free id in a sequence
  485 + *
  486 + * @param string $seq_name name of the sequence
  487 + * @param boolean $ondemand when true, the seqence is automatically
  488 + * created if it does not exist
  489 + *
  490 + * @return int the next id number in the sequence. DB_Error if problem.
  491 + *
  492 + * @internal
  493 + * @see DB_common::nextID()
  494 + * @access public
  495 + */
  496 + function nextId($seq_name, $ondemand = true)
  497 + {
  498 + $seqname = $this->getSequenceName($seq_name);
  499 + do {
  500 + $repeat = 0;
  501 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  502 + $result = $this->query("UPDATE ${seqname} ".
  503 + 'SET id=LAST_INSERT_ID(id+1)');
  504 + $this->popErrorHandling();
  505 + if ($result === DB_OK) {
  506 + /** COMMON CASE **/
  507 + $id = @mysql_insert_id($this->connection);
  508 + if ($id != 0) {
  509 + return $id;
  510 + }
  511 + /** EMPTY SEQ TABLE **/
  512 + // Sequence table must be empty for some reason, so fill it and return 1
  513 + // Obtain a user-level lock
  514 + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
  515 + if (DB::isError($result)) {
  516 + return $this->raiseError($result);
  517 + }
  518 + if ($result == 0) {
  519 + // Failed to get the lock, bail with a DB_ERROR_NOT_LOCKED error
  520 + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
  521 + }
  522 +
  523 + // add the default value
  524 + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)");
  525 + if (DB::isError($result)) {
  526 + return $this->raiseError($result);
  527 + }
  528 +
  529 + // Release the lock
  530 + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
  531 + if (DB::isError($result)) {
  532 + return $this->raiseError($result);
  533 + }
  534 + // We know what the result will be, so no need to try again
  535 + return 1;
  536 +
  537 + /** ONDEMAND TABLE CREATION **/
  538 + } elseif ($ondemand && DB::isError($result) &&
  539 + $result->getCode() == DB_ERROR_NOSUCHTABLE)
  540 + {
  541 + $result = $this->createSequence($seq_name);
  542 + if (DB::isError($result)) {
  543 + return $this->raiseError($result);
  544 + } else {
  545 + $repeat = 1;
  546 + }
  547 +
  548 + /** BACKWARDS COMPAT **/
  549 + } elseif (DB::isError($result) &&
  550 + $result->getCode() == DB_ERROR_ALREADY_EXISTS)
  551 + {
  552 + // see _BCsequence() comment
  553 + $result = $this->_BCsequence($seqname);
  554 + if (DB::isError($result)) {
  555 + return $this->raiseError($result);
  556 + }
  557 + $repeat = 1;
  558 + }
  559 + } while ($repeat);
  560 +
  561 + return $this->raiseError($result);
  562 + }
  563 +
  564 + // }}}
  565 + // {{{ createSequence()
  566 +
  567 + /**
  568 + * Creates a new sequence
  569 + *
  570 + * @param string $seq_name name of the new sequence
  571 + *
  572 + * @return int DB_OK on success. A DB_Error object is returned if
  573 + * problems arise.
  574 + *
  575 + * @internal
  576 + * @see DB_common::createSequence()
  577 + * @access public
  578 + */
  579 + function createSequence($seq_name)
  580 + {
  581 + $seqname = $this->getSequenceName($seq_name);
  582 + $res = $this->query("CREATE TABLE ${seqname} ".
  583 + '(id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'.
  584 + ' PRIMARY KEY(id))');
  585 + if (DB::isError($res)) {
  586 + return $res;
  587 + }
  588 + // insert yields value 1, nextId call will generate ID 2
  589 + $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
  590 + if (DB::isError($res)) {
  591 + return $res;
  592 + }
  593 + // so reset to zero
  594 + return $this->query("UPDATE ${seqname} SET id = 0;");
  595 + }
  596 +
  597 + // }}}
  598 + // {{{ dropSequence()
  599 +
  600 + /**
  601 + * Deletes a sequence
  602 + *
  603 + * @param string $seq_name name of the sequence to be deleted
  604 + *
  605 + * @return int DB_OK on success. DB_Error if problems.
  606 + *
  607 + * @internal
  608 + * @see DB_common::dropSequence()
  609 + * @access public
  610 + */
  611 + function dropSequence($seq_name)
  612 + {
  613 + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
  614 + }
  615 +
  616 + // }}}
  617 + // {{{ _BCsequence()
  618 +
  619 + /**
  620 + * Backwards compatibility with old sequence emulation implementation
  621 + * (clean up the dupes)
  622 + *
  623 + * @param string $seqname The sequence name to clean up
  624 + * @return mixed DB_Error or true
  625 + */
  626 + function _BCsequence($seqname)
  627 + {
  628 + // Obtain a user-level lock... this will release any previous
  629 + // application locks, but unlike LOCK TABLES, it does not abort
  630 + // the current transaction and is much less frequently used.
  631 + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
  632 + if (DB::isError($result)) {
  633 + return $result;
  634 + }
  635 + if ($result == 0) {
  636 + // Failed to get the lock, can't do the conversion, bail
  637 + // with a DB_ERROR_NOT_LOCKED error
  638 + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
  639 + }
  640 +
  641 + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
  642 + if (DB::isError($highest_id)) {
  643 + return $highest_id;
  644 + }
  645 + // This should kill all rows except the highest
  646 + // We should probably do something if $highest_id isn't
  647 + // numeric, but I'm at a loss as how to handle that...
  648 + $result = $this->query("DELETE FROM ${seqname} WHERE id <> $highest_id");
  649 + if (DB::isError($result)) {
  650 + return $result;
  651 + }
  652 +
  653 + // If another thread has been waiting for this lock,
  654 + // it will go thru the above procedure, but will have no
  655 + // real effect
  656 + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
  657 + if (DB::isError($result)) {
  658 + return $result;
  659 + }
  660 + return true;
  661 + }
  662 +
  663 + // }}}
  664 + // {{{ quoteIdentifier()
  665 +
  666 + /**
  667 + * Quote a string so it can be safely used as a table or column name
  668 + *
  669 + * Quoting style depends on which database driver is being used.
  670 + *
  671 + * MySQL can't handle the backtick character (<kbd>`</kbd>) in
  672 + * table or column names.
  673 + *
  674 + * @param string $str identifier name to be quoted
  675 + *
  676 + * @return string quoted identifier string
  677 + *
  678 + * @since 1.6.0
  679 + * @access public
  680 + * @internal
  681 + */
  682 + function quoteIdentifier($str)
  683 + {
  684 + return '`' . $str . '`';
  685 + }
  686 +
  687 + // }}}
  688 + // {{{ quote()
  689 +
  690 + /**
  691 + * @deprecated Deprecated in release 1.6.0
  692 + * @internal
  693 + */
  694 + function quote($str) {
  695 + return $this->quoteSmart($str);
  696 + }
  697 +
  698 + // }}}
  699 + // {{{ escapeSimple()
  700 +
  701 + /**
  702 + * Escape a string according to the current DBMS's standards
  703 + *
  704 + * @param string $str the string to be escaped
  705 + *
  706 + * @return string the escaped string
  707 + *
  708 + * @internal
  709 + */
  710 + function escapeSimple($str) {
  711 + if (function_exists('mysql_real_escape_string')) {
  712 + return @mysql_real_escape_string($str, $this->connection);
  713 + } else {
  714 + return @mysql_escape_string($str);
  715 + }
  716 + }
  717 +
  718 + // }}}
  719 + // {{{ modifyQuery()
  720 +
  721 + function modifyQuery($query)
  722 + {
  723 + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
  724 + // "DELETE FROM table" gives 0 affected rows in MySQL.
  725 + // This little hack lets you know how many rows were deleted.
  726 + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
  727 + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
  728 + 'DELETE FROM \1 WHERE 1=1', $query);
  729 + }
  730 + }
  731 + return $query;
  732 + }
  733 +
  734 + // }}}
  735 + // {{{ modifyLimitQuery()
  736 +
  737 + function modifyLimitQuery($query, $from, $count, $params = array())
  738 + {
  739 + if (DB::isManip($query)) {
  740 + return $query . " LIMIT $count";
  741 + } else {
  742 + return $query . " LIMIT $from, $count";
  743 + }
  744 + }
  745 +
  746 + // }}}
  747 + // {{{ mysqlRaiseError()
  748 +
  749 + /**
  750 + * Gather information about an error, then use that info to create a
  751 + * DB error object and finally return that object.
  752 + *
  753 + * @param integer $errno PEAR error number (usually a DB constant) if
  754 + * manually raising an error
  755 + * @return object DB error object
  756 + * @see DB_common::errorCode()
  757 + * @see DB_common::raiseError()
  758 + */
  759 + function mysqlRaiseError($errno = null)
  760 + {
  761 + if ($errno === null) {
  762 + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
  763 + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
  764 + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
  765 + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
  766 + } else {
  767 + // Doing this in case mode changes during runtime.
  768 + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
  769 + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
  770 + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
  771 + }
  772 + $errno = $this->errorCode(mysql_errno($this->connection));
  773 + }
  774 + return $this->raiseError($errno, null, null, null,
  775 + @mysql_errno($this->connection) . ' ** ' .
  776 + @mysql_error($this->connection));
  777 + }
  778 +
  779 + // }}}
  780 + // {{{ tableInfo()
  781 +
  782 + /**
  783 + * Returns information about a table or a result set.
  784 + *
  785 + * @param object|string $result DB_result object from a query or a
  786 + * string containing the name of a table
  787 + * @param int $mode a valid tableInfo mode
  788 + * @return array an associative array with the information requested
  789 + * or an error object if something is wrong
  790 + * @access public
  791 + * @internal
  792 + * @see DB_common::tableInfo()
  793 + */
  794 + function tableInfo($result, $mode = null) {
  795 + if (isset($result->result)) {
  796 + /*
  797 + * Probably received a result object.
  798 + * Extract the result resource identifier.
  799 + */
  800 + $id = $result->result;
  801 + $got_string = false;
  802 + } elseif (is_string($result)) {
  803 + /*
  804 + * Probably received a table name.
  805 + * Create a result resource identifier.
  806 + */
  807 + $id = @mysql_list_fields($this->dsn['database'],
  808 + $result, $this->connection);
  809 + $got_string = true;
  810 + } else {
  811 + /*
  812 + * Probably received a result resource identifier.
  813 + * Copy it.
  814 + * Deprecated. Here for compatibility only.
  815 + */
  816 + $id = $result;
  817 + $got_string = false;
  818 + }
  819 +
  820 + if (!is_resource($id)) {
  821 + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA);
  822 + }
  823 +
  824 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  825 + $case_func = 'strtolower';
  826 + } else {
  827 + $case_func = 'strval';
  828 + }
  829 +
  830 + $count = @mysql_num_fields($id);
  831 +
  832 + // made this IF due to performance (one if is faster than $count if's)
  833 + if (!$mode) {
  834 + for ($i=0; $i<$count; $i++) {
  835 + $res[$i]['table'] = $case_func(@mysql_field_table($id, $i));
  836 + $res[$i]['name'] = $case_func(@mysql_field_name($id, $i));
  837 + $res[$i]['type'] = @mysql_field_type($id, $i);
  838 + $res[$i]['len'] = @mysql_field_len($id, $i);
  839 + $res[$i]['flags'] = @mysql_field_flags($id, $i);
  840 + }
  841 + } else { // full
  842 + $res['num_fields']= $count;
  843 +
  844 + for ($i=0; $i<$count; $i++) {
  845 + $res[$i]['table'] = $case_func(@mysql_field_table($id, $i));
  846 + $res[$i]['name'] = $case_func(@mysql_field_name($id, $i));
  847 + $res[$i]['type'] = @mysql_field_type($id, $i);
  848 + $res[$i]['len'] = @mysql_field_len($id, $i);
  849 + $res[$i]['flags'] = @mysql_field_flags($id, $i);
  850 +
  851 + if ($mode & DB_TABLEINFO_ORDER) {
  852 + $res['order'][$res[$i]['name']] = $i;
  853 + }
  854 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  855 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  856 + }
  857 + }
  858 + }
  859 +
  860 + // free the result only if we were called on a table
  861 + if ($got_string) {
  862 + @mysql_free_result($id);
  863 + }
  864 + return $res;
  865 + }
  866 +
  867 + // }}}
  868 + // {{{ getSpecialQuery()
  869 +
  870 + /**
  871 + * Returns the query needed to get some backend info
  872 + * @param string $type What kind of info you want to retrieve
  873 + * @return string The SQL query string
  874 + */
  875 + function getSpecialQuery($type)
  876 + {
  877 + switch ($type) {
  878 + case 'tables':
  879 + return 'SHOW TABLES';
  880 + case 'views':
  881 + return DB_ERROR_NOT_CAPABLE;
  882 + case 'users':
  883 + $sql = 'select distinct User from user';
  884 + if ($this->dsn['database'] != 'mysql') {
  885 + $dsn = $this->dsn;
  886 + $dsn['database'] = 'mysql';
  887 + if (DB::isError($db = DB::connect($dsn))) {
  888 + return $db;
  889 + }
  890 + $sql = $db->getCol($sql);
  891 + $db->disconnect();
  892 + // XXX Fixme the mysql driver should take care of this
  893 + if (!@mysql_select_db($this->dsn['database'], $this->connection)) {
  894 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  895 + }
  896 + }
  897 + return $sql;
  898 + case 'databases':
  899 + return 'SHOW DATABASES';
  900 + default:
  901 + return null;
  902 + }
  903 + }
  904 +
  905 + // }}}
  906 +
  907 +}
  908 +
  909 +/*
  910 + * Local variables:
  911 + * tab-width: 4
  912 + * c-basic-offset: 4
  913 + * End:
  914 + */
  915 +
  916 +?>
... ...
pear/DB/mysqli.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Daniel Convissor <danielc@php.net> |
  17 +// +----------------------------------------------------------------------+
  18 +//
  19 +// $Id$
  20 +
  21 +
  22 +// EXPERIMENTAL
  23 +
  24 +
  25 +require_once 'DB/common.php';
  26 +
  27 +/**
  28 + * Database independent query interface definition for PHP's mysqli
  29 + * extension.
  30 + *
  31 + * This is for MySQL versions 4.1 and above. Requires PHP 5.
  32 + *
  33 + * Note that persistent connections no longer exist.
  34 + *
  35 + * @package DB
  36 + * @version $Id$
  37 + * @category Database
  38 + * @author Daniel Convissor <danielc@php.net>
  39 + * @since Class functional since Release 1.6.3
  40 + */
  41 +class DB_mysqli extends DB_common
  42 +{
  43 + // {{{ properties
  44 +
  45 + var $connection;
  46 + var $phptype, $dbsyntax;
  47 + var $prepare_tokens = array();
  48 + var $prepare_types = array();
  49 + var $num_rows = array();
  50 + var $transaction_opcount = 0;
  51 + var $autocommit = true;
  52 + var $fetchmode = DB_FETCHMODE_ORDERED; /* Default fetch mode */
  53 + var $_db = false;
  54 +
  55 + /**
  56 + * Array for converting MYSQLI_*_FLAG constants to text values
  57 + * @var array
  58 + * @access public
  59 + * @since Property available since Release 1.6.5
  60 + */
  61 + var $mysqli_flags = array(
  62 + MYSQLI_NOT_NULL_FLAG => 'not_null',
  63 + MYSQLI_PRI_KEY_FLAG => 'primary_key',
  64 + MYSQLI_UNIQUE_KEY_FLAG => 'unique_key',
  65 + MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key',
  66 + MYSQLI_BLOB_FLAG => 'blob',
  67 + MYSQLI_UNSIGNED_FLAG => 'unsigned',
  68 + MYSQLI_ZEROFILL_FLAG => 'zerofill',
  69 + MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment',
  70 + MYSQLI_TIMESTAMP_FLAG => 'timestamp',
  71 + MYSQLI_SET_FLAG => 'set',
  72 + // MYSQLI_NUM_FLAG => 'numeric', // unnecessary
  73 + // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie
  74 + MYSQLI_GROUP_FLAG => 'group_by'
  75 + );
  76 +
  77 + /**
  78 + * Array for converting MYSQLI_TYPE_* constants to text values
  79 + * @var array
  80 + * @access public
  81 + * @since Property available since Release 1.6.5
  82 + */
  83 + var $mysqli_types = array(
  84 + MYSQLI_TYPE_DECIMAL => 'decimal',
  85 + MYSQLI_TYPE_TINY => 'tinyint',
  86 + MYSQLI_TYPE_SHORT => 'int',
  87 + MYSQLI_TYPE_LONG => 'int',
  88 + MYSQLI_TYPE_FLOAT => 'float',
  89 + MYSQLI_TYPE_DOUBLE => 'double',
  90 + // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it
  91 + MYSQLI_TYPE_TIMESTAMP => 'timestamp',
  92 + MYSQLI_TYPE_LONGLONG => 'bigint',
  93 + MYSQLI_TYPE_INT24 => 'mediumint',
  94 + MYSQLI_TYPE_DATE => 'date',
  95 + MYSQLI_TYPE_TIME => 'time',
  96 + MYSQLI_TYPE_DATETIME => 'datetime',
  97 + MYSQLI_TYPE_YEAR => 'year',
  98 + MYSQLI_TYPE_NEWDATE => 'date',
  99 + MYSQLI_TYPE_ENUM => 'enum',
  100 + MYSQLI_TYPE_SET => 'set',
  101 + MYSQLI_TYPE_TINY_BLOB => 'tinyblob',
  102 + MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob',
  103 + MYSQLI_TYPE_LONG_BLOB => 'longblob',
  104 + MYSQLI_TYPE_BLOB => 'blob',
  105 + MYSQLI_TYPE_VAR_STRING => 'varchar',
  106 + MYSQLI_TYPE_STRING => 'char',
  107 + MYSQLI_TYPE_GEOMETRY => 'geometry',
  108 + );
  109 +
  110 + // }}}
  111 + // {{{ constructor
  112 +
  113 + /**
  114 + * DB_mysql constructor.
  115 + *
  116 + * @access public
  117 + */
  118 + function DB_mysqli()
  119 + {
  120 + $this->DB_common();
  121 + $this->phptype = 'mysqli';
  122 + $this->dbsyntax = 'mysqli';
  123 + $this->features = array(
  124 + 'prepare' => false,
  125 + 'ssl' => true,
  126 + 'transactions' => true,
  127 + 'limit' => 'alter'
  128 + );
  129 + $this->errorcode_map = array(
  130 + 1004 => DB_ERROR_CANNOT_CREATE,
  131 + 1005 => DB_ERROR_CANNOT_CREATE,
  132 + 1006 => DB_ERROR_CANNOT_CREATE,
  133 + 1007 => DB_ERROR_ALREADY_EXISTS,
  134 + 1008 => DB_ERROR_CANNOT_DROP,
  135 + 1022 => DB_ERROR_ALREADY_EXISTS,
  136 + 1046 => DB_ERROR_NODBSELECTED,
  137 + 1048 => DB_ERROR_CONSTRAINT,
  138 + 1050 => DB_ERROR_ALREADY_EXISTS,
  139 + 1051 => DB_ERROR_NOSUCHTABLE,
  140 + 1054 => DB_ERROR_NOSUCHFIELD,
  141 + 1062 => DB_ERROR_ALREADY_EXISTS,
  142 + 1064 => DB_ERROR_SYNTAX,
  143 + 1100 => DB_ERROR_NOT_LOCKED,
  144 + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
  145 + 1146 => DB_ERROR_NOSUCHTABLE,
  146 + 1216 => DB_ERROR_CONSTRAINT,
  147 + 1217 => DB_ERROR_CONSTRAINT,
  148 + );
  149 + }
  150 +
  151 + // }}}
  152 + // {{{ connect()
  153 +
  154 + /**
  155 + * Connect to a database and log in as the specified user.
  156 + *
  157 + * @param string $dsn the data source name (see DB::parseDSN for syntax)
  158 + * @param boolean $persistent (optional) whether the connection should
  159 + * be persistent
  160 + * @return mixed DB_OK on success, a DB error on failure
  161 + * @access public
  162 + */
  163 + function connect($dsninfo, $persistent = false)
  164 + {
  165 + if (!DB::assertExtension('mysqli')) {
  166 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  167 + }
  168 +
  169 + $this->dsn = $dsninfo;
  170 + $conn = false;
  171 + @ini_set('track_errors', true);
  172 +
  173 + if ($this->getOption('ssl') === true) {
  174 + $init = mysqli_init();
  175 + mysqli_ssl_set(
  176 + $init,
  177 + empty($dsninfo['key']) ? null : $dsninfo['key'],
  178 + empty($dsninfo['cert']) ? null : $dsninfo['cert'],
  179 + empty($dsninfo['ca']) ? null : $dsninfo['ca'],
  180 + empty($dsninfo['capath']) ? null : $dsninfo['capath'],
  181 + empty($dsninfo['cipher']) ? null : $dsninfo['cipher']
  182 + );
  183 + if ($conn = @mysqli_real_connect($init,
  184 + $dsninfo['hostspec'],
  185 + $dsninfo['username'],
  186 + $dsninfo['password'],
  187 + $dsninfo['database'],
  188 + $dsninfo['port'],
  189 + $dsninfo['socket']))
  190 + {
  191 + $conn = $init;
  192 + }
  193 + } else {
  194 + $conn = @mysqli_connect(
  195 + $dsninfo['hostspec'],
  196 + $dsninfo['username'],
  197 + $dsninfo['password'],
  198 + $dsninfo['database'],
  199 + $dsninfo['port'],
  200 + $dsninfo['socket']
  201 + );
  202 + }
  203 +
  204 + @ini_restore('track_errors');
  205 +
  206 + if (!$conn) {
  207 + if (($err = @mysqli_connect_error()) != '') {
  208 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  209 + null, $err);
  210 + } elseif (empty($php_errormsg)) {
  211 + return $this->raiseError(DB_ERROR_CONNECT_FAILED);
  212 + } else {
  213 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  214 + null, $php_errormsg);
  215 + }
  216 + }
  217 +
  218 + if ($dsninfo['database']) {
  219 + $this->_db = $dsninfo['database'];
  220 + }
  221 +
  222 + $this->connection = $conn;
  223 + return DB_OK;
  224 + }
  225 +
  226 + // }}}
  227 + // {{{ disconnect()
  228 +
  229 + /**
  230 + * Log out and disconnect from the database.
  231 + *
  232 + * @return boolean true on success, false if not connected
  233 + * @access public
  234 + */
  235 + function disconnect()
  236 + {
  237 + $ret = @mysqli_close($this->connection);
  238 + $this->connection = null;
  239 + return $ret;
  240 + }
  241 +
  242 + // }}}
  243 + // {{{ simpleQuery()
  244 +
  245 + /**
  246 + * Send a query to MySQL and return the results as a MySQL resource
  247 + * identifier.
  248 + *
  249 + * @param string $query the SQL query
  250 + * @return mixed a valid MySQL result for successful SELECT
  251 + * queries, DB_OK for other successful queries.
  252 + * A DB error is returned on failure.
  253 + * @access public
  254 + */
  255 + function simpleQuery($query)
  256 + {
  257 + $ismanip = DB::isManip($query);
  258 + $this->last_query = $query;
  259 + $query = $this->modifyQuery($query);
  260 + if ($this->_db) {
  261 + if (!@mysqli_select_db($this->connection, $this->_db)) {
  262 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  263 + }
  264 + }
  265 + if (!$this->autocommit && $ismanip) {
  266 + if ($this->transaction_opcount == 0) {
  267 + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=0');
  268 + $result = @mysqli_query($this->connection, 'BEGIN');
  269 + if (!$result) {
  270 + return $this->mysqlRaiseError();
  271 + }
  272 + }
  273 + $this->transaction_opcount++;
  274 + }
  275 + $result = @mysqli_query($this->connection, $query);
  276 + if (!$result) {
  277 + return $this->mysqlRaiseError();
  278 + }
  279 +# this next block is still sketchy..
  280 + if (is_object($result)) {
  281 + $numrows = $this->numrows($result);
  282 + if (is_object($numrows)) {
  283 + return $numrows;
  284 + }
  285 +# need to come up with different means for next line
  286 +# since $result is object (int)$result won't fly...
  287 +// $this->num_rows[(int)$result] = $numrows;
  288 + return $result;
  289 + }
  290 + return DB_OK;
  291 + }
  292 +
  293 + // }}}
  294 + // {{{ nextResult()
  295 +
  296 + /**
  297 + * Move the internal mysql result pointer to the next available result.
  298 + *
  299 + * This method has not been implemented yet.
  300 + *
  301 + * @param resource $result a valid sql result resource
  302 + * @return false
  303 + * @access public
  304 + */
  305 + function nextResult($result)
  306 + {
  307 + return false;
  308 + }
  309 +
  310 + // }}}
  311 + // {{{ fetchInto()
  312 +
  313 + /**
  314 + * Fetch a row and insert the data into an existing array.
  315 + *
  316 + * Formating of the array and the data therein are configurable.
  317 + * See DB_result::fetchInto() for more information.
  318 + *
  319 + * @param resource $result query result identifier
  320 + * @param array $arr (reference) array where data from the row
  321 + * should be placed
  322 + * @param int $fetchmode how the resulting array should be indexed
  323 + * @param int $rownum the row number to fetch
  324 + *
  325 + * @return mixed DB_OK on success, null when end of result set is
  326 + * reached or on failure
  327 + *
  328 + * @see DB_result::fetchInto()
  329 + * @access private
  330 + */
  331 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  332 + {
  333 + if ($rownum !== null) {
  334 + if (!@mysqli_data_seek($result, $rownum)) {
  335 + return null;
  336 + }
  337 + }
  338 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  339 + $arr = @mysqli_fetch_array($result, MYSQLI_ASSOC);
  340 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  341 + $arr = array_change_key_case($arr, CASE_LOWER);
  342 + }
  343 + } else {
  344 + $arr = @mysqli_fetch_row($result);
  345 + }
  346 + if (!$arr) {
  347 + $errno = @mysqli_errno($this->connection);
  348 + if (!$errno) {
  349 + return null;
  350 + }
  351 + return $this->mysqlRaiseError($errno);
  352 + }
  353 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  354 + /*
  355 + * Even though this DBMS already trims output, we do this because
  356 + * a field might have intentional whitespace at the end that
  357 + * gets removed by DB_PORTABILITY_RTRIM under another driver.
  358 + */
  359 + $this->_rtrimArrayValues($arr);
  360 + }
  361 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  362 + $this->_convertNullArrayValuesToEmpty($arr);
  363 + }
  364 + return DB_OK;
  365 + }
  366 +
  367 + // }}}
  368 + // {{{ freeResult()
  369 +
  370 + /**
  371 + * Free the internal resources associated with $result.
  372 + *
  373 + * @param resource $result MySQL result identifier
  374 + * @return bool true on success, false if $result is invalid
  375 + * @access public
  376 + */
  377 + function freeResult($result)
  378 + {
  379 +# need to come up with different means for next line
  380 +# since $result is object (int)$result won't fly...
  381 +// unset($this->num_rows[(int)$result]);
  382 + return @mysqli_free_result($result);
  383 + }
  384 +
  385 + // }}}
  386 + // {{{ numCols()
  387 +
  388 + /**
  389 + * Get the number of columns in a result set.
  390 + *
  391 + * @param $result MySQL result identifier
  392 + *
  393 + * @access public
  394 + *
  395 + * @return int the number of columns per row in $result
  396 + */
  397 + function numCols($result)
  398 + {
  399 + $cols = @mysqli_num_fields($result);
  400 +
  401 + if (!$cols) {
  402 + return $this->mysqlRaiseError();
  403 + }
  404 +
  405 + return $cols;
  406 + }
  407 +
  408 + // }}}
  409 + // {{{ numRows()
  410 +
  411 + /**
  412 + * Get the number of rows in a result set.
  413 + *
  414 + * @param resource $result MySQL result identifier
  415 + * @return int the number of rows in $result
  416 + * @access public
  417 + */
  418 + function numRows($result)
  419 + {
  420 + $rows = @mysqli_num_rows($result);
  421 + if ($rows === null) {
  422 + return $this->mysqlRaiseError();
  423 + }
  424 + return $rows;
  425 + }
  426 +
  427 + // }}}
  428 + // {{{ autoCommit()
  429 +
  430 + /**
  431 + * Enable/disable automatic commits.
  432 + */
  433 + function autoCommit($onoff = false)
  434 + {
  435 + // XXX if $this->transaction_opcount > 0, we should probably
  436 + // issue a warning here.
  437 + $this->autocommit = $onoff ? true : false;
  438 + return DB_OK;
  439 + }
  440 +
  441 + // }}}
  442 + // {{{ commit()
  443 +
  444 + /**
  445 + * Commit the current transaction.
  446 + */
  447 + function commit()
  448 + {
  449 + if ($this->transaction_opcount > 0) {
  450 + if ($this->_db) {
  451 + if (!@mysqli_select_db($this->connection, $this->_db)) {
  452 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  453 + }
  454 + }
  455 + $result = @mysqli_query($this->connection, 'COMMIT');
  456 + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1');
  457 + $this->transaction_opcount = 0;
  458 + if (!$result) {
  459 + return $this->mysqlRaiseError();
  460 + }
  461 + }
  462 + return DB_OK;
  463 + }
  464 +
  465 + // }}}
  466 + // {{{ rollback()
  467 +
  468 + /**
  469 + * Roll back (undo) the current transaction.
  470 + */
  471 + function rollback()
  472 + {
  473 + if ($this->transaction_opcount > 0) {
  474 + if ($this->_db) {
  475 + if (!@mysqli_select_db($this->connection, $this->_db)) {
  476 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  477 + }
  478 + }
  479 + $result = @mysqli_query($this->connection, 'ROLLBACK');
  480 + $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1');
  481 + $this->transaction_opcount = 0;
  482 + if (!$result) {
  483 + return $this->mysqlRaiseError();
  484 + }
  485 + }
  486 + return DB_OK;
  487 + }
  488 +
  489 + // }}}
  490 + // {{{ affectedRows()
  491 +
  492 + /**
  493 + * Gets the number of rows affected by the data manipulation
  494 + * query. For other queries, this function returns 0.
  495 + *
  496 + * @return integer number of rows affected by the last query
  497 + */
  498 + function affectedRows()
  499 + {
  500 + if (DB::isManip($this->last_query)) {
  501 + return @mysqli_affected_rows($this->connection);
  502 + } else {
  503 + return 0;
  504 + }
  505 + }
  506 +
  507 + // }}}
  508 + // {{{ errorNative()
  509 +
  510 + /**
  511 + * Get the native error code of the last error (if any) that
  512 + * occured on the current connection.
  513 + *
  514 + * @return int native MySQL error code
  515 + * @access public
  516 + */
  517 + function errorNative()
  518 + {
  519 + return @mysqli_errno($this->connection);
  520 + }
  521 +
  522 + // }}}
  523 + // {{{ nextId()
  524 +
  525 + /**
  526 + * Returns the next free id in a sequence
  527 + *
  528 + * @param string $seq_name name of the sequence
  529 + * @param boolean $ondemand when true, the seqence is automatically
  530 + * created if it does not exist
  531 + *
  532 + * @return int the next id number in the sequence. DB_Error if problem.
  533 + *
  534 + * @internal
  535 + * @see DB_common::nextID()
  536 + * @access public
  537 + */
  538 + function nextId($seq_name, $ondemand = true)
  539 + {
  540 + $seqname = $this->getSequenceName($seq_name);
  541 + do {
  542 + $repeat = 0;
  543 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  544 + $result = $this->query("UPDATE ${seqname} ".
  545 + 'SET id=LAST_INSERT_ID(id+1)');
  546 + $this->popErrorHandling();
  547 + if ($result === DB_OK) {
  548 + /** COMMON CASE **/
  549 + $id = @mysqli_insert_id($this->connection);
  550 + if ($id != 0) {
  551 + return $id;
  552 + }
  553 + /** EMPTY SEQ TABLE **/
  554 + // Sequence table must be empty for some reason, so fill it and return 1
  555 + // Obtain a user-level lock
  556 + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
  557 + if (DB::isError($result)) {
  558 + return $this->raiseError($result);
  559 + }
  560 + if ($result == 0) {
  561 + // Failed to get the lock, bail with a DB_ERROR_NOT_LOCKED error
  562 + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
  563 + }
  564 +
  565 + // add the default value
  566 + $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)");
  567 + if (DB::isError($result)) {
  568 + return $this->raiseError($result);
  569 + }
  570 +
  571 + // Release the lock
  572 + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
  573 + if (DB::isError($result)) {
  574 + return $this->raiseError($result);
  575 + }
  576 + // We know what the result will be, so no need to try again
  577 + return 1;
  578 +
  579 + /** ONDEMAND TABLE CREATION **/
  580 + } elseif ($ondemand && DB::isError($result) &&
  581 + $result->getCode() == DB_ERROR_NOSUCHTABLE)
  582 + {
  583 + $result = $this->createSequence($seq_name);
  584 + // Since createSequence initializes the ID to be 1,
  585 + // we do not need to retrieve the ID again (or we will get 2)
  586 + if (DB::isError($result)) {
  587 + return $this->raiseError($result);
  588 + } else {
  589 + // First ID of a newly created sequence is 1
  590 + return 1;
  591 + }
  592 +
  593 + /** BACKWARDS COMPAT **/
  594 + } elseif (DB::isError($result) &&
  595 + $result->getCode() == DB_ERROR_ALREADY_EXISTS)
  596 + {
  597 + // see _BCsequence() comment
  598 + $result = $this->_BCsequence($seqname);
  599 + if (DB::isError($result)) {
  600 + return $this->raiseError($result);
  601 + }
  602 + $repeat = 1;
  603 + }
  604 + } while ($repeat);
  605 +
  606 + return $this->raiseError($result);
  607 + }
  608 +
  609 + /**
  610 + * Creates a new sequence
  611 + *
  612 + * @param string $seq_name name of the new sequence
  613 + *
  614 + * @return int DB_OK on success. A DB_Error object is returned if
  615 + * problems arise.
  616 + *
  617 + * @internal
  618 + * @see DB_common::createSequence()
  619 + * @access public
  620 + */
  621 + function createSequence($seq_name)
  622 + {
  623 + $seqname = $this->getSequenceName($seq_name);
  624 + $res = $this->query("CREATE TABLE ${seqname} ".
  625 + '(id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'.
  626 + ' PRIMARY KEY(id))');
  627 + if (DB::isError($res)) {
  628 + return $res;
  629 + }
  630 + // insert yields value 1, nextId call will generate ID 2
  631 + return $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
  632 + }
  633 +
  634 + // }}}
  635 + // {{{ dropSequence()
  636 +
  637 + /**
  638 + * Deletes a sequence
  639 + *
  640 + * @param string $seq_name name of the sequence to be deleted
  641 + *
  642 + * @return int DB_OK on success. DB_Error if problems.
  643 + *
  644 + * @internal
  645 + * @see DB_common::dropSequence()
  646 + * @access public
  647 + */
  648 + function dropSequence($seq_name)
  649 + {
  650 + return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
  651 + }
  652 +
  653 + // }}}
  654 + // {{{ _BCsequence()
  655 +
  656 + /**
  657 + * Backwards compatibility with old sequence emulation implementation
  658 + * (clean up the dupes).
  659 + *
  660 + * @param string $seqname The sequence name to clean up
  661 + * @return mixed DB_Error or true
  662 + */
  663 + function _BCsequence($seqname)
  664 + {
  665 + // Obtain a user-level lock... this will release any previous
  666 + // application locks, but unlike LOCK TABLES, it does not abort
  667 + // the current transaction and is much less frequently used.
  668 + $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
  669 + if (DB::isError($result)) {
  670 + return $result;
  671 + }
  672 + if ($result == 0) {
  673 + // Failed to get the lock, can't do the conversion, bail
  674 + // with a DB_ERROR_NOT_LOCKED error
  675 + return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
  676 + }
  677 +
  678 + $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
  679 + if (DB::isError($highest_id)) {
  680 + return $highest_id;
  681 + }
  682 + // This should kill all rows except the highest
  683 + // We should probably do something if $highest_id isn't
  684 + // numeric, but I'm at a loss as how to handle that...
  685 + $result = $this->query("DELETE FROM ${seqname} WHERE id <> $highest_id");
  686 + if (DB::isError($result)) {
  687 + return $result;
  688 + }
  689 +
  690 + // If another thread has been waiting for this lock,
  691 + // it will go thru the above procedure, but will have no
  692 + // real effect
  693 + $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
  694 + if (DB::isError($result)) {
  695 + return $result;
  696 + }
  697 + return true;
  698 + }
  699 +
  700 + // }}}
  701 + // {{{ quoteIdentifier()
  702 +
  703 + /**
  704 + * Quote a string so it can be safely used as a table or column name
  705 + *
  706 + * Quoting style depends on which database driver is being used.
  707 + *
  708 + * MySQL can't handle the backtick character (<kbd>`</kbd>) in
  709 + * table or column names.
  710 + *
  711 + * @param string $str identifier name to be quoted
  712 + *
  713 + * @return string quoted identifier string
  714 + *
  715 + * @since 1.6.0
  716 + * @access public
  717 + * @internal
  718 + */
  719 + function quoteIdentifier($str)
  720 + {
  721 + return '`' . $str . '`';
  722 + }
  723 +
  724 + // }}}
  725 + // {{{ escapeSimple()
  726 +
  727 + /**
  728 + * Escape a string according to the current DBMS's standards
  729 + *
  730 + * @param string $str the string to be escaped
  731 + *
  732 + * @return string the escaped string
  733 + *
  734 + * @internal
  735 + */
  736 + function escapeSimple($str) {
  737 + return @mysqli_real_escape_string($this->connection, $str);
  738 + }
  739 +
  740 + // }}}
  741 + // {{{ modifyQuery()
  742 +
  743 + function modifyQuery($query)
  744 + {
  745 + return $query;
  746 + }
  747 +
  748 + // }}}
  749 + // {{{ modifyLimitQuery()
  750 +
  751 + function modifyLimitQuery($query, $from, $count, $params = array())
  752 + {
  753 + if (DB::isManip($query)) {
  754 + return $query . " LIMIT $count";
  755 + } else {
  756 + return $query . " LIMIT $from, $count";
  757 + }
  758 + }
  759 +
  760 + // }}}
  761 + // {{{ mysqlRaiseError()
  762 +
  763 + /**
  764 + * Gather information about an error, then use that info to create a
  765 + * DB error object and finally return that object.
  766 + *
  767 + * @param integer $errno PEAR error number (usually a DB constant) if
  768 + * manually raising an error
  769 + * @return object DB error object
  770 + * @see DB_common::errorCode()
  771 + * @see DB_common::raiseError()
  772 + */
  773 + function mysqlRaiseError($errno = null)
  774 + {
  775 + if ($errno === null) {
  776 + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
  777 + $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
  778 + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
  779 + $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
  780 + } else {
  781 + // Doing this in case mode changes during runtime.
  782 + $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
  783 + $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
  784 + $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
  785 + }
  786 + $errno = $this->errorCode(mysqli_errno($this->connection));
  787 + }
  788 + return $this->raiseError($errno, null, null, null,
  789 + @mysqli_errno($this->connection) . ' ** ' .
  790 + @mysqli_error($this->connection));
  791 + }
  792 +
  793 + // }}}
  794 + // {{{ tableInfo()
  795 +
  796 + /**
  797 + * Returns information about a table or a result set.
  798 + *
  799 + * WARNING: this method will probably not work because the mysqli_*()
  800 + * functions it relies upon may not exist.
  801 + *
  802 + * @param object|string $result DB_result object from a query or a
  803 + * string containing the name of a table
  804 + * @param int $mode a valid tableInfo mode
  805 + * @return array an associative array with the information requested
  806 + * or an error object if something is wrong
  807 + * @access public
  808 + * @internal
  809 + * @see DB_common::tableInfo()
  810 + */
  811 + function tableInfo($result, $mode = null) {
  812 + if (isset($result->result)) {
  813 + /*
  814 + * Probably received a result object.
  815 + * Extract the result resource identifier.
  816 + */
  817 + $id = $result->result;
  818 + $got_string = false;
  819 + } elseif (is_string($result)) {
  820 + /*
  821 + * Probably received a table name.
  822 + * Create a result resource identifier.
  823 + */
  824 + $id = @mysqli_query($this->connection,
  825 + "SELECT * FROM $result LIMIT 0");
  826 + $got_string = true;
  827 + } else {
  828 + /*
  829 + * Probably received a result resource identifier.
  830 + * Copy it.
  831 + * Deprecated. Here for compatibility only.
  832 + */
  833 + $id = $result;
  834 + $got_string = false;
  835 + }
  836 +
  837 + if (!is_a($id, 'mysqli_result')) {
  838 + return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA);
  839 + }
  840 +
  841 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  842 + $case_func = 'strtolower';
  843 + } else {
  844 + $case_func = 'strval';
  845 + }
  846 +
  847 + $count = @mysqli_num_fields($id);
  848 +
  849 + // made this IF due to performance (one if is faster than $count if's)
  850 + if (!$mode) {
  851 + for ($i=0; $i<$count; $i++) {
  852 + $tmp = @mysqli_fetch_field($id);
  853 + $res[$i]['table'] = $case_func($tmp->table);
  854 + $res[$i]['name'] = $case_func($tmp->name);
  855 + $res[$i]['type'] = isset($this->mysqli_types[$tmp->type]) ?
  856 + $this->mysqli_types[$tmp->type] :
  857 + 'unknown';
  858 + $res[$i]['len'] = $tmp->max_length;
  859 +
  860 + $res[$i]['flags'] = '';
  861 + foreach ($this->mysqli_flags as $const => $means) {
  862 + if ($tmp->flags & $const) {
  863 + $res[$i]['flags'] .= $means . ' ';
  864 + }
  865 + }
  866 + if ($tmp->def) {
  867 + $res[$i]['flags'] .= 'default_' . rawurlencode($tmp->def);
  868 + }
  869 + $res[$i]['flags'] = trim($res[$i]['flags']);
  870 + }
  871 + } else { // full
  872 + $res['num_fields']= $count;
  873 +
  874 + for ($i=0; $i<$count; $i++) {
  875 + $tmp = @mysqli_fetch_field($id);
  876 + $res[$i]['table'] = $case_func($tmp->table);
  877 + $res[$i]['name'] = $case_func($tmp->name);
  878 + $res[$i]['type'] = isset($this->mysqli_types[$tmp->type]) ?
  879 + $this->mysqli_types[$tmp->type] :
  880 + 'unknown';
  881 + $res[$i]['len'] = $tmp->max_length;
  882 +
  883 + $res[$i]['flags'] = '';
  884 + foreach ($this->mysqli_flags as $const => $means) {
  885 + if ($tmp->flags & $const) {
  886 + $res[$i]['flags'] .= $means . ' ';
  887 + }
  888 + }
  889 + if ($tmp->def) {
  890 + $res[$i]['flags'] .= 'default_' . rawurlencode($tmp->def);
  891 + }
  892 + $res[$i]['flags'] = trim($res[$i]['flags']);
  893 +
  894 + if ($mode & DB_TABLEINFO_ORDER) {
  895 + $res['order'][$res[$i]['name']] = $i;
  896 + }
  897 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  898 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  899 + }
  900 + }
  901 + }
  902 +
  903 + // free the result only if we were called on a table
  904 + if ($got_string) {
  905 + @mysqli_free_result($id);
  906 + }
  907 + return $res;
  908 + }
  909 +
  910 + // }}}
  911 + // {{{ getSpecialQuery()
  912 +
  913 + /**
  914 + * Returns the query needed to get some backend info.
  915 + *
  916 + * @param string $type What kind of info you want to retrieve
  917 + * @return string The SQL query string
  918 + */
  919 + function getSpecialQuery($type)
  920 + {
  921 + switch ($type) {
  922 + case 'tables':
  923 + return 'SHOW TABLES';
  924 + case 'views':
  925 + return DB_ERROR_NOT_CAPABLE;
  926 + case 'users':
  927 + $sql = 'select distinct User from user';
  928 + if ($this->dsn['database'] != 'mysql') {
  929 + $dsn = $this->dsn;
  930 + $dsn['database'] = 'mysql';
  931 + if (DB::isError($db = DB::connect($dsn))) {
  932 + return $db;
  933 + }
  934 + $sql = $db->getCol($sql);
  935 + $db->disconnect();
  936 + // XXX Fixme the mysql driver should take care of this
  937 + if (!@mysqli_select_db($this->connection, $this->dsn['database'])) {
  938 + return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
  939 + }
  940 + }
  941 + return $sql;
  942 + case 'databases':
  943 + return 'SHOW DATABASES';
  944 + default:
  945 + return null;
  946 + }
  947 + }
  948 +
  949 + // }}}
  950 +
  951 +}
  952 +
  953 +/*
  954 + * Local variables:
  955 + * tab-width: 4
  956 + * c-basic-offset: 4
  957 + * End:
  958 + */
  959 +
  960 +?>
... ...
pear/DB/oci8.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: James L. Pine <jlp@valinux.com> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// be aware... OCIError() only appears to return anything when given a
  24 +// statement, so functions return the generic DB_ERROR instead of more
  25 +// useful errors that have to do with feedback from the database.
  26 +
  27 +
  28 +require_once 'DB/common.php';
  29 +
  30 +/**
  31 + * Database independent query interface definition for PHP's Oracle 8
  32 + * call-interface extension.
  33 + *
  34 + * Definitely works with versions 8 and 9 of Oracle.
  35 + *
  36 + * @package DB
  37 + * @version $Id$
  38 + * @category Database
  39 + * @author James L. Pine <jlp@valinux.com>
  40 + */
  41 +class DB_oci8 extends DB_common
  42 +{
  43 + // {{{ properties
  44 +
  45 + var $connection;
  46 + var $phptype, $dbsyntax;
  47 + var $manip_query = array();
  48 + var $prepare_types = array();
  49 + var $autoCommit = 1;
  50 + var $last_stmt = false;
  51 +
  52 + /**
  53 + * stores the $data passed to execute() in the oci8 driver
  54 + *
  55 + * Gets reset to array() when simpleQuery() is run.
  56 + *
  57 + * Needed in case user wants to call numRows() after prepare/execute
  58 + * was used.
  59 + *
  60 + * @var array
  61 + * @access private
  62 + */
  63 + var $_data = array();
  64 +
  65 + // }}}
  66 + // {{{ constructor
  67 +
  68 + function DB_oci8()
  69 + {
  70 + $this->DB_common();
  71 + $this->phptype = 'oci8';
  72 + $this->dbsyntax = 'oci8';
  73 + $this->features = array(
  74 + 'prepare' => false,
  75 + 'pconnect' => true,
  76 + 'transactions' => true,
  77 + 'limit' => 'alter'
  78 + );
  79 + $this->errorcode_map = array(
  80 + 1 => DB_ERROR_CONSTRAINT,
  81 + 900 => DB_ERROR_SYNTAX,
  82 + 904 => DB_ERROR_NOSUCHFIELD,
  83 + 921 => DB_ERROR_SYNTAX,
  84 + 923 => DB_ERROR_SYNTAX,
  85 + 942 => DB_ERROR_NOSUCHTABLE,
  86 + 955 => DB_ERROR_ALREADY_EXISTS,
  87 + 1400 => DB_ERROR_CONSTRAINT_NOT_NULL,
  88 + 1407 => DB_ERROR_CONSTRAINT_NOT_NULL,
  89 + 1476 => DB_ERROR_DIVZERO,
  90 + 1722 => DB_ERROR_INVALID_NUMBER,
  91 + 2289 => DB_ERROR_NOSUCHTABLE,
  92 + 2291 => DB_ERROR_CONSTRAINT,
  93 + 2292 => DB_ERROR_CONSTRAINT,
  94 + 2449 => DB_ERROR_CONSTRAINT,
  95 + );
  96 + }
  97 +
  98 + // }}}
  99 + // {{{ connect()
  100 +
  101 + /**
  102 + * Connect to a database and log in as the specified user.
  103 + *
  104 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  105 + * @param $persistent (optional) whether the connection should
  106 + * be persistent
  107 + *
  108 + * @return int DB_OK on success, a DB error code on failure
  109 + */
  110 + function connect($dsninfo, $persistent = false)
  111 + {
  112 + if (!DB::assertExtension('oci8')) {
  113 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  114 + }
  115 + $this->dsn = $dsninfo;
  116 +
  117 + $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon';
  118 +
  119 + if ($dsninfo['hostspec']) {
  120 + $conn = @$connect_function($dsninfo['username'],
  121 + $dsninfo['password'],
  122 + $dsninfo['hostspec']);
  123 + } elseif ($dsninfo['username'] || $dsninfo['password']) {
  124 + $conn = @$connect_function($dsninfo['username'],
  125 + $dsninfo['password']);
  126 + } else {
  127 + $conn = false;
  128 + }
  129 + if ($conn == false) {
  130 + $error = OCIError();
  131 + $error = (is_array($error)) ? $error['message'] : null;
  132 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  133 + null, $error);
  134 + }
  135 + $this->connection = $conn;
  136 + return DB_OK;
  137 + }
  138 +
  139 + // }}}
  140 + // {{{ disconnect()
  141 +
  142 + /**
  143 + * Log out and disconnect from the database.
  144 + *
  145 + * @return bool true on success, false if not connected.
  146 + */
  147 + function disconnect()
  148 + {
  149 + $ret = @OCILogOff($this->connection);
  150 + $this->connection = null;
  151 + return $ret;
  152 + }
  153 +
  154 + // }}}
  155 + // {{{ simpleQuery()
  156 +
  157 + /**
  158 + * Send a query to oracle and return the results as an oci8 resource
  159 + * identifier.
  160 + *
  161 + * @param $query the SQL query
  162 + *
  163 + * @return int returns a valid oci8 result for successful SELECT
  164 + * queries, DB_OK for other successful queries. A DB error code
  165 + * is returned on failure.
  166 + */
  167 + function simpleQuery($query)
  168 + {
  169 + $this->_data = array();
  170 + $this->last_query = $query;
  171 + $query = $this->modifyQuery($query);
  172 + $result = @OCIParse($this->connection, $query);
  173 + if (!$result) {
  174 + return $this->oci8RaiseError();
  175 + }
  176 + if ($this->autoCommit) {
  177 + $success = @OCIExecute($result,OCI_COMMIT_ON_SUCCESS);
  178 + } else {
  179 + $success = @OCIExecute($result,OCI_DEFAULT);
  180 + }
  181 + if (!$success) {
  182 + return $this->oci8RaiseError($result);
  183 + }
  184 + $this->last_stmt=$result;
  185 + // Determine which queries that should return data, and which
  186 + // should return an error code only.
  187 + return DB::isManip($query) ? DB_OK : $result;
  188 + }
  189 +
  190 + // }}}
  191 + // {{{ nextResult()
  192 +
  193 + /**
  194 + * Move the internal oracle result pointer to the next available result
  195 + *
  196 + * @param a valid oci8 result resource
  197 + *
  198 + * @access public
  199 + *
  200 + * @return true if a result is available otherwise return false
  201 + */
  202 + function nextResult($result)
  203 + {
  204 + return false;
  205 + }
  206 +
  207 + // }}}
  208 + // {{{ fetchInto()
  209 +
  210 + /**
  211 + * Fetch a row and insert the data into an existing array.
  212 + *
  213 + * Formating of the array and the data therein are configurable.
  214 + * See DB_result::fetchInto() for more information.
  215 + *
  216 + * @param resource $result query result identifier
  217 + * @param array $arr (reference) array where data from the row
  218 + * should be placed
  219 + * @param int $fetchmode how the resulting array should be indexed
  220 + * @param int $rownum the row number to fetch
  221 + *
  222 + * @return mixed DB_OK on success, null when end of result set is
  223 + * reached or on failure
  224 + *
  225 + * @see DB_result::fetchInto()
  226 + * @access private
  227 + */
  228 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  229 + {
  230 + if ($rownum !== null) {
  231 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  232 + }
  233 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  234 + $moredata = @OCIFetchInto($result,$arr,OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS);
  235 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE &&
  236 + $moredata)
  237 + {
  238 + $arr = array_change_key_case($arr, CASE_LOWER);
  239 + }
  240 + } else {
  241 + $moredata = OCIFetchInto($result,$arr,OCI_RETURN_NULLS+OCI_RETURN_LOBS);
  242 + }
  243 + if (!$moredata) {
  244 + return null;
  245 + }
  246 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  247 + $this->_rtrimArrayValues($arr);
  248 + }
  249 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  250 + $this->_convertNullArrayValuesToEmpty($arr);
  251 + }
  252 + return DB_OK;
  253 + }
  254 +
  255 + // }}}
  256 + // {{{ freeResult()
  257 +
  258 + /**
  259 + * Free the internal resources associated with $result.
  260 + *
  261 + * @param $result oci8 result identifier
  262 + *
  263 + * @return bool true on success, false if $result is invalid
  264 + */
  265 + function freeResult($result)
  266 + {
  267 + return @OCIFreeStatement($result);
  268 + }
  269 +
  270 + /**
  271 + * Free the internal resources associated with a prepared query.
  272 + *
  273 + * @param $stmt oci8 statement identifier
  274 + *
  275 + * @return bool true on success, false if $result is invalid
  276 + */
  277 + function freePrepared($stmt)
  278 + {
  279 + if (isset($this->prepare_types[(int)$stmt])) {
  280 + unset($this->prepare_types[(int)$stmt]);
  281 + unset($this->manip_query[(int)$stmt]);
  282 + } else {
  283 + return false;
  284 + }
  285 + return true;
  286 + }
  287 +
  288 + // }}}
  289 + // {{{ numRows()
  290 +
  291 + function numRows($result)
  292 + {
  293 + // emulate numRows for Oracle. yuck.
  294 + if ($this->options['portability'] & DB_PORTABILITY_NUMROWS &&
  295 + $result === $this->last_stmt)
  296 + {
  297 + $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')';
  298 + $save_query = $this->last_query;
  299 + $save_stmt = $this->last_stmt;
  300 +
  301 + if (count($this->_data)) {
  302 + $smt = $this->prepare('SELECT COUNT(*) FROM ('.$this->last_query.')');
  303 + $count = $this->execute($smt, $this->_data);
  304 + } else {
  305 + $count =& $this->query($countquery);
  306 + }
  307 +
  308 + if (DB::isError($count) ||
  309 + DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED)))
  310 + {
  311 + $this->last_query = $save_query;
  312 + $this->last_stmt = $save_stmt;
  313 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  314 + }
  315 + return $row[0];
  316 + }
  317 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  318 + }
  319 +
  320 + // }}}
  321 + // {{{ numCols()
  322 +
  323 + /**
  324 + * Get the number of columns in a result set.
  325 + *
  326 + * @param $result oci8 result identifier
  327 + *
  328 + * @return int the number of columns per row in $result
  329 + */
  330 + function numCols($result)
  331 + {
  332 + $cols = @OCINumCols($result);
  333 + if (!$cols) {
  334 + return $this->oci8RaiseError($result);
  335 + }
  336 + return $cols;
  337 + }
  338 +
  339 + // }}}
  340 + // {{{ errorNative()
  341 +
  342 + /**
  343 + * Get the native error code of the last error (if any) that occured
  344 + * on the current connection. This does not work, as OCIError does
  345 + * not work unless given a statement. If OCIError does return
  346 + * something, so will this.
  347 + *
  348 + * @return int native oci8 error code
  349 + */
  350 + function errorNative()
  351 + {
  352 + if (is_resource($this->last_stmt)) {
  353 + $error = @OCIError($this->last_stmt);
  354 + } else {
  355 + $error = @OCIError($this->connection);
  356 + }
  357 + if (is_array($error)) {
  358 + return $error['code'];
  359 + }
  360 + return false;
  361 + }
  362 +
  363 + // }}}
  364 + // {{{ prepare()
  365 +
  366 + /**
  367 + * Prepares a query for multiple execution with execute().
  368 + *
  369 + * With oci8, this is emulated.
  370 + *
  371 + * prepare() requires a generic query as string like <code>
  372 + * INSERT INTO numbers VALUES (?, ?, ?)
  373 + * </code>. The <kbd>?</kbd> characters are placeholders.
  374 + *
  375 + * Three types of placeholders can be used:
  376 + * + <kbd>?</kbd> a quoted scalar value, i.e. strings, integers
  377 + * + <kbd>!</kbd> value is inserted 'as is'
  378 + * + <kbd>&</kbd> requires a file name. The file's contents get
  379 + * inserted into the query (i.e. saving binary
  380 + * data in a db)
  381 + *
  382 + * Use backslashes to escape placeholder characters if you don't want
  383 + * them to be interpreted as placeholders. Example: <code>
  384 + * "UPDATE foo SET col=? WHERE col='over \& under'"
  385 + * </code>
  386 + *
  387 + * @param string $query query to be prepared
  388 + * @return mixed DB statement resource on success. DB_Error on failure.
  389 + */
  390 + function prepare($query)
  391 + {
  392 + $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1,
  393 + PREG_SPLIT_DELIM_CAPTURE);
  394 + $binds = count($tokens) - 1;
  395 + $token = 0;
  396 + $types = array();
  397 + $newquery = '';
  398 +
  399 + foreach ($tokens as $key => $val) {
  400 + switch ($val) {
  401 + case '?':
  402 + $types[$token++] = DB_PARAM_SCALAR;
  403 + unset($tokens[$key]);
  404 + break;
  405 + case '&':
  406 + $types[$token++] = DB_PARAM_OPAQUE;
  407 + unset($tokens[$key]);
  408 + break;
  409 + case '!':
  410 + $types[$token++] = DB_PARAM_MISC;
  411 + unset($tokens[$key]);
  412 + break;
  413 + default:
  414 + $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
  415 + if ($key != $binds) {
  416 + $newquery .= $tokens[$key] . ':bind' . $token;
  417 + } else {
  418 + $newquery .= $tokens[$key];
  419 + }
  420 + }
  421 + }
  422 +
  423 + $this->last_query = $query;
  424 + $newquery = $this->modifyQuery($newquery);
  425 + if (!$stmt = @OCIParse($this->connection, $newquery)) {
  426 + return $this->oci8RaiseError();
  427 + }
  428 + $this->prepare_types[(int)$stmt] = $types;
  429 + $this->manip_query[(int)$stmt] = DB::isManip($query);
  430 + return $stmt;
  431 + }
  432 +
  433 + // }}}
  434 + // {{{ execute()
  435 +
  436 + /**
  437 + * Executes a DB statement prepared with prepare().
  438 + *
  439 + * @param resource $stmt a DB statement resource returned from prepare()
  440 + * @param mixed $data array, string or numeric data to be used in
  441 + * execution of the statement. Quantity of items
  442 + * passed must match quantity of placeholders in
  443 + * query: meaning 1 for non-array items or the
  444 + * quantity of elements in the array.
  445 + * @return int returns an oci8 result resource for successful
  446 + * SELECT queries, DB_OK for other successful queries. A DB error
  447 + * code is returned on failure.
  448 + * @see DB_oci::prepare()
  449 + */
  450 + function &execute($stmt, $data = array())
  451 + {
  452 + if (!is_array($data)) {
  453 + $data = array($data);
  454 + }
  455 +
  456 + $this->_data = $data;
  457 +
  458 + $types =& $this->prepare_types[(int)$stmt];
  459 + if (count($types) != count($data)) {
  460 + $tmp =& $this->raiseError(DB_ERROR_MISMATCH);
  461 + return $tmp;
  462 + }
  463 +
  464 + $i = 0;
  465 + foreach ($data as $key => $value) {
  466 + if ($types[$i] == DB_PARAM_MISC) {
  467 + /*
  468 + * Oracle doesn't seem to have the ability to pass a
  469 + * parameter along unchanged, so strip off quotes from start
  470 + * and end, plus turn two single quotes to one single quote,
  471 + * in order to avoid the quotes getting escaped by
  472 + * Oracle and ending up in the database.
  473 + */
  474 + $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
  475 + $data[$key] = str_replace("''", "'", $data[$key]);
  476 + } elseif ($types[$i] == DB_PARAM_OPAQUE) {
  477 + $fp = @fopen($data[$key], 'rb');
  478 + if (!$fp) {
  479 + $tmp =& $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
  480 + return $tmp;
  481 + }
  482 + $data[$key] = fread($fp, filesize($data[$key]));
  483 + fclose($fp);
  484 + }
  485 + if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) {
  486 + $tmp = $this->oci8RaiseError($stmt);
  487 + return $tmp;
  488 + }
  489 + $i++;
  490 + }
  491 + if ($this->autoCommit) {
  492 + $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS);
  493 + } else {
  494 + $success = @OCIExecute($stmt, OCI_DEFAULT);
  495 + }
  496 + if (!$success) {
  497 + $tmp = $this->oci8RaiseError($stmt);
  498 + return $tmp;
  499 + }
  500 + $this->last_stmt = $stmt;
  501 + if ($this->manip_query[(int)$stmt]) {
  502 + $tmp = DB_OK;
  503 + } else {
  504 + $tmp =& new DB_result($this, $stmt);
  505 + }
  506 + return $tmp;
  507 + }
  508 +
  509 + // }}}
  510 + // {{{ autoCommit()
  511 +
  512 + /**
  513 + * Enable/disable automatic commits
  514 + *
  515 + * @param $onoff true/false whether to autocommit
  516 + */
  517 + function autoCommit($onoff = false)
  518 + {
  519 + $this->autoCommit = (bool)$onoff;;
  520 + return DB_OK;
  521 + }
  522 +
  523 + // }}}
  524 + // {{{ commit()
  525 +
  526 + /**
  527 + * Commit transactions on the current connection
  528 + *
  529 + * @return DB_ERROR or DB_OK
  530 + */
  531 + function commit()
  532 + {
  533 + $result = @OCICommit($this->connection);
  534 + if (!$result) {
  535 + return $this->oci8RaiseError();
  536 + }
  537 + return DB_OK;
  538 + }
  539 +
  540 + // }}}
  541 + // {{{ rollback()
  542 +
  543 + /**
  544 + * Roll back all uncommitted transactions on the current connection.
  545 + *
  546 + * @return DB_ERROR or DB_OK
  547 + */
  548 + function rollback()
  549 + {
  550 + $result = @OCIRollback($this->connection);
  551 + if (!$result) {
  552 + return $this->oci8RaiseError();
  553 + }
  554 + return DB_OK;
  555 + }
  556 +
  557 + // }}}
  558 + // {{{ affectedRows()
  559 +
  560 + /**
  561 + * Gets the number of rows affected by the last query.
  562 + * if the last query was a select, returns 0.
  563 + *
  564 + * @return number of rows affected by the last query or DB_ERROR
  565 + */
  566 + function affectedRows()
  567 + {
  568 + if ($this->last_stmt === false) {
  569 + return $this->oci8RaiseError();
  570 + }
  571 + $result = @OCIRowCount($this->last_stmt);
  572 + if ($result === false) {
  573 + return $this->oci8RaiseError($this->last_stmt);
  574 + }
  575 + return $result;
  576 + }
  577 +
  578 + // }}}
  579 + // {{{ modifyQuery()
  580 +
  581 + function modifyQuery($query)
  582 + {
  583 + // "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle
  584 + if (preg_match('/^\s*SELECT/i', $query) &&
  585 + !preg_match('/\sFROM\s/i', $query)) {
  586 + $query .= ' FROM dual';
  587 + }
  588 + return $query;
  589 + }
  590 +
  591 + // }}}
  592 + // {{{ modifyLimitQuery()
  593 +
  594 + /**
  595 + * Emulate the row limit support altering the query
  596 + *
  597 + * @param string $query The query to treat
  598 + * @param int $from The row to start to fetch from
  599 + * @param int $count The offset
  600 + * @return string The modified query
  601 + *
  602 + * @author Tomas V.V.Cox <cox@idecnet.com>
  603 + */
  604 + function modifyLimitQuery($query, $from, $count, $params = array())
  605 + {
  606 + // Let Oracle return the name of the columns instead of
  607 + // coding a "home" SQL parser
  608 +
  609 + if (count($params)) {
  610 + $result = $this->prepare("SELECT * FROM ($query) "
  611 + . 'WHERE NULL = NULL');
  612 + $tmp =& $this->execute($result, $params);
  613 + } else {
  614 + $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL";
  615 +
  616 + if (!$result = @OCIParse($this->connection, $q_fields)) {
  617 + $this->last_query = $q_fields;
  618 + return $this->oci8RaiseError();
  619 + }
  620 + if (!@OCIExecute($result, OCI_DEFAULT)) {
  621 + $this->last_query = $q_fields;
  622 + return $this->oci8RaiseError($result);
  623 + }
  624 + }
  625 +
  626 + $ncols = OCINumCols($result);
  627 + $cols = array();
  628 + for ( $i = 1; $i <= $ncols; $i++ ) {
  629 + $cols[] = '"' . OCIColumnName($result, $i) . '"';
  630 + }
  631 + $fields = implode(', ', $cols);
  632 + // XXX Test that (tip by John Lim)
  633 + //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) {
  634 + // // Introduce the FIRST_ROWS Oracle query optimizer
  635 + // $query = substr($query, strlen($match[0]), strlen($query));
  636 + // $query = "SELECT /* +FIRST_ROWS */ " . $query;
  637 + //}
  638 +
  639 + // Construct the query
  640 + // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2
  641 + // Perhaps this could be optimized with the use of Unions
  642 + $query = "SELECT $fields FROM".
  643 + " (SELECT rownum as linenum, $fields FROM".
  644 + " ($query)".
  645 + ' WHERE rownum <= '. ($from + $count) .
  646 + ') WHERE linenum >= ' . ++$from;
  647 + return $query;
  648 + }
  649 +
  650 + // }}}
  651 + // {{{ nextId()
  652 +
  653 + /**
  654 + * Returns the next free id in a sequence
  655 + *
  656 + * @param string $seq_name name of the sequence
  657 + * @param boolean $ondemand when true, the seqence is automatically
  658 + * created if it does not exist
  659 + *
  660 + * @return int the next id number in the sequence. DB_Error if problem.
  661 + *
  662 + * @internal
  663 + * @see DB_common::nextID()
  664 + * @access public
  665 + */
  666 + function nextId($seq_name, $ondemand = true)
  667 + {
  668 + $seqname = $this->getSequenceName($seq_name);
  669 + $repeat = 0;
  670 + do {
  671 + $this->expectError(DB_ERROR_NOSUCHTABLE);
  672 + $result =& $this->query("SELECT ${seqname}.nextval FROM dual");
  673 + $this->popExpect();
  674 + if ($ondemand && DB::isError($result) &&
  675 + $result->getCode() == DB_ERROR_NOSUCHTABLE) {
  676 + $repeat = 1;
  677 + $result = $this->createSequence($seq_name);
  678 + if (DB::isError($result)) {
  679 + return $this->raiseError($result);
  680 + }
  681 + } else {
  682 + $repeat = 0;
  683 + }
  684 + } while ($repeat);
  685 + if (DB::isError($result)) {
  686 + return $this->raiseError($result);
  687 + }
  688 + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
  689 + return $arr[0];
  690 + }
  691 +
  692 + /**
  693 + * Creates a new sequence
  694 + *
  695 + * @param string $seq_name name of the new sequence
  696 + *
  697 + * @return int DB_OK on success. A DB_Error object is returned if
  698 + * problems arise.
  699 + *
  700 + * @internal
  701 + * @see DB_common::createSequence()
  702 + * @access public
  703 + */
  704 + function createSequence($seq_name)
  705 + {
  706 + $seqname = $this->getSequenceName($seq_name);
  707 + return $this->query("CREATE SEQUENCE ${seqname}");
  708 + }
  709 +
  710 + // }}}
  711 + // {{{ dropSequence()
  712 +
  713 + /**
  714 + * Deletes a sequence
  715 + *
  716 + * @param string $seq_name name of the sequence to be deleted
  717 + *
  718 + * @return int DB_OK on success. DB_Error if problems.
  719 + *
  720 + * @internal
  721 + * @see DB_common::dropSequence()
  722 + * @access public
  723 + */
  724 + function dropSequence($seq_name)
  725 + {
  726 + $seqname = $this->getSequenceName($seq_name);
  727 + return $this->query("DROP SEQUENCE ${seqname}");
  728 + }
  729 +
  730 + // }}}
  731 + // {{{ oci8RaiseError()
  732 +
  733 + /**
  734 + * Gather information about an error, then use that info to create a
  735 + * DB error object and finally return that object.
  736 + *
  737 + * @param integer $errno PEAR error number (usually a DB constant) if
  738 + * manually raising an error
  739 + * @return object DB error object
  740 + * @see DB_common::errorCode()
  741 + * @see DB_common::raiseError()
  742 + */
  743 + function oci8RaiseError($errno = null)
  744 + {
  745 + if ($errno === null) {
  746 + $error = @OCIError($this->connection);
  747 + return $this->raiseError($this->errorCode($error['code']),
  748 + null, null, null, $error['message']);
  749 + } elseif (is_resource($errno)) {
  750 + $error = @OCIError($errno);
  751 + return $this->raiseError($this->errorCode($error['code']),
  752 + null, null, null, $error['message']);
  753 + }
  754 + return $this->raiseError($this->errorCode($errno));
  755 + }
  756 +
  757 + // }}}
  758 + // {{{ getSpecialQuery()
  759 +
  760 + /**
  761 + * Returns the query needed to get some backend info
  762 + * @param string $type What kind of info you want to retrieve
  763 + * @return string The SQL query string
  764 + */
  765 + function getSpecialQuery($type)
  766 + {
  767 + switch ($type) {
  768 + case 'tables':
  769 + return 'SELECT table_name FROM user_tables';
  770 + default:
  771 + return null;
  772 + }
  773 + }
  774 +
  775 + // }}}
  776 + // {{{ tableInfo()
  777 +
  778 + /**
  779 + * Returns information about a table or a result set.
  780 + *
  781 + * NOTE: only supports 'table' and 'flags' if <var>$result</var>
  782 + * is a table name.
  783 + *
  784 + * NOTE: flags won't contain index information.
  785 + *
  786 + * @param object|string $result DB_result object from a query or a
  787 + * string containing the name of a table
  788 + * @param int $mode a valid tableInfo mode
  789 + * @return array an associative array with the information requested
  790 + * or an error object if something is wrong
  791 + * @access public
  792 + * @internal
  793 + * @see DB_common::tableInfo()
  794 + */
  795 + function tableInfo($result, $mode = null)
  796 + {
  797 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  798 + $case_func = 'strtolower';
  799 + } else {
  800 + $case_func = 'strval';
  801 + }
  802 +
  803 + if (is_string($result)) {
  804 + /*
  805 + * Probably received a table name.
  806 + * Create a result resource identifier.
  807 + */
  808 + $result = strtoupper($result);
  809 + $q_fields = 'SELECT column_name, data_type, data_length, '
  810 + . 'nullable '
  811 + . 'FROM user_tab_columns '
  812 + . "WHERE table_name='$result' ORDER BY column_id";
  813 +
  814 + $this->last_query = $q_fields;
  815 +
  816 + if (!$stmt = @OCIParse($this->connection, $q_fields)) {
  817 + return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA);
  818 + }
  819 + if (!@OCIExecute($stmt, OCI_DEFAULT)) {
  820 + return $this->oci8RaiseError($stmt);
  821 + }
  822 +
  823 + $i = 0;
  824 + while (@OCIFetch($stmt)) {
  825 + $res[$i]['table'] = $case_func($result);
  826 + $res[$i]['name'] = $case_func(@OCIResult($stmt, 1));
  827 + $res[$i]['type'] = @OCIResult($stmt, 2);
  828 + $res[$i]['len'] = @OCIResult($stmt, 3);
  829 + $res[$i]['flags'] = (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '';
  830 +
  831 + if ($mode & DB_TABLEINFO_ORDER) {
  832 + $res['order'][$res[$i]['name']] = $i;
  833 + }
  834 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  835 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  836 + }
  837 + $i++;
  838 + }
  839 +
  840 + if ($mode) {
  841 + $res['num_fields'] = $i;
  842 + }
  843 + @OCIFreeStatement($stmt);
  844 +
  845 + } else {
  846 + if (isset($result->result)) {
  847 + /*
  848 + * Probably received a result object.
  849 + * Extract the result resource identifier.
  850 + */
  851 + $result = $result->result;
  852 + } else {
  853 + /*
  854 + * ELSE, probably received a result resource identifier.
  855 + * Deprecated. Here for compatibility only.
  856 + */
  857 + }
  858 +
  859 + if ($result === $this->last_stmt) {
  860 + $count = @OCINumCols($result);
  861 +
  862 + for ($i=0; $i<$count; $i++) {
  863 + $res[$i]['table'] = '';
  864 + $res[$i]['name'] = $case_func(@OCIColumnName($result, $i+1));
  865 + $res[$i]['type'] = @OCIColumnType($result, $i+1);
  866 + $res[$i]['len'] = @OCIColumnSize($result, $i+1);
  867 + $res[$i]['flags'] = '';
  868 +
  869 + if ($mode & DB_TABLEINFO_ORDER) {
  870 + $res['order'][$res[$i]['name']] = $i;
  871 + }
  872 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  873 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  874 + }
  875 + }
  876 +
  877 + if ($mode) {
  878 + $res['num_fields'] = $count;
  879 + }
  880 +
  881 + } else {
  882 + return $this->raiseError(DB_ERROR_NOT_CAPABLE);
  883 + }
  884 + }
  885 + return $res;
  886 + }
  887 +
  888 + // }}}
  889 +
  890 +}
  891 +
  892 +/*
  893 + * Local variables:
  894 + * tab-width: 4
  895 + * c-basic-offset: 4
  896 + * End:
  897 + */
  898 +
  899 +?>
... ...
pear/DB/odbc.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Stig Bakken <ssb@php.net> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +
  23 +// XXX legend:
  24 +// More info on ODBC errors could be found here:
  25 +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
  26 +//
  27 +// XXX ERRORMSG: The error message from the odbc function should
  28 +// be registered here.
  29 +
  30 +
  31 +require_once 'DB/common.php';
  32 +
  33 +/**
  34 + * Database independent query interface definition for PHP's ODBC
  35 + * extension.
  36 + *
  37 + * @package DB
  38 + * @version $Id$
  39 + * @category Database
  40 + * @author Stig Bakken <ssb@php.net>
  41 + */
  42 +class DB_odbc extends DB_common
  43 +{
  44 + // {{{ properties
  45 +
  46 + var $connection;
  47 + var $phptype, $dbsyntax;
  48 + var $row = array();
  49 +
  50 + // }}}
  51 + // {{{ constructor
  52 +
  53 + function DB_odbc()
  54 + {
  55 + $this->DB_common();
  56 + $this->phptype = 'odbc';
  57 + $this->dbsyntax = 'sql92';
  58 + $this->features = array(
  59 + 'prepare' => true,
  60 + 'pconnect' => true,
  61 + 'transactions' => false,
  62 + 'limit' => 'emulate'
  63 + );
  64 + $this->errorcode_map = array(
  65 + '01004' => DB_ERROR_TRUNCATED,
  66 + '07001' => DB_ERROR_MISMATCH,
  67 + '21S01' => DB_ERROR_MISMATCH,
  68 + '21S02' => DB_ERROR_MISMATCH,
  69 + '22003' => DB_ERROR_INVALID_NUMBER,
  70 + '22005' => DB_ERROR_INVALID_NUMBER,
  71 + '22008' => DB_ERROR_INVALID_DATE,
  72 + '22012' => DB_ERROR_DIVZERO,
  73 + '23000' => DB_ERROR_CONSTRAINT,
  74 + '23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
  75 + '23503' => DB_ERROR_CONSTRAINT,
  76 + '23505' => DB_ERROR_CONSTRAINT,
  77 + '24000' => DB_ERROR_INVALID,
  78 + '34000' => DB_ERROR_INVALID,
  79 + '37000' => DB_ERROR_SYNTAX,
  80 + '42000' => DB_ERROR_SYNTAX,
  81 + '42601' => DB_ERROR_SYNTAX,
  82 + 'IM001' => DB_ERROR_UNSUPPORTED,
  83 + 'S0000' => DB_ERROR_NOSUCHTABLE,
  84 + 'S0001' => DB_ERROR_ALREADY_EXISTS,
  85 + 'S0002' => DB_ERROR_NOSUCHTABLE,
  86 + 'S0011' => DB_ERROR_ALREADY_EXISTS,
  87 + 'S0012' => DB_ERROR_NOT_FOUND,
  88 + 'S0021' => DB_ERROR_ALREADY_EXISTS,
  89 + 'S0022' => DB_ERROR_NOSUCHFIELD,
  90 + 'S1000' => DB_ERROR_CONSTRAINT_NOT_NULL,
  91 + 'S1009' => DB_ERROR_INVALID,
  92 + 'S1090' => DB_ERROR_INVALID,
  93 + 'S1C00' => DB_ERROR_NOT_CAPABLE
  94 + );
  95 + }
  96 +
  97 + // }}}
  98 + // {{{ connect()
  99 +
  100 + /**
  101 + * Connect to a database and log in as the specified user.
  102 + *
  103 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  104 + * @param $persistent (optional) whether the connection should
  105 + * be persistent
  106 + *
  107 + * @return int DB_OK on success, a DB error code on failure
  108 + */
  109 + function connect($dsninfo, $persistent = false)
  110 + {
  111 + if (!DB::assertExtension('odbc')) {
  112 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  113 + }
  114 +
  115 + $this->dsn = $dsninfo;
  116 + if ($dsninfo['dbsyntax']) {
  117 + $this->dbsyntax = $dsninfo['dbsyntax'];
  118 + }
  119 + switch ($this->dbsyntax) {
  120 + case 'solid':
  121 + $this->features = array(
  122 + 'prepare' => true,
  123 + 'pconnect' => true,
  124 + 'transactions' => true
  125 + );
  126 + break;
  127 + case 'navision':
  128 + // the Navision driver doesn't support fetch row by number
  129 + $this->features['limit'] = false;
  130 + }
  131 +
  132 + /*
  133 + * This is hear for backwards compatibility.
  134 + * Should have been using 'database' all along, but used hostspec.
  135 + */
  136 + if ($dsninfo['database']) {
  137 + $odbcdsn = $dsninfo['database'];
  138 + } elseif ($dsninfo['hostspec']) {
  139 + $odbcdsn = $dsninfo['hostspec'];
  140 + } else {
  141 + $odbcdsn = 'localhost';
  142 + }
  143 +
  144 + if ($this->provides('pconnect')) {
  145 + $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
  146 + } else {
  147 + $connect_function = 'odbc_connect';
  148 + }
  149 +
  150 + if (empty($dsninfo['cursor'])) {
  151 + $conn = @$connect_function($odbcdsn, $dsninfo['username'],
  152 + $dsninfo['password']);
  153 + } else {
  154 + $conn = @$connect_function($odbcdsn, $dsninfo['username'],
  155 + $dsninfo['password'],
  156 + $dsninfo['cursor']);
  157 + }
  158 +
  159 + if (!is_resource($conn)) {
  160 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null, null,
  161 + null, $this->errorNative());
  162 + }
  163 + $this->connection = $conn;
  164 + return DB_OK;
  165 + }
  166 +
  167 + // }}}
  168 + // {{{ disconnect()
  169 +
  170 + function disconnect()
  171 + {
  172 + $err = @odbc_close($this->connection);
  173 + $this->connection = null;
  174 + return $err;
  175 + }
  176 +
  177 + // }}}
  178 + // {{{ simpleQuery()
  179 +
  180 + /**
  181 + * Send a query to ODBC and return the results as a ODBC resource
  182 + * identifier.
  183 + *
  184 + * @param $query the SQL query
  185 + *
  186 + * @return int returns a valid ODBC result for successful SELECT
  187 + * queries, DB_OK for other successful queries. A DB error code
  188 + * is returned on failure.
  189 + */
  190 + function simpleQuery($query)
  191 + {
  192 + $this->last_query = $query;
  193 + $query = $this->modifyQuery($query);
  194 + $result = @odbc_exec($this->connection, $query);
  195 + if (!$result) {
  196 + return $this->odbcRaiseError(); // XXX ERRORMSG
  197 + }
  198 + // Determine which queries that should return data, and which
  199 + // should return an error code only.
  200 + if (DB::isManip($query)) {
  201 + $this->manip_result = $result; // For affectedRows()
  202 + return DB_OK;
  203 + }
  204 + $this->row[(int)$result] = 0;
  205 + $this->manip_result = 0;
  206 + return $result;
  207 + }
  208 +
  209 + // }}}
  210 + // {{{ nextResult()
  211 +
  212 + /**
  213 + * Move the internal odbc result pointer to the next available result
  214 + *
  215 + * @param a valid fbsql result resource
  216 + *
  217 + * @access public
  218 + *
  219 + * @return true if a result is available otherwise return false
  220 + */
  221 + function nextResult($result)
  222 + {
  223 + return @odbc_next_result($result);
  224 + }
  225 +
  226 + // }}}
  227 + // {{{ fetchInto()
  228 +
  229 + /**
  230 + * Fetch a row and insert the data into an existing array.
  231 + *
  232 + * Formating of the array and the data therein are configurable.
  233 + * See DB_result::fetchInto() for more information.
  234 + *
  235 + * @param resource $result query result identifier
  236 + * @param array $arr (reference) array where data from the row
  237 + * should be placed
  238 + * @param int $fetchmode how the resulting array should be indexed
  239 + * @param int $rownum the row number to fetch
  240 + *
  241 + * @return mixed DB_OK on success, null when end of result set is
  242 + * reached or on failure
  243 + *
  244 + * @see DB_result::fetchInto()
  245 + * @access private
  246 + */
  247 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  248 + {
  249 + $arr = array();
  250 + if ($rownum !== null) {
  251 + $rownum++; // ODBC first row is 1
  252 + if (version_compare(phpversion(), '4.2.0', 'ge')) {
  253 + $cols = @odbc_fetch_into($result, $arr, $rownum);
  254 + } else {
  255 + $cols = @odbc_fetch_into($result, $rownum, $arr);
  256 + }
  257 + } else {
  258 + $cols = @odbc_fetch_into($result, $arr);
  259 + }
  260 +
  261 + if (!$cols) {
  262 + /* XXX FIXME: doesn't work with unixODBC and easysoft
  263 + (get corrupted $errno values)
  264 + if ($errno = @odbc_error($this->connection)) {
  265 + return $this->RaiseError($errno);
  266 + }*/
  267 + return null;
  268 + }
  269 + if ($fetchmode !== DB_FETCHMODE_ORDERED) {
  270 + for ($i = 0; $i < count($arr); $i++) {
  271 + $colName = @odbc_field_name($result, $i+1);
  272 + $a[$colName] = $arr[$i];
  273 + }
  274 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  275 + $a = array_change_key_case($a, CASE_LOWER);
  276 + }
  277 + $arr = $a;
  278 + }
  279 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  280 + $this->_rtrimArrayValues($arr);
  281 + }
  282 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  283 + $this->_convertNullArrayValuesToEmpty($arr);
  284 + }
  285 + return DB_OK;
  286 + }
  287 +
  288 + // }}}
  289 + // {{{ freeResult()
  290 +
  291 + function freeResult($result)
  292 + {
  293 + unset($this->row[(int)$result]);
  294 + return @odbc_free_result($result);
  295 + }
  296 +
  297 + // }}}
  298 + // {{{ numCols()
  299 +
  300 + function numCols($result)
  301 + {
  302 + $cols = @odbc_num_fields($result);
  303 + if (!$cols) {
  304 + return $this->odbcRaiseError();
  305 + }
  306 + return $cols;
  307 + }
  308 +
  309 + // }}}
  310 + // {{{ affectedRows()
  311 +
  312 + /**
  313 + * Returns the number of rows affected by a manipulative query
  314 + * (INSERT, DELETE, UPDATE)
  315 + * @return mixed int affected rows, 0 when non manip queries or
  316 + * DB error on error
  317 + */
  318 + function affectedRows()
  319 + {
  320 + if (empty($this->manip_result)) { // In case of SELECT stms
  321 + return 0;
  322 + }
  323 + $nrows = @odbc_num_rows($this->manip_result);
  324 + if ($nrows == -1) {
  325 + return $this->odbcRaiseError();
  326 + }
  327 + return $nrows;
  328 + }
  329 +
  330 + // }}}
  331 + // {{{ numRows()
  332 +
  333 + /**
  334 + * ODBC may or may not support counting rows in the result set of
  335 + * SELECTs.
  336 + *
  337 + * @param $result the odbc result resource
  338 + * @return the number of rows, or 0
  339 + */
  340 + function numRows($result)
  341 + {
  342 + $nrows = @odbc_num_rows($result);
  343 + if ($nrows == -1) {
  344 + return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
  345 + }
  346 + return $nrows;
  347 + }
  348 +
  349 + // }}}
  350 + // {{{ quoteIdentifier()
  351 +
  352 + /**
  353 + * Quote a string so it can be safely used as a table / column name
  354 + *
  355 + * Quoting style depends on which dbsyntax was passed in the DSN.
  356 + *
  357 + * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
  358 + * "Use ANSI quoted identifiers" when setting up the ODBC data source.
  359 + *
  360 + * @param string $str identifier name to be quoted
  361 + *
  362 + * @return string quoted identifier string
  363 + *
  364 + * @since 1.6.0
  365 + * @access public
  366 + */
  367 + function quoteIdentifier($str)
  368 + {
  369 + switch ($this->dsn['dbsyntax']) {
  370 + case 'access':
  371 + return '[' . $str . ']';
  372 + case 'mssql':
  373 + case 'sybase':
  374 + return '[' . str_replace(']', ']]', $str) . ']';
  375 + case 'mysql':
  376 + case 'mysqli':
  377 + return '`' . $str . '`';
  378 + default:
  379 + return '"' . str_replace('"', '""', $str) . '"';
  380 + }
  381 + }
  382 +
  383 + // }}}
  384 + // {{{ quote()
  385 +
  386 + /**
  387 + * @deprecated Deprecated in release 1.6.0
  388 + * @internal
  389 + */
  390 + function quote($str) {
  391 + return $this->quoteSmart($str);
  392 + }
  393 +
  394 + // }}}
  395 + // {{{ errorNative()
  396 +
  397 + /**
  398 + * Get the native error code of the last error (if any) that
  399 + * occured on the current connection.
  400 + *
  401 + * @access public
  402 + *
  403 + * @return int ODBC error code
  404 + */
  405 + function errorNative()
  406 + {
  407 + if (!isset($this->connection) || !is_resource($this->connection)) {
  408 + return @odbc_error() . ' ' . @odbc_errormsg();
  409 + }
  410 + return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection);
  411 + }
  412 +
  413 + // }}}
  414 + // {{{ nextId()
  415 +
  416 + /**
  417 + * Returns the next free id in a sequence
  418 + *
  419 + * @param string $seq_name name of the sequence
  420 + * @param boolean $ondemand when true, the seqence is automatically
  421 + * created if it does not exist
  422 + *
  423 + * @return int the next id number in the sequence. DB_Error if problem.
  424 + *
  425 + * @internal
  426 + * @see DB_common::nextID()
  427 + * @access public
  428 + */
  429 + function nextId($seq_name, $ondemand = true)
  430 + {
  431 + $seqname = $this->getSequenceName($seq_name);
  432 + $repeat = 0;
  433 + do {
  434 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  435 + $result = $this->query("update ${seqname} set id = id + 1");
  436 + $this->popErrorHandling();
  437 + if ($ondemand && DB::isError($result) &&
  438 + $result->getCode() == DB_ERROR_NOSUCHTABLE) {
  439 + $repeat = 1;
  440 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  441 + $result = $this->createSequence($seq_name);
  442 + $this->popErrorHandling();
  443 + if (DB::isError($result)) {
  444 + return $this->raiseError($result);
  445 + }
  446 + $result = $this->query("insert into ${seqname} (id) values(0)");
  447 + } else {
  448 + $repeat = 0;
  449 + }
  450 + } while ($repeat);
  451 +
  452 + if (DB::isError($result)) {
  453 + return $this->raiseError($result);
  454 + }
  455 +
  456 + $result = $this->query("select id from ${seqname}");
  457 + if (DB::isError($result)) {
  458 + return $result;
  459 + }
  460 +
  461 + $row = $result->fetchRow(DB_FETCHMODE_ORDERED);
  462 + if (DB::isError($row || !$row)) {
  463 + return $row;
  464 + }
  465 +
  466 + return $row[0];
  467 + }
  468 +
  469 + /**
  470 + * Creates a new sequence
  471 + *
  472 + * @param string $seq_name name of the new sequence
  473 + *
  474 + * @return int DB_OK on success. A DB_Error object is returned if
  475 + * problems arise.
  476 + *
  477 + * @internal
  478 + * @see DB_common::createSequence()
  479 + * @access public
  480 + */
  481 + function createSequence($seq_name)
  482 + {
  483 + $seqname = $this->getSequenceName($seq_name);
  484 + return $this->query("CREATE TABLE ${seqname} ".
  485 + '(id integer NOT NULL,'.
  486 + ' PRIMARY KEY(id))');
  487 + }
  488 +
  489 + // }}}
  490 + // {{{ dropSequence()
  491 +
  492 + /**
  493 + * Deletes a sequence
  494 + *
  495 + * @param string $seq_name name of the sequence to be deleted
  496 + *
  497 + * @return int DB_OK on success. DB_Error if problems.
  498 + *
  499 + * @internal
  500 + * @see DB_common::dropSequence()
  501 + * @access public
  502 + */
  503 + function dropSequence($seq_name)
  504 + {
  505 + $seqname = $this->getSequenceName($seq_name);
  506 + return $this->query("DROP TABLE ${seqname}");
  507 + }
  508 +
  509 + // }}}
  510 + // {{{ autoCommit()
  511 +
  512 + function autoCommit($onoff = false)
  513 + {
  514 + if (!@odbc_autocommit($this->connection, $onoff)) {
  515 + return $this->odbcRaiseError();
  516 + }
  517 + return DB_OK;
  518 + }
  519 +
  520 + // }}}
  521 + // {{{ commit()
  522 +
  523 + function commit()
  524 + {
  525 + if (!@odbc_commit($this->connection)) {
  526 + return $this->odbcRaiseError();
  527 + }
  528 + return DB_OK;
  529 + }
  530 +
  531 + // }}}
  532 + // {{{ rollback()
  533 +
  534 + function rollback()
  535 + {
  536 + if (!@odbc_rollback($this->connection)) {
  537 + return $this->odbcRaiseError();
  538 + }
  539 + return DB_OK;
  540 + }
  541 +
  542 + // }}}
  543 + // {{{ odbcRaiseError()
  544 +
  545 + /**
  546 + * Gather information about an error, then use that info to create a
  547 + * DB error object and finally return that object.
  548 + *
  549 + * @param integer $errno PEAR error number (usually a DB constant) if
  550 + * manually raising an error
  551 + * @return object DB error object
  552 + * @see errorNative()
  553 + * @see DB_common::errorCode()
  554 + * @see DB_common::raiseError()
  555 + */
  556 + function odbcRaiseError($errno = null)
  557 + {
  558 + if ($errno === null) {
  559 + switch ($this->dbsyntax) {
  560 + case 'access':
  561 + if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
  562 + $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD;
  563 + } else {
  564 + // Doing this in case mode changes during runtime.
  565 + $this->errorcode_map['07001'] = DB_ERROR_MISMATCH;
  566 + }
  567 + }
  568 + $errno = $this->errorCode(odbc_error($this->connection));
  569 + }
  570 + return $this->raiseError($errno, null, null, null,
  571 + $this->errorNative());
  572 + }
  573 +
  574 + // }}}
  575 +
  576 +}
  577 +
  578 +/*
  579 + * Local variables:
  580 + * tab-width: 4
  581 + * c-basic-offset: 4
  582 + * End:
  583 + */
  584 +
  585 +?>
... ...
pear/DB/pgsql.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Authors: Rui Hirokawa <hirokawa@php.net> |
  17 +// | Stig Bakken <ssb@php.net> |
  18 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  19 +// +----------------------------------------------------------------------+
  20 +//
  21 +// $Id$
  22 +
  23 +require_once 'DB/common.php';
  24 +
  25 +/**
  26 + * Database independent query interface definition for PHP's PostgreSQL
  27 + * extension.
  28 + *
  29 + * @package DB
  30 + * @version $Id$
  31 + * @category Database
  32 + * @author Rui Hirokawa <hirokawa@php.net>
  33 + * @author Stig Bakken <ssb@php.net>
  34 + */
  35 +class DB_pgsql extends DB_common
  36 +{
  37 + // {{{ properties
  38 +
  39 + var $connection;
  40 + var $phptype, $dbsyntax;
  41 + var $prepare_tokens = array();
  42 + var $prepare_types = array();
  43 + var $transaction_opcount = 0;
  44 + var $dsn = array();
  45 + var $row = array();
  46 + var $num_rows = array();
  47 + var $affected = 0;
  48 + var $autocommit = true;
  49 + var $fetchmode = DB_FETCHMODE_ORDERED;
  50 +
  51 + // }}}
  52 + // {{{ constructor
  53 +
  54 + function DB_pgsql()
  55 + {
  56 + $this->DB_common();
  57 + $this->phptype = 'pgsql';
  58 + $this->dbsyntax = 'pgsql';
  59 + $this->features = array(
  60 + 'prepare' => false,
  61 + 'pconnect' => true,
  62 + 'transactions' => true,
  63 + 'limit' => 'alter'
  64 + );
  65 + $this->errorcode_map = array(
  66 + );
  67 + }
  68 +
  69 + // }}}
  70 + // {{{ connect()
  71 +
  72 + /**
  73 + * Connect to a database and log in as the specified user.
  74 + *
  75 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  76 + * @param $persistent (optional) whether the connection should
  77 + * be persistent
  78 + *
  79 + * @return int DB_OK on success, a DB error code on failure.
  80 + */
  81 + function connect($dsninfo, $persistent = false)
  82 + {
  83 + if (!DB::assertExtension('pgsql')) {
  84 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  85 + }
  86 +
  87 + $this->dsn = $dsninfo;
  88 + $protocol = $dsninfo['protocol'] ? $dsninfo['protocol'] : 'tcp';
  89 + $connstr = '';
  90 +
  91 + if ($protocol == 'tcp') {
  92 + if ($dsninfo['hostspec']) {
  93 + $connstr .= 'host=' . $dsninfo['hostspec'];
  94 + }
  95 + if ($dsninfo['port']) {
  96 + $connstr .= ' port=' . $dsninfo['port'];
  97 + }
  98 + } elseif ($protocol == 'unix') {
  99 + // Allow for pg socket in non-standard locations.
  100 + if ($dsninfo['socket']) {
  101 + $connstr .= 'host=' . $dsninfo['socket'];
  102 + }
  103 + if ($dsninfo['port']) {
  104 + $connstr .= ' port=' . $dsninfo['port'];
  105 + }
  106 + }
  107 +
  108 + if ($dsninfo['database']) {
  109 + $connstr .= ' dbname=\'' . addslashes($dsninfo['database']) . '\'';
  110 + }
  111 + if ($dsninfo['username']) {
  112 + $connstr .= ' user=\'' . addslashes($dsninfo['username']) . '\'';
  113 + }
  114 + if ($dsninfo['password']) {
  115 + $connstr .= ' password=\'' . addslashes($dsninfo['password']) . '\'';
  116 + }
  117 + if (!empty($dsninfo['options'])) {
  118 + $connstr .= ' options=' . $dsninfo['options'];
  119 + }
  120 + if (!empty($dsninfo['tty'])) {
  121 + $connstr .= ' tty=' . $dsninfo['tty'];
  122 + }
  123 +
  124 + $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect';
  125 +
  126 + $ini = ini_get('track_errors');
  127 + if ($ini) {
  128 + $conn = @$connect_function($connstr);
  129 + } else {
  130 + ini_set('track_errors', 1);
  131 + $conn = @$connect_function($connstr);
  132 + ini_set('track_errors', $ini);
  133 + }
  134 + if ($conn == false) {
  135 + return $this->raiseError(DB_ERROR_CONNECT_FAILED, null,
  136 + null, null, strip_tags($php_errormsg));
  137 + }
  138 + $this->connection = $conn;
  139 + return DB_OK;
  140 + }
  141 +
  142 + // }}}
  143 + // {{{ disconnect()
  144 +
  145 + /**
  146 + * Log out and disconnect from the database.
  147 + *
  148 + * @return bool true on success, false if not connected.
  149 + */
  150 + function disconnect()
  151 + {
  152 + $ret = @pg_close($this->connection);
  153 + $this->connection = null;
  154 + return $ret;
  155 + }
  156 +
  157 + // }}}
  158 + // {{{ simpleQuery()
  159 +
  160 + /**
  161 + * Send a query to PostgreSQL and return the results as a
  162 + * PostgreSQL resource identifier.
  163 + *
  164 + * @param $query the SQL query
  165 + *
  166 + * @return int returns a valid PostgreSQL result for successful SELECT
  167 + * queries, DB_OK for other successful queries. A DB error code
  168 + * is returned on failure.
  169 + */
  170 + function simpleQuery($query)
  171 + {
  172 + $ismanip = DB::isManip($query);
  173 + $this->last_query = $query;
  174 + $query = $this->modifyQuery($query);
  175 + if (!$this->autocommit && $ismanip) {
  176 + if ($this->transaction_opcount == 0) {
  177 + $result = @pg_exec($this->connection, 'begin;');
  178 + if (!$result) {
  179 + return $this->pgsqlRaiseError();
  180 + }
  181 + }
  182 + $this->transaction_opcount++;
  183 + }
  184 + $result = @pg_exec($this->connection, $query);
  185 + if (!$result) {
  186 + return $this->pgsqlRaiseError();
  187 + }
  188 + // Determine which queries that should return data, and which
  189 + // should return an error code only.
  190 + if ($ismanip) {
  191 + $this->affected = @pg_cmdtuples($result);
  192 + return DB_OK;
  193 + } elseif (preg_match('/^\s*\(?\s*(SELECT(?!\s+INTO)|EXPLAIN|SHOW)\s/si', $query)) {
  194 + /* PostgreSQL commands:
  195 + ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
  196 + CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
  197 + GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
  198 + REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
  199 + UNLISTEN, UPDATE, VACUUM
  200 + */
  201 + $this->row[(int)$result] = 0; // reset the row counter.
  202 + $numrows = $this->numrows($result);
  203 + if (is_object($numrows)) {
  204 + return $numrows;
  205 + }
  206 + $this->num_rows[(int)$result] = $numrows;
  207 + $this->affected = 0;
  208 + return $result;
  209 + } else {
  210 + $this->affected = 0;
  211 + return DB_OK;
  212 + }
  213 + }
  214 +
  215 + // }}}
  216 + // {{{ nextResult()
  217 +
  218 + /**
  219 + * Move the internal pgsql result pointer to the next available result
  220 + *
  221 + * @param a valid fbsql result resource
  222 + *
  223 + * @access public
  224 + *
  225 + * @return true if a result is available otherwise return false
  226 + */
  227 + function nextResult($result)
  228 + {
  229 + return false;
  230 + }
  231 +
  232 + // }}}
  233 + // {{{ errorCode()
  234 +
  235 + /**
  236 + * Determine PEAR::DB error code from the database's text error message.
  237 + *
  238 + * @param string $errormsg error message returned from the database
  239 + * @return integer an error number from a DB error constant
  240 + */
  241 + function errorCode($errormsg)
  242 + {
  243 + static $error_regexps;
  244 + if (!isset($error_regexps)) {
  245 + $error_regexps = array(
  246 + '/(([Rr]elation|[Ss]equence|[Tt]able)( [\"\'].*[\"\'])? does not exist|[Cc]lass ".+" not found)$/' => DB_ERROR_NOSUCHTABLE,
  247 + '/[Cc]olumn [\"\'].*[\"\'] .*does not exist/' => DB_ERROR_NOSUCHFIELD,
  248 + '/[Rr]elation [\"\'].*[\"\'] already exists|[Cc]annot insert a duplicate key into (a )?unique index.*/' => DB_ERROR_ALREADY_EXISTS,
  249 + '/(divide|division) by zero$/' => DB_ERROR_DIVZERO,
  250 + '/pg_atoi: error in .*: can\'t parse /' => DB_ERROR_INVALID_NUMBER,
  251 + '/invalid input syntax for integer/' => DB_ERROR_INVALID_NUMBER,
  252 + '/ttribute [\"\'].*[\"\'] not found$|[Rr]elation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']/' => DB_ERROR_NOSUCHFIELD,
  253 + '/parser: parse error at or near \"/' => DB_ERROR_SYNTAX,
  254 + '/syntax error at/' => DB_ERROR_SYNTAX,
  255 + '/permission denied/' => DB_ERROR_ACCESS_VIOLATION,
  256 + '/violates not-null constraint/' => DB_ERROR_CONSTRAINT_NOT_NULL,
  257 + '/violates [\w ]+ constraint/' => DB_ERROR_CONSTRAINT,
  258 + '/referential integrity violation/' => DB_ERROR_CONSTRAINT
  259 + );
  260 + }
  261 + foreach ($error_regexps as $regexp => $code) {
  262 + if (preg_match($regexp, $errormsg)) {
  263 + return $code;
  264 + }
  265 + }
  266 + // Fall back to DB_ERROR if there was no mapping.
  267 + return DB_ERROR;
  268 + }
  269 +
  270 + // }}}
  271 + // {{{ fetchInto()
  272 +
  273 + /**
  274 + * Fetch a row and insert the data into an existing array.
  275 + *
  276 + * Formating of the array and the data therein are configurable.
  277 + * See DB_result::fetchInto() for more information.
  278 + *
  279 + * @param resource $result query result identifier
  280 + * @param array $arr (reference) array where data from the row
  281 + * should be placed
  282 + * @param int $fetchmode how the resulting array should be indexed
  283 + * @param int $rownum the row number to fetch
  284 + *
  285 + * @return mixed DB_OK on success, null when end of result set is
  286 + * reached or on failure
  287 + *
  288 + * @see DB_result::fetchInto()
  289 + * @access private
  290 + */
  291 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  292 + {
  293 + $result_int = (int)$result;
  294 + $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int];
  295 + if ($rownum >= $this->num_rows[$result_int]) {
  296 + return null;
  297 + }
  298 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  299 + $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC);
  300 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  301 + $arr = array_change_key_case($arr, CASE_LOWER);
  302 + }
  303 + } else {
  304 + $arr = @pg_fetch_row($result, $rownum);
  305 + }
  306 + if (!$arr) {
  307 + $err = pg_errormessage($this->connection);
  308 + if (!$err) {
  309 + return null;
  310 + }
  311 + return $this->pgsqlRaiseError();
  312 + }
  313 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  314 + $this->_rtrimArrayValues($arr);
  315 + }
  316 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  317 + $this->_convertNullArrayValuesToEmpty($arr);
  318 + }
  319 + $this->row[$result_int] = ++$rownum;
  320 + return DB_OK;
  321 + }
  322 +
  323 + // }}}
  324 + // {{{ freeResult()
  325 +
  326 + /**
  327 + * Free the internal resources associated with $result.
  328 + *
  329 + * @param $result int PostgreSQL result identifier
  330 + *
  331 + * @return bool true on success, false if $result is invalid
  332 + */
  333 + function freeResult($result)
  334 + {
  335 + if (is_resource($result)) {
  336 + unset($this->row[(int)$result]);
  337 + unset($this->num_rows[(int)$result]);
  338 + $this->affected = 0;
  339 + return @pg_freeresult($result);
  340 + }
  341 + return false;
  342 + }
  343 +
  344 + // }}}
  345 + // {{{ quote()
  346 +
  347 + /**
  348 + * @deprecated Deprecated in release 1.6.0
  349 + * @internal
  350 + */
  351 + function quote($str) {
  352 + return $this->quoteSmart($str);
  353 + }
  354 +
  355 + // }}}
  356 + // {{{ quoteSmart()
  357 +
  358 + /**
  359 + * Format input so it can be safely used in a query
  360 + *
  361 + * @param mixed $in data to be quoted
  362 + *
  363 + * @return mixed Submitted variable's type = returned value:
  364 + * + null = the string <samp>NULL</samp>
  365 + * + boolean = string <samp>TRUE</samp> or <samp>FALSE</samp>
  366 + * + integer or double = the unquoted number
  367 + * + other (including strings and numeric strings) =
  368 + * the data escaped according to MySQL's settings
  369 + * then encapsulated between single quotes
  370 + *
  371 + * @internal
  372 + */
  373 + function quoteSmart($in)
  374 + {
  375 + if (is_int($in) || is_double($in)) {
  376 + return $in;
  377 + } elseif (is_bool($in)) {
  378 + return $in ? 'TRUE' : 'FALSE';
  379 + } elseif (is_null($in)) {
  380 + return 'NULL';
  381 + } else {
  382 + return "'" . $this->escapeSimple($in) . "'";
  383 + }
  384 + }
  385 +
  386 + // }}}
  387 + // {{{ escapeSimple()
  388 +
  389 + /**
  390 + * Escape a string according to the current DBMS's standards
  391 + *
  392 + * PostgreSQL treats a backslash as an escape character, so they are
  393 + * removed.
  394 + *
  395 + * Not using pg_escape_string() yet because it requires PostgreSQL
  396 + * to be at version 7.2 or greater.
  397 + *
  398 + * @param string $str the string to be escaped
  399 + *
  400 + * @return string the escaped string
  401 + *
  402 + * @internal
  403 + */
  404 + function escapeSimple($str) {
  405 + return str_replace("'", "''", str_replace('\\', '\\\\', $str));
  406 + }
  407 +
  408 + // }}}
  409 + // {{{ numCols()
  410 +
  411 + /**
  412 + * Get the number of columns in a result set.
  413 + *
  414 + * @param $result resource PostgreSQL result identifier
  415 + *
  416 + * @return int the number of columns per row in $result
  417 + */
  418 + function numCols($result)
  419 + {
  420 + $cols = @pg_numfields($result);
  421 + if (!$cols) {
  422 + return $this->pgsqlRaiseError();
  423 + }
  424 + return $cols;
  425 + }
  426 +
  427 + // }}}
  428 + // {{{ numRows()
  429 +
  430 + /**
  431 + * Get the number of rows in a result set.
  432 + *
  433 + * @param $result resource PostgreSQL result identifier
  434 + *
  435 + * @return int the number of rows in $result
  436 + */
  437 + function numRows($result)
  438 + {
  439 + $rows = @pg_numrows($result);
  440 + if ($rows === null) {
  441 + return $this->pgsqlRaiseError();
  442 + }
  443 + return $rows;
  444 + }
  445 +
  446 + // }}}
  447 + // {{{ errorNative()
  448 +
  449 + /**
  450 + * Get the native error code of the last error (if any) that
  451 + * occured on the current connection.
  452 + *
  453 + * @return int native PostgreSQL error code
  454 + */
  455 + function errorNative()
  456 + {
  457 + return pg_errormessage($this->connection);
  458 + }
  459 +
  460 + // }}}
  461 + // {{{ autoCommit()
  462 +
  463 + /**
  464 + * Enable/disable automatic commits
  465 + */
  466 + function autoCommit($onoff = false)
  467 + {
  468 + // XXX if $this->transaction_opcount > 0, we should probably
  469 + // issue a warning here.
  470 + $this->autocommit = $onoff ? true : false;
  471 + return DB_OK;
  472 + }
  473 +
  474 + // }}}
  475 + // {{{ commit()
  476 +
  477 + /**
  478 + * Commit the current transaction.
  479 + */
  480 + function commit()
  481 + {
  482 + if ($this->transaction_opcount > 0) {
  483 + // (disabled) hack to shut up error messages from libpq.a
  484 + //@fclose(@fopen("php://stderr", "w"));
  485 + $result = @pg_exec($this->connection, 'end;');
  486 + $this->transaction_opcount = 0;
  487 + if (!$result) {
  488 + return $this->pgsqlRaiseError();
  489 + }
  490 + }
  491 + return DB_OK;
  492 + }
  493 +
  494 + // }}}
  495 + // {{{ rollback()
  496 +
  497 + /**
  498 + * Roll back (undo) the current transaction.
  499 + */
  500 + function rollback()
  501 + {
  502 + if ($this->transaction_opcount > 0) {
  503 + $result = @pg_exec($this->connection, 'abort;');
  504 + $this->transaction_opcount = 0;
  505 + if (!$result) {
  506 + return $this->pgsqlRaiseError();
  507 + }
  508 + }
  509 + return DB_OK;
  510 + }
  511 +
  512 + // }}}
  513 + // {{{ affectedRows()
  514 +
  515 + /**
  516 + * Gets the number of rows affected by the last query.
  517 + * if the last query was a select, returns 0.
  518 + *
  519 + * @return int number of rows affected by the last query or DB_ERROR
  520 + */
  521 + function affectedRows()
  522 + {
  523 + return $this->affected;
  524 + }
  525 +
  526 + // }}}
  527 + // {{{ nextId()
  528 +
  529 + /**
  530 + * Returns the next free id in a sequence
  531 + *
  532 + * @param string $seq_name name of the sequence
  533 + * @param boolean $ondemand when true, the seqence is automatically
  534 + * created if it does not exist
  535 + *
  536 + * @return int the next id number in the sequence. DB_Error if problem.
  537 + *
  538 + * @internal
  539 + * @see DB_common::nextID()
  540 + * @access public
  541 + */
  542 + function nextId($seq_name, $ondemand = true)
  543 + {
  544 + $seqname = $this->getSequenceName($seq_name);
  545 + $repeat = false;
  546 + do {
  547 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  548 + $result =& $this->query("SELECT NEXTVAL('${seqname}')");
  549 + $this->popErrorHandling();
  550 + if ($ondemand && DB::isError($result) &&
  551 + $result->getCode() == DB_ERROR_NOSUCHTABLE) {
  552 + $repeat = true;
  553 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  554 + $result = $this->createSequence($seq_name);
  555 + $this->popErrorHandling();
  556 + if (DB::isError($result)) {
  557 + return $this->raiseError($result);
  558 + }
  559 + } else {
  560 + $repeat = false;
  561 + }
  562 + } while ($repeat);
  563 + if (DB::isError($result)) {
  564 + return $this->raiseError($result);
  565 + }
  566 + $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
  567 + $result->free();
  568 + return $arr[0];
  569 + }
  570 +
  571 + // }}}
  572 + // {{{ createSequence()
  573 +
  574 + /**
  575 + * Create the sequence
  576 + *
  577 + * @param string $seq_name the name of the sequence
  578 + * @return mixed DB_OK on success or DB error on error
  579 + * @access public
  580 + */
  581 + function createSequence($seq_name)
  582 + {
  583 + $seqname = $this->getSequenceName($seq_name);
  584 + $result = $this->query("CREATE SEQUENCE ${seqname}");
  585 + return $result;
  586 + }
  587 +
  588 + // }}}
  589 + // {{{ dropSequence()
  590 +
  591 + /**
  592 + * Drop a sequence
  593 + *
  594 + * @param string $seq_name the name of the sequence
  595 + * @return mixed DB_OK on success or DB error on error
  596 + * @access public
  597 + */
  598 + function dropSequence($seq_name)
  599 + {
  600 + $seqname = $this->getSequenceName($seq_name);
  601 + return $this->query("DROP SEQUENCE ${seqname}");
  602 + }
  603 +
  604 + // }}}
  605 + // {{{ modifyLimitQuery()
  606 +
  607 + function modifyLimitQuery($query, $from, $count, $params = array())
  608 + {
  609 + $query = $query . " LIMIT $count OFFSET $from";
  610 + return $query;
  611 + }
  612 +
  613 + // }}}
  614 + // {{{ pgsqlRaiseError()
  615 +
  616 + /**
  617 + * Gather information about an error, then use that info to create a
  618 + * DB error object and finally return that object.
  619 + *
  620 + * @param integer $errno PEAR error number (usually a DB constant) if
  621 + * manually raising an error
  622 + * @return object DB error object
  623 + * @see errorNative()
  624 + * @see errorCode()
  625 + * @see DB_common::raiseError()
  626 + */
  627 + function pgsqlRaiseError($errno = null)
  628 + {
  629 + $native = $this->errorNative();
  630 + if ($errno === null) {
  631 + $err = $this->errorCode($native);
  632 + } else {
  633 + $err = $errno;
  634 + }
  635 + return $this->raiseError($err, null, null, null, $native);
  636 + }
  637 +
  638 + // }}}
  639 + // {{{ _pgFieldFlags()
  640 +
  641 + /**
  642 + * Flags of a Field
  643 + *
  644 + * @param int $resource PostgreSQL result identifier
  645 + * @param int $num_field the field number
  646 + *
  647 + * @return string The flags of the field ("not_null", "default_value",
  648 + * "primary_key", "unique_key" and "multiple_key"
  649 + * are supported). The default value is passed
  650 + * through rawurlencode() in case there are spaces in it.
  651 + * @access private
  652 + */
  653 + function _pgFieldFlags($resource, $num_field, $table_name)
  654 + {
  655 + $field_name = @pg_fieldname($resource, $num_field);
  656 +
  657 + $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
  658 + FROM pg_attribute f, pg_class tab, pg_type typ
  659 + WHERE tab.relname = typ.typname
  660 + AND typ.typrelid = f.attrelid
  661 + AND f.attname = '$field_name'
  662 + AND tab.relname = '$table_name'");
  663 + if (@pg_numrows($result) > 0) {
  664 + $row = @pg_fetch_row($result, 0);
  665 + $flags = ($row[0] == 't') ? 'not_null ' : '';
  666 +
  667 + if ($row[1] == 't') {
  668 + $result = @pg_exec($this->connection, "SELECT a.adsrc
  669 + FROM pg_attribute f, pg_class tab, pg_type typ, pg_attrdef a
  670 + WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
  671 + AND f.attrelid = a.adrelid AND f.attname = '$field_name'
  672 + AND tab.relname = '$table_name' AND f.attnum = a.adnum");
  673 + $row = @pg_fetch_row($result, 0);
  674 + $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]);
  675 + $flags .= 'default_' . rawurlencode($num) . ' ';
  676 + }
  677 + } else {
  678 + $flags = '';
  679 + }
  680 + $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey
  681 + FROM pg_attribute f, pg_class tab, pg_type typ, pg_index i
  682 + WHERE tab.relname = typ.typname
  683 + AND typ.typrelid = f.attrelid
  684 + AND f.attrelid = i.indrelid
  685 + AND f.attname = '$field_name'
  686 + AND tab.relname = '$table_name'");
  687 + $count = @pg_numrows($result);
  688 +
  689 + for ($i = 0; $i < $count ; $i++) {
  690 + $row = @pg_fetch_row($result, $i);
  691 + $keys = explode(' ', $row[2]);
  692 +
  693 + if (in_array($num_field + 1, $keys)) {
  694 + $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : '';
  695 + $flags .= ($row[1] == 't') ? 'primary_key ' : '';
  696 + if (count($keys) > 1)
  697 + $flags .= 'multiple_key ';
  698 + }
  699 + }
  700 +
  701 + return trim($flags);
  702 + }
  703 +
  704 + // }}}
  705 + // {{{ tableInfo()
  706 +
  707 + /**
  708 + * Returns information about a table or a result set.
  709 + *
  710 + * NOTE: only supports 'table' and 'flags' if <var>$result</var>
  711 + * is a table name.
  712 + *
  713 + * @param object|string $result DB_result object from a query or a
  714 + * string containing the name of a table
  715 + * @param int $mode a valid tableInfo mode
  716 + * @return array an associative array with the information requested
  717 + * or an error object if something is wrong
  718 + * @access public
  719 + * @internal
  720 + * @see DB_common::tableInfo()
  721 + */
  722 + function tableInfo($result, $mode = null)
  723 + {
  724 + if (isset($result->result)) {
  725 + /*
  726 + * Probably received a result object.
  727 + * Extract the result resource identifier.
  728 + */
  729 + $id = $result->result;
  730 + $got_string = false;
  731 + } elseif (is_string($result)) {
  732 + /*
  733 + * Probably received a table name.
  734 + * Create a result resource identifier.
  735 + */
  736 + $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0");
  737 + $got_string = true;
  738 + } else {
  739 + /*
  740 + * Probably received a result resource identifier.
  741 + * Copy it.
  742 + * Deprecated. Here for compatibility only.
  743 + */
  744 + $id = $result;
  745 + $got_string = false;
  746 + }
  747 +
  748 + if (!is_resource($id)) {
  749 + return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
  750 + }
  751 +
  752 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  753 + $case_func = 'strtolower';
  754 + } else {
  755 + $case_func = 'strval';
  756 + }
  757 +
  758 + $count = @pg_numfields($id);
  759 +
  760 + // made this IF due to performance (one if is faster than $count if's)
  761 + if (!$mode) {
  762 +
  763 + for ($i=0; $i<$count; $i++) {
  764 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  765 + $res[$i]['name'] = $case_func(@pg_fieldname($id, $i));
  766 + $res[$i]['type'] = @pg_fieldtype($id, $i);
  767 + $res[$i]['len'] = @pg_fieldsize($id, $i);
  768 + $res[$i]['flags'] = $got_string ? $this->_pgFieldflags($id, $i, $result) : '';
  769 + }
  770 +
  771 + } else { // full
  772 + $res['num_fields']= $count;
  773 +
  774 + for ($i=0; $i<$count; $i++) {
  775 + $res[$i]['table'] = $got_string ? $case_func($result) : '';
  776 + $res[$i]['name'] = $case_func(@pg_fieldname($id, $i));
  777 + $res[$i]['type'] = @pg_fieldtype($id, $i);
  778 + $res[$i]['len'] = @pg_fieldsize($id, $i);
  779 + $res[$i]['flags'] = $got_string ? $this->_pgFieldFlags($id, $i, $result) : '';
  780 +
  781 + if ($mode & DB_TABLEINFO_ORDER) {
  782 + $res['order'][$res[$i]['name']] = $i;
  783 + }
  784 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  785 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  786 + }
  787 + }
  788 + }
  789 +
  790 + // free the result only if we were called on a table
  791 + if ($got_string) {
  792 + @pg_freeresult($id);
  793 + }
  794 + return $res;
  795 + }
  796 +
  797 + // }}}
  798 + // {{{ getTablesQuery()
  799 +
  800 + /**
  801 + * Returns the query needed to get some backend info
  802 + * @param string $type What kind of info you want to retrieve
  803 + * @return string The SQL query string
  804 + */
  805 + function getSpecialQuery($type)
  806 + {
  807 + switch ($type) {
  808 + case 'tables':
  809 + return "SELECT c.relname as \"Name\"
  810 + FROM pg_class c, pg_user u
  811 + WHERE c.relowner = u.usesysid AND c.relkind = 'r'
  812 + AND not exists (select 1 from pg_views where viewname = c.relname)
  813 + AND c.relname !~ '^(pg_|sql_)'
  814 + UNION
  815 + SELECT c.relname as \"Name\"
  816 + FROM pg_class c
  817 + WHERE c.relkind = 'r'
  818 + AND not exists (select 1 from pg_views where viewname = c.relname)
  819 + AND not exists (select 1 from pg_user where usesysid = c.relowner)
  820 + AND c.relname !~ '^pg_'";
  821 + case 'views':
  822 + // Table cols: viewname | viewowner | definition
  823 + return 'SELECT viewname FROM pg_views';
  824 + case 'users':
  825 + // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil
  826 + return 'SELECT usename FROM pg_user';
  827 + case 'databases':
  828 + return 'SELECT datname FROM pg_database';
  829 + case 'functions':
  830 + return 'SELECT proname FROM pg_proc';
  831 + default:
  832 + return null;
  833 + }
  834 + }
  835 +
  836 + // }}}
  837 +
  838 +}
  839 +
  840 +/*
  841 + * Local variables:
  842 + * tab-width: 4
  843 + * c-basic-offset: 4
  844 + * End:
  845 + */
  846 +
  847 +?>
... ...
pear/DB/sqlite.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Authors: Urs Gehrig <urs@circle.ch> |
  17 +// | Mika Tuupola <tuupola@appelsiini.net> |
  18 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  19 +// +----------------------------------------------------------------------+
  20 +//
  21 +// $Id$
  22 +
  23 +require_once 'DB/common.php';
  24 +
  25 +/**
  26 + * Database independent query interface definition for the SQLite
  27 + * PECL extension.
  28 + *
  29 + * @package DB
  30 + * @version $Id$
  31 + * @category Database
  32 + * @author Urs Gehrig <urs@circle.ch>
  33 + * @author Mika Tuupola <tuupola@appelsiini.net>
  34 + */
  35 +class DB_sqlite extends DB_common
  36 +{
  37 + // {{{ properties
  38 +
  39 + var $connection;
  40 + var $phptype, $dbsyntax;
  41 + var $prepare_tokens = array();
  42 + var $prepare_types = array();
  43 + var $_lasterror = '';
  44 +
  45 + // }}}
  46 + // {{{ constructor
  47 +
  48 + /**
  49 + * Constructor for this class.
  50 + *
  51 + * Error codes according to sqlite_exec. Error Codes specification is
  52 + * in the {@link http://sqlite.org/c_interface.html online manual}.
  53 + *
  54 + * This errorhandling based on sqlite_exec is not yet implemented.
  55 + *
  56 + * @access public
  57 + */
  58 + function DB_sqlite()
  59 + {
  60 +
  61 + $this->DB_common();
  62 + $this->phptype = 'sqlite';
  63 + $this->dbsyntax = 'sqlite';
  64 + $this->features = array (
  65 + 'prepare' => false,
  66 + 'pconnect' => true,
  67 + 'transactions' => false,
  68 + 'limit' => 'alter'
  69 + );
  70 +
  71 + // SQLite data types, http://www.sqlite.org/datatypes.html
  72 + $this->keywords = array (
  73 + 'BLOB' => '',
  74 + 'BOOLEAN' => '',
  75 + 'CHARACTER' => '',
  76 + 'CLOB' => '',
  77 + 'FLOAT' => '',
  78 + 'INTEGER' => '',
  79 + 'KEY' => '',
  80 + 'NATIONAL' => '',
  81 + 'NUMERIC' => '',
  82 + 'NVARCHAR' => '',
  83 + 'PRIMARY' => '',
  84 + 'TEXT' => '',
  85 + 'TIMESTAMP' => '',
  86 + 'UNIQUE' => '',
  87 + 'VARCHAR' => '',
  88 + 'VARYING' => ''
  89 + );
  90 + $this->errorcode_map = array(
  91 + );
  92 + }
  93 +
  94 + // }}}
  95 + // {{{ connect()
  96 +
  97 + /**
  98 + * Connect to a database represented by a file.
  99 + *
  100 + * @param $dsn the data source name; the file is taken as
  101 + * database; "sqlite://root:@host/test.db?mode=0644"
  102 + * @param $persistent (optional) whether the connection should
  103 + * be persistent
  104 + * @access public
  105 + * @return int DB_OK on success, a DB error on failure
  106 + */
  107 + function connect($dsninfo, $persistent = false)
  108 + {
  109 + if (!DB::assertExtension('sqlite')) {
  110 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  111 + }
  112 +
  113 + $this->dsn = $dsninfo;
  114 +
  115 + if ($dsninfo['database']) {
  116 + if (!file_exists($dsninfo['database'])) {
  117 + if (!touch($dsninfo['database'])) {
  118 + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
  119 + }
  120 + if (!isset($dsninfo['mode']) ||
  121 + !is_numeric($dsninfo['mode']))
  122 + {
  123 + $mode = 0644;
  124 + } else {
  125 + $mode = octdec($dsninfo['mode']);
  126 + }
  127 + if (!chmod($dsninfo['database'], $mode)) {
  128 + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
  129 + }
  130 + if (!file_exists($dsninfo['database'])) {
  131 + return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
  132 + }
  133 + }
  134 + if (!is_file($dsninfo['database'])) {
  135 + return $this->sqliteRaiseError(DB_ERROR_INVALID);
  136 + }
  137 + if (!is_readable($dsninfo['database'])) {
  138 + return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
  139 + }
  140 + } else {
  141 + return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
  142 + }
  143 +
  144 + $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
  145 + if (!($conn = @$connect_function($dsninfo['database']))) {
  146 + return $this->sqliteRaiseError(DB_ERROR_NODBSELECTED);
  147 + }
  148 + $this->connection = $conn;
  149 +
  150 + return DB_OK;
  151 + }
  152 +
  153 + // }}}
  154 + // {{{ disconnect()
  155 +
  156 + /**
  157 + * Log out and disconnect from the database.
  158 + *
  159 + * @access public
  160 + * @return bool true on success, false if not connected.
  161 + * @todo fix return values
  162 + */
  163 + function disconnect()
  164 + {
  165 + $ret = @sqlite_close($this->connection);
  166 + $this->connection = null;
  167 + return $ret;
  168 + }
  169 +
  170 + // }}}
  171 + // {{{ simpleQuery()
  172 +
  173 + /**
  174 + * Send a query to SQLite and returns the results as a SQLite resource
  175 + * identifier.
  176 + *
  177 + * @param the SQL query
  178 + * @access public
  179 + * @return mixed returns a valid SQLite result for successful SELECT
  180 + * queries, DB_OK for other successful queries. A DB error is
  181 + * returned on failure.
  182 + */
  183 + function simpleQuery($query)
  184 + {
  185 + $ismanip = DB::isManip($query);
  186 + $this->last_query = $query;
  187 + $query = $this->_modifyQuery($query);
  188 + ini_set('track_errors', true);
  189 + $result = @sqlite_query($query, $this->connection);
  190 + ini_restore('track_errors');
  191 + $this->_lasterror = isset($php_errormsg) ? $php_errormsg : '';
  192 + $this->result = $result;
  193 + if (!$this->result) {
  194 + return $this->sqliteRaiseError(null);
  195 + }
  196 +
  197 + /* sqlite_query() seems to allways return a resource */
  198 + /* so cant use that. Using $ismanip instead */
  199 + if (!$ismanip) {
  200 + $numRows = $this->numRows($result);
  201 +
  202 + /* if numRows() returned PEAR_Error */
  203 + if (is_object($numRows)) {
  204 + return $numRows;
  205 + }
  206 + return $result;
  207 + }
  208 + return DB_OK;
  209 + }
  210 +
  211 + // }}}
  212 + // {{{ nextResult()
  213 +
  214 + /**
  215 + * Move the internal sqlite result pointer to the next available result.
  216 + *
  217 + * @param a valid sqlite result resource
  218 + * @access public
  219 + * @return true if a result is available otherwise return false
  220 + */
  221 + function nextResult($result)
  222 + {
  223 + return false;
  224 + }
  225 +
  226 + // }}}
  227 + // {{{ fetchInto()
  228 +
  229 + /**
  230 + * Fetch a row and insert the data into an existing array.
  231 + *
  232 + * Formating of the array and the data therein are configurable.
  233 + * See DB_result::fetchInto() for more information.
  234 + *
  235 + * @param resource $result query result identifier
  236 + * @param array $arr (reference) array where data from the row
  237 + * should be placed
  238 + * @param int $fetchmode how the resulting array should be indexed
  239 + * @param int $rownum the row number to fetch
  240 + *
  241 + * @return mixed DB_OK on success, null when end of result set is
  242 + * reached or on failure
  243 + *
  244 + * @see DB_result::fetchInto()
  245 + * @access private
  246 + */
  247 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  248 + {
  249 + if ($rownum !== null) {
  250 + if (!@sqlite_seek($this->result, $rownum)) {
  251 + return null;
  252 + }
  253 + }
  254 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  255 + $arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
  256 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  257 + $arr = array_change_key_case($arr, CASE_LOWER);
  258 + }
  259 + } else {
  260 + $arr = @sqlite_fetch_array($result, SQLITE_NUM);
  261 + }
  262 + if (!$arr) {
  263 + /* See: http://bugs.php.net/bug.php?id=22328 */
  264 + return null;
  265 + }
  266 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  267 + /*
  268 + * Even though this DBMS already trims output, we do this because
  269 + * a field might have intentional whitespace at the end that
  270 + * gets removed by DB_PORTABILITY_RTRIM under another driver.
  271 + */
  272 + $this->_rtrimArrayValues($arr);
  273 + }
  274 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  275 + $this->_convertNullArrayValuesToEmpty($arr);
  276 + }
  277 + return DB_OK;
  278 + }
  279 +
  280 + // }}}
  281 + // {{{ freeResult()
  282 +
  283 + /**
  284 + * Free the internal resources associated with $result.
  285 + *
  286 + * @param $result SQLite result identifier
  287 + * @access public
  288 + * @return bool true on success, false if $result is invalid
  289 + */
  290 + function freeResult(&$result)
  291 + {
  292 + // XXX No native free?
  293 + if (!is_resource($result)) {
  294 + return false;
  295 + }
  296 + $result = null;
  297 + return true;
  298 + }
  299 +
  300 + // }}}
  301 + // {{{ numCols()
  302 +
  303 + /**
  304 + * Gets the number of columns in a result set.
  305 + *
  306 + * @return number of columns in a result set
  307 + */
  308 + function numCols($result)
  309 + {
  310 + $cols = @sqlite_num_fields($result);
  311 + if (!$cols) {
  312 + return $this->sqliteRaiseError();
  313 + }
  314 + return $cols;
  315 + }
  316 +
  317 + // }}}
  318 + // {{{ numRows()
  319 +
  320 + /**
  321 + * Gets the number of rows affected by a query.
  322 + *
  323 + * @return number of rows affected by the last query
  324 + */
  325 + function numRows($result)
  326 + {
  327 + $rows = @sqlite_num_rows($result);
  328 + if (!is_integer($rows)) {
  329 + return $this->raiseError();
  330 + }
  331 + return $rows;
  332 + }
  333 +
  334 + // }}}
  335 + // {{{ affected()
  336 +
  337 + /**
  338 + * Gets the number of rows affected by a query.
  339 + *
  340 + * @return number of rows affected by the last query
  341 + */
  342 + function affectedRows()
  343 + {
  344 + return @sqlite_changes($this->connection);
  345 + }
  346 +
  347 + // }}}
  348 + // {{{ errorNative()
  349 +
  350 + /**
  351 + * Get the native error string of the last error (if any) that
  352 + * occured on the current connection.
  353 + *
  354 + * This is used to retrieve more meaningfull error messages DB_pgsql
  355 + * way since sqlite_last_error() does not provide adequate info.
  356 + *
  357 + * @return string native SQLite error message
  358 + */
  359 + function errorNative()
  360 + {
  361 + return($this->_lasterror);
  362 + }
  363 +
  364 + // }}}
  365 + // {{{ errorCode()
  366 +
  367 + /**
  368 + * Determine PEAR::DB error code from the database's text error message.
  369 + *
  370 + * @param string $errormsg error message returned from the database
  371 + * @return integer an error number from a DB error constant
  372 + */
  373 + function errorCode($errormsg)
  374 + {
  375 + static $error_regexps;
  376 + if (!isset($error_regexps)) {
  377 + $error_regexps = array(
  378 + '/^no such table:/' => DB_ERROR_NOSUCHTABLE,
  379 + '/^table .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
  380 + '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
  381 + '/is not unique/' => DB_ERROR_CONSTRAINT,
  382 + '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
  383 + '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
  384 + '/^no such column:/' => DB_ERROR_NOSUCHFIELD,
  385 + '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX
  386 + );
  387 + }
  388 + foreach ($error_regexps as $regexp => $code) {
  389 + if (preg_match($regexp, $errormsg)) {
  390 + return $code;
  391 + }
  392 + }
  393 + // Fall back to DB_ERROR if there was no mapping.
  394 + return DB_ERROR;
  395 + }
  396 +
  397 + // }}}
  398 + // {{{ dropSequence()
  399 +
  400 + /**
  401 + * Deletes a sequence
  402 + *
  403 + * @param string $seq_name name of the sequence to be deleted
  404 + *
  405 + * @return int DB_OK on success. DB_Error if problems.
  406 + *
  407 + * @internal
  408 + * @see DB_common::dropSequence()
  409 + * @access public
  410 + */
  411 + function dropSequence($seq_name)
  412 + {
  413 + $seqname = $this->getSequenceName($seq_name);
  414 + return $this->query("DROP TABLE $seqname");
  415 + }
  416 +
  417 + /**
  418 + * Creates a new sequence
  419 + *
  420 + * @param string $seq_name name of the new sequence
  421 + *
  422 + * @return int DB_OK on success. A DB_Error object is returned if
  423 + * problems arise.
  424 + *
  425 + * @internal
  426 + * @see DB_common::createSequence()
  427 + * @access public
  428 + */
  429 + function createSequence($seq_name)
  430 + {
  431 + $seqname = $this->getSequenceName($seq_name);
  432 + $query = 'CREATE TABLE ' . $seqname .
  433 + ' (id INTEGER UNSIGNED PRIMARY KEY) ';
  434 + $result = $this->query($query);
  435 + if (DB::isError($result)) {
  436 + return($result);
  437 + }
  438 + $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
  439 + BEGIN
  440 + DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
  441 + END ";
  442 + $result = $this->query($query);
  443 + if (DB::isError($result)) {
  444 + return($result);
  445 + }
  446 + }
  447 +
  448 + // }}}
  449 + // {{{ nextId()
  450 +
  451 + /**
  452 + * Returns the next free id in a sequence
  453 + *
  454 + * @param string $seq_name name of the sequence
  455 + * @param boolean $ondemand when true, the seqence is automatically
  456 + * created if it does not exist
  457 + *
  458 + * @return int the next id number in the sequence. DB_Error if problem.
  459 + *
  460 + * @internal
  461 + * @see DB_common::nextID()
  462 + * @access public
  463 + */
  464 + function nextId($seq_name, $ondemand = true)
  465 + {
  466 + $seqname = $this->getSequenceName($seq_name);
  467 +
  468 + do {
  469 + $repeat = 0;
  470 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  471 + $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)");
  472 + $this->popErrorHandling();
  473 + if ($result === DB_OK) {
  474 + $id = @sqlite_last_insert_rowid($this->connection);
  475 + if ($id != 0) {
  476 + return $id;
  477 + }
  478 + } elseif ($ondemand && DB::isError($result) &&
  479 + $result->getCode() == DB_ERROR_NOSUCHTABLE)
  480 + {
  481 + $result = $this->createSequence($seq_name);
  482 + if (DB::isError($result)) {
  483 + return $this->raiseError($result);
  484 + } else {
  485 + $repeat = 1;
  486 + }
  487 + }
  488 + } while ($repeat);
  489 +
  490 + return $this->raiseError($result);
  491 + }
  492 +
  493 + // }}}
  494 + // {{{ getSpecialQuery()
  495 +
  496 + /**
  497 + * Returns the query needed to get some backend info.
  498 + *
  499 + * Refer to the online manual at http://sqlite.org/sqlite.html.
  500 + *
  501 + * @param string $type What kind of info you want to retrieve
  502 + * @return string The SQL query string
  503 + */
  504 + function getSpecialQuery($type, $args=array())
  505 + {
  506 + if (!is_array($args))
  507 + return $this->raiseError('no key specified', null, null, null,
  508 + 'Argument has to be an array.');
  509 + switch (strtolower($type)) {
  510 + case 'master':
  511 + return 'SELECT * FROM sqlite_master;';
  512 + case 'tables':
  513 + return "SELECT name FROM sqlite_master WHERE type='table' "
  514 + . 'UNION ALL SELECT name FROM sqlite_temp_master '
  515 + . "WHERE type='table' ORDER BY name;";
  516 + case 'schema':
  517 + return 'SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL '
  518 + . 'SELECT * FROM sqlite_temp_master) '
  519 + . "WHERE type!='meta' ORDER BY tbl_name, type DESC, name;";
  520 + case 'schemax':
  521 + case 'schema_x':
  522 + /*
  523 + * Use like:
  524 + * $res = $db->query($db->getSpecialQuery('schema_x', array('table' => 'table3')));
  525 + */
  526 + return 'SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL '
  527 + . 'SELECT * FROM sqlite_temp_master) '
  528 + . "WHERE tbl_name LIKE '{$args['table']}' AND type!='meta' "
  529 + . 'ORDER BY type DESC, name;';
  530 + case 'alter':
  531 + /*
  532 + * SQLite does not support ALTER TABLE; this is a helper query
  533 + * to handle this. 'table' represents the table name, 'rows'
  534 + * the news rows to create, 'save' the row(s) to keep _with_
  535 + * the data.
  536 + *
  537 + * Use like:
  538 + * $args = array(
  539 + * 'table' => $table,
  540 + * 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
  541 + * 'save' => "NULL, titel, content, datetime"
  542 + * );
  543 + * $res = $db->query( $db->getSpecialQuery('alter', $args));
  544 + */
  545 + $rows = strtr($args['rows'], $this->keywords);
  546 +
  547 + $q = array(
  548 + 'BEGIN TRANSACTION',
  549 + "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
  550 + "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
  551 + "DROP TABLE {$args['table']}",
  552 + "CREATE TABLE {$args['table']} ({$args['rows']})",
  553 + "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
  554 + "DROP TABLE {$args['table']}_backup",
  555 + 'COMMIT',
  556 + );
  557 +
  558 + // This is a dirty hack, since the above query will no get executed with a single
  559 + // query call; so here the query method will be called directly and return a select instead.
  560 + foreach ($q as $query) {
  561 + $this->query($query);
  562 + }
  563 + return "SELECT * FROM {$args['table']};";
  564 + default:
  565 + return null;
  566 + }
  567 + }
  568 +
  569 + // }}}
  570 + // {{{ getDbFileStats()
  571 +
  572 + /**
  573 + * Get the file stats for the current database.
  574 + *
  575 + * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
  576 + * atime, mtime, ctime, blksize, blocks or a numeric key between
  577 + * 0 and 12.
  578 + *
  579 + * @param string $arg Array key for stats()
  580 + * @return mixed array on an unspecified key, integer on a passed arg and
  581 + * false at a stats error.
  582 + */
  583 + function getDbFileStats($arg = '')
  584 + {
  585 + $stats = stat($this->dsn['database']);
  586 + if ($stats == false) {
  587 + return false;
  588 + }
  589 + if (is_array($stats)) {
  590 + if (is_numeric($arg)) {
  591 + if (((int)$arg <= 12) & ((int)$arg >= 0)) {
  592 + return false;
  593 + }
  594 + return $stats[$arg ];
  595 + }
  596 + if (array_key_exists(trim($arg), $stats)) {
  597 + return $stats[$arg ];
  598 + }
  599 + }
  600 + return $stats;
  601 + }
  602 +
  603 + // }}}
  604 + // {{{ escapeSimple()
  605 +
  606 + /**
  607 + * Escape a string according to the current DBMS's standards
  608 + *
  609 + * In SQLite, this makes things safe for inserts/updates, but may
  610 + * cause problems when performing text comparisons against columns
  611 + * containing binary data. See the
  612 + * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
  613 + *
  614 + * @param string $str the string to be escaped
  615 + *
  616 + * @return string the escaped string
  617 + *
  618 + * @since 1.6.1
  619 + * @see DB_common::escapeSimple()
  620 + * @internal
  621 + */
  622 + function escapeSimple($str) {
  623 + return @sqlite_escape_string($str);
  624 + }
  625 +
  626 + // }}}
  627 + // {{{ modifyLimitQuery()
  628 +
  629 + function modifyLimitQuery($query, $from, $count, $params = array())
  630 + {
  631 + $query = $query . " LIMIT $count OFFSET $from";
  632 + return $query;
  633 + }
  634 +
  635 + // }}}
  636 + // {{{ modifyQuery()
  637 +
  638 + /**
  639 + * "DELETE FROM table" gives 0 affected rows in SQLite.
  640 + *
  641 + * This little hack lets you know how many rows were deleted.
  642 + *
  643 + * @param string $query The SQL query string
  644 + * @return string The SQL query string
  645 + */
  646 + function _modifyQuery($query)
  647 + {
  648 + if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
  649 + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
  650 + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
  651 + 'DELETE FROM \1 WHERE 1=1', $query);
  652 + }
  653 + }
  654 + return $query;
  655 + }
  656 +
  657 + // }}}
  658 + // {{{ sqliteRaiseError()
  659 +
  660 + /**
  661 + * Gather information about an error, then use that info to create a
  662 + * DB error object and finally return that object.
  663 + *
  664 + * @param integer $errno PEAR error number (usually a DB constant) if
  665 + * manually raising an error
  666 + * @return object DB error object
  667 + * @see errorNative()
  668 + * @see errorCode()
  669 + * @see DB_common::raiseError()
  670 + */
  671 + function sqliteRaiseError($errno = null)
  672 + {
  673 +
  674 + $native = $this->errorNative();
  675 + if ($errno === null) {
  676 + $errno = $this->errorCode($native);
  677 + }
  678 +
  679 + $errorcode = @sqlite_last_error($this->connection);
  680 + $userinfo = "$errorcode ** $this->last_query";
  681 +
  682 + return $this->raiseError($errno, null, null, $userinfo, $native);
  683 + }
  684 +
  685 + // }}}
  686 +}
  687 +
  688 +/*
  689 + * Local variables:
  690 + * tab-width: 4
  691 + * c-basic-offset: 4
  692 + * End:
  693 + */
  694 +
  695 +?>
... ...
pear/DB/storage.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Author: Stig Bakken <stig@php.net> |
  17 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  18 +// +----------------------------------------------------------------------+
  19 +//
  20 +// $Id$
  21 +
  22 +require_once 'DB.php';
  23 +
  24 +/**
  25 + * Provides an object interface to a table row.
  26 + *
  27 + * It lets you add, delete and change rows using objects rather than SQL
  28 + * statements.
  29 + *
  30 + * @package DB
  31 + * @version $Id$
  32 + * @category Database
  33 + * @author Stig Bakken <stig@php.net>
  34 + */
  35 +class DB_storage extends PEAR
  36 +{
  37 + // {{{ properties
  38 +
  39 + /** the name of the table (or view, if the backend database supports
  40 + updates in views) we hold data from */
  41 + var $_table = null;
  42 +
  43 + /** which column(s) in the table contains primary keys, can be a
  44 + string for single-column primary keys, or an array of strings
  45 + for multiple-column primary keys */
  46 + var $_keycolumn = null;
  47 +
  48 + /** DB connection handle used for all transactions */
  49 + var $_dbh = null;
  50 +
  51 + /** an assoc with the names of database fields stored as properties
  52 + in this object */
  53 + var $_properties = array();
  54 +
  55 + /** an assoc with the names of the properties in this object that
  56 + have been changed since they were fetched from the database */
  57 + var $_changes = array();
  58 +
  59 + /** flag that decides if data in this object can be changed.
  60 + objects that don't have their table's key column in their
  61 + property lists will be flagged as read-only. */
  62 + var $_readonly = false;
  63 +
  64 + /** function or method that implements a validator for fields that
  65 + are set, this validator function returns true if the field is
  66 + valid, false if not */
  67 + var $_validator = null;
  68 +
  69 + // }}}
  70 + // {{{ constructor
  71 +
  72 + /**
  73 + * Constructor
  74 + *
  75 + * @param $table string the name of the database table
  76 + *
  77 + * @param $keycolumn mixed string with name of key column, or array of
  78 + * strings if the table has a primary key of more than one column
  79 + *
  80 + * @param $dbh object database connection object
  81 + *
  82 + * @param $validator mixed function or method used to validate
  83 + * each new value, called with three parameters: the name of the
  84 + * field/column that is changing, a reference to the new value and
  85 + * a reference to this object
  86 + *
  87 + */
  88 + function DB_storage($table, $keycolumn, &$dbh, $validator = null)
  89 + {
  90 + $this->PEAR('DB_Error');
  91 + $this->_table = $table;
  92 + $this->_keycolumn = $keycolumn;
  93 + $this->_dbh = $dbh;
  94 + $this->_readonly = false;
  95 + $this->_validator = $validator;
  96 + }
  97 +
  98 + // }}}
  99 + // {{{ _makeWhere()
  100 +
  101 + /**
  102 + * Utility method to build a "WHERE" clause to locate ourselves in
  103 + * the table.
  104 + *
  105 + * XXX future improvement: use rowids?
  106 + *
  107 + * @access private
  108 + */
  109 + function _makeWhere($keyval = null)
  110 + {
  111 + if (is_array($this->_keycolumn)) {
  112 + if ($keyval === null) {
  113 + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
  114 + $keyval[] = $this->{$this->_keycolumn[$i]};
  115 + }
  116 + }
  117 + $whereclause = '';
  118 + for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
  119 + if ($i > 0) {
  120 + $whereclause .= ' AND ';
  121 + }
  122 + $whereclause .= $this->_keycolumn[$i];
  123 + if (is_null($keyval[$i])) {
  124 + // there's not much point in having a NULL key,
  125 + // but we support it anyway
  126 + $whereclause .= ' IS NULL';
  127 + } else {
  128 + $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
  129 + }
  130 + }
  131 + } else {
  132 + if ($keyval === null) {
  133 + $keyval = @$this->{$this->_keycolumn};
  134 + }
  135 + $whereclause = $this->_keycolumn;
  136 + if (is_null($keyval)) {
  137 + // there's not much point in having a NULL key,
  138 + // but we support it anyway
  139 + $whereclause .= ' IS NULL';
  140 + } else {
  141 + $whereclause .= ' = ' . $this->_dbh->quote($keyval);
  142 + }
  143 + }
  144 + return $whereclause;
  145 + }
  146 +
  147 + // }}}
  148 + // {{{ setup()
  149 +
  150 + /**
  151 + * Method used to initialize a DB_storage object from the
  152 + * configured table.
  153 + *
  154 + * @param $keyval mixed the key[s] of the row to fetch (string or array)
  155 + *
  156 + * @return int DB_OK on success, a DB error if not
  157 + */
  158 + function setup($keyval)
  159 + {
  160 + $whereclause = $this->_makeWhere($keyval);
  161 + $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
  162 + $sth = $this->_dbh->query($query);
  163 + if (DB::isError($sth)) {
  164 + return $sth;
  165 + }
  166 + $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
  167 + if (DB::isError($row)) {
  168 + return $row;
  169 + }
  170 + if (!$row) {
  171 + return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
  172 + $query, null, true);
  173 + }
  174 + foreach ($row as $key => $value) {
  175 + $this->_properties[$key] = true;
  176 + $this->$key = $value;
  177 + }
  178 + return DB_OK;
  179 + }
  180 +
  181 + // }}}
  182 + // {{{ insert()
  183 +
  184 + /**
  185 + * Create a new (empty) row in the configured table for this
  186 + * object.
  187 + */
  188 + function insert($newpk)
  189 + {
  190 + if (is_array($this->_keycolumn)) {
  191 + $primarykey = $this->_keycolumn;
  192 + } else {
  193 + $primarykey = array($this->_keycolumn);
  194 + }
  195 + settype($newpk, "array");
  196 + for ($i = 0; $i < sizeof($primarykey); $i++) {
  197 + $pkvals[] = $this->_dbh->quote($newpk[$i]);
  198 + }
  199 +
  200 + $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
  201 + implode(",", $primarykey) . ") VALUES(" .
  202 + implode(",", $pkvals) . ")");
  203 + if (DB::isError($sth)) {
  204 + return $sth;
  205 + }
  206 + if (sizeof($newpk) == 1) {
  207 + $newpk = $newpk[0];
  208 + }
  209 + $this->setup($newpk);
  210 + }
  211 +
  212 + // }}}
  213 + // {{{ toString()
  214 +
  215 + /**
  216 + * Output a simple description of this DB_storage object.
  217 + * @return string object description
  218 + */
  219 + function toString()
  220 + {
  221 + $info = strtolower(get_class($this));
  222 + $info .= " (table=";
  223 + $info .= $this->_table;
  224 + $info .= ", keycolumn=";
  225 + if (is_array($this->_keycolumn)) {
  226 + $info .= "(" . implode(",", $this->_keycolumn) . ")";
  227 + } else {
  228 + $info .= $this->_keycolumn;
  229 + }
  230 + $info .= ", dbh=";
  231 + if (is_object($this->_dbh)) {
  232 + $info .= $this->_dbh->toString();
  233 + } else {
  234 + $info .= "null";
  235 + }
  236 + $info .= ")";
  237 + if (sizeof($this->_properties)) {
  238 + $info .= " [loaded, key=";
  239 + $keyname = $this->_keycolumn;
  240 + if (is_array($keyname)) {
  241 + $info .= "(";
  242 + for ($i = 0; $i < sizeof($keyname); $i++) {
  243 + if ($i > 0) {
  244 + $info .= ",";
  245 + }
  246 + $info .= $this->$keyname[$i];
  247 + }
  248 + $info .= ")";
  249 + } else {
  250 + $info .= $this->$keyname;
  251 + }
  252 + $info .= "]";
  253 + }
  254 + if (sizeof($this->_changes)) {
  255 + $info .= " [modified]";
  256 + }
  257 + return $info;
  258 + }
  259 +
  260 + // }}}
  261 + // {{{ dump()
  262 +
  263 + /**
  264 + * Dump the contents of this object to "standard output".
  265 + */
  266 + function dump()
  267 + {
  268 + foreach ($this->_properties as $prop => $foo) {
  269 + print "$prop = ";
  270 + print htmlentities($this->$prop);
  271 + print "<br />\n";
  272 + }
  273 + }
  274 +
  275 + // }}}
  276 + // {{{ &create()
  277 +
  278 + /**
  279 + * Static method used to create new DB storage objects.
  280 + * @param $data assoc. array where the keys are the names
  281 + * of properties/columns
  282 + * @return object a new instance of DB_storage or a subclass of it
  283 + */
  284 + function &create($table, &$data)
  285 + {
  286 + $classname = strtolower(get_class($this));
  287 + $obj =& new $classname($table);
  288 + foreach ($data as $name => $value) {
  289 + $obj->_properties[$name] = true;
  290 + $obj->$name = &$value;
  291 + }
  292 + return $obj;
  293 + }
  294 +
  295 + // }}}
  296 + // {{{ loadFromQuery()
  297 +
  298 + /**
  299 + * Loads data into this object from the given query. If this
  300 + * object already contains table data, changes will be saved and
  301 + * the object re-initialized first.
  302 + *
  303 + * @param $query SQL query
  304 + *
  305 + * @param $params parameter list in case you want to use
  306 + * prepare/execute mode
  307 + *
  308 + * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
  309 + * returned object is read-only (because the object's specified
  310 + * key column was not found among the columns returned by $query),
  311 + * or another DB error code in case of errors.
  312 + */
  313 +// XXX commented out for now
  314 +/*
  315 + function loadFromQuery($query, $params = null)
  316 + {
  317 + if (sizeof($this->_properties)) {
  318 + if (sizeof($this->_changes)) {
  319 + $this->store();
  320 + $this->_changes = array();
  321 + }
  322 + $this->_properties = array();
  323 + }
  324 + $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
  325 + if (DB::isError($rowdata)) {
  326 + return $rowdata;
  327 + }
  328 + reset($rowdata);
  329 + $found_keycolumn = false;
  330 + while (list($key, $value) = each($rowdata)) {
  331 + if ($key == $this->_keycolumn) {
  332 + $found_keycolumn = true;
  333 + }
  334 + $this->_properties[$key] = true;
  335 + $this->$key = &$value;
  336 + unset($value); // have to unset, or all properties will
  337 + // refer to the same value
  338 + }
  339 + if (!$found_keycolumn) {
  340 + $this->_readonly = true;
  341 + return DB_WARNING_READ_ONLY;
  342 + }
  343 + return DB_OK;
  344 + }
  345 + */
  346 +
  347 + // }}}
  348 + // {{{ set()
  349 +
  350 + /**
  351 + * Modify an attriute value.
  352 + */
  353 + function set($property, $newvalue)
  354 + {
  355 + // only change if $property is known and object is not
  356 + // read-only
  357 + if ($this->_readonly) {
  358 + return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
  359 + null, null, null, true);
  360 + }
  361 + if (@isset($this->_properties[$property])) {
  362 + if (empty($this->_validator)) {
  363 + $valid = true;
  364 + } else {
  365 + $valid = @call_user_func($this->_validator,
  366 + $this->_table,
  367 + $property,
  368 + $newvalue,
  369 + $this->$property,
  370 + $this);
  371 + }
  372 + if ($valid) {
  373 + $this->$property = $newvalue;
  374 + if (empty($this->_changes[$property])) {
  375 + $this->_changes[$property] = 0;
  376 + } else {
  377 + $this->_changes[$property]++;
  378 + }
  379 + } else {
  380 + return $this->raiseError(null, DB_ERROR_INVALID, null,
  381 + null, "invalid field: $property",
  382 + null, true);
  383 + }
  384 + return true;
  385 + }
  386 + return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
  387 + null, "unknown field: $property",
  388 + null, true);
  389 + }
  390 +
  391 + // }}}
  392 + // {{{ &get()
  393 +
  394 + /**
  395 + * Fetch an attribute value.
  396 + *
  397 + * @param string attribute name
  398 + *
  399 + * @return attribute contents, or null if the attribute name is
  400 + * unknown
  401 + */
  402 + function &get($property)
  403 + {
  404 + // only return if $property is known
  405 + if (isset($this->_properties[$property])) {
  406 + return $this->$property;
  407 + }
  408 + $tmp = null;
  409 + return $tmp;
  410 + }
  411 +
  412 + // }}}
  413 + // {{{ _DB_storage()
  414 +
  415 + /**
  416 + * Destructor, calls DB_storage::store() if there are changes
  417 + * that are to be kept.
  418 + */
  419 + function _DB_storage()
  420 + {
  421 + if (sizeof($this->_changes)) {
  422 + $this->store();
  423 + }
  424 + $this->_properties = array();
  425 + $this->_changes = array();
  426 + $this->_table = null;
  427 + }
  428 +
  429 + // }}}
  430 + // {{{ store()
  431 +
  432 + /**
  433 + * Stores changes to this object in the database.
  434 + *
  435 + * @return DB_OK or a DB error
  436 + */
  437 + function store()
  438 + {
  439 + foreach ($this->_changes as $name => $foo) {
  440 + $params[] = &$this->$name;
  441 + $vars[] = $name . ' = ?';
  442 + }
  443 + if ($vars) {
  444 + $query = 'UPDATE ' . $this->_table . ' SET ' .
  445 + implode(', ', $vars) . ' WHERE ' .
  446 + $this->_makeWhere();
  447 + $stmt = $this->_dbh->prepare($query);
  448 + $res = $this->_dbh->execute($stmt, $params);
  449 + if (DB::isError($res)) {
  450 + return $res;
  451 + }
  452 + $this->_changes = array();
  453 + }
  454 + return DB_OK;
  455 + }
  456 +
  457 + // }}}
  458 + // {{{ remove()
  459 +
  460 + /**
  461 + * Remove the row represented by this object from the database.
  462 + *
  463 + * @return mixed DB_OK or a DB error
  464 + */
  465 + function remove()
  466 + {
  467 + if ($this->_readonly) {
  468 + return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
  469 + null, null, null, true);
  470 + }
  471 + $query = 'DELETE FROM ' . $this->_table .' WHERE '.
  472 + $this->_makeWhere();
  473 + $res = $this->_dbh->query($query);
  474 + if (DB::isError($res)) {
  475 + return $res;
  476 + }
  477 + foreach ($this->_properties as $prop => $foo) {
  478 + unset($this->$prop);
  479 + }
  480 + $this->_properties = array();
  481 + $this->_changes = array();
  482 + return DB_OK;
  483 + }
  484 +
  485 + // }}}
  486 +}
  487 +
  488 +/*
  489 + * Local variables:
  490 + * tab-width: 4
  491 + * c-basic-offset: 4
  492 + * End:
  493 + */
  494 +
  495 +?>
... ...
pear/DB/sybase.php 0 → 100644
  1 +<?php
  2 +/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3 +// +----------------------------------------------------------------------+
  4 +// | PHP Version 4 |
  5 +// +----------------------------------------------------------------------+
  6 +// | Copyright (c) 1997-2004 The PHP Group |
  7 +// +----------------------------------------------------------------------+
  8 +// | This source file is subject to version 2.02 of the PHP license, |
  9 +// | that is bundled with this package in the file LICENSE, and is |
  10 +// | available at through the world-wide-web at |
  11 +// | http://www.php.net/license/2_02.txt. |
  12 +// | If you did not receive a copy of the PHP license and are unable to |
  13 +// | obtain it through the world-wide-web, please send a note to |
  14 +// | license@php.net so we can mail you a copy immediately. |
  15 +// +----------------------------------------------------------------------+
  16 +// | Authors: Sterling Hughes <sterling@php.net> |
  17 +// | Antônio Carlos Venâncio Júnior <floripa@php.net> |
  18 +// | Maintainer: Daniel Convissor <danielc@php.net> |
  19 +// +----------------------------------------------------------------------+
  20 +//
  21 +// $Id$
  22 +
  23 +
  24 +// TODO
  25 +// - This driver may fail with multiple connections under the same
  26 +// user/pass/host and different databases
  27 +
  28 +
  29 +require_once 'DB/common.php';
  30 +
  31 +/**
  32 + * Database independent query interface definition for PHP's Sybase
  33 + * extension.
  34 + *
  35 + * @package DB
  36 + * @version $Id$
  37 + * @category Database
  38 + * @author Sterling Hughes <sterling@php.net>
  39 + * @author Antônio Carlos Venâncio Júnior <floripa@php.net>
  40 + */
  41 +class DB_sybase extends DB_common
  42 +{
  43 + // {{{ properties
  44 +
  45 + var $connection;
  46 + var $phptype, $dbsyntax;
  47 + var $prepare_tokens = array();
  48 + var $prepare_types = array();
  49 + var $transaction_opcount = 0;
  50 + var $autocommit = true;
  51 +
  52 + // }}}
  53 + // {{{ constructor
  54 +
  55 + /**
  56 + * DB_sybase constructor.
  57 + *
  58 + * @access public
  59 + */
  60 + function DB_sybase()
  61 + {
  62 + $this->DB_common();
  63 + $this->phptype = 'sybase';
  64 + $this->dbsyntax = 'sybase';
  65 + $this->features = array(
  66 + 'prepare' => false,
  67 + 'pconnect' => true,
  68 + 'transactions' => false,
  69 + 'limit' => 'emulate'
  70 + );
  71 + $this->errorcode_map = array(
  72 + );
  73 + }
  74 +
  75 + // }}}
  76 + // {{{ connect()
  77 +
  78 + /**
  79 + * Connect to a database and log in as the specified user.
  80 + *
  81 + * @param $dsn the data source name (see DB::parseDSN for syntax)
  82 + * @param $persistent (optional) whether the connection should
  83 + * be persistent
  84 + * @access public
  85 + * @return int DB_OK on success, a DB error on failure
  86 + */
  87 + function connect($dsninfo, $persistent = false)
  88 + {
  89 + if (!DB::assertExtension('sybase') &&
  90 + !DB::assertExtension('sybase_ct'))
  91 + {
  92 + return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
  93 + }
  94 +
  95 + $this->dsn = $dsninfo;
  96 +
  97 + $interface = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
  98 + $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect';
  99 + $dsninfo['password'] = !empty($dsninfo['password']) ? $dsninfo['password'] : false;
  100 + $dsninfo['charset'] = isset($dsninfo['charset']) ? $dsninfo['charset'] : false;
  101 + $dsninfo['appname'] = isset($dsninfo['appname']) ? $dsninfo['appname'] : false;
  102 +
  103 + if ($interface && $dsninfo['username']) {
  104 + $conn = @$connect_function($interface, $dsninfo['username'],
  105 + $dsninfo['password'],
  106 + $dsninfo['charset'],
  107 + $dsninfo['appname']);
  108 + } else {
  109 + $conn = false;
  110 + }
  111 +
  112 + if (!$conn) {
  113 + return $this->raiseError(DB_ERROR_CONNECT_FAILED);
  114 + }
  115 +
  116 + if ($dsninfo['database']) {
  117 + if (!@sybase_select_db($dsninfo['database'], $conn)) {
  118 + return $this->raiseError(DB_ERROR_NODBSELECTED, null,
  119 + null, null, @sybase_get_last_message());
  120 + }
  121 + $this->_db = $dsninfo['database'];
  122 + }
  123 +
  124 + $this->connection = $conn;
  125 + return DB_OK;
  126 + }
  127 +
  128 + // }}}
  129 + // {{{ disconnect()
  130 +
  131 + /**
  132 + * Log out and disconnect from the database.
  133 + *
  134 + * @access public
  135 + *
  136 + * @return bool true on success, false if not connected.
  137 + */
  138 + function disconnect()
  139 + {
  140 + $ret = @sybase_close($this->connection);
  141 + $this->connection = null;
  142 + return $ret;
  143 + }
  144 +
  145 + // }}}
  146 + // {{{ errorNative()
  147 +
  148 + /**
  149 + * Get the last server error messge (if any)
  150 + *
  151 + * @return string sybase last error message
  152 + */
  153 + function errorNative()
  154 + {
  155 + return @sybase_get_last_message();
  156 + }
  157 +
  158 + // }}}
  159 + // {{{ errorCode()
  160 +
  161 + /**
  162 + * Determine PEAR::DB error code from the database's text error message.
  163 + *
  164 + * @param string $errormsg error message returned from the database
  165 + * @return integer an error number from a DB error constant
  166 + */
  167 + function errorCode($errormsg)
  168 + {
  169 + static $error_regexps;
  170 + if (!isset($error_regexps)) {
  171 + $error_regexps = array(
  172 + '/Incorrect syntax near/'
  173 + => DB_ERROR_SYNTAX,
  174 + '/^Unclosed quote before the character string [\"\'].*[\"\']\./'
  175 + => DB_ERROR_SYNTAX,
  176 + '/Implicit conversion from datatype [\"\'].+[\"\'] to [\"\'].+[\"\'] is not allowed\./'
  177 + => DB_ERROR_INVALID_NUMBER,
  178 + '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./'
  179 + => DB_ERROR_NOSUCHTABLE,
  180 + '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./'
  181 + => DB_ERROR_ACCESS_VIOLATION,
  182 + '/^.+ permission denied on object .+, database .+, owner .+/'
  183 + => DB_ERROR_ACCESS_VIOLATION,
  184 + '/^.* permission denied, database .+, owner .+/'
  185 + => DB_ERROR_ACCESS_VIOLATION,
  186 + '/[^.*] not found\./'
  187 + => DB_ERROR_NOSUCHTABLE,
  188 + '/There is already an object named/'
  189 + => DB_ERROR_ALREADY_EXISTS,
  190 + '/Invalid column name/'
  191 + => DB_ERROR_NOSUCHFIELD,
  192 + '/does not allow null values/'
  193 + => DB_ERROR_CONSTRAINT_NOT_NULL,
  194 + '/Command has been aborted/'
  195 + => DB_ERROR_CONSTRAINT,
  196 + );
  197 + }
  198 +
  199 + foreach ($error_regexps as $regexp => $code) {
  200 + if (preg_match($regexp, $errormsg)) {
  201 + return $code;
  202 + }
  203 + }
  204 + return DB_ERROR;
  205 + }
  206 +
  207 + // }}}
  208 + // {{{ sybaseRaiseError()
  209 +
  210 + /**
  211 + * Gather information about an error, then use that info to create a
  212 + * DB error object and finally return that object.
  213 + *
  214 + * @param integer $errno PEAR error number (usually a DB constant) if
  215 + * manually raising an error
  216 + * @return object DB error object
  217 + * @see errorNative()
  218 + * @see errorCode()
  219 + * @see DB_common::raiseError()
  220 + */
  221 + function sybaseRaiseError($errno = null)
  222 + {
  223 + $native = $this->errorNative();
  224 + if ($errno === null) {
  225 + $errno = $this->errorCode($native);
  226 + }
  227 + return $this->raiseError($errno, null, null, null, $native);
  228 + }
  229 +
  230 + // }}}
  231 + // {{{ simpleQuery()
  232 +
  233 + /**
  234 + * Send a query to Sybase and return the results as a Sybase resource
  235 + * identifier.
  236 + *
  237 + * @param the SQL query
  238 + *
  239 + * @access public
  240 + *
  241 + * @return mixed returns a valid Sybase result for successful SELECT
  242 + * queries, DB_OK for other successful queries. A DB error is
  243 + * returned on failure.
  244 + */
  245 + function simpleQuery($query)
  246 + {
  247 + $ismanip = DB::isManip($query);
  248 + $this->last_query = $query;
  249 + if (!@sybase_select_db($this->_db, $this->connection)) {
  250 + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
  251 + }
  252 + $query = $this->modifyQuery($query);
  253 + if (!$this->autocommit && $ismanip) {
  254 + if ($this->transaction_opcount == 0) {
  255 + $result = @sybase_query('BEGIN TRANSACTION', $this->connection);
  256 + if (!$result) {
  257 + return $this->sybaseRaiseError();
  258 + }
  259 + }
  260 + $this->transaction_opcount++;
  261 + }
  262 + $result = @sybase_query($query, $this->connection);
  263 + if (!$result) {
  264 + return $this->sybaseRaiseError();
  265 + }
  266 + if (is_resource($result)) {
  267 + $numrows = $this->numRows($result);
  268 + if (is_object($numrows)) {
  269 + return $numrows;
  270 + }
  271 + $this->num_rows[(int)$result] = $numrows;
  272 + return $result;
  273 + }
  274 + // Determine which queries that should return data, and which
  275 + // should return an error code only.
  276 + return $ismanip ? DB_OK : $result;
  277 + }
  278 +
  279 + // }}}
  280 + // {{{ nextResult()
  281 +
  282 + /**
  283 + * Move the internal sybase result pointer to the next available result
  284 + *
  285 + * @param a valid sybase result resource
  286 + *
  287 + * @access public
  288 + *
  289 + * @return true if a result is available otherwise return false
  290 + */
  291 + function nextResult($result)
  292 + {
  293 + return false;
  294 + }
  295 +
  296 + // }}}
  297 + // {{{ fetchInto()
  298 +
  299 + /**
  300 + * Fetch a row and insert the data into an existing array.
  301 + *
  302 + * Formating of the array and the data therein are configurable.
  303 + * See DB_result::fetchInto() for more information.
  304 + *
  305 + * @param resource $result query result identifier
  306 + * @param array $arr (reference) array where data from the row
  307 + * should be placed
  308 + * @param int $fetchmode how the resulting array should be indexed
  309 + * @param int $rownum the row number to fetch
  310 + *
  311 + * @return mixed DB_OK on success, null when end of result set is
  312 + * reached or on failure
  313 + *
  314 + * @see DB_result::fetchInto()
  315 + * @access private
  316 + */
  317 + function fetchInto($result, &$arr, $fetchmode, $rownum=null)
  318 + {
  319 + if ($rownum !== null) {
  320 + if (!@sybase_data_seek($result, $rownum)) {
  321 + return null;
  322 + }
  323 + }
  324 + if ($fetchmode & DB_FETCHMODE_ASSOC) {
  325 + if (function_exists('sybase_fetch_assoc')) {
  326 + $arr = @sybase_fetch_assoc($result);
  327 + } else {
  328 + if ($arr = @sybase_fetch_array($result)) {
  329 + foreach ($arr as $key => $value) {
  330 + if (is_int($key)) {
  331 + unset($arr[$key]);
  332 + }
  333 + }
  334 + }
  335 + }
  336 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
  337 + $arr = array_change_key_case($arr, CASE_LOWER);
  338 + }
  339 + } else {
  340 + $arr = @sybase_fetch_row($result);
  341 + }
  342 + if (!$arr) {
  343 + // reported not work as seems that sybase_get_last_message()
  344 + // always return a message here
  345 + //if ($errmsg = @sybase_get_last_message()) {
  346 + // return $this->sybaseRaiseError($errmsg);
  347 + //} else {
  348 + return null;
  349 + //}
  350 + }
  351 + if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
  352 + $this->_rtrimArrayValues($arr);
  353 + }
  354 + if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
  355 + $this->_convertNullArrayValuesToEmpty($arr);
  356 + }
  357 + return DB_OK;
  358 + }
  359 +
  360 + // }}}
  361 + // {{{ freeResult()
  362 +
  363 + /**
  364 + * Free the internal resources associated with $result.
  365 + *
  366 + * @param $result Sybase result identifier
  367 + *
  368 + * @access public
  369 + *
  370 + * @return bool true on success, false if $result is invalid
  371 + */
  372 + function freeResult($result)
  373 + {
  374 + unset($this->num_rows[(int)$result]);
  375 + return @sybase_free_result($result);
  376 + }
  377 +
  378 + // }}}
  379 + // {{{ numCols()
  380 +
  381 + /**
  382 + * Get the number of columns in a result set.
  383 + *
  384 + * @param $result Sybase result identifier
  385 + *
  386 + * @access public
  387 + *
  388 + * @return int the number of columns per row in $result
  389 + */
  390 + function numCols($result)
  391 + {
  392 + $cols = @sybase_num_fields($result);
  393 + if (!$cols) {
  394 + return $this->sybaseRaiseError();
  395 + }
  396 + return $cols;
  397 + }
  398 +
  399 + // }}}
  400 + // {{{ numRows()
  401 +
  402 + /**
  403 + * Get the number of rows in a result set.
  404 + *
  405 + * @param $result Sybase result identifier
  406 + *
  407 + * @access public
  408 + *
  409 + * @return int the number of rows in $result
  410 + */
  411 + function numRows($result)
  412 + {
  413 + $rows = @sybase_num_rows($result);
  414 + if ($rows === false) {
  415 + return $this->sybaseRaiseError();
  416 + }
  417 + return $rows;
  418 + }
  419 +
  420 + // }}}
  421 + // {{{ affectedRows()
  422 +
  423 + /**
  424 + * Gets the number of rows affected by the data manipulation
  425 + * query. For other queries, this function returns 0.
  426 + *
  427 + * @return number of rows affected by the last query
  428 + */
  429 + function affectedRows()
  430 + {
  431 + if (DB::isManip($this->last_query)) {
  432 + $result = @sybase_affected_rows($this->connection);
  433 + } else {
  434 + $result = 0;
  435 + }
  436 + return $result;
  437 + }
  438 +
  439 + // }}}
  440 + // {{{ nextId()
  441 +
  442 + /**
  443 + * Returns the next free id in a sequence
  444 + *
  445 + * @param string $seq_name name of the sequence
  446 + * @param boolean $ondemand when true, the seqence is automatically
  447 + * created if it does not exist
  448 + *
  449 + * @return int the next id number in the sequence. DB_Error if problem.
  450 + *
  451 + * @internal
  452 + * @see DB_common::nextID()
  453 + * @access public
  454 + */
  455 + function nextId($seq_name, $ondemand = true)
  456 + {
  457 + $seqname = $this->getSequenceName($seq_name);
  458 + if (!@sybase_select_db($this->_db, $this->connection)) {
  459 + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
  460 + }
  461 + $repeat = 0;
  462 + do {
  463 + $this->pushErrorHandling(PEAR_ERROR_RETURN);
  464 + $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
  465 + $this->popErrorHandling();
  466 + if ($ondemand && DB::isError($result) &&
  467 + ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
  468 + {
  469 + $repeat = 1;
  470 + $result = $this->createSequence($seq_name);
  471 + if (DB::isError($result)) {
  472 + return $this->raiseError($result);
  473 + }
  474 + } elseif (!DB::isError($result)) {
  475 + $result =& $this->query("SELECT @@IDENTITY FROM $seqname");
  476 + $repeat = 0;
  477 + } else {
  478 + $repeat = false;
  479 + }
  480 + } while ($repeat);
  481 + if (DB::isError($result)) {
  482 + return $this->raiseError($result);
  483 + }
  484 + $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
  485 + return $result[0];
  486 + }
  487 +
  488 + /**
  489 + * Creates a new sequence
  490 + *
  491 + * @param string $seq_name name of the new sequence
  492 + *
  493 + * @return int DB_OK on success. A DB_Error object is returned if
  494 + * problems arise.
  495 + *
  496 + * @internal
  497 + * @see DB_common::createSequence()
  498 + * @access public
  499 + */
  500 + function createSequence($seq_name)
  501 + {
  502 + $seqname = $this->getSequenceName($seq_name);
  503 + return $this->query("CREATE TABLE $seqname ".
  504 + '(id numeric(10,0) IDENTITY NOT NULL ,' .
  505 + 'vapor int NULL)');
  506 + }
  507 +
  508 + // }}}
  509 + // {{{ dropSequence()
  510 +
  511 + /**
  512 + * Deletes a sequence
  513 + *
  514 + * @param string $seq_name name of the sequence to be deleted
  515 + *
  516 + * @return int DB_OK on success. DB_Error if problems.
  517 + *
  518 + * @internal
  519 + * @see DB_common::dropSequence()
  520 + * @access public
  521 + */
  522 + function dropSequence($seq_name)
  523 + {
  524 + $seqname = $this->getSequenceName($seq_name);
  525 + return $this->query("DROP TABLE $seqname");
  526 + }
  527 +
  528 + // }}}
  529 + // {{{ getSpecialQuery()
  530 +
  531 + /**
  532 + * Returns the query needed to get some backend info
  533 + * @param string $type What kind of info you want to retrieve
  534 + * @return string The SQL query string
  535 + */
  536 + function getSpecialQuery($type)
  537 + {
  538 + switch ($type) {
  539 + case 'tables':
  540 + return "select name from sysobjects where type = 'U' order by name";
  541 + case 'views':
  542 + return "select name from sysobjects where type = 'V'";
  543 + default:
  544 + return null;
  545 + }
  546 + }
  547 +
  548 + // }}}
  549 + // {{{ autoCommit()
  550 +
  551 + /**
  552 + * Enable/disable automatic commits
  553 + */
  554 + function autoCommit($onoff = false)
  555 + {
  556 + // XXX if $this->transaction_opcount > 0, we should probably
  557 + // issue a warning here.
  558 + $this->autocommit = $onoff ? true : false;
  559 + return DB_OK;
  560 + }
  561 +
  562 + // }}}
  563 + // {{{ commit()
  564 +
  565 + /**
  566 + * Commit the current transaction.
  567 + */
  568 + function commit()
  569 + {
  570 + if ($this->transaction_opcount > 0) {
  571 + if (!@sybase_select_db($this->_db, $this->connection)) {
  572 + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
  573 + }
  574 + $result = @sybase_query('COMMIT', $this->connection);
  575 + $this->transaction_opcount = 0;
  576 + if (!$result) {
  577 + return $this->sybaseRaiseError();
  578 + }
  579 + }
  580 + return DB_OK;
  581 + }
  582 +
  583 + // }}}
  584 + // {{{ rollback()
  585 +
  586 + /**
  587 + * Roll back (undo) the current transaction.
  588 + */
  589 + function rollback()
  590 + {
  591 + if ($this->transaction_opcount > 0) {
  592 + if (!@sybase_select_db($this->_db, $this->connection)) {
  593 + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
  594 + }
  595 + $result = @sybase_query('ROLLBACK', $this->connection);
  596 + $this->transaction_opcount = 0;
  597 + if (!$result) {
  598 + return $this->sybaseRaiseError();
  599 + }
  600 + }
  601 + return DB_OK;
  602 + }
  603 +
  604 + // }}}
  605 + // {{{ tableInfo()
  606 +
  607 + /**
  608 + * Returns information about a table or a result set.
  609 + *
  610 + * NOTE: only supports 'table' and 'flags' if <var>$result</var>
  611 + * is a table name.
  612 + *
  613 + * @param object|string $result DB_result object from a query or a
  614 + * string containing the name of a table
  615 + * @param int $mode a valid tableInfo mode
  616 + * @return array an associative array with the information requested
  617 + * or an error object if something is wrong
  618 + * @access public
  619 + * @internal
  620 + * @since 1.6.0
  621 + * @see DB_common::tableInfo()
  622 + */
  623 + function tableInfo($result, $mode = null)
  624 + {
  625 + if (isset($result->result)) {
  626 + /*
  627 + * Probably received a result object.
  628 + * Extract the result resource identifier.
  629 + */
  630 + $id = $result->result;
  631 + $got_string = false;
  632 + } elseif (is_string($result)) {
  633 + /*
  634 + * Probably received a table name.
  635 + * Create a result resource identifier.
  636 + */
  637 + if (!@sybase_select_db($this->_db, $this->connection)) {
  638 + return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
  639 + }
  640 + $id = @sybase_query("SELECT * FROM $result WHERE 1=0",
  641 + $this->connection);
  642 + $got_string = true;
  643 + } else {
  644 + /*
  645 + * Probably received a result resource identifier.
  646 + * Copy it.
  647 + * Deprecated. Here for compatibility only.
  648 + */
  649 + $id = $result;
  650 + $got_string = false;
  651 + }
  652 +
  653 + if (!is_resource($id)) {
  654 + return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA);
  655 + }
  656 +
  657 + if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
  658 + $case_func = 'strtolower';
  659 + } else {
  660 + $case_func = 'strval';
  661 + }
  662 +
  663 + $count = @sybase_num_fields($id);
  664 +
  665 + // made this IF due to performance (one if is faster than $count if's)
  666 + if (!$mode) {
  667 +
  668 + for ($i=0; $i<$count; $i++) {
  669 + $f = @sybase_fetch_field($id, $i);
  670 +
  671 + // column_source is often blank
  672 + if ($got_string) {
  673 + $res[$i]['table'] = $case_func($result);
  674 + } else {
  675 + $res[$i]['table'] = $case_func($f->column_source);
  676 + }
  677 + $res[$i]['name'] = $case_func($f->name);
  678 + $res[$i]['type'] = $f->type;
  679 + $res[$i]['len'] = $f->max_length;
  680 + if ($res[$i]['table']) {
  681 + $res[$i]['flags'] = $this->_sybase_field_flags(
  682 + $res[$i]['table'], $res[$i]['name']);
  683 + } else {
  684 + $res[$i]['flags'] = '';
  685 + }
  686 + }
  687 +
  688 + } else {
  689 + // get full info
  690 +
  691 + $res['num_fields'] = $count;
  692 +
  693 + for ($i=0; $i<$count; $i++) {
  694 + $f = @sybase_fetch_field($id, $i);
  695 +
  696 + // column_source is often blank
  697 + if ($got_string) {
  698 + $res[$i]['table'] = $case_func($result);
  699 + } else {
  700 + $res[$i]['table'] = $case_func($f->column_source);
  701 + }
  702 + $res[$i]['name'] = $case_func($f->name);
  703 + $res[$i]['type'] = $f->type;
  704 + $res[$i]['len'] = $f->max_length;
  705 + if ($res[$i]['table']) {
  706 + $res[$i]['flags'] = $this->_sybase_field_flags(
  707 + $res[$i]['table'], $res[$i]['name']);
  708 + } else {
  709 + $res[$i]['flags'] = '';
  710 + }
  711 +
  712 + if ($mode & DB_TABLEINFO_ORDER) {
  713 + $res['order'][$res[$i]['name']] = $i;
  714 + }
  715 + if ($mode & DB_TABLEINFO_ORDERTABLE) {
  716 + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
  717 + }
  718 + }
  719 + }
  720 +
  721 + // free the result only if we were called on a table
  722 + if ($got_string) {
  723 + @sybase_free_result($id);
  724 + }
  725 + return $res;
  726 + }
  727 +
  728 + // }}}
  729 + // {{{ _sybase_field_flags()
  730 +
  731 + /**
  732 + * Get the flags for a field.
  733 + *
  734 + * Currently supports:
  735 + * + <samp>unique_key</samp> (unique index, unique check or primary_key)
  736 + * + <samp>multiple_key</samp> (multi-key index)
  737 + *
  738 + * @param string $table table name
  739 + * @param string $column field name
  740 + * @return string space delimited string of flags. Empty string if none.
  741 + * @access private
  742 + */
  743 + function _sybase_field_flags($table, $column)
  744 + {
  745 + static $tableName = null;
  746 + static $flags = array();
  747 +
  748 + if ($table != $tableName) {
  749 + $flags = array();
  750 + $tableName = $table;
  751 +
  752 + // get unique/primary keys
  753 + $res = $this->getAll("sp_helpindex $table", DB_FETCHMODE_ASSOC);
  754 +
  755 + if (!isset($res[0]['index_description'])) {
  756 + return '';
  757 + }
  758 +
  759 + foreach ($res as $val) {
  760 + $keys = explode(', ', trim($val['index_keys']));
  761 +
  762 + if (sizeof($keys) > 1) {
  763 + foreach ($keys as $key) {
  764 + $this->_add_flag($flags[$key], 'multiple_key');
  765 + }
  766 + }
  767 +
  768 + if (strpos($val['index_description'], 'unique')) {
  769 + foreach ($keys as $key) {
  770 + $this->_add_flag($flags[$key], 'unique_key');
  771 + }
  772 + }
  773 + }
  774 +
  775 + }
  776 +
  777 + if (array_key_exists($column, $flags)) {
  778 + return(implode(' ', $flags[$column]));
  779 + }
  780 +
  781 + return '';
  782 + }
  783 +
  784 + // }}}
  785 + // {{{ _add_flag()
  786 +
  787 + /**
  788 + * Adds a string to the flags array if the flag is not yet in there
  789 + * - if there is no flag present the array is created.
  790 + *
  791 + * @param array $array reference of flags array to add a value to
  792 + * @param mixed $value value to add to the flag array
  793 + * @access private
  794 + */
  795 + function _add_flag(&$array, $value)
  796 + {
  797 + if (!is_array($array)) {
  798 + $array = array($value);
  799 + } elseif (!in_array($value, $array)) {
  800 + array_push($array, $value);
  801 + }
  802 + }
  803 +
  804 + // }}}
  805 + // {{{ quoteIdentifier()
  806 +
  807 + /**
  808 + * Quote a string so it can be safely used as a table / column name
  809 + *
  810 + * Quoting style depends on which database driver is being used.
  811 + *
  812 + * @param string $str identifier name to be quoted
  813 + *
  814 + * @return string quoted identifier string
  815 + *
  816 + * @since 1.6.0
  817 + * @access public
  818 + */
  819 + function quoteIdentifier($str)
  820 + {
  821 + return '[' . str_replace(']', ']]', $str) . ']';
  822 + }
  823 +
  824 + // }}}
  825 +
  826 +}
  827 +
  828 +/*
  829 + * Local variables:
  830 + * tab-width: 4
  831 + * c-basic-offset: 4
  832 + * End:
  833 + */
  834 +
  835 +?>
... ...