[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * This is the Postgres database abstraction layer. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup Database 22 */ 23 24 class PostgresField implements Field { 25 private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname, 26 $has_default, $default; 27 28 /** 29 * @param DatabaseBase $db 30 * @param string $table 31 * @param string $field 32 * @return null|PostgresField 33 */ 34 static function fromText( $db, $table, $field ) { 35 $q = <<<SQL 36 SELECT 37 attnotnull, attlen, conname AS conname, 38 atthasdef, 39 adsrc, 40 COALESCE(condeferred, 'f') AS deferred, 41 COALESCE(condeferrable, 'f') AS deferrable, 42 CASE WHEN typname = 'int2' THEN 'smallint' 43 WHEN typname = 'int4' THEN 'integer' 44 WHEN typname = 'int8' THEN 'bigint' 45 WHEN typname = 'bpchar' THEN 'char' 46 ELSE typname END AS typname 47 FROM pg_class c 48 JOIN pg_namespace n ON (n.oid = c.relnamespace) 49 JOIN pg_attribute a ON (a.attrelid = c.oid) 50 JOIN pg_type t ON (t.oid = a.atttypid) 51 LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f') 52 LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum 53 WHERE relkind = 'r' 54 AND nspname=%s 55 AND relname=%s 56 AND attname=%s; 57 SQL; 58 59 $table = $db->tableName( $table, 'raw' ); 60 $res = $db->query( 61 sprintf( $q, 62 $db->addQuotes( $db->getCoreSchema() ), 63 $db->addQuotes( $table ), 64 $db->addQuotes( $field ) 65 ) 66 ); 67 $row = $db->fetchObject( $res ); 68 if ( !$row ) { 69 return null; 70 } 71 $n = new PostgresField; 72 $n->type = $row->typname; 73 $n->nullable = ( $row->attnotnull == 'f' ); 74 $n->name = $field; 75 $n->tablename = $table; 76 $n->max_length = $row->attlen; 77 $n->deferrable = ( $row->deferrable == 't' ); 78 $n->deferred = ( $row->deferred == 't' ); 79 $n->conname = $row->conname; 80 $n->has_default = ( $row->atthasdef === 't' ); 81 $n->default = $row->adsrc; 82 83 return $n; 84 } 85 86 function name() { 87 return $this->name; 88 } 89 90 function tableName() { 91 return $this->tablename; 92 } 93 94 function type() { 95 return $this->type; 96 } 97 98 function isNullable() { 99 return $this->nullable; 100 } 101 102 function maxLength() { 103 return $this->max_length; 104 } 105 106 function is_deferrable() { 107 return $this->deferrable; 108 } 109 110 function is_deferred() { 111 return $this->deferred; 112 } 113 114 function conname() { 115 return $this->conname; 116 } 117 118 /** 119 * @since 1.19 120 * @return bool|mixed 121 */ 122 function defaultValue() { 123 if ( $this->has_default ) { 124 return $this->default; 125 } else { 126 return false; 127 } 128 } 129 } 130 131 /** 132 * Used to debug transaction processing 133 * Only used if $wgDebugDBTransactions is true 134 * 135 * @since 1.19 136 * @ingroup Database 137 */ 138 class PostgresTransactionState { 139 private static $WATCHED = array( 140 array( 141 "desc" => "%s: Connection state changed from %s -> %s\n", 142 "states" => array( 143 PGSQL_CONNECTION_OK => "OK", 144 PGSQL_CONNECTION_BAD => "BAD" 145 ) 146 ), 147 array( 148 "desc" => "%s: Transaction state changed from %s -> %s\n", 149 "states" => array( 150 PGSQL_TRANSACTION_IDLE => "IDLE", 151 PGSQL_TRANSACTION_ACTIVE => "ACTIVE", 152 PGSQL_TRANSACTION_INTRANS => "TRANS", 153 PGSQL_TRANSACTION_INERROR => "ERROR", 154 PGSQL_TRANSACTION_UNKNOWN => "UNKNOWN" 155 ) 156 ) 157 ); 158 159 /** @var array */ 160 private $mNewState; 161 162 /** @var array */ 163 private $mCurrentState; 164 165 public function __construct( $conn ) { 166 $this->mConn = $conn; 167 $this->update(); 168 $this->mCurrentState = $this->mNewState; 169 } 170 171 public function update() { 172 $this->mNewState = array( 173 pg_connection_status( $this->mConn ), 174 pg_transaction_status( $this->mConn ) 175 ); 176 } 177 178 public function check() { 179 global $wgDebugDBTransactions; 180 $this->update(); 181 if ( $wgDebugDBTransactions ) { 182 if ( $this->mCurrentState !== $this->mNewState ) { 183 $old = reset( $this->mCurrentState ); 184 $new = reset( $this->mNewState ); 185 foreach ( self::$WATCHED as $watched ) { 186 if ( $old !== $new ) { 187 $this->log_changed( $old, $new, $watched ); 188 } 189 $old = next( $this->mCurrentState ); 190 $new = next( $this->mNewState ); 191 } 192 } 193 } 194 $this->mCurrentState = $this->mNewState; 195 } 196 197 protected function describe_changed( $status, $desc_table ) { 198 if ( isset( $desc_table[$status] ) ) { 199 return $desc_table[$status]; 200 } else { 201 return "STATUS " . $status; 202 } 203 } 204 205 protected function log_changed( $old, $new, $watched ) { 206 wfDebug( sprintf( $watched["desc"], 207 $this->mConn, 208 $this->describe_changed( $old, $watched["states"] ), 209 $this->describe_changed( $new, $watched["states"] ) 210 ) ); 211 } 212 } 213 214 /** 215 * Manage savepoints within a transaction 216 * @ingroup Database 217 * @since 1.19 218 */ 219 class SavepointPostgres { 220 /** @var DatabaseBase Establish a savepoint within a transaction */ 221 protected $dbw; 222 protected $id; 223 protected $didbegin; 224 225 /** 226 * @param DatabaseBase $dbw 227 * @param int $id 228 */ 229 public function __construct( $dbw, $id ) { 230 $this->dbw = $dbw; 231 $this->id = $id; 232 $this->didbegin = false; 233 /* If we are not in a transaction, we need to be for savepoint trickery */ 234 if ( !$dbw->trxLevel() ) { 235 $dbw->begin( "FOR SAVEPOINT" ); 236 $this->didbegin = true; 237 } 238 } 239 240 public function __destruct() { 241 if ( $this->didbegin ) { 242 $this->dbw->rollback(); 243 $this->didbegin = false; 244 } 245 } 246 247 public function commit() { 248 if ( $this->didbegin ) { 249 $this->dbw->commit(); 250 $this->didbegin = false; 251 } 252 } 253 254 protected function query( $keyword, $msg_ok, $msg_failed ) { 255 global $wgDebugDBTransactions; 256 if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) { 257 if ( $wgDebugDBTransactions ) { 258 wfDebug( sprintf( $msg_ok, $this->id ) ); 259 } 260 } else { 261 wfDebug( sprintf( $msg_failed, $this->id ) ); 262 } 263 } 264 265 public function savepoint() { 266 $this->query( "SAVEPOINT", 267 "Transaction state: savepoint \"%s\" established.\n", 268 "Transaction state: establishment of savepoint \"%s\" FAILED.\n" 269 ); 270 } 271 272 public function release() { 273 $this->query( "RELEASE", 274 "Transaction state: savepoint \"%s\" released.\n", 275 "Transaction state: release of savepoint \"%s\" FAILED.\n" 276 ); 277 } 278 279 public function rollback() { 280 $this->query( "ROLLBACK TO", 281 "Transaction state: savepoint \"%s\" rolled back.\n", 282 "Transaction state: rollback of savepoint \"%s\" FAILED.\n" 283 ); 284 } 285 286 public function __toString() { 287 return (string)$this->id; 288 } 289 } 290 291 /** 292 * @ingroup Database 293 */ 294 class DatabasePostgres extends DatabaseBase { 295 /** @var resource */ 296 protected $mLastResult = null; 297 298 /** @var int The number of rows affected as an integer */ 299 protected $mAffectedRows = null; 300 301 /** @var int */ 302 private $mInsertId = null; 303 304 /** @var float|string */ 305 private $numericVersion = null; 306 307 /** @var string Connect string to open a PostgreSQL connection */ 308 private $connectString; 309 310 /** @var PostgresTransactionState */ 311 private $mTransactionState; 312 313 /** @var string */ 314 private $mCoreSchema; 315 316 function getType() { 317 return 'postgres'; 318 } 319 320 function cascadingDeletes() { 321 return true; 322 } 323 324 function cleanupTriggers() { 325 return true; 326 } 327 328 function strictIPs() { 329 return true; 330 } 331 332 function realTimestamps() { 333 return true; 334 } 335 336 function implicitGroupby() { 337 return false; 338 } 339 340 function implicitOrderby() { 341 return false; 342 } 343 344 function searchableIPs() { 345 return true; 346 } 347 348 function functionalIndexes() { 349 return true; 350 } 351 352 function hasConstraint( $name ) { 353 $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " . 354 "WHERE c.connamespace = n.oid AND conname = '" . 355 pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . 356 pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'"; 357 $res = $this->doQuery( $sql ); 358 359 return $this->numRows( $res ); 360 } 361 362 /** 363 * Usually aborts on failure 364 * @param string $server 365 * @param string $user 366 * @param string $password 367 * @param string $dbName 368 * @throws DBConnectionError|Exception 369 * @return DatabaseBase|null 370 */ 371 function open( $server, $user, $password, $dbName ) { 372 # Test for Postgres support, to avoid suppressed fatal error 373 if ( !function_exists( 'pg_connect' ) ) { 374 throw new DBConnectionError( 375 $this, 376 "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" . 377 "option? (Note: if you recently installed PHP, you may need to restart your\n" . 378 "webserver and database)\n" 379 ); 380 } 381 382 global $wgDBport; 383 384 if ( !strlen( $user ) ) { # e.g. the class is being loaded 385 return null; 386 } 387 388 $this->mServer = $server; 389 $port = $wgDBport; 390 $this->mUser = $user; 391 $this->mPassword = $password; 392 $this->mDBname = $dbName; 393 394 $connectVars = array( 395 'dbname' => $dbName, 396 'user' => $user, 397 'password' => $password 398 ); 399 if ( $server != false && $server != '' ) { 400 $connectVars['host'] = $server; 401 } 402 if ( $port != false && $port != '' ) { 403 $connectVars['port'] = $port; 404 } 405 if ( $this->mFlags & DBO_SSL ) { 406 $connectVars['sslmode'] = 1; 407 } 408 409 $this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW ); 410 $this->close(); 411 $this->installErrorHandler(); 412 413 try { 414 $this->mConn = pg_connect( $this->connectString ); 415 } catch ( Exception $ex ) { 416 $this->restoreErrorHandler(); 417 throw $ex; 418 } 419 420 $phpError = $this->restoreErrorHandler(); 421 422 if ( !$this->mConn ) { 423 wfDebug( "DB connection error\n" ); 424 wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . 425 substr( $password, 0, 3 ) . "...\n" ); 426 wfDebug( $this->lastError() . "\n" ); 427 throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) ); 428 } 429 430 $this->mOpened = true; 431 $this->mTransactionState = new PostgresTransactionState( $this->mConn ); 432 433 global $wgCommandLineMode; 434 # If called from the command-line (e.g. importDump), only show errors 435 if ( $wgCommandLineMode ) { 436 $this->doQuery( "SET client_min_messages = 'ERROR'" ); 437 } 438 439 $this->query( "SET client_encoding='UTF8'", __METHOD__ ); 440 $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ ); 441 $this->query( "SET timezone = 'GMT'", __METHOD__ ); 442 $this->query( "SET standard_conforming_strings = on", __METHOD__ ); 443 if ( $this->getServerVersion() >= 9.0 ) { 444 $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127 445 } 446 447 global $wgDBmwschema; 448 $this->determineCoreSchema( $wgDBmwschema ); 449 450 return $this->mConn; 451 } 452 453 /** 454 * Postgres doesn't support selectDB in the same way MySQL does. So if the 455 * DB name doesn't match the open connection, open a new one 456 * @param string $db 457 * @return bool 458 */ 459 function selectDB( $db ) { 460 if ( $this->mDBname !== $db ) { 461 return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db ); 462 } else { 463 return true; 464 } 465 } 466 467 function makeConnectionString( $vars ) { 468 $s = ''; 469 foreach ( $vars as $name => $value ) { 470 $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' "; 471 } 472 473 return $s; 474 } 475 476 /** 477 * Closes a database connection, if it is open 478 * Returns success, true if already closed 479 * @return bool 480 */ 481 protected function closeConnection() { 482 return pg_close( $this->mConn ); 483 } 484 485 public function doQuery( $sql ) { 486 if ( function_exists( 'mb_convert_encoding' ) ) { 487 $sql = mb_convert_encoding( $sql, 'UTF-8' ); 488 } 489 $this->mTransactionState->check(); 490 if ( pg_send_query( $this->mConn, $sql ) === false ) { 491 throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" ); 492 } 493 $this->mLastResult = pg_get_result( $this->mConn ); 494 $this->mTransactionState->check(); 495 $this->mAffectedRows = null; 496 if ( pg_result_error( $this->mLastResult ) ) { 497 return false; 498 } 499 500 return $this->mLastResult; 501 } 502 503 protected function dumpError() { 504 $diags = array( 505 PGSQL_DIAG_SEVERITY, 506 PGSQL_DIAG_SQLSTATE, 507 PGSQL_DIAG_MESSAGE_PRIMARY, 508 PGSQL_DIAG_MESSAGE_DETAIL, 509 PGSQL_DIAG_MESSAGE_HINT, 510 PGSQL_DIAG_STATEMENT_POSITION, 511 PGSQL_DIAG_INTERNAL_POSITION, 512 PGSQL_DIAG_INTERNAL_QUERY, 513 PGSQL_DIAG_CONTEXT, 514 PGSQL_DIAG_SOURCE_FILE, 515 PGSQL_DIAG_SOURCE_LINE, 516 PGSQL_DIAG_SOURCE_FUNCTION 517 ); 518 foreach ( $diags as $d ) { 519 wfDebug( sprintf( "PgSQL ERROR(%d): %s\n", 520 $d, pg_result_error_field( $this->mLastResult, $d ) ) ); 521 } 522 } 523 524 function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) { 525 if ( $tempIgnore ) { 526 /* Check for constraint violation */ 527 if ( $errno === '23505' ) { 528 parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore ); 529 530 return; 531 } 532 } 533 /* Transaction stays in the ERROR state until rolledback */ 534 if ( $this->mTrxLevel ) { 535 $this->rollback( __METHOD__ ); 536 }; 537 parent::reportQueryError( $error, $errno, $sql, $fname, false ); 538 } 539 540 function queryIgnore( $sql, $fname = __METHOD__ ) { 541 return $this->query( $sql, $fname, true ); 542 } 543 544 /** 545 * @param stdClass|ResultWrapper $res 546 * @throws DBUnexpectedError 547 */ 548 function freeResult( $res ) { 549 if ( $res instanceof ResultWrapper ) { 550 $res = $res->result; 551 } 552 wfSuppressWarnings(); 553 $ok = pg_free_result( $res ); 554 wfRestoreWarnings(); 555 if ( !$ok ) { 556 throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" ); 557 } 558 } 559 560 /** 561 * @param ResultWrapper|stdClass $res 562 * @return stdClass 563 * @throws DBUnexpectedError 564 */ 565 function fetchObject( $res ) { 566 if ( $res instanceof ResultWrapper ) { 567 $res = $res->result; 568 } 569 wfSuppressWarnings(); 570 $row = pg_fetch_object( $res ); 571 wfRestoreWarnings(); 572 # @todo FIXME: HACK HACK HACK HACK debug 573 574 # @todo hashar: not sure if the following test really trigger if the object 575 # fetching failed. 576 if ( pg_last_error( $this->mConn ) ) { 577 throw new DBUnexpectedError( 578 $this, 579 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) 580 ); 581 } 582 583 return $row; 584 } 585 586 function fetchRow( $res ) { 587 if ( $res instanceof ResultWrapper ) { 588 $res = $res->result; 589 } 590 wfSuppressWarnings(); 591 $row = pg_fetch_array( $res ); 592 wfRestoreWarnings(); 593 if ( pg_last_error( $this->mConn ) ) { 594 throw new DBUnexpectedError( 595 $this, 596 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) 597 ); 598 } 599 600 return $row; 601 } 602 603 function numRows( $res ) { 604 if ( $res instanceof ResultWrapper ) { 605 $res = $res->result; 606 } 607 wfSuppressWarnings(); 608 $n = pg_num_rows( $res ); 609 wfRestoreWarnings(); 610 if ( pg_last_error( $this->mConn ) ) { 611 throw new DBUnexpectedError( 612 $this, 613 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) 614 ); 615 } 616 617 return $n; 618 } 619 620 function numFields( $res ) { 621 if ( $res instanceof ResultWrapper ) { 622 $res = $res->result; 623 } 624 625 return pg_num_fields( $res ); 626 } 627 628 function fieldName( $res, $n ) { 629 if ( $res instanceof ResultWrapper ) { 630 $res = $res->result; 631 } 632 633 return pg_field_name( $res, $n ); 634 } 635 636 /** 637 * Return the result of the last call to nextSequenceValue(); 638 * This must be called after nextSequenceValue(). 639 * 640 * @return int|null 641 */ 642 function insertId() { 643 return $this->mInsertId; 644 } 645 646 /** 647 * @param mixed $res 648 * @param int $row 649 * @return bool 650 */ 651 function dataSeek( $res, $row ) { 652 if ( $res instanceof ResultWrapper ) { 653 $res = $res->result; 654 } 655 656 return pg_result_seek( $res, $row ); 657 } 658 659 function lastError() { 660 if ( $this->mConn ) { 661 if ( $this->mLastResult ) { 662 return pg_result_error( $this->mLastResult ); 663 } else { 664 return pg_last_error(); 665 } 666 } else { 667 return 'No database connection'; 668 } 669 } 670 671 function lastErrno() { 672 if ( $this->mLastResult ) { 673 return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE ); 674 } else { 675 return false; 676 } 677 } 678 679 function affectedRows() { 680 if ( !is_null( $this->mAffectedRows ) ) { 681 // Forced result for simulated queries 682 return $this->mAffectedRows; 683 } 684 if ( empty( $this->mLastResult ) ) { 685 return 0; 686 } 687 688 return pg_affected_rows( $this->mLastResult ); 689 } 690 691 /** 692 * Estimate rows in dataset 693 * Returns estimated count, based on EXPLAIN output 694 * This is not necessarily an accurate estimate, so use sparingly 695 * Returns -1 if count cannot be found 696 * Takes same arguments as Database::select() 697 * 698 * @param string $table 699 * @param string $vars 700 * @param string $conds 701 * @param string $fname 702 * @param array $options 703 * @return int 704 */ 705 function estimateRowCount( $table, $vars = '*', $conds = '', 706 $fname = __METHOD__, $options = array() 707 ) { 708 $options['EXPLAIN'] = true; 709 $res = $this->select( $table, $vars, $conds, $fname, $options ); 710 $rows = -1; 711 if ( $res ) { 712 $row = $this->fetchRow( $res ); 713 $count = array(); 714 if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) { 715 $rows = $count[1]; 716 } 717 } 718 719 return $rows; 720 } 721 722 /** 723 * Returns information about an index 724 * If errors are explicitly ignored, returns NULL on failure 725 * 726 * @param string $table 727 * @param string $index 728 * @param string $fname 729 * @return bool|null 730 */ 731 function indexInfo( $table, $index, $fname = __METHOD__ ) { 732 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'"; 733 $res = $this->query( $sql, $fname ); 734 if ( !$res ) { 735 return null; 736 } 737 foreach ( $res as $row ) { 738 if ( $row->indexname == $this->indexName( $index ) ) { 739 return $row; 740 } 741 } 742 743 return false; 744 } 745 746 /** 747 * Returns is of attributes used in index 748 * 749 * @since 1.19 750 * @param string $index 751 * @param bool|string $schema 752 * @return array 753 */ 754 function indexAttributes( $index, $schema = false ) { 755 if ( $schema === false ) { 756 $schema = $this->getCoreSchema(); 757 } 758 /* 759 * A subquery would be not needed if we didn't care about the order 760 * of attributes, but we do 761 */ 762 $sql = <<<__INDEXATTR__ 763 764 SELECT opcname, 765 attname, 766 i.indoption[s.g] as option, 767 pg_am.amname 768 FROM 769 (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g 770 FROM 771 pg_index isub 772 JOIN pg_class cis 773 ON cis.oid=isub.indexrelid 774 JOIN pg_namespace ns 775 ON cis.relnamespace = ns.oid 776 WHERE cis.relname='$index' AND ns.nspname='$schema') AS s, 777 pg_attribute, 778 pg_opclass opcls, 779 pg_am, 780 pg_class ci 781 JOIN pg_index i 782 ON ci.oid=i.indexrelid 783 JOIN pg_class ct 784 ON ct.oid = i.indrelid 785 JOIN pg_namespace n 786 ON ci.relnamespace = n.oid 787 WHERE 788 ci.relname='$index' AND n.nspname='$schema' 789 AND attrelid = ct.oid 790 AND i.indkey[s.g] = attnum 791 AND i.indclass[s.g] = opcls.oid 792 AND pg_am.oid = opcls.opcmethod 793 __INDEXATTR__; 794 $res = $this->query( $sql, __METHOD__ ); 795 $a = array(); 796 if ( $res ) { 797 foreach ( $res as $row ) { 798 $a[] = array( 799 $row->attname, 800 $row->opcname, 801 $row->amname, 802 $row->option ); 803 } 804 } else { 805 return null; 806 } 807 808 return $a; 809 } 810 811 function indexUnique( $table, $index, $fname = __METHOD__ ) { 812 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" . 813 " AND indexdef LIKE 'CREATE UNIQUE%(" . 814 $this->strencode( $this->indexName( $index ) ) . 815 ")'"; 816 $res = $this->query( $sql, $fname ); 817 if ( !$res ) { 818 return null; 819 } 820 821 return $res->numRows() > 0; 822 } 823 824 /** 825 * Change the FOR UPDATE option as necessary based on the join conditions. Then pass 826 * to the parent function to get the actual SQL text. 827 * 828 * In Postgres when using FOR UPDATE, only the main table and tables that are inner joined 829 * can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to do 830 * so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly. 831 * 832 * MySQL uses "ORDER BY NULL" as an optimization hint, but that syntax is illegal in PostgreSQL. 833 * @see DatabaseBase::selectSQLText 834 */ 835 function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__, 836 $options = array(), $join_conds = array() 837 ) { 838 if ( is_array( $options ) ) { 839 $forUpdateKey = array_search( 'FOR UPDATE', $options, true ); 840 if ( $forUpdateKey !== false && $join_conds ) { 841 unset( $options[$forUpdateKey] ); 842 843 foreach ( $join_conds as $table_cond => $join_cond ) { 844 if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) { 845 $options['FOR UPDATE'][] = $table_cond; 846 } 847 } 848 } 849 850 if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) { 851 unset( $options['ORDER BY'] ); 852 } 853 } 854 855 return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds ); 856 } 857 858 /** 859 * INSERT wrapper, inserts an array into a table 860 * 861 * $args may be a single associative array, or an array of these with numeric keys, 862 * for multi-row insert (Postgres version 8.2 and above only). 863 * 864 * @param string $table Name of the table to insert to. 865 * @param array $args Items to insert into the table. 866 * @param string $fname Name of the function, for profiling 867 * @param array|string $options String or array. Valid options: IGNORE 868 * @return bool Success of insert operation. IGNORE always returns true. 869 */ 870 function insert( $table, $args, $fname = __METHOD__, $options = array() ) { 871 if ( !count( $args ) ) { 872 return true; 873 } 874 875 $table = $this->tableName( $table ); 876 if ( !isset( $this->numericVersion ) ) { 877 $this->getServerVersion(); 878 } 879 880 if ( !is_array( $options ) ) { 881 $options = array( $options ); 882 } 883 884 if ( isset( $args[0] ) && is_array( $args[0] ) ) { 885 $multi = true; 886 $keys = array_keys( $args[0] ); 887 } else { 888 $multi = false; 889 $keys = array_keys( $args ); 890 } 891 892 // If IGNORE is set, we use savepoints to emulate mysql's behavior 893 $savepoint = null; 894 if ( in_array( 'IGNORE', $options ) ) { 895 $savepoint = new SavepointPostgres( $this, 'mw' ); 896 $olde = error_reporting( 0 ); 897 // For future use, we may want to track the number of actual inserts 898 // Right now, insert (all writes) simply return true/false 899 $numrowsinserted = 0; 900 } 901 902 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; 903 904 if ( $multi ) { 905 if ( $this->numericVersion >= 8.2 && !$savepoint ) { 906 $first = true; 907 foreach ( $args as $row ) { 908 if ( $first ) { 909 $first = false; 910 } else { 911 $sql .= ','; 912 } 913 $sql .= '(' . $this->makeList( $row ) . ')'; 914 } 915 $res = (bool)$this->query( $sql, $fname, $savepoint ); 916 } else { 917 $res = true; 918 $origsql = $sql; 919 foreach ( $args as $row ) { 920 $tempsql = $origsql; 921 $tempsql .= '(' . $this->makeList( $row ) . ')'; 922 923 if ( $savepoint ) { 924 $savepoint->savepoint(); 925 } 926 927 $tempres = (bool)$this->query( $tempsql, $fname, $savepoint ); 928 929 if ( $savepoint ) { 930 $bar = pg_last_error(); 931 if ( $bar != false ) { 932 $savepoint->rollback(); 933 } else { 934 $savepoint->release(); 935 $numrowsinserted++; 936 } 937 } 938 939 // If any of them fail, we fail overall for this function call 940 // Note that this will be ignored if IGNORE is set 941 if ( !$tempres ) { 942 $res = false; 943 } 944 } 945 } 946 } else { 947 // Not multi, just a lone insert 948 if ( $savepoint ) { 949 $savepoint->savepoint(); 950 } 951 952 $sql .= '(' . $this->makeList( $args ) . ')'; 953 $res = (bool)$this->query( $sql, $fname, $savepoint ); 954 if ( $savepoint ) { 955 $bar = pg_last_error(); 956 if ( $bar != false ) { 957 $savepoint->rollback(); 958 } else { 959 $savepoint->release(); 960 $numrowsinserted++; 961 } 962 } 963 } 964 if ( $savepoint ) { 965 error_reporting( $olde ); 966 $savepoint->commit(); 967 968 // Set the affected row count for the whole operation 969 $this->mAffectedRows = $numrowsinserted; 970 971 // IGNORE always returns true 972 return true; 973 } 974 975 return $res; 976 } 977 978 /** 979 * INSERT SELECT wrapper 980 * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...) 981 * Source items may be literals rather then field names, but strings should 982 * be quoted with Database::addQuotes() 983 * $conds may be "*" to copy the whole table 984 * srcTable may be an array of tables. 985 * @todo FIXME: Implement this a little better (seperate select/insert)? 986 * 987 * @param string $destTable 988 * @param array|string $srcTable 989 * @param array $varMap 990 * @param array $conds 991 * @param string $fname 992 * @param array $insertOptions 993 * @param array $selectOptions 994 * @return bool 995 */ 996 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__, 997 $insertOptions = array(), $selectOptions = array() ) { 998 $destTable = $this->tableName( $destTable ); 999 1000 if ( !is_array( $insertOptions ) ) { 1001 $insertOptions = array( $insertOptions ); 1002 } 1003 1004 /* 1005 * If IGNORE is set, we use savepoints to emulate mysql's behavior 1006 * Ignore LOW PRIORITY option, since it is MySQL-specific 1007 */ 1008 $savepoint = null; 1009 if ( in_array( 'IGNORE', $insertOptions ) ) { 1010 $savepoint = new SavepointPostgres( $this, 'mw' ); 1011 $olde = error_reporting( 0 ); 1012 $numrowsinserted = 0; 1013 $savepoint->savepoint(); 1014 } 1015 1016 if ( !is_array( $selectOptions ) ) { 1017 $selectOptions = array( $selectOptions ); 1018 } 1019 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 1020 if ( is_array( $srcTable ) ) { 1021 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 1022 } else { 1023 $srcTable = $this->tableName( $srcTable ); 1024 } 1025 1026 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 1027 " SELECT $startOpts " . implode( ',', $varMap ) . 1028 " FROM $srcTable $useIndex"; 1029 1030 if ( $conds != '*' ) { 1031 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 1032 } 1033 1034 $sql .= " $tailOpts"; 1035 1036 $res = (bool)$this->query( $sql, $fname, $savepoint ); 1037 if ( $savepoint ) { 1038 $bar = pg_last_error(); 1039 if ( $bar != false ) { 1040 $savepoint->rollback(); 1041 } else { 1042 $savepoint->release(); 1043 $numrowsinserted++; 1044 } 1045 error_reporting( $olde ); 1046 $savepoint->commit(); 1047 1048 // Set the affected row count for the whole operation 1049 $this->mAffectedRows = $numrowsinserted; 1050 1051 // IGNORE always returns true 1052 return true; 1053 } 1054 1055 return $res; 1056 } 1057 1058 function tableName( $name, $format = 'quoted' ) { 1059 # Replace reserved words with better ones 1060 switch ( $name ) { 1061 case 'user': 1062 return $this->realTableName( 'mwuser', $format ); 1063 case 'text': 1064 return $this->realTableName( 'pagecontent', $format ); 1065 default: 1066 return $this->realTableName( $name, $format ); 1067 } 1068 } 1069 1070 /* Don't cheat on installer */ 1071 function realTableName( $name, $format = 'quoted' ) { 1072 return parent::tableName( $name, $format ); 1073 } 1074 1075 /** 1076 * Return the next in a sequence, save the value for retrieval via insertId() 1077 * 1078 * @param string $seqName 1079 * @return int|null 1080 */ 1081 function nextSequenceValue( $seqName ) { 1082 $safeseq = str_replace( "'", "''", $seqName ); 1083 $res = $this->query( "SELECT nextval('$safeseq')" ); 1084 $row = $this->fetchRow( $res ); 1085 $this->mInsertId = $row[0]; 1086 1087 return $this->mInsertId; 1088 } 1089 1090 /** 1091 * Return the current value of a sequence. Assumes it has been nextval'ed in this session. 1092 * 1093 * @param string $seqName 1094 * @return int 1095 */ 1096 function currentSequenceValue( $seqName ) { 1097 $safeseq = str_replace( "'", "''", $seqName ); 1098 $res = $this->query( "SELECT currval('$safeseq')" ); 1099 $row = $this->fetchRow( $res ); 1100 $currval = $row[0]; 1101 1102 return $currval; 1103 } 1104 1105 # Returns the size of a text field, or -1 for "unlimited" 1106 function textFieldSize( $table, $field ) { 1107 $table = $this->tableName( $table ); 1108 $sql = "SELECT t.typname as ftype,a.atttypmod as size 1109 FROM pg_class c, pg_attribute a, pg_type t 1110 WHERE relname='$table' AND a.attrelid=c.oid AND 1111 a.atttypid=t.oid and a.attname='$field'"; 1112 $res = $this->query( $sql ); 1113 $row = $this->fetchObject( $res ); 1114 if ( $row->ftype == 'varchar' ) { 1115 $size = $row->size - 4; 1116 } else { 1117 $size = $row->size; 1118 } 1119 1120 return $size; 1121 } 1122 1123 function limitResult( $sql, $limit, $offset = false ) { 1124 return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' ); 1125 } 1126 1127 function wasDeadlock() { 1128 return $this->lastErrno() == '40P01'; 1129 } 1130 1131 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) { 1132 $newName = $this->addIdentifierQuotes( $newName ); 1133 $oldName = $this->addIdentifierQuotes( $oldName ); 1134 1135 return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " . 1136 "(LIKE $oldName INCLUDING DEFAULTS)", $fname ); 1137 } 1138 1139 function listTables( $prefix = null, $fname = __METHOD__ ) { 1140 $eschema = $this->addQuotes( $this->getCoreSchema() ); 1141 $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname ); 1142 $endArray = array(); 1143 1144 foreach ( $result as $table ) { 1145 $vars = get_object_vars( $table ); 1146 $table = array_pop( $vars ); 1147 if ( !$prefix || strpos( $table, $prefix ) === 0 ) { 1148 $endArray[] = $table; 1149 } 1150 } 1151 1152 return $endArray; 1153 } 1154 1155 function timestamp( $ts = 0 ) { 1156 return wfTimestamp( TS_POSTGRES, $ts ); 1157 } 1158 1159 /** 1160 * Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12 1161 * to http://www.php.net/manual/en/ref.pgsql.php 1162 * 1163 * Parsing a postgres array can be a tricky problem, he's my 1164 * take on this, it handles multi-dimensional arrays plus 1165 * escaping using a nasty regexp to determine the limits of each 1166 * data-item. 1167 * 1168 * This should really be handled by PHP PostgreSQL module 1169 * 1170 * @since 1.19 1171 * @param string $text Postgreql array returned in a text form like {a,b} 1172 * @param string $output 1173 * @param int $limit 1174 * @param int $offset 1175 * @return string 1176 */ 1177 function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) { 1178 if ( false === $limit ) { 1179 $limit = strlen( $text ) - 1; 1180 $output = array(); 1181 } 1182 if ( '{}' == $text ) { 1183 return $output; 1184 } 1185 do { 1186 if ( '{' != $text[$offset] ) { 1187 preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/", 1188 $text, $match, 0, $offset ); 1189 $offset += strlen( $match[0] ); 1190 $output[] = ( '"' != $match[1][0] 1191 ? $match[1] 1192 : stripcslashes( substr( $match[1], 1, -1 ) ) ); 1193 if ( '},' == $match[3] ) { 1194 return $output; 1195 } 1196 } else { 1197 $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 ); 1198 } 1199 } while ( $limit > $offset ); 1200 1201 return $output; 1202 } 1203 1204 /** 1205 * Return aggregated value function call 1206 * @param array $valuedata 1207 * @param string $valuename 1208 * @return array 1209 */ 1210 public function aggregateValue( $valuedata, $valuename = 'value' ) { 1211 return $valuedata; 1212 } 1213 1214 /** 1215 * @return string Wikitext of a link to the server software's web site 1216 */ 1217 public function getSoftwareLink() { 1218 return '[{{int:version-db-postgres-url}} PostgreSQL]'; 1219 } 1220 1221 /** 1222 * Return current schema (executes SELECT current_schema()) 1223 * Needs transaction 1224 * 1225 * @since 1.19 1226 * @return string Default schema for the current session 1227 */ 1228 function getCurrentSchema() { 1229 $res = $this->query( "SELECT current_schema()", __METHOD__ ); 1230 $row = $this->fetchRow( $res ); 1231 1232 return $row[0]; 1233 } 1234 1235 /** 1236 * Return list of schemas which are accessible without schema name 1237 * This is list does not contain magic keywords like "$user" 1238 * Needs transaction 1239 * 1240 * @see getSearchPath() 1241 * @see setSearchPath() 1242 * @since 1.19 1243 * @return array List of actual schemas for the current sesson 1244 */ 1245 function getSchemas() { 1246 $res = $this->query( "SELECT current_schemas(false)", __METHOD__ ); 1247 $row = $this->fetchRow( $res ); 1248 $schemas = array(); 1249 1250 /* PHP pgsql support does not support array type, "{a,b}" string is returned */ 1251 1252 return $this->pg_array_parse( $row[0], $schemas ); 1253 } 1254 1255 /** 1256 * Return search patch for schemas 1257 * This is different from getSchemas() since it contain magic keywords 1258 * (like "$user"). 1259 * Needs transaction 1260 * 1261 * @since 1.19 1262 * @return array How to search for table names schemas for the current user 1263 */ 1264 function getSearchPath() { 1265 $res = $this->query( "SHOW search_path", __METHOD__ ); 1266 $row = $this->fetchRow( $res ); 1267 1268 /* PostgreSQL returns SHOW values as strings */ 1269 1270 return explode( ",", $row[0] ); 1271 } 1272 1273 /** 1274 * Update search_path, values should already be sanitized 1275 * Values may contain magic keywords like "$user" 1276 * @since 1.19 1277 * 1278 * @param array $search_path List of schemas to be searched by default 1279 */ 1280 function setSearchPath( $search_path ) { 1281 $this->query( "SET search_path = " . implode( ", ", $search_path ) ); 1282 } 1283 1284 /** 1285 * Determine default schema for MediaWiki core 1286 * Adjust this session schema search path if desired schema exists 1287 * and is not alread there. 1288 * 1289 * We need to have name of the core schema stored to be able 1290 * to query database metadata. 1291 * 1292 * This will be also called by the installer after the schema is created 1293 * 1294 * @since 1.19 1295 * 1296 * @param string $desiredSchema 1297 */ 1298 function determineCoreSchema( $desiredSchema ) { 1299 $this->begin( __METHOD__ ); 1300 if ( $this->schemaExists( $desiredSchema ) ) { 1301 if ( in_array( $desiredSchema, $this->getSchemas() ) ) { 1302 $this->mCoreSchema = $desiredSchema; 1303 wfDebug( "Schema \"" . $desiredSchema . "\" already in the search path\n" ); 1304 } else { 1305 /** 1306 * Prepend our schema (e.g. 'mediawiki') in front 1307 * of the search path 1308 * Fixes bug 15816 1309 */ 1310 $search_path = $this->getSearchPath(); 1311 array_unshift( $search_path, 1312 $this->addIdentifierQuotes( $desiredSchema ) ); 1313 $this->setSearchPath( $search_path ); 1314 $this->mCoreSchema = $desiredSchema; 1315 wfDebug( "Schema \"" . $desiredSchema . "\" added to the search path\n" ); 1316 } 1317 } else { 1318 $this->mCoreSchema = $this->getCurrentSchema(); 1319 wfDebug( "Schema \"" . $desiredSchema . "\" not found, using current \"" . 1320 $this->mCoreSchema . "\"\n" ); 1321 } 1322 /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */ 1323 $this->commit( __METHOD__ ); 1324 } 1325 1326 /** 1327 * Return schema name fore core MediaWiki tables 1328 * 1329 * @since 1.19 1330 * @return string Core schema name 1331 */ 1332 function getCoreSchema() { 1333 return $this->mCoreSchema; 1334 } 1335 1336 /** 1337 * @return string Version information from the database 1338 */ 1339 function getServerVersion() { 1340 if ( !isset( $this->numericVersion ) ) { 1341 $versionInfo = pg_version( $this->mConn ); 1342 if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) { 1343 // Old client, abort install 1344 $this->numericVersion = '7.3 or earlier'; 1345 } elseif ( isset( $versionInfo['server'] ) ) { 1346 // Normal client 1347 $this->numericVersion = $versionInfo['server']; 1348 } else { 1349 // Bug 16937: broken pgsql extension from PHP<5.3 1350 $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' ); 1351 } 1352 } 1353 1354 return $this->numericVersion; 1355 } 1356 1357 /** 1358 * Query whether a given relation exists (in the given schema, or the 1359 * default mw one if not given) 1360 * @param string $table 1361 * @param array|string $types 1362 * @param bool|string $schema 1363 * @return bool 1364 */ 1365 function relationExists( $table, $types, $schema = false ) { 1366 if ( !is_array( $types ) ) { 1367 $types = array( $types ); 1368 } 1369 if ( !$schema ) { 1370 $schema = $this->getCoreSchema(); 1371 } 1372 $table = $this->realTableName( $table, 'raw' ); 1373 $etable = $this->addQuotes( $table ); 1374 $eschema = $this->addQuotes( $schema ); 1375 $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n " 1376 . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema " 1377 . "AND c.relkind IN ('" . implode( "','", $types ) . "')"; 1378 $res = $this->query( $sql ); 1379 $count = $res ? $res->numRows() : 0; 1380 1381 return (bool)$count; 1382 } 1383 1384 /** 1385 * For backward compatibility, this function checks both tables and 1386 * views. 1387 * @param string $table 1388 * @param string $fname 1389 * @param bool|string $schema 1390 * @return bool 1391 */ 1392 function tableExists( $table, $fname = __METHOD__, $schema = false ) { 1393 return $this->relationExists( $table, array( 'r', 'v' ), $schema ); 1394 } 1395 1396 function sequenceExists( $sequence, $schema = false ) { 1397 return $this->relationExists( $sequence, 'S', $schema ); 1398 } 1399 1400 function triggerExists( $table, $trigger ) { 1401 $q = <<<SQL 1402 SELECT 1 FROM pg_class, pg_namespace, pg_trigger 1403 WHERE relnamespace=pg_namespace.oid AND relkind='r' 1404 AND tgrelid=pg_class.oid 1405 AND nspname=%s AND relname=%s AND tgname=%s 1406 SQL; 1407 $res = $this->query( 1408 sprintf( 1409 $q, 1410 $this->addQuotes( $this->getCoreSchema() ), 1411 $this->addQuotes( $table ), 1412 $this->addQuotes( $trigger ) 1413 ) 1414 ); 1415 if ( !$res ) { 1416 return null; 1417 } 1418 $rows = $res->numRows(); 1419 1420 return $rows; 1421 } 1422 1423 function ruleExists( $table, $rule ) { 1424 $exists = $this->selectField( 'pg_rules', 'rulename', 1425 array( 1426 'rulename' => $rule, 1427 'tablename' => $table, 1428 'schemaname' => $this->getCoreSchema() 1429 ) 1430 ); 1431 1432 return $exists === $rule; 1433 } 1434 1435 function constraintExists( $table, $constraint ) { 1436 $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " . 1437 "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", 1438 $this->addQuotes( $this->getCoreSchema() ), 1439 $this->addQuotes( $table ), 1440 $this->addQuotes( $constraint ) 1441 ); 1442 $res = $this->query( $sql ); 1443 if ( !$res ) { 1444 return null; 1445 } 1446 $rows = $res->numRows(); 1447 1448 return $rows; 1449 } 1450 1451 /** 1452 * Query whether a given schema exists. Returns true if it does, false if it doesn't. 1453 * @param string $schema 1454 * @return bool 1455 */ 1456 function schemaExists( $schema ) { 1457 $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1, 1458 array( 'nspname' => $schema ), __METHOD__ ); 1459 1460 return (bool)$exists; 1461 } 1462 1463 /** 1464 * Returns true if a given role (i.e. user) exists, false otherwise. 1465 * @param string $roleName 1466 * @return bool 1467 */ 1468 function roleExists( $roleName ) { 1469 $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1, 1470 array( 'rolname' => $roleName ), __METHOD__ ); 1471 1472 return (bool)$exists; 1473 } 1474 1475 function fieldInfo( $table, $field ) { 1476 return PostgresField::fromText( $this, $table, $field ); 1477 } 1478 1479 /** 1480 * pg_field_type() wrapper 1481 * @param ResultWrapper|resource $res ResultWrapper or PostgreSQL query result resource 1482 * @param int $index Field number, starting from 0 1483 * @return string 1484 */ 1485 function fieldType( $res, $index ) { 1486 if ( $res instanceof ResultWrapper ) { 1487 $res = $res->result; 1488 } 1489 1490 return pg_field_type( $res, $index ); 1491 } 1492 1493 /** 1494 * @param string $b 1495 * @return Blob 1496 */ 1497 function encodeBlob( $b ) { 1498 return new Blob( pg_escape_bytea( $this->mConn, $b ) ); 1499 } 1500 1501 function decodeBlob( $b ) { 1502 if ( $b instanceof Blob ) { 1503 $b = $b->fetch(); 1504 } 1505 1506 return pg_unescape_bytea( $b ); 1507 } 1508 1509 function strencode( $s ) { # Should not be called by us 1510 return pg_escape_string( $this->mConn, $s ); 1511 } 1512 1513 /** 1514 * @param null|bool|Blob $s 1515 * @return int|string 1516 */ 1517 function addQuotes( $s ) { 1518 if ( is_null( $s ) ) { 1519 return 'NULL'; 1520 } elseif ( is_bool( $s ) ) { 1521 return intval( $s ); 1522 } elseif ( $s instanceof Blob ) { 1523 return "'" . $s->fetch( $s ) . "'"; 1524 } 1525 1526 return "'" . pg_escape_string( $this->mConn, $s ) . "'"; 1527 } 1528 1529 /** 1530 * Postgres specific version of replaceVars. 1531 * Calls the parent version in Database.php 1532 * 1533 * @param string $ins SQL string, read from a stream (usually tables.sql) 1534 * @return string SQL string 1535 */ 1536 protected function replaceVars( $ins ) { 1537 $ins = parent::replaceVars( $ins ); 1538 1539 if ( $this->numericVersion >= 8.3 ) { 1540 // Thanks for not providing backwards-compatibility, 8.3 1541 $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins ); 1542 } 1543 1544 if ( $this->numericVersion <= 8.1 ) { // Our minimum version 1545 $ins = str_replace( 'USING gin', 'USING gist', $ins ); 1546 } 1547 1548 return $ins; 1549 } 1550 1551 /** 1552 * Various select options 1553 * 1554 * @param array $options An associative array of options to be turned into 1555 * an SQL query, valid keys are listed in the function. 1556 * @return array 1557 */ 1558 function makeSelectOptions( $options ) { 1559 $preLimitTail = $postLimitTail = ''; 1560 $startOpts = $useIndex = ''; 1561 1562 $noKeyOptions = array(); 1563 foreach ( $options as $key => $option ) { 1564 if ( is_numeric( $key ) ) { 1565 $noKeyOptions[$option] = true; 1566 } 1567 } 1568 1569 $preLimitTail .= $this->makeGroupByWithHaving( $options ); 1570 1571 $preLimitTail .= $this->makeOrderBy( $options ); 1572 1573 //if ( isset( $options['LIMIT'] ) ) { 1574 // $tailOpts .= $this->limitResult( '', $options['LIMIT'], 1575 // isset( $options['OFFSET'] ) ? $options['OFFSET'] 1576 // : false ); 1577 //} 1578 1579 if ( isset( $options['FOR UPDATE'] ) ) { 1580 $postLimitTail .= ' FOR UPDATE OF ' . 1581 implode( ', ', array_map( array( &$this, 'tableName' ), $options['FOR UPDATE'] ) ); 1582 } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 1583 $postLimitTail .= ' FOR UPDATE'; 1584 } 1585 1586 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 1587 $startOpts .= 'DISTINCT'; 1588 } 1589 1590 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 1591 } 1592 1593 function getDBname() { 1594 return $this->mDBname; 1595 } 1596 1597 function getServer() { 1598 return $this->mServer; 1599 } 1600 1601 function buildConcat( $stringList ) { 1602 return implode( ' || ', $stringList ); 1603 } 1604 1605 public function buildGroupConcatField( 1606 $delimiter, $table, $field, $conds = '', $options = array(), $join_conds = array() 1607 ) { 1608 $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')'; 1609 1610 return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')'; 1611 } 1612 1613 public function getSearchEngine() { 1614 return 'SearchPostgres'; 1615 } 1616 1617 public function streamStatementEnd( &$sql, &$newLine ) { 1618 # Allow dollar quoting for function declarations 1619 if ( substr( $newLine, 0, 4 ) == '$mw$' ) { 1620 if ( $this->delimiter ) { 1621 $this->delimiter = false; 1622 } else { 1623 $this->delimiter = ';'; 1624 } 1625 } 1626 1627 return parent::streamStatementEnd( $sql, $newLine ); 1628 } 1629 1630 /** 1631 * Check to see if a named lock is available. This is non-blocking. 1632 * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS 1633 * 1634 * @param string $lockName Name of lock to poll 1635 * @param string $method Name of method calling us 1636 * @return bool 1637 * @since 1.20 1638 */ 1639 public function lockIsFree( $lockName, $method ) { 1640 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) ); 1641 $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key)) 1642 WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method ); 1643 $row = $this->fetchObject( $result ); 1644 1645 return ( $row->lockstatus === 't' ); 1646 } 1647 1648 /** 1649 * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS 1650 * @param string $lockName 1651 * @param string $method 1652 * @param int $timeout 1653 * @return bool 1654 */ 1655 public function lock( $lockName, $method, $timeout = 5 ) { 1656 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) ); 1657 for ( $attempts = 1; $attempts <= $timeout; ++$attempts ) { 1658 $result = $this->query( 1659 "SELECT pg_try_advisory_lock($key) AS lockstatus", $method ); 1660 $row = $this->fetchObject( $result ); 1661 if ( $row->lockstatus === 't' ) { 1662 return true; 1663 } else { 1664 sleep( 1 ); 1665 } 1666 } 1667 wfDebug( __METHOD__ . " failed to acquire lock\n" ); 1668 1669 return false; 1670 } 1671 1672 /** 1673 * See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM 1674 * PG DOCS: http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS 1675 * @param string $lockName 1676 * @param string $method 1677 * @return bool 1678 */ 1679 public function unlock( $lockName, $method ) { 1680 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) ); 1681 $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method ); 1682 $row = $this->fetchObject( $result ); 1683 1684 return ( $row->lockstatus === 't' ); 1685 } 1686 1687 /** 1688 * @param string $lockName 1689 * @return string Integer 1690 */ 1691 private function bigintFromLockName( $lockName ) { 1692 return wfBaseConvert( substr( sha1( $lockName ), 0, 15 ), 16, 10 ); 1693 } 1694 } // end DatabasePostgres class
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 |