X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fdb%2FDatabasePostgres.php;h=af5f30c95c3389c3dcab550af02ee6457c03db99;hb=ee2b60ca604d1e5c5710a0bd9d0342c278bc09e0;hp=4d14b0012bab4db96242611c2e7add5836b469ef;hpb=fcc6f32017ec533c82630dfd2642c65225b2f6ae;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php index 4d14b0012b..af5f30c95c 100644 --- a/includes/db/DatabasePostgres.php +++ b/includes/db/DatabasePostgres.php @@ -1,33 +1,39 @@ tableName( $table ); $res = $db->query(sprintf($q, $db->addQuotes($wgDBmwschema), $db->addQuotes($table), @@ -41,6 +47,9 @@ END; $n->name = $field; $n->tablename = $table; $n->max_length = $row->attlen; + $n->deferrable = ($row->deferrable == 't'); + $n->deferred = ($row->deferred == 't'); + $n->conname = $row->conname; return $n; } @@ -63,32 +72,44 @@ END; function maxLength() { return $this->max_length; } + + function is_deferrable() { + return $this->deferrable; + } + + function is_deferred() { + return $this->deferred; + } + + function conname() { + return $this->conname; + } + } /** * @ingroup Database */ -class DatabasePostgres extends Database { - var $mInsertId = NULL; - var $mLastResult = NULL; - var $numeric_version = NULL; +class DatabasePostgres extends DatabaseBase { + var $mInsertId = null; + var $mLastResult = null; + var $numeric_version = null; + var $mAffectedRows = null; function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0 ) { - global $wgOut; - # Can't get a reference if it hasn't been set yet - if ( !isset( $wgOut ) ) { - $wgOut = NULL; - } - $this->mOut =& $wgOut; $this->mFailFunction = $failFunction; $this->mFlags = $flags; $this->open( $server, $user, $password, $dbName); } + function getType() { + return 'postgres'; + } + function cascadingDeletes() { return true; } @@ -140,7 +161,6 @@ class DatabasePostgres extends Database { if (!strlen($user)) { ## e.g. the class is being loaded return; } - $this->close(); $this->mServer = $server; $this->mPort = $port = $wgDBport; @@ -148,22 +168,31 @@ class DatabasePostgres extends Database { $this->mPassword = $password; $this->mDBname = $dbName; - $hstring=""; + $connectVars = array( + 'dbname' => $dbName, + 'user' => $user, + 'password' => $password ); if ($server!=false && $server!="") { - $hstring="host=$server "; + $connectVars['host'] = $server; } if ($port!=false && $port!="") { - $hstring .= "port=$port "; + $connectVars['port'] = $port; } + $connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW ); - error_reporting( E_ALL ); - @$this->mConn = pg_connect("$hstring dbname=$dbName user=$user password=$password"); + $this->installErrorHandler(); + $this->mConn = pg_connect( $connectString ); + $phpError = $this->restoreErrorHandler(); - if ( $this->mConn == false ) { + if ( !$this->mConn ) { wfDebug( "DB connection error\n" ); wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); wfDebug( $this->lastError()."\n" ); - return false; + if ( !$this->mFailFunction ) { + throw new DBConnectionError( $this, $phpError ); + } else { + return false; + } } $this->mOpened = true; @@ -171,9 +200,11 @@ class DatabasePostgres extends Database { global $wgCommandLineMode; ## If called from the command-line (e.g. importDump), only show errors if ($wgCommandLineMode) { - $this->doQuery("SET client_min_messages = 'ERROR'"); + $this->doQuery( "SET client_min_messages = 'ERROR'" ); } + $this->doQuery( "SET client_encoding='UTF8'" ); + global $wgDBmwschema, $wgDBts2schema; if (isset( $wgDBmwschema ) && isset( $wgDBts2schema ) && $wgDBmwschema !== 'mediawiki' @@ -182,39 +213,47 @@ class DatabasePostgres extends Database { ) { $safeschema = $this->quote_ident($wgDBmwschema); $safeschema2 = $this->quote_ident($wgDBts2schema); - $this->doQuery("SET search_path = $safeschema, $wgDBts2schema, public"); + $this->doQuery( "SET search_path = $safeschema, $wgDBts2schema, public" ); } return $this->mConn; } + function makeConnectionString( $vars ) { + $s = ''; + foreach ( $vars as $name => $value ) { + $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' "; + } + return $s; + } + - function initial_setup($password, $dbName) { + function initial_setup($superuser, $password, $dbName) { // If this is the initial connection, setup the schema stuff and possibly create the user - global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, $wgDBts2schema; + global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBmwschema, $wgDBts2schema; print "
  • Checking the version of Postgres..."; $version = $this->getServerVersion(); $PGMINVER = '8.1'; - if ($this->numeric_version < $PGMINVER) { - print "FAILED. Required version is $PGMINVER. You have $this->numeric_version ($version)
  • \n"; + if ($version < $PGMINVER) { + print "FAILED. Required version is $PGMINVER. You have " . htmlspecialchars( $version ) . "\n"; dieout(""); } - print "version $this->numeric_version is OK.\n"; + print "version " . htmlspecialchars( $this->numeric_version ) . " is OK.\n"; $safeuser = $this->quote_ident($wgDBuser); // Are we connecting as a superuser for the first time? - if ($wgDBsuperuser) { + if ($superuser) { // Are we really a superuser? Check out our rights $SQL = "SELECT CASE WHEN usesuper IS TRUE THEN CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END END AS rights - FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBsuperuser); + FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($superuser); $rows = $this->numRows($res = $this->doQuery($SQL)); if (!$rows) { - print "
  • ERROR: Could not read permissions for user \"$wgDBsuperuser\"
  • \n"; + print "
  • ERROR: Could not read permissions for user \"" . htmlspecialchars( $superuser ) . "\"
  • \n"; dieout(''); } $perms = pg_fetch_result($res, 0, 0); @@ -222,15 +261,15 @@ class DatabasePostgres extends Database { $SQL = "SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBuser); $rows = $this->numRows($this->doQuery($SQL)); if ($rows) { - print "
  • User \"$wgDBuser\" already exists, skipping account creation.
  • "; + print "
  • User \"" . htmlspecialchars( $wgDBuser ) . "\" already exists, skipping account creation.
  • "; } else { if ($perms != 1 and $perms != 3) { - print "
  • ERROR: the user \"$wgDBsuperuser\" cannot create other users. "; + print "
  • ERROR: the user \"" . htmlspecialchars( $superuser ) . "\" cannot create other users. "; print 'Please use a different Postgres user.
  • '; dieout(''); } - print "
  • Creating user $wgDBuser..."; + print "
  • Creating user " . htmlspecialchars( $wgDBuser ) . "..."; $safepass = $this->addQuotes($wgDBpassword); $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass"; $this->doQuery($SQL); @@ -241,15 +280,15 @@ class DatabasePostgres extends Database { $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this->addQuotes($wgDBname); $rows = $this->numRows($this->doQuery($SQL)); if ($rows) { - print "
  • Database \"$wgDBname\" already exists, skipping database creation.
  • "; + print "
  • Database \"" . htmlspecialchars( $wgDBname ) . "\" already exists, skipping database creation.
  • "; } else { - if ($perms < 2) { - print "
  • ERROR: the user \"$wgDBsuperuser\" cannot create databases. "; + if ($perms < 1) { + print "
  • ERROR: the user \"" . htmlspecialchars( $superuser ) . "\" cannot create databases. "; print 'Please use a different Postgres user.
  • '; dieout(''); } - print "
  • Creating database $wgDBname..."; + print "
  • Creating database " . htmlspecialchars( $wgDBname ) . "..."; $safename = $this->quote_ident($wgDBname); $SQL = "CREATE DATABASE $safename OWNER $safeuser "; $this->doQuery($SQL); @@ -258,18 +297,22 @@ class DatabasePostgres extends Database { } // Reconnect to check out tsearch2 rights for this user - print "
  • Connecting to \"$wgDBname\" as superuser \"$wgDBsuperuser\" to check rights..."; + print "
  • Connecting to \"" . htmlspecialchars( $wgDBname ) . "\" as superuser \"" . + htmlspecialchars( $superuser ) . "\" to check rights..."; - $hstring=""; + $connectVars = array(); if ($this->mServer!=false && $this->mServer!="") { - $hstring="host=$this->mServer "; + $connectVars['host'] = $this->mServer; } if ($this->mPort!=false && $this->mPort!="") { - $hstring .= "port=$this->mPort "; + $connectVars['port'] = $this->mPort; } + $connectVars['dbname'] = $wgDBname; + $connectVars['user'] = $superuser; + $connectVars['password'] = $password; - @$this->mConn = pg_connect("$hstring dbname=$wgDBname user=$wgDBsuperuser password=$password"); - if ( $this->mConn == false ) { + @$this->mConn = pg_connect( $this->makeConnectionString( $connectVars ) ); + if ( !$this->mConn ) { print "FAILED TO CONNECT!
  • "; dieout(""); } @@ -278,15 +321,18 @@ class DatabasePostgres extends Database { if ($this->numeric_version < 8.3) { // Tsearch2 checks - print "
  • Checking that tsearch2 is installed in the database \"$wgDBname\"..."; + print "
  • Checking that tsearch2 is installed in the database \"" . + htmlspecialchars( $wgDBname ) . "\"..."; if (! $this->tableExists("pg_ts_cfg", $wgDBts2schema)) { - print "FAILED. tsearch2 must be installed in the database \"$wgDBname\"."; + print "FAILED. tsearch2 must be installed in the database \"" . + htmlspecialchars( $wgDBname ) . "\"."; print "Please see this article"; print " for instructions or ask on #postgresql on irc.freenode.net
  • \n"; dieout(""); } print "OK\n"; - print "
  • Ensuring that user \"$wgDBuser\" has select rights on the tsearch2 tables..."; + print "
  • Ensuring that user \"" . htmlspecialchars( $wgDBuser ) . + "\" has select rights on the tsearch2 tables..."; foreach (array('cfg','cfgmap','dict','parser') as $table) { $SQL = "GRANT SELECT ON pg_ts_$table TO $safeuser"; $this->doQuery($SQL); @@ -298,7 +344,7 @@ class DatabasePostgres extends Database { $result = $this->schemaExists($wgDBmwschema); $safeschema = $this->quote_ident($wgDBmwschema); if (!$result) { - print "
  • Creating schema $wgDBmwschema ..."; + print "
  • Creating schema " . htmlspecialchars( $wgDBmwschema ) . " ..."; $result = $this->doQuery("CREATE SCHEMA $safeschema AUTHORIZATION $safeuser"); if (!$result) { print "FAILED.
  • \n"; @@ -335,7 +381,7 @@ class DatabasePostgres extends Database { // Install plpgsql if needed $this->setup_plpgsql(); - $wgDBsuperuser = ''; + $superuser = ''; return true; // Reconnect as regular user } // end superuser @@ -344,7 +390,7 @@ class DatabasePostgres extends Database { if ($this->numeric_version < 8.3) { // Do we have the basic tsearch2 table? - print "
  • Checking for tsearch2 in the schema \"$wgDBts2schema\"..."; + print "
  • Checking for tsearch2 in the schema \"" . htmlspecialchars( $wgDBts2schema ) . "\"..."; if (! $this->tableExists("pg_ts_dict", $wgDBts2schema)) { print "FAILED. Make sure tsearch2 is installed. See this article"; @@ -364,12 +410,13 @@ class DatabasePostgres extends Database { $SQL = "SELECT count(*) FROM $safetsschema.pg_ts_$tname"; $res = $this->doQuery($SQL); if (!$res) { - print "FAILED to access pg_ts_$tname. Make sure that the user ". - "\"$wgDBuser\" has SELECT access to all four tsearch2 tables
  • \n"; + print "FAILED to access " . htmlspecialchars( "pg_ts_$tname" ) . + ". Make sure that the user \"". htmlspecialchars( $wgDBuser ) . + "\" has SELECT access to all four tsearch2 tables\n"; dieout(""); } } - $SQL = "SELECT ts_name FROM $safetsschema.pg_ts_cfg WHERE locale = '$ctype'"; + $SQL = "SELECT ts_name FROM $safetsschema.pg_ts_cfg WHERE locale = " . $this->addQuotes( $ctype ) ; $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END"; $res = $this->doQuery($SQL); error_reporting( E_ALL ); @@ -380,28 +427,30 @@ class DatabasePostgres extends Database { print "OK"; // Will the current locale work? Can we force it to? - print "
  • Verifying tsearch2 locale with $ctype..."; + print "
  • Verifying tsearch2 locale with " . htmlspecialchars( $ctype ) . "..."; $rows = $this->numRows($res); $resetlocale = 0; if (!$rows) { print "not found
  • \n"; - print "
  • Attempting to set default tsearch2 locale to \"$ctype\"..."; + print "
  • Attempting to set default tsearch2 locale to \"" . htmlspecialchars( $ctype ) . "\"..."; $resetlocale = 1; } else { $tsname = pg_fetch_result($res, 0, 0); if ($tsname != 'default') { - print "not set to default ($tsname)"; - print "
  • Attempting to change tsearch2 default locale to \"$ctype\"..."; + print "not set to default (" . htmlspecialchars( $tsname ) . ")"; + print "
  • Attempting to change tsearch2 default locale to \"" . + htmlspecialchars( $ctype ) . "\"..."; $resetlocale = 1; } } if ($resetlocale) { - $SQL = "UPDATE $safetsschema.pg_ts_cfg SET locale = '$ctype' WHERE ts_name = 'default'"; + $SQL = "UPDATE $safetsschema.pg_ts_cfg SET locale = " . $this->addQuotes( $ctype ) . " WHERE ts_name = 'default'"; $res = $this->doQuery($SQL); if (!$res) { print "FAILED. "; - print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"$ctype\"
  • \n"; + print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"" . + htmlspecialchars( $ctype ) . "\"\n"; dieout(""); } print "OK"; @@ -411,7 +460,7 @@ class DatabasePostgres extends Database { $SQL = "SELECT $safetsschema.to_tsvector('default','MediaWiki tsearch2 testing')"; $res = $this->doQuery($SQL); if (!$res) { - print "FAILED. Specifically, \"$SQL\" did not work."; + print "FAILED. Specifically, \"" . htmlspecialchars( $SQL ) . "\" did not work."; dieout(""); } print "OK"; @@ -423,28 +472,32 @@ class DatabasePostgres extends Database { // Does the schema already exist? Who owns it? $result = $this->schemaExists($wgDBmwschema); if (!$result) { - print "
  • Creating schema $wgDBmwschema ..."; + print "
  • Creating schema " . htmlspecialchars( $wgDBmwschema ) . " ..."; error_reporting( 0 ); $safeschema = $this->quote_ident($wgDBmwschema); $result = $this->doQuery("CREATE SCHEMA $safeschema"); error_reporting( E_ALL ); if (!$result) { - print "FAILED. The user \"$wgDBuser\" must be able to access the schema. ". + print "FAILED. The user \"" . htmlspecialchars( $wgDBuser ) . + "\" must be able to access the schema. ". "You can try making them the owner of the database, or try creating the schema with a ". - "different user, and then grant access to the \"$wgDBuser\" user.
  • \n"; + "different user, and then grant access to the \"" . + htmlspecialchars( $wgDBuser ) . "\" user.\n"; dieout(""); } print "OK\n"; } else if ($result != $wgDBuser) { - print "
  • Schema \"$wgDBmwschema\" exists but is not owned by \"$wgDBuser\". Not ideal.
  • \n"; + print "
  • Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists but is not owned by \"" . + htmlspecialchars( $wgDBuser ) . "\". Not ideal.
  • \n"; } else { - print "
  • Schema \"$wgDBmwschema\" exists and is owned by \"$wgDBuser\". Excellent.
  • \n"; + print "
  • Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists and is owned by \"" . + htmlspecialchars( $wgDBuser ) . "\". Excellent.
  • \n"; } // Always return GMT time to accomodate the existing integer-based timestamp assumption - print "
  • Setting the timezone to GMT for user \"$wgDBuser\" ..."; + print "
  • Setting the timezone to GMT for user \"" . htmlspecialchars( $wgDBuser ) . "\" ..."; $SQL = "ALTER USER $safeuser SET timezone = 'GMT'"; $result = pg_query($this->mConn, $SQL); if (!$result) { @@ -460,7 +513,7 @@ class DatabasePostgres extends Database { dieout(""); } - print "
  • Setting the datestyle to ISO, YMD for user \"$wgDBuser\" ..."; + print "
  • Setting the datestyle to ISO, YMD for user \"" . htmlspecialchars( $wgDBuser ) . "\" ..."; $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'"; $result = pg_query($this->mConn, $SQL); if (!$result) { @@ -477,7 +530,7 @@ class DatabasePostgres extends Database { } // Fix up the search paths if needed - print "
  • Setting the search path for user \"$wgDBuser\" ..."; + print "
  • Setting the search path for user \"" . htmlspecialchars( $wgDBuser ) . "\" ..."; $path = $this->quote_ident($wgDBmwschema); if ($wgDBts2schema !== $wgDBmwschema) $path .= ", ". $this->quote_ident($wgDBts2schema); @@ -503,27 +556,30 @@ class DatabasePostgres extends Database { function setup_plpgsql() { - print "
  • Checking for Pl/Pgsql ..."; + print "
  • Checking for PL/pgSQL ..."; $SQL = "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'"; $rows = $this->numRows($this->doQuery($SQL)); if ($rows < 1) { // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it - print "not installed. Attempting to install Pl/Pgsql ..."; + print "not installed. Attempting to install PL/pgSQL ..."; $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ". "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'"; $rows = $this->numRows($this->doQuery($SQL)); + global $wgDBname; if ($rows >= 1) { - $olde = error_reporting(0); + $olde = error_reporting(0); error_reporting($olde - E_WARNING); $result = $this->doQuery("CREATE LANGUAGE plpgsql"); error_reporting($olde); if (!$result) { - print "FAILED. You need to install the language plpgsql in the database $wgDBname
  • "; + print "FAILED. You need to install the language PL/pgSQL in the database " . + htmlspecialchars( $wgDBname ) . ""; dieout(""); } } else { - print "FAILED. You need to install the language plpgsql in the database $wgDBname"; + print "FAILED. You need to install the language PL/pgSQL in the database " . + htmlspecialchars( $wgDBname ) . ""; dieout(""); } } @@ -546,9 +602,11 @@ class DatabasePostgres extends Database { function doQuery( $sql ) { if (function_exists('mb_convert_encoding')) { - return $this->mLastResult=pg_query( $this->mConn , mb_convert_encoding($sql,'UTF-8') ); + $sql = mb_convert_encoding($sql,'UTF-8'); } - return $this->mLastResult=pg_query( $this->mConn , $sql); + $this->mLastResult = pg_query( $this->mConn, $sql); + $this->mAffectedRows = null; // use pg_affected_rows(mLastResult) + return $this->mLastResult; } function queryIgnore( $sql, $fname = '' ) { @@ -641,9 +699,12 @@ class DatabasePostgres extends Database { } function affectedRows() { - if( !isset( $this->mLastResult ) or ! $this->mLastResult ) + if ( !is_null( $this->mAffectedRows ) ) { + // Forced result for simulated queries + return $this->mAffectedRows; + } + if( empty( $this->mLastResult ) ) return 0; - return pg_affected_rows( $this->mLastResult ); } @@ -655,7 +716,7 @@ class DatabasePostgres extends Database { * Takes same arguments as Database::select() */ - function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { + function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) { $options['EXPLAIN'] = true; $res = $this->select( $table, $vars, $conds, $fname, $options ); $rows = -1; @@ -675,26 +736,28 @@ class DatabasePostgres extends Database { * Returns information about an index * If errors are explicitly ignored, returns NULL on failure */ - function indexInfo( $table, $index, $fname = 'Database::indexExists' ) { + function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) { $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'"; $res = $this->query( $sql, $fname ); if ( !$res ) { - return NULL; + return null; } while ( $row = $this->fetchObject( $res ) ) { - if ( $row->indexname == $index ) { + if ( $row->indexname == $this->indexName( $index ) ) { return $row; } } return false; } - function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { + function indexUnique ($table, $index, $fname = 'DatabasePostgres::indexUnique' ) { $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'". - " AND indexdef LIKE 'CREATE UNIQUE%({$index})'"; + " AND indexdef LIKE 'CREATE UNIQUE%(" . + $this->strencode( $this->indexName( $index ) ) . + ")'"; $res = $this->query( $sql, $fname ); if ( !$res ) - return NULL; + return null; while ($row = $this->fetchObject( $res )) return true; return false; @@ -707,24 +770,21 @@ class DatabasePostgres extends Database { * $args may be a single associative array, or an array of these with numeric keys, * for multi-row insert (Postgres version 8.2 and above only). * - * @param array $table String: Name of the table to insert to. - * @param array $args Array: Items to insert into the table. - * @param array $fname String: Name of the function, for profiling - * @param mixed $options String or Array. Valid options: IGNORE + * @param $table String: Name of the table to insert to. + * @param $args Array: Items to insert into the table. + * @param $fname String: Name of the function, for profiling + * @param $options String or Array. Valid options: IGNORE * * @return bool Success of insert operation. IGNORE always returns true. */ function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) { - global $wgDBversion; - if ( !count( $args ) ) { return true; } $table = $this->tableName( $table ); - if (! isset( $wgDBversion ) ) { + if (! isset( $this->numeric_version ) ) { $this->getServerVersion(); - $wgDBversion = $this->numeric_version; } if ( !is_array( $options ) ) @@ -758,7 +818,7 @@ class DatabasePostgres extends Database { $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; if ( $multi ) { - if ( $wgDBversion >= 8.2 && !$ignore ) { + if ( $this->numeric_version >= 8.2 && !$ignore ) { $first = true; foreach ( $args as $row ) { if ( $first ) { @@ -809,7 +869,6 @@ class DatabasePostgres extends Database { $sql .= '(' . $this->makeList( $args ) . ')'; $res = (bool)$this->query( $sql, $fname, $ignore ); - if ( $ignore ) { $bar = pg_last_error(); if ($bar != false) { @@ -821,13 +880,15 @@ class DatabasePostgres extends Database { } } } - if ( $ignore ) { $olde = error_reporting( $olde ); if ($didbegin) { $this->commit(); } + // Set the affected row count for the whole operation + $this->mAffectedRows = $numrowsinserted; + // IGNORE always returns true return true; } @@ -837,6 +898,81 @@ class DatabasePostgres extends Database { } + /** + * INSERT SELECT wrapper + * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...) + * Source items may be literals rather then field names, but strings should be quoted with Database::addQuotes() + * $conds may be "*" to copy the whole table + * srcTable may be an array of tables. + * @todo FIXME: implement this a little better (seperate select/insert)? + */ + function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect', + $insertOptions = array(), $selectOptions = array() ) + { + $destTable = $this->tableName( $destTable ); + + // If IGNORE is set, we use savepoints to emulate mysql's behavior + $ignore = in_array( 'IGNORE', $insertOptions ) ? 'mw' : ''; + + if( is_array( $insertOptions ) ) { + $insertOptions = implode( ' ', $insertOptions ); + } + if( !is_array( $selectOptions ) ) { + $selectOptions = array( $selectOptions ); + } + list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions ); + if( is_array( $srcTable ) ) { + $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) ); + } else { + $srcTable = $this->tableName( $srcTable ); + } + + // If we are not in a transaction, we need to be for savepoint trickery + $didbegin = 0; + if ( $ignore ) { + if( !$this->mTrxLevel ) { + $this->begin(); + $didbegin = 1; + } + $olde = error_reporting( 0 ); + $numrowsinserted = 0; + pg_query( $this->mConn, "SAVEPOINT $ignore"); + } + + $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' . + " SELECT $startOpts " . implode( ',', $varMap ) . + " FROM $srcTable $useIndex"; + + if ( $conds != '*') { + $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND ); + } + + $sql .= " $tailOpts"; + + $res = (bool)$this->query( $sql, $fname, $ignore ); + if( $ignore ) { + $bar = pg_last_error(); + if( $bar != false ) { + pg_query( $this->mConn, "ROLLBACK TO $ignore" ); + } else { + pg_query( $this->mConn, "RELEASE $ignore" ); + $numrowsinserted++; + } + $olde = error_reporting( $olde ); + if( $didbegin ) { + $this->commit(); + } + + // Set the affected row count for the whole operation + $this->mAffectedRows = $numrowsinserted; + + // IGNORE always returns true + return true; + } + + return $res; + } + function tableName( $name ) { # Replace reserved words with better ones switch( $name ) { @@ -862,7 +998,7 @@ class DatabasePostgres extends Database { } /** - * Return the current value of a sequence. Assumes it has ben nextval'ed in this session. + * Return the current value of a sequence. Assumes it has been nextval'ed in this session. */ function currentSequenceValue( $seqName ) { $safeseq = preg_replace( "/'/", "''", $seqName ); @@ -873,13 +1009,6 @@ class DatabasePostgres extends Database { return $currval; } - /** - * Postgres does not have a "USE INDEX" clause, so return an empty string - */ - function useIndexClause( $index ) { - return ''; - } - # REPLACE query wrapper # Postgres simulates this with a DELETE followed by INSERT # $row is the row to insert, an associative array @@ -889,7 +1018,7 @@ class DatabasePostgres extends Database { # It may be more efficient to leave off unique indexes which are unlikely to collide. # However if you do this, you run the risk of encountering errors which wouldn't have # occurred in MySQL - function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) { + function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) { $table = $this->tableName( $table ); if (count($rows)==0) { @@ -939,7 +1068,7 @@ class DatabasePostgres extends Database { } # DELETE where the condition is a join - function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { + function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabasePostgres::deleteJoin' ) { if ( !$conds ) { throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); } @@ -973,31 +1102,18 @@ class DatabasePostgres extends Database { return $size; } - function lowPriorityOption() { - return ''; - } - function limitResult($sql, $limit, $offset=false) { return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":""); } - /** - * Returns an SQL expression for a simple conditional. - * Uses CASE on Postgres - * - * @param string $cond SQL expression which will result in a boolean value - * @param string $trueVal SQL expression to return if true - * @param string $falseVal SQL expression to return if false - * @return string SQL fragment - */ - function conditional( $cond, $trueVal, $falseVal ) { - return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; - } - function wasDeadlock() { return $this->lastErrno() == '40P01'; } + function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) { + return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname ); + } + function timestamp( $ts=0 ) { return wfTimestamp(TS_POSTGRES,$ts); } @@ -1031,7 +1147,7 @@ class DatabasePostgres extends Database { /** * @return string wikitext of a link to the server software's web site */ - function getSoftwareLink() { + function getSoftwareLink() { return "[http://www.postgresql.org/ PostgreSQL]"; } @@ -1039,28 +1155,34 @@ class DatabasePostgres extends Database { * @return string Version information from the database */ function getServerVersion() { - $version = pg_fetch_result($this->doQuery("SELECT version()"),0,0); - $thisver = array(); - if (!preg_match('/PostgreSQL (\d+\.\d+)(\S+)/', $version, $thisver)) { - die("Could not determine the numeric version from $version!"); + if ( !isset( $this->numeric_version ) ) { + $versionInfo = pg_version( $this->mConn ); + if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) { + // Old client, abort install + $this->numeric_version = '7.3 or earlier'; + } elseif ( isset( $versionInfo['server'] ) ) { + // Normal client + $this->numeric_version = $versionInfo['server']; + } else { + // Bug 16937: broken pgsql extension from PHP<5.3 + $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' ); + } } - $this->numeric_version = $thisver[1]; - return $version; + return $this->numeric_version; } - /** * Query whether a given relation exists (in the given schema, or the * default mw one if not given) */ function relationExists( $table, $types, $schema = false ) { global $wgDBmwschema; - if (!is_array($types)) - $types = array($types); - if (! $schema ) + if ( !is_array( $types ) ) + $types = array( $types ); + if ( !$schema ) $schema = $wgDBmwschema; - $etable = $this->addQuotes($table); - $eschema = $this->addQuotes($schema); + $etable = $this->addQuotes( $table ); + $eschema = $this->addQuotes( $schema ); $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n " . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema " . "AND c.relkind IN ('" . implode("','", $types) . "')"; @@ -1075,44 +1197,44 @@ class DatabasePostgres extends Database { * For backward compatibility, this function checks both tables and * views. */ - function tableExists ($table, $schema = false) { - return $this->relationExists($table, array('r', 'v'), $schema); + function tableExists( $table, $schema = false ) { + return $this->relationExists( $table, array( 'r', 'v' ), $schema ); } - function sequenceExists ($sequence, $schema = false) { - return $this->relationExists($sequence, 'S', $schema); + function sequenceExists( $sequence, $schema = false ) { + return $this->relationExists( $sequence, 'S', $schema ); } - function triggerExists($table, $trigger) { + function triggerExists( $table, $trigger ) { global $wgDBmwschema; - $q = <<query(sprintf($q, $this->addQuotes($wgDBmwschema), $this->addQuotes($table), $this->addQuotes($trigger))); if (!$res) - return NULL; + return null; $rows = $res->numRows(); - $this->freeResult($res); + $this->freeResult( $res ); return $rows; } - function ruleExists($table, $rule) { + function ruleExists( $table, $rule ) { global $wgDBmwschema; $exists = $this->selectField("pg_rules", "rulename", array( "rulename" => $rule, "tablename" => $table, - "schemaname" => $wgDBmwschema)); + "schemaname" => $wgDBmwschema ) ); return $exists === $rule; } - function constraintExists($table, $constraint) { + function constraintExists( $table, $constraint ) { global $wgDBmwschema; $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ". "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", @@ -1121,7 +1243,7 @@ END; $this->addQuotes($constraint)); $res = $this->query($SQL); if (!$res) - return NULL; + return null; $rows = $res->numRows(); $this->freeResult($res); return $rows; @@ -1146,42 +1268,32 @@ END; return $owner; } - /** - * Query whether a given column exists in the mediawiki schema - */ - function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) { - global $wgDBmwschema; - $etable = preg_replace("/'/", "''", $table); - $eschema = preg_replace("/'/", "''", $wgDBmwschema); - $ecol = preg_replace("/'/", "''", $field); - $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a " - . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' " - . "AND a.attrelid = c.oid AND a.attname = '$ecol'"; - $res = $this->query( $SQL, $fname ); - $count = $res ? $res->numRows() : 0; - if ($res) - $this->freeResult( $res ); - return $count; - } - function fieldInfo( $table, $field ) { return PostgresField::fromText($this, $table, $field); } + + /** + * pg_field_type() wrapper + */ + function fieldType( $res, $index ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + return pg_field_type( $res, $index ); + } function begin( $fname = 'DatabasePostgres::begin' ) { $this->query( 'BEGIN', $fname ); $this->mTrxLevel = 1; } - function immediateCommit( $fname = 'DatabasePostgres::immediateCommit' ) { - return true; - } + function commit( $fname = 'DatabasePostgres::commit' ) { $this->query( 'COMMIT', $fname ); $this->mTrxLevel = 0; } /* Not even sure why this is used in the main codebase... */ - function limitResultForUpdate($sql, $num) { + function limitResultForUpdate( $sql, $num ) { return $sql; } @@ -1200,12 +1312,19 @@ END; $res = $this->doQuery($SQL); error_reporting( $olde ); if (!$res) { - print "FAILED. Make sure that the user \"$wgDBuser\" can write to the schema \"$wgDBmwschema\"\n"; - dieout(""); + print "FAILED. Make sure that the user \"" . htmlspecialchars( $wgDBuser ) . + "\" can write to the schema \"" . htmlspecialchars( $wgDBmwschema ) . "\"\n"; + dieout(""); # Will close the main list