MediaWiki  master
DatabaseSqlite.php
Go to the documentation of this file.
1 <?php
20 namespace Wikimedia\Rdbms;
21 
22 use FSLockManager;
23 use LockManager;
25 use NullLockManager;
26 use PDO;
27 use PDOException;
28 use PDOStatement;
29 use RuntimeException;
33 
41 class DatabaseSqlite extends Database {
43  protected $dbDir;
45  protected $dbPath;
47  protected $trxMode;
48 
51 
53  protected $conn;
54 
56  protected $lockMgr;
57 
59  private $version;
60 
62  private $sessionAttachedDbs = [];
63 
65  private const VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
66 
68  private const VALID_PRAGMAS = [
69  // Optimizations or requirements regarding fsync() usage
70  'synchronous' => [ 'EXTRA', 'FULL', 'NORMAL', 'OFF' ],
71  // Optimizations for TEMPORARY tables
72  'temp_store' => [ 'FILE', 'MEMORY' ],
73  // Optimizations for disk use and page cache
74  'mmap_size' => 'integer'
75  ];
76 
78  protected $platform;
79 
87  public function __construct( array $params ) {
88  if ( isset( $params['dbFilePath'] ) ) {
89  $this->dbPath = $params['dbFilePath'];
90  if ( !isset( $params['dbname'] ) || $params['dbname'] === '' ) {
91  $params['dbname'] = self::generateDatabaseName( $this->dbPath );
92  }
93  } elseif ( isset( $params['dbDirectory'] ) ) {
94  $this->dbDir = $params['dbDirectory'];
95  }
96 
97  parent::__construct( $params );
98 
99  $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
100 
101  $this->lockMgr = $this->makeLockManager();
102  $this->platform = new SqlitePlatform(
103  $this,
104  $this->logger,
105  $this->currentDomain,
106  $this->errorLogger
107  );
108  $this->replicationReporter = new ReplicationReporter(
109  $params['topologyRole'],
110  $this->logger,
111  $params['srvCache']
112  );
113  }
114 
115  public static function getAttributes() {
116  return [
117  self::ATTR_DB_IS_FILE => true,
118  self::ATTR_DB_LEVEL_LOCKING => true
119  ];
120  }
121 
131  public static function newStandaloneInstance( $filename, array $p = [] ) {
132  $p['dbFilePath'] = $filename;
133  $p['schema'] = null;
134  $p['tablePrefix'] = '';
136  $db = MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'sqlite', $p );
137  '@phan-var DatabaseSqlite $db';
138 
139  return $db;
140  }
141 
145  public function getType() {
146  return 'sqlite';
147  }
148 
149  protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
150  $this->close( __METHOD__ );
151 
152  // Note that for SQLite, $server, $user, and $pass are ignored
153 
154  if ( $schema !== null ) {
155  throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
156  }
157 
158  if ( $this->dbPath !== null ) {
160  } elseif ( $this->dbDir !== null ) {
161  $path = self::generateFileName( $this->dbDir, $db );
162  } else {
163  throw $this->newExceptionAfterConnectError( "DB path or directory required" );
164  }
165 
166  // Check if the database file already exists but is non-readable
167  if ( !self::isProcessMemoryPath( $path ) && is_file( $path ) && !is_readable( $path ) ) {
168  throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
169  } elseif ( !in_array( $this->trxMode, self::VALID_TRX_MODES, true ) ) {
170  throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
171  }
172 
173  $attributes = [
174  PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
175  // Starting with PHP 8.1, The SQLite PDO returns proper types instead
176  // of strings or null for everything. We cast every non-null value to
177  // string to restore the old behavior.
178  PDO::ATTR_STRINGIFY_FETCHES => true
179  ];
180  if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
181  // Persistent connections can avoid some schema index reading overhead.
182  // On the other hand, they can cause horrible contention with DBO_TRX.
183  if ( $this->getFlag( self::DBO_TRX ) || $this->getFlag( self::DBO_DEFAULT ) ) {
184  $this->logger->warning(
185  __METHOD__ . ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
186  $this->getLogContext()
187  );
188  } else {
189  $attributes[PDO::ATTR_PERSISTENT] = true;
190  }
191  }
192 
193  try {
194  // Open the database file, creating it if it does not yet exist
195  $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
196  } catch ( PDOException $e ) {
197  throw $this->newExceptionAfterConnectError( $e->getMessage() );
198  }
199 
200  $this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
201  $this->platform->setPrefix( $tablePrefix );
202 
203  try {
204  $flags = self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY;
205  // Enforce LIKE to be case sensitive, just like MySQL
206  $this->query( 'PRAGMA case_sensitive_like = 1', __METHOD__, $flags );
207  // Set any connection-level custom PRAGMA options
208  $pragmas = array_intersect_key( $this->connectionVariables, self::VALID_PRAGMAS );
209  $pragmas += $this->getDefaultPragmas();
210  foreach ( $pragmas as $name => $value ) {
211  $allowed = self::VALID_PRAGMAS[$name];
212  if (
213  ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
214  ( is_string( $allowed ) && gettype( $value ) === $allowed )
215  ) {
216  $this->query( "PRAGMA $name = $value", __METHOD__, $flags );
217  }
218  }
219  $this->attachDatabasesFromTableAliases();
220  } catch ( RuntimeException $e ) {
221  throw $this->newExceptionAfterConnectError( $e->getMessage() );
222  }
223  }
224 
228  private function getDefaultPragmas() {
229  $variables = [];
230 
231  if ( !$this->cliMode ) {
232  $variables['temp_store'] = 'MEMORY';
233  }
234 
235  return $variables;
236  }
237 
243  public function getDbFilePath() {
244  return $this->dbPath ?? self::generateFileName( $this->dbDir, $this->getDBname() );
245  }
246 
250  public function getLockFileDirectory() {
251  if ( $this->dbPath !== null && !self::isProcessMemoryPath( $this->dbPath ) ) {
252  return dirname( $this->dbPath ) . '/locks';
253  } elseif ( $this->dbDir !== null && !self::isProcessMemoryPath( $this->dbDir ) ) {
254  return $this->dbDir . '/locks';
255  }
256 
257  return null;
258  }
259 
265  private function makeLockManager(): LockManager {
266  $lockDirectory = $this->getLockFileDirectory();
267  if ( $lockDirectory !== null ) {
268  return new FSLockManager( [
269  'domain' => $this->getDomainID(),
270  'lockDirectory' => $lockDirectory,
271  ] );
272  } else {
273  return new NullLockManager( [ 'domain' => $this->getDomainID() ] );
274  }
275  }
276 
281  protected function closeConnection() {
282  $this->conn = null;
283  // Release all locks, via FSLockManager::__destruct, as the base class expects
284  $this->lockMgr = null;
285 
286  return true;
287  }
288 
296  public static function generateFileName( $dir, $dbName ) {
297  if ( $dir == '' ) {
298  throw new DBUnexpectedError( null, __CLASS__ . ": no DB directory specified" );
299  } elseif ( self::isProcessMemoryPath( $dir ) ) {
300  throw new DBUnexpectedError(
301  null,
302  __CLASS__ . ": cannot use process memory directory '$dir'"
303  );
304  } elseif ( !strlen( $dbName ) ) {
305  throw new DBUnexpectedError( null, __CLASS__ . ": no DB name specified" );
306  }
307 
308  return "$dir/$dbName.sqlite";
309  }
310 
315  private static function generateDatabaseName( $path ) {
316  if ( preg_match( '/^(:memory:$|file::memory:)/', $path ) ) {
317  // E.g. "file::memory:?cache=shared" => ":memory":
318  return ':memory:';
319  } elseif ( preg_match( '/^file::([^?]+)\?mode=memory(&|$)/', $path, $m ) ) {
320  // E.g. "file:memdb1?mode=memory" => ":memdb1:"
321  return ":{$m[1]}:";
322  } else {
323  // E.g. "/home/.../some_db.sqlite3" => "some_db"
324  return preg_replace( '/\.sqlite\d?$/', '', basename( $path ) );
325  }
326  }
327 
332  private static function isProcessMemoryPath( $path ) {
333  return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
334  }
335 
340  public static function getFulltextSearchModule() {
341  static $cachedResult = null;
342  if ( $cachedResult !== null ) {
343  return $cachedResult;
344  }
345  $cachedResult = false;
346  $table = 'dummy_search_test';
347 
348  $db = self::newStandaloneInstance( ':memory:' );
349  if ( $db->query(
350  "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)",
351  __METHOD__,
352  IDatabase::QUERY_SILENCE_ERRORS
353  ) ) {
354  $cachedResult = 'FTS3';
355  }
356  $db->close( __METHOD__ );
357 
358  return $cachedResult;
359  }
360 
373  public function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
374  $file = is_string( $file ) ? $file : self::generateFileName( $this->dbDir, $name );
375  $encFile = $this->addQuotes( $file );
376 
377  return $this->query(
378  "ATTACH DATABASE $encFile AS $name",
379  $fname,
380  self::QUERY_CHANGE_TRX
381  );
382  }
383 
384  protected function doSingleStatementQuery( string $sql ): QueryStatus {
385  $conn = $this->getBindingHandle();
386 
387  $res = $conn->query( $sql );
388  $this->lastAffectedRowCount = $res ? $res->rowCount() : 0;
389 
390  return new QueryStatus(
391  $res instanceof PDOStatement ? new SqliteResultWrapper( $res ) : $res,
392  $res ? $res->rowCount() : 0,
393  $this->lastError(),
394  $this->lastErrno()
395  );
396  }
397 
398  protected function doSelectDomain( DatabaseDomain $domain ) {
399  if ( $domain->getSchema() !== null ) {
400  throw new DBExpectedError(
401  $this,
402  __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
403  );
404  }
405 
406  $database = $domain->getDatabase();
407  // A null database means "don't care" so leave it as is and update the table prefix
408  if ( $database === null ) {
409  $this->currentDomain = new DatabaseDomain(
410  $this->currentDomain->getDatabase(),
411  null,
412  $domain->getTablePrefix()
413  );
414  $this->platform->setPrefix( $domain->getTablePrefix() );
415 
416  return true;
417  }
418 
419  if ( $database !== $this->getDBname() ) {
420  throw new DBExpectedError(
421  $this,
422  __CLASS__ . ": cannot change database (got '$database')"
423  );
424  }
425 
426  // Update that domain fields on success (no exception thrown)
427  $this->currentDomain = $domain;
428  $this->platform->setPrefix( $domain->getTablePrefix() );
429 
430  return true;
431  }
432 
438  public function insertId() {
439  // PDO::lastInsertId yields a string :(
440  return intval( $this->getBindingHandle()->lastInsertId() );
441  }
442 
446  public function lastError() {
447  if ( is_object( $this->conn ) ) {
448  $e = $this->conn->errorInfo();
449 
450  return $e[2] ?? $this->lastConnectError;
451  }
452 
453  return 'No database connection';
454  }
455 
459  public function lastErrno() {
460  if ( is_object( $this->conn ) ) {
461  $info = $this->conn->errorInfo();
462 
463  if ( isset( $info[1] ) ) {
464  return $info[1];
465  }
466  }
467 
468  return 0;
469  }
470 
474  protected function fetchAffectedRowCount() {
475  return $this->lastAffectedRowCount;
476  }
477 
478  public function tableExists( $table, $fname = __METHOD__ ) {
479  $tableRaw = $this->tableName( $table, 'raw' );
480  if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
481  return true; // already known to exist
482  }
483 
484  $encTable = $this->addQuotes( $tableRaw );
485  $res = $this->query(
486  "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
487  __METHOD__,
488  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
489  );
490 
491  return $res->numRows() ? true : false;
492  }
493 
504  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
505  $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
506  $res = $this->query( $sql, $fname, self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE );
507  if ( !$res || $res->numRows() == 0 ) {
508  return false;
509  }
510  $info = [];
511  foreach ( $res as $row ) {
512  $info[] = $row->name;
513  }
514 
515  return $info;
516  }
517 
524  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
525  $row = $this->selectRow( 'sqlite_master', '*',
526  [
527  'type' => 'index',
528  'name' => $this->indexName( $index ),
529  ], $fname );
530  if ( !$row || !isset( $row->sql ) ) {
531  return null;
532  }
533 
534  // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
535  $indexPos = strpos( $row->sql, 'INDEX' );
536  if ( $indexPos === false ) {
537  return null;
538  }
539  $firstPart = substr( $row->sql, 0, $indexPos );
540  $options = explode( ' ', $firstPart );
541 
542  return in_array( 'UNIQUE', $options );
543  }
544 
545  protected function doReplace( $table, array $identityKey, array $rows, $fname ) {
546  $encTable = $this->tableName( $table );
547  [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
548  // https://sqlite.org/lang_insert.html
549  $this->query(
550  "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
551  $fname,
552  self::QUERY_CHANGE_ROWS
553  );
554  }
555 
564  public function textFieldSize( $table, $field ) {
565  return -1;
566  }
567 
571  public function wasDeadlock() {
572  return $this->lastErrno() == 5; // SQLITE_BUSY
573  }
574 
578  public function wasReadOnlyError() {
579  return $this->lastErrno() == 8; // SQLITE_READONLY;
580  }
581 
582  protected function isConnectionError( $errno ) {
583  return $errno == 17; // SQLITE_SCHEMA;
584  }
585 
586  protected function isKnownStatementRollbackError( $errno ) {
587  // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
588  // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
589  // https://sqlite.org/lang_createtable.html#uniqueconst
590  // https://sqlite.org/lang_conflict.html
591  return false;
592  }
593 
594  public function serverIsReadOnly() {
595  $this->assertHasConnectionHandle();
596 
597  $path = $this->getDbFilePath();
598 
599  return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
600  }
601 
605  public function getSoftwareLink() {
606  return "[{{int:version-db-sqlite-url}} SQLite]";
607  }
608 
612  public function getServerVersion() {
613  if ( $this->version === null ) {
614  $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
615  }
616 
617  return $this->version;
618  }
619 
628  public function fieldInfo( $table, $field ) {
629  $tableRaw = $this->tableName( $table, 'raw' );
630  $res = $this->query(
631  'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
632  __METHOD__,
633  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
634  );
635  foreach ( $res as $row ) {
636  if ( $row->name == $field ) {
637  return new SQLiteField( $row, $tableRaw );
638  }
639  }
640 
641  return false;
642  }
643 
644  protected function doBegin( $fname = '' ) {
645  if ( $this->trxMode != '' ) {
646  $this->query( "BEGIN {$this->trxMode}", $fname, self::QUERY_CHANGE_TRX );
647  } else {
648  $this->query( 'BEGIN', $fname, self::QUERY_CHANGE_TRX );
649  }
650  }
651 
656  public function strencode( $s ) {
657  return substr( $this->addQuotes( $s ), 1, -1 );
658  }
659 
664  public function encodeBlob( $b ) {
665  return new Blob( $b );
666  }
667 
672  public function decodeBlob( $b ) {
673  if ( $b instanceof Blob ) {
674  $b = $b->fetch();
675  }
676  if ( $b === null ) {
677  // An empty blob is decoded as null in PHP before PHP 8.1.
678  // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
679  $b = '';
680  }
681 
682  return $b;
683  }
684 
689  public function addQuotes( $s ) {
690  if ( $s instanceof Blob ) {
691  return "x'" . bin2hex( $s->fetch() ) . "'";
692  } elseif ( is_bool( $s ) ) {
693  return (string)(int)$s;
694  } elseif ( is_int( $s ) ) {
695  return (string)$s;
696  } elseif ( strpos( (string)$s, "\0" ) !== false ) {
697  // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
698  // This is a known limitation of SQLite's mprintf function which PDO
699  // should work around, but doesn't. I have reported this to php.net as bug #63419:
700  // https://bugs.php.net/bug.php?id=63419
701  // There was already a similar report for SQLite3::escapeString, bug #62361:
702  // https://bugs.php.net/bug.php?id=62361
703  // There is an additional bug regarding sorting this data after insert
704  // on older versions of sqlite shipped with ubuntu 12.04
705  // https://phabricator.wikimedia.org/T74367
706  $this->logger->debug(
707  __FUNCTION__ .
708  ': Quoting value containing null byte. ' .
709  'For consistency all binary data should have been ' .
710  'first processed with self::encodeBlob()'
711  );
712  return "x'" . bin2hex( (string)$s ) . "'";
713  } else {
714  return $this->getBindingHandle()->quote( (string)$s );
715  }
716  }
717 
718  public function doLockIsFree( string $lockName, string $method ) {
719  // Only locks by this thread will be checked
720  return true;
721  }
722 
723  public function doLock( string $lockName, string $method, int $timeout ) {
724  $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
725  if (
726  $this->lockMgr instanceof FSLockManager &&
727  $status->hasMessage( 'lockmanager-fail-openlock' )
728  ) {
729  throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
730  }
731 
732  return $status->isOK() ? microtime( true ) : null;
733  }
734 
735  public function doUnlock( string $lockName, string $method ) {
736  return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
737  }
738 
747  public function duplicateTableStructure(
748  $oldName, $newName, $temporary = false, $fname = __METHOD__
749  ) {
750  $res = $this->query(
751  "SELECT sql FROM sqlite_master WHERE tbl_name=" .
752  $this->addQuotes( $oldName ) . " AND type='table'",
753  $fname,
754  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
755  );
756  $obj = $res->fetchObject();
757  if ( !$obj ) {
758  throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
759  }
760  $sqlCreateTable = $obj->sql;
761  $sqlCreateTable = preg_replace(
762  '/(?<=\W)"?' .
763  preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
764  '"?(?=\W)/',
765  $this->platform->addIdentifierQuotes( $newName ),
766  $sqlCreateTable,
767  1
768  );
769  if ( $temporary ) {
770  if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
771  $this->logger->debug(
772  "Table $oldName is virtual, can't create a temporary duplicate." );
773  } else {
774  $sqlCreateTable = str_replace(
775  'CREATE TABLE',
776  'CREATE TEMPORARY TABLE',
777  $sqlCreateTable
778  );
779  }
780  }
781 
782  $res = $this->query(
783  $sqlCreateTable,
784  $fname,
785  self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT
786  );
787 
788  // Take over indexes
789  $indexList = $this->query(
790  'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
791  $fname,
792  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
793  );
794  foreach ( $indexList as $index ) {
795  if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
796  continue;
797  }
798 
799  if ( $index->unique ) {
800  $sqlIndex = 'CREATE UNIQUE INDEX';
801  } else {
802  $sqlIndex = 'CREATE INDEX';
803  }
804  // Try to come up with a new index name, given indexes have database scope in SQLite
805  $indexName = $newName . '_' . $index->name;
806  $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
807  ' ON ' . $this->platform->addIdentifierQuotes( $newName );
808 
809  $indexInfo = $this->query(
810  'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
811  $fname,
812  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
813  );
814  $fields = [];
815  foreach ( $indexInfo as $indexInfoRow ) {
816  $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
817  }
818 
819  $sqlIndex .= '(' . implode( ',', $fields ) . ')';
820 
821  $this->query(
822  $sqlIndex,
823  __METHOD__,
824  self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT
825  );
826  }
827 
828  return $res;
829  }
830 
839  public function listTables( $prefix = null, $fname = __METHOD__ ) {
840  $result = $this->query(
841  "SELECT name FROM sqlite_master WHERE type = 'table'",
842  $fname,
843  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
844  );
845 
846  $endArray = [];
847 
848  foreach ( $result as $table ) {
849  $vars = get_object_vars( $table );
850  $table = array_pop( $vars );
851 
852  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
853  if ( strpos( $table, 'sqlite_' ) !== 0 ) {
854  $endArray[] = $table;
855  }
856  }
857  }
858 
859  return $endArray;
860  }
861 
862  protected function doTruncate( array $tables, $fname ) {
863  $this->startAtomic( $fname );
864 
865  $encSeqNames = [];
866  foreach ( $tables as $table ) {
867  // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
868  $sql = "DELETE FROM " . $this->tableName( $table );
869  $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA );
870 
871  $encSeqNames[] = $this->addQuotes( $this->tableName( $table, 'raw' ) );
872  }
873 
874  $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
875  $this->query(
876  "DELETE FROM $encMasterTable WHERE name IN(" . implode( ',', $encSeqNames ) . ")",
877  $fname,
878  self::QUERY_CHANGE_SCHEMA
879  );
880 
881  $this->endAtomic( $fname );
882  }
883 
884  public function setTableAliases( array $aliases ) {
885  parent::setTableAliases( $aliases );
886  if ( $this->isOpen() ) {
887  $this->attachDatabasesFromTableAliases();
888  }
889  }
890 
894  private function attachDatabasesFromTableAliases() {
895  foreach ( $this->platform->getTableAliases() as $params ) {
896  if (
897  $params['dbname'] !== $this->getDBname() &&
898  !isset( $this->sessionAttachedDbs[$params['dbname']] )
899  ) {
900  $this->attachDatabase( $params['dbname'], false, __METHOD__ );
901  $this->sessionAttachedDbs[$params['dbname']] = true;
902  }
903  }
904  }
905 
906  public function databasesAreIndependent() {
907  return true;
908  }
909 
910  protected function doHandleSessionLossPreconnect() {
911  $this->sessionAttachedDbs = [];
912  // Release all locks, via FSLockManager::__destruct, as the base class expects;
913  $this->lockMgr = null;
914  // Create a new lock manager instance
915  $this->lockMgr = $this->makeLockManager();
916  }
917 
918  protected function doFlushSession( $fname ) {
919  // Release all locks, via FSLockManager::__destruct, as the base class expects
920  $this->lockMgr = null;
921  // Create a new lock manager instance
922  $this->lockMgr = $this->makeLockManager();
923  }
924 
928  protected function getBindingHandle() {
929  return parent::getBindingHandle();
930  }
931 }
932 
936 class_alias( DatabaseSqlite::class, 'DatabaseSqlite' );
Simple lock management based on server-local temporary files.
Resource locking handling.
Definition: LockManager.php:47
const LOCK_EX
Definition: LockManager.php:70
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Simple lock management based on in-process reference counting.
Database error base class.
Definition: DBError.php:31
Base class for the more common types of database errors.
Class to handle database/schema/prefix specifications for IDatabase.
This is the SQLite database abstraction layer.
fieldInfo( $table, $field)
Get information about a given field Returns false if the field does not exist.
serverIsReadOnly()
bool Whether the DB server is marked as read-only server-side If an error occurs, {query} 1....
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.In systems like mysql/mariadb,...
indexUnique( $table, $index, $fname=__METHOD__)
doLock(string $lockName, string $method, int $timeout)
__construct(array $params)
Additional params include:
string null $dbPath
Explicit path for the SQLite database file.
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited" In SQLite this is SQLITE_MAX_LENGTH,...
string $trxMode
Transaction mode.
attachDatabase( $name, $file=false, $fname=__METHOD__)
Attaches external database to the connection handle.
isConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
doSelectDomain(DatabaseDomain $domain)
doLockIsFree(string $lockName, string $method)
doHandleSessionLossPreconnect()
Reset any additional subclass trx* and session* fields.
static getFulltextSearchModule()
Returns version of currently supported SQLite fulltext search module or false if none present.
closeConnection()
Does not actually close the connection, just destroys the reference for GC to do its work.
indexInfo( $table, $index, $fname=__METHOD__)
Returns information about an index Returns false if the index does not exist.
int $lastAffectedRowCount
The number of rows affected as an integer.
doBegin( $fname='')
Issues the BEGIN command to the database server.
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
static newStandaloneInstance( $filename, array $p=[])
string null $dbDir
Directory for SQLite database files listed under their DB name.
doTruncate(array $tables, $fname)
open( $server, $user, $password, $db, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
doFlushSession( $fname)
Reset the server-side session state for named locks and table locks.
LockManager null $lockMgr
(hopefully on the same server as the DB)
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
doSingleStatementQuery(string $sql)
Run a query and return a QueryStatus instance with the query result information.
doUnlock(string $lockName, string $method)
static generateFileName( $dir, $dbName)
Generates a database file name.
doReplace( $table, array $identityKey, array $rows, $fname)
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
insertId()
This must be called after nextSequenceVal.
Relational database abstraction object.
Definition: Database.php:45
newExceptionAfterConnectError( $error)
Definition: Database.php:1531
getDomainID()
Return the currently selected domain ID.
Definition: Database.php:495
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:574
close( $fname=__METHOD__)
Close the database connection.
Definition: Database.php:585
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:3552
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.
Definition: Database.php:837
getDBname()
Get the current database name; null if there isn't one.
Definition: Database.php:1800
Interface for query language.
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
const DBO_DEFAULT
Definition: defines.php:13
const DBO_PERSISTENT
Definition: defines.php:14
const DBO_TRX
Definition: defines.php:12
return true
Definition: router.php:90
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42