}
public function estimateRowCount(
- $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
) {
return $this->__call( __FUNCTION__, func_get_args() );
}
return $this->__call( __FUNCTION__, func_get_args() );
}
+ public function setIndexAliases( array $aliases ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
/**
* Clean up the connection when out of scope
*/
protected $password;
/** @var string */
protected $dbName;
- /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
+ /** @var array[] Map of (table => (dbname, schema, prefix) map) */
protected $tableAliases = [];
+ /** @var string[] Map of (index alias => index) */
+ protected $indexAliases = [];
/** @var bool Whether this PHP instance is for a CLI script */
protected $cliMode;
/** @var string Agent name for query profiling */
public function writesOrCallbacksPending() {
return $this->trxLevel && (
- $this->trxDoneWrites || $this->trxIdleCallbacks || $this->trxPreCommitCallbacks
+ $this->trxDoneWrites ||
+ $this->trxIdleCallbacks ||
+ $this->trxPreCommitCallbacks ||
+ $this->trxEndCallbacks
);
}
public function close() {
if ( $this->conn ) {
+ // Resolve any dangling transaction first
if ( $this->trxLevel() ) {
+ // Meaningful transactions should ideally have been resolved by now
+ if ( $this->writesOrCallbacksPending() ) {
+ $this->queryLogger->warning(
+ __METHOD__ . ": writes or callbacks still pending.",
+ [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
+ );
+ }
+ // Check if it is possible to properly commit and trigger callbacks
+ if ( $this->trxEndCallbacksSuppressed ) {
+ throw new DBUnexpectedError(
+ $this,
+ __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
+ );
+ }
+ // Commit the changes and run any callbacks as needed
$this->commit( __METHOD__, self::FLUSHING_INTERNAL );
}
-
+ // Close the actual connection in the binding handle
$closed = $this->closeConnection();
$this->conn = false;
- } elseif (
- $this->trxIdleCallbacks ||
- $this->trxPreCommitCallbacks ||
- $this->trxEndCallbacks
- ) { // sanity
- throw new RuntimeException( "Transaction callbacks still pending." );
+ // Sanity check that no callbacks are dangling
+ if (
+ $this->trxIdleCallbacks || $this->trxPreCommitCallbacks || $this->trxEndCallbacks
+ ) {
+ throw new RuntimeException( "Transaction callbacks still pending." );
+ }
} else {
- $closed = true;
+ $closed = true; // already closed; nothing to do
}
+
$this->opened = false;
return $closed;
}
public function estimateRowCount(
- $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
) {
$rows = 0;
- $res = $this->select( $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options );
+ $res = $this->select(
+ $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
+ );
if ( $res ) {
$row = $this->fetchRow( $res );
* @return string
*/
protected function indexName( $index ) {
- return $index;
+ return isset( $this->indexAliases[$index] )
+ ? $this->indexAliases[$index]
+ : $index;
}
public function addQuotes( $s ) {
$this->tableAliases = $aliases;
}
+ public function setIndexAliases( array $aliases ) {
+ $this->indexAliases = $aliases;
+ }
+
/**
* @return bool Whether a DB user is required to access the DB
* @since 1.28
* @param string|array $conds
* @param string $fname
* @param string|array $options
+ * @param array $join_conds
* @return bool|int
*/
public function estimateRowCount( $table, $vars = '*', $conds = '',
- $fname = __METHOD__, $options = []
+ $fname = __METHOD__, $options = [], $join_conds = []
) {
$options['EXPLAIN'] = true;
- $res = $this->select( $table, $vars, $conds, $fname, $options );
+ $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
if ( $res === false ) {
return false;
}
return in_array( $name, $this->listViews( $prefix ) );
}
- /**
- * Allows for index remapping in queries where this is not consistent across DBMS
- *
- * @param string $index
- * @return string
- */
- protected function indexName( $index ) {
- /**
- * When SQLite indexes were introduced in r45764, it was noted that
- * SQLite requires index names to be unique within the whole database,
- * not just within a schema. As discussed in CR r45819, to avoid the
- * need for a schema change on existing installations, the indexes
- * were implicitly mapped from the new names to the old names.
- *
- * This mapping can be removed if DB patches are introduced to alter
- * the relevant tables in existing installations. Note that because
- * this index mapping applies to table creation, even new installations
- * of MySQL have the old names (except for installations created during
- * a period where this mapping was inappropriately removed, see
- * T154872).
- */
- $renamed = [
- 'ar_usertext_timestamp' => 'usertext_timestamp',
- 'un_user_id' => 'user_id',
- 'un_user_ip' => 'user_ip',
- ];
-
- if ( isset( $renamed[$index] ) ) {
- return $renamed[$index];
- } else {
- return $index;
- }
- }
-
protected function isTransactableQuery( $sql ) {
return parent::isTransactableQuery( $sql ) &&
!preg_match( '/^SELECT\s+(GET|RELEASE|IS_FREE)_LOCK\(/', $sql );
public function getType();
/**
- * Open a connection to the database. Usually aborts on failure
+ * Open a new connection to the database (closing any existing one)
*
* @param string $server Database server host
* @param string $user Database user name
public function getServerVersion();
/**
- * Closes a database connection.
- * if it is open : commits any open transactions
+ * Close the database connection
+ *
+ * This should only be called after any transactions have been resolved,
+ * aside from read-only transactions (assuming no callbacks are registered).
+ * If a transaction is still open anyway, it will be committed if possible.
*
* @throws DBError
* @return bool Operation success. true if already closed.
* @param array|string $conds Filters on the table
* @param string $fname Function name for profiling
* @param array $options Options for select
+ * @param array|string $join_conds Join conditions
* @return int Row count
* @throws DBError
*/
public function estimateRowCount(
- $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
+ $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
);
/**
* @since 1.28
*/
public function setTableAliases( array $aliases );
+
+ /**
+ * Convert certain index names to alternative names before querying the DB
+ *
+ * Note that this applies to indexes regardless of the table they belong to.
+ *
+ * This can be employed when an index was renamed X => Y in code, but the new Y-named
+ * indexes were not yet built on all DBs. After all the Y-named ones are added by the DBA,
+ * the aliases can be removed, and then the old X-named indexes dropped.
+ *
+ * @param string[] $aliases
+ * @return mixed
+ * @since 1.31
+ */
+ public function setIndexAliases( array $aliases );
}
class_alias( IDatabase::class, 'IDatabase' );