MediaWiki  master
DatabaseSqlite.php
Go to the documentation of this file.
1 <?php
20 namespace Wikimedia\Rdbms;
21 
22 use FSLockManager;
23 use LockManager;
24 use NullLockManager;
25 use PDO;
26 use PDOException;
27 use PDOStatement;
28 use RuntimeException;
31 
39 class DatabaseSqlite extends Database {
41  protected $dbDir;
43  protected $dbPath;
45  protected $trxMode;
46 
49 
51  protected $conn;
52 
54  protected $lockMgr;
55 
57  private $version;
58 
60  private $sessionAttachedDbs = [];
61 
63  private const VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
64 
66  private const VALID_PRAGMAS = [
67  // Optimizations or requirements regarding fsync() usage
68  'synchronous' => [ 'EXTRA', 'FULL', 'NORMAL', 'OFF' ],
69  // Optimizations for TEMPORARY tables
70  'temp_store' => [ 'FILE', 'MEMORY' ],
71  // Optimizations for disk use and page cache
72  'mmap_size' => 'integer'
73  ];
74 
76  protected $platform;
77 
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 );
90  }
91  } elseif ( isset( $params['dbDirectory'] ) ) {
92  $this->dbDir = $params['dbDirectory'];
93  }
94 
95  parent::__construct( $params );
96 
97  $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
98 
99  $this->lockMgr = $this->makeLockManager();
100  $this->platform = new SqlitePlatform(
101  $this,
102  $params['queryLogger'],
103  $this->currentDomain,
104  $this->errorLogger
105  );
106  }
107 
108  public static function getAttributes() {
109  return [
110  self::ATTR_DB_IS_FILE => true,
111  self::ATTR_DB_LEVEL_LOCKING => true
112  ];
113  }
114 
124  public static function newStandaloneInstance( $filename, array $p = [] ) {
125  $p['dbFilePath'] = $filename;
126  $p['schema'] = null;
127  $p['tablePrefix'] = '';
129  $db = Database::factory( 'sqlite', $p );
130  '@phan-var DatabaseSqlite $db';
131 
132  return $db;
133  }
134 
138  public function getType() {
139  return 'sqlite';
140  }
141 
142  protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
143  $this->close( __METHOD__ );
144 
145  // Note that for SQLite, $server, $user, and $pass are ignored
146 
147  if ( $schema !== null ) {
148  throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
149  }
150 
151  if ( $this->dbPath !== null ) {
153  } elseif ( $this->dbDir !== null ) {
154  $path = self::generateFileName( $this->dbDir, $db );
155  } else {
156  throw $this->newExceptionAfterConnectError( "DB path or directory required" );
157  }
158 
159  // Check if the database file already exists but is non-readable
160  if ( !self::isProcessMemoryPath( $path ) && is_file( $path ) && !is_readable( $path ) ) {
161  throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
162  } elseif ( !in_array( $this->trxMode, self::VALID_TRX_MODES, true ) ) {
163  throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
164  }
165 
166  $attributes = [
167  PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
168  // Starting with PHP 8.1, The SQLite PDO returns proper types instead
169  // of strings or null for everything. We cast every non-null value to
170  // string to restore the old behavior.
171  PDO::ATTR_STRINGIFY_FETCHES => true
172  ];
173  if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
174  // Persistent connections can avoid some schema index reading overhead.
175  // On the other hand, they can cause horrible contention with DBO_TRX.
176  if ( $this->getFlag( self::DBO_TRX ) || $this->getFlag( self::DBO_DEFAULT ) ) {
177  $this->connLogger->warning(
178  __METHOD__ . ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
179  $this->getLogContext()
180  );
181  } else {
182  $attributes[PDO::ATTR_PERSISTENT] = true;
183  }
184  }
185 
186  try {
187  // Open the database file, creating it if it does not yet exist
188  $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
189  } catch ( PDOException $e ) {
190  throw $this->newExceptionAfterConnectError( $e->getMessage() );
191  }
192 
193  $this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
194  $this->platform->setPrefix( $tablePrefix );
195 
196  try {
197  $flags = self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY;
198  // Enforce LIKE to be case sensitive, just like MySQL
199  $this->query( 'PRAGMA case_sensitive_like = 1', __METHOD__, $flags );
200  // Set any connection-level custom PRAGMA options
201  $pragmas = array_intersect_key( $this->connectionVariables, self::VALID_PRAGMAS );
202  $pragmas += $this->getDefaultPragmas();
203  foreach ( $pragmas as $name => $value ) {
204  $allowed = self::VALID_PRAGMAS[$name];
205  if (
206  ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
207  ( is_string( $allowed ) && gettype( $value ) === $allowed )
208  ) {
209  $this->query( "PRAGMA $name = $value", __METHOD__, $flags );
210  }
211  }
212  $this->attachDatabasesFromTableAliases();
213  } catch ( RuntimeException $e ) {
214  throw $this->newExceptionAfterConnectError( $e->getMessage() );
215  }
216  }
217 
221  private function getDefaultPragmas() {
222  $variables = [];
223 
224  if ( !$this->cliMode ) {
225  $variables['temp_store'] = 'MEMORY';
226  }
227 
228  return $variables;
229  }
230 
236  public function getDbFilePath() {
237  return $this->dbPath ?? self::generateFileName( $this->dbDir, $this->getDBname() );
238  }
239 
243  public function getLockFileDirectory() {
244  if ( $this->dbPath !== null && !self::isProcessMemoryPath( $this->dbPath ) ) {
245  return dirname( $this->dbPath ) . '/locks';
246  } elseif ( $this->dbDir !== null && !self::isProcessMemoryPath( $this->dbDir ) ) {
247  return $this->dbDir . '/locks';
248  }
249 
250  return null;
251  }
252 
258  private function makeLockManager(): LockManager {
259  $lockDirectory = $this->getLockFileDirectory();
260  if ( $lockDirectory !== null ) {
261  return new FSLockManager( [
262  'domain' => $this->getDomainID(),
263  'lockDirectory' => $lockDirectory,
264  ] );
265  } else {
266  return new NullLockManager( [ 'domain' => $this->getDomainID() ] );
267  }
268  }
269 
274  protected function closeConnection() {
275  $this->conn = null;
276  // Release all locks, via FSLockManager::__destruct, as the base class expects
277  $this->lockMgr = null;
278 
279  return true;
280  }
281 
289  public static function generateFileName( $dir, $dbName ) {
290  if ( $dir == '' ) {
291  throw new DBUnexpectedError( null, __CLASS__ . ": no DB directory specified" );
292  } elseif ( self::isProcessMemoryPath( $dir ) ) {
293  throw new DBUnexpectedError(
294  null,
295  __CLASS__ . ": cannot use process memory directory '$dir'"
296  );
297  } elseif ( !strlen( $dbName ) ) {
298  throw new DBUnexpectedError( null, __CLASS__ . ": no DB name specified" );
299  }
300 
301  return "$dir/$dbName.sqlite";
302  }
303 
308  private static function generateDatabaseName( $path ) {
309  if ( preg_match( '/^(:memory:$|file::memory:)/', $path ) ) {
310  // E.g. "file::memory:?cache=shared" => ":memory":
311  return ':memory:';
312  } elseif ( preg_match( '/^file::([^?]+)\?mode=memory(&|$)/', $path, $m ) ) {
313  // E.g. "file:memdb1?mode=memory" => ":memdb1:"
314  return ":{$m[1]}:";
315  } else {
316  // E.g. "/home/.../some_db.sqlite3" => "some_db"
317  return preg_replace( '/\.sqlite\d?$/', '', basename( $path ) );
318  }
319  }
320 
325  private static function isProcessMemoryPath( $path ) {
326  return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
327  }
328 
333  public static function getFulltextSearchModule() {
334  static $cachedResult = null;
335  if ( $cachedResult !== null ) {
336  return $cachedResult;
337  }
338  $cachedResult = false;
339  $table = 'dummy_search_test';
340 
341  $db = self::newStandaloneInstance( ':memory:' );
342  if ( $db->query(
343  "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)",
344  __METHOD__,
345  IDatabase::QUERY_SILENCE_ERRORS
346  ) ) {
347  $cachedResult = 'FTS3';
348  }
349  $db->close( __METHOD__ );
350 
351  return $cachedResult;
352  }
353 
366  public function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
367  $file = is_string( $file ) ? $file : self::generateFileName( $this->dbDir, $name );
368  $encFile = $this->addQuotes( $file );
369 
370  return $this->query(
371  "ATTACH DATABASE $encFile AS $name",
372  $fname,
373  self::QUERY_CHANGE_TRX
374  );
375  }
376 
377  protected function doSingleStatementQuery( string $sql ): QueryStatus {
378  $conn = $this->getBindingHandle();
379 
380  $res = $conn->query( $sql );
381  $this->lastAffectedRowCount = $res ? $res->rowCount() : 0;
382 
383  return new QueryStatus(
384  $res instanceof PDOStatement ? new SqliteResultWrapper( $res ) : $res,
385  $res ? $res->rowCount() : 0,
386  $this->lastError(),
387  $this->lastErrno()
388  );
389  }
390 
391  protected function doSelectDomain( DatabaseDomain $domain ) {
392  if ( $domain->getSchema() !== null ) {
393  throw new DBExpectedError(
394  $this,
395  __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
396  );
397  }
398 
399  $database = $domain->getDatabase();
400  // A null database means "don't care" so leave it as is and update the table prefix
401  if ( $database === null ) {
402  $this->currentDomain = new DatabaseDomain(
403  $this->currentDomain->getDatabase(),
404  null,
405  $domain->getTablePrefix()
406  );
407  $this->platform->setPrefix( $domain->getTablePrefix() );
408 
409  return true;
410  }
411 
412  if ( $database !== $this->getDBname() ) {
413  throw new DBExpectedError(
414  $this,
415  __CLASS__ . ": cannot change database (got '$database')"
416  );
417  }
418 
419  // Update that domain fields on success (no exception thrown)
420  $this->currentDomain = $domain;
421  $this->platform->setPrefix( $domain->getTablePrefix() );
422 
423  return true;
424  }
425 
431  public function insertId() {
432  // PDO::lastInsertId yields a string :(
433  return intval( $this->getBindingHandle()->lastInsertId() );
434  }
435 
439  public function lastError() {
440  if ( is_object( $this->conn ) ) {
441  $e = $this->conn->errorInfo();
442 
443  return $e[2] ?? $this->lastConnectError;
444  }
445 
446  return 'No database connection';
447  }
448 
452  public function lastErrno() {
453  if ( is_object( $this->conn ) ) {
454  $info = $this->conn->errorInfo();
455 
456  if ( isset( $info[1] ) ) {
457  return $info[1];
458  }
459  }
460 
461  return 0;
462  }
463 
467  protected function fetchAffectedRowCount() {
468  return $this->lastAffectedRowCount;
469  }
470 
471  public function tableExists( $table, $fname = __METHOD__ ) {
472  $tableRaw = $this->tableName( $table, 'raw' );
473  if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
474  return true; // already known to exist
475  }
476 
477  $encTable = $this->addQuotes( $tableRaw );
478  $res = $this->query(
479  "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
480  __METHOD__,
481  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
482  );
483 
484  return $res->numRows() ? true : false;
485  }
486 
497  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
498  $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
499  $res = $this->query( $sql, $fname, self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE );
500  if ( !$res || $res->numRows() == 0 ) {
501  return false;
502  }
503  $info = [];
504  foreach ( $res as $row ) {
505  $info[] = $row->name;
506  }
507 
508  return $info;
509  }
510 
517  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
518  $row = $this->selectRow( 'sqlite_master', '*',
519  [
520  'type' => 'index',
521  'name' => $this->indexName( $index ),
522  ], $fname );
523  if ( !$row || !isset( $row->sql ) ) {
524  return null;
525  }
526 
527  // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
528  $indexPos = strpos( $row->sql, 'INDEX' );
529  if ( $indexPos === false ) {
530  return null;
531  }
532  $firstPart = substr( $row->sql, 0, $indexPos );
533  $options = explode( ' ', $firstPart );
534 
535  return in_array( 'UNIQUE', $options );
536  }
537 
538  protected function doReplace( $table, array $identityKey, array $rows, $fname ) {
539  $encTable = $this->tableName( $table );
540  [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
541  // https://sqlite.org/lang_insert.html
542  $this->query(
543  "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
544  $fname,
545  self::QUERY_CHANGE_ROWS
546  );
547  }
548 
557  public function textFieldSize( $table, $field ) {
558  return -1;
559  }
560 
564  public function wasDeadlock() {
565  return $this->lastErrno() == 5; // SQLITE_BUSY
566  }
567 
571  public function wasReadOnlyError() {
572  return $this->lastErrno() == 8; // SQLITE_READONLY;
573  }
574 
575  protected function isConnectionError( $errno ) {
576  return $errno == 17; // SQLITE_SCHEMA;
577  }
578 
579  protected function isKnownStatementRollbackError( $errno ) {
580  // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
581  // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
582  // https://sqlite.org/lang_createtable.html#uniqueconst
583  // https://sqlite.org/lang_conflict.html
584  return false;
585  }
586 
587  public function getTopologyBasedServerId() {
588  // Sqlite topologies trivially consist of single primary server for the dataset
589  return '0';
590  }
591 
592  public function serverIsReadOnly() {
593  $this->assertHasConnectionHandle();
594 
595  $path = $this->getDbFilePath();
596 
597  return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
598  }
599 
603  public function getSoftwareLink() {
604  return "[{{int:version-db-sqlite-url}} SQLite]";
605  }
606 
610  public function getServerVersion() {
611  if ( $this->version === null ) {
612  $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
613  }
614 
615  return $this->version;
616  }
617 
626  public function fieldInfo( $table, $field ) {
627  $tableRaw = $this->tableName( $table, 'raw' );
628  $res = $this->query(
629  'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
630  __METHOD__,
631  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
632  );
633  foreach ( $res as $row ) {
634  if ( $row->name == $field ) {
635  return new SQLiteField( $row, $tableRaw );
636  }
637  }
638 
639  return false;
640  }
641 
642  protected function doBegin( $fname = '' ) {
643  if ( $this->trxMode != '' ) {
644  $this->query( "BEGIN {$this->trxMode}", $fname, self::QUERY_CHANGE_TRX );
645  } else {
646  $this->query( 'BEGIN', $fname, self::QUERY_CHANGE_TRX );
647  }
648  }
649 
654  public function strencode( $s ) {
655  return substr( $this->addQuotes( $s ), 1, -1 );
656  }
657 
662  public function encodeBlob( $b ) {
663  return new Blob( $b );
664  }
665 
670  public function decodeBlob( $b ) {
671  if ( $b instanceof Blob ) {
672  $b = $b->fetch();
673  }
674  if ( $b === null ) {
675  // An empty blob is decoded as null in PHP before PHP 8.1.
676  // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
677  $b = '';
678  }
679 
680  return $b;
681  }
682 
687  public function addQuotes( $s ) {
688  if ( $s instanceof Blob ) {
689  return "x'" . bin2hex( $s->fetch() ) . "'";
690  } elseif ( is_bool( $s ) ) {
691  return (string)(int)$s;
692  } elseif ( is_int( $s ) ) {
693  return (string)$s;
694  } elseif ( strpos( (string)$s, "\0" ) !== false ) {
695  // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
696  // This is a known limitation of SQLite's mprintf function which PDO
697  // should work around, but doesn't. I have reported this to php.net as bug #63419:
698  // https://bugs.php.net/bug.php?id=63419
699  // There was already a similar report for SQLite3::escapeString, bug #62361:
700  // https://bugs.php.net/bug.php?id=62361
701  // There is an additional bug regarding sorting this data after insert
702  // on older versions of sqlite shipped with ubuntu 12.04
703  // https://phabricator.wikimedia.org/T74367
704  $this->queryLogger->debug(
705  __FUNCTION__ .
706  ': Quoting value containing null byte. ' .
707  'For consistency all binary data should have been ' .
708  'first processed with self::encodeBlob()'
709  );
710  return "x'" . bin2hex( (string)$s ) . "'";
711  } else {
712  return $this->getBindingHandle()->quote( (string)$s );
713  }
714  }
715 
722  public function deadlockLoop( ...$args ) {
723  $function = array_shift( $args );
724 
725  return $function( ...$args );
726  }
727 
728  public function doLockIsFree( string $lockName, string $method ) {
729  // Only locks by this thread will be checked
730  return true;
731  }
732 
733  public function doLock( string $lockName, string $method, int $timeout ) {
734  $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
735  if (
736  $this->lockMgr instanceof FSLockManager &&
737  $status->hasMessage( 'lockmanager-fail-openlock' )
738  ) {
739  throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
740  }
741 
742  return $status->isOK() ? microtime( true ) : null;
743  }
744 
745  public function doUnlock( string $lockName, string $method ) {
746  return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
747  }
748 
757  public function duplicateTableStructure(
758  $oldName, $newName, $temporary = false, $fname = __METHOD__
759  ) {
760  $res = $this->query(
761  "SELECT sql FROM sqlite_master WHERE tbl_name=" .
762  $this->addQuotes( $oldName ) . " AND type='table'",
763  $fname,
764  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
765  );
766  $obj = $res->fetchObject();
767  if ( !$obj ) {
768  throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
769  }
770  $sqlCreateTable = $obj->sql;
771  $sqlCreateTable = preg_replace(
772  '/(?<=\W)"?' .
773  preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
774  '"?(?=\W)/',
775  $this->platform->addIdentifierQuotes( $newName ),
776  $sqlCreateTable,
777  1
778  );
779  if ( $temporary ) {
780  if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
781  $this->queryLogger->debug(
782  "Table $oldName is virtual, can't create a temporary duplicate." );
783  } else {
784  $sqlCreateTable = str_replace(
785  'CREATE TABLE',
786  'CREATE TEMPORARY TABLE',
787  $sqlCreateTable
788  );
789  }
790  }
791 
792  $res = $this->query(
793  $sqlCreateTable,
794  $fname,
795  self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT
796  );
797 
798  // Take over indexes
799  $indexList = $this->query(
800  'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
801  $fname,
802  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
803  );
804  foreach ( $indexList as $index ) {
805  if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
806  continue;
807  }
808 
809  if ( $index->unique ) {
810  $sqlIndex = 'CREATE UNIQUE INDEX';
811  } else {
812  $sqlIndex = 'CREATE INDEX';
813  }
814  // Try to come up with a new index name, given indexes have database scope in SQLite
815  $indexName = $newName . '_' . $index->name;
816  $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
817  ' ON ' . $this->platform->addIdentifierQuotes( $newName );
818 
819  $indexInfo = $this->query(
820  'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
821  $fname,
822  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
823  );
824  $fields = [];
825  foreach ( $indexInfo as $indexInfoRow ) {
826  $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
827  }
828 
829  $sqlIndex .= '(' . implode( ',', $fields ) . ')';
830 
831  $this->query(
832  $sqlIndex,
833  __METHOD__,
834  self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT
835  );
836  }
837 
838  return $res;
839  }
840 
849  public function listTables( $prefix = null, $fname = __METHOD__ ) {
850  $result = $this->query(
851  "SELECT name FROM sqlite_master WHERE type = 'table'",
852  $fname,
853  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
854  );
855 
856  $endArray = [];
857 
858  foreach ( $result as $table ) {
859  $vars = get_object_vars( $table );
860  $table = array_pop( $vars );
861 
862  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
863  if ( strpos( $table, 'sqlite_' ) !== 0 ) {
864  $endArray[] = $table;
865  }
866  }
867  }
868 
869  return $endArray;
870  }
871 
872  protected function doTruncate( array $tables, $fname ) {
873  $this->startAtomic( $fname );
874 
875  $encSeqNames = [];
876  foreach ( $tables as $table ) {
877  // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
878  $sql = "DELETE FROM " . $this->tableName( $table );
879  $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA );
880 
881  $encSeqNames[] = $this->addQuotes( $this->tableName( $table, 'raw' ) );
882  }
883 
884  $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
885  $this->query(
886  "DELETE FROM $encMasterTable WHERE name IN(" . implode( ',', $encSeqNames ) . ")",
887  $fname,
888  self::QUERY_CHANGE_SCHEMA
889  );
890 
891  $this->endAtomic( $fname );
892  }
893 
894  public function setTableAliases( array $aliases ) {
895  parent::setTableAliases( $aliases );
896  if ( $this->isOpen() ) {
897  $this->attachDatabasesFromTableAliases();
898  }
899  }
900 
904  private function attachDatabasesFromTableAliases() {
905  foreach ( $this->platform->getTableAliases() as $params ) {
906  if (
907  $params['dbname'] !== $this->getDBname() &&
908  !isset( $this->sessionAttachedDbs[$params['dbname']] )
909  ) {
910  $this->attachDatabase( $params['dbname'], false, __METHOD__ );
911  $this->sessionAttachedDbs[$params['dbname']] = true;
912  }
913  }
914  }
915 
916  public function databasesAreIndependent() {
917  return true;
918  }
919 
920  protected function doHandleSessionLossPreconnect() {
921  $this->sessionAttachedDbs = [];
922  // Release all locks, via FSLockManager::__destruct, as the base class expects;
923  $this->lockMgr = null;
924  // Create a new lock manager instance
925  $this->lockMgr = $this->makeLockManager();
926  }
927 
928  protected function doFlushSession( $fname ) {
929  // Release all locks, via FSLockManager::__destruct, as the base class expects
930  $this->lockMgr = null;
931  // Create a new lock manager instance
932  $this->lockMgr = $this->makeLockManager();
933  }
934 
938  protected function getBindingHandle() {
939  return parent::getBindingHandle();
940  }
941 }
942 
946 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
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.
getTopologyBasedServerId()
Get a non-recycled ID that uniquely identifies this server within the replication topology.
deadlockLoop(... $args)
No-op version of deadlockLoop.
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:44
string null $password
Password used to establish the current connection.
Definition: Database.php:81
newExceptionAfterConnectError( $error)
Definition: Database.php:1563
getDomainID()
Return the currently selected domain ID.
Definition: Database.php:531
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:610
string null $server
Server that this instance is currently connected to.
Definition: Database.php:77
close( $fname=__METHOD__)
Close the database connection.
Definition: Database.php:621
static factory( $type, $params=[], $connect=self::NEW_CONNECTED)
Construct a Database subclass instance given a database type and parameters.
Definition: Database.php:367
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:3734
string null $user
User that this instance is currently connected under the name of.
Definition: Database.php:79
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.
Definition: Database.php:873
getDBname()
Get the current database name; null if there isn't one.
Definition: Database.php:1829
Interface for query language.
if( $line===false) $args
Definition: mcc.php:124
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