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 return -1;
573 }
574
578 public function wasDeadlock() {
579 return $this->lastErrno() == 5; // SQLITE_BUSY
580 }
581
585 public function wasReadOnlyError() {
586 return $this->lastErrno() == 8; // SQLITE_READONLY;
587 }
588
589 protected function isConnectionError( $errno ) {
590 return $errno == 17; // SQLITE_SCHEMA;
591 }
592
593 protected function isKnownStatementRollbackError( $errno ) {
594 // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
595 // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
596 // https://sqlite.org/lang_createtable.html#uniqueconst
597 // https://sqlite.org/lang_conflict.html
598 return false;
599 }
600
601 public function serverIsReadOnly() {
602 $this->assertHasConnectionHandle();
603
604 $path = $this->getDbFilePath();
605
606 return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
607 }
608
612 public function getSoftwareLink() {
613 return "[{{int:version-db-sqlite-url}} SQLite]";
614 }
615
619 public function getServerVersion() {
620 if ( $this->version === null ) {
621 $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
622 }
623
624 return $this->version;
625 }
626
635 public function fieldInfo( $table, $field ) {
636 $tableRaw = $this->tableName( $table, 'raw' );
637 $query = new Query(
638 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
639 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
640 'PRAGMA'
641 );
642 $res = $this->query( $query, __METHOD__ );
643 foreach ( $res as $row ) {
644 if ( $row->name == $field ) {
645 return new SQLiteField( $row, $tableRaw );
646 }
647 }
648
649 return false;
650 }
651
652 protected function doBegin( $fname = '' ) {
653 if ( $this->trxMode != '' ) {
654 $sql = "BEGIN {$this->trxMode}";
655 } else {
656 $sql = 'BEGIN';
657 }
658 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'BEGIN' );
659 $this->query( $query, $fname );
660 }
661
666 public function strencode( $s ) {
667 return substr( $this->addQuotes( $s ), 1, -1 );
668 }
669
674 public function encodeBlob( $b ) {
675 return new Blob( $b );
676 }
677
682 public function decodeBlob( $b ) {
683 if ( $b instanceof Blob ) {
684 $b = $b->fetch();
685 }
686 if ( $b === null ) {
687 // An empty blob is decoded as null in PHP before PHP 8.1.
688 // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
689 $b = '';
690 }
691
692 return $b;
693 }
694
699 public function addQuotes( $s ) {
700 if ( $s instanceof Blob ) {
701 return "x'" . bin2hex( $s->fetch() ) . "'";
702 } elseif ( is_bool( $s ) ) {
703 return (string)(int)$s;
704 } elseif ( is_int( $s ) ) {
705 return (string)$s;
706 } elseif ( strpos( (string)$s, "\0" ) !== false ) {
707 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
708 // This is a known limitation of SQLite's mprintf function which PDO
709 // should work around, but doesn't. I have reported this to php.net as bug #63419:
710 // https://bugs.php.net/bug.php?id=63419
711 // There was already a similar report for SQLite3::escapeString, bug #62361:
712 // https://bugs.php.net/bug.php?id=62361
713 // There is an additional bug regarding sorting this data after insert
714 // on older versions of sqlite shipped with ubuntu 12.04
715 // https://phabricator.wikimedia.org/T74367
716 $this->logger->debug(
717 __FUNCTION__ .
718 ': Quoting value containing null byte. ' .
719 'For consistency all binary data should have been ' .
720 'first processed with self::encodeBlob()'
721 );
722 return "x'" . bin2hex( (string)$s ) . "'";
723 } else {
724 return $this->getBindingHandle()->quote( (string)$s );
725 }
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 $query = new Query(
761 "SELECT sql FROM sqlite_master WHERE tbl_name=" .
762 $this->addQuotes( $oldName ) . " AND type='table'",
763 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
764 'SELECT'
765 );
766 $res = $this->query( $query, $fname );
767 $obj = $res->fetchObject();
768 if ( !$obj ) {
769 throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
770 }
771 $sqlCreateTable = $obj->sql;
772 $sqlCreateTable = preg_replace(
773 '/(?<=\W)"?' .
774 preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
775 '"?(?=\W)/',
776 $this->platform->addIdentifierQuotes( $newName ),
777 $sqlCreateTable,
778 1
779 );
780 $flags = self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT;
781 if ( $temporary ) {
782 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
783 $this->logger->debug(
784 "Table $oldName is virtual, can't create a temporary duplicate." );
785 } else {
786 $sqlCreateTable = str_replace(
787 'CREATE TABLE',
788 'CREATE TEMPORARY TABLE',
789 $sqlCreateTable
790 );
791 $flags |= self::QUERY_CREATE_TEMP;
792 }
793 }
794
795 $query = new Query(
796 $sqlCreateTable,
797 $flags,
798 $temporary ? 'CREATE TEMPORARY' : 'CREATE',
799 // Use a dot to avoid double-prefixing in Database::getTempTableWrites()
800 '.' . $newName
801 );
802 $res = $this->query( $query, $fname );
803
804 $query = new Query(
805 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
806 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
807 'PRAGMA'
808 );
809 // Take over indexes
810 $indexList = $this->query( $query, $fname );
811 foreach ( $indexList as $index ) {
812 if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
813 continue;
814 }
815
816 if ( $index->unique ) {
817 $sqlIndex = 'CREATE UNIQUE INDEX';
818 } else {
819 $sqlIndex = 'CREATE INDEX';
820 }
821 // Try to come up with a new index name, given indexes have database scope in SQLite
822 $indexName = $newName . '_' . $index->name;
823 $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
824 ' ON ' . $this->platform->addIdentifierQuotes( $newName );
825
826 $query = new Query(
827 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
828 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
829 'PRAGMA'
830 );
831 $indexInfo = $this->query( $query, $fname );
832 $fields = [];
833 foreach ( $indexInfo as $indexInfoRow ) {
834 $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
835 }
836
837 $sqlIndex .= '(' . implode( ',', $fields ) . ')';
838
839 $query = new Query(
840 $sqlIndex,
841 self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT,
842 'CREATE',
843 $newName
844 );
845 $this->query( $query, __METHOD__ );
846 }
847
848 return $res;
849 }
850
859 public function listTables( $prefix = null, $fname = __METHOD__ ) {
860 $query = new Query(
861 "SELECT name FROM sqlite_master WHERE type = 'table'",
862 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
863 'SELECT'
864 );
865 $result = $this->query( $query, $fname );
866
867 $endArray = [];
868
869 foreach ( $result as $table ) {
870 $vars = get_object_vars( $table );
871 $table = array_pop( $vars );
872
873 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
874 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
875 $endArray[] = $table;
876 }
877 }
878 }
879
880 return $endArray;
881 }
882
883 public function truncateTable( $table, $fname = __METHOD__ ) {
884 $this->startAtomic( $fname );
885 // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
886 $query = new Query(
887 "DELETE FROM " . $this->tableName( $table ),
888 self::QUERY_CHANGE_SCHEMA,
889 'DELETE',
890 $table
891 );
892 $this->query( $query, $fname );
893
894 $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
895 $encSequenceName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
896 $query = new Query(
897 "DELETE FROM $encMasterTable WHERE name = $encSequenceName",
898 self::QUERY_CHANGE_SCHEMA,
899 'DELETE',
900 'sqlite_sequence'
901 );
902 $this->query( $query, $fname );
903
904 $this->endAtomic( $fname );
905 }
906
907 public function setTableAliases( array $aliases ) {
908 parent::setTableAliases( $aliases );
909 if ( $this->isOpen() ) {
910 $this->attachDatabasesFromTableAliases();
911 }
912 }
913
917 private function attachDatabasesFromTableAliases() {
918 foreach ( $this->platform->getTableAliases() as $params ) {
919 if (
920 $params['dbname'] !== $this->getDBname() &&
921 !isset( $this->sessionAttachedDbs[$params['dbname']] )
922 ) {
923 $this->attachDatabase( $params['dbname'], false, __METHOD__ );
924 $this->sessionAttachedDbs[$params['dbname']] = true;
925 }
926 }
927 }
928
929 public function databasesAreIndependent() {
930 return true;
931 }
932
933 protected function doHandleSessionLossPreconnect() {
934 $this->sessionAttachedDbs = [];
935 // Release all locks, via FSLockManager::__destruct, as the base class expects;
936 $this->lockMgr = null;
937 // Create a new lock manager instance
938 $this->lockMgr = $this->makeLockManager();
939 }
940
941 protected function doFlushSession( $fname ) {
942 // Release all locks, via FSLockManager::__destruct, as the base class expects
943 $this->lockMgr = null;
944 // Create a new lock manager instance
945 $this->lockMgr = $this->makeLockManager();
946 }
947
951 protected function getBindingHandle() {
952 return parent::getBindingHandle();
953 }
954
955 protected function getInsertIdColumnForUpsert( $table ) {
956 $tableRaw = $this->tableName( $table, 'raw' );
957 $query = new Query(
958 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
959 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
960 'PRAGMA'
961 );
962 $res = $this->query( $query, __METHOD__ );
963 foreach ( $res as $row ) {
964 if ( $row->pk && strtolower( $row->type ) === 'integer' ) {
965 return $row->name;
966 }
967 }
968
969 return null;
970 }
971}
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