MediaWiki
REL1_19
|
00001 <?php 00009 class PostgresField implements Field { 00010 private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname; 00011 00018 static function fromText( $db, $table, $field ) { 00019 global $wgDBmwschema; 00020 00021 $q = <<<SQL 00022 SELECT 00023 attnotnull, attlen, COALESCE(conname, '') AS conname, 00024 COALESCE(condeferred, 'f') AS deferred, 00025 COALESCE(condeferrable, 'f') AS deferrable, 00026 CASE WHEN typname = 'int2' THEN 'smallint' 00027 WHEN typname = 'int4' THEN 'integer' 00028 WHEN typname = 'int8' THEN 'bigint' 00029 WHEN typname = 'bpchar' THEN 'char' 00030 ELSE typname END AS typname 00031 FROM pg_class c 00032 JOIN pg_namespace n ON (n.oid = c.relnamespace) 00033 JOIN pg_attribute a ON (a.attrelid = c.oid) 00034 JOIN pg_type t ON (t.oid = a.atttypid) 00035 LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f') 00036 WHERE relkind = 'r' 00037 AND nspname=%s 00038 AND relname=%s 00039 AND attname=%s; 00040 SQL; 00041 00042 $table = $db->tableName( $table, 'raw' ); 00043 $res = $db->query( 00044 sprintf( $q, 00045 $db->addQuotes( $wgDBmwschema ), 00046 $db->addQuotes( $table ), 00047 $db->addQuotes( $field ) 00048 ) 00049 ); 00050 $row = $db->fetchObject( $res ); 00051 if ( !$row ) { 00052 return null; 00053 } 00054 $n = new PostgresField; 00055 $n->type = $row->typname; 00056 $n->nullable = ( $row->attnotnull == 'f' ); 00057 $n->name = $field; 00058 $n->tablename = $table; 00059 $n->max_length = $row->attlen; 00060 $n->deferrable = ( $row->deferrable == 't' ); 00061 $n->deferred = ( $row->deferred == 't' ); 00062 $n->conname = $row->conname; 00063 return $n; 00064 } 00065 00066 function name() { 00067 return $this->name; 00068 } 00069 00070 function tableName() { 00071 return $this->tablename; 00072 } 00073 00074 function type() { 00075 return $this->type; 00076 } 00077 00078 function isNullable() { 00079 return $this->nullable; 00080 } 00081 00082 function maxLength() { 00083 return $this->max_length; 00084 } 00085 00086 function is_deferrable() { 00087 return $this->deferrable; 00088 } 00089 00090 function is_deferred() { 00091 return $this->deferred; 00092 } 00093 00094 function conname() { 00095 return $this->conname; 00096 } 00097 00098 } 00099 00103 class DatabasePostgres extends DatabaseBase { 00104 var $mInsertId = null; 00105 var $mLastResult = null; 00106 var $numeric_version = null; 00107 var $mAffectedRows = null; 00108 00109 function getType() { 00110 return 'postgres'; 00111 } 00112 00113 function cascadingDeletes() { 00114 return true; 00115 } 00116 function cleanupTriggers() { 00117 return true; 00118 } 00119 function strictIPs() { 00120 return true; 00121 } 00122 function realTimestamps() { 00123 return true; 00124 } 00125 function implicitGroupby() { 00126 return false; 00127 } 00128 function implicitOrderby() { 00129 return false; 00130 } 00131 function searchableIPs() { 00132 return true; 00133 } 00134 function functionalIndexes() { 00135 return true; 00136 } 00137 00138 function hasConstraint( $name ) { 00139 global $wgDBmwschema; 00140 $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" . 00141 pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $wgDBmwschema ) ."'"; 00142 $res = $this->doQuery( $SQL ); 00143 return $this->numRows( $res ); 00144 } 00145 00149 function open( $server, $user, $password, $dbName ) { 00150 # Test for Postgres support, to avoid suppressed fatal error 00151 if ( !function_exists( 'pg_connect' ) ) { 00152 throw new DBConnectionError( $this, "Postgres functions missing, have you compiled PHP with the --with-pgsql option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" ); 00153 } 00154 00155 global $wgDBport; 00156 00157 if ( !strlen( $user ) ) { # e.g. the class is being loaded 00158 return; 00159 } 00160 00161 $this->close(); 00162 $this->mServer = $server; 00163 $port = $wgDBport; 00164 $this->mUser = $user; 00165 $this->mPassword = $password; 00166 $this->mDBname = $dbName; 00167 00168 $connectVars = array( 00169 'dbname' => $dbName, 00170 'user' => $user, 00171 'password' => $password 00172 ); 00173 if ( $server != false && $server != '' ) { 00174 $connectVars['host'] = $server; 00175 } 00176 if ( $port != false && $port != '' ) { 00177 $connectVars['port'] = $port; 00178 } 00179 $connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW ); 00180 00181 $this->installErrorHandler(); 00182 $this->mConn = pg_connect( $connectString ); 00183 $phpError = $this->restoreErrorHandler(); 00184 00185 if ( !$this->mConn ) { 00186 wfDebug( "DB connection error\n" ); 00187 wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); 00188 wfDebug( $this->lastError() . "\n" ); 00189 throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) ); 00190 } 00191 00192 $this->mOpened = true; 00193 00194 global $wgCommandLineMode; 00195 # If called from the command-line (e.g. importDump), only show errors 00196 if ( $wgCommandLineMode ) { 00197 $this->doQuery( "SET client_min_messages = 'ERROR'" ); 00198 } 00199 00200 $this->query( "SET client_encoding='UTF8'", __METHOD__ ); 00201 $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ ); 00202 $this->query( "SET timezone = 'GMT'", __METHOD__ ); 00203 $this->query( "SET standard_conforming_strings = on", __METHOD__ ); 00204 00205 global $wgDBmwschema; 00206 if ( $this->schemaExists( $wgDBmwschema ) ) { 00207 $safeschema = $this->addIdentifierQuotes( $wgDBmwschema ); 00208 $this->doQuery( "SET search_path = $safeschema" ); 00209 } else { 00210 $this->doQuery( "SET search_path = public" ); 00211 } 00212 00213 return $this->mConn; 00214 } 00215 00221 function selectDB( $db ) { 00222 if ( $this->mDBname !== $db ) { 00223 return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db ); 00224 } else { 00225 return true; 00226 } 00227 } 00228 00229 function makeConnectionString( $vars ) { 00230 $s = ''; 00231 foreach ( $vars as $name => $value ) { 00232 $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' "; 00233 } 00234 return $s; 00235 } 00236 00241 function close() { 00242 $this->mOpened = false; 00243 if ( $this->mConn ) { 00244 return pg_close( $this->mConn ); 00245 } else { 00246 return true; 00247 } 00248 } 00249 00250 protected function doQuery( $sql ) { 00251 if ( function_exists( 'mb_convert_encoding' ) ) { 00252 $sql = mb_convert_encoding( $sql, 'UTF-8' ); 00253 } 00254 $this->mLastResult = pg_query( $this->mConn, $sql ); 00255 $this->mAffectedRows = null; // use pg_affected_rows(mLastResult) 00256 return $this->mLastResult; 00257 } 00258 00259 function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) { 00260 return $this->query( $sql, $fname, true ); 00261 } 00262 00263 function freeResult( $res ) { 00264 if ( $res instanceof ResultWrapper ) { 00265 $res = $res->result; 00266 } 00267 wfSuppressWarnings(); 00268 $ok = pg_free_result( $res ); 00269 wfRestoreWarnings(); 00270 if ( !$ok ) { 00271 throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" ); 00272 } 00273 } 00274 00275 function fetchObject( $res ) { 00276 if ( $res instanceof ResultWrapper ) { 00277 $res = $res->result; 00278 } 00279 wfSuppressWarnings(); 00280 $row = pg_fetch_object( $res ); 00281 wfRestoreWarnings(); 00282 # @todo FIXME: HACK HACK HACK HACK debug 00283 00284 # @todo hashar: not sure if the following test really trigger if the object 00285 # fetching failed. 00286 if( pg_last_error( $this->mConn ) ) { 00287 throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) ); 00288 } 00289 return $row; 00290 } 00291 00292 function fetchRow( $res ) { 00293 if ( $res instanceof ResultWrapper ) { 00294 $res = $res->result; 00295 } 00296 wfSuppressWarnings(); 00297 $row = pg_fetch_array( $res ); 00298 wfRestoreWarnings(); 00299 if( pg_last_error( $this->mConn ) ) { 00300 throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) ); 00301 } 00302 return $row; 00303 } 00304 00305 function numRows( $res ) { 00306 if ( $res instanceof ResultWrapper ) { 00307 $res = $res->result; 00308 } 00309 wfSuppressWarnings(); 00310 $n = pg_num_rows( $res ); 00311 wfRestoreWarnings(); 00312 if( pg_last_error( $this->mConn ) ) { 00313 throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) ); 00314 } 00315 return $n; 00316 } 00317 00318 function numFields( $res ) { 00319 if ( $res instanceof ResultWrapper ) { 00320 $res = $res->result; 00321 } 00322 return pg_num_fields( $res ); 00323 } 00324 00325 function fieldName( $res, $n ) { 00326 if ( $res instanceof ResultWrapper ) { 00327 $res = $res->result; 00328 } 00329 return pg_field_name( $res, $n ); 00330 } 00331 00335 function insertId() { 00336 return $this->mInsertId; 00337 } 00338 00339 function dataSeek( $res, $row ) { 00340 if ( $res instanceof ResultWrapper ) { 00341 $res = $res->result; 00342 } 00343 return pg_result_seek( $res, $row ); 00344 } 00345 00346 function lastError() { 00347 if ( $this->mConn ) { 00348 return pg_last_error(); 00349 } else { 00350 return 'No database connection'; 00351 } 00352 } 00353 function lastErrno() { 00354 return pg_last_error() ? 1 : 0; 00355 } 00356 00357 function affectedRows() { 00358 if ( !is_null( $this->mAffectedRows ) ) { 00359 // Forced result for simulated queries 00360 return $this->mAffectedRows; 00361 } 00362 if( empty( $this->mLastResult ) ) { 00363 return 0; 00364 } 00365 return pg_affected_rows( $this->mLastResult ); 00366 } 00367 00375 function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) { 00376 $options['EXPLAIN'] = true; 00377 $res = $this->select( $table, $vars, $conds, $fname, $options ); 00378 $rows = -1; 00379 if ( $res ) { 00380 $row = $this->fetchRow( $res ); 00381 $count = array(); 00382 if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) { 00383 $rows = $count[1]; 00384 } 00385 } 00386 return $rows; 00387 } 00388 00393 function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) { 00394 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'"; 00395 $res = $this->query( $sql, $fname ); 00396 if ( !$res ) { 00397 return null; 00398 } 00399 foreach ( $res as $row ) { 00400 if ( $row->indexname == $this->indexName( $index ) ) { 00401 return $row; 00402 } 00403 } 00404 return false; 00405 } 00406 00407 function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) { 00408 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'". 00409 " AND indexdef LIKE 'CREATE UNIQUE%(" . 00410 $this->strencode( $this->indexName( $index ) ) . 00411 ")'"; 00412 $res = $this->query( $sql, $fname ); 00413 if ( !$res ) { 00414 return null; 00415 } 00416 foreach ( $res as $row ) { 00417 return true; 00418 } 00419 return false; 00420 } 00421 00435 function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) { 00436 if ( !count( $args ) ) { 00437 return true; 00438 } 00439 00440 $table = $this->tableName( $table ); 00441 if (! isset( $this->numeric_version ) ) { 00442 $this->getServerVersion(); 00443 } 00444 00445 if ( !is_array( $options ) ) { 00446 $options = array( $options ); 00447 } 00448 00449 if ( isset( $args[0] ) && is_array( $args[0] ) ) { 00450 $multi = true; 00451 $keys = array_keys( $args[0] ); 00452 } else { 00453 $multi = false; 00454 $keys = array_keys( $args ); 00455 } 00456 00457 // If IGNORE is set, we use savepoints to emulate mysql's behavior 00458 $ignore = in_array( 'IGNORE', $options ) ? 'mw' : ''; 00459 00460 // If we are not in a transaction, we need to be for savepoint trickery 00461 $didbegin = 0; 00462 if ( $ignore ) { 00463 if ( !$this->mTrxLevel ) { 00464 $this->begin(); 00465 $didbegin = 1; 00466 } 00467 $olde = error_reporting( 0 ); 00468 // For future use, we may want to track the number of actual inserts 00469 // Right now, insert (all writes) simply return true/false 00470 $numrowsinserted = 0; 00471 } 00472 00473 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; 00474 00475 if ( $multi ) { 00476 if ( $this->numeric_version >= 8.2 && !$ignore ) { 00477 $first = true; 00478 foreach ( $args as $row ) { 00479 if ( $first ) { 00480 $first = false; 00481 } else { 00482 $sql .= ','; 00483 } 00484 $sql .= '(' . $this->makeList( $row ) . ')'; 00485 } 00486 $res = (bool)$this->query( $sql, $fname, $ignore ); 00487 } else { 00488 $res = true; 00489 $origsql = $sql; 00490 foreach ( $args as $row ) { 00491 $tempsql = $origsql; 00492 $tempsql .= '(' . $this->makeList( $row ) . ')'; 00493 00494 if ( $ignore ) { 00495 pg_query( $this->mConn, "SAVEPOINT $ignore" ); 00496 } 00497 00498 $tempres = (bool)$this->query( $tempsql, $fname, $ignore ); 00499 00500 if ( $ignore ) { 00501 $bar = pg_last_error(); 00502 if ( $bar != false ) { 00503 pg_query( $this->mConn, "ROLLBACK TO $ignore" ); 00504 } else { 00505 pg_query( $this->mConn, "RELEASE $ignore" ); 00506 $numrowsinserted++; 00507 } 00508 } 00509 00510 // If any of them fail, we fail overall for this function call 00511 // Note that this will be ignored if IGNORE is set 00512 if ( !$tempres ) { 00513 $res = false; 00514 } 00515 } 00516 } 00517 } else { 00518 // Not multi, just a lone insert 00519 if ( $ignore ) { 00520 pg_query($this->mConn, "SAVEPOINT $ignore"); 00521 } 00522 00523 $sql .= '(' . $this->makeList( $args ) . ')'; 00524 $res = (bool)$this->query( $sql, $fname, $ignore ); 00525 if ( $ignore ) { 00526 $bar = pg_last_error(); 00527 if ( $bar != false ) { 00528 pg_query( $this->mConn, "ROLLBACK TO $ignore" ); 00529 } else { 00530 pg_query( $this->mConn, "RELEASE $ignore" ); 00531 $numrowsinserted++; 00532 } 00533 } 00534 } 00535 if ( $ignore ) { 00536 $olde = error_reporting( $olde ); 00537 if ( $didbegin ) { 00538 $this->commit(); 00539 } 00540 00541 // Set the affected row count for the whole operation 00542 $this->mAffectedRows = $numrowsinserted; 00543 00544 // IGNORE always returns true 00545 return true; 00546 } 00547 00548 return $res; 00549 } 00550 00559 function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect', 00560 $insertOptions = array(), $selectOptions = array() ) 00561 { 00562 $destTable = $this->tableName( $destTable ); 00563 00564 // If IGNORE is set, we use savepoints to emulate mysql's behavior 00565 $ignore = in_array( 'IGNORE', $insertOptions ) ? 'mw' : ''; 00566 00567 if( is_array( $insertOptions ) ) { 00568 $insertOptions = implode( ' ', $insertOptions ); // FIXME: This is unused 00569 } 00570 if( !is_array( $selectOptions ) ) { 00571 $selectOptions = array( $selectOptions ); 00572 } 00573 list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); 00574 if( is_array( $srcTable ) ) { 00575 $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); 00576 } else { 00577 $srcTable = $this->tableName( $srcTable ); 00578 } 00579 00580 // If we are not in a transaction, we need to be for savepoint trickery 00581 $didbegin = 0; 00582 if ( $ignore ) { 00583 if( !$this->mTrxLevel ) { 00584 $this->begin(); 00585 $didbegin = 1; 00586 } 00587 $olde = error_reporting( 0 ); 00588 $numrowsinserted = 0; 00589 pg_query( $this->mConn, "SAVEPOINT $ignore"); 00590 } 00591 00592 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . 00593 " SELECT $startOpts " . implode( ',', $varMap ) . 00594 " FROM $srcTable $useIndex"; 00595 00596 if ( $conds != '*' ) { 00597 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); 00598 } 00599 00600 $sql .= " $tailOpts"; 00601 00602 $res = (bool)$this->query( $sql, $fname, $ignore ); 00603 if( $ignore ) { 00604 $bar = pg_last_error(); 00605 if( $bar != false ) { 00606 pg_query( $this->mConn, "ROLLBACK TO $ignore" ); 00607 } else { 00608 pg_query( $this->mConn, "RELEASE $ignore" ); 00609 $numrowsinserted++; 00610 } 00611 $olde = error_reporting( $olde ); 00612 if( $didbegin ) { 00613 $this->commit(); 00614 } 00615 00616 // Set the affected row count for the whole operation 00617 $this->mAffectedRows = $numrowsinserted; 00618 00619 // IGNORE always returns true 00620 return true; 00621 } 00622 00623 return $res; 00624 } 00625 00626 function tableName( $name, $format = 'quoted' ) { 00627 # Replace reserved words with better ones 00628 switch( $name ) { 00629 case 'user': 00630 return $this->realTableName( 'mwuser', $format ); 00631 case 'text': 00632 return $this->realTableName( 'pagecontent', $format ); 00633 default: 00634 return $this->realTableName( $name, $format ); 00635 } 00636 } 00637 00638 /* Don't cheat on installer */ 00639 function realTableName( $name, $format = 'quoted' ) { 00640 return parent::tableName( $name, $format ); 00641 } 00642 00646 function nextSequenceValue( $seqName ) { 00647 $safeseq = str_replace( "'", "''", $seqName ); 00648 $res = $this->query( "SELECT nextval('$safeseq')" ); 00649 $row = $this->fetchRow( $res ); 00650 $this->mInsertId = $row[0]; 00651 return $this->mInsertId; 00652 } 00653 00657 function currentSequenceValue( $seqName ) { 00658 $safeseq = str_replace( "'", "''", $seqName ); 00659 $res = $this->query( "SELECT currval('$safeseq')" ); 00660 $row = $this->fetchRow( $res ); 00661 $currval = $row[0]; 00662 return $currval; 00663 } 00664 00665 # Returns the size of a text field, or -1 for "unlimited" 00666 function textFieldSize( $table, $field ) { 00667 $table = $this->tableName( $table ); 00668 $sql = "SELECT t.typname as ftype,a.atttypmod as size 00669 FROM pg_class c, pg_attribute a, pg_type t 00670 WHERE relname='$table' AND a.attrelid=c.oid AND 00671 a.atttypid=t.oid and a.attname='$field'"; 00672 $res =$this->query( $sql ); 00673 $row = $this->fetchObject( $res ); 00674 if ( $row->ftype == 'varchar' ) { 00675 $size = $row->size - 4; 00676 } else { 00677 $size = $row->size; 00678 } 00679 return $size; 00680 } 00681 00682 function limitResult( $sql, $limit, $offset = false ) { 00683 return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' ); 00684 } 00685 00686 function wasDeadlock() { 00687 return $this->lastErrno() == '40P01'; 00688 } 00689 00690 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) { 00691 $newName = $this->addIdentifierQuotes( $newName ); 00692 $oldName = $this->addIdentifierQuotes( $oldName ); 00693 return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname ); 00694 } 00695 00696 function listTables( $prefix = null, $fname = 'DatabasePostgres::listTables' ) { 00697 global $wgDBmwschema; 00698 $eschema = $this->addQuotes( $wgDBmwschema ); 00699 $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname ); 00700 00701 $endArray = array(); 00702 00703 foreach( $result as $table ) { 00704 $vars = get_object_vars($table); 00705 $table = array_pop( $vars ); 00706 if( !$prefix || strpos( $table, $prefix ) === 0 ) { 00707 $endArray[] = $table; 00708 } 00709 } 00710 00711 return $endArray; 00712 } 00713 00714 function timestamp( $ts = 0 ) { 00715 return wfTimestamp( TS_POSTGRES, $ts ); 00716 } 00717 00721 function aggregateValue( $valuedata, $valuename = 'value' ) { 00722 return $valuedata; 00723 } 00724 00728 public static function getSoftwareLink() { 00729 return '[http://www.postgresql.org/ PostgreSQL]'; 00730 } 00731 00735 function getServerVersion() { 00736 if ( !isset( $this->numeric_version ) ) { 00737 $versionInfo = pg_version( $this->mConn ); 00738 if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) { 00739 // Old client, abort install 00740 $this->numeric_version = '7.3 or earlier'; 00741 } elseif ( isset( $versionInfo['server'] ) ) { 00742 // Normal client 00743 $this->numeric_version = $versionInfo['server']; 00744 } else { 00745 // Bug 16937: broken pgsql extension from PHP<5.3 00746 $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' ); 00747 } 00748 } 00749 return $this->numeric_version; 00750 } 00751 00756 function relationExists( $table, $types, $schema = false ) { 00757 global $wgDBmwschema; 00758 if ( !is_array( $types ) ) { 00759 $types = array( $types ); 00760 } 00761 if ( !$schema ) { 00762 $schema = $wgDBmwschema; 00763 } 00764 $table = $this->realTableName( $table, 'raw' ); 00765 $etable = $this->addQuotes( $table ); 00766 $eschema = $this->addQuotes( $schema ); 00767 $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n " 00768 . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema " 00769 . "AND c.relkind IN ('" . implode( "','", $types ) . "')"; 00770 $res = $this->query( $SQL ); 00771 $count = $res ? $res->numRows() : 0; 00772 return (bool)$count; 00773 } 00774 00779 function tableExists( $table, $fname = __METHOD__, $schema = false ) { 00780 return $this->relationExists( $table, array( 'r', 'v' ), $schema ); 00781 } 00782 00783 function sequenceExists( $sequence, $schema = false ) { 00784 return $this->relationExists( $sequence, 'S', $schema ); 00785 } 00786 00787 function triggerExists( $table, $trigger ) { 00788 global $wgDBmwschema; 00789 00790 $q = <<<SQL 00791 SELECT 1 FROM pg_class, pg_namespace, pg_trigger 00792 WHERE relnamespace=pg_namespace.oid AND relkind='r' 00793 AND tgrelid=pg_class.oid 00794 AND nspname=%s AND relname=%s AND tgname=%s 00795 SQL; 00796 $res = $this->query( 00797 sprintf( 00798 $q, 00799 $this->addQuotes( $wgDBmwschema ), 00800 $this->addQuotes( $table ), 00801 $this->addQuotes( $trigger ) 00802 ) 00803 ); 00804 if ( !$res ) { 00805 return null; 00806 } 00807 $rows = $res->numRows(); 00808 return $rows; 00809 } 00810 00811 function ruleExists( $table, $rule ) { 00812 global $wgDBmwschema; 00813 $exists = $this->selectField( 'pg_rules', 'rulename', 00814 array( 00815 'rulename' => $rule, 00816 'tablename' => $table, 00817 'schemaname' => $wgDBmwschema 00818 ) 00819 ); 00820 return $exists === $rule; 00821 } 00822 00823 function constraintExists( $table, $constraint ) { 00824 global $wgDBmwschema; 00825 $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ". 00826 "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", 00827 $this->addQuotes( $wgDBmwschema ), 00828 $this->addQuotes( $table ), 00829 $this->addQuotes( $constraint ) 00830 ); 00831 $res = $this->query( $SQL ); 00832 if ( !$res ) { 00833 return null; 00834 } 00835 $rows = $res->numRows(); 00836 return $rows; 00837 } 00838 00842 function schemaExists( $schema ) { 00843 $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1, 00844 array( 'nspname' => $schema ), __METHOD__ ); 00845 return (bool)$exists; 00846 } 00847 00851 function roleExists( $roleName ) { 00852 $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1, 00853 array( 'rolname' => $roleName ), __METHOD__ ); 00854 return (bool)$exists; 00855 } 00856 00857 function fieldInfo( $table, $field ) { 00858 return PostgresField::fromText( $this, $table, $field ); 00859 } 00860 00864 function fieldType( $res, $index ) { 00865 if ( $res instanceof ResultWrapper ) { 00866 $res = $res->result; 00867 } 00868 return pg_field_type( $res, $index ); 00869 } 00870 00871 /* Not even sure why this is used in the main codebase... */ 00872 function limitResultForUpdate( $sql, $num ) { 00873 return $sql; 00874 } 00875 00880 function encodeBlob( $b ) { 00881 return new Blob( pg_escape_bytea( $this->mConn, $b ) ); 00882 } 00883 00884 function decodeBlob( $b ) { 00885 if ( $b instanceof Blob ) { 00886 $b = $b->fetch(); 00887 } 00888 return pg_unescape_bytea( $b ); 00889 } 00890 00891 function strencode( $s ) { # Should not be called by us 00892 return pg_escape_string( $this->mConn, $s ); 00893 } 00894 00899 function addQuotes( $s ) { 00900 if ( is_null( $s ) ) { 00901 return 'NULL'; 00902 } elseif ( is_bool( $s ) ) { 00903 return intval( $s ); 00904 } elseif ( $s instanceof Blob ) { 00905 return "'" . $s->fetch( $s ) . "'"; 00906 } 00907 return "'" . pg_escape_string( $this->mConn, $s ) . "'"; 00908 } 00909 00920 protected function replaceVars( $ins ) { 00921 $ins = parent::replaceVars( $ins ); 00922 00923 if ( $this->numeric_version >= 8.3 ) { 00924 // Thanks for not providing backwards-compatibility, 8.3 00925 $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins ); 00926 } 00927 00928 if ( $this->numeric_version <= 8.1 ) { // Our minimum version 00929 $ins = str_replace( 'USING gin', 'USING gist', $ins ); 00930 } 00931 00932 return $ins; 00933 } 00934 00944 function makeSelectOptions( $options ) { 00945 $preLimitTail = $postLimitTail = ''; 00946 $startOpts = $useIndex = ''; 00947 00948 $noKeyOptions = array(); 00949 foreach ( $options as $key => $option ) { 00950 if ( is_numeric( $key ) ) { 00951 $noKeyOptions[$option] = true; 00952 } 00953 } 00954 00955 if ( isset( $options['GROUP BY'] ) ) { 00956 $gb = is_array( $options['GROUP BY'] ) 00957 ? implode( ',', $options['GROUP BY'] ) 00958 : $options['GROUP BY']; 00959 $preLimitTail .= " GROUP BY {$gb}"; 00960 } 00961 00962 if ( isset( $options['HAVING'] ) ) { 00963 $preLimitTail .= " HAVING {$options['HAVING']}"; 00964 } 00965 00966 if ( isset( $options['ORDER BY'] ) ) { 00967 $ob = is_array( $options['ORDER BY'] ) 00968 ? implode( ',', $options['ORDER BY'] ) 00969 : $options['ORDER BY']; 00970 $preLimitTail .= " ORDER BY {$ob}"; 00971 } 00972 00973 //if ( isset( $options['LIMIT'] ) ) { 00974 // $tailOpts .= $this->limitResult( '', $options['LIMIT'], 00975 // isset( $options['OFFSET'] ) ? $options['OFFSET'] 00976 // : false ); 00977 //} 00978 00979 if ( isset( $noKeyOptions['FOR UPDATE'] ) ) { 00980 $postLimitTail .= ' FOR UPDATE'; 00981 } 00982 if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) { 00983 $postLimitTail .= ' LOCK IN SHARE MODE'; 00984 } 00985 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) { 00986 $startOpts .= 'DISTINCT'; 00987 } 00988 00989 return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail ); 00990 } 00991 00992 function setFakeMaster( $enabled = true ) {} 00993 00994 function getDBname() { 00995 return $this->mDBname; 00996 } 00997 00998 function getServer() { 00999 return $this->mServer; 01000 } 01001 01002 function buildConcat( $stringList ) { 01003 return implode( ' || ', $stringList ); 01004 } 01005 01006 public function getSearchEngine() { 01007 return 'SearchPostgres'; 01008 } 01009 01010 public function streamStatementEnd( &$sql, &$newLine ) { 01011 # Allow dollar quoting for function declarations 01012 if ( substr( $newLine, 0, 4 ) == '$mw$' ) { 01013 if ( $this->delimiter ) { 01014 $this->delimiter = false; 01015 } 01016 else { 01017 $this->delimiter = ';'; 01018 } 01019 } 01020 return parent::streamStatementEnd( $sql, $newLine ); 01021 } 01022 } // end DatabasePostgres class