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;
32 
40 class DatabaseSqlite extends Database {
42  protected $dbDir;
44  protected $dbPath;
46  protected $trxMode;
47 
49  protected $conn;
50 
52  protected $lockMgr;
53 
55  private $version;
56 
58  private $sessionAttachedDbs = [];
59 
61  private const VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
62 
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  ];
72 
74  protected $platform;
75 
83  public function __construct( array $params ) {
84  if ( isset( $params['dbFilePath'] ) ) {
85  $this->dbPath = $params['dbFilePath'];
86  if ( !isset( $params['dbname'] ) || $params['dbname'] === '' ) {
87  $params['dbname'] = self::generateDatabaseName( $this->dbPath );
88  }
89  } elseif ( isset( $params['dbDirectory'] ) ) {
90  $this->dbDir = $params['dbDirectory'];
91  }
92 
93  parent::__construct( $params );
94 
95  $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
96 
97  $this->lockMgr = $this->makeLockManager();
98  $this->platform = new SqlitePlatform(
99  $this,
100  $this->logger,
101  $this->currentDomain,
102  $this->errorLogger
103  );
104  $this->replicationReporter = new ReplicationReporter(
105  $params['topologyRole'],
106  $this->logger,
107  $params['srvCache']
108  );
109  }
110 
111  public static function getAttributes() {
112  return [
113  self::ATTR_DB_IS_FILE => true,
114  self::ATTR_DB_LEVEL_LOCKING => true
115  ];
116  }
117 
127  public static function newStandaloneInstance( $filename, array $p = [] ) {
128  $p['dbFilePath'] = $filename;
129  $p['schema'] = null;
130  $p['tablePrefix'] = '';
132  $db = ( new DatabaseFactory() )->create( 'sqlite', $p );
133  '@phan-var DatabaseSqlite $db';
134 
135  return $db;
136  }
137 
141  public function getType() {
142  return 'sqlite';
143  }
144 
145  protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
146  $this->close( __METHOD__ );
147 
148  // Note that for SQLite, $server, $user, and $pass are ignored
149 
150  if ( $schema !== null ) {
151  throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
152  }
153 
154  if ( $this->dbPath !== null ) {
156  } elseif ( $this->dbDir !== null ) {
157  $path = self::generateFileName( $this->dbDir, $db );
158  } else {
159  throw $this->newExceptionAfterConnectError( "DB path or directory required" );
160  }
161 
162  // Check if the database file already exists but is non-readable
163  if ( !self::isProcessMemoryPath( $path ) && is_file( $path ) && !is_readable( $path ) ) {
164  throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
165  } elseif ( !in_array( $this->trxMode, self::VALID_TRX_MODES, true ) ) {
166  throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
167  }
168 
169  $attributes = [
170  PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
171  // Starting with PHP 8.1, The SQLite PDO returns proper types instead
172  // of strings or null for everything. We cast every non-null value to
173  // string to restore the old behavior.
174  PDO::ATTR_STRINGIFY_FETCHES => true
175  ];
176  if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
177  // Persistent connections can avoid some schema index reading overhead.
178  // On the other hand, they can cause horrible contention with DBO_TRX.
179  if ( $this->getFlag( self::DBO_TRX ) || $this->getFlag( self::DBO_DEFAULT ) ) {
180  $this->logger->warning(
181  __METHOD__ . ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
182  $this->getLogContext()
183  );
184  } else {
185  $attributes[PDO::ATTR_PERSISTENT] = true;
186  }
187  }
188 
189  try {
190  // Open the database file, creating it if it does not yet exist
191  $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
192  } catch ( PDOException $e ) {
193  throw $this->newExceptionAfterConnectError( $e->getMessage() );
194  }
195 
196  $this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
197  $this->platform->setPrefix( $tablePrefix );
198 
199  try {
200  $flags = self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY;
201  // Enforce LIKE to be case sensitive, just like MySQL
202  $this->query( 'PRAGMA case_sensitive_like = 1', __METHOD__, $flags );
203  // Set any connection-level custom PRAGMA options
204  $pragmas = array_intersect_key( $this->connectionVariables, self::VALID_PRAGMAS );
205  $pragmas += $this->getDefaultPragmas();
206  foreach ( $pragmas as $name => $value ) {
207  $allowed = self::VALID_PRAGMAS[$name];
208  if (
209  ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
210  ( is_string( $allowed ) && gettype( $value ) === $allowed )
211  ) {
212  $this->query( "PRAGMA $name = $value", __METHOD__, $flags );
213  }
214  }
215  $this->attachDatabasesFromTableAliases();
216  } catch ( RuntimeException $e ) {
217  throw $this->newExceptionAfterConnectError( $e->getMessage() );
218  }
219  }
220 
224  private function getDefaultPragmas() {
225  $variables = [];
226 
227  if ( !$this->cliMode ) {
228  $variables['temp_store'] = 'MEMORY';
229  }
230 
231  return $variables;
232  }
233 
239  public function getDbFilePath() {
240  return $this->dbPath ?? self::generateFileName( $this->dbDir, $this->getDBname() );
241  }
242 
246  public function getLockFileDirectory() {
247  if ( $this->dbPath !== null && !self::isProcessMemoryPath( $this->dbPath ) ) {
248  return dirname( $this->dbPath ) . '/locks';
249  } elseif ( $this->dbDir !== null && !self::isProcessMemoryPath( $this->dbDir ) ) {
250  return $this->dbDir . '/locks';
251  }
252 
253  return null;
254  }
255 
261  private function makeLockManager(): LockManager {
262  $lockDirectory = $this->getLockFileDirectory();
263  if ( $lockDirectory !== null ) {
264  return new FSLockManager( [
265  'domain' => $this->getDomainID(),
266  'lockDirectory' => $lockDirectory,
267  ] );
268  } else {
269  return new NullLockManager( [ 'domain' => $this->getDomainID() ] );
270  }
271  }
272 
277  protected function closeConnection() {
278  $this->conn = null;
279  // Release all locks, via FSLockManager::__destruct, as the base class expects
280  $this->lockMgr = null;
281 
282  return true;
283  }
284 
292  public static function generateFileName( $dir, $dbName ) {
293  if ( $dir == '' ) {
294  throw new DBUnexpectedError( null, __CLASS__ . ": no DB directory specified" );
295  } elseif ( self::isProcessMemoryPath( $dir ) ) {
296  throw new DBUnexpectedError(
297  null,
298  __CLASS__ . ": cannot use process memory directory '$dir'"
299  );
300  } elseif ( !strlen( $dbName ) ) {
301  throw new DBUnexpectedError( null, __CLASS__ . ": no DB name specified" );
302  }
303 
304  return "$dir/$dbName.sqlite";
305  }
306 
311  private static function generateDatabaseName( $path ) {
312  if ( preg_match( '/^(:memory:$|file::memory:)/', $path ) ) {
313  // E.g. "file::memory:?cache=shared" => ":memory":
314  return ':memory:';
315  } elseif ( preg_match( '/^file::([^?]+)\?mode=memory(&|$)/', $path, $m ) ) {
316  // E.g. "file:memdb1?mode=memory" => ":memdb1:"
317  return ":{$m[1]}:";
318  } else {
319  // E.g. "/home/.../some_db.sqlite3" => "some_db"
320  return preg_replace( '/\.sqlite\d?$/', '', basename( $path ) );
321  }
322  }
323 
328  private static function isProcessMemoryPath( $path ) {
329  return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
330  }
331 
336  public static function getFulltextSearchModule() {
337  static $cachedResult = null;
338  if ( $cachedResult !== null ) {
339  return $cachedResult;
340  }
341  $cachedResult = false;
342  $table = 'dummy_search_test';
343 
344  $db = self::newStandaloneInstance( ':memory:' );
345  if ( $db->query(
346  "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)",
347  __METHOD__,
348  IDatabase::QUERY_SILENCE_ERRORS
349  ) ) {
350  $cachedResult = 'FTS3';
351  }
352  $db->close( __METHOD__ );
353 
354  return $cachedResult;
355  }
356 
369  public function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
370  $file = is_string( $file ) ? $file : self::generateFileName( $this->dbDir, $name );
371  $encFile = $this->addQuotes( $file );
372 
373  return $this->query(
374  "ATTACH DATABASE $encFile AS $name",
375  $fname,
376  self::QUERY_CHANGE_TRX
377  );
378  }
379 
380  protected function doSingleStatementQuery( string $sql ): QueryStatus {
381  $res = $this->getBindingHandle()->query( $sql );
382  // Note that rowCount() returns 0 for SELECT for SQLite
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 
426  protected function lastInsertId() {
427  // PDO::lastInsertId yields a string :(
428  return (int)$this->getBindingHandle()->lastInsertId();
429  }
430 
434  public function lastError() {
435  if ( is_object( $this->conn ) ) {
436  $e = $this->conn->errorInfo();
437 
438  return $e[2] ?? $this->lastConnectError;
439  }
440 
441  return 'No database connection';
442  }
443 
447  public function lastErrno() {
448  if ( is_object( $this->conn ) ) {
449  $info = $this->conn->errorInfo();
450 
451  if ( isset( $info[1] ) ) {
452  return $info[1];
453  }
454  }
455 
456  return 0;
457  }
458 
459  public function tableExists( $table, $fname = __METHOD__ ) {
460  $tableRaw = $this->tableName( $table, 'raw' );
461  if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
462  return true; // already known to exist
463  }
464 
465  $encTable = $this->addQuotes( $tableRaw );
466  $res = $this->query(
467  "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
468  __METHOD__,
469  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
470  );
471 
472  return (bool)$res->numRows();
473  }
474 
485  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
486  $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->platform->indexName( $index ) ) . ')';
487  $res = $this->query( $sql, $fname, self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE );
488  if ( !$res || $res->numRows() == 0 ) {
489  return false;
490  }
491  $info = [];
492  foreach ( $res as $row ) {
493  $info[] = $row->name;
494  }
495 
496  return $info;
497  }
498 
505  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
506  $row = $this->selectRow( 'sqlite_master', '*',
507  [
508  'type' => 'index',
509  'name' => $this->platform->indexName( $index ),
510  ], $fname );
511  if ( !$row || !isset( $row->sql ) ) {
512  return null;
513  }
514 
515  // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
516  $indexPos = strpos( $row->sql, 'INDEX' );
517  if ( $indexPos === false ) {
518  return null;
519  }
520  $firstPart = substr( $row->sql, 0, $indexPos );
521  $options = explode( ' ', $firstPart );
522 
523  return in_array( 'UNIQUE', $options );
524  }
525 
526  public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
527  $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
528  if ( !$rows ) {
529  return;
530  }
531  $encTable = $this->tableName( $table );
532  [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
533  // https://sqlite.org/lang_insert.html
534  // Note that any auto-increment columns on conflicting rows will be reassigned
535  // due to combined DELETE+INSERT semantics. This will be reflected in insertId().
536  $this->query(
537  "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
538  $fname,
539  self::QUERY_CHANGE_ROWS
540  );
541  }
542 
551  public function textFieldSize( $table, $field ) {
552  return -1;
553  }
554 
558  public function wasDeadlock() {
559  return $this->lastErrno() == 5; // SQLITE_BUSY
560  }
561 
565  public function wasReadOnlyError() {
566  return $this->lastErrno() == 8; // SQLITE_READONLY;
567  }
568 
569  protected function isConnectionError( $errno ) {
570  return $errno == 17; // SQLITE_SCHEMA;
571  }
572 
573  protected function isKnownStatementRollbackError( $errno ) {
574  // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
575  // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
576  // https://sqlite.org/lang_createtable.html#uniqueconst
577  // https://sqlite.org/lang_conflict.html
578  return false;
579  }
580 
581  public function serverIsReadOnly() {
582  $this->assertHasConnectionHandle();
583 
584  $path = $this->getDbFilePath();
585 
586  return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
587  }
588 
592  public function getSoftwareLink() {
593  return "[{{int:version-db-sqlite-url}} SQLite]";
594  }
595 
599  public function getServerVersion() {
600  if ( $this->version === null ) {
601  $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
602  }
603 
604  return $this->version;
605  }
606 
615  public function fieldInfo( $table, $field ) {
616  $tableRaw = $this->tableName( $table, 'raw' );
617  $res = $this->query(
618  'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
619  __METHOD__,
620  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
621  );
622  foreach ( $res as $row ) {
623  if ( $row->name == $field ) {
624  return new SQLiteField( $row, $tableRaw );
625  }
626  }
627 
628  return false;
629  }
630 
631  protected function doBegin( $fname = '' ) {
632  if ( $this->trxMode != '' ) {
633  $this->query( "BEGIN {$this->trxMode}", $fname, self::QUERY_CHANGE_TRX );
634  } else {
635  $this->query( 'BEGIN', $fname, self::QUERY_CHANGE_TRX );
636  }
637  }
638 
643  public function strencode( $s ) {
644  return substr( $this->addQuotes( $s ), 1, -1 );
645  }
646 
651  public function encodeBlob( $b ) {
652  return new Blob( $b );
653  }
654 
659  public function decodeBlob( $b ) {
660  if ( $b instanceof Blob ) {
661  $b = $b->fetch();
662  }
663  if ( $b === null ) {
664  // An empty blob is decoded as null in PHP before PHP 8.1.
665  // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
666  $b = '';
667  }
668 
669  return $b;
670  }
671 
676  public function addQuotes( $s ) {
677  if ( $s instanceof Blob ) {
678  return "x'" . bin2hex( $s->fetch() ) . "'";
679  } elseif ( is_bool( $s ) ) {
680  return (string)(int)$s;
681  } elseif ( is_int( $s ) ) {
682  return (string)$s;
683  } elseif ( strpos( (string)$s, "\0" ) !== false ) {
684  // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
685  // This is a known limitation of SQLite's mprintf function which PDO
686  // should work around, but doesn't. I have reported this to php.net as bug #63419:
687  // https://bugs.php.net/bug.php?id=63419
688  // There was already a similar report for SQLite3::escapeString, bug #62361:
689  // https://bugs.php.net/bug.php?id=62361
690  // There is an additional bug regarding sorting this data after insert
691  // on older versions of sqlite shipped with ubuntu 12.04
692  // https://phabricator.wikimedia.org/T74367
693  $this->logger->debug(
694  __FUNCTION__ .
695  ': Quoting value containing null byte. ' .
696  'For consistency all binary data should have been ' .
697  'first processed with self::encodeBlob()'
698  );
699  return "x'" . bin2hex( (string)$s ) . "'";
700  } else {
701  return $this->getBindingHandle()->quote( (string)$s );
702  }
703  }
704 
705  public function doLockIsFree( string $lockName, string $method ) {
706  // Only locks by this thread will be checked
707  return true;
708  }
709 
710  public function doLock( string $lockName, string $method, int $timeout ) {
711  $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
712  if (
713  $this->lockMgr instanceof FSLockManager &&
714  $status->hasMessage( 'lockmanager-fail-openlock' )
715  ) {
716  throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
717  }
718 
719  return $status->isOK() ? microtime( true ) : null;
720  }
721 
722  public function doUnlock( string $lockName, string $method ) {
723  return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
724  }
725 
734  public function duplicateTableStructure(
735  $oldName, $newName, $temporary = false, $fname = __METHOD__
736  ) {
737  $res = $this->query(
738  "SELECT sql FROM sqlite_master WHERE tbl_name=" .
739  $this->addQuotes( $oldName ) . " AND type='table'",
740  $fname,
741  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
742  );
743  $obj = $res->fetchObject();
744  if ( !$obj ) {
745  throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
746  }
747  $sqlCreateTable = $obj->sql;
748  $sqlCreateTable = preg_replace(
749  '/(?<=\W)"?' .
750  preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
751  '"?(?=\W)/',
752  $this->platform->addIdentifierQuotes( $newName ),
753  $sqlCreateTable,
754  1
755  );
756  if ( $temporary ) {
757  if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
758  $this->logger->debug(
759  "Table $oldName is virtual, can't create a temporary duplicate." );
760  } else {
761  $sqlCreateTable = str_replace(
762  'CREATE TABLE',
763  'CREATE TEMPORARY TABLE',
764  $sqlCreateTable
765  );
766  }
767  }
768 
769  $res = $this->query(
770  $sqlCreateTable,
771  $fname,
772  self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT
773  );
774 
775  // Take over indexes
776  $indexList = $this->query(
777  'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
778  $fname,
779  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
780  );
781  foreach ( $indexList as $index ) {
782  if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
783  continue;
784  }
785 
786  if ( $index->unique ) {
787  $sqlIndex = 'CREATE UNIQUE INDEX';
788  } else {
789  $sqlIndex = 'CREATE INDEX';
790  }
791  // Try to come up with a new index name, given indexes have database scope in SQLite
792  $indexName = $newName . '_' . $index->name;
793  $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
794  ' ON ' . $this->platform->addIdentifierQuotes( $newName );
795 
796  $indexInfo = $this->query(
797  'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
798  $fname,
799  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
800  );
801  $fields = [];
802  foreach ( $indexInfo as $indexInfoRow ) {
803  $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
804  }
805 
806  $sqlIndex .= '(' . implode( ',', $fields ) . ')';
807 
808  $this->query(
809  $sqlIndex,
810  __METHOD__,
811  self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT
812  );
813  }
814 
815  return $res;
816  }
817 
826  public function listTables( $prefix = null, $fname = __METHOD__ ) {
827  $result = $this->query(
828  "SELECT name FROM sqlite_master WHERE type = 'table'",
829  $fname,
830  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
831  );
832 
833  $endArray = [];
834 
835  foreach ( $result as $table ) {
836  $vars = get_object_vars( $table );
837  $table = array_pop( $vars );
838 
839  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
840  if ( strpos( $table, 'sqlite_' ) !== 0 ) {
841  $endArray[] = $table;
842  }
843  }
844  }
845 
846  return $endArray;
847  }
848 
849  protected function doTruncate( array $tables, $fname ) {
850  $this->startAtomic( $fname );
851 
852  $encSeqNames = [];
853  foreach ( $tables as $table ) {
854  // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
855  $sql = "DELETE FROM " . $this->tableName( $table );
856  $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA );
857 
858  $encSeqNames[] = $this->addQuotes( $this->tableName( $table, 'raw' ) );
859  }
860 
861  $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
862  $this->query(
863  "DELETE FROM $encMasterTable WHERE name IN(" . implode( ',', $encSeqNames ) . ")",
864  $fname,
865  self::QUERY_CHANGE_SCHEMA
866  );
867 
868  $this->endAtomic( $fname );
869  }
870 
871  public function setTableAliases( array $aliases ) {
872  parent::setTableAliases( $aliases );
873  if ( $this->isOpen() ) {
874  $this->attachDatabasesFromTableAliases();
875  }
876  }
877 
881  private function attachDatabasesFromTableAliases() {
882  foreach ( $this->platform->getTableAliases() as $params ) {
883  if (
884  $params['dbname'] !== $this->getDBname() &&
885  !isset( $this->sessionAttachedDbs[$params['dbname']] )
886  ) {
887  $this->attachDatabase( $params['dbname'], false, __METHOD__ );
888  $this->sessionAttachedDbs[$params['dbname']] = true;
889  }
890  }
891  }
892 
893  public function databasesAreIndependent() {
894  return true;
895  }
896 
897  protected function doHandleSessionLossPreconnect() {
898  $this->sessionAttachedDbs = [];
899  // Release all locks, via FSLockManager::__destruct, as the base class expects;
900  $this->lockMgr = null;
901  // Create a new lock manager instance
902  $this->lockMgr = $this->makeLockManager();
903  }
904 
905  protected function doFlushSession( $fname ) {
906  // Release all locks, via FSLockManager::__destruct, as the base class expects
907  $this->lockMgr = null;
908  // Create a new lock manager instance
909  $this->lockMgr = $this->makeLockManager();
910  }
911 
915  protected function getBindingHandle() {
916  return parent::getBindingHandle();
917  }
918 
919  protected function getInsertIdColumnForUpsert( $table ) {
920  $tableRaw = $this->tableName( $table, 'raw' );
921  $res = $this->query(
922  'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
923  __METHOD__,
924  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
925  );
926  foreach ( $res as $row ) {
927  if ( $row->pk && strtolower( $row->type ) === 'integer' ) {
928  return $row->name;
929  }
930  }
931 
932  return null;
933  }
934 }
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:37
Base class for the more common types of database errors.
Class to handle database/schema/prefix specifications for IDatabase.
Constructs Database objects.
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 this DB server is running in server-side read-only mode If an error occurs,...
lastInsertId()
Get a row ID from the last insert statement to implicitly assign one within the session.
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.
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.
replace( $table, $uniqueKeys, $rows, $fname=__METHOD__)
Insert row(s) into a table, in the provided order, while deleting conflicting rows.
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.
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Relational database abstraction object.
Definition: Database.php:44
newExceptionAfterConnectError( $error)
Definition: Database.php:1244
getDomainID()
Return the currently selected domain ID.
Definition: Database.php:404
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:483
close( $fname=__METHOD__)
Close the database connection.
Definition: Database.php:494
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:3371
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.
Definition: Database.php:658
getDBname()
Get the current database name; null if there isn't one.
Definition: Database.php:1564
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