Move DatabasePostgres to /libs/rdbms
Fixed all IDEA warnings in the postgres classes too. Change-Id: I06b4c5b2c581fb65552d980cc106aa10fed40285
This commit is contained in:
parent
62ebd86b32
commit
adfd1b93d9
5 changed files with 265 additions and 229 deletions
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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 ];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
}
|
||||||
101
includes/libs/rdbms/database/utils/SavepointPostgres.php
Normal file
101
includes/libs/rdbms/database/utils/SavepointPostgres.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
includes/libs/rdbms/field/PostgresField.php
Normal file
107
includes/libs/rdbms/field/PostgresField.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue