3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
20 namespace Wikimedia\Rdbms
;
29 use Wikimedia\Rdbms\Platform\SqlitePlatform
;
30 use Wikimedia\Rdbms\Platform\SQLPlatform
;
31 use Wikimedia\Rdbms\Replication\ReplicationReporter
;
34 * This is the SQLite database abstraction layer.
36 * See docs/sqlite.txt for development notes about MediaWiki's sqlite schema.
40 class DatabaseSqlite
extends Database
{
41 /** @var string|null Directory for SQLite database files listed under their DB name */
43 /** @var string|null Explicit path for the SQLite database file */
45 /** @var string Transaction mode */
51 /** @var LockManager|null (hopefully on the same server as the DB) */
54 /** @var string|null */
57 /** @var array List of shared database already attached to this connection */
58 private $sessionAttachedDbs = [];
60 /** @var string[] See https://www.sqlite.org/lang_transaction.html */
61 private const VALID_TRX_MODES
= [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
63 /** @var string[][] */
64 private const VALID_PRAGMAS
= [
65 // Optimizations or requirements regarding fsync() usage
66 'synchronous' => [ 'EXTRA', 'FULL', 'NORMAL', 'OFF' ],
67 // Optimizations for TEMPORARY tables
68 'temp_store' => [ 'FILE', 'MEMORY' ],
69 // Optimizations for disk use and page cache
70 'mmap_size' => 'integer',
71 // How many DB pages to keep in memory
72 'cache_size' => 'integer',
75 /** @var SQLPlatform */
79 * Additional params include:
80 * - dbDirectory : directory containing the DB and the lock file directory
81 * - dbFilePath : use this to force the path of the DB file
82 * - trxMode : one of (deferred, immediate, exclusive)
83 * @param array $params
85 public function __construct( array $params ) {
86 if ( isset( $params['dbFilePath'] ) ) {
87 $this->dbPath
= $params['dbFilePath'];
88 if ( !isset( $params['dbname'] ) ||
$params['dbname'] === '' ) {
89 $params['dbname'] = self
::generateDatabaseName( $this->dbPath
);
91 } elseif ( isset( $params['dbDirectory'] ) ) {
92 $this->dbDir
= $params['dbDirectory'];
95 parent
::__construct( $params );
97 $this->trxMode
= strtoupper( $params['trxMode'] ??
'' );
99 $this->lockMgr
= $this->makeLockManager();
100 $this->platform
= new SqlitePlatform(
103 $this->currentDomain
,
106 $this->replicationReporter
= new ReplicationReporter(
107 $params['topologyRole'],
113 public static function getAttributes() {
115 self
::ATTR_DB_IS_FILE
=> true,
116 self
::ATTR_DB_LEVEL_LOCKING
=> true
121 * @param string $filename
122 * @param array $p Options map; supports:
123 * - flags : (same as __construct counterpart)
124 * - trxMode : (same as __construct counterpart)
125 * - dbDirectory : (same as __construct counterpart)
126 * @return DatabaseSqlite
129 public static function newStandaloneInstance( $filename, array $p = [] ) {
130 $p['dbFilePath'] = $filename;
132 $p['tablePrefix'] = '';
133 /** @var DatabaseSqlite $db */
134 $db = ( new DatabaseFactory() )->create( 'sqlite', $p );
135 '@phan-var DatabaseSqlite $db';
143 public function getType() {
147 protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
148 $this->close( __METHOD__
);
150 // Note that for SQLite, $server, $user, and $pass are ignored
152 if ( $schema !== null ) {
153 throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
156 if ( $this->dbPath
!== null ) {
157 $path = $this->dbPath
;
158 } elseif ( $this->dbDir
!== null ) {
159 $path = self
::generateFileName( $this->dbDir
, $db );
161 throw $this->newExceptionAfterConnectError( "DB path or directory required" );
164 // Check if the database file already exists but is non-readable
165 if ( !self
::isProcessMemoryPath( $path ) && is_file( $path ) && !is_readable( $path ) ) {
166 throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
167 } elseif ( !in_array( $this->trxMode
, self
::VALID_TRX_MODES
, true ) ) {
168 throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
172 PDO
::ATTR_ERRMODE
=> PDO
::ERRMODE_SILENT
,
173 // Starting with PHP 8.1, The SQLite PDO returns proper types instead
174 // of strings or null for everything. We cast every non-null value to
175 // string to restore the old behavior.
176 PDO
::ATTR_STRINGIFY_FETCHES
=> true
178 if ( $this->getFlag( self
::DBO_PERSISTENT
) ) {
179 // Persistent connections can avoid some schema index reading overhead.
180 // On the other hand, they can cause horrible contention with DBO_TRX.
181 if ( $this->getFlag( self
::DBO_TRX
) ||
$this->getFlag( self
::DBO_DEFAULT
) ) {
182 $this->logger
->warning(
183 __METHOD__
. ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
184 $this->getLogContext()
187 $attributes[PDO
::ATTR_PERSISTENT
] = true;
192 // Open the database file, creating it if it does not yet exist
193 $this->conn
= new PDO( "sqlite:$path", null, null, $attributes );
194 } catch ( PDOException
$e ) {
195 throw $this->newExceptionAfterConnectError( $e->getMessage() );
198 $this->currentDomain
= new DatabaseDomain( $db, null, $tablePrefix );
199 $this->platform
->setCurrentDomain( $this->currentDomain
);
202 // Enforce LIKE to be case sensitive, just like MySQL
204 'PRAGMA case_sensitive_like = 1',
205 self
::QUERY_CHANGE_TRX | self
::QUERY_NO_RETRY
,
208 $this->query( $query, __METHOD__
);
209 // Set any connection-level custom PRAGMA options
210 $pragmas = array_intersect_key( $this->connectionVariables
, self
::VALID_PRAGMAS
);
211 $pragmas +
= $this->getDefaultPragmas();
212 foreach ( $pragmas as $name => $value ) {
213 $allowed = self
::VALID_PRAGMAS
[$name];
215 ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
216 ( is_string( $allowed ) && gettype( $value ) === $allowed )
219 "PRAGMA $name = $value",
220 self
::QUERY_CHANGE_TRX | self
::QUERY_NO_RETRY
,
225 $this->query( $query, __METHOD__
);
228 $this->attachDatabasesFromTableAliases();
229 } catch ( RuntimeException
$e ) {
230 throw $this->newExceptionAfterConnectError( $e->getMessage() );
235 * @return array Map of (name => value) for default values to set via PRAGMA
237 private function getDefaultPragmas() {
240 if ( !$this->cliMode
) {
241 $variables['temp_store'] = 'MEMORY';
248 * @return string|null SQLite DB file path
249 * @throws DBUnexpectedError
252 public function getDbFilePath() {
253 return $this->dbPath ?? self
::generateFileName( $this->dbDir
, $this->getDBname() );
257 * @return string|null Lock file directory
259 public function getLockFileDirectory() {
260 if ( $this->dbPath
!== null && !self
::isProcessMemoryPath( $this->dbPath
) ) {
261 return dirname( $this->dbPath
) . '/locks';
262 } elseif ( $this->dbDir
!== null && !self
::isProcessMemoryPath( $this->dbDir
) ) {
263 return $this->dbDir
. '/locks';
270 * Initialize/reset the LockManager instance
272 * @return LockManager
274 private function makeLockManager(): LockManager
{
275 $lockDirectory = $this->getLockFileDirectory();
276 if ( $lockDirectory !== null ) {
277 return new FSLockManager( [
278 'domain' => $this->getDomainID(),
279 'lockDirectory' => $lockDirectory,
282 return new NullLockManager( [ 'domain' => $this->getDomainID() ] );
287 * Does not actually close the connection, just destroys the reference for GC to do its work
290 protected function closeConnection() {
292 // Release all locks, via FSLockManager::__destruct, as the base class expects
293 $this->lockMgr
= null;
299 * Generates a database file name. Explicitly public for installer.
300 * @param string $dir Directory where database resides
301 * @param string|null $dbName Database name (or null from Database::factory, validated here)
303 * @throws DBUnexpectedError
305 public static function generateFileName( $dir, $dbName ) {
307 throw new DBUnexpectedError( null, __CLASS__
. ": no DB directory specified" );
308 } elseif ( self
::isProcessMemoryPath( $dir ) ) {
309 throw new DBUnexpectedError(
311 __CLASS__
. ": cannot use process memory directory '$dir'"
313 } elseif ( !strlen( $dbName ) ) {
314 throw new DBUnexpectedError( null, __CLASS__
. ": no DB name specified" );
317 return "$dir/$dbName.sqlite";
321 * @param string $path
324 private static function generateDatabaseName( $path ) {
325 if ( preg_match( '/^(:memory:$|file::memory:)/', $path ) ) {
326 // E.g. "file::memory:?cache=shared" => ":memory":
328 } elseif ( preg_match( '/^file::([^?]+)\?mode=memory(&|$)/', $path, $m ) ) {
329 // E.g. "file:memdb1?mode=memory" => ":memdb1:"
332 // E.g. "/home/.../some_db.sqlite3" => "some_db"
333 return preg_replace( '/\.sqlite\d?$/', '', basename( $path ) );
338 * @param string $path
341 private static function isProcessMemoryPath( $path ) {
342 return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
346 * Returns version of currently supported SQLite fulltext search module or false if none present.
347 * @return string|false
349 public static function getFulltextSearchModule() {
350 static $cachedResult = null;
351 if ( $cachedResult !== null ) {
352 return $cachedResult;
354 $cachedResult = false;
355 $table = 'dummy_search_test';
357 $db = self
::newStandaloneInstance( ':memory:' );
359 "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)",
361 IDatabase
::QUERY_SILENCE_ERRORS
363 $cachedResult = 'FTS3';
365 $db->close( __METHOD__
);
367 return $cachedResult;
371 * Attaches external database to the connection handle
373 * @see https://sqlite.org/lang_attach.html
375 * @param string $name Database name to be used in queries like
376 * SELECT foo FROM dbname.table
377 * @param bool|string $file Database file name. If omitted, will be generated
378 * using $name and configured data directory
379 * @param string $fname Calling function name @phan-mandatory-param
380 * @return IResultWrapper
382 public function attachDatabase( $name, $file = false, $fname = __METHOD__
) {
383 $file = is_string( $file ) ?
$file : self
::generateFileName( $this->dbDir
, $name );
384 $encFile = $this->addQuotes( $file );
386 "ATTACH DATABASE $encFile AS $name",
387 self
::QUERY_CHANGE_TRX
,
390 return $this->query( $query, $fname );
393 protected function doSingleStatementQuery( string $sql ): QueryStatus
{
394 $res = $this->getBindingHandle()->query( $sql );
395 // Note that rowCount() returns 0 for SELECT for SQLite
396 return new QueryStatus(
397 $res instanceof PDOStatement ?
new SqliteResultWrapper( $res ) : $res,
398 $res ?
$res->rowCount() : 0,
404 protected function doSelectDomain( DatabaseDomain
$domain ) {
405 if ( $domain->getSchema() !== null ) {
406 throw new DBExpectedError(
408 __CLASS__
. ": domain '{$domain->getId()}' has a schema component"
412 $database = $domain->getDatabase();
413 // A null database means "don't care" so leave it as is and update the table prefix
414 if ( $database === null ) {
415 $this->currentDomain
= new DatabaseDomain(
416 $this->currentDomain
->getDatabase(),
418 $domain->getTablePrefix()
420 $this->platform
->setCurrentDomain( $this->currentDomain
);
425 if ( $database !== $this->getDBname() ) {
426 throw new DBExpectedError(
428 __CLASS__
. ": cannot change database (got '$database')"
432 // Update that domain fields on success (no exception thrown)
433 $this->currentDomain
= $domain;
434 $this->platform
->setCurrentDomain( $domain );
439 protected function lastInsertId() {
440 // PDO::lastInsertId yields a string :(
441 return (int)$this->getBindingHandle()->lastInsertId();
447 public function lastError() {
448 if ( is_object( $this->conn
) ) {
449 $e = $this->conn
->errorInfo();
451 return $e[2] ??
$this->lastConnectError
;
454 return 'No database connection';
460 public function lastErrno() {
461 if ( is_object( $this->conn
) ) {
462 $info = $this->conn
->errorInfo();
464 if ( isset( $info[1] ) ) {
472 public function tableExists( $table, $fname = __METHOD__
) {
473 [ $db, $pt ] = $this->platform
->getDatabaseAndTableIdentifier( $table );
474 if ( isset( $this->sessionTempTables
[$db][$pt] ) ) {
475 return true; // already known to exist
478 $encTable = $this->addQuotes( $pt );
480 "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
481 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
484 $res = $this->query( $query, __METHOD__
);
486 return (bool)$res->numRows();
489 public function indexInfo( $table, $index, $fname = __METHOD__
) {
490 $indexName = $this->platform
->indexName( $index );
491 $components = $this->platform
->qualifiedTableComponents( $table );
492 $tableRaw = end( $components );
494 'PRAGMA index_list(' . $this->addQuotes( $tableRaw ) . ')',
495 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
498 $res = $this->query( $query, $fname );
500 foreach ( $res as $row ) {
501 if ( $row->name
=== $indexName ) {
502 return [ 'unique' => (bool)$row->unique
];
509 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__
) {
510 $this->platform
->normalizeUpsertParams( $uniqueKeys, $rows );
514 $encTable = $this->tableName( $table );
515 [ $sqlColumns, $sqlTuples ] = $this->platform
->makeInsertLists( $rows );
516 // https://sqlite.org/lang_insert.html
517 // Note that any auto-increment columns on conflicting rows will be reassigned
518 // due to combined DELETE+INSERT semantics. This will be reflected in insertId().
520 "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
521 self
::QUERY_CHANGE_ROWS
,
525 $this->query( $query, $fname );
528 protected function isConnectionError( $errno ) {
529 return $errno == 17; // SQLITE_SCHEMA;
532 protected function isKnownStatementRollbackError( $errno ) {
533 // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
534 // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
535 // https://sqlite.org/lang_createtable.html#uniqueconst
536 // https://sqlite.org/lang_conflict.html
540 public function serverIsReadOnly() {
541 $this->assertHasConnectionHandle();
543 $path = $this->getDbFilePath();
545 return ( !self
::isProcessMemoryPath( $path ) && !is_writable( $path ) );
549 * @return string Wikitext of a link to the server software's web site
551 public function getSoftwareLink() {
552 return "[{{int:version-db-sqlite-url}} SQLite]";
556 * @return string Version information from the database
558 public function getServerVersion() {
559 if ( $this->version
=== null ) {
560 $this->version
= $this->getBindingHandle()->getAttribute( PDO
::ATTR_SERVER_VERSION
);
563 return $this->version
;
567 * Get information about a given field
568 * Returns false if the field does not exist.
570 * @param string $table
571 * @param string $field
572 * @return SQLiteField|false False on failure
574 public function fieldInfo( $table, $field ) {
575 $components = $this->platform
->qualifiedTableComponents( $table );
576 $tableRaw = end( $components );
578 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
579 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
582 $res = $this->query( $query, __METHOD__
);
583 foreach ( $res as $row ) {
584 if ( $row->name
== $field ) {
585 return new SQLiteField( $row, $tableRaw );
592 protected function doBegin( $fname = '' ) {
593 if ( $this->trxMode
!= '' ) {
594 $sql = "BEGIN {$this->trxMode}";
598 $query = new Query( $sql, self
::QUERY_CHANGE_TRX
, 'BEGIN' );
599 $this->query( $query, $fname );
606 public function strencode( $s ) {
607 return substr( $this->addQuotes( $s ), 1, -1 );
614 public function encodeBlob( $b ) {
615 return new Blob( $b );
619 * @param Blob|string $b
622 public function decodeBlob( $b ) {
623 if ( $b instanceof Blob
) {
627 // An empty blob is decoded as null in PHP before PHP 8.1.
628 // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
635 public function addQuotes( $s ) {
636 if ( $s instanceof RawSQLValue
) {
639 if ( $s instanceof Blob
) {
640 return "x'" . bin2hex( $s->fetch() ) . "'";
641 } elseif ( is_bool( $s ) ) {
642 return (string)(int)$s;
643 } elseif ( is_int( $s ) ) {
645 } elseif ( strpos( (string)$s, "\0" ) !== false ) {
646 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
647 // This is a known limitation of SQLite's mprintf function which PDO
648 // should work around, but doesn't. I have reported this to php.net as bug #63419:
649 // https://bugs.php.net/bug.php?id=63419
650 // There was already a similar report for SQLite3::escapeString, bug #62361:
651 // https://bugs.php.net/bug.php?id=62361
652 // There is an additional bug regarding sorting this data after insert
653 // on older versions of sqlite shipped with ubuntu 12.04
654 // https://phabricator.wikimedia.org/T74367
655 $this->logger
->debug(
657 ': Quoting value containing null byte. ' .
658 'For consistency all binary data should have been ' .
659 'first processed with self::encodeBlob()'
661 return "x'" . bin2hex( (string)$s ) . "'";
663 return $this->getBindingHandle()->quote( (string)$s );
667 public function doLockIsFree( string $lockName, string $method ) {
668 // Only locks by this thread will be checked
672 public function doLock( string $lockName, string $method, int $timeout ) {
673 $status = $this->lockMgr
->lock( [ $lockName ], LockManager
::LOCK_EX
, $timeout );
675 $this->lockMgr
instanceof FSLockManager
&&
676 $status->hasMessage( 'lockmanager-fail-openlock' )
678 throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
681 return $status->isOK() ?
microtime( true ) : null;
684 public function doUnlock( string $lockName, string $method ) {
685 return $this->lockMgr
->unlock( [ $lockName ], LockManager
::LOCK_EX
)->isGood();
689 * @param string $oldName
690 * @param string $newName
691 * @param bool $temporary
692 * @param string $fname
693 * @return bool|IResultWrapper
694 * @throws RuntimeException
696 public function duplicateTableStructure(
697 $oldName, $newName, $temporary = false, $fname = __METHOD__
700 "SELECT sql FROM sqlite_master WHERE tbl_name=" .
701 $this->addQuotes( $oldName ) . " AND type='table'",
702 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
705 $res = $this->query( $query, $fname );
706 $obj = $res->fetchObject();
708 throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
710 $sqlCreateTable = $obj->sql
;
711 $sqlCreateTable = preg_replace(
713 preg_quote( trim( $this->platform
->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
715 $this->platform
->addIdentifierQuotes( $newName ),
719 $flags = self
::QUERY_CHANGE_SCHEMA | self
::QUERY_PSEUDO_PERMANENT
;
721 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
722 $this->logger
->debug(
723 "Table $oldName is virtual, can't create a temporary duplicate." );
725 $sqlCreateTable = str_replace(
727 'CREATE TEMPORARY TABLE',
736 $temporary ?
'CREATE TEMPORARY' : 'CREATE',
737 // Use a dot to avoid double-prefixing in Database::getTempTableWrites()
740 $res = $this->query( $query, $fname );
743 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
744 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
748 $indexList = $this->query( $query, $fname );
749 foreach ( $indexList as $index ) {
750 if ( strpos( $index->name
, 'sqlite_autoindex' ) === 0 ) {
754 if ( $index->unique
) {
755 $sqlIndex = 'CREATE UNIQUE INDEX';
757 $sqlIndex = 'CREATE INDEX';
759 // Try to come up with a new index name, given indexes have database scope in SQLite
760 $indexName = $newName . '_' . $index->name
;
761 $sqlIndex .= ' ' . $this->platform
->addIdentifierQuotes( $indexName ) .
762 ' ON ' . $this->platform
->addIdentifierQuotes( $newName );
765 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name
) . ')',
766 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
769 $indexInfo = $this->query( $query, $fname );
771 foreach ( $indexInfo as $indexInfoRow ) {
772 $fields[$indexInfoRow->seqno
] = $this->addQuotes( $indexInfoRow->name
);
775 $sqlIndex .= '(' . implode( ',', $fields ) . ')';
779 self
::QUERY_CHANGE_SCHEMA | self
::QUERY_PSEUDO_PERMANENT
,
783 $this->query( $query, __METHOD__
);
790 * List all tables on the database
792 * @param string|null $prefix Only show tables with this prefix, e.g. mw_
793 * @param string $fname Calling function name
797 public function listTables( $prefix = null, $fname = __METHOD__
) {
799 "SELECT name FROM sqlite_master WHERE type = 'table'",
800 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
803 $result = $this->query( $query, $fname );
807 foreach ( $result as $table ) {
808 $vars = get_object_vars( $table );
809 $table = array_pop( $vars );
811 if ( !$prefix ||
strpos( $table, $prefix ) === 0 ) {
812 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
813 $endArray[] = $table;
821 public function truncateTable( $table, $fname = __METHOD__
) {
822 $this->startAtomic( $fname );
823 // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
825 "DELETE FROM " . $this->tableName( $table ),
826 self
::QUERY_CHANGE_SCHEMA
,
830 $this->query( $query, $fname );
832 $encMasterTable = $this->platform
->addIdentifierQuotes( 'sqlite_sequence' );
833 $encSequenceName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
835 "DELETE FROM $encMasterTable WHERE name = $encSequenceName",
836 self
::QUERY_CHANGE_SCHEMA
,
840 $this->query( $query, $fname );
842 $this->endAtomic( $fname );
845 public function setTableAliases( array $aliases ) {
846 parent
::setTableAliases( $aliases );
847 if ( $this->isOpen() ) {
848 $this->attachDatabasesFromTableAliases();
853 * Issue ATTATCH statements for all unattached foreign DBs in table aliases
855 private function attachDatabasesFromTableAliases() {
856 foreach ( $this->platform
->getTableAliases() as $params ) {
858 $params['dbname'] !== $this->getDBname() &&
859 !isset( $this->sessionAttachedDbs
[$params['dbname']] )
861 $this->attachDatabase( $params['dbname'], false, __METHOD__
);
862 $this->sessionAttachedDbs
[$params['dbname']] = true;
867 public function databasesAreIndependent() {
871 protected function doHandleSessionLossPreconnect() {
872 $this->sessionAttachedDbs
= [];
873 // Release all locks, via FSLockManager::__destruct, as the base class expects;
874 $this->lockMgr
= null;
875 // Create a new lock manager instance
876 $this->lockMgr
= $this->makeLockManager();
879 protected function doFlushSession( $fname ) {
880 // Release all locks, via FSLockManager::__destruct, as the base class expects
881 $this->lockMgr
= null;
882 // Create a new lock manager instance
883 $this->lockMgr
= $this->makeLockManager();
889 protected function getBindingHandle() {
890 return parent
::getBindingHandle();
893 protected function getInsertIdColumnForUpsert( $table ) {
894 $components = $this->platform
->qualifiedTableComponents( $table );
895 $tableRaw = end( $components );
897 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
898 self
::QUERY_IGNORE_DBO_TRX | self
::QUERY_CHANGE_NONE
,
901 $res = $this->query( $query, __METHOD__
);
902 foreach ( $res as $row ) {
903 if ( $row->pk
&& strtolower( $row->type
) === 'integer' ) {