[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @defgroup Database Database 5 * 6 * This file deals with database interface functions 7 * and query specifics/optimisations. 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 * http://www.gnu.org/copyleft/gpl.html 23 * 24 * @file 25 * @ingroup Database 26 */ 27 28 /** 29 * Base interface for all DBMS-specific code. At a bare minimum, all of the 30 * following must be implemented to support MediaWiki 31 * 32 * @file 33 * @ingroup Database 34 */ 35 interface DatabaseType { 36 /** 37 * Get the type of the DBMS, as it appears in $wgDBtype. 38 * 39 * @return string 40 */ 41 function getType(); 42 43 /** 44 * Open a connection to the database. Usually aborts on failure 45 * 46 * @param string $server Database server host 47 * @param string $user Database user name 48 * @param string $password Database user password 49 * @param string $dbName Database name 50 * @return bool 51 * @throws DBConnectionError 52 */ 53 function open( $server, $user, $password, $dbName ); 54 55 /** 56 * Fetch the next row from the given result object, in object form. 57 * Fields can be retrieved with $row->fieldname, with fields acting like 58 * member variables. 59 * If no more rows are available, false is returned. 60 * 61 * @param ResultWrapper|stdClass $res Object as returned from DatabaseBase::query(), etc. 62 * @return stdClass|bool 63 * @throws DBUnexpectedError Thrown if the database returns an error 64 */ 65 function fetchObject( $res ); 66 67 /** 68 * Fetch the next row from the given result object, in associative array 69 * form. Fields are retrieved with $row['fieldname']. 70 * If no more rows are available, false is returned. 71 * 72 * @param ResultWrapper $res Result object as returned from DatabaseBase::query(), etc. 73 * @return array|bool 74 * @throws DBUnexpectedError Thrown if the database returns an error 75 */ 76 function fetchRow( $res ); 77 78 /** 79 * Get the number of rows in a result object 80 * 81 * @param mixed $res A SQL result 82 * @return int 83 */ 84 function numRows( $res ); 85 86 /** 87 * Get the number of fields in a result object 88 * @see http://www.php.net/mysql_num_fields 89 * 90 * @param mixed $res A SQL result 91 * @return int 92 */ 93 function numFields( $res ); 94 95 /** 96 * Get a field name in a result object 97 * @see http://www.php.net/mysql_field_name 98 * 99 * @param mixed $res A SQL result 100 * @param int $n 101 * @return string 102 */ 103 function fieldName( $res, $n ); 104 105 /** 106 * Get the inserted value of an auto-increment row 107 * 108 * The value inserted should be fetched from nextSequenceValue() 109 * 110 * Example: 111 * $id = $dbw->nextSequenceValue( 'page_page_id_seq' ); 112 * $dbw->insert( 'page', array( 'page_id' => $id ) ); 113 * $id = $dbw->insertId(); 114 * 115 * @return int 116 */ 117 function insertId(); 118 119 /** 120 * Change the position of the cursor in a result object 121 * @see http://www.php.net/mysql_data_seek 122 * 123 * @param mixed $res A SQL result 124 * @param int $row 125 */ 126 function dataSeek( $res, $row ); 127 128 /** 129 * Get the last error number 130 * @see http://www.php.net/mysql_errno 131 * 132 * @return int 133 */ 134 function lastErrno(); 135 136 /** 137 * Get a description of the last error 138 * @see http://www.php.net/mysql_error 139 * 140 * @return string 141 */ 142 function lastError(); 143 144 /** 145 * mysql_fetch_field() wrapper 146 * Returns false if the field doesn't exist 147 * 148 * @param string $table Table name 149 * @param string $field Field name 150 * 151 * @return Field 152 */ 153 function fieldInfo( $table, $field ); 154 155 /** 156 * Get information about an index into an object 157 * @param string $table Table name 158 * @param string $index Index name 159 * @param string $fname Calling function name 160 * @return mixed Database-specific index description class or false if the index does not exist 161 */ 162 function indexInfo( $table, $index, $fname = __METHOD__ ); 163 164 /** 165 * Get the number of rows affected by the last write query 166 * @see http://www.php.net/mysql_affected_rows 167 * 168 * @return int 169 */ 170 function affectedRows(); 171 172 /** 173 * Wrapper for addslashes() 174 * 175 * @param string $s String to be slashed. 176 * @return string Slashed string. 177 */ 178 function strencode( $s ); 179 180 /** 181 * Returns a wikitext link to the DB's website, e.g., 182 * return "[http://www.mysql.com/ MySQL]"; 183 * Should at least contain plain text, if for some reason 184 * your database has no website. 185 * 186 * @return string Wikitext of a link to the server software's web site 187 */ 188 function getSoftwareLink(); 189 190 /** 191 * A string describing the current software version, like from 192 * mysql_get_server_info(). 193 * 194 * @return string Version information from the database server. 195 */ 196 function getServerVersion(); 197 198 /** 199 * A string describing the current software version, and possibly 200 * other details in a user-friendly way. Will be listed on Special:Version, etc. 201 * Use getServerVersion() to get machine-friendly information. 202 * 203 * @return string Version information from the database server 204 */ 205 function getServerInfo(); 206 } 207 208 /** 209 * Interface for classes that implement or wrap DatabaseBase 210 * @ingroup Database 211 */ 212 interface IDatabase { 213 } 214 215 /** 216 * Database abstraction object 217 * @ingroup Database 218 */ 219 abstract class DatabaseBase implements IDatabase, DatabaseType { 220 /** Number of times to re-try an operation in case of deadlock */ 221 const DEADLOCK_TRIES = 4; 222 223 /** Minimum time to wait before retry, in microseconds */ 224 const DEADLOCK_DELAY_MIN = 500000; 225 226 /** Maximum time to wait before retry */ 227 const DEADLOCK_DELAY_MAX = 1500000; 228 229 # ------------------------------------------------------------------------------ 230 # Variables 231 # ------------------------------------------------------------------------------ 232 233 protected $mLastQuery = ''; 234 protected $mDoneWrites = false; 235 protected $mPHPError = false; 236 237 protected $mServer, $mUser, $mPassword, $mDBname; 238 239 /** @var resource Database connection */ 240 protected $mConn = null; 241 protected $mOpened = false; 242 243 /** @var callable[] */ 244 protected $mTrxIdleCallbacks = array(); 245 /** @var callable[] */ 246 protected $mTrxPreCommitCallbacks = array(); 247 248 protected $mTablePrefix; 249 protected $mSchema; 250 protected $mFlags; 251 protected $mForeign; 252 protected $mErrorCount = 0; 253 protected $mLBInfo = array(); 254 protected $mDefaultBigSelects = null; 255 protected $mSchemaVars = false; 256 257 protected $preparedArgs; 258 259 protected $htmlErrors; 260 261 protected $delimiter = ';'; 262 263 /** 264 * Either 1 if a transaction is active or 0 otherwise. 265 * The other Trx fields may not be meaningfull if this is 0. 266 * 267 * @var int 268 */ 269 protected $mTrxLevel = 0; 270 271 /** 272 * Either a short hexidecimal string if a transaction is active or "" 273 * 274 * @var string 275 */ 276 protected $mTrxShortId = ''; 277 278 /** 279 * Remembers the function name given for starting the most recent transaction via begin(). 280 * Used to provide additional context for error reporting. 281 * 282 * @var string 283 * @see DatabaseBase::mTrxLevel 284 */ 285 private $mTrxFname = null; 286 287 /** 288 * Record if possible write queries were done in the last transaction started 289 * 290 * @var bool 291 * @see DatabaseBase::mTrxLevel 292 */ 293 private $mTrxDoneWrites = false; 294 295 /** 296 * Record if the current transaction was started implicitly due to DBO_TRX being set. 297 * 298 * @var bool 299 * @see DatabaseBase::mTrxLevel 300 */ 301 private $mTrxAutomatic = false; 302 303 /** 304 * Array of levels of atomicity within transactions 305 * 306 * @var SplStack 307 */ 308 private $mTrxAtomicLevels; 309 310 /** 311 * Record if the current transaction was started implicitly by DatabaseBase::startAtomic 312 * 313 * @var bool 314 */ 315 private $mTrxAutomaticAtomic = false; 316 317 /** 318 * @since 1.21 319 * @var resource File handle for upgrade 320 */ 321 protected $fileHandle = null; 322 323 /** 324 * @since 1.22 325 * @var string[] Process cache of VIEWs names in the database 326 */ 327 protected $allViews = null; 328 329 # ------------------------------------------------------------------------------ 330 # Accessors 331 # ------------------------------------------------------------------------------ 332 # These optionally set a variable and return the previous state 333 334 /** 335 * A string describing the current software version, and possibly 336 * other details in a user-friendly way. Will be listed on Special:Version, etc. 337 * Use getServerVersion() to get machine-friendly information. 338 * 339 * @return string Version information from the database server 340 */ 341 public function getServerInfo() { 342 return $this->getServerVersion(); 343 } 344 345 /** 346 * @return string Command delimiter used by this database engine 347 */ 348 public function getDelimiter() { 349 return $this->delimiter; 350 } 351 352 /** 353 * Boolean, controls output of large amounts of debug information. 354 * @param bool|null $debug 355 * - true to enable debugging 356 * - false to disable debugging 357 * - omitted or null to do nothing 358 * 359 * @return bool|null Previous value of the flag 360 */ 361 public function debug( $debug = null ) { 362 return wfSetBit( $this->mFlags, DBO_DEBUG, $debug ); 363 } 364 365 /** 366 * Turns buffering of SQL result sets on (true) or off (false). Default is 367 * "on". 368 * 369 * Unbuffered queries are very troublesome in MySQL: 370 * 371 * - If another query is executed while the first query is being read 372 * out, the first query is killed. This means you can't call normal 373 * MediaWiki functions while you are reading an unbuffered query result 374 * from a normal wfGetDB() connection. 375 * 376 * - Unbuffered queries cause the MySQL server to use large amounts of 377 * memory and to hold broad locks which block other queries. 378 * 379 * If you want to limit client-side memory, it's almost always better to 380 * split up queries into batches using a LIMIT clause than to switch off 381 * buffering. 382 * 383 * @param null|bool $buffer 384 * @return null|bool The previous value of the flag 385 */ 386 public function bufferResults( $buffer = null ) { 387 if ( is_null( $buffer ) ) { 388 return !(bool)( $this->mFlags & DBO_NOBUFFER ); 389 } else { 390 return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer ); 391 } 392 } 393 394 /** 395 * Turns on (false) or off (true) the automatic generation and sending 396 * of a "we're sorry, but there has been a database error" page on 397 * database errors. Default is on (false). When turned off, the 398 * code should use lastErrno() and lastError() to handle the 399 * situation as appropriate. 400 * 401 * Do not use this function outside of the Database classes. 402 * 403 * @param null|bool $ignoreErrors 404 * @return bool The previous value of the flag. 405 */ 406 public function ignoreErrors( $ignoreErrors = null ) { 407 return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors ); 408 } 409 410 /** 411 * Gets the current transaction level. 412 * 413 * Historically, transactions were allowed to be "nested". This is no 414 * longer supported, so this function really only returns a boolean. 415 * 416 * @return int The previous value 417 */ 418 public function trxLevel() { 419 return $this->mTrxLevel; 420 } 421 422 /** 423 * Get/set the number of errors logged. Only useful when errors are ignored 424 * @param int $count The count to set, or omitted to leave it unchanged. 425 * @return int The error count 426 */ 427 public function errorCount( $count = null ) { 428 return wfSetVar( $this->mErrorCount, $count ); 429 } 430 431 /** 432 * Get/set the table prefix. 433 * @param string $prefix The table prefix to set, or omitted to leave it unchanged. 434 * @return string The previous table prefix. 435 */ 436 public function tablePrefix( $prefix = null ) { 437 return wfSetVar( $this->mTablePrefix, $prefix ); 438 } 439 440 /** 441 * Get/set the db schema. 442 * @param string $schema The database schema to set, or omitted to leave it unchanged. 443 * @return string The previous db schema. 444 */ 445 public function dbSchema( $schema = null ) { 446 return wfSetVar( $this->mSchema, $schema ); 447 } 448 449 /** 450 * Set the filehandle to copy write statements to. 451 * 452 * @param resource $fh File handle 453 */ 454 public function setFileHandle( $fh ) { 455 $this->fileHandle = $fh; 456 } 457 458 /** 459 * Get properties passed down from the server info array of the load 460 * balancer. 461 * 462 * @param string $name The entry of the info array to get, or null to get the 463 * whole array 464 * 465 * @return array|mixed|null 466 */ 467 public function getLBInfo( $name = null ) { 468 if ( is_null( $name ) ) { 469 return $this->mLBInfo; 470 } else { 471 if ( array_key_exists( $name, $this->mLBInfo ) ) { 472 return $this->mLBInfo[$name]; 473 } else { 474 return null; 475 } 476 } 477 } 478 479 /** 480 * Set the LB info array, or a member of it. If called with one parameter, 481 * the LB info array is set to that parameter. If it is called with two 482 * parameters, the member with the given name is set to the given value. 483 * 484 * @param string $name 485 * @param array $value 486 */ 487 public function setLBInfo( $name, $value = null ) { 488 if ( is_null( $value ) ) { 489 $this->mLBInfo = $name; 490 } else { 491 $this->mLBInfo[$name] = $value; 492 } 493 } 494 495 /** 496 * Set lag time in seconds for a fake slave 497 * 498 * @param mixed $lag Valid values for this parameter are determined by the 499 * subclass, but should be a PHP scalar or array that would be sensible 500 * as part of $wgLBFactoryConf. 501 */ 502 public function setFakeSlaveLag( $lag ) { 503 } 504 505 /** 506 * Make this connection a fake master 507 * 508 * @param bool $enabled 509 */ 510 public function setFakeMaster( $enabled = true ) { 511 } 512 513 /** 514 * Returns true if this database supports (and uses) cascading deletes 515 * 516 * @return bool 517 */ 518 public function cascadingDeletes() { 519 return false; 520 } 521 522 /** 523 * Returns true if this database supports (and uses) triggers (e.g. on the page table) 524 * 525 * @return bool 526 */ 527 public function cleanupTriggers() { 528 return false; 529 } 530 531 /** 532 * Returns true if this database is strict about what can be put into an IP field. 533 * Specifically, it uses a NULL value instead of an empty string. 534 * 535 * @return bool 536 */ 537 public function strictIPs() { 538 return false; 539 } 540 541 /** 542 * Returns true if this database uses timestamps rather than integers 543 * 544 * @return bool 545 */ 546 public function realTimestamps() { 547 return false; 548 } 549 550 /** 551 * Returns true if this database does an implicit sort when doing GROUP BY 552 * 553 * @return bool 554 */ 555 public function implicitGroupby() { 556 return true; 557 } 558 559 /** 560 * Returns true if this database does an implicit order by when the column has an index 561 * For example: SELECT page_title FROM page LIMIT 1 562 * 563 * @return bool 564 */ 565 public function implicitOrderby() { 566 return true; 567 } 568 569 /** 570 * Returns true if this database can do a native search on IP columns 571 * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32'; 572 * 573 * @return bool 574 */ 575 public function searchableIPs() { 576 return false; 577 } 578 579 /** 580 * Returns true if this database can use functional indexes 581 * 582 * @return bool 583 */ 584 public function functionalIndexes() { 585 return false; 586 } 587 588 /** 589 * Return the last query that went through DatabaseBase::query() 590 * @return string 591 */ 592 public function lastQuery() { 593 return $this->mLastQuery; 594 } 595 596 /** 597 * Returns true if the connection may have been used for write queries. 598 * Should return true if unsure. 599 * 600 * @return bool 601 */ 602 public function doneWrites() { 603 return (bool)$this->mDoneWrites; 604 } 605 606 /** 607 * Returns the last time the connection may have been used for write queries. 608 * Should return a timestamp if unsure. 609 * 610 * @return int|float UNIX timestamp or false 611 * @since 1.24 612 */ 613 public function lastDoneWrites() { 614 return $this->mDoneWrites ?: false; 615 } 616 617 /** 618 * Returns true if there is a transaction open with possible write 619 * queries or transaction pre-commit/idle callbacks waiting on it to finish. 620 * 621 * @return bool 622 */ 623 public function writesOrCallbacksPending() { 624 return $this->mTrxLevel && ( 625 $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks 626 ); 627 } 628 629 /** 630 * Is a connection to the database open? 631 * @return bool 632 */ 633 public function isOpen() { 634 return $this->mOpened; 635 } 636 637 /** 638 * Set a flag for this connection 639 * 640 * @param int $flag DBO_* constants from Defines.php: 641 * - DBO_DEBUG: output some debug info (same as debug()) 642 * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) 643 * - DBO_TRX: automatically start transactions 644 * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode 645 * and removes it in command line mode 646 * - DBO_PERSISTENT: use persistant database connection 647 */ 648 public function setFlag( $flag ) { 649 global $wgDebugDBTransactions; 650 $this->mFlags |= $flag; 651 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 652 wfDebug( "Implicit transactions are now enabled.\n" ); 653 } 654 } 655 656 /** 657 * Clear a flag for this connection 658 * 659 * @param int $flag DBO_* constants from Defines.php: 660 * - DBO_DEBUG: output some debug info (same as debug()) 661 * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) 662 * - DBO_TRX: automatically start transactions 663 * - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode 664 * and removes it in command line mode 665 * - DBO_PERSISTENT: use persistant database connection 666 */ 667 public function clearFlag( $flag ) { 668 global $wgDebugDBTransactions; 669 $this->mFlags &= ~$flag; 670 if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) { 671 wfDebug( "Implicit transactions are now disabled.\n" ); 672 } 673 } 674 675 /** 676 * Returns a boolean whether the flag $flag is set for this connection 677 * 678 * @param int $flag DBO_* constants from Defines.php: 679 * - DBO_DEBUG: output some debug info (same as debug()) 680 * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults()) 681 * - DBO_TRX: automatically start transactions 682 * - DBO_PERSISTENT: use persistant database connection 683 * @return bool 684 */ 685 public function getFlag( $flag ) { 686 return !!( $this->mFlags & $flag ); 687 } 688 689 /** 690 * General read-only accessor 691 * 692 * @param string $name 693 * @return string 694 */ 695 public function getProperty( $name ) { 696 return $this->$name; 697 } 698 699 /** 700 * @return string 701 */ 702 public function getWikiID() { 703 if ( $this->mTablePrefix ) { 704 return "{$this->mDBname}-{$this->mTablePrefix}"; 705 } else { 706 return $this->mDBname; 707 } 708 } 709 710 /** 711 * Return a path to the DBMS-specific SQL file if it exists, 712 * otherwise default SQL file 713 * 714 * @param string $filename 715 * @return string 716 */ 717 private function getSqlFilePath( $filename ) { 718 global $IP; 719 $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename"; 720 if ( file_exists( $dbmsSpecificFilePath ) ) { 721 return $dbmsSpecificFilePath; 722 } else { 723 return "$IP/maintenance/$filename"; 724 } 725 } 726 727 /** 728 * Return a path to the DBMS-specific schema file, 729 * otherwise default to tables.sql 730 * 731 * @return string 732 */ 733 public function getSchemaPath() { 734 return $this->getSqlFilePath( 'tables.sql' ); 735 } 736 737 /** 738 * Return a path to the DBMS-specific update key file, 739 * otherwise default to update-keys.sql 740 * 741 * @return string 742 */ 743 public function getUpdateKeysPath() { 744 return $this->getSqlFilePath( 'update-keys.sql' ); 745 } 746 747 # ------------------------------------------------------------------------------ 748 # Other functions 749 # ------------------------------------------------------------------------------ 750 751 /** 752 * Constructor. 753 * 754 * FIXME: It is possible to construct a Database object with no associated 755 * connection object, by specifying no parameters to __construct(). This 756 * feature is deprecated and should be removed. 757 * 758 * DatabaseBase subclasses should not be constructed directly in external 759 * code. DatabaseBase::factory() should be used instead. 760 * 761 * @param array $params Parameters passed from DatabaseBase::factory() 762 */ 763 function __construct( $params = null ) { 764 global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions; 765 766 $this->mTrxAtomicLevels = new SplStack; 767 768 if ( is_array( $params ) ) { // MW 1.22 769 $server = $params['host']; 770 $user = $params['user']; 771 $password = $params['password']; 772 $dbName = $params['dbname']; 773 $flags = $params['flags']; 774 $tablePrefix = $params['tablePrefix']; 775 $schema = $params['schema']; 776 $foreign = $params['foreign']; 777 } else { // legacy calling pattern 778 wfDeprecated( __METHOD__ . " method called without parameter array.", "1.23" ); 779 $args = func_get_args(); 780 $server = isset( $args[0] ) ? $args[0] : false; 781 $user = isset( $args[1] ) ? $args[1] : false; 782 $password = isset( $args[2] ) ? $args[2] : false; 783 $dbName = isset( $args[3] ) ? $args[3] : false; 784 $flags = isset( $args[4] ) ? $args[4] : 0; 785 $tablePrefix = isset( $args[5] ) ? $args[5] : 'get from global'; 786 $schema = 'get from global'; 787 $foreign = isset( $args[6] ) ? $args[6] : false; 788 } 789 790 $this->mFlags = $flags; 791 if ( $this->mFlags & DBO_DEFAULT ) { 792 if ( $wgCommandLineMode ) { 793 $this->mFlags &= ~DBO_TRX; 794 if ( $wgDebugDBTransactions ) { 795 wfDebug( "Implicit transaction open disabled.\n" ); 796 } 797 } else { 798 $this->mFlags |= DBO_TRX; 799 if ( $wgDebugDBTransactions ) { 800 wfDebug( "Implicit transaction open enabled.\n" ); 801 } 802 } 803 } 804 805 /** Get the default table prefix*/ 806 if ( $tablePrefix == 'get from global' ) { 807 $this->mTablePrefix = $wgDBprefix; 808 } else { 809 $this->mTablePrefix = $tablePrefix; 810 } 811 812 /** Get the database schema*/ 813 if ( $schema == 'get from global' ) { 814 $this->mSchema = $wgDBmwschema; 815 } else { 816 $this->mSchema = $schema; 817 } 818 819 $this->mForeign = $foreign; 820 821 if ( $user ) { 822 $this->open( $server, $user, $password, $dbName ); 823 } 824 } 825 826 /** 827 * Called by serialize. Throw an exception when DB connection is serialized. 828 * This causes problems on some database engines because the connection is 829 * not restored on unserialize. 830 */ 831 public function __sleep() { 832 throw new MWException( 'Database serialization may cause problems, since ' . 833 'the connection is not restored on wakeup.' ); 834 } 835 836 /** 837 * Given a DB type, construct the name of the appropriate child class of 838 * DatabaseBase. This is designed to replace all of the manual stuff like: 839 * $class = 'Database' . ucfirst( strtolower( $dbType ) ); 840 * as well as validate against the canonical list of DB types we have 841 * 842 * This factory function is mostly useful for when you need to connect to a 843 * database other than the MediaWiki default (such as for external auth, 844 * an extension, et cetera). Do not use this to connect to the MediaWiki 845 * database. Example uses in core: 846 * @see LoadBalancer::reallyOpenConnection() 847 * @see ForeignDBRepo::getMasterDB() 848 * @see WebInstallerDBConnect::execute() 849 * 850 * @since 1.18 851 * 852 * @param string $dbType A possible DB type 853 * @param array $p An array of options to pass to the constructor. 854 * Valid options are: host, user, password, dbname, flags, tablePrefix, schema, driver 855 * @throws MWException If the database driver or extension cannot be found 856 * @return DatabaseBase|null DatabaseBase subclass or null 857 */ 858 final public static function factory( $dbType, $p = array() ) { 859 $canonicalDBTypes = array( 860 'mysql' => array( 'mysqli', 'mysql' ), 861 'postgres' => array(), 862 'sqlite' => array(), 863 'oracle' => array(), 864 'mssql' => array(), 865 ); 866 867 $driver = false; 868 $dbType = strtolower( $dbType ); 869 if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) { 870 $possibleDrivers = $canonicalDBTypes[$dbType]; 871 if ( !empty( $p['driver'] ) ) { 872 if ( in_array( $p['driver'], $possibleDrivers ) ) { 873 $driver = $p['driver']; 874 } else { 875 throw new MWException( __METHOD__ . 876 " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" ); 877 } 878 } else { 879 foreach ( $possibleDrivers as $posDriver ) { 880 if ( extension_loaded( $posDriver ) ) { 881 $driver = $posDriver; 882 break; 883 } 884 } 885 } 886 } else { 887 $driver = $dbType; 888 } 889 if ( $driver === false ) { 890 throw new MWException( __METHOD__ . 891 " no viable database extension found for type '$dbType'" ); 892 } 893 894 // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema, 895 // and everything else doesn't use a schema (e.g. null) 896 // Although postgres and oracle support schemas, we don't use them (yet) 897 // to maintain backwards compatibility 898 $defaultSchemas = array( 899 'mysql' => null, 900 'postgres' => null, 901 'sqlite' => null, 902 'oracle' => null, 903 'mssql' => 'get from global', 904 ); 905 906 $class = 'Database' . ucfirst( $driver ); 907 if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) { 908 $params = array( 909 'host' => isset( $p['host'] ) ? $p['host'] : false, 910 'user' => isset( $p['user'] ) ? $p['user'] : false, 911 'password' => isset( $p['password'] ) ? $p['password'] : false, 912 'dbname' => isset( $p['dbname'] ) ? $p['dbname'] : false, 913 'flags' => isset( $p['flags'] ) ? $p['flags'] : 0, 914 'tablePrefix' => isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global', 915 'schema' => isset( $p['schema'] ) ? $p['schema'] : $defaultSchemas[$dbType], 916 'foreign' => isset( $p['foreign'] ) ? $p['foreign'] : false 917 ); 918 919 return new $class( $params ); 920 } else { 921 return null; 922 } 923 } 924 925 protected function installErrorHandler() { 926 $this->mPHPError = false; 927 $this->htmlErrors = ini_set( 'html_errors', '0' ); 928 set_error_handler( array( $this, 'connectionErrorHandler' ) ); 929 } 930 931 /** 932 * @return bool|string 933 */ 934 protected function restoreErrorHandler() { 935 restore_error_handler(); 936 if ( $this->htmlErrors !== false ) { 937 ini_set( 'html_errors', $this->htmlErrors ); 938 } 939 if ( $this->mPHPError ) { 940 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError ); 941 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error ); 942 943 return $error; 944 } else { 945 return false; 946 } 947 } 948 949 /** 950 * @param int $errno 951 * @param string $errstr 952 */ 953 public function connectionErrorHandler( $errno, $errstr ) { 954 $this->mPHPError = $errstr; 955 } 956 957 /** 958 * Closes a database connection. 959 * if it is open : commits any open transactions 960 * 961 * @throws MWException 962 * @return bool Operation success. true if already closed. 963 */ 964 public function close() { 965 if ( count( $this->mTrxIdleCallbacks ) ) { // sanity 966 throw new MWException( "Transaction idle callbacks still pending." ); 967 } 968 if ( $this->mConn ) { 969 if ( $this->trxLevel() ) { 970 if ( !$this->mTrxAutomatic ) { 971 wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " . 972 " performing implicit commit before closing connection!" ); 973 } 974 975 $this->commit( __METHOD__, 'flush' ); 976 } 977 978 $closed = $this->closeConnection(); 979 $this->mConn = false; 980 } else { 981 $closed = true; 982 } 983 $this->mOpened = false; 984 985 return $closed; 986 } 987 988 /** 989 * Closes underlying database connection 990 * @since 1.20 991 * @return bool Whether connection was closed successfully 992 */ 993 abstract protected function closeConnection(); 994 995 /** 996 * @param string $error Fallback error message, used if none is given by DB 997 * @throws DBConnectionError 998 */ 999 function reportConnectionError( $error = 'Unknown error' ) { 1000 $myError = $this->lastError(); 1001 if ( $myError ) { 1002 $error = $myError; 1003 } 1004 1005 # New method 1006 throw new DBConnectionError( $this, $error ); 1007 } 1008 1009 /** 1010 * The DBMS-dependent part of query() 1011 * 1012 * @param string $sql SQL query. 1013 * @return ResultWrapper|bool Result object to feed to fetchObject, 1014 * fetchRow, ...; or false on failure 1015 */ 1016 abstract protected function doQuery( $sql ); 1017 1018 /** 1019 * Determine whether a query writes to the DB. 1020 * Should return true if unsure. 1021 * 1022 * @param string $sql 1023 * @return bool 1024 */ 1025 public function isWriteQuery( $sql ) { 1026 return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql ); 1027 } 1028 1029 /** 1030 * Run an SQL query and return the result. Normally throws a DBQueryError 1031 * on failure. If errors are ignored, returns false instead. 1032 * 1033 * In new code, the query wrappers select(), insert(), update(), delete(), 1034 * etc. should be used where possible, since they give much better DBMS 1035 * independence and automatically quote or validate user input in a variety 1036 * of contexts. This function is generally only useful for queries which are 1037 * explicitly DBMS-dependent and are unsupported by the query wrappers, such 1038 * as CREATE TABLE. 1039 * 1040 * However, the query wrappers themselves should call this function. 1041 * 1042 * @param string $sql SQL query 1043 * @param string $fname Name of the calling function, for profiling/SHOW PROCESSLIST 1044 * comment (you can use __METHOD__ or add some extra info) 1045 * @param bool $tempIgnore Whether to avoid throwing an exception on errors... 1046 * maybe best to catch the exception instead? 1047 * @throws MWException 1048 * @return bool|ResultWrapper True for a successful write query, ResultWrapper object 1049 * for a successful read query, or false on failure if $tempIgnore set 1050 */ 1051 public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) { 1052 global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength; 1053 1054 $this->mLastQuery = $sql; 1055 if ( $this->isWriteQuery( $sql ) ) { 1056 # Set a flag indicating that writes have been done 1057 wfDebug( __METHOD__ . ': Writes done: ' . DatabaseBase::generalizeSQL( $sql ) . "\n" ); 1058 $this->mDoneWrites = microtime( true ); 1059 } 1060 1061 # Add a comment for easy SHOW PROCESSLIST interpretation 1062 if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) { 1063 $userName = $wgUser->getName(); 1064 if ( mb_strlen( $userName ) > 15 ) { 1065 $userName = mb_substr( $userName, 0, 15 ) . '...'; 1066 } 1067 $userName = str_replace( '/', '', $userName ); 1068 } else { 1069 $userName = ''; 1070 } 1071 1072 // Add trace comment to the begin of the sql string, right after the operator. 1073 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598) 1074 $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 ); 1075 1076 # If DBO_TRX is set, start a transaction 1077 if ( ( $this->mFlags & DBO_TRX ) && !$this->mTrxLevel && 1078 $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' 1079 ) { 1080 # Avoid establishing transactions for SHOW and SET statements too - 1081 # that would delay transaction initializations to once connection 1082 # is really used by application 1083 $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm) 1084 if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 ) { 1085 if ( $wgDebugDBTransactions ) { 1086 wfDebug( "Implicit transaction start.\n" ); 1087 } 1088 $this->begin( __METHOD__ . " ($fname)" ); 1089 $this->mTrxAutomatic = true; 1090 } 1091 } 1092 1093 # Keep track of whether the transaction has write queries pending 1094 if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $this->isWriteQuery( $sql ) ) { 1095 $this->mTrxDoneWrites = true; 1096 Profiler::instance()->transactionWritingIn( 1097 $this->mServer, $this->mDBname, $this->mTrxShortId ); 1098 } 1099 1100 $queryProf = ''; 1101 $totalProf = ''; 1102 $isMaster = !is_null( $this->getLBInfo( 'master' ) ); 1103 1104 if ( !Profiler::instance()->isStub() ) { 1105 # generalizeSQL will probably cut down the query to reasonable 1106 # logging size most of the time. The substr is really just a sanity check. 1107 if ( $isMaster ) { 1108 $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 1109 $totalProf = 'DatabaseBase::query-master'; 1110 } else { 1111 $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 ); 1112 $totalProf = 'DatabaseBase::query'; 1113 } 1114 # Include query transaction state 1115 $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : ""; 1116 1117 $trx = $this->mTrxLevel ? 'TRX=yes' : 'TRX=no'; 1118 wfProfileIn( $totalProf ); 1119 wfProfileIn( $queryProf ); 1120 } 1121 1122 if ( $this->debug() ) { 1123 static $cnt = 0; 1124 1125 $cnt++; 1126 $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength ) 1127 : $commentedSql; 1128 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 1129 1130 $master = $isMaster ? 'master' : 'slave'; 1131 wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" ); 1132 } 1133 1134 $queryId = MWDebug::query( $sql, $fname, $isMaster ); 1135 1136 # Avoid fatals if close() was called 1137 if ( !$this->isOpen() ) { 1138 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 1139 } 1140 1141 # Do the query and handle errors 1142 $ret = $this->doQuery( $commentedSql ); 1143 1144 MWDebug::queryTime( $queryId ); 1145 1146 # Try reconnecting if the connection was lost 1147 if ( false === $ret && $this->wasErrorReissuable() ) { 1148 # Transaction is gone, like it or not 1149 $hadTrx = $this->mTrxLevel; // possible lost transaction 1150 $this->mTrxLevel = 0; 1151 $this->mTrxIdleCallbacks = array(); // bug 65263 1152 $this->mTrxPreCommitCallbacks = array(); // bug 65263 1153 wfDebug( "Connection lost, reconnecting...\n" ); 1154 # Stash the last error values since ping() might clear them 1155 $lastError = $this->lastError(); 1156 $lastErrno = $this->lastErrno(); 1157 if ( $this->ping() ) { 1158 global $wgRequestTime; 1159 wfDebug( "Reconnected\n" ); 1160 $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength ) 1161 : $commentedSql; 1162 $sqlx = strtr( $sqlx, "\t\n", ' ' ); 1163 $elapsed = round( microtime( true ) - $wgRequestTime, 3 ); 1164 if ( $elapsed < 300 ) { 1165 # Not a database error to lose a transaction after a minute or two 1166 wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx" ); 1167 } 1168 if ( $hadTrx ) { 1169 # Leave $ret as false and let an error be reported. 1170 # Callers may catch the exception and continue to use the DB. 1171 $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore ); 1172 } else { 1173 # Should be safe to silently retry (no trx and thus no callbacks) 1174 $ret = $this->doQuery( $commentedSql ); 1175 } 1176 } else { 1177 wfDebug( "Failed\n" ); 1178 } 1179 } 1180 1181 if ( false === $ret ) { 1182 $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore ); 1183 } 1184 1185 if ( !Profiler::instance()->isStub() ) { 1186 wfProfileOut( $queryProf ); 1187 wfProfileOut( $totalProf ); 1188 } 1189 1190 return $this->resultObject( $ret ); 1191 } 1192 1193 /** 1194 * Report a query error. Log the error, and if neither the object ignore 1195 * flag nor the $tempIgnore flag is set, throw a DBQueryError. 1196 * 1197 * @param string $error 1198 * @param int $errno 1199 * @param string $sql 1200 * @param string $fname 1201 * @param bool $tempIgnore 1202 * @throws DBQueryError 1203 */ 1204 public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 1205 # Ignore errors during error handling to avoid infinite recursion 1206 $ignore = $this->ignoreErrors( true ); 1207 ++$this->mErrorCount; 1208 1209 if ( $ignore || $tempIgnore ) { 1210 wfDebug( "SQL ERROR (ignored): $error\n" ); 1211 $this->ignoreErrors( $ignore ); 1212 } else { 1213 $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ); 1214 wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line" ); 1215 wfDebug( "SQL ERROR: " . $error . "\n" ); 1216 throw new DBQueryError( $this, $error, $errno, $sql, $fname ); 1217 } 1218 } 1219 1220 /** 1221 * Intended to be compatible with the PEAR::DB wrapper functions. 1222 * http://pear.php.net/manual/en/package.database.db.intro-execute.php 1223 * 1224 * ? = scalar value, quoted as necessary 1225 * ! = raw SQL bit (a function for instance) 1226 * & = filename; reads the file and inserts as a blob 1227 * (we don't use this though...) 1228 * 1229 * @param string $sql 1230 * @param string $func 1231 * 1232 * @return array 1233 */ 1234 protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) { 1235 /* MySQL doesn't support prepared statements (yet), so just 1236 * pack up the query for reference. We'll manually replace 1237 * the bits later. 1238 */ 1239 return array( 'query' => $sql, 'func' => $func ); 1240 } 1241 1242 /** 1243 * Free a prepared query, generated by prepare(). 1244 * @param string $prepared 1245 */ 1246 protected function freePrepared( $prepared ) { 1247 /* No-op by default */ 1248 } 1249 1250 /** 1251 * Execute a prepared query with the various arguments 1252 * @param string $prepared The prepared sql 1253 * @param mixed $args Either an array here, or put scalars as varargs 1254 * 1255 * @return ResultWrapper 1256 */ 1257 public function execute( $prepared, $args = null ) { 1258 if ( !is_array( $args ) ) { 1259 # Pull the var args 1260 $args = func_get_args(); 1261 array_shift( $args ); 1262 } 1263 1264 $sql = $this->fillPrepared( $prepared['query'], $args ); 1265 1266 return $this->query( $sql, $prepared['func'] ); 1267 } 1268 1269 /** 1270 * For faking prepared SQL statements on DBs that don't support it directly. 1271 * 1272 * @param string $preparedQuery A 'preparable' SQL statement 1273 * @param array $args Array of Arguments to fill it with 1274 * @return string Executable SQL 1275 */ 1276 public function fillPrepared( $preparedQuery, $args ) { 1277 reset( $args ); 1278 $this->preparedArgs =& $args; 1279 1280 return preg_replace_callback( '/(\\\\[?!&]|[?!&])/', 1281 array( &$this, 'fillPreparedArg' ), $preparedQuery ); 1282 } 1283 1284 /** 1285 * preg_callback func for fillPrepared() 1286 * The arguments should be in $this->preparedArgs and must not be touched 1287 * while we're doing this. 1288 * 1289 * @param array $matches 1290 * @throws DBUnexpectedError 1291 * @return string 1292 */ 1293 protected function fillPreparedArg( $matches ) { 1294 switch ( $matches[1] ) { 1295 case '\\?': 1296 return '?'; 1297 case '\\!': 1298 return '!'; 1299 case '\\&': 1300 return '&'; 1301 } 1302 1303 list( /* $n */, $arg ) = each( $this->preparedArgs ); 1304 1305 switch ( $matches[1] ) { 1306 case '?': 1307 return $this->addQuotes( $arg ); 1308 case '!': 1309 return $arg; 1310 case '&': 1311 # return $this->addQuotes( file_get_contents( $arg ) ); 1312 throw new DBUnexpectedError( 1313 $this, 1314 '& mode is not implemented. If it\'s really needed, uncomment the line above.' 1315 ); 1316 default: 1317 throw new DBUnexpectedError( 1318 $this, 1319 'Received invalid match. This should never happen!' 1320 ); 1321 } 1322 } 1323 1324 /** 1325 * Free a result object returned by query() or select(). It's usually not 1326 * necessary to call this, just use unset() or let the variable holding 1327 * the result object go out of scope. 1328 * 1329 * @param mixed $res A SQL result 1330 */ 1331 public function freeResult( $res ) { 1332 } 1333 1334 /** 1335 * A SELECT wrapper which returns a single field from a single result row. 1336 * 1337 * Usually throws a DBQueryError on failure. If errors are explicitly 1338 * ignored, returns false on failure. 1339 * 1340 * If no result rows are returned from the query, false is returned. 1341 * 1342 * @param string|array $table Table name. See DatabaseBase::select() for details. 1343 * @param string $var The field name to select. This must be a valid SQL 1344 * fragment: do not use unvalidated user input. 1345 * @param string|array $cond The condition array. See DatabaseBase::select() for details. 1346 * @param string $fname The function name of the caller. 1347 * @param string|array $options The query options. See DatabaseBase::select() for details. 1348 * 1349 * @return bool|mixed The value from the field, or false on failure. 1350 */ 1351 public function selectField( $table, $var, $cond = '', $fname = __METHOD__, 1352 $options = array() 1353 ) { 1354 if ( !is_array( $options ) ) { 1355 $options = array( $options ); 1356 } 1357 1358 $options['LIMIT'] = 1; 1359 1360 $res = $this->select( $table, $var, $cond, $fname, $options ); 1361 1362 if ( $res === false || !$this->numRows( $res ) ) { 1363 return false; 1364 } 1365 1366 $row = $this->fetchRow( $res ); 1367 1368 if ( $row !== false ) { 1369 return reset( $row ); 1370 } else { 1371 return false; 1372 } 1373 } 1374 1375 /** 1376 * Returns an optional USE INDEX clause to go after the table, and a 1377 * string to go at the end of the query. 1378 * 1379 * @param array $options Associative array of options to be turned into 1380 * an SQL query, valid keys are listed in the function. 1381 * @return array 1382 * @see DatabaseBase::select() 1383 */ 1384 public function makeSelectOptions( $options ) { 1385 $preLimitTail = $postLimitTail = ''; 1386 $startOpts = ''; 1387 1388 $noKeyOptions = array(); 1389 1390 foreach ( $options as $key => $option ) { 1391 if ( is_numeric( $key ) ) { 1392 $noKeyOptions[$option] = true; 1393 } 1394 } 1395 1396 $preLimitTail .= $this->makeGroupByWithHaving( $options ); 1397 1398 $preLimitTail .= $this->makeOrderBy( $options ); 1399 1400 // if (isset($options['LIMIT'])) { 1401 // $tailOpts .= $this->limitResult('', $options['LIMIT'], 1402 // isset($options['OFFSET']) ? $options['OFFSET'] 1403 // : false); 1404 // } 1405 1406 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 1407 $postLimitTail .= ' FOR UPDATE'; 1408 } 1409 1410 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 1411 $postLimitTail .= ' LOCK IN SHARE MODE'; 1412 } 1413 1414 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 1415 $startOpts .= 'DISTINCT'; 1416 } 1417 1418 # Various MySQL extensions 1419 if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) { 1420 $startOpts .= ' /*! STRAIGHT_JOIN */'; 1421 } 1422 1423 if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) { 1424 $startOpts .= ' HIGH_PRIORITY'; 1425 } 1426 1427 if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) { 1428 $startOpts .= ' SQL_BIG_RESULT'; 1429 } 1430 1431 if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) { 1432 $startOpts .= ' SQL_BUFFER_RESULT'; 1433 } 1434 1435 if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) { 1436 $startOpts .= ' SQL_SMALL_RESULT'; 1437 } 1438 1439 if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) { 1440 $startOpts .= ' SQL_CALC_FOUND_ROWS'; 1441 } 1442 1443 if ( isset( $noKeyOptions['SQL_CACHE'] ) ) { 1444 $startOpts .= ' SQL_CACHE'; 1445 } 1446 1447 if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) { 1448 $startOpts .= ' SQL_NO_CACHE'; 1449 } 1450 1451 if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) { 1452 $useIndex = $this->useIndexClause( $options['USE INDEX'] ); 1453 } else { 1454 $useIndex = ''; 1455 } 1456 1457 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 1458 } 1459 1460 /** 1461 * Returns an optional GROUP BY with an optional HAVING 1462 * 1463 * @param array $options Associative array of options 1464 * @return string 1465 * @see DatabaseBase::select() 1466 * @since 1.21 1467 */ 1468 public function makeGroupByWithHaving( $options ) { 1469 $sql = ''; 1470 if ( isset( $options['GROUP BY'] ) ) { 1471 $gb = is_array( $options['GROUP BY'] ) 1472 ? implode( ',', $options['GROUP BY'] ) 1473 : $options['GROUP BY']; 1474 $sql .= ' GROUP BY ' . $gb; 1475 } 1476 if ( isset( $options['HAVING'] ) ) { 1477 $having = is_array( $options['HAVING'] ) 1478 ? $this->makeList( $options['HAVING'], LIST_AND ) 1479 : $options['HAVING']; 1480 $sql .= ' HAVING ' . $having; 1481 } 1482 1483 return $sql; 1484 } 1485 1486 /** 1487 * Returns an optional ORDER BY 1488 * 1489 * @param array $options Associative array of options 1490 * @return string 1491 * @see DatabaseBase::select() 1492 * @since 1.21 1493 */ 1494 public function makeOrderBy( $options ) { 1495 if ( isset( $options['ORDER BY'] ) ) { 1496 $ob = is_array( $options['ORDER BY'] ) 1497 ? implode( ',', $options['ORDER BY'] ) 1498 : $options['ORDER BY']; 1499 1500 return ' ORDER BY ' . $ob; 1501 } 1502 1503 return ''; 1504 } 1505 1506 /** 1507 * Execute a SELECT query constructed using the various parameters provided. 1508 * See below for full details of the parameters. 1509 * 1510 * @param string|array $table Table name 1511 * @param string|array $vars Field names 1512 * @param string|array $conds Conditions 1513 * @param string $fname Caller function name 1514 * @param array $options Query options 1515 * @param array $join_conds Join conditions 1516 * 1517 * 1518 * @param string|array $table 1519 * 1520 * May be either an array of table names, or a single string holding a table 1521 * name. If an array is given, table aliases can be specified, for example: 1522 * 1523 * array( 'a' => 'user' ) 1524 * 1525 * This includes the user table in the query, with the alias "a" available 1526 * for use in field names (e.g. a.user_name). 1527 * 1528 * All of the table names given here are automatically run through 1529 * DatabaseBase::tableName(), which causes the table prefix (if any) to be 1530 * added, and various other table name mappings to be performed. 1531 * 1532 * 1533 * @param string|array $vars 1534 * 1535 * May be either a field name or an array of field names. The field names 1536 * can be complete fragments of SQL, for direct inclusion into the SELECT 1537 * query. If an array is given, field aliases can be specified, for example: 1538 * 1539 * array( 'maxrev' => 'MAX(rev_id)' ) 1540 * 1541 * This includes an expression with the alias "maxrev" in the query. 1542 * 1543 * If an expression is given, care must be taken to ensure that it is 1544 * DBMS-independent. 1545 * 1546 * 1547 * @param string|array $conds 1548 * 1549 * May be either a string containing a single condition, or an array of 1550 * conditions. If an array is given, the conditions constructed from each 1551 * element are combined with AND. 1552 * 1553 * Array elements may take one of two forms: 1554 * 1555 * - Elements with a numeric key are interpreted as raw SQL fragments. 1556 * - Elements with a string key are interpreted as equality conditions, 1557 * where the key is the field name. 1558 * - If the value of such an array element is a scalar (such as a 1559 * string), it will be treated as data and thus quoted appropriately. 1560 * If it is null, an IS NULL clause will be added. 1561 * - If the value is an array, an IN(...) clause will be constructed, 1562 * such that the field name may match any of the elements in the 1563 * array. The elements of the array will be quoted. 1564 * 1565 * Note that expressions are often DBMS-dependent in their syntax. 1566 * DBMS-independent wrappers are provided for constructing several types of 1567 * expression commonly used in condition queries. See: 1568 * - DatabaseBase::buildLike() 1569 * - DatabaseBase::conditional() 1570 * 1571 * 1572 * @param string|array $options 1573 * 1574 * Optional: Array of query options. Boolean options are specified by 1575 * including them in the array as a string value with a numeric key, for 1576 * example: 1577 * 1578 * array( 'FOR UPDATE' ) 1579 * 1580 * The supported options are: 1581 * 1582 * - OFFSET: Skip this many rows at the start of the result set. OFFSET 1583 * with LIMIT can theoretically be used for paging through a result set, 1584 * but this is discouraged in MediaWiki for performance reasons. 1585 * 1586 * - LIMIT: Integer: return at most this many rows. The rows are sorted 1587 * and then the first rows are taken until the limit is reached. LIMIT 1588 * is applied to a result set after OFFSET. 1589 * 1590 * - FOR UPDATE: Boolean: lock the returned rows so that they can't be 1591 * changed until the next COMMIT. 1592 * 1593 * - DISTINCT: Boolean: return only unique result rows. 1594 * 1595 * - GROUP BY: May be either an SQL fragment string naming a field or 1596 * expression to group by, or an array of such SQL fragments. 1597 * 1598 * - HAVING: May be either an string containing a HAVING clause or an array of 1599 * conditions building the HAVING clause. If an array is given, the conditions 1600 * constructed from each element are combined with AND. 1601 * 1602 * - ORDER BY: May be either an SQL fragment giving a field name or 1603 * expression to order by, or an array of such SQL fragments. 1604 * 1605 * - USE INDEX: This may be either a string giving the index name to use 1606 * for the query, or an array. If it is an associative array, each key 1607 * gives the table name (or alias), each value gives the index name to 1608 * use for that table. All strings are SQL fragments and so should be 1609 * validated by the caller. 1610 * 1611 * - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run, 1612 * instead of SELECT. 1613 * 1614 * And also the following boolean MySQL extensions, see the MySQL manual 1615 * for documentation: 1616 * 1617 * - LOCK IN SHARE MODE 1618 * - STRAIGHT_JOIN 1619 * - HIGH_PRIORITY 1620 * - SQL_BIG_RESULT 1621 * - SQL_BUFFER_RESULT 1622 * - SQL_SMALL_RESULT 1623 * - SQL_CALC_FOUND_ROWS 1624 * - SQL_CACHE 1625 * - SQL_NO_CACHE 1626 * 1627 * 1628 * @param string|array $join_conds 1629 * 1630 * Optional associative array of table-specific join conditions. In the 1631 * most common case, this is unnecessary, since the join condition can be 1632 * in $conds. However, it is useful for doing a LEFT JOIN. 1633 * 1634 * The key of the array contains the table name or alias. The value is an 1635 * array with two elements, numbered 0 and 1. The first gives the type of 1636 * join, the second is an SQL fragment giving the join condition for that 1637 * table. For example: 1638 * 1639 * array( 'page' => array( 'LEFT JOIN', 'page_latest=rev_id' ) ) 1640 * 1641 * @return ResultWrapper|bool If the query returned no rows, a ResultWrapper 1642 * with no rows in it will be returned. If there was a query error, a 1643 * DBQueryError exception will be thrown, except if the "ignore errors" 1644 * option was set, in which case false will be returned. 1645 */ 1646 public function select( $table, $vars, $conds = '', $fname = __METHOD__, 1647 $options = array(), $join_conds = array() ) { 1648 $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 1649 1650 return $this->query( $sql, $fname ); 1651 } 1652 1653 /** 1654 * The equivalent of DatabaseBase::select() except that the constructed SQL 1655 * is returned, instead of being immediately executed. This can be useful for 1656 * doing UNION queries, where the SQL text of each query is needed. In general, 1657 * however, callers outside of Database classes should just use select(). 1658 * 1659 * @param string|array $table Table name 1660 * @param string|array $vars Field names 1661 * @param string|array $conds Conditions 1662 * @param string $fname Caller function name 1663 * @param string|array $options Query options 1664 * @param string|array $join_conds Join conditions 1665 * 1666 * @return string SQL query string. 1667 * @see DatabaseBase::select() 1668 */ 1669 public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, 1670 $options = array(), $join_conds = array() 1671 ) { 1672 if ( is_array( $vars ) ) { 1673 $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) ); 1674 } 1675 1676 $options = (array)$options; 1677 $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) ) 1678 ? $options['USE INDEX'] 1679 : array(); 1680 1681 if ( is_array( $table ) ) { 1682 $from = ' FROM ' . 1683 $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds ); 1684 } elseif ( $table != '' ) { 1685 if ( $table[0] == ' ' ) { 1686 $from = ' FROM ' . $table; 1687 } else { 1688 $from = ' FROM ' . 1689 $this->tableNamesWithUseIndexOrJOIN( array( $table ), $useIndexes, array() ); 1690 } 1691 } else { 1692 $from = ''; 1693 } 1694 1695 list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = 1696 $this->makeSelectOptions( $options ); 1697 1698 if ( !empty( $conds ) ) { 1699 if ( is_array( $conds ) ) { 1700 $conds = $this->makeList( $conds, LIST_AND ); 1701 } 1702 $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail"; 1703 } else { 1704 $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail"; 1705 } 1706 1707 if ( isset( $options['LIMIT'] ) ) { 1708 $sql = $this->limitResult( $sql, $options['LIMIT'], 1709 isset( $options['OFFSET'] ) ? $options['OFFSET'] : false ); 1710 } 1711 $sql = "$sql $postLimitTail"; 1712 1713 if ( isset( $options['EXPLAIN'] ) ) { 1714 $sql = 'EXPLAIN ' . $sql; 1715 } 1716 1717 return $sql; 1718 } 1719 1720 /** 1721 * Single row SELECT wrapper. Equivalent to DatabaseBase::select(), except 1722 * that a single row object is returned. If the query returns no rows, 1723 * false is returned. 1724 * 1725 * @param string|array $table Table name 1726 * @param string|array $vars Field names 1727 * @param array $conds Conditions 1728 * @param string $fname Caller function name 1729 * @param string|array $options Query options 1730 * @param array|string $join_conds Join conditions 1731 * 1732 * @return stdClass|bool 1733 */ 1734 public function selectRow( $table, $vars, $conds, $fname = __METHOD__, 1735 $options = array(), $join_conds = array() 1736 ) { 1737 $options = (array)$options; 1738 $options['LIMIT'] = 1; 1739 $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds ); 1740 1741 if ( $res === false ) { 1742 return false; 1743 } 1744 1745 if ( !$this->numRows( $res ) ) { 1746 return false; 1747 } 1748 1749 $obj = $this->fetchObject( $res ); 1750 1751 return $obj; 1752 } 1753 1754 /** 1755 * Estimate the number of rows in dataset 1756 * 1757 * MySQL allows you to estimate the number of rows that would be returned 1758 * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using 1759 * index cardinality statistics, and is notoriously inaccurate, especially 1760 * when large numbers of rows have recently been added or deleted. 1761 * 1762 * For DBMSs that don't support fast result size estimation, this function 1763 * will actually perform the SELECT COUNT(*). 1764 * 1765 * Takes the same arguments as DatabaseBase::select(). 1766 * 1767 * @param string $table Table name 1768 * @param string $vars Unused 1769 * @param array|string $conds Filters on the table 1770 * @param string $fname Function name for profiling 1771 * @param array $options Options for select 1772 * @return int Row count 1773 */ 1774 public function estimateRowCount( 1775 $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() 1776 ) { 1777 $rows = 0; 1778 $res = $this->select( $table, array( 'rowcount' => 'COUNT(*)' ), $conds, $fname, $options ); 1779 1780 if ( $res ) { 1781 $row = $this->fetchRow( $res ); 1782 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 1783 } 1784 1785 return $rows; 1786 } 1787 1788 /** 1789 * Get the number of rows in dataset 1790 * 1791 * This is useful when trying to do COUNT(*) but with a LIMIT for performance. 1792 * 1793 * Takes the same arguments as DatabaseBase::select(). 1794 * 1795 * @param string $table Table name 1796 * @param string $vars Unused 1797 * @param array|string $conds Filters on the table 1798 * @param string $fname Function name for profiling 1799 * @param array $options Options for select 1800 * @return int Row count 1801 * @since 1.24 1802 */ 1803 public function selectRowCount( 1804 $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = array() 1805 ) { 1806 $rows = 0; 1807 $sql = $this->selectSQLText( $table, '1', $conds, $fname, $options ); 1808 $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count" ); 1809 1810 if ( $res ) { 1811 $row = $this->fetchRow( $res ); 1812 $rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0; 1813 } 1814 1815 return $rows; 1816 } 1817 1818 /** 1819 * Removes most variables from an SQL query and replaces them with X or N for numbers. 1820 * It's only slightly flawed. Don't use for anything important. 1821 * 1822 * @param string $sql A SQL Query 1823 * 1824 * @return string 1825 */ 1826 static function generalizeSQL( $sql ) { 1827 # This does the same as the regexp below would do, but in such a way 1828 # as to avoid crashing php on some large strings. 1829 # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql ); 1830 1831 $sql = str_replace( "\\\\", '', $sql ); 1832 $sql = str_replace( "\\'", '', $sql ); 1833 $sql = str_replace( "\\\"", '', $sql ); 1834 $sql = preg_replace( "/'.*'/s", "'X'", $sql ); 1835 $sql = preg_replace( '/".*"/s', "'X'", $sql ); 1836 1837 # All newlines, tabs, etc replaced by single space 1838 $sql = preg_replace( '/\s+/', ' ', $sql ); 1839 1840 # All numbers => N, 1841 # except the ones surrounded by characters, e.g. l10n 1842 $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql ); 1843 $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql ); 1844 1845 return $sql; 1846 } 1847 1848 /** 1849 * Determines whether a field exists in a table 1850 * 1851 * @param string $table Table name 1852 * @param string $field Filed to check on that table 1853 * @param string $fname Calling function name (optional) 1854 * @return bool Whether $table has filed $field 1855 */ 1856 public function fieldExists( $table, $field, $fname = __METHOD__ ) { 1857 $info = $this->fieldInfo( $table, $field ); 1858 1859 return (bool)$info; 1860 } 1861 1862 /** 1863 * Determines whether an index exists 1864 * Usually throws a DBQueryError on failure 1865 * If errors are explicitly ignored, returns NULL on failure 1866 * 1867 * @param string $table 1868 * @param string $index 1869 * @param string $fname 1870 * @return bool|null 1871 */ 1872 public function indexExists( $table, $index, $fname = __METHOD__ ) { 1873 if ( !$this->tableExists( $table ) ) { 1874 return null; 1875 } 1876 1877 $info = $this->indexInfo( $table, $index, $fname ); 1878 if ( is_null( $info ) ) { 1879 return null; 1880 } else { 1881 return $info !== false; 1882 } 1883 } 1884 1885 /** 1886 * Query whether a given table exists 1887 * 1888 * @param string $table 1889 * @param string $fname 1890 * @return bool 1891 */ 1892 public function tableExists( $table, $fname = __METHOD__ ) { 1893 $table = $this->tableName( $table ); 1894 $old = $this->ignoreErrors( true ); 1895 $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname ); 1896 $this->ignoreErrors( $old ); 1897 1898 return (bool)$res; 1899 } 1900 1901 /** 1902 * Determines if a given index is unique 1903 * 1904 * @param string $table 1905 * @param string $index 1906 * 1907 * @return bool 1908 */ 1909 public function indexUnique( $table, $index ) { 1910 $indexInfo = $this->indexInfo( $table, $index ); 1911 1912 if ( !$indexInfo ) { 1913 return null; 1914 } 1915 1916 return !$indexInfo[0]->Non_unique; 1917 } 1918 1919 /** 1920 * Helper for DatabaseBase::insert(). 1921 * 1922 * @param array $options 1923 * @return string 1924 */ 1925 protected function makeInsertOptions( $options ) { 1926 return implode( ' ', $options ); 1927 } 1928 1929 /** 1930 * INSERT wrapper, inserts an array into a table. 1931 * 1932 * $a may be either: 1933 * 1934 * - A single associative array. The array keys are the field names, and 1935 * the values are the values to insert. The values are treated as data 1936 * and will be quoted appropriately. If NULL is inserted, this will be 1937 * converted to a database NULL. 1938 * - An array with numeric keys, holding a list of associative arrays. 1939 * This causes a multi-row INSERT on DBMSs that support it. The keys in 1940 * each subarray must be identical to each other, and in the same order. 1941 * 1942 * Usually throws a DBQueryError on failure. If errors are explicitly ignored, 1943 * returns success. 1944 * 1945 * $options is an array of options, with boolean options encoded as values 1946 * with numeric keys, in the same style as $options in 1947 * DatabaseBase::select(). Supported options are: 1948 * 1949 * - IGNORE: Boolean: if present, duplicate key errors are ignored, and 1950 * any rows which cause duplicate key errors are not inserted. It's 1951 * possible to determine how many rows were successfully inserted using 1952 * DatabaseBase::affectedRows(). 1953 * 1954 * @param string $table Table name. This will be passed through 1955 * DatabaseBase::tableName(). 1956 * @param array $a Array of rows to insert 1957 * @param string $fname Calling function name (use __METHOD__) for logs/profiling 1958 * @param array $options Array of options 1959 * 1960 * @return bool 1961 */ 1962 public function insert( $table, $a, $fname = __METHOD__, $options = array() ) { 1963 # No rows to insert, easy just return now 1964 if ( !count( $a ) ) { 1965 return true; 1966 } 1967 1968 $table = $this->tableName( $table ); 1969 1970 if ( !is_array( $options ) ) { 1971 $options = array( $options ); 1972 } 1973 1974 $fh = null; 1975 if ( isset( $options['fileHandle'] ) ) { 1976 $fh = $options['fileHandle']; 1977 } 1978 $options = $this->makeInsertOptions( $options ); 1979 1980 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 1981 $multi = true; 1982 $keys = array_keys( $a[0] ); 1983 } else { 1984 $multi = false; 1985 $keys = array_keys( $a ); 1986 } 1987 1988 $sql = 'INSERT ' . $options . 1989 " INTO $table (" . implode( ',', $keys ) . ') VALUES '; 1990 1991 if ( $multi ) { 1992 $first = true; 1993 foreach ( $a as $row ) { 1994 if ( $first ) { 1995 $first = false; 1996 } else { 1997 $sql .= ','; 1998 } 1999 $sql .= '(' . $this->makeList( $row ) . ')'; 2000 } 2001 } else { 2002 $sql .= '(' . $this->makeList( $a ) . ')'; 2003 } 2004 2005 if ( $fh !== null && false === fwrite( $fh, $sql ) ) { 2006 return false; 2007 } elseif ( $fh !== null ) { 2008 return true; 2009 } 2010 2011 return (bool)$this->query( $sql, $fname ); 2012 } 2013 2014 /** 2015 * Make UPDATE options array for DatabaseBase::makeUpdateOptions 2016 * 2017 * @param array $options 2018 * @return array 2019 */ 2020 protected function makeUpdateOptionsArray( $options ) { 2021 if ( !is_array( $options ) ) { 2022 $options = array( $options ); 2023 } 2024 2025 $opts = array(); 2026 2027 if ( in_array( 'LOW_PRIORITY', $options ) ) { 2028 $opts[] = $this->lowPriorityOption(); 2029 } 2030 2031 if ( in_array( 'IGNORE', $options ) ) { 2032 $opts[] = 'IGNORE'; 2033 } 2034 2035 return $opts; 2036 } 2037 2038 /** 2039 * Make UPDATE options for the DatabaseBase::update function 2040 * 2041 * @param array $options The options passed to DatabaseBase::update 2042 * @return string 2043 */ 2044 protected function makeUpdateOptions( $options ) { 2045 $opts = $this->makeUpdateOptionsArray( $options ); 2046 2047 return implode( ' ', $opts ); 2048 } 2049 2050 /** 2051 * UPDATE wrapper. Takes a condition array and a SET array. 2052 * 2053 * @param string $table Name of the table to UPDATE. This will be passed through 2054 * DatabaseBase::tableName(). 2055 * @param array $values An array of values to SET. For each array element, 2056 * the key gives the field name, and the value gives the data to set 2057 * that field to. The data will be quoted by DatabaseBase::addQuotes(). 2058 * @param array $conds An array of conditions (WHERE). See 2059 * DatabaseBase::select() for the details of the format of condition 2060 * arrays. Use '*' to update all rows. 2061 * @param string $fname The function name of the caller (from __METHOD__), 2062 * for logging and profiling. 2063 * @param array $options An array of UPDATE options, can be: 2064 * - IGNORE: Ignore unique key conflicts 2065 * - LOW_PRIORITY: MySQL-specific, see MySQL manual. 2066 * @return bool 2067 */ 2068 function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) { 2069 $table = $this->tableName( $table ); 2070 $opts = $this->makeUpdateOptions( $options ); 2071 $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET ); 2072 2073 if ( $conds !== array() && $conds !== '*' ) { 2074 $sql .= " WHERE " . $this->makeList( $conds, LIST_AND ); 2075 } 2076 2077 return $this->query( $sql, $fname ); 2078 } 2079 2080 /** 2081 * Makes an encoded list of strings from an array 2082 * 2083 * @param array $a Containing the data 2084 * @param int $mode Constant 2085 * - LIST_COMMA: Comma separated, no field names 2086 * - LIST_AND: ANDed WHERE clause (without the WHERE). See the 2087 * documentation for $conds in DatabaseBase::select(). 2088 * - LIST_OR: ORed WHERE clause (without the WHERE) 2089 * - LIST_SET: Comma separated with field names, like a SET clause 2090 * - LIST_NAMES: Comma separated field names 2091 * @throws MWException|DBUnexpectedError 2092 * @return string 2093 */ 2094 public function makeList( $a, $mode = LIST_COMMA ) { 2095 if ( !is_array( $a ) ) { 2096 throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' ); 2097 } 2098 2099 $first = true; 2100 $list = ''; 2101 2102 foreach ( $a as $field => $value ) { 2103 if ( !$first ) { 2104 if ( $mode == LIST_AND ) { 2105 $list .= ' AND '; 2106 } elseif ( $mode == LIST_OR ) { 2107 $list .= ' OR '; 2108 } else { 2109 $list .= ','; 2110 } 2111 } else { 2112 $first = false; 2113 } 2114 2115 if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) { 2116 $list .= "($value)"; 2117 } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) { 2118 $list .= "$value"; 2119 } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) { 2120 if ( count( $value ) == 0 ) { 2121 throw new MWException( __METHOD__ . ": empty input for field $field" ); 2122 } elseif ( count( $value ) == 1 ) { 2123 // Special-case single values, as IN isn't terribly efficient 2124 // Don't necessarily assume the single key is 0; we don't 2125 // enforce linear numeric ordering on other arrays here. 2126 $value = array_values( $value ); 2127 $list .= $field . " = " . $this->addQuotes( $value[0] ); 2128 } else { 2129 $list .= $field . " IN (" . $this->makeList( $value ) . ") "; 2130 } 2131 } elseif ( $value === null ) { 2132 if ( $mode == LIST_AND || $mode == LIST_OR ) { 2133 $list .= "$field IS "; 2134 } elseif ( $mode == LIST_SET ) { 2135 $list .= "$field = "; 2136 } 2137 $list .= 'NULL'; 2138 } else { 2139 if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { 2140 $list .= "$field = "; 2141 } 2142 $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value ); 2143 } 2144 } 2145 2146 return $list; 2147 } 2148 2149 /** 2150 * Build a partial where clause from a 2-d array such as used for LinkBatch. 2151 * The keys on each level may be either integers or strings. 2152 * 2153 * @param array $data Organized as 2-d 2154 * array(baseKeyVal => array(subKeyVal => [ignored], ...), ...) 2155 * @param string $baseKey Field name to match the base-level keys to (eg 'pl_namespace') 2156 * @param string $subKey Field name to match the sub-level keys to (eg 'pl_title') 2157 * @return string|bool SQL fragment, or false if no items in array 2158 */ 2159 public function makeWhereFrom2d( $data, $baseKey, $subKey ) { 2160 $conds = array(); 2161 2162 foreach ( $data as $base => $sub ) { 2163 if ( count( $sub ) ) { 2164 $conds[] = $this->makeList( 2165 array( $baseKey => $base, $subKey => array_keys( $sub ) ), 2166 LIST_AND ); 2167 } 2168 } 2169 2170 if ( $conds ) { 2171 return $this->makeList( $conds, LIST_OR ); 2172 } else { 2173 // Nothing to search for... 2174 return false; 2175 } 2176 } 2177 2178 /** 2179 * Return aggregated value alias 2180 * 2181 * @param array $valuedata 2182 * @param string $valuename 2183 * 2184 * @return string 2185 */ 2186 public function aggregateValue( $valuedata, $valuename = 'value' ) { 2187 return $valuename; 2188 } 2189 2190 /** 2191 * @param string $field 2192 * @return string 2193 */ 2194 public function bitNot( $field ) { 2195 return "(~$field)"; 2196 } 2197 2198 /** 2199 * @param string $fieldLeft 2200 * @param string $fieldRight 2201 * @return string 2202 */ 2203 public function bitAnd( $fieldLeft, $fieldRight ) { 2204 return "($fieldLeft & $fieldRight)"; 2205 } 2206 2207 /** 2208 * @param string $fieldLeft 2209 * @param string $fieldRight 2210 * @return string 2211 */ 2212 public function bitOr( $fieldLeft, $fieldRight ) { 2213 return "($fieldLeft | $fieldRight)"; 2214 } 2215 2216 /** 2217 * Build a concatenation list to feed into a SQL query 2218 * @param array $stringList List of raw SQL expressions; caller is 2219 * responsible for any quoting 2220 * @return string 2221 */ 2222 public function buildConcat( $stringList ) { 2223 return 'CONCAT(' . implode( ',', $stringList ) . ')'; 2224 } 2225 2226 /** 2227 * Build a GROUP_CONCAT or equivalent statement for a query. 2228 * 2229 * This is useful for combining a field for several rows into a single string. 2230 * NULL values will not appear in the output, duplicated values will appear, 2231 * and the resulting delimiter-separated values have no defined sort order. 2232 * Code using the results may need to use the PHP unique() or sort() methods. 2233 * 2234 * @param string $delim Glue to bind the results together 2235 * @param string|array $table Table name 2236 * @param string $field Field name 2237 * @param string|array $conds Conditions 2238 * @param string|array $join_conds Join conditions 2239 * @return string SQL text 2240 * @since 1.23 2241 */ 2242 public function buildGroupConcatField( 2243 $delim, $table, $field, $conds = '', $join_conds = array() 2244 ) { 2245 $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')'; 2246 2247 return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')'; 2248 } 2249 2250 /** 2251 * Change the current database 2252 * 2253 * @todo Explain what exactly will fail if this is not overridden. 2254 * 2255 * @param string $db 2256 * 2257 * @return bool Success or failure 2258 */ 2259 public function selectDB( $db ) { 2260 # Stub. Shouldn't cause serious problems if it's not overridden, but 2261 # if your database engine supports a concept similar to MySQL's 2262 # databases you may as well. 2263 $this->mDBname = $db; 2264 2265 return true; 2266 } 2267 2268 /** 2269 * Get the current DB name 2270 * @return string 2271 */ 2272 public function getDBname() { 2273 return $this->mDBname; 2274 } 2275 2276 /** 2277 * Get the server hostname or IP address 2278 * @return string 2279 */ 2280 public function getServer() { 2281 return $this->mServer; 2282 } 2283 2284 /** 2285 * Format a table name ready for use in constructing an SQL query 2286 * 2287 * This does two important things: it quotes the table names to clean them up, 2288 * and it adds a table prefix if only given a table name with no quotes. 2289 * 2290 * All functions of this object which require a table name call this function 2291 * themselves. Pass the canonical name to such functions. This is only needed 2292 * when calling query() directly. 2293 * 2294 * @param string $name Database table name 2295 * @param string $format One of: 2296 * quoted - Automatically pass the table name through addIdentifierQuotes() 2297 * so that it can be used in a query. 2298 * raw - Do not add identifier quotes to the table name 2299 * @return string Full database name 2300 */ 2301 public function tableName( $name, $format = 'quoted' ) { 2302 global $wgSharedDB, $wgSharedPrefix, $wgSharedTables, $wgSharedSchema; 2303 # Skip the entire process when we have a string quoted on both ends. 2304 # Note that we check the end so that we will still quote any use of 2305 # use of `database`.table. But won't break things if someone wants 2306 # to query a database table with a dot in the name. 2307 if ( $this->isQuotedIdentifier( $name ) ) { 2308 return $name; 2309 } 2310 2311 # Lets test for any bits of text that should never show up in a table 2312 # name. Basically anything like JOIN or ON which are actually part of 2313 # SQL queries, but may end up inside of the table value to combine 2314 # sql. Such as how the API is doing. 2315 # Note that we use a whitespace test rather than a \b test to avoid 2316 # any remote case where a word like on may be inside of a table name 2317 # surrounded by symbols which may be considered word breaks. 2318 if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) { 2319 return $name; 2320 } 2321 2322 # Split database and table into proper variables. 2323 # We reverse the explode so that database.table and table both output 2324 # the correct table. 2325 $dbDetails = explode( '.', $name, 2 ); 2326 if ( count( $dbDetails ) == 3 ) { 2327 list( $database, $schema, $table ) = $dbDetails; 2328 # We don't want any prefix added in this case 2329 $prefix = ''; 2330 } elseif ( count( $dbDetails ) == 2 ) { 2331 list( $database, $table ) = $dbDetails; 2332 # We don't want any prefix added in this case 2333 # In dbs that support it, $database may actually be the schema 2334 # but that doesn't affect any of the functionality here 2335 $prefix = ''; 2336 $schema = null; 2337 } else { 2338 list( $table ) = $dbDetails; 2339 if ( $wgSharedDB !== null # We have a shared database 2340 && $this->mForeign == false # We're not working on a foreign database 2341 && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`' 2342 && in_array( $table, $wgSharedTables ) # A shared table is selected 2343 ) { 2344 $database = $wgSharedDB; 2345 $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema; 2346 $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix; 2347 } else { 2348 $database = null; 2349 $schema = $this->mSchema; # Default schema 2350 $prefix = $this->mTablePrefix; # Default prefix 2351 } 2352 } 2353 2354 # Quote $table and apply the prefix if not quoted. 2355 # $tableName might be empty if this is called from Database::replaceVars() 2356 $tableName = "{$prefix}{$table}"; 2357 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) { 2358 $tableName = $this->addIdentifierQuotes( $tableName ); 2359 } 2360 2361 # Quote $schema and merge it with the table name if needed 2362 if ( $schema !== null ) { 2363 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) { 2364 $schema = $this->addIdentifierQuotes( $schema ); 2365 } 2366 $tableName = $schema . '.' . $tableName; 2367 } 2368 2369 # Quote $database and merge it with the table name if needed 2370 if ( $database !== null ) { 2371 if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) { 2372 $database = $this->addIdentifierQuotes( $database ); 2373 } 2374 $tableName = $database . '.' . $tableName; 2375 } 2376 2377 return $tableName; 2378 } 2379 2380 /** 2381 * Fetch a number of table names into an array 2382 * This is handy when you need to construct SQL for joins 2383 * 2384 * Example: 2385 * extract( $dbr->tableNames( 'user', 'watchlist' ) ); 2386 * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user 2387 * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; 2388 * 2389 * @return array 2390 */ 2391 public function tableNames() { 2392 $inArray = func_get_args(); 2393 $retVal = array(); 2394 2395 foreach ( $inArray as $name ) { 2396 $retVal[$name] = $this->tableName( $name ); 2397 } 2398 2399 return $retVal; 2400 } 2401 2402 /** 2403 * Fetch a number of table names into an zero-indexed numerical array 2404 * This is handy when you need to construct SQL for joins 2405 * 2406 * Example: 2407 * list( $user, $watchlist ) = $dbr->tableNamesN( 'user', 'watchlist' ); 2408 * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user 2409 * WHERE wl_user=user_id AND wl_user=$nameWithQuotes"; 2410 * 2411 * @return array 2412 */ 2413 public function tableNamesN() { 2414 $inArray = func_get_args(); 2415 $retVal = array(); 2416 2417 foreach ( $inArray as $name ) { 2418 $retVal[] = $this->tableName( $name ); 2419 } 2420 2421 return $retVal; 2422 } 2423 2424 /** 2425 * Get an aliased table name 2426 * e.g. tableName AS newTableName 2427 * 2428 * @param string $name Table name, see tableName() 2429 * @param string|bool $alias Alias (optional) 2430 * @return string SQL name for aliased table. Will not alias a table to its own name 2431 */ 2432 public function tableNameWithAlias( $name, $alias = false ) { 2433 if ( !$alias || $alias == $name ) { 2434 return $this->tableName( $name ); 2435 } else { 2436 return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias ); 2437 } 2438 } 2439 2440 /** 2441 * Gets an array of aliased table names 2442 * 2443 * @param array $tables Array( [alias] => table ) 2444 * @return string[] See tableNameWithAlias() 2445 */ 2446 public function tableNamesWithAlias( $tables ) { 2447 $retval = array(); 2448 foreach ( $tables as $alias => $table ) { 2449 if ( is_numeric( $alias ) ) { 2450 $alias = $table; 2451 } 2452 $retval[] = $this->tableNameWithAlias( $table, $alias ); 2453 } 2454 2455 return $retval; 2456 } 2457 2458 /** 2459 * Get an aliased field name 2460 * e.g. fieldName AS newFieldName 2461 * 2462 * @param string $name Field name 2463 * @param string|bool $alias Alias (optional) 2464 * @return string SQL name for aliased field. Will not alias a field to its own name 2465 */ 2466 public function fieldNameWithAlias( $name, $alias = false ) { 2467 if ( !$alias || (string)$alias === (string)$name ) { 2468 return $name; 2469 } else { 2470 return $name . ' AS ' . $alias; //PostgreSQL needs AS 2471 } 2472 } 2473 2474 /** 2475 * Gets an array of aliased field names 2476 * 2477 * @param array $fields Array( [alias] => field ) 2478 * @return string[] See fieldNameWithAlias() 2479 */ 2480 public function fieldNamesWithAlias( $fields ) { 2481 $retval = array(); 2482 foreach ( $fields as $alias => $field ) { 2483 if ( is_numeric( $alias ) ) { 2484 $alias = $field; 2485 } 2486 $retval[] = $this->fieldNameWithAlias( $field, $alias ); 2487 } 2488 2489 return $retval; 2490 } 2491 2492 /** 2493 * Get the aliased table name clause for a FROM clause 2494 * which might have a JOIN and/or USE INDEX clause 2495 * 2496 * @param array $tables ( [alias] => table ) 2497 * @param array $use_index Same as for select() 2498 * @param array $join_conds Same as for select() 2499 * @return string 2500 */ 2501 protected function tableNamesWithUseIndexOrJOIN( 2502 $tables, $use_index = array(), $join_conds = array() 2503 ) { 2504 $ret = array(); 2505 $retJOIN = array(); 2506 $use_index = (array)$use_index; 2507 $join_conds = (array)$join_conds; 2508 2509 foreach ( $tables as $alias => $table ) { 2510 if ( !is_string( $alias ) ) { 2511 // No alias? Set it equal to the table name 2512 $alias = $table; 2513 } 2514 // Is there a JOIN clause for this table? 2515 if ( isset( $join_conds[$alias] ) ) { 2516 list( $joinType, $conds ) = $join_conds[$alias]; 2517 $tableClause = $joinType; 2518 $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias ); 2519 if ( isset( $use_index[$alias] ) ) { // has USE INDEX? 2520 $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) ); 2521 if ( $use != '' ) { 2522 $tableClause .= ' ' . $use; 2523 } 2524 } 2525 $on = $this->makeList( (array)$conds, LIST_AND ); 2526 if ( $on != '' ) { 2527 $tableClause .= ' ON (' . $on . ')'; 2528 } 2529 2530 $retJOIN[] = $tableClause; 2531 } elseif ( isset( $use_index[$alias] ) ) { 2532 // Is there an INDEX clause for this table? 2533 $tableClause = $this->tableNameWithAlias( $table, $alias ); 2534 $tableClause .= ' ' . $this->useIndexClause( 2535 implode( ',', (array)$use_index[$alias] ) 2536 ); 2537 2538 $ret[] = $tableClause; 2539 } else { 2540 $tableClause = $this->tableNameWithAlias( $table, $alias ); 2541 2542 $ret[] = $tableClause; 2543 } 2544 } 2545 2546 // We can't separate explicit JOIN clauses with ',', use ' ' for those 2547 $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : ""; 2548 $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : ""; 2549 2550 // Compile our final table clause 2551 return implode( ' ', array( $implicitJoins, $explicitJoins ) ); 2552 } 2553 2554 /** 2555 * Get the name of an index in a given table 2556 * 2557 * @param string $index 2558 * @return string 2559 */ 2560 protected function indexName( $index ) { 2561 // Backwards-compatibility hack 2562 $renamed = array( 2563 'ar_usertext_timestamp' => 'usertext_timestamp', 2564 'un_user_id' => 'user_id', 2565 'un_user_ip' => 'user_ip', 2566 ); 2567 2568 if ( isset( $renamed[$index] ) ) { 2569 return $renamed[$index]; 2570 } else { 2571 return $index; 2572 } 2573 } 2574 2575 /** 2576 * Adds quotes and backslashes. 2577 * 2578 * @param string $s 2579 * @return string 2580 */ 2581 public function addQuotes( $s ) { 2582 if ( $s === null ) { 2583 return 'NULL'; 2584 } else { 2585 # This will also quote numeric values. This should be harmless, 2586 # and protects against weird problems that occur when they really 2587 # _are_ strings such as article titles and string->number->string 2588 # conversion is not 1:1. 2589 return "'" . $this->strencode( $s ) . "'"; 2590 } 2591 } 2592 2593 /** 2594 * Quotes an identifier using `backticks` or "double quotes" depending on the database type. 2595 * MySQL uses `backticks` while basically everything else uses double quotes. 2596 * Since MySQL is the odd one out here the double quotes are our generic 2597 * and we implement backticks in DatabaseMysql. 2598 * 2599 * @param string $s 2600 * @return string 2601 */ 2602 public function addIdentifierQuotes( $s ) { 2603 return '"' . str_replace( '"', '""', $s ) . '"'; 2604 } 2605 2606 /** 2607 * Returns if the given identifier looks quoted or not according to 2608 * the database convention for quoting identifiers . 2609 * 2610 * @param string $name 2611 * @return bool 2612 */ 2613 public function isQuotedIdentifier( $name ) { 2614 return $name[0] == '"' && substr( $name, -1, 1 ) == '"'; 2615 } 2616 2617 /** 2618 * @param string $s 2619 * @return string 2620 */ 2621 protected function escapeLikeInternal( $s ) { 2622 return addcslashes( $s, '\%_' ); 2623 } 2624 2625 /** 2626 * LIKE statement wrapper, receives a variable-length argument list with 2627 * parts of pattern to match containing either string literals that will be 2628 * escaped or tokens returned by anyChar() or anyString(). Alternatively, 2629 * the function could be provided with an array of aforementioned 2630 * parameters. 2631 * 2632 * Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns 2633 * a LIKE clause that searches for subpages of 'My page title'. 2634 * Alternatively: 2635 * $pattern = array( 'My_page_title/', $dbr->anyString() ); 2636 * $query .= $dbr->buildLike( $pattern ); 2637 * 2638 * @since 1.16 2639 * @return string Fully built LIKE statement 2640 */ 2641 public function buildLike() { 2642 $params = func_get_args(); 2643 2644 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 2645 $params = $params[0]; 2646 } 2647 2648 $s = ''; 2649 2650 foreach ( $params as $value ) { 2651 if ( $value instanceof LikeMatch ) { 2652 $s .= $value->toString(); 2653 } else { 2654 $s .= $this->escapeLikeInternal( $value ); 2655 } 2656 } 2657 2658 return " LIKE {$this->addQuotes( $s )} "; 2659 } 2660 2661 /** 2662 * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query 2663 * 2664 * @return LikeMatch 2665 */ 2666 public function anyChar() { 2667 return new LikeMatch( '_' ); 2668 } 2669 2670 /** 2671 * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query 2672 * 2673 * @return LikeMatch 2674 */ 2675 public function anyString() { 2676 return new LikeMatch( '%' ); 2677 } 2678 2679 /** 2680 * Returns an appropriately quoted sequence value for inserting a new row. 2681 * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL 2682 * subclass will return an integer, and save the value for insertId() 2683 * 2684 * Any implementation of this function should *not* involve reusing 2685 * sequence numbers created for rolled-back transactions. 2686 * See http://bugs.mysql.com/bug.php?id=30767 for details. 2687 * @param string $seqName 2688 * @return null|int 2689 */ 2690 public function nextSequenceValue( $seqName ) { 2691 return null; 2692 } 2693 2694 /** 2695 * USE INDEX clause. Unlikely to be useful for anything but MySQL. This 2696 * is only needed because a) MySQL must be as efficient as possible due to 2697 * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about 2698 * which index to pick. Anyway, other databases might have different 2699 * indexes on a given table. So don't bother overriding this unless you're 2700 * MySQL. 2701 * @param string $index 2702 * @return string 2703 */ 2704 public function useIndexClause( $index ) { 2705 return ''; 2706 } 2707 2708 /** 2709 * REPLACE query wrapper. 2710 * 2711 * REPLACE is a very handy MySQL extension, which functions like an INSERT 2712 * except that when there is a duplicate key error, the old row is deleted 2713 * and the new row is inserted in its place. 2714 * 2715 * We simulate this with standard SQL with a DELETE followed by INSERT. To 2716 * perform the delete, we need to know what the unique indexes are so that 2717 * we know how to find the conflicting rows. 2718 * 2719 * It may be more efficient to leave off unique indexes which are unlikely 2720 * to collide. However if you do this, you run the risk of encountering 2721 * errors which wouldn't have occurred in MySQL. 2722 * 2723 * @param string $table The table to replace the row(s) in. 2724 * @param array $uniqueIndexes Is an array of indexes. Each element may be either 2725 * a field name or an array of field names 2726 * @param array $rows Can be either a single row to insert, or multiple rows, 2727 * in the same format as for DatabaseBase::insert() 2728 * @param string $fname Calling function name (use __METHOD__) for logs/profiling 2729 */ 2730 public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { 2731 $quotedTable = $this->tableName( $table ); 2732 2733 if ( count( $rows ) == 0 ) { 2734 return; 2735 } 2736 2737 # Single row case 2738 if ( !is_array( reset( $rows ) ) ) { 2739 $rows = array( $rows ); 2740 } 2741 2742 foreach ( $rows as $row ) { 2743 # Delete rows which collide 2744 if ( $uniqueIndexes ) { 2745 $sql = "DELETE FROM $quotedTable WHERE "; 2746 $first = true; 2747 foreach ( $uniqueIndexes as $index ) { 2748 if ( $first ) { 2749 $first = false; 2750 $sql .= '( '; 2751 } else { 2752 $sql .= ' ) OR ( '; 2753 } 2754 if ( is_array( $index ) ) { 2755 $first2 = true; 2756 foreach ( $index as $col ) { 2757 if ( $first2 ) { 2758 $first2 = false; 2759 } else { 2760 $sql .= ' AND '; 2761 } 2762 $sql .= $col . '=' . $this->addQuotes( $row[$col] ); 2763 } 2764 } else { 2765 $sql .= $index . '=' . $this->addQuotes( $row[$index] ); 2766 } 2767 } 2768 $sql .= ' )'; 2769 $this->query( $sql, $fname ); 2770 } 2771 2772 # Now insert the row 2773 $this->insert( $table, $row, $fname ); 2774 } 2775 } 2776 2777 /** 2778 * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE 2779 * statement. 2780 * 2781 * @param string $table Table name 2782 * @param array|string $rows Row(s) to insert 2783 * @param string $fname Caller function name 2784 * 2785 * @return ResultWrapper 2786 */ 2787 protected function nativeReplace( $table, $rows, $fname ) { 2788 $table = $this->tableName( $table ); 2789 2790 # Single row case 2791 if ( !is_array( reset( $rows ) ) ) { 2792 $rows = array( $rows ); 2793 } 2794 2795 $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES '; 2796 $first = true; 2797 2798 foreach ( $rows as $row ) { 2799 if ( $first ) { 2800 $first = false; 2801 } else { 2802 $sql .= ','; 2803 } 2804 2805 $sql .= '(' . $this->makeList( $row ) . ')'; 2806 } 2807 2808 return $this->query( $sql, $fname ); 2809 } 2810 2811 /** 2812 * INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table. 2813 * 2814 * This updates any conflicting rows (according to the unique indexes) using 2815 * the provided SET clause and inserts any remaining (non-conflicted) rows. 2816 * 2817 * $rows may be either: 2818 * - A single associative array. The array keys are the field names, and 2819 * the values are the values to insert. The values are treated as data 2820 * and will be quoted appropriately. If NULL is inserted, this will be 2821 * converted to a database NULL. 2822 * - An array with numeric keys, holding a list of associative arrays. 2823 * This causes a multi-row INSERT on DBMSs that support it. The keys in 2824 * each subarray must be identical to each other, and in the same order. 2825 * 2826 * It may be more efficient to leave off unique indexes which are unlikely 2827 * to collide. However if you do this, you run the risk of encountering 2828 * errors which wouldn't have occurred in MySQL. 2829 * 2830 * Usually throws a DBQueryError on failure. If errors are explicitly ignored, 2831 * returns success. 2832 * 2833 * @since 1.22 2834 * 2835 * @param string $table Table name. This will be passed through DatabaseBase::tableName(). 2836 * @param array $rows A single row or list of rows to insert 2837 * @param array $uniqueIndexes List of single field names or field name tuples 2838 * @param array $set An array of values to SET. For each array element, the 2839 * key gives the field name, and the value gives the data to set that 2840 * field to. The data will be quoted by DatabaseBase::addQuotes(). 2841 * @param string $fname Calling function name (use __METHOD__) for logs/profiling 2842 * @throws Exception 2843 * @return bool 2844 */ 2845 public function upsert( $table, array $rows, array $uniqueIndexes, array $set, 2846 $fname = __METHOD__ 2847 ) { 2848 if ( !count( $rows ) ) { 2849 return true; // nothing to do 2850 } 2851 2852 if ( !is_array( reset( $rows ) ) ) { 2853 $rows = array( $rows ); 2854 } 2855 2856 if ( count( $uniqueIndexes ) ) { 2857 $clauses = array(); // list WHERE clauses that each identify a single row 2858 foreach ( $rows as $row ) { 2859 foreach ( $uniqueIndexes as $index ) { 2860 $index = is_array( $index ) ? $index : array( $index ); // columns 2861 $rowKey = array(); // unique key to this row 2862 foreach ( $index as $column ) { 2863 $rowKey[$column] = $row[$column]; 2864 } 2865 $clauses[] = $this->makeList( $rowKey, LIST_AND ); 2866 } 2867 } 2868 $where = array( $this->makeList( $clauses, LIST_OR ) ); 2869 } else { 2870 $where = false; 2871 } 2872 2873 $useTrx = !$this->mTrxLevel; 2874 if ( $useTrx ) { 2875 $this->begin( $fname ); 2876 } 2877 try { 2878 # Update any existing conflicting row(s) 2879 if ( $where !== false ) { 2880 $ok = $this->update( $table, $set, $where, $fname ); 2881 } else { 2882 $ok = true; 2883 } 2884 # Now insert any non-conflicting row(s) 2885 $ok = $this->insert( $table, $rows, $fname, array( 'IGNORE' ) ) && $ok; 2886 } catch ( Exception $e ) { 2887 if ( $useTrx ) { 2888 $this->rollback( $fname ); 2889 } 2890 throw $e; 2891 } 2892 if ( $useTrx ) { 2893 $this->commit( $fname ); 2894 } 2895 2896 return $ok; 2897 } 2898 2899 /** 2900 * DELETE where the condition is a join. 2901 * 2902 * MySQL overrides this to use a multi-table DELETE syntax, in other databases 2903 * we use sub-selects 2904 * 2905 * For safety, an empty $conds will not delete everything. If you want to 2906 * delete all rows where the join condition matches, set $conds='*'. 2907 * 2908 * DO NOT put the join condition in $conds. 2909 * 2910 * @param string $delTable The table to delete from. 2911 * @param string $joinTable The other table. 2912 * @param string $delVar The variable to join on, in the first table. 2913 * @param string $joinVar The variable to join on, in the second table. 2914 * @param array $conds Condition array of field names mapped to variables, 2915 * ANDed together in the WHERE clause 2916 * @param string $fname Calling function name (use __METHOD__) for logs/profiling 2917 * @throws DBUnexpectedError 2918 */ 2919 public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, 2920 $fname = __METHOD__ 2921 ) { 2922 if ( !$conds ) { 2923 throw new DBUnexpectedError( $this, 2924 'DatabaseBase::deleteJoin() called with empty $conds' ); 2925 } 2926 2927 $delTable = $this->tableName( $delTable ); 2928 $joinTable = $this->tableName( $joinTable ); 2929 $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; 2930 if ( $conds != '*' ) { 2931 $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); 2932 } 2933 $sql .= ')'; 2934 2935 $this->query( $sql, $fname ); 2936 } 2937 2938 /** 2939 * Returns the size of a text field, or -1 for "unlimited" 2940 * 2941 * @param string $table 2942 * @param string $field 2943 * @return int 2944 */ 2945 public function textFieldSize( $table, $field ) { 2946 $table = $this->tableName( $table ); 2947 $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";"; 2948 $res = $this->query( $sql, 'DatabaseBase::textFieldSize' ); 2949 $row = $this->fetchObject( $res ); 2950 2951 $m = array(); 2952 2953 if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) { 2954 $size = $m[1]; 2955 } else { 2956 $size = -1; 2957 } 2958 2959 return $size; 2960 } 2961 2962 /** 2963 * A string to insert into queries to show that they're low-priority, like 2964 * MySQL's LOW_PRIORITY. If no such feature exists, return an empty 2965 * string and nothing bad should happen. 2966 * 2967 * @return string Returns the text of the low priority option if it is 2968 * supported, or a blank string otherwise 2969 */ 2970 public function lowPriorityOption() { 2971 return ''; 2972 } 2973 2974 /** 2975 * DELETE query wrapper. 2976 * 2977 * @param array $table Table name 2978 * @param string|array $conds Array of conditions. See $conds in DatabaseBase::select() 2979 * for the format. Use $conds == "*" to delete all rows 2980 * @param string $fname Name of the calling function 2981 * @throws DBUnexpectedError 2982 * @return bool|ResultWrapper 2983 */ 2984 public function delete( $table, $conds, $fname = __METHOD__ ) { 2985 if ( !$conds ) { 2986 throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' ); 2987 } 2988 2989 $table = $this->tableName( $table ); 2990 $sql = "DELETE FROM $table"; 2991 2992 if ( $conds != '*' ) { 2993 if ( is_array( $conds ) ) { 2994 $conds = $this->makeList( $conds, LIST_AND ); 2995 } 2996 $sql .= ' WHERE ' . $conds; 2997 } 2998 2999 return $this->query( $sql, $fname ); 3000 } 3001 3002 /** 3003 * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it 3004 * into another table. 3005 * 3006 * @param string $destTable The table name to insert into 3007 * @param string|array $srcTable May be either a table name, or an array of table names 3008 * to include in a join. 3009 * 3010 * @param array $varMap Must be an associative array of the form 3011 * array( 'dest1' => 'source1', ...). Source items may be literals 3012 * rather than field names, but strings should be quoted with 3013 * DatabaseBase::addQuotes() 3014 * 3015 * @param array $conds Condition array. See $conds in DatabaseBase::select() for 3016 * the details of the format of condition arrays. May be "*" to copy the 3017 * whole table. 3018 * 3019 * @param string $fname The function name of the caller, from __METHOD__ 3020 * 3021 * @param array $insertOptions Options for the INSERT part of the query, see 3022 * DatabaseBase::insert() for details. 3023 * @param array $selectOptions Options for the SELECT part of the query, see 3024 * DatabaseBase::select() for details. 3025 * 3026 * @return ResultWrapper 3027 */ 3028 public function insertSelect( $destTable, $srcTable, $varMap, $conds, 3029 $fname = __METHOD__, 3030 $insertOptions = array(), $selectOptions = array() 3031 ) { 3032 $destTable = $this->tableName( $destTable ); 3033 3034 if ( !is_array( $insertOptions ) ) { 3035 $insertOptions = array( $insertOptions ); 3036 } 3037 3038 $insertOptions = $this->makeInsertOptions( $insertOptions ); 3039 3040 if ( !is_array( $selectOptions ) ) { 3041 $selectOptions = array( $selectOptions ); 3042 } 3043 3044 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 3045 3046 if ( is_array( $srcTable ) ) { 3047 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 3048 } else { 3049 $srcTable = $this->tableName( $srcTable ); 3050 } 3051 3052 $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 3053 " SELECT $startOpts " . implode( ',', $varMap ) . 3054 " FROM $srcTable $useIndex "; 3055 3056 if ( $conds != '*' ) { 3057 if ( is_array( $conds ) ) { 3058 $conds = $this->makeList( $conds, LIST_AND ); 3059 } 3060 $sql .= " WHERE $conds"; 3061 } 3062 3063 $sql .= " $tailOpts"; 3064 3065 return $this->query( $sql, $fname ); 3066 } 3067 3068 /** 3069 * Construct a LIMIT query with optional offset. This is used for query 3070 * pages. The SQL should be adjusted so that only the first $limit rows 3071 * are returned. If $offset is provided as well, then the first $offset 3072 * rows should be discarded, and the next $limit rows should be returned. 3073 * If the result of the query is not ordered, then the rows to be returned 3074 * are theoretically arbitrary. 3075 * 3076 * $sql is expected to be a SELECT, if that makes a difference. 3077 * 3078 * The version provided by default works in MySQL and SQLite. It will very 3079 * likely need to be overridden for most other DBMSes. 3080 * 3081 * @param string $sql SQL query we will append the limit too 3082 * @param int $limit The SQL limit 3083 * @param int|bool $offset The SQL offset (default false) 3084 * @throws DBUnexpectedError 3085 * @return string 3086 */ 3087 public function limitResult( $sql, $limit, $offset = false ) { 3088 if ( !is_numeric( $limit ) ) { 3089 throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); 3090 } 3091 3092 return "$sql LIMIT " 3093 . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" ) 3094 . "{$limit} "; 3095 } 3096 3097 /** 3098 * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries 3099 * within the UNION construct. 3100 * @return bool 3101 */ 3102 public function unionSupportsOrderAndLimit() { 3103 return true; // True for almost every DB supported 3104 } 3105 3106 /** 3107 * Construct a UNION query 3108 * This is used for providing overload point for other DB abstractions 3109 * not compatible with the MySQL syntax. 3110 * @param array $sqls SQL statements to combine 3111 * @param bool $all Use UNION ALL 3112 * @return string SQL fragment 3113 */ 3114 public function unionQueries( $sqls, $all ) { 3115 $glue = $all ? ') UNION ALL (' : ') UNION ('; 3116 3117 return '(' . implode( $glue, $sqls ) . ')'; 3118 } 3119 3120 /** 3121 * Returns an SQL expression for a simple conditional. This doesn't need 3122 * to be overridden unless CASE isn't supported in your DBMS. 3123 * 3124 * @param string|array $cond SQL expression which will result in a boolean value 3125 * @param string $trueVal SQL expression to return if true 3126 * @param string $falseVal SQL expression to return if false 3127 * @return string SQL fragment 3128 */ 3129 public function conditional( $cond, $trueVal, $falseVal ) { 3130 if ( is_array( $cond ) ) { 3131 $cond = $this->makeList( $cond, LIST_AND ); 3132 } 3133 3134 return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; 3135 } 3136 3137 /** 3138 * Returns a comand for str_replace function in SQL query. 3139 * Uses REPLACE() in MySQL 3140 * 3141 * @param string $orig Column to modify 3142 * @param string $old Column to seek 3143 * @param string $new Column to replace with 3144 * 3145 * @return string 3146 */ 3147 public function strreplace( $orig, $old, $new ) { 3148 return "REPLACE({$orig}, {$old}, {$new})"; 3149 } 3150 3151 /** 3152 * Determines how long the server has been up 3153 * STUB 3154 * 3155 * @return int 3156 */ 3157 public function getServerUptime() { 3158 return 0; 3159 } 3160 3161 /** 3162 * Determines if the last failure was due to a deadlock 3163 * STUB 3164 * 3165 * @return bool 3166 */ 3167 public function wasDeadlock() { 3168 return false; 3169 } 3170 3171 /** 3172 * Determines if the last failure was due to a lock timeout 3173 * STUB 3174 * 3175 * @return bool 3176 */ 3177 public function wasLockTimeout() { 3178 return false; 3179 } 3180 3181 /** 3182 * Determines if the last query error was something that should be dealt 3183 * with by pinging the connection and reissuing the query. 3184 * STUB 3185 * 3186 * @return bool 3187 */ 3188 public function wasErrorReissuable() { 3189 return false; 3190 } 3191 3192 /** 3193 * Determines if the last failure was due to the database being read-only. 3194 * STUB 3195 * 3196 * @return bool 3197 */ 3198 public function wasReadOnlyError() { 3199 return false; 3200 } 3201 3202 /** 3203 * Perform a deadlock-prone transaction. 3204 * 3205 * This function invokes a callback function to perform a set of write 3206 * queries. If a deadlock occurs during the processing, the transaction 3207 * will be rolled back and the callback function will be called again. 3208 * 3209 * Usage: 3210 * $dbw->deadlockLoop( callback, ... ); 3211 * 3212 * Extra arguments are passed through to the specified callback function. 3213 * 3214 * Returns whatever the callback function returned on its successful, 3215 * iteration, or false on error, for example if the retry limit was 3216 * reached. 3217 * 3218 * @return bool 3219 */ 3220 public function deadlockLoop() { 3221 $this->begin( __METHOD__ ); 3222 $args = func_get_args(); 3223 $function = array_shift( $args ); 3224 $oldIgnore = $this->ignoreErrors( true ); 3225 $tries = self::DEADLOCK_TRIES; 3226 3227 if ( is_array( $function ) ) { 3228 $fname = $function[0]; 3229 } else { 3230 $fname = $function; 3231 } 3232 3233 do { 3234 $retVal = call_user_func_array( $function, $args ); 3235 $error = $this->lastError(); 3236 $errno = $this->lastErrno(); 3237 $sql = $this->lastQuery(); 3238 3239 if ( $errno ) { 3240 if ( $this->wasDeadlock() ) { 3241 # Retry 3242 usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) ); 3243 } else { 3244 $this->reportQueryError( $error, $errno, $sql, $fname ); 3245 } 3246 } 3247 } while ( $this->wasDeadlock() && --$tries > 0 ); 3248 3249 $this->ignoreErrors( $oldIgnore ); 3250 3251 if ( $tries <= 0 ) { 3252 $this->rollback( __METHOD__ ); 3253 $this->reportQueryError( $error, $errno, $sql, $fname ); 3254 3255 return false; 3256 } else { 3257 $this->commit( __METHOD__ ); 3258 3259 return $retVal; 3260 } 3261 } 3262 3263 /** 3264 * Wait for the slave to catch up to a given master position. 3265 * 3266 * @param DBMasterPos $pos 3267 * @param int $timeout The maximum number of seconds to wait for 3268 * synchronisation 3269 * @return int Zero if the slave was past that position already, 3270 * greater than zero if we waited for some period of time, less than 3271 * zero if we timed out. 3272 */ 3273 public function masterPosWait( DBMasterPos $pos, $timeout ) { 3274 # Real waits are implemented in the subclass. 3275 return 0; 3276 } 3277 3278 /** 3279 * Get the replication position of this slave 3280 * 3281 * @return DBMasterPos|bool False if this is not a slave. 3282 */ 3283 public function getSlavePos() { 3284 # Stub 3285 return false; 3286 } 3287 3288 /** 3289 * Get the position of this master 3290 * 3291 * @return DBMasterPos|bool False if this is not a master 3292 */ 3293 public function getMasterPos() { 3294 # Stub 3295 return false; 3296 } 3297 3298 /** 3299 * Run an anonymous function as soon as there is no transaction pending. 3300 * If there is a transaction and it is rolled back, then the callback is cancelled. 3301 * Queries in the function will run in AUTO-COMMIT mode unless there are begin() calls. 3302 * Callbacks must commit any transactions that they begin. 3303 * 3304 * This is useful for updates to different systems or when separate transactions are needed. 3305 * For example, one might want to enqueue jobs into a system outside the database, but only 3306 * after the database is updated so that the jobs will see the data when they actually run. 3307 * It can also be used for updates that easily cause deadlocks if locks are held too long. 3308 * 3309 * @param callable $callback 3310 * @since 1.20 3311 */ 3312 final public function onTransactionIdle( $callback ) { 3313 $this->mTrxIdleCallbacks[] = array( $callback, wfGetCaller() ); 3314 if ( !$this->mTrxLevel ) { 3315 $this->runOnTransactionIdleCallbacks(); 3316 } 3317 } 3318 3319 /** 3320 * Run an anonymous function before the current transaction commits or now if there is none. 3321 * If there is a transaction and it is rolled back, then the callback is cancelled. 3322 * Callbacks must not start nor commit any transactions. 3323 * 3324 * This is useful for updates that easily cause deadlocks if locks are held too long 3325 * but where atomicity is strongly desired for these updates and some related updates. 3326 * 3327 * @param callable $callback 3328 * @since 1.22 3329 */ 3330 final public function onTransactionPreCommitOrIdle( $callback ) { 3331 if ( $this->mTrxLevel ) { 3332 $this->mTrxPreCommitCallbacks[] = array( $callback, wfGetCaller() ); 3333 } else { 3334 $this->onTransactionIdle( $callback ); // this will trigger immediately 3335 } 3336 } 3337 3338 /** 3339 * Actually any "on transaction idle" callbacks. 3340 * 3341 * @since 1.20 3342 */ 3343 protected function runOnTransactionIdleCallbacks() { 3344 $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled? 3345 3346 $e = $ePrior = null; // last exception 3347 do { // callbacks may add callbacks :) 3348 $callbacks = $this->mTrxIdleCallbacks; 3349 $this->mTrxIdleCallbacks = array(); // recursion guard 3350 foreach ( $callbacks as $callback ) { 3351 try { 3352 list( $phpCallback ) = $callback; 3353 $this->clearFlag( DBO_TRX ); // make each query its own transaction 3354 call_user_func( $phpCallback ); 3355 $this->setFlag( $autoTrx ? DBO_TRX : 0 ); // restore automatic begin() 3356 } catch ( Exception $e ) { 3357 if ( $ePrior ) { 3358 MWExceptionHandler::logException( $ePrior ); 3359 } 3360 $ePrior = $e; 3361 } 3362 } 3363 } while ( count( $this->mTrxIdleCallbacks ) ); 3364 3365 if ( $e instanceof Exception ) { 3366 throw $e; // re-throw any last exception 3367 } 3368 } 3369 3370 /** 3371 * Actually any "on transaction pre-commit" callbacks. 3372 * 3373 * @since 1.22 3374 */ 3375 protected function runOnTransactionPreCommitCallbacks() { 3376 $e = $ePrior = null; // last exception 3377 do { // callbacks may add callbacks :) 3378 $callbacks = $this->mTrxPreCommitCallbacks; 3379 $this->mTrxPreCommitCallbacks = array(); // recursion guard 3380 foreach ( $callbacks as $callback ) { 3381 try { 3382 list( $phpCallback ) = $callback; 3383 call_user_func( $phpCallback ); 3384 } catch ( Exception $e ) { 3385 if ( $ePrior ) { 3386 MWExceptionHandler::logException( $ePrior ); 3387 } 3388 $ePrior = $e; 3389 } 3390 } 3391 } while ( count( $this->mTrxPreCommitCallbacks ) ); 3392 3393 if ( $e instanceof Exception ) { 3394 throw $e; // re-throw any last exception 3395 } 3396 } 3397 3398 /** 3399 * Begin an atomic section of statements 3400 * 3401 * If a transaction has been started already, just keep track of the given 3402 * section name to make sure the transaction is not committed pre-maturely. 3403 * This function can be used in layers (with sub-sections), so use a stack 3404 * to keep track of the different atomic sections. If there is no transaction, 3405 * start one implicitly. 3406 * 3407 * The goal of this function is to create an atomic section of SQL queries 3408 * without having to start a new transaction if it already exists. 3409 * 3410 * Atomic sections are more strict than transactions. With transactions, 3411 * attempting to begin a new transaction when one is already running results 3412 * in MediaWiki issuing a brief warning and doing an implicit commit. All 3413 * atomic levels *must* be explicitly closed using DatabaseBase::endAtomic(), 3414 * and any database transactions cannot be began or committed until all atomic 3415 * levels are closed. There is no such thing as implicitly opening or closing 3416 * an atomic section. 3417 * 3418 * @since 1.23 3419 * @param string $fname 3420 * @throws DBError 3421 */ 3422 final public function startAtomic( $fname = __METHOD__ ) { 3423 if ( !$this->mTrxLevel ) { 3424 $this->begin( $fname ); 3425 $this->mTrxAutomatic = true; 3426 $this->mTrxAutomaticAtomic = true; 3427 } 3428 3429 $this->mTrxAtomicLevels->push( $fname ); 3430 } 3431 3432 /** 3433 * Ends an atomic section of SQL statements 3434 * 3435 * Ends the next section of atomic SQL statements and commits the transaction 3436 * if necessary. 3437 * 3438 * @since 1.23 3439 * @see DatabaseBase::startAtomic 3440 * @param string $fname 3441 * @throws DBError 3442 */ 3443 final public function endAtomic( $fname = __METHOD__ ) { 3444 if ( !$this->mTrxLevel ) { 3445 throw new DBUnexpectedError( $this, 'No atomic transaction is open.' ); 3446 } 3447 if ( $this->mTrxAtomicLevels->isEmpty() || 3448 $this->mTrxAtomicLevels->pop() !== $fname 3449 ) { 3450 throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' ); 3451 } 3452 3453 if ( $this->mTrxAtomicLevels->isEmpty() && $this->mTrxAutomaticAtomic ) { 3454 $this->commit( $fname, 'flush' ); 3455 } 3456 } 3457 3458 /** 3459 * Begin a transaction. If a transaction is already in progress, 3460 * that transaction will be committed before the new transaction is started. 3461 * 3462 * Note that when the DBO_TRX flag is set (which is usually the case for web 3463 * requests, but not for maintenance scripts), any previous database query 3464 * will have started a transaction automatically. 3465 * 3466 * Nesting of transactions is not supported. Attempts to nest transactions 3467 * will cause a warning, unless the current transaction was started 3468 * automatically because of the DBO_TRX flag. 3469 * 3470 * @param string $fname 3471 * @throws DBError 3472 */ 3473 final public function begin( $fname = __METHOD__ ) { 3474 global $wgDebugDBTransactions; 3475 3476 if ( $this->mTrxLevel ) { // implicit commit 3477 if ( !$this->mTrxAtomicLevels->isEmpty() ) { 3478 // If the current transaction was an automatic atomic one, then we definitely have 3479 // a problem. Same if there is any unclosed atomic level. 3480 throw new DBUnexpectedError( $this, 3481 "Attempted to start explicit transaction when atomic levels are still open." 3482 ); 3483 } elseif ( !$this->mTrxAutomatic ) { 3484 // We want to warn about inadvertently nested begin/commit pairs, but not about 3485 // auto-committing implicit transactions that were started by query() via DBO_TRX 3486 $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " . 3487 " performing implicit commit!"; 3488 wfWarn( $msg ); 3489 wfLogDBError( $msg ); 3490 } else { 3491 // if the transaction was automatic and has done write operations, 3492 // log it if $wgDebugDBTransactions is enabled. 3493 if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) { 3494 wfDebug( "$fname: Automatic transaction with writes in progress" . 3495 " (from {$this->mTrxFname}), performing implicit commit!\n" 3496 ); 3497 } 3498 } 3499 3500 $this->runOnTransactionPreCommitCallbacks(); 3501 $this->doCommit( $fname ); 3502 if ( $this->mTrxDoneWrites ) { 3503 Profiler::instance()->transactionWritingOut( 3504 $this->mServer, $this->mDBname, $this->mTrxShortId ); 3505 } 3506 $this->runOnTransactionIdleCallbacks(); 3507 } 3508 3509 # Avoid fatals if close() was called 3510 if ( !$this->isOpen() ) { 3511 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 3512 } 3513 3514 $this->doBegin( $fname ); 3515 $this->mTrxFname = $fname; 3516 $this->mTrxDoneWrites = false; 3517 $this->mTrxAutomatic = false; 3518 $this->mTrxAutomaticAtomic = false; 3519 $this->mTrxAtomicLevels = new SplStack; 3520 $this->mTrxIdleCallbacks = array(); 3521 $this->mTrxPreCommitCallbacks = array(); 3522 $this->mTrxShortId = wfRandomString( 12 ); 3523 } 3524 3525 /** 3526 * Issues the BEGIN command to the database server. 3527 * 3528 * @see DatabaseBase::begin() 3529 * @param string $fname 3530 */ 3531 protected function doBegin( $fname ) { 3532 $this->query( 'BEGIN', $fname ); 3533 $this->mTrxLevel = 1; 3534 } 3535 3536 /** 3537 * Commits a transaction previously started using begin(). 3538 * If no transaction is in progress, a warning is issued. 3539 * 3540 * Nesting of transactions is not supported. 3541 * 3542 * @param string $fname 3543 * @param string $flush Flush flag, set to 'flush' to disable warnings about 3544 * explicitly committing implicit transactions, or calling commit when no 3545 * transaction is in progress. This will silently break any ongoing 3546 * explicit transaction. Only set the flush flag if you are sure that it 3547 * is safe to ignore these warnings in your context. 3548 * @throws DBUnexpectedError 3549 */ 3550 final public function commit( $fname = __METHOD__, $flush = '' ) { 3551 if ( !$this->mTrxAtomicLevels->isEmpty() ) { 3552 // There are still atomic sections open. This cannot be ignored 3553 throw new DBUnexpectedError( 3554 $this, 3555 "Attempted to commit transaction while atomic sections are still open" 3556 ); 3557 } 3558 3559 if ( $flush === 'flush' ) { 3560 if ( !$this->mTrxLevel ) { 3561 return; // nothing to do 3562 } elseif ( !$this->mTrxAutomatic ) { 3563 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 3564 } 3565 } else { 3566 if ( !$this->mTrxLevel ) { 3567 wfWarn( "$fname: No transaction to commit, something got out of sync!" ); 3568 return; // nothing to do 3569 } elseif ( $this->mTrxAutomatic ) { 3570 wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" ); 3571 } 3572 } 3573 3574 # Avoid fatals if close() was called 3575 if ( !$this->isOpen() ) { 3576 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 3577 } 3578 3579 $this->runOnTransactionPreCommitCallbacks(); 3580 $this->doCommit( $fname ); 3581 if ( $this->mTrxDoneWrites ) { 3582 Profiler::instance()->transactionWritingOut( 3583 $this->mServer, $this->mDBname, $this->mTrxShortId ); 3584 } 3585 $this->runOnTransactionIdleCallbacks(); 3586 } 3587 3588 /** 3589 * Issues the COMMIT command to the database server. 3590 * 3591 * @see DatabaseBase::commit() 3592 * @param string $fname 3593 */ 3594 protected function doCommit( $fname ) { 3595 if ( $this->mTrxLevel ) { 3596 $this->query( 'COMMIT', $fname ); 3597 $this->mTrxLevel = 0; 3598 } 3599 } 3600 3601 /** 3602 * Rollback a transaction previously started using begin(). 3603 * If no transaction is in progress, a warning is issued. 3604 * 3605 * No-op on non-transactional databases. 3606 * 3607 * @param string $fname 3608 * @param string $flush Flush flag, set to 'flush' to disable warnings about 3609 * calling rollback when no transaction is in progress. This will silently 3610 * break any ongoing explicit transaction. Only set the flush flag if you 3611 * are sure that it is safe to ignore these warnings in your context. 3612 * @since 1.23 Added $flush parameter 3613 */ 3614 final public function rollback( $fname = __METHOD__, $flush = '' ) { 3615 if ( $flush !== 'flush' ) { 3616 if ( !$this->mTrxLevel ) { 3617 wfWarn( "$fname: No transaction to rollback, something got out of sync!" ); 3618 return; // nothing to do 3619 } elseif ( $this->mTrxAutomatic ) { 3620 wfWarn( "$fname: Explicit rollback of implicit transaction. Something may be out of sync!" ); 3621 } 3622 } else { 3623 if ( !$this->mTrxLevel ) { 3624 return; // nothing to do 3625 } elseif ( !$this->mTrxAutomatic ) { 3626 wfWarn( "$fname: Flushing an explicit transaction, getting out of sync!" ); 3627 } 3628 } 3629 3630 # Avoid fatals if close() was called 3631 if ( !$this->isOpen() ) { 3632 throw new DBUnexpectedError( $this, "DB connection was already closed." ); 3633 } 3634 3635 $this->doRollback( $fname ); 3636 $this->mTrxIdleCallbacks = array(); // cancel 3637 $this->mTrxPreCommitCallbacks = array(); // cancel 3638 $this->mTrxAtomicLevels = new SplStack; 3639 if ( $this->mTrxDoneWrites ) { 3640 Profiler::instance()->transactionWritingOut( 3641 $this->mServer, $this->mDBname, $this->mTrxShortId ); 3642 } 3643 } 3644 3645 /** 3646 * Issues the ROLLBACK command to the database server. 3647 * 3648 * @see DatabaseBase::rollback() 3649 * @param string $fname 3650 */ 3651 protected function doRollback( $fname ) { 3652 if ( $this->mTrxLevel ) { 3653 $this->query( 'ROLLBACK', $fname, true ); 3654 $this->mTrxLevel = 0; 3655 } 3656 } 3657 3658 /** 3659 * Creates a new table with structure copied from existing table 3660 * Note that unlike most database abstraction functions, this function does not 3661 * automatically append database prefix, because it works at a lower 3662 * abstraction level. 3663 * The table names passed to this function shall not be quoted (this 3664 * function calls addIdentifierQuotes when needed). 3665 * 3666 * @param string $oldName Name of table whose structure should be copied 3667 * @param string $newName Name of table to be created 3668 * @param bool $temporary Whether the new table should be temporary 3669 * @param string $fname Calling function name 3670 * @throws MWException 3671 * @return bool True if operation was successful 3672 */ 3673 public function duplicateTableStructure( $oldName, $newName, $temporary = false, 3674 $fname = __METHOD__ 3675 ) { 3676 throw new MWException( 3677 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' ); 3678 } 3679 3680 /** 3681 * List all tables on the database 3682 * 3683 * @param string $prefix Only show tables with this prefix, e.g. mw_ 3684 * @param string $fname Calling function name 3685 * @throws MWException 3686 */ 3687 function listTables( $prefix = null, $fname = __METHOD__ ) { 3688 throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' ); 3689 } 3690 3691 /** 3692 * Reset the views process cache set by listViews() 3693 * @since 1.22 3694 */ 3695 final public function clearViewsCache() { 3696 $this->allViews = null; 3697 } 3698 3699 /** 3700 * Lists all the VIEWs in the database 3701 * 3702 * For caching purposes the list of all views should be stored in 3703 * $this->allViews. The process cache can be cleared with clearViewsCache() 3704 * 3705 * @param string $prefix Only show VIEWs with this prefix, eg. unit_test_ 3706 * @param string $fname Name of calling function 3707 * @throws MWException 3708 * @since 1.22 3709 */ 3710 public function listViews( $prefix = null, $fname = __METHOD__ ) { 3711 throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' ); 3712 } 3713 3714 /** 3715 * Differentiates between a TABLE and a VIEW 3716 * 3717 * @param string $name Name of the database-structure to test. 3718 * @throws MWException 3719 * @since 1.22 3720 */ 3721 public function isView( $name ) { 3722 throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' ); 3723 } 3724 3725 /** 3726 * Convert a timestamp in one of the formats accepted by wfTimestamp() 3727 * to the format used for inserting into timestamp fields in this DBMS. 3728 * 3729 * The result is unquoted, and needs to be passed through addQuotes() 3730 * before it can be included in raw SQL. 3731 * 3732 * @param string|int $ts 3733 * 3734 * @return string 3735 */ 3736 public function timestamp( $ts = 0 ) { 3737 return wfTimestamp( TS_MW, $ts ); 3738 } 3739 3740 /** 3741 * Convert a timestamp in one of the formats accepted by wfTimestamp() 3742 * to the format used for inserting into timestamp fields in this DBMS. If 3743 * NULL is input, it is passed through, allowing NULL values to be inserted 3744 * into timestamp fields. 3745 * 3746 * The result is unquoted, and needs to be passed through addQuotes() 3747 * before it can be included in raw SQL. 3748 * 3749 * @param string|int $ts 3750 * 3751 * @return string 3752 */ 3753 public function timestampOrNull( $ts = null ) { 3754 if ( is_null( $ts ) ) { 3755 return null; 3756 } else { 3757 return $this->timestamp( $ts ); 3758 } 3759 } 3760 3761 /** 3762 * Take the result from a query, and wrap it in a ResultWrapper if 3763 * necessary. Boolean values are passed through as is, to indicate success 3764 * of write queries or failure. 3765 * 3766 * Once upon a time, DatabaseBase::query() returned a bare MySQL result 3767 * resource, and it was necessary to call this function to convert it to 3768 * a wrapper. Nowadays, raw database objects are never exposed to external 3769 * callers, so this is unnecessary in external code. For compatibility with 3770 * old code, ResultWrapper objects are passed through unaltered. 3771 * 3772 * @param bool|ResultWrapper|resource $result 3773 * @return bool|ResultWrapper 3774 */ 3775 public function resultObject( $result ) { 3776 if ( empty( $result ) ) { 3777 return false; 3778 } elseif ( $result instanceof ResultWrapper ) { 3779 return $result; 3780 } elseif ( $result === true ) { 3781 // Successful write query 3782 return $result; 3783 } else { 3784 return new ResultWrapper( $this, $result ); 3785 } 3786 } 3787 3788 /** 3789 * Ping the server and try to reconnect if it there is no connection 3790 * 3791 * @return bool Success or failure 3792 */ 3793 public function ping() { 3794 # Stub. Not essential to override. 3795 return true; 3796 } 3797 3798 /** 3799 * Get slave lag. Currently supported only by MySQL. 3800 * 3801 * Note that this function will generate a fatal error on many 3802 * installations. Most callers should use LoadBalancer::safeGetLag() 3803 * instead. 3804 * 3805 * @return int Database replication lag in seconds 3806 */ 3807 public function getLag() { 3808 return 0; 3809 } 3810 3811 /** 3812 * Return the maximum number of items allowed in a list, or 0 for unlimited. 3813 * 3814 * @return int 3815 */ 3816 function maxListLen() { 3817 return 0; 3818 } 3819 3820 /** 3821 * Some DBMSs have a special format for inserting into blob fields, they 3822 * don't allow simple quoted strings to be inserted. To insert into such 3823 * a field, pass the data through this function before passing it to 3824 * DatabaseBase::insert(). 3825 * 3826 * @param string $b 3827 * @return string 3828 */ 3829 public function encodeBlob( $b ) { 3830 return $b; 3831 } 3832 3833 /** 3834 * Some DBMSs return a special placeholder object representing blob fields 3835 * in result objects. Pass the object through this function to return the 3836 * original string. 3837 * 3838 * @param string $b 3839 * @return string 3840 */ 3841 public function decodeBlob( $b ) { 3842 return $b; 3843 } 3844 3845 /** 3846 * Override database's default behavior. $options include: 3847 * 'connTimeout' : Set the connection timeout value in seconds. 3848 * May be useful for very long batch queries such as 3849 * full-wiki dumps, where a single query reads out over 3850 * hours or days. 3851 * 3852 * @param array $options 3853 * @return void 3854 */ 3855 public function setSessionOptions( array $options ) { 3856 } 3857 3858 /** 3859 * Read and execute SQL commands from a file. 3860 * 3861 * Returns true on success, error string or exception on failure (depending 3862 * on object's error ignore settings). 3863 * 3864 * @param string $filename File name to open 3865 * @param bool|callable $lineCallback Optional function called before reading each line 3866 * @param bool|callable $resultCallback Optional function called for each MySQL result 3867 * @param bool|string $fname Calling function name or false if name should be 3868 * generated dynamically using $filename 3869 * @param bool|callable $inputCallback Optional function called for each 3870 * complete line sent 3871 * @throws Exception|MWException 3872 * @return bool|string 3873 */ 3874 public function sourceFile( 3875 $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false 3876 ) { 3877 wfSuppressWarnings(); 3878 $fp = fopen( $filename, 'r' ); 3879 wfRestoreWarnings(); 3880 3881 if ( false === $fp ) { 3882 throw new MWException( "Could not open \"{$filename}\".\n" ); 3883 } 3884 3885 if ( !$fname ) { 3886 $fname = __METHOD__ . "( $filename )"; 3887 } 3888 3889 try { 3890 $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback ); 3891 } catch ( MWException $e ) { 3892 fclose( $fp ); 3893 throw $e; 3894 } 3895 3896 fclose( $fp ); 3897 3898 return $error; 3899 } 3900 3901 /** 3902 * Get the full path of a patch file. Originally based on archive() 3903 * from updaters.inc. Keep in mind this always returns a patch, as 3904 * it fails back to MySQL if no DB-specific patch can be found 3905 * 3906 * @param string $patch The name of the patch, like patch-something.sql 3907 * @return string Full path to patch file 3908 */ 3909 public function patchPath( $patch ) { 3910 global $IP; 3911 3912 $dbType = $this->getType(); 3913 if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) { 3914 return "$IP/maintenance/$dbType/archives/$patch"; 3915 } else { 3916 return "$IP/maintenance/archives/$patch"; 3917 } 3918 } 3919 3920 /** 3921 * Set variables to be used in sourceFile/sourceStream, in preference to the 3922 * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at 3923 * all. If it's set to false, $GLOBALS will be used. 3924 * 3925 * @param bool|array $vars Mapping variable name to value. 3926 */ 3927 public function setSchemaVars( $vars ) { 3928 $this->mSchemaVars = $vars; 3929 } 3930 3931 /** 3932 * Read and execute commands from an open file handle. 3933 * 3934 * Returns true on success, error string or exception on failure (depending 3935 * on object's error ignore settings). 3936 * 3937 * @param resource $fp File handle 3938 * @param bool|callable $lineCallback Optional function called before reading each query 3939 * @param bool|callable $resultCallback Optional function called for each MySQL result 3940 * @param string $fname Calling function name 3941 * @param bool|callable $inputCallback Optional function called for each complete query sent 3942 * @return bool|string 3943 */ 3944 public function sourceStream( $fp, $lineCallback = false, $resultCallback = false, 3945 $fname = __METHOD__, $inputCallback = false 3946 ) { 3947 $cmd = ''; 3948 3949 while ( !feof( $fp ) ) { 3950 if ( $lineCallback ) { 3951 call_user_func( $lineCallback ); 3952 } 3953 3954 $line = trim( fgets( $fp ) ); 3955 3956 if ( $line == '' ) { 3957 continue; 3958 } 3959 3960 if ( '-' == $line[0] && '-' == $line[1] ) { 3961 continue; 3962 } 3963 3964 if ( $cmd != '' ) { 3965 $cmd .= ' '; 3966 } 3967 3968 $done = $this->streamStatementEnd( $cmd, $line ); 3969 3970 $cmd .= "$line\n"; 3971 3972 if ( $done || feof( $fp ) ) { 3973 $cmd = $this->replaceVars( $cmd ); 3974 3975 if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) { 3976 $res = $this->query( $cmd, $fname ); 3977 3978 if ( $resultCallback ) { 3979 call_user_func( $resultCallback, $res, $this ); 3980 } 3981 3982 if ( false === $res ) { 3983 $err = $this->lastError(); 3984 3985 return "Query \"{$cmd}\" failed with error code \"$err\".\n"; 3986 } 3987 } 3988 $cmd = ''; 3989 } 3990 } 3991 3992 return true; 3993 } 3994 3995 /** 3996 * Called by sourceStream() to check if we've reached a statement end 3997 * 3998 * @param string $sql SQL assembled so far 3999 * @param string $newLine New line about to be added to $sql 4000 * @return bool Whether $newLine contains end of the statement 4001 */ 4002 public function streamStatementEnd( &$sql, &$newLine ) { 4003 if ( $this->delimiter ) { 4004 $prev = $newLine; 4005 $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine ); 4006 if ( $newLine != $prev ) { 4007 return true; 4008 } 4009 } 4010 4011 return false; 4012 } 4013 4014 /** 4015 * Database independent variable replacement. Replaces a set of variables 4016 * in an SQL statement with their contents as given by $this->getSchemaVars(). 4017 * 4018 * Supports '{$var}' `{$var}` and / *$var* / (without the spaces) style variables. 4019 * 4020 * - '{$var}' should be used for text and is passed through the database's 4021 * addQuotes method. 4022 * - `{$var}` should be used for identifiers (eg: table and database names), 4023 * it is passed through the database's addIdentifierQuotes method which 4024 * can be overridden if the database uses something other than backticks. 4025 * - / *$var* / is just encoded, besides traditional table prefix and 4026 * table options its use should be avoided. 4027 * 4028 * @param string $ins SQL statement to replace variables in 4029 * @return string The new SQL statement with variables replaced 4030 */ 4031 protected function replaceSchemaVars( $ins ) { 4032 $vars = $this->getSchemaVars(); 4033 foreach ( $vars as $var => $value ) { 4034 // replace '{$var}' 4035 $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins ); 4036 // replace `{$var}` 4037 $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins ); 4038 // replace /*$var*/ 4039 $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ), $ins ); 4040 } 4041 4042 return $ins; 4043 } 4044 4045 /** 4046 * Replace variables in sourced SQL 4047 * 4048 * @param string $ins 4049 * @return string 4050 */ 4051 protected function replaceVars( $ins ) { 4052 $ins = $this->replaceSchemaVars( $ins ); 4053 4054 // Table prefixes 4055 $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', 4056 array( $this, 'tableNameCallback' ), $ins ); 4057 4058 // Index names 4059 $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', 4060 array( $this, 'indexNameCallback' ), $ins ); 4061 4062 return $ins; 4063 } 4064 4065 /** 4066 * Get schema variables. If none have been set via setSchemaVars(), then 4067 * use some defaults from the current object. 4068 * 4069 * @return array 4070 */ 4071 protected function getSchemaVars() { 4072 if ( $this->mSchemaVars ) { 4073 return $this->mSchemaVars; 4074 } else { 4075 return $this->getDefaultSchemaVars(); 4076 } 4077 } 4078 4079 /** 4080 * Get schema variables to use if none have been set via setSchemaVars(). 4081 * 4082 * Override this in derived classes to provide variables for tables.sql 4083 * and SQL patch files. 4084 * 4085 * @return array 4086 */ 4087 protected function getDefaultSchemaVars() { 4088 return array(); 4089 } 4090 4091 /** 4092 * Table name callback 4093 * 4094 * @param array $matches 4095 * @return string 4096 */ 4097 protected function tableNameCallback( $matches ) { 4098 return $this->tableName( $matches[1] ); 4099 } 4100 4101 /** 4102 * Index name callback 4103 * 4104 * @param array $matches 4105 * @return string 4106 */ 4107 protected function indexNameCallback( $matches ) { 4108 return $this->indexName( $matches[1] ); 4109 } 4110 4111 /** 4112 * Check to see if a named lock is available. This is non-blocking. 4113 * 4114 * @param string $lockName Name of lock to poll 4115 * @param string $method Name of method calling us 4116 * @return bool 4117 * @since 1.20 4118 */ 4119 public function lockIsFree( $lockName, $method ) { 4120 return true; 4121 } 4122 4123 /** 4124 * Acquire a named lock 4125 * 4126 * Abstracted from Filestore::lock() so child classes can implement for 4127 * their own needs. 4128 * 4129 * @param string $lockName Name of lock to aquire 4130 * @param string $method Name of method calling us 4131 * @param int $timeout 4132 * @return bool 4133 */ 4134 public function lock( $lockName, $method, $timeout = 5 ) { 4135 return true; 4136 } 4137 4138 /** 4139 * Release a lock. 4140 * 4141 * @param string $lockName Name of lock to release 4142 * @param string $method Name of method calling us 4143 * 4144 * @return int Returns 1 if the lock was released, 0 if the lock was not established 4145 * by this thread (in which case the lock is not released), and NULL if the named 4146 * lock did not exist 4147 */ 4148 public function unlock( $lockName, $method ) { 4149 return true; 4150 } 4151 4152 /** 4153 * Lock specific tables 4154 * 4155 * @param array $read Array of tables to lock for read access 4156 * @param array $write Array of tables to lock for write access 4157 * @param string $method Name of caller 4158 * @param bool $lowPriority Whether to indicate writes to be LOW PRIORITY 4159 * @return bool 4160 */ 4161 public function lockTables( $read, $write, $method, $lowPriority = true ) { 4162 return true; 4163 } 4164 4165 /** 4166 * Unlock specific tables 4167 * 4168 * @param string $method The caller 4169 * @return bool 4170 */ 4171 public function unlockTables( $method ) { 4172 return true; 4173 } 4174 4175 /** 4176 * Delete a table 4177 * @param string $tableName 4178 * @param string $fName 4179 * @return bool|ResultWrapper 4180 * @since 1.18 4181 */ 4182 public function dropTable( $tableName, $fName = __METHOD__ ) { 4183 if ( !$this->tableExists( $tableName, $fName ) ) { 4184 return false; 4185 } 4186 $sql = "DROP TABLE " . $this->tableName( $tableName ); 4187 if ( $this->cascadingDeletes() ) { 4188 $sql .= " CASCADE"; 4189 } 4190 4191 return $this->query( $sql, $fName ); 4192 } 4193 4194 /** 4195 * Get search engine class. All subclasses of this need to implement this 4196 * if they wish to use searching. 4197 * 4198 * @return string 4199 */ 4200 public function getSearchEngine() { 4201 return 'SearchEngineDummy'; 4202 } 4203 4204 /** 4205 * Find out when 'infinity' is. Most DBMSes support this. This is a special 4206 * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well 4207 * because "i" sorts after all numbers. 4208 * 4209 * @return string 4210 */ 4211 public function getInfinity() { 4212 return 'infinity'; 4213 } 4214 4215 /** 4216 * Encode an expiry time into the DBMS dependent format 4217 * 4218 * @param string $expiry Timestamp for expiry, or the 'infinity' string 4219 * @return string 4220 */ 4221 public function encodeExpiry( $expiry ) { 4222 return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) 4223 ? $this->getInfinity() 4224 : $this->timestamp( $expiry ); 4225 } 4226 4227 /** 4228 * Decode an expiry time into a DBMS independent format 4229 * 4230 * @param string $expiry DB timestamp field value for expiry 4231 * @param int $format TS_* constant, defaults to TS_MW 4232 * @return string 4233 */ 4234 public function decodeExpiry( $expiry, $format = TS_MW ) { 4235 return ( $expiry == '' || $expiry == $this->getInfinity() ) 4236 ? 'infinity' 4237 : wfTimestamp( $format, $expiry ); 4238 } 4239 4240 /** 4241 * Allow or deny "big selects" for this session only. This is done by setting 4242 * the sql_big_selects session variable. 4243 * 4244 * This is a MySQL-specific feature. 4245 * 4246 * @param bool|string $value True for allow, false for deny, or "default" to 4247 * restore the initial value 4248 */ 4249 public function setBigSelects( $value = true ) { 4250 // no-op 4251 } 4252 4253 /** 4254 * @since 1.19 4255 * @return string 4256 */ 4257 public function __toString() { 4258 return (string)$this->mConn; 4259 } 4260 4261 /** 4262 * Run a few simple sanity checks 4263 */ 4264 public function __destruct() { 4265 if ( $this->mTrxLevel && $this->mTrxDoneWrites ) { 4266 trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." ); 4267 } 4268 if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) { 4269 $callers = array(); 4270 foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) { 4271 $callers[] = $callbackInfo[1]; 4272 } 4273 $callers = implode( ', ', $callers ); 4274 trigger_error( "DB transaction callbacks still pending (from $callers)." ); 4275 } 4276 } 4277 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |