Move DatabasePostgres to /libs/rdbms

Fixed all IDEA warnings in the postgres classes too.

Change-Id: I06b4c5b2c581fb65552d980cc106aa10fed40285
This commit is contained in:
Aaron Schulz 2016-09-19 10:37:57 -07:00
parent 62ebd86b32
commit adfd1b93d9
5 changed files with 265 additions and 229 deletions

View file

@ -327,7 +327,7 @@ $wgAutoloadLocalClasses = [
'DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php', 'DatabaseMysqlBase' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqlBase.php',
'DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php', 'DatabaseMysqli' => __DIR__ . '/includes/libs/rdbms/database/DatabaseMysqli.php',
'DatabaseOracle' => __DIR__ . '/includes/db/DatabaseOracle.php', 'DatabaseOracle' => __DIR__ . '/includes/db/DatabaseOracle.php',
'DatabasePostgres' => __DIR__ . '/includes/db/DatabasePostgres.php', 'DatabasePostgres' => __DIR__ . '/includes/libs/rdbms/database/DatabasePostgres.php',
'DatabaseSqlite' => __DIR__ . '/includes/libs/rdbms/database/DatabaseSqlite.php', 'DatabaseSqlite' => __DIR__ . '/includes/libs/rdbms/database/DatabaseSqlite.php',
'DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php', 'DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php',
'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php', 'DateFormats' => __DIR__ . '/maintenance/language/date-formats.php',
@ -1071,7 +1071,7 @@ $wgAutoloadLocalClasses = [
'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php', 'PopulateRevisionSha1' => __DIR__ . '/maintenance/populateRevisionSha1.php',
'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php', 'PostgreSqlLockManager' => __DIR__ . '/includes/filebackend/lockmanager/PostgreSqlLockManager.php',
'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php', 'PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php',
'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php', 'PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php',
'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php', 'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php', 'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
'Preferences' => __DIR__ . '/includes/Preferences.php', 'Preferences' => __DIR__ . '/includes/Preferences.php',
@ -1226,7 +1226,7 @@ $wgAutoloadLocalClasses = [
'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php', 'SVGReader' => __DIR__ . '/includes/media/SVGMetadataExtractor.php',
'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php', 'SamplingStatsdClient' => __DIR__ . '/includes/libs/SamplingStatsdClient.php',
'Sanitizer' => __DIR__ . '/includes/Sanitizer.php', 'Sanitizer' => __DIR__ . '/includes/Sanitizer.php',
'SavepointPostgres' => __DIR__ . '/includes/db/DatabasePostgres.php', 'SavepointPostgres' => __DIR__ . '/includes/libs/rdbms/database/utils/SavepointPostgres.php',
'ScopedCallback' => __DIR__ . '/includes/libs/ScopedCallback.php', 'ScopedCallback' => __DIR__ . '/includes/libs/ScopedCallback.php',
'ScopedLock' => __DIR__ . '/includes/filebackend/lockmanager/ScopedLock.php', 'ScopedLock' => __DIR__ . '/includes/filebackend/lockmanager/ScopedLock.php',
'SearchApi' => __DIR__ . '/includes/api/SearchApi.php', 'SearchApi' => __DIR__ . '/includes/api/SearchApi.php',

View file

@ -62,6 +62,8 @@ abstract class LBFactoryMW {
foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) { foreach ( $mainConfig->get( 'DBservers' ) as $i => $server ) {
if ( $server['type'] === 'sqlite' ) { if ( $server['type'] === 'sqlite' ) {
$server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ]; $server += [ 'dbDirectory' => $mainConfig->get( 'SQLiteDataDir' ) ];
} elseif ( $server['type'] === 'postgres' ) {
$server += [ 'port' => $mainConfig->get( 'DBport' ) ];
} }
$lbConf['servers'][$i] = $server + [ $lbConf['servers'][$i] = $server + [
'schema' => $mainConfig->get( 'DBmwschema' ), 'schema' => $mainConfig->get( 'DBmwschema' ),
@ -91,6 +93,8 @@ abstract class LBFactoryMW {
]; ];
if ( $server['type'] === 'sqlite' ) { if ( $server['type'] === 'sqlite' ) {
$server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' ); $server[ 'dbDirectory'] = $mainConfig->get( 'SQLiteDataDir' );
} elseif ( $server['type'] === 'postgres' ) {
$server['port'] = $mainConfig->get( 'DBport' );
} }
$lbConf['servers'] = [ $server ]; $lbConf['servers'] = [ $server ];
} }

View file

@ -21,208 +21,32 @@
* @ingroup Database * @ingroup Database
*/ */
class PostgresField implements Field {
private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
$has_default, $default;
/**
* @param IDatabase $db
* @param string $table
* @param string $field
* @return null|PostgresField
*/
static function fromText( $db, $table, $field ) {
$q = <<<SQL
SELECT
attnotnull, attlen, conname AS conname,
atthasdef,
adsrc,
COALESCE(condeferred, 'f') AS deferred,
COALESCE(condeferrable, 'f') AS deferrable,
CASE WHEN typname = 'int2' THEN 'smallint'
WHEN typname = 'int4' THEN 'integer'
WHEN typname = 'int8' THEN 'bigint'
WHEN typname = 'bpchar' THEN 'char'
ELSE typname END AS typname
FROM pg_class c
JOIN pg_namespace n ON (n.oid = c.relnamespace)
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
WHERE relkind = 'r'
AND nspname=%s
AND relname=%s
AND attname=%s;
SQL;
$table = $db->tableName( $table, 'raw' );
$res = $db->query(
sprintf( $q,
$db->addQuotes( $db->getCoreSchema() ),
$db->addQuotes( $table ),
$db->addQuotes( $field )
)
);
$row = $db->fetchObject( $res );
if ( !$row ) {
return null;
}
$n = new PostgresField;
$n->type = $row->typname;
$n->nullable = ( $row->attnotnull == 'f' );
$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;
$n->has_default = ( $row->atthasdef === 't' );
$n->default = $row->adsrc;
return $n;
}
function name() {
return $this->name;
}
function tableName() {
return $this->tablename;
}
function type() {
return $this->type;
}
function isNullable() {
return $this->nullable;
}
function maxLength() {
return $this->max_length;
}
function is_deferrable() {
return $this->deferrable;
}
function is_deferred() {
return $this->deferred;
}
function conname() {
return $this->conname;
}
/**
* @since 1.19
* @return bool|mixed
*/
function defaultValue() {
if ( $this->has_default ) {
return $this->default;
} else {
return false;
}
}
}
/**
* Manage savepoints within a transaction
* @ingroup Database
* @since 1.19
*/
class SavepointPostgres {
/** @var DatabasePostgres Establish a savepoint within a transaction */
protected $dbw;
protected $id;
protected $didbegin;
/**
* @param IDatabase $dbw
* @param int $id
*/
public function __construct( $dbw, $id ) {
$this->dbw = $dbw;
$this->id = $id;
$this->didbegin = false;
/* If we are not in a transaction, we need to be for savepoint trickery */
if ( !$dbw->trxLevel() ) {
$dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
$this->didbegin = true;
}
}
public function __destruct() {
if ( $this->didbegin ) {
$this->dbw->rollback();
$this->didbegin = false;
}
}
public function commit() {
if ( $this->didbegin ) {
$this->dbw->commit();
$this->didbegin = false;
}
}
protected function query( $keyword, $msg_ok, $msg_failed ) {
if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
} else {
wfDebug( sprintf( $msg_failed, $this->id ) );
}
}
public function savepoint() {
$this->query( "SAVEPOINT",
"Transaction state: savepoint \"%s\" established.\n",
"Transaction state: establishment of savepoint \"%s\" FAILED.\n"
);
}
public function release() {
$this->query( "RELEASE",
"Transaction state: savepoint \"%s\" released.\n",
"Transaction state: release of savepoint \"%s\" FAILED.\n"
);
}
public function rollback() {
$this->query( "ROLLBACK TO",
"Transaction state: savepoint \"%s\" rolled back.\n",
"Transaction state: rollback of savepoint \"%s\" FAILED.\n"
);
}
public function __toString() {
return (string)$this->id;
}
}
/** /**
* @ingroup Database * @ingroup Database
*/ */
class DatabasePostgres extends DatabaseBase { class DatabasePostgres extends DatabaseBase {
/** @var int|bool */
protected $port;
/** @var resource */ /** @var resource */
protected $mLastResult = null; protected $mLastResult = null;
/** @var int The number of rows affected as an integer */ /** @var int The number of rows affected as an integer */
protected $mAffectedRows = null; protected $mAffectedRows = null;
/** @var int */ /** @var int */
private $mInsertId = null; private $mInsertId = null;
/** @var float|string */ /** @var float|string */
private $numericVersion = null; private $numericVersion = null;
/** @var string Connect string to open a PostgreSQL connection */ /** @var string Connect string to open a PostgreSQL connection */
private $connectString; private $connectString;
/** @var string */ /** @var string */
private $mCoreSchema; private $mCoreSchema;
public function __construct( array $params ) {
parent::__construct( $params );
$this->port = isset( $params['port'] ) ? $params['port'] : false;
}
function getType() { function getType() {
return 'postgres'; return 'postgres';
} }
@ -265,14 +89,11 @@ class DatabasePostgres extends DatabaseBase {
); );
} }
global $wgDBport;
if ( !strlen( $user ) ) { # e.g. the class is being loaded if ( !strlen( $user ) ) { # e.g. the class is being loaded
return null; return null;
} }
$this->mServer = $server; $this->mServer = $server;
$port = $wgDBport;
$this->mUser = $user; $this->mUser = $user;
$this->mPassword = $password; $this->mPassword = $password;
$this->mDBname = $dbName; $this->mDBname = $dbName;
@ -285,14 +106,14 @@ class DatabasePostgres extends DatabaseBase {
if ( $server != false && $server != '' ) { if ( $server != false && $server != '' ) {
$connectVars['host'] = $server; $connectVars['host'] = $server;
} }
if ( $port != false && $port != '' ) { if ( (int)$this->port > 0 ) {
$connectVars['port'] = $port; $connectVars['port'] = (int)$this->port;
} }
if ( $this->mFlags & DBO_SSL ) { if ( $this->mFlags & DBO_SSL ) {
$connectVars['sslmode'] = 1; $connectVars['sslmode'] = 1;
} }
$this->connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW ); $this->connectString = $this->makeConnectionString( $connectVars );
$this->close(); $this->close();
$this->installErrorHandler(); $this->installErrorHandler();
@ -306,18 +127,18 @@ class DatabasePostgres extends DatabaseBase {
$phpError = $this->restoreErrorHandler(); $phpError = $this->restoreErrorHandler();
if ( !$this->mConn ) { if ( !$this->mConn ) {
wfDebug( "DB connection error\n" ); $this->queryLogger->debug( "DB connection error\n" );
wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . $this->queryLogger->debug(
"Server: $server, Database: $dbName, User: $user, Password: " .
substr( $password, 0, 3 ) . "...\n" ); substr( $password, 0, 3 ) . "...\n" );
wfDebug( $this->lastError() . "\n" ); $this->queryLogger->debug( $this->lastError() . "\n" );
throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) ); throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
} }
$this->mOpened = true; $this->mOpened = true;
global $wgCommandLineMode;
# If called from the command-line (e.g. importDump), only show errors # If called from the command-line (e.g. importDump), only show errors
if ( $wgCommandLineMode ) { if ( $this->cliMode ) {
$this->doQuery( "SET client_min_messages = 'ERROR'" ); $this->doQuery( "SET client_min_messages = 'ERROR'" );
} }
@ -329,8 +150,7 @@ class DatabasePostgres extends DatabaseBase {
$this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127 $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
} }
global $wgDBmwschema; $this->determineCoreSchema( $this->mSchema );
$this->determineCoreSchema( $wgDBmwschema );
return $this->mConn; return $this->mConn;
} }
@ -401,7 +221,7 @@ class DatabasePostgres extends DatabaseBase {
PGSQL_DIAG_SOURCE_FUNCTION PGSQL_DIAG_SOURCE_FUNCTION
]; ];
foreach ( $diags as $d ) { foreach ( $diags as $d ) {
wfDebug( sprintf( "PgSQL ERROR(%d): %s\n", $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
$d, pg_result_error_field( $this->mLastResult, $d ) ) ); $d, pg_result_error_field( $this->mLastResult, $d ) ) );
} }
} }
@ -708,20 +528,16 @@ __INDEXATTR__;
return $res->numRows() > 0; return $res->numRows() > 0;
} }
/** function selectSQLText(
* Change the FOR UPDATE option as necessary based on the join conditions. Then pass $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
* to the parent function to get the actual SQL text.
*
* In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
* can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to do
* so causes a DB error. This wrapper checks which tables can be locked and adjusts it accordingly.
*
* MySQL uses "ORDER BY NULL" as an optimization hint, but that syntax is illegal in PostgreSQL.
* @see DatabaseBase::selectSQLText
*/
function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
$options = [], $join_conds = []
) { ) {
// Change the FOR UPDATE option as necessary based on the join conditions. Then pass
// to the parent function to get the actual SQL text.
// In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
// can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
// do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
// accordingly.
// MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
if ( is_array( $options ) ) { if ( is_array( $options ) ) {
$forUpdateKey = array_search( 'FOR UPDATE', $options, true ); $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
if ( $forUpdateKey !== false && $join_conds ) { if ( $forUpdateKey !== false && $join_conds ) {
@ -777,13 +593,13 @@ __INDEXATTR__;
} }
// If IGNORE is set, we use savepoints to emulate mysql's behavior // If IGNORE is set, we use savepoints to emulate mysql's behavior
$savepoint = null; $savepoint = $olde = null;
$numrowsinserted = 0;
if ( in_array( 'IGNORE', $options ) ) { if ( in_array( 'IGNORE', $options ) ) {
$savepoint = new SavepointPostgres( $this, 'mw' ); $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
$olde = error_reporting( 0 ); $olde = error_reporting( 0 );
// For future use, we may want to track the number of actual inserts // For future use, we may want to track the number of actual inserts
// Right now, insert (all writes) simply return true/false // Right now, insert (all writes) simply return true/false
$numrowsinserted = 0;
} }
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
@ -892,11 +708,11 @@ __INDEXATTR__;
* If IGNORE is set, we use savepoints to emulate mysql's behavior * If IGNORE is set, we use savepoints to emulate mysql's behavior
* Ignore LOW PRIORITY option, since it is MySQL-specific * Ignore LOW PRIORITY option, since it is MySQL-specific
*/ */
$savepoint = null; $savepoint = $olde = null;
if ( in_array( 'IGNORE', $insertOptions ) ) {
$savepoint = new SavepointPostgres( $this, 'mw' );
$olde = error_reporting( 0 );
$numrowsinserted = 0; $numrowsinserted = 0;
if ( in_array( 'IGNORE', $insertOptions ) ) {
$savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
$olde = error_reporting( 0 );
$savepoint->savepoint(); $savepoint->savepoint();
} }
@ -1016,7 +832,9 @@ __INDEXATTR__;
return $this->lastErrno() == '40P01'; return $this->lastErrno() == '40P01';
} }
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) { function duplicateTableStructure(
$oldName, $newName, $temporary = false, $fname = __METHOD__
) {
$newName = $this->addIdentifierQuotes( $newName ); $newName = $this->addIdentifierQuotes( $newName );
$oldName = $this->addIdentifierQuotes( $oldName ); $oldName = $this->addIdentifierQuotes( $oldName );
@ -1026,7 +844,8 @@ __INDEXATTR__;
function listTables( $prefix = null, $fname = __METHOD__ ) { function listTables( $prefix = null, $fname = __METHOD__ ) {
$eschema = $this->addQuotes( $this->getCoreSchema() ); $eschema = $this->addQuotes( $this->getCoreSchema() );
$result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname ); $result = $this->query(
"SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
$endArray = []; $endArray = [];
foreach ( $result as $table ) { foreach ( $result as $table ) {
@ -1041,7 +860,9 @@ __INDEXATTR__;
} }
function timestamp( $ts = 0 ) { function timestamp( $ts = 0 ) {
return wfTimestamp( TS_POSTGRES, $ts ); $ct = new ConvertableTimestamp( $ts );
return $ct->getTimestamp( TS_POSTGRES );
} }
/** /**
@ -1058,7 +879,7 @@ __INDEXATTR__;
* @since 1.19 * @since 1.19
* @param string $text Postgreql array returned in a text form like {a,b} * @param string $text Postgreql array returned in a text form like {a,b}
* @param string $output * @param string $output
* @param int $limit * @param int|bool $limit
* @param int $offset * @param int $offset
* @return string * @return string
*/ */
@ -1188,7 +1009,8 @@ __INDEXATTR__;
if ( $this->schemaExists( $desiredSchema ) ) { if ( $this->schemaExists( $desiredSchema ) ) {
if ( in_array( $desiredSchema, $this->getSchemas() ) ) { if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
$this->mCoreSchema = $desiredSchema; $this->mCoreSchema = $desiredSchema;
wfDebug( "Schema \"" . $desiredSchema . "\" already in the search path\n" ); $this->queryLogger->debug(
"Schema \"" . $desiredSchema . "\" already in the search path\n" );
} else { } else {
/** /**
* Prepend our schema (e.g. 'mediawiki') in front * Prepend our schema (e.g. 'mediawiki') in front
@ -1200,11 +1022,13 @@ __INDEXATTR__;
$this->addIdentifierQuotes( $desiredSchema ) ); $this->addIdentifierQuotes( $desiredSchema ) );
$this->setSearchPath( $search_path ); $this->setSearchPath( $search_path );
$this->mCoreSchema = $desiredSchema; $this->mCoreSchema = $desiredSchema;
wfDebug( "Schema \"" . $desiredSchema . "\" added to the search path\n" ); $this->queryLogger->debug(
"Schema \"" . $desiredSchema . "\" added to the search path\n" );
} }
} else { } else {
$this->mCoreSchema = $this->getCurrentSchema(); $this->mCoreSchema = $this->getCurrentSchema();
wfDebug( "Schema \"" . $desiredSchema . "\" not found, using current \"" . $this->queryLogger->debug(
"Schema \"" . $desiredSchema . "\" not found, using current \"" .
$this->mCoreSchema . "\"\n" ); $this->mCoreSchema . "\"\n" );
} }
/* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */ /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
@ -1595,7 +1419,7 @@ SQL;
return true; return true;
} }
wfDebug( __METHOD__ . " failed to release lock\n" ); $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
return false; return false;
} }
@ -1607,4 +1431,4 @@ SQL;
private function bigintFromLockName( $lockName ) { private function bigintFromLockName( $lockName ) {
return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 ); return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
} }
} // end DatabasePostgres class }

View file

@ -0,0 +1,101 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Database
*/
use Psr\Log\LoggerInterface;
/**
* Manage savepoints within a transaction
* @ingroup Database
* @since 1.19
*/
class SavepointPostgres {
/** @var DatabasePostgres Establish a savepoint within a transaction */
protected $dbw;
/** @var LoggerInterface */
protected $logger;
/** @var int */
protected $id;
/** @var bool */
protected $didbegin;
/**
* @param DatabasePostgres $dbw
* @param int $id
* @param LoggerInterface $logger
*/
public function __construct( DatabasePostgres $dbw, $id, LoggerInterface $logger ) {
$this->dbw = $dbw;
$this->logger = $logger;
$this->id = $id;
$this->didbegin = false;
/* If we are not in a transaction, we need to be for savepoint trickery */
if ( !$dbw->trxLevel() ) {
$dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
$this->didbegin = true;
}
}
public function __destruct() {
if ( $this->didbegin ) {
$this->dbw->rollback();
$this->didbegin = false;
}
}
public function commit() {
if ( $this->didbegin ) {
$this->dbw->commit();
$this->didbegin = false;
}
}
protected function query( $keyword, $msg_ok, $msg_failed ) {
if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
$this->logger->debug( sprintf( $msg_ok, $this->id ) );
} else {
$this->logger->debug( sprintf( $msg_failed, $this->id ) );
}
}
public function savepoint() {
$this->query( "SAVEPOINT",
"Transaction state: savepoint \"%s\" established.\n",
"Transaction state: establishment of savepoint \"%s\" FAILED.\n"
);
}
public function release() {
$this->query( "RELEASE",
"Transaction state: savepoint \"%s\" released.\n",
"Transaction state: release of savepoint \"%s\" FAILED.\n"
);
}
public function rollback() {
$this->query( "ROLLBACK TO",
"Transaction state: savepoint \"%s\" rolled back.\n",
"Transaction state: rollback of savepoint \"%s\" FAILED.\n"
);
}
public function __toString() {
return (string)$this->id;
}
}

View file

@ -0,0 +1,107 @@
<?php
class PostgresField implements Field {
private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname,
$has_default, $default;
/**
* @param DatabasePostgres $db
* @param string $table
* @param string $field
* @return null|PostgresField
*/
static function fromText( $db, $table, $field ) {
$q = <<<SQL
SELECT
attnotnull, attlen, conname AS conname,
atthasdef,
adsrc,
COALESCE(condeferred, 'f') AS deferred,
COALESCE(condeferrable, 'f') AS deferrable,
CASE WHEN typname = 'int2' THEN 'smallint'
WHEN typname = 'int4' THEN 'integer'
WHEN typname = 'int8' THEN 'bigint'
WHEN typname = 'bpchar' THEN 'char'
ELSE typname END AS typname
FROM pg_class c
JOIN pg_namespace n ON (n.oid = c.relnamespace)
JOIN pg_attribute a ON (a.attrelid = c.oid)
JOIN pg_type t ON (t.oid = a.atttypid)
LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
LEFT JOIN pg_attrdef d on c.oid=d.adrelid and a.attnum=d.adnum
WHERE relkind = 'r'
AND nspname=%s
AND relname=%s
AND attname=%s;
SQL;
$table = $db->tableName( $table, 'raw' );
$res = $db->query(
sprintf( $q,
$db->addQuotes( $db->getCoreSchema() ),
$db->addQuotes( $table ),
$db->addQuotes( $field )
)
);
$row = $db->fetchObject( $res );
if ( !$row ) {
return null;
}
$n = new PostgresField;
$n->type = $row->typname;
$n->nullable = ( $row->attnotnull == 'f' );
$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;
$n->has_default = ( $row->atthasdef === 't' );
$n->default = $row->adsrc;
return $n;
}
function name() {
return $this->name;
}
function tableName() {
return $this->tablename;
}
function type() {
return $this->type;
}
function isNullable() {
return $this->nullable;
}
function maxLength() {
return $this->max_length;
}
function is_deferrable() {
return $this->deferrable;
}
function is_deferred() {
return $this->deferred;
}
function conname() {
return $this->conname;
}
/**
* @since 1.19
* @return bool|mixed
*/
function defaultValue() {
if ( $this->has_default ) {
return $this->default;
} else {
return false;
}
}
}