summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Connection/Database.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Connection/Database.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Connection/Database.php907
1 files changed, 907 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Connection/Database.php b/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Connection/Database.php
new file mode 100644
index 00000000..244e0723
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/src/MediaWiki/Connection/Database.php
@@ -0,0 +1,907 @@
+<?php
+
+namespace SMW\MediaWiki\Connection;
+
+use DBError;
+use Exception;
+use ResultWrapper;
+use RuntimeException;
+use SMW\ApplicationFactory;
+use SMW\Connection\ConnectionProviderRef;
+use UnexpectedValueException;
+
+/**
+ * This adapter class covers MW DB specific operations. Changes to the
+ * interface are likely therefore this class should not be used other than by
+ * SMW itself.
+ *
+ * @license GNU GPL v2+
+ * @since 1.9
+ *
+ * @author mwjames
+ */
+class Database {
+
+ /**
+ * Identifies a request to be executed using an auto commit state
+ *
+ * @note (#1605 "... creating temporary tables in a transaction is not
+ * replication-safe and causes errors in MySQL 5.6. ...")
+ */
+ const AUTO_COMMIT = 'auto.commit';
+
+ /**
+ * @see IDatabase::TRIGGER_ROLLBACK
+ */
+ const TRIGGER_ROLLBACK = 3;
+
+ /**
+ * @var ConnectionProviderRef
+ */
+ private $connectionProviderRef;
+
+ /**
+ * @var ILBFactory
+ */
+ private $loadBalancerFactory;
+
+ /**
+ * @var Database
+ */
+ private $readConnection;
+
+ /**
+ * @var Database
+ */
+ private $writeConnection;
+
+ /**
+ * @var string
+ */
+ private $dbPrefix = '';
+
+ /**
+ * @var TransactionProfiler
+ */
+ private $transactionProfiler;
+
+ /**
+ * @var boolean
+ */
+ private $initConnection = false;
+
+ /**
+ * @var boolean
+ */
+ private $autoCommit = false;
+
+ /**
+ * @var integer
+ */
+ private $insertId = null;
+
+ /**
+ * @since 1.9
+ *
+ * @param ConnectionProviderRef $connectionProviderRef
+ * @param ILBFactory|null $loadBalancerFactory
+ */
+ public function __construct( ConnectionProviderRef $connectionProviderRef, $loadBalancerFactory = null ) {
+ $this->connectionProviderRef = $connectionProviderRef;
+ $this->loadBalancerFactory = $loadBalancerFactory;
+
+ if ( $this->loadBalancerFactory === null ) {
+ $this->loadBalancerFactory = ApplicationFactory::getInstance()->create( 'DBLoadBalancerFactory' );
+ }
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param TransactionProfiler $transactionProfiler
+ */
+ public function setTransactionProfiler( TransactionProfiler $transactionProfiler ) {
+ $this->transactionProfiler = $transactionProfiler;
+ }
+
+ /**
+ * @since 2.5
+ *
+ * @param string $type
+ *
+ * @return boolean
+ */
+ public function releaseConnection() {
+ $this->connectionProviderRef->releaseConnection();
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @return boolean
+ */
+ public function ping() {
+ return true;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @return Query
+ */
+ public function newQuery() {
+ return new Query( $this );
+ }
+
+ /**
+ * @since 2.5
+ *
+ * @param string $type
+ *
+ * @return boolean
+ */
+ public function isType( $type ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->getType() === $type;
+ }
+
+ /**
+ * @see DatabaseBase::getServerInfo
+ *
+ * @since 3.0
+ *
+ * @return array
+ */
+ public function getInfo() {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return [ $this->getType() => $this->readConnection->getServerInfo() ];
+ }
+
+ /**
+ * @see DatabaseBase::getType
+ *
+ * @since 1.9
+ *
+ * @return string
+ */
+ public function getType() {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->getType();
+ }
+
+ /**
+ * @since 2.1
+ *
+ * @param string $dbPrefix
+ */
+ public function setDBPrefix( $dbPrefix ) {
+ $this->dbPrefix = $dbPrefix;
+ }
+
+ /**
+ * @see DatabaseBase::tableName
+ *
+ * @since 1.9
+ *
+ * @param string $tableName
+ *
+ * @return string
+ */
+ public function tableName( $tableName ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( $this->getType() === 'sqlite' ) {
+ return $this->dbPrefix . $tableName;
+ }
+
+ return $this->readConnection->tableName( $tableName );
+ }
+
+ /**
+ * @see DatabaseBase::timestamp
+ *
+ * @since 3.0
+ *
+ * @param integer $ts
+ *
+ * @return string
+ */
+ public function timestamp( $ts = 0 ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->timestamp( $ts );
+ }
+
+ /**
+ * @see DatabaseBase::tablePrefix
+ *
+ * @since 3.0
+ *
+ * @param string $prefix
+ *
+ * @return string
+ */
+ public function tablePrefix( $prefix = null ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->tablePrefix( $prefix );
+ }
+
+ /**
+ * @see DatabaseBase::addQuotes
+ *
+ * @since 1.9
+ *
+ * @param string $tableName
+ *
+ * @return string
+ */
+ public function addQuotes( $value ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->addQuotes( $value );
+ }
+
+ /**
+ * @see DatabaseBase::fetchObject
+ *
+ * @since 1.9
+ *
+ * @param ResultWrapper $res
+ *
+ * @return string
+ */
+ public function fetchObject( $res ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->fetchObject( $res );
+ }
+
+ /**
+ * @see DatabaseBase::numRows
+ *
+ * @since 1.9
+ *
+ * @param mixed $results
+ *
+ * @return integer
+ */
+ public function numRows( $results ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->numRows( $results );
+ }
+
+ /**
+ * @see DatabaseBase::freeResult
+ *
+ * @since 1.9
+ *
+ * @param ResultWrapper $res
+ */
+ public function freeResult( $res ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ $this->readConnection->freeResult( $res );
+ }
+
+ /**
+ * @see DatabaseBase::select
+ *
+ * @since 1.9
+ *
+ * @param string $tableName
+ * @param $fields
+ * @param $conditions
+ * @param array $options
+ * @param array $joinConditions
+ *
+ * @return ResultWrapper
+ * @throws UnexpectedValueException
+ */
+ public function select( $tableName, $fields, $conditions = '', $fname, array $options = [], $joinConditions = [] ) {
+
+ $tablePrefix = null;
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( $this->getType() === 'sqlite' ) {
+
+ // MW's SQLite implementation adds an auto prefix to the tableName but
+ // not to the conditions and since ::tableName will handle prefixing
+ // consistently ensure that the select doesn't add an extra prefix
+ $tablePrefix = $this->readConnection->tablePrefix( '' );
+
+ if ( isset( $options['ORDER BY'] ) ) {
+ $options['ORDER BY'] = str_replace( 'RAND', 'RANDOM', $options['ORDER BY'] );
+ }
+ }
+
+ try {
+ $results = $this->readConnection->select(
+ $tableName,
+ $fields,
+ $conditions,
+ $fname,
+ $options,
+ $joinConditions
+ );
+ } catch ( DBError $e ) {
+ throw new RuntimeException ( $e->getMessage() . "\n" . $e->getTraceAsString() );
+ }
+
+ if ( $tablePrefix !== null ) {
+ $this->readConnection->tablePrefix( $tablePrefix );
+ }
+
+ if ( $results instanceof ResultWrapper ) {
+ return $results;
+ }
+
+ throw new UnexpectedValueException (
+ 'Expected a ResultWrapper for ' . "\n" .
+ $tableName . "\n" .
+ $fields . "\n" .
+ $conditions
+ );
+ }
+
+ /**
+ * @see DatabaseBase::query
+ *
+ * @since 1.9
+ *
+ * @param Query|string $sql
+ * @param string $fname
+ * @param boolean $ignoreException
+ *
+ * @return ResultWrapper
+ * @throws RuntimeException
+ */
+ public function query( $sql, $fname = __METHOD__, $ignoreException = false ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( $sql instanceof Query ) {
+ $sql = $sql->build();
+ }
+
+ if ( !$this->isType( 'postgres' ) ) {
+ $sql = str_replace( '@INT', '', $sql );
+ }
+
+ if ( $this->isType( 'postgres' ) ) {
+ $sql = str_replace( '@INT', '::integer', $sql );
+ $sql = str_replace( 'IGNORE', '', $sql );
+ $sql = str_replace( 'DROP TEMPORARY TABLE', 'DROP TABLE IF EXISTS', $sql );
+ $sql = str_replace( 'RAND()', ( strpos( $sql, 'DISTINCT' ) !== false ? '' : 'RANDOM()' ), $sql );
+ }
+
+ if ( $this->isType( 'sqlite' ) ) {
+ $sql = str_replace( 'IGNORE', '', $sql );
+ $sql = str_replace( 'TEMPORARY', 'TEMP', $sql );
+ $sql = str_replace( 'ENGINE=MEMORY', '', $sql );
+ $sql = str_replace( 'DROP TEMP', 'DROP', $sql );
+ $sql = str_replace( 'TRUNCATE TABLE', 'DELETE FROM', $sql );
+ $sql = str_replace( 'RAND', 'RANDOM', $sql );
+ }
+
+ // https://github.com/wikimedia/mediawiki/blob/42d5e6f43a00eb8bedc3532876125f74e3188343/includes/deferred/AutoCommitUpdate.php
+ // https://github.com/wikimedia/mediawiki/blob/f7dad57c64db3eb1296894c2d3ae97b9f7f27c4c/includes/installer/DatabaseInstaller.php#L157
+ if ( $this->autoCommit ) {
+ $autoTrx = $this->writeConnection->getFlag( DBO_TRX );
+ $this->writeConnection->clearFlag( DBO_TRX );
+
+ if ( $autoTrx && $this->writeConnection->trxLevel() ) {
+ $this->writeConnection->commit( __METHOD__ );
+ }
+ }
+
+ try {
+ $exception = null;
+ $results = $this->writeConnection->query(
+ $sql,
+ $fname,
+ $ignoreException
+ );
+ } catch ( Exception $exception ) {
+ }
+
+ if ( $this->autoCommit && $autoTrx ) {
+ $this->writeConnection->setFlag( DBO_TRX );
+ }
+
+ // State is only valid for a single transaction
+ $this->autoCommit = false;
+
+ if ( $exception ) {
+ throw $exception;
+ }
+
+ return $results;
+ }
+
+ /**
+ * @see DatabaseBase::selectRow
+ *
+ * @since 1.9
+ */
+ public function selectRow( $table, $vars, $conds, $fname = __METHOD__, $options = [], $joinConditions = [] ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->selectRow(
+ $table,
+ $vars,
+ $conds,
+ $fname,
+ $options,
+ $joinConditions
+ );
+ }
+
+ /**
+ * @see DatabaseBase::affectedRows
+ *
+ * @since 1.9
+ *
+ * @return int
+ */
+ function affectedRows() {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->affectedRows();
+ }
+
+ /**
+ * @note Method was made protected in 1.28, hence the need
+ * for the DatabaseHelper that copies the functionality.
+ *
+ * @see DatabaseBase::makeSelectOptions
+ *
+ * @since 1.9
+ *
+ * @param array $options
+ *
+ * @return array
+ */
+ public function makeSelectOptions( $options ) {
+ return OptionsBuilder::makeSelectOptions( $this, $options );
+ }
+
+ /**
+ * @see DatabaseBase::nextSequenceValue
+ *
+ * @since 1.9
+ *
+ * @param string $seqName
+ *
+ * @return int|null
+ */
+ public function nextSequenceValue( $seqName ) {
+ $this->insertId = null;
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( !$this->isType( 'postgres' ) ) {
+ return null;
+ }
+
+ // #3101, #2903
+ // MW 1.31+
+ // https://github.com/wikimedia/mediawiki/commit/0a9c55bfd39e22828f2d152ab71789cef3b0897c#diff-278465351b7c14bbcadac82036080e9f
+ $safeseq = str_replace( "'", "''", $seqName );
+ $res = $this->writeConnection->query( "SELECT nextval('$safeseq')" );
+ $row = $this->readConnection->fetchRow( $res );
+
+ return $this->insertId = is_null( $row[0] ) ? null : (int)$row[0];
+ }
+
+ /**
+ * @see DatabaseBase::insertId
+ *
+ * @since 1.9
+ *
+ * @return int
+ */
+ function insertId() {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( $this->insertId !== null ) {
+ return $this->insertId;
+ }
+
+ return (int)$this->writeConnection->insertId();
+ }
+
+ /**
+ * @see DatabaseBase::clearFlag
+ *
+ * @since 2.4
+ */
+ function clearFlag( $flag ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ $this->writeConnection->clearFlag( $flag );
+ }
+
+ /**
+ * @see DatabaseBase::getFlag
+ *
+ * @since 2.4
+ */
+ function getFlag( $flag ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->writeConnection->getFlag( $flag );
+ }
+
+ /**
+ * @see DatabaseBase::setFlag
+ *
+ * @since 2.4
+ */
+ function setFlag( $flag ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( $flag === self::AUTO_COMMIT ) {
+ return $this->autoCommit = true;
+ }
+
+ $this->writeConnection->setFlag( $flag );
+ }
+
+ /**
+ * @see DatabaseBase::insert
+ *
+ * @since 1.9
+ */
+ public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ $oldSilenced = $this->transactionProfiler->setSilenced(
+ true
+ );
+
+ $res = $this->writeConnection->insert( $table, $rows, $fname, $options );
+
+ $this->transactionProfiler->setSilenced(
+ $oldSilenced
+ );
+
+ return $res;
+ }
+
+ /**
+ * @see DatabaseBase::update
+ *
+ * @since 1.9
+ */
+ function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ $oldSilenced = $this->transactionProfiler->setSilenced(
+ true
+ );
+
+ $res = $this->writeConnection->update( $table, $values, $conds, $fname, $options );
+
+ $this->transactionProfiler->setSilenced(
+ $oldSilenced
+ );
+
+ return $res;
+ }
+
+ /**
+ * @see DatabaseBase::delete
+ *
+ * @since 1.9
+ */
+ public function delete( $table, $conds, $fname = __METHOD__ ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ $oldSilenced = $this->transactionProfiler->setSilenced(
+ true
+ );
+
+ $res = $this->writeConnection->delete( $table, $conds, $fname );
+
+ $this->transactionProfiler->setSilenced(
+ $oldSilenced
+ );
+
+ return $res;
+ }
+
+ /**
+ * @see DatabaseBase::replace
+ *
+ * @since 2.5
+ */
+ public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ $oldSilenced = $this->transactionProfiler->setSilenced(
+ true
+ );
+
+ $res = $this->writeConnection->replace( $table, $uniqueIndexes, $rows, $fname );
+
+ $this->transactionProfiler->setSilenced(
+ $oldSilenced
+ );
+
+ return $res;
+ }
+
+ /**
+ * @see DatabaseBase::makeList
+ *
+ * @since 1.9
+ */
+ public function makeList( $data, $mode ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->writeConnection->makeList( $data, $mode );
+ }
+
+ /**
+ * @see DatabaseBase::tableExists
+ *
+ * @since 1.9
+ *
+ * @param string $table
+ * @param string $fname
+ *
+ * @return bool
+ */
+ public function tableExists( $table, $fname = __METHOD__ ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->tableExists( $table, $fname );
+ }
+
+ /**
+ * @see DatabaseBase::selectField
+ *
+ * @since 1.9.2
+ */
+ public function selectField( $table, $fieldName, $conditions = '', $fname = __METHOD__, $options = [] ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->selectField( $table, $fieldName, $conditions, $fname, $options );
+ }
+
+ /**
+ * @see DatabaseBase::estimateRowCount
+ *
+ * @since 2.1
+ */
+ public function estimateRowCount( $table, $vars = '*', $conditions = '', $fname = __METHOD__, $options = [] ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ return $this->readConnection->estimateRowCount(
+ $table,
+ $vars,
+ $conditions,
+ $fname,
+ $options
+ );
+ }
+
+ /**
+ * @note Only supported with 1.28+
+ * @since 3.0
+ *
+ * @param string $fname Caller name (e.g. __METHOD__)
+ *
+ * @return mixed A value to pass to commitAndWaitForReplication
+ */
+ public function getEmptyTransactionTicket( $fname = __METHOD__ ) {
+
+ $ticket = null;
+
+ if ( !method_exists( $this->loadBalancerFactory, 'getEmptyTransactionTicket' ) ) {
+ return $ticket;
+ }
+
+ // @see LBFactory::getEmptyTransactionTicket
+ // We don't try very hard at this point and will continue without a ticket
+ // if the check fails and hereby avoid a "... does not have outer scope" error
+ if ( !$this->loadBalancerFactory->hasMasterChanges() ) {
+ $ticket = $this->loadBalancerFactory->getEmptyTransactionTicket( $fname );
+ }
+
+ return $ticket;
+ }
+
+ /**
+ * Convenience method for safely running commitMasterChanges/waitForReplication
+ * where it will allow to commit and wait for whena TransactionTicket is
+ * available.
+ *
+ * @note Only supported with 1.28+
+ *
+ * @since 3.0
+ *
+ * @param string $fname Caller name (e.g. __METHOD__)
+ * @param mixed $ticket Result of Database::getEmptyTransactionTicket
+ * @param array $opts Options to waitForReplication
+ */
+ public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
+
+ if ( !is_int( $ticket ) || !method_exists( $this->loadBalancerFactory, 'commitAndWaitForReplication' ) ) {
+ return;
+ }
+
+ return $this->loadBalancerFactory->commitAndWaitForReplication( $fname, $ticket, $opts );
+ }
+
+ /**
+ * @since 2.3
+ *
+ * @param string $fname
+ */
+ public function beginAtomicTransaction( $fname = __METHOD__ ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ // MW 1.23
+ if ( !method_exists( $this->writeConnection, 'startAtomic' ) ) {
+ return null;
+ }
+
+ $this->writeConnection->startAtomic( $fname );
+ }
+
+ /**
+ * @since 2.3
+ *
+ * @param string $fname
+ */
+ public function endAtomicTransaction( $fname = __METHOD__ ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ // MW 1.23
+ if ( !method_exists( $this->writeConnection, 'endAtomic' ) ) {
+ return null;
+ }
+
+ $this->writeConnection->endAtomic( $fname );
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param callable $callback
+ */
+ public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ if ( method_exists( $this->writeConnection, 'onTransactionResolution' ) && $this->writeConnection->trxLevel() ) {
+ $this->writeConnection->onTransactionResolution( $callback, $fname );
+ }
+ }
+
+ /**
+ * @since 2.3
+ *
+ * @param callable $callback
+ */
+ public function onTransactionIdle( $callback ) {
+
+ if ( $this->initConnection === false ) {
+ $this->initConnection();
+ }
+
+ // FIXME For 1.19 it is an unknown method hence execute without idle
+ if ( !method_exists( $this->writeConnection, 'onTransactionIdle' ) ) {
+ return call_user_func( $callback );
+ }
+
+ $this->writeConnection->onTransactionIdle( $callback );
+ }
+
+ private function initConnection() {
+
+ if ( $this->readConnection === null ) {
+ $this->readConnection = $this->connectionProviderRef->getConnection( 'read' );
+ }
+
+ if ( $this->writeConnection === null && $this->connectionProviderRef->hasConnection( 'write' ) ) {
+ $this->writeConnection = $this->connectionProviderRef->getConnection( 'write' );
+ }
+
+ $this->initConnection = true;
+ }
+
+}