MediaWiki master
DatabaseSqlite.php
Go to the documentation of this file.
1<?php
20namespace Wikimedia\Rdbms;
21
23use LockManager;
25use PDO;
26use PDOException;
27use PDOStatement;
28use RuntimeException;
32
40class 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 // How many DB pages to keep in memory
72 'cache_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 $this->logger,
103 $this->currentDomain,
104 $this->errorLogger
105 );
106 $this->replicationReporter = new ReplicationReporter(
107 $params['topologyRole'],
108 $this->logger,
109 $params['srvCache']
110 );
111 }
112
113 public static function getAttributes() {
114 return [
115 self::ATTR_DB_IS_FILE => true,
116 self::ATTR_DB_LEVEL_LOCKING => true
117 ];
118 }
119
129 public static function newStandaloneInstance( $filename, array $p = [] ) {
130 $p['dbFilePath'] = $filename;
131 $p['schema'] = null;
132 $p['tablePrefix'] = '';
134 $db = ( new DatabaseFactory() )->create( 'sqlite', $p );
135 '@phan-var DatabaseSqlite $db';
136
137 return $db;
138 }
139
143 public function getType() {
144 return 'sqlite';
145 }
146
147 protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
148 $this->close( __METHOD__ );
149
150 // Note that for SQLite, $server, $user, and $pass are ignored
151
152 if ( $schema !== null ) {
153 throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
154 }
155
156 if ( $this->dbPath !== null ) {
158 } elseif ( $this->dbDir !== null ) {
159 $path = self::generateFileName( $this->dbDir, $db );
160 } else {
161 throw $this->newExceptionAfterConnectError( "DB path or directory required" );
162 }
163
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" );
169 }
170
171 $attributes = [
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
177 ];
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()
185 );
186 } else {
187 $attributes[PDO::ATTR_PERSISTENT] = true;
188 }
189 }
190
191 try {
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() );
196 }
197
198 $this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
199 $this->platform->setPrefix( $tablePrefix );
200
201 try {
202 // Enforce LIKE to be case sensitive, just like MySQL
203 $query = new Query(
204 'PRAGMA case_sensitive_like = 1',
205 self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY,
206 'PRAGMA'
207 );
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];
214 if (
215 ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
216 ( is_string( $allowed ) && gettype( $value ) === $allowed )
217 ) {
218 $query = new Query(
219 "PRAGMA $name = $value",
220 self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY,
221 'PRAGMA',
222 null,
223 "PRAGMA $name = '?'"
224 );
225 $this->query( $query, __METHOD__ );
226 }
227 }
228 $this->attachDatabasesFromTableAliases();
229 } catch ( RuntimeException $e ) {
230 throw $this->newExceptionAfterConnectError( $e->getMessage() );
231 }
232 }
233
237 private function getDefaultPragmas() {
238 $variables = [];
239
240 if ( !$this->cliMode ) {
241 $variables['temp_store'] = 'MEMORY';
242 }
243
244 return $variables;
245 }
246
252 public function getDbFilePath() {
253 return $this->dbPath ?? self::generateFileName( $this->dbDir, $this->getDBname() );
254 }
255
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';
264 }
265
266 return null;
267 }
268
274 private function makeLockManager(): LockManager {
275 $lockDirectory = $this->getLockFileDirectory();
276 if ( $lockDirectory !== null ) {
277 return new FSLockManager( [
278 'domain' => $this->getDomainID(),
279 'lockDirectory' => $lockDirectory,
280 ] );
281 } else {
282 return new NullLockManager( [ 'domain' => $this->getDomainID() ] );
283 }
284 }
285
290 protected function closeConnection() {
291 $this->conn = null;
292 // Release all locks, via FSLockManager::__destruct, as the base class expects
293 $this->lockMgr = null;
294
295 return true;
296 }
297
305 public static function generateFileName( $dir, $dbName ) {
306 if ( $dir == '' ) {
307 throw new DBUnexpectedError( null, __CLASS__ . ": no DB directory specified" );
308 } elseif ( self::isProcessMemoryPath( $dir ) ) {
309 throw new DBUnexpectedError(
310 null,
311 __CLASS__ . ": cannot use process memory directory '$dir'"
312 );
313 } elseif ( !strlen( $dbName ) ) {
314 throw new DBUnexpectedError( null, __CLASS__ . ": no DB name specified" );
315 }
316
317 return "$dir/$dbName.sqlite";
318 }
319
324 private static function generateDatabaseName( $path ) {
325 if ( preg_match( '/^(:memory:$|file::memory:)/', $path ) ) {
326 // E.g. "file::memory:?cache=shared" => ":memory":
327 return ':memory:';
328 } elseif ( preg_match( '/^file::([^?]+)\?mode=memory(&|$)/', $path, $m ) ) {
329 // E.g. "file:memdb1?mode=memory" => ":memdb1:"
330 return ":{$m[1]}:";
331 } else {
332 // E.g. "/home/.../some_db.sqlite3" => "some_db"
333 return preg_replace( '/\.sqlite\d?$/', '', basename( $path ) );
334 }
335 }
336
341 private static function isProcessMemoryPath( $path ) {
342 return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
343 }
344
349 public static function getFulltextSearchModule() {
350 static $cachedResult = null;
351 if ( $cachedResult !== null ) {
352 return $cachedResult;
353 }
354 $cachedResult = false;
355 $table = 'dummy_search_test';
356
357 $db = self::newStandaloneInstance( ':memory:' );
358 if ( $db->query(
359 "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)",
360 __METHOD__,
361 IDatabase::QUERY_SILENCE_ERRORS
362 ) ) {
363 $cachedResult = 'FTS3';
364 }
365 $db->close( __METHOD__ );
366
367 return $cachedResult;
368 }
369
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 );
385 $query = new Query(
386 "ATTACH DATABASE $encFile AS $name",
387 self::QUERY_CHANGE_TRX,
388 'ATTACH'
389 );
390 return $this->query( $query, $fname );
391 }
392
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,
399 $this->lastError(),
400 $this->lastErrno()
401 );
402 }
403
404 protected function doSelectDomain( DatabaseDomain $domain ) {
405 if ( $domain->getSchema() !== null ) {
406 throw new DBExpectedError(
407 $this,
408 __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
409 );
410 }
411
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(),
417 null,
418 $domain->getTablePrefix()
419 );
420 $this->platform->setPrefix( $domain->getTablePrefix() );
421
422 return true;
423 }
424
425 if ( $database !== $this->getDBname() ) {
426 throw new DBExpectedError(
427 $this,
428 __CLASS__ . ": cannot change database (got '$database')"
429 );
430 }
431
432 // Update that domain fields on success (no exception thrown)
433 $this->currentDomain = $domain;
434 $this->platform->setPrefix( $domain->getTablePrefix() );
435
436 return true;
437 }
438
439 protected function lastInsertId() {
440 // PDO::lastInsertId yields a string :(
441 return (int)$this->getBindingHandle()->lastInsertId();
442 }
443
447 public function lastError() {
448 if ( is_object( $this->conn ) ) {
449 $e = $this->conn->errorInfo();
450
451 return $e[2] ?? $this->lastConnectError;
452 }
453
454 return 'No database connection';
455 }
456
460 public function lastErrno() {
461 if ( is_object( $this->conn ) ) {
462 $info = $this->conn->errorInfo();
463
464 if ( isset( $info[1] ) ) {
465 return $info[1];
466 }
467 }
468
469 return 0;
470 }
471
472 public function tableExists( $table, $fname = __METHOD__ ) {
473 $tableRaw = $this->tableName( $table, 'raw' );
474 if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
475 return true; // already known to exist
476 }
477
478 $encTable = $this->addQuotes( $tableRaw );
479 $query = new Query(
480 "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
481 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
482 'SELECT'
483 );
484 $res = $this->query( $query, __METHOD__ );
485
486 return (bool)$res->numRows();
487 }
488
499 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
500 $query = new Query(
501 'PRAGMA index_info(' . $this->addQuotes( $this->platform->indexName( $index ) ) . ')',
502 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
503 'PRAGMA'
504 );
505 $res = $this->query( $query, $fname );
506 if ( !$res || $res->numRows() == 0 ) {
507 return false;
508 }
509 $info = [];
510 foreach ( $res as $row ) {
511 $info[] = $row->name;
512 }
513
514 return $info;
515 }
516
523 public function indexUnique( $table, $index, $fname = __METHOD__ ) {
524 $row = $this->selectRow( 'sqlite_master', '*',
525 [
526 'type' => 'index',
527 'name' => $this->platform->indexName( $index ),
528 ], $fname );
529 if ( !$row || !isset( $row->sql ) ) {
530 return null;
531 }
532
533 // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
534 $indexPos = strpos( $row->sql, 'INDEX' );
535 if ( $indexPos === false ) {
536 return null;
537 }
538 $firstPart = substr( $row->sql, 0, $indexPos );
539 $options = explode( ' ', $firstPart );
540
541 return in_array( 'UNIQUE', $options );
542 }
543
544 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
545 $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
546 if ( !$rows ) {
547 return;
548 }
549 $encTable = $this->tableName( $table );
550 [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
551 // https://sqlite.org/lang_insert.html
552 // Note that any auto-increment columns on conflicting rows will be reassigned
553 // due to combined DELETE+INSERT semantics. This will be reflected in insertId().
554 $query = new Query(
555 "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
556 self::QUERY_CHANGE_ROWS,
557 'REPLACE',
558 $table
559 );
560 $this->query( $query, $fname );
561 }
562
571 public function textFieldSize( $table, $field ) {
572 wfDeprecated( __METHOD__, '1.43' );
573 return -1;
574 }
575
579 public function wasDeadlock() {
580 return $this->lastErrno() == 5; // SQLITE_BUSY
581 }
582
586 public function wasReadOnlyError() {
587 return $this->lastErrno() == 8; // SQLITE_READONLY;
588 }
589
590 protected function isConnectionError( $errno ) {
591 return $errno == 17; // SQLITE_SCHEMA;
592 }
593
594 protected function isKnownStatementRollbackError( $errno ) {
595 // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
596 // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
597 // https://sqlite.org/lang_createtable.html#uniqueconst
598 // https://sqlite.org/lang_conflict.html
599 return false;
600 }
601
602 public function serverIsReadOnly() {
603 $this->assertHasConnectionHandle();
604
605 $path = $this->getDbFilePath();
606
607 return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
608 }
609
613 public function getSoftwareLink() {
614 return "[{{int:version-db-sqlite-url}} SQLite]";
615 }
616
620 public function getServerVersion() {
621 if ( $this->version === null ) {
622 $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
623 }
624
625 return $this->version;
626 }
627
636 public function fieldInfo( $table, $field ) {
637 $components = $this->platform->qualifiedTableComponents( $table );
638 $tableRaw = end( $components );
639 $query = new Query(
640 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
641 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
642 'PRAGMA'
643 );
644 $res = $this->query( $query, __METHOD__ );
645 foreach ( $res as $row ) {
646 if ( $row->name == $field ) {
647 return new SQLiteField( $row, $tableRaw );
648 }
649 }
650
651 return false;
652 }
653
654 protected function doBegin( $fname = '' ) {
655 if ( $this->trxMode != '' ) {
656 $sql = "BEGIN {$this->trxMode}";
657 } else {
658 $sql = 'BEGIN';
659 }
660 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'BEGIN' );
661 $this->query( $query, $fname );
662 }
663
668 public function strencode( $s ) {
669 return substr( $this->addQuotes( $s ), 1, -1 );
670 }
671
676 public function encodeBlob( $b ) {
677 return new Blob( $b );
678 }
679
684 public function decodeBlob( $b ) {
685 if ( $b instanceof Blob ) {
686 $b = $b->fetch();
687 }
688 if ( $b === null ) {
689 // An empty blob is decoded as null in PHP before PHP 8.1.
690 // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
691 $b = '';
692 }
693
694 return $b;
695 }
696
701 public function addQuotes( $s ) {
702 if ( $s instanceof Blob ) {
703 return "x'" . bin2hex( $s->fetch() ) . "'";
704 } elseif ( is_bool( $s ) ) {
705 return (string)(int)$s;
706 } elseif ( is_int( $s ) ) {
707 return (string)$s;
708 } elseif ( strpos( (string)$s, "\0" ) !== false ) {
709 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
710 // This is a known limitation of SQLite's mprintf function which PDO
711 // should work around, but doesn't. I have reported this to php.net as bug #63419:
712 // https://bugs.php.net/bug.php?id=63419
713 // There was already a similar report for SQLite3::escapeString, bug #62361:
714 // https://bugs.php.net/bug.php?id=62361
715 // There is an additional bug regarding sorting this data after insert
716 // on older versions of sqlite shipped with ubuntu 12.04
717 // https://phabricator.wikimedia.org/T74367
718 $this->logger->debug(
719 __FUNCTION__ .
720 ': Quoting value containing null byte. ' .
721 'For consistency all binary data should have been ' .
722 'first processed with self::encodeBlob()'
723 );
724 return "x'" . bin2hex( (string)$s ) . "'";
725 } else {
726 return $this->getBindingHandle()->quote( (string)$s );
727 }
728 }
729
730 public function doLockIsFree( string $lockName, string $method ) {
731 // Only locks by this thread will be checked
732 return true;
733 }
734
735 public function doLock( string $lockName, string $method, int $timeout ) {
736 $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
737 if (
738 $this->lockMgr instanceof FSLockManager &&
739 $status->hasMessage( 'lockmanager-fail-openlock' )
740 ) {
741 throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
742 }
743
744 return $status->isOK() ? microtime( true ) : null;
745 }
746
747 public function doUnlock( string $lockName, string $method ) {
748 return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
749 }
750
759 public function duplicateTableStructure(
760 $oldName, $newName, $temporary = false, $fname = __METHOD__
761 ) {
762 $query = new Query(
763 "SELECT sql FROM sqlite_master WHERE tbl_name=" .
764 $this->addQuotes( $oldName ) . " AND type='table'",
765 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
766 'SELECT'
767 );
768 $res = $this->query( $query, $fname );
769 $obj = $res->fetchObject();
770 if ( !$obj ) {
771 throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
772 }
773 $sqlCreateTable = $obj->sql;
774 $sqlCreateTable = preg_replace(
775 '/(?<=\W)"?' .
776 preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
777 '"?(?=\W)/',
778 $this->platform->addIdentifierQuotes( $newName ),
779 $sqlCreateTable,
780 1
781 );
782 $flags = self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT;
783 if ( $temporary ) {
784 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
785 $this->logger->debug(
786 "Table $oldName is virtual, can't create a temporary duplicate." );
787 } else {
788 $sqlCreateTable = str_replace(
789 'CREATE TABLE',
790 'CREATE TEMPORARY TABLE',
791 $sqlCreateTable
792 );
793 }
794 }
795
796 $query = new Query(
797 $sqlCreateTable,
798 $flags,
799 $temporary ? 'CREATE TEMPORARY' : 'CREATE',
800 // Use a dot to avoid double-prefixing in Database::getTempTableWrites()
801 '.' . $newName
802 );
803 $res = $this->query( $query, $fname );
804
805 $query = new Query(
806 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
807 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
808 'PRAGMA'
809 );
810 // Take over indexes
811 $indexList = $this->query( $query, $fname );
812 foreach ( $indexList as $index ) {
813 if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
814 continue;
815 }
816
817 if ( $index->unique ) {
818 $sqlIndex = 'CREATE UNIQUE INDEX';
819 } else {
820 $sqlIndex = 'CREATE INDEX';
821 }
822 // Try to come up with a new index name, given indexes have database scope in SQLite
823 $indexName = $newName . '_' . $index->name;
824 $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
825 ' ON ' . $this->platform->addIdentifierQuotes( $newName );
826
827 $query = new Query(
828 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
829 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
830 'PRAGMA'
831 );
832 $indexInfo = $this->query( $query, $fname );
833 $fields = [];
834 foreach ( $indexInfo as $indexInfoRow ) {
835 $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
836 }
837
838 $sqlIndex .= '(' . implode( ',', $fields ) . ')';
839
840 $query = new Query(
841 $sqlIndex,
842 self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT,
843 'CREATE',
844 $newName
845 );
846 $this->query( $query, __METHOD__ );
847 }
848
849 return $res;
850 }
851
860 public function listTables( $prefix = null, $fname = __METHOD__ ) {
861 $query = new Query(
862 "SELECT name FROM sqlite_master WHERE type = 'table'",
863 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
864 'SELECT'
865 );
866 $result = $this->query( $query, $fname );
867
868 $endArray = [];
869
870 foreach ( $result as $table ) {
871 $vars = get_object_vars( $table );
872 $table = array_pop( $vars );
873
874 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
875 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
876 $endArray[] = $table;
877 }
878 }
879 }
880
881 return $endArray;
882 }
883
884 public function truncateTable( $table, $fname = __METHOD__ ) {
885 $this->startAtomic( $fname );
886 // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
887 $query = new Query(
888 "DELETE FROM " . $this->tableName( $table ),
889 self::QUERY_CHANGE_SCHEMA,
890 'DELETE',
891 $table
892 );
893 $this->query( $query, $fname );
894
895 $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
896 $encSequenceName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
897 $query = new Query(
898 "DELETE FROM $encMasterTable WHERE name = $encSequenceName",
899 self::QUERY_CHANGE_SCHEMA,
900 'DELETE',
901 'sqlite_sequence'
902 );
903 $this->query( $query, $fname );
904
905 $this->endAtomic( $fname );
906 }
907
908 public function setTableAliases( array $aliases ) {
909 parent::setTableAliases( $aliases );
910 if ( $this->isOpen() ) {
911 $this->attachDatabasesFromTableAliases();
912 }
913 }
914
918 private function attachDatabasesFromTableAliases() {
919 foreach ( $this->platform->getTableAliases() as $params ) {
920 if (
921 $params['dbname'] !== $this->getDBname() &&
922 !isset( $this->sessionAttachedDbs[$params['dbname']] )
923 ) {
924 $this->attachDatabase( $params['dbname'], false, __METHOD__ );
925 $this->sessionAttachedDbs[$params['dbname']] = true;
926 }
927 }
928 }
929
930 public function databasesAreIndependent() {
931 return true;
932 }
933
934 protected function doHandleSessionLossPreconnect() {
935 $this->sessionAttachedDbs = [];
936 // Release all locks, via FSLockManager::__destruct, as the base class expects;
937 $this->lockMgr = null;
938 // Create a new lock manager instance
939 $this->lockMgr = $this->makeLockManager();
940 }
941
942 protected function doFlushSession( $fname ) {
943 // Release all locks, via FSLockManager::__destruct, as the base class expects
944 $this->lockMgr = null;
945 // Create a new lock manager instance
946 $this->lockMgr = $this->makeLockManager();
947 }
948
952 protected function getBindingHandle() {
953 return parent::getBindingHandle();
954 }
955
956 protected function getInsertIdColumnForUpsert( $table ) {
957 $components = $this->platform->qualifiedTableComponents( $table );
958 $tableRaw = end( $components );
959 $query = new Query(
960 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
961 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
962 'PRAGMA'
963 );
964 $res = $this->query( $query, __METHOD__ );
965 foreach ( $res as $row ) {
966 if ( $row->pk && strtolower( $row->type ) === 'integer' ) {
967 return $row->name;
968 }
969 }
970
971 return null;
972 }
973}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
array $params
The job parameters.
Simple lock management based on server-local temporary files.
Resource locking handling.
Simple lock management based on in-process reference counting.
Database error base class.
Definition DBError.php:36
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 query} 1.28
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.
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.
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.
truncateTable( $table, $fname=__METHOD__)
Delete all data in a table and reset any sequences owned by that table.
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:43
newExceptionAfterConnectError( $error)
getDomainID()
Return the currently selected domain ID.
Definition Database.php:399
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:476
close( $fname=__METHOD__)
Close the database connection.
Definition Database.php:487
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.
Definition Database.php:631
getDBname()
Get the current database name; null if there isn't one.
Holds information on Query to be executed.
Definition Query.php:31