Commit affd3e45e87cfcc7beaf030688792a03086453ae
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
Showing
16 changed files
with
12611 additions
and
0 deletions
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¶m2=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 | +?> |