1<?php 2 3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 4 5/** 6 * The PEAR DB driver for PHP's fbsql extension 7 * for interacting with FrontBase databases 8 * 9 * PHP version 5 10 * 11 * LICENSE: This source file is subject to version 3.0 of the PHP license 12 * that is available through the world-wide-web at the following URI: 13 * http://www.php.net/license/3_0.txt. If you did not receive a copy of 14 * the PHP License and are unable to obtain it through the web, please 15 * send a note to license@php.net so we can mail you a copy immediately. 16 * 17 * @category Database 18 * @package DB 19 * @author Frank M. Kromann <frank@frontbase.com> 20 * @author Daniel Convissor <danielc@php.net> 21 * @copyright 1997-2007 The PHP Group 22 * @license http://www.php.net/license/3_0.txt PHP License 3.0 23 * @version CVS: $Id$ 24 * @link http://pear.php.net/package/DB 25 */ 26 27/** 28 * Obtain the DB_common class so it can be extended from 29 */ 30require_once 'DB/common.php'; 31 32/** 33 * The methods PEAR DB uses to interact with PHP's fbsql extension 34 * for interacting with FrontBase databases 35 * 36 * These methods overload the ones declared in DB_common. 37 * 38 * @category Database 39 * @package DB 40 * @author Frank M. Kromann <frank@frontbase.com> 41 * @author Daniel Convissor <danielc@php.net> 42 * @copyright 1997-2007 The PHP Group 43 * @license http://www.php.net/license/3_0.txt PHP License 3.0 44 * @version Release: 1.11.0 45 * @link http://pear.php.net/package/DB 46 * @since Class functional since Release 1.7.0 47 */ 48class DB_fbsql extends DB_common 49{ 50 // {{{ properties 51 52 /** 53 * The DB driver type (mysql, oci8, odbc, etc.) 54 * @var string 55 */ 56 var $phptype = 'fbsql'; 57 58 /** 59 * The database syntax variant to be used (db2, access, etc.), if any 60 * @var string 61 */ 62 var $dbsyntax = 'fbsql'; 63 64 /** 65 * The capabilities of this DB implementation 66 * 67 * The 'new_link' element contains the PHP version that first provided 68 * new_link support for this DBMS. Contains false if it's unsupported. 69 * 70 * Meaning of the 'limit' element: 71 * + 'emulate' = emulate with fetch row by number 72 * + 'alter' = alter the query 73 * + false = skip rows 74 * 75 * @var array 76 */ 77 var $features = array( 78 'limit' => 'alter', 79 'new_link' => false, 80 'numrows' => true, 81 'pconnect' => true, 82 'prepare' => false, 83 'ssl' => false, 84 'transactions' => true, 85 ); 86 87 /** 88 * A mapping of native error codes to DB error codes 89 * @var array 90 */ 91 var $errorcode_map = array( 92 22 => DB_ERROR_SYNTAX, 93 85 => DB_ERROR_ALREADY_EXISTS, 94 108 => DB_ERROR_SYNTAX, 95 116 => DB_ERROR_NOSUCHTABLE, 96 124 => DB_ERROR_VALUE_COUNT_ON_ROW, 97 215 => DB_ERROR_NOSUCHFIELD, 98 217 => DB_ERROR_INVALID_NUMBER, 99 226 => DB_ERROR_NOSUCHFIELD, 100 231 => DB_ERROR_INVALID, 101 239 => DB_ERROR_TRUNCATED, 102 251 => DB_ERROR_SYNTAX, 103 266 => DB_ERROR_NOT_FOUND, 104 357 => DB_ERROR_CONSTRAINT_NOT_NULL, 105 358 => DB_ERROR_CONSTRAINT, 106 360 => DB_ERROR_CONSTRAINT, 107 361 => DB_ERROR_CONSTRAINT, 108 ); 109 110 /** 111 * The raw database connection created by PHP 112 * @var resource 113 */ 114 var $connection; 115 116 /** 117 * The DSN information for connecting to a database 118 * @var array 119 */ 120 var $dsn = array(); 121 122 123 // }}} 124 // {{{ constructor 125 126 /** 127 * This constructor calls <kbd>parent::__construct()</kbd> 128 * 129 * @return void 130 */ 131 function __construct() 132 { 133 parent::__construct(); 134 } 135 136 // }}} 137 // {{{ connect() 138 139 /** 140 * Connect to the database server, log in and open the database 141 * 142 * Don't call this method directly. Use DB::connect() instead. 143 * 144 * @param array $dsn the data source name 145 * @param bool $persistent should the connection be persistent? 146 * 147 * @return int DB_OK on success. A DB_Error object on failure. 148 */ 149 function connect($dsn, $persistent = false) 150 { 151 if (!PEAR::loadExtension('fbsql')) { 152 return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND); 153 } 154 155 $this->dsn = $dsn; 156 if ($dsn['dbsyntax']) { 157 $this->dbsyntax = $dsn['dbsyntax']; 158 } 159 160 $params = array( 161 $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost', 162 $dsn['username'] ? $dsn['username'] : null, 163 $dsn['password'] ? $dsn['password'] : null, 164 ); 165 166 $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect'; 167 168 $ini = ini_get('track_errors'); 169 $php_errormsg = ''; 170 if ($ini) { 171 $this->connection = @call_user_func_array($connect_function, 172 $params); 173 } else { 174 @ini_set('track_errors', 1); 175 $this->connection = @call_user_func_array($connect_function, 176 $params); 177 @ini_set('track_errors', $ini); 178 } 179 180 if (!$this->connection) { 181 return $this->raiseError(DB_ERROR_CONNECT_FAILED, 182 null, null, null, 183 $php_errormsg); 184 } 185 186 if ($dsn['database']) { 187 if (!@fbsql_select_db($dsn['database'], $this->connection)) { 188 return $this->fbsqlRaiseError(); 189 } 190 } 191 192 return DB_OK; 193 } 194 195 // }}} 196 // {{{ disconnect() 197 198 /** 199 * Disconnects from the database server 200 * 201 * @return bool TRUE on success, FALSE on failure 202 */ 203 function disconnect() 204 { 205 $ret = @fbsql_close($this->connection); 206 $this->connection = null; 207 return $ret; 208 } 209 210 // }}} 211 // {{{ simpleQuery() 212 213 /** 214 * Sends a query to the database server 215 * 216 * @param string the SQL query string 217 * 218 * @return mixed + a PHP result resrouce for successful SELECT queries 219 * + the DB_OK constant for other successful queries 220 * + a DB_Error object on failure 221 */ 222 function simpleQuery($query) 223 { 224 $this->last_query = $query; 225 $query = $this->modifyQuery($query); 226 $result = @fbsql_query("$query;", $this->connection); 227 if (!$result) { 228 return $this->fbsqlRaiseError(); 229 } 230 // Determine which queries that should return data, and which 231 // should return an error code only. 232 if ($this->_checkManip($query)) { 233 return DB_OK; 234 } 235 return $result; 236 } 237 238 // }}} 239 // {{{ nextResult() 240 241 /** 242 * Move the internal fbsql result pointer to the next available result 243 * 244 * @param a valid fbsql result resource 245 * 246 * @access public 247 * 248 * @return true if a result is available otherwise return false 249 */ 250 function nextResult($result) 251 { 252 return @fbsql_next_result($result); 253 } 254 255 // }}} 256 // {{{ fetchInto() 257 258 /** 259 * Places a row from the result set into the given array 260 * 261 * Formating of the array and the data therein are configurable. 262 * See DB_result::fetchInto() for more information. 263 * 264 * This method is not meant to be called directly. Use 265 * DB_result::fetchInto() instead. It can't be declared "protected" 266 * because DB_result is a separate object. 267 * 268 * @param resource $result the query result resource 269 * @param array $arr the referenced array to put the data in 270 * @param int $fetchmode how the resulting array should be indexed 271 * @param int $rownum the row number to fetch (0 = first row) 272 * 273 * @return mixed DB_OK on success, NULL when the end of a result set is 274 * reached or on failure 275 * 276 * @see DB_result::fetchInto() 277 */ 278 function fetchInto($result, &$arr, $fetchmode, $rownum = null) 279 { 280 if ($rownum !== null) { 281 if (!@fbsql_data_seek($result, $rownum)) { 282 return null; 283 } 284 } 285 if ($fetchmode & DB_FETCHMODE_ASSOC) { 286 $arr = @fbsql_fetch_array($result, FBSQL_ASSOC); 287 if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) { 288 $arr = array_change_key_case($arr, CASE_LOWER); 289 } 290 } else { 291 $arr = @fbsql_fetch_row($result); 292 } 293 if (!$arr) { 294 return null; 295 } 296 if ($this->options['portability'] & DB_PORTABILITY_RTRIM) { 297 $this->_rtrimArrayValues($arr); 298 } 299 if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) { 300 $this->_convertNullArrayValuesToEmpty($arr); 301 } 302 return DB_OK; 303 } 304 305 // }}} 306 // {{{ freeResult() 307 308 /** 309 * Deletes the result set and frees the memory occupied by the result set 310 * 311 * This method is not meant to be called directly. Use 312 * DB_result::free() instead. It can't be declared "protected" 313 * because DB_result is a separate object. 314 * 315 * @param resource $result PHP's query result resource 316 * 317 * @return bool TRUE on success, FALSE if $result is invalid 318 * 319 * @see DB_result::free() 320 */ 321 function freeResult($result) 322 { 323 return is_resource($result) ? fbsql_free_result($result) : false; 324 } 325 326 // }}} 327 // {{{ autoCommit() 328 329 /** 330 * Enables or disables automatic commits 331 * 332 * @param bool $onoff true turns it on, false turns it off 333 * 334 * @return int DB_OK on success. A DB_Error object if the driver 335 * doesn't support auto-committing transactions. 336 */ 337 function autoCommit($onoff=false) 338 { 339 if ($onoff) { 340 $this->query("SET COMMIT TRUE"); 341 } else { 342 $this->query("SET COMMIT FALSE"); 343 } 344 } 345 346 // }}} 347 // {{{ commit() 348 349 /** 350 * Commits the current transaction 351 * 352 * @return int DB_OK on success. A DB_Error object on failure. 353 */ 354 function commit() 355 { 356 @fbsql_commit($this->connection); 357 } 358 359 // }}} 360 // {{{ rollback() 361 362 /** 363 * Reverts the current transaction 364 * 365 * @return int DB_OK on success. A DB_Error object on failure. 366 */ 367 function rollback() 368 { 369 @fbsql_rollback($this->connection); 370 } 371 372 // }}} 373 // {{{ numCols() 374 375 /** 376 * Gets the number of columns in a result set 377 * 378 * This method is not meant to be called directly. Use 379 * DB_result::numCols() instead. It can't be declared "protected" 380 * because DB_result is a separate object. 381 * 382 * @param resource $result PHP's query result resource 383 * 384 * @return int the number of columns. A DB_Error object on failure. 385 * 386 * @see DB_result::numCols() 387 */ 388 function numCols($result) 389 { 390 $cols = @fbsql_num_fields($result); 391 if (!$cols) { 392 return $this->fbsqlRaiseError(); 393 } 394 return $cols; 395 } 396 397 // }}} 398 // {{{ numRows() 399 400 /** 401 * Gets the number of rows in a result set 402 * 403 * This method is not meant to be called directly. Use 404 * DB_result::numRows() instead. It can't be declared "protected" 405 * because DB_result is a separate object. 406 * 407 * @param resource $result PHP's query result resource 408 * 409 * @return int the number of rows. A DB_Error object on failure. 410 * 411 * @see DB_result::numRows() 412 */ 413 function numRows($result) 414 { 415 $rows = @fbsql_num_rows($result); 416 if ($rows === null) { 417 return $this->fbsqlRaiseError(); 418 } 419 return $rows; 420 } 421 422 // }}} 423 // {{{ affectedRows() 424 425 /** 426 * Determines the number of rows affected by a data maniuplation query 427 * 428 * 0 is returned for queries that don't manipulate data. 429 * 430 * @return int the number of rows. A DB_Error object on failure. 431 */ 432 function affectedRows() 433 { 434 if ($this->_last_query_manip) { 435 $result = @fbsql_affected_rows($this->connection); 436 } else { 437 $result = 0; 438 } 439 return $result; 440 } 441 442 // }}} 443 // {{{ nextId() 444 445 /** 446 * Returns the next free id in a sequence 447 * 448 * @param string $seq_name name of the sequence 449 * @param boolean $ondemand when true, the seqence is automatically 450 * created if it does not exist 451 * 452 * @return int the next id number in the sequence. 453 * A DB_Error object on failure. 454 * 455 * @see DB_common::nextID(), DB_common::getSequenceName(), 456 * DB_fbsql::createSequence(), DB_fbsql::dropSequence() 457 */ 458 function nextId($seq_name, $ondemand = true) 459 { 460 $seqname = $this->getSequenceName($seq_name); 461 do { 462 $repeat = 0; 463 $this->pushErrorHandling(PEAR_ERROR_RETURN); 464 $result = $this->query('SELECT UNIQUE FROM ' . $seqname); 465 $this->popErrorHandling(); 466 if ($ondemand && DB::isError($result) && 467 $result->getCode() == DB_ERROR_NOSUCHTABLE) { 468 $repeat = 1; 469 $result = $this->createSequence($seq_name); 470 if (DB::isError($result)) { 471 return $result; 472 } 473 } else { 474 $repeat = 0; 475 } 476 } while ($repeat); 477 if (DB::isError($result)) { 478 return $this->fbsqlRaiseError(); 479 } 480 $result->fetchInto($tmp, DB_FETCHMODE_ORDERED); 481 return $tmp[0]; 482 } 483 484 /** 485 * Creates a new sequence 486 * 487 * @param string $seq_name name of the new sequence 488 * 489 * @return int DB_OK on success. A DB_Error object on failure. 490 * 491 * @see DB_common::createSequence(), DB_common::getSequenceName(), 492 * DB_fbsql::nextID(), DB_fbsql::dropSequence() 493 */ 494 function createSequence($seq_name) 495 { 496 $seqname = $this->getSequenceName($seq_name); 497 $res = $this->query('CREATE TABLE ' . $seqname 498 . ' (id INTEGER NOT NULL,' 499 . ' PRIMARY KEY(id))'); 500 if ($res) { 501 $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname); 502 } 503 return $res; 504 } 505 506 // }}} 507 // {{{ dropSequence() 508 509 /** 510 * Deletes a sequence 511 * 512 * @param string $seq_name name of the sequence to be deleted 513 * 514 * @return int DB_OK on success. A DB_Error object on failure. 515 * 516 * @see DB_common::dropSequence(), DB_common::getSequenceName(), 517 * DB_fbsql::nextID(), DB_fbsql::createSequence() 518 */ 519 function dropSequence($seq_name) 520 { 521 return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name) 522 . ' RESTRICT'); 523 } 524 525 // }}} 526 // {{{ modifyLimitQuery() 527 528 /** 529 * Adds LIMIT clauses to a query string according to current DBMS standards 530 * 531 * @param string $query the query to modify 532 * @param int $from the row to start to fetching (0 = the first row) 533 * @param int $count the numbers of rows to fetch 534 * @param mixed $params array, string or numeric data to be used in 535 * execution of the statement. Quantity of items 536 * passed must match quantity of placeholders in 537 * query: meaning 1 placeholder for non-array 538 * parameters or 1 placeholder per array element. 539 * 540 * @return string the query string with LIMIT clauses added 541 * 542 * @access protected 543 */ 544 function modifyLimitQuery($query, $from, $count, $params = array()) 545 { 546 if (DB::isManip($query) || $this->_next_query_manip) { 547 return preg_replace('/^([\s(])*SELECT/i', 548 "\\1SELECT TOP($count)", $query); 549 } else { 550 return preg_replace('/([\s(])*SELECT/i', 551 "\\1SELECT TOP($from, $count)", $query); 552 } 553 } 554 555 // }}} 556 // {{{ quoteBoolean() 557 558 /** 559 * Formats a boolean value for use within a query in a locale-independent 560 * manner. 561 * 562 * @param boolean the boolean value to be quoted. 563 * @return string the quoted string. 564 * @see DB_common::quoteSmart() 565 * @since Method available since release 1.7.8. 566 */ 567 function quoteBoolean($boolean) { 568 return $boolean ? 'TRUE' : 'FALSE'; 569 } 570 571 // }}} 572 // {{{ quoteFloat() 573 574 /** 575 * Formats a float value for use within a query in a locale-independent 576 * manner. 577 * 578 * @param float the float value to be quoted. 579 * @return string the quoted string. 580 * @see DB_common::quoteSmart() 581 * @since Method available since release 1.7.8. 582 */ 583 function quoteFloat($float) { 584 return $this->escapeSimple(str_replace(',', '.', strval(floatval($float)))); 585 } 586 587 // }}} 588 // {{{ fbsqlRaiseError() 589 590 /** 591 * Produces a DB_Error object regarding the current problem 592 * 593 * @param int $errno if the error is being manually raised pass a 594 * DB_ERROR* constant here. If this isn't passed 595 * the error information gathered from the DBMS. 596 * 597 * @return object the DB_Error object 598 * 599 * @see DB_common::raiseError(), 600 * DB_fbsql::errorNative(), DB_common::errorCode() 601 */ 602 function fbsqlRaiseError($errno = null) 603 { 604 if ($errno === null) { 605 $errno = $this->errorCode(fbsql_errno($this->connection)); 606 } 607 return $this->raiseError($errno, null, null, null, 608 @fbsql_error($this->connection)); 609 } 610 611 // }}} 612 // {{{ errorNative() 613 614 /** 615 * Gets the DBMS' native error code produced by the last query 616 * 617 * @return int the DBMS' error code 618 */ 619 function errorNative() 620 { 621 return @fbsql_errno($this->connection); 622 } 623 624 // }}} 625 // {{{ tableInfo() 626 627 /** 628 * Returns information about a table or a result set 629 * 630 * @param object|string $result DB_result object from a query or a 631 * string containing the name of a table. 632 * While this also accepts a query result 633 * resource identifier, this behavior is 634 * deprecated. 635 * @param int $mode a valid tableInfo mode 636 * 637 * @return array an associative array with the information requested. 638 * A DB_Error object on failure. 639 * 640 * @see DB_common::tableInfo() 641 */ 642 function tableInfo($result, $mode = null) 643 { 644 if (is_string($result)) { 645 /* 646 * Probably received a table name. 647 * Create a result resource identifier. 648 */ 649 $id = @fbsql_list_fields($this->dsn['database'], 650 $result, $this->connection); 651 $got_string = true; 652 } elseif (isset($result->result)) { 653 /* 654 * Probably received a result object. 655 * Extract the result resource identifier. 656 */ 657 $id = $result->result; 658 $got_string = false; 659 } else { 660 /* 661 * Probably received a result resource identifier. 662 * Copy it. 663 * Deprecated. Here for compatibility only. 664 */ 665 $id = $result; 666 $got_string = false; 667 } 668 669 if (!is_resource($id)) { 670 return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA); 671 } 672 673 if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) { 674 $case_func = 'strtolower'; 675 } else { 676 $case_func = 'strval'; 677 } 678 679 $count = @fbsql_num_fields($id); 680 $res = array(); 681 682 if ($mode) { 683 $res['num_fields'] = $count; 684 } 685 686 for ($i = 0; $i < $count; $i++) { 687 $res[$i] = array( 688 'table' => $case_func(@fbsql_field_table($id, $i)), 689 'name' => $case_func(@fbsql_field_name($id, $i)), 690 'type' => @fbsql_field_type($id, $i), 691 'len' => @fbsql_field_len($id, $i), 692 'flags' => @fbsql_field_flags($id, $i), 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 // free the result only if we were called on a table 703 if ($got_string) { 704 @fbsql_free_result($id); 705 } 706 return $res; 707 } 708 709 // }}} 710 // {{{ getSpecialQuery() 711 712 /** 713 * Obtains the query string needed for listing a given type of objects 714 * 715 * @param string $type the kind of objects you want to retrieve 716 * 717 * @return string the SQL query string or null if the driver doesn't 718 * support the object type requested 719 * 720 * @access protected 721 * @see DB_common::getListOf() 722 */ 723 function getSpecialQuery($type) 724 { 725 switch ($type) { 726 case 'tables': 727 return 'SELECT "table_name" FROM information_schema.tables' 728 . ' t0, information_schema.schemata t1' 729 . ' WHERE t0.schema_pk=t1.schema_pk AND' 730 . ' "table_type" = \'BASE TABLE\'' 731 . ' AND "schema_name" = current_schema'; 732 case 'views': 733 return 'SELECT "table_name" FROM information_schema.tables' 734 . ' t0, information_schema.schemata t1' 735 . ' WHERE t0.schema_pk=t1.schema_pk AND' 736 . ' "table_type" = \'VIEW\'' 737 . ' AND "schema_name" = current_schema'; 738 case 'users': 739 return 'SELECT "user_name" from information_schema.users'; 740 case 'functions': 741 return 'SELECT "routine_name" FROM' 742 . ' information_schema.psm_routines' 743 . ' t0, information_schema.schemata t1' 744 . ' WHERE t0.schema_pk=t1.schema_pk' 745 . ' AND "routine_kind"=\'FUNCTION\'' 746 . ' AND "schema_name" = current_schema'; 747 case 'procedures': 748 return 'SELECT "routine_name" FROM' 749 . ' information_schema.psm_routines' 750 . ' t0, information_schema.schemata t1' 751 . ' WHERE t0.schema_pk=t1.schema_pk' 752 . ' AND "routine_kind"=\'PROCEDURE\'' 753 . ' AND "schema_name" = current_schema'; 754 default: 755 return null; 756 } 757 } 758 759 // }}} 760} 761 762/* 763 * Local variables: 764 * tab-width: 4 765 * c-basic-offset: 4 766 * End: 767 */ 768 769?> 770