MediaWiki master
DatabaseSqlite.php
Go to the documentation of this file.
1<?php
6namespace Wikimedia\Rdbms;
7
8use PDO;
9use PDOException;
10use PDOStatement;
11use RuntimeException;
18
26class DatabaseSqlite extends Database {
28 protected $dbDir;
30 protected $dbPath;
32 protected $trxMode;
33
35 protected $conn;
36
38 protected $lockMgr;
39
41 private $version;
42
44 private $sessionAttachedDbs = [];
45
47 private const VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
48
50 private const VALID_PRAGMAS = [
51 // Optimizations or requirements regarding fsync() usage
52 'synchronous' => [ 'EXTRA', 'FULL', 'NORMAL', 'OFF' ],
53 // Optimizations for TEMPORARY tables
54 'temp_store' => [ 'FILE', 'MEMORY' ],
55 // Optimizations for disk use and page cache
56 'mmap_size' => 'integer',
57 // How many DB pages to keep in memory
58 'cache_size' => 'integer',
59 ];
60
62 protected $platform;
63
70 public function __construct( array $params ) {
71 if ( isset( $params['dbFilePath'] ) ) {
72 $this->dbPath = $params['dbFilePath'];
73 if ( !isset( $params['dbname'] ) || $params['dbname'] === '' ) {
74 $params['dbname'] = self::generateDatabaseName( $this->dbPath );
75 }
76 } elseif ( isset( $params['dbDirectory'] ) ) {
77 $this->dbDir = $params['dbDirectory'];
78 }
79
80 parent::__construct( $params );
81
82 $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
83
84 $this->lockMgr = $this->makeLockManager();
85 $this->platform = new SqlitePlatform(
86 $this,
87 $this->logger,
88 $this->currentDomain,
89 $this->errorLogger
90 );
91 $this->replicationReporter = new ReplicationReporter(
92 $params['topologyRole'],
93 $this->logger,
94 $params['srvCache']
95 );
96 }
97
99 public static function getAttributes() {
100 return [
101 self::ATTR_DB_IS_FILE => true,
102 self::ATTR_DB_LEVEL_LOCKING => true
103 ];
104 }
105
115 public static function newStandaloneInstance( $filename, array $p = [] ) {
116 $p['dbFilePath'] = $filename;
117 $p['schema'] = null;
118 $p['tablePrefix'] = '';
120 $db = ( new DatabaseFactory() )->create( 'sqlite', $p );
121 '@phan-var DatabaseSqlite $db';
122
123 return $db;
124 }
125
129 public function getType() {
130 return 'sqlite';
131 }
132
134 protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
135 $this->close( __METHOD__ );
136
137 // Note that for SQLite, $server, $user, and $pass are ignored
138
139 if ( $schema !== null ) {
140 throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
141 }
142
143 if ( $this->dbPath !== null ) {
145 } elseif ( $this->dbDir !== null ) {
146 $path = self::generateFileName( $this->dbDir, $db );
147 } else {
148 throw $this->newExceptionAfterConnectError( "DB path or directory required" );
149 }
150
151 // Check if the database file already exists but is non-readable
152 if ( !self::isProcessMemoryPath( $path ) && is_file( $path ) && !is_readable( $path ) ) {
153 throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
154 } elseif ( !in_array( $this->trxMode, self::VALID_TRX_MODES, true ) ) {
155 throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
156 }
157
158 $attributes = [
159 PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
160 // Starting with PHP 8.1, The SQLite PDO returns proper types instead
161 // of strings or null for everything. We cast every non-null value to
162 // string to restore the old behavior.
163 PDO::ATTR_STRINGIFY_FETCHES => true
164 ];
165 if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
166 // Persistent connections can avoid some schema index reading overhead.
167 // On the other hand, they can cause horrible contention with DBO_TRX.
168 if ( $this->getFlag( self::DBO_TRX ) || $this->getFlag( self::DBO_DEFAULT ) ) {
169 $this->logger->warning(
170 __METHOD__ . ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
171 $this->getLogContext()
172 );
173 } else {
174 $attributes[PDO::ATTR_PERSISTENT] = true;
175 }
176 }
177
178 try {
179 // Open the database file, creating it if it does not yet exist
180 $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
181 } catch ( PDOException $e ) {
182 throw $this->newExceptionAfterConnectError( $e->getMessage() );
183 }
184
185 $this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
186 $this->platform->setCurrentDomain( $this->currentDomain );
187
188 try {
189 // Enforce LIKE to be case sensitive, just like MySQL
190 $query = new Query(
191 'PRAGMA case_sensitive_like = 1',
192 self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY,
193 'PRAGMA'
194 );
195 $this->query( $query, __METHOD__ );
196 // Set any connection-level custom PRAGMA options
197 $pragmas = array_intersect_key( $this->connectionVariables, self::VALID_PRAGMAS );
198 $pragmas += $this->getDefaultPragmas();
199 foreach ( $pragmas as $name => $value ) {
200 $allowed = self::VALID_PRAGMAS[$name];
201 if (
202 ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
203 ( is_string( $allowed ) && gettype( $value ) === $allowed )
204 ) {
205 $query = new Query(
206 "PRAGMA $name = $value",
207 self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY,
208 'PRAGMA',
209 null,
210 "PRAGMA $name = '?'"
211 );
212 $this->query( $query, __METHOD__ );
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__,
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 $query = new Query(
373 "ATTACH DATABASE $encFile AS $name",
374 self::QUERY_CHANGE_TRX,
375 'ATTACH'
376 );
377 return $this->query( $query, $fname );
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
392 protected function doSelectDomain( DatabaseDomain $domain ) {
393 if ( $domain->getSchema() !== null ) {
394 throw new DBExpectedError(
395 $this,
396 __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
397 );
398 }
399
400 $database = $domain->getDatabase();
401 // A null database means "don't care" so leave it as is and update the table prefix
402 if ( $database === null ) {
403 $this->currentDomain = new DatabaseDomain(
404 $this->currentDomain->getDatabase(),
405 null,
406 $domain->getTablePrefix()
407 );
408 $this->platform->setCurrentDomain( $this->currentDomain );
409
410 return true;
411 }
412
413 if ( $database !== $this->getDBname() ) {
414 throw new DBExpectedError(
415 $this,
416 __CLASS__ . ": cannot change database (got '$database')"
417 );
418 }
419
420 // Update that domain fields on success (no exception thrown)
421 $this->currentDomain = $domain;
422 $this->platform->setCurrentDomain( $domain );
423
424 return true;
425 }
426
428 protected function lastInsertId() {
429 // PDO::lastInsertId yields a string :(
430 return (int)$this->getBindingHandle()->lastInsertId();
431 }
432
436 public function lastError() {
437 if ( is_object( $this->conn ) ) {
438 $e = $this->conn->errorInfo();
439
440 return $e[2] ?? $this->lastConnectError;
441 }
442
443 return 'No database connection';
444 }
445
449 public function lastErrno() {
450 if ( is_object( $this->conn ) ) {
451 $info = $this->conn->errorInfo();
452
453 if ( isset( $info[1] ) ) {
454 return $info[1];
455 }
456 }
457
458 return 0;
459 }
460
462 public function tableExists( $table, $fname = __METHOD__ ) {
463 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
464 if ( isset( $this->sessionTempTables[$db][$pt] ) ) {
465 return true; // already known to exist
466 }
467
468 $encTable = $this->addQuotes( $pt );
469 $query = new Query(
470 "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
471 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
472 'SELECT'
473 );
474 $res = $this->query( $query, __METHOD__ );
475
476 return (bool)$res->numRows();
477 }
478
480 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
481 $components = $this->platform->qualifiedTableComponents( $table );
482 $tableRaw = end( $components );
483 $query = new Query(
484 'PRAGMA index_list(' . $this->addQuotes( $tableRaw ) . ')',
485 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
486 'PRAGMA'
487 );
488 $res = $this->query( $query, $fname );
489
490 foreach ( $res as $row ) {
491 if ( $row->name === $index ) {
492 return [ 'unique' => (bool)$row->unique ];
493 }
494 }
495
496 return false;
497 }
498
500 public function getPrimaryKeyColumns( $table, $fname = __METHOD__ ) {
501 $components = $this->platform->qualifiedTableComponents( $table );
502 $tableRaw = end( $components );
503 $query = new Query(
504 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
505 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
506 'PRAGMA'
507 );
508 $res = $this->query( $query, $fname );
509
510 $pkBySeq = [];
511 foreach ( $res as $row ) {
512 if ( isset( $row->pk ) && (int)$row->pk > 0 ) {
513 $pkBySeq[(int)$row->pk] = (string)$row->name;
514 }
515 }
516
517 ksort( $pkBySeq );
518
519 return array_values( $pkBySeq );
520 }
521
523 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
524 $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
525 if ( !$rows ) {
526 return;
527 }
528 $encTable = $this->tableName( $table );
529 [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
530 // https://sqlite.org/lang_insert.html
531 // Note that any auto-increment columns on conflicting rows will be reassigned
532 // due to combined DELETE+INSERT semantics. This will be reflected in insertId().
533 $query = new Query(
534 "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
535 self::QUERY_CHANGE_ROWS,
536 'REPLACE',
537 $table
538 );
539 $this->query( $query, $fname );
540 }
541
543 protected function isConnectionError( $errno ) {
544 return $errno == 17; // SQLITE_SCHEMA;
545 }
546
548 protected function isKnownStatementRollbackError( $errno ) {
549 // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
550 // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
551 // https://sqlite.org/lang_createtable.html#uniqueconst
552 // https://sqlite.org/lang_conflict.html
553 return false;
554 }
555
557 public function serverIsReadOnly() {
558 $this->assertHasConnectionHandle();
559
560 $path = $this->getDbFilePath();
561
562 return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
563 }
564
568 public function getSoftwareLink() {
569 return "[{{int:version-db-sqlite-url}} SQLite]";
570 }
571
575 public function getServerVersion() {
576 if ( $this->version === null ) {
577 $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
578 }
579
580 return $this->version;
581 }
582
591 public function fieldInfo( $table, $field ) {
592 $components = $this->platform->qualifiedTableComponents( $table );
593 $tableRaw = end( $components );
594 $query = new Query(
595 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
596 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
597 'PRAGMA'
598 );
599 $res = $this->query( $query, __METHOD__ );
600 foreach ( $res as $row ) {
601 if ( $row->name == $field ) {
602 return new SQLiteField( $row, $tableRaw );
603 }
604 }
605
606 return false;
607 }
608
610 protected function doBegin( $fname = '' ) {
611 if ( $this->trxMode != '' ) {
612 $sql = "BEGIN {$this->trxMode}";
613 } else {
614 $sql = 'BEGIN';
615 }
616 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'BEGIN' );
617 $this->query( $query, $fname );
618 }
619
624 public function strencode( $s ) {
625 return substr( $this->addQuotes( $s ), 1, -1 );
626 }
627
632 public function encodeBlob( $b ) {
633 return new Blob( $b );
634 }
635
640 public function decodeBlob( $b ) {
641 if ( $b instanceof Blob ) {
642 $b = $b->fetch();
643 }
644 if ( $b === null ) {
645 // An empty blob is decoded as null in PHP before PHP 8.1.
646 // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
647 $b = '';
648 }
649
650 return $b;
651 }
652
654 public function addQuotes( $s ) {
655 if ( $s instanceof RawSQLValue ) {
656 return $s->toSql();
657 }
658 if ( $s instanceof Blob ) {
659 return "x'" . bin2hex( $s->fetch() ) . "'";
660 } elseif ( is_bool( $s ) ) {
661 return (string)(int)$s;
662 } elseif ( is_int( $s ) ) {
663 return (string)$s;
664 } elseif ( str_contains( (string)$s, "\0" ) ) {
665 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
666 // This is a known limitation of SQLite's mprintf function which PDO
667 // should work around, but doesn't. I have reported this to php.net as bug #63419:
668 // https://bugs.php.net/bug.php?id=63419
669 // There was already a similar report for SQLite3::escapeString, bug #62361:
670 // https://bugs.php.net/bug.php?id=62361
671 // There is an additional bug regarding sorting this data after insert
672 // on older versions of sqlite shipped with ubuntu 12.04
673 // https://phabricator.wikimedia.org/T74367
674 $this->logger->debug(
675 __FUNCTION__ .
676 ': Quoting value containing null byte. ' .
677 'For consistency all binary data should have been ' .
678 'first processed with self::encodeBlob()'
679 );
680 return "x'" . bin2hex( (string)$s ) . "'";
681 } else {
682 return $this->getBindingHandle()->quote( (string)$s );
683 }
684 }
685
687 public function doLockIsFree( string $lockName, string $method ) {
688 // Only locks by this thread will be checked
689 return true;
690 }
691
693 public function doLock( string $lockName, string $method, int $timeout ) {
694 $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
695 if (
696 $this->lockMgr instanceof FSLockManager &&
697 $status->hasMessage( 'lockmanager-fail-openlock' )
698 ) {
699 throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
700 }
701
702 return $status->isOK() ? microtime( true ) : null;
703 }
704
706 public function doUnlock( string $lockName, string $method ) {
707 return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
708 }
709
718 public function duplicateTableStructure(
719 $oldName, $newName, $temporary = false, $fname = __METHOD__
720 ) {
721 $query = new Query(
722 "SELECT sql FROM sqlite_master WHERE tbl_name=" .
723 $this->addQuotes( $oldName ) . " AND type='table'",
724 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
725 'SELECT'
726 );
727 $res = $this->query( $query, $fname );
728 $obj = $res->fetchObject();
729 if ( !$obj ) {
730 throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
731 }
732 $sqlCreateTable = $obj->sql;
733 $sqlCreateTable = preg_replace(
734 '/(?<=\W)"?' .
735 preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
736 '"?(?=\W)/',
737 $this->platform->addIdentifierQuotes( $newName ),
738 $sqlCreateTable,
739 1
740 );
741 $flags = self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT;
742 if ( $temporary ) {
743 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
744 $this->logger->debug(
745 "Table $oldName is virtual, can't create a temporary duplicate." );
746 } else {
747 $sqlCreateTable = str_replace(
748 'CREATE TABLE',
749 'CREATE TEMPORARY TABLE',
750 $sqlCreateTable
751 );
752 }
753 }
754
755 $query = new Query(
756 $sqlCreateTable,
757 $flags,
758 $temporary ? 'CREATE TEMPORARY' : 'CREATE',
759 // Use a dot to avoid double-prefixing in Database::getTempTableWrites()
760 '.' . $newName
761 );
762 $res = $this->query( $query, $fname );
763
764 $query = new Query(
765 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
766 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
767 'PRAGMA'
768 );
769 // Take over indexes
770 $indexList = $this->query( $query, $fname );
771 foreach ( $indexList as $index ) {
772 if ( str_starts_with( $index->name, 'sqlite_autoindex' ) ) {
773 continue;
774 }
775
776 if ( $index->unique ) {
777 $sqlIndex = 'CREATE UNIQUE INDEX';
778 } else {
779 $sqlIndex = 'CREATE INDEX';
780 }
781 // Try to come up with a new index name, given indexes have database scope in SQLite
782 $indexName = $newName . '_' . $index->name;
783 $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
784 ' ON ' . $this->platform->addIdentifierQuotes( $newName );
785
786 $query = new Query(
787 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
788 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
789 'PRAGMA'
790 );
791 $indexInfo = $this->query( $query, $fname );
792 $fields = [];
793 foreach ( $indexInfo as $indexInfoRow ) {
794 $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
795 }
796
797 $sqlIndex .= '(' . implode( ',', $fields ) . ')';
798
799 $query = new Query(
800 $sqlIndex,
801 self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT,
802 'CREATE',
803 $newName
804 );
805 $this->query( $query, __METHOD__ );
806 }
807
808 return $res;
809 }
810
819 public function listTables( $prefix = null, $fname = __METHOD__ ) {
820 $query = new Query(
821 "SELECT name FROM sqlite_master WHERE type = 'table'",
822 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
823 'SELECT'
824 );
825 $result = $this->query( $query, $fname );
826
827 $endArray = [];
828
829 foreach ( $result as $table ) {
830 $vars = get_object_vars( $table );
831 $table = array_pop( $vars );
832
833 if ( !$prefix || str_starts_with( $table, $prefix ) ) {
834 if ( !str_starts_with( $table, 'sqlite_' ) ) {
835 $endArray[] = $table;
836 }
837 }
838 }
839
840 return $endArray;
841 }
842
844 public function truncateTable( $table, $fname = __METHOD__ ) {
845 $this->startAtomic( $fname );
846 // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
847 $query = new Query(
848 "DELETE FROM " . $this->tableName( $table ),
849 self::QUERY_CHANGE_SCHEMA,
850 'DELETE',
851 $table
852 );
853 $this->query( $query, $fname );
854
855 $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
856 $encSequenceName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
857 $query = new Query(
858 "DELETE FROM $encMasterTable WHERE name = $encSequenceName",
859 self::QUERY_CHANGE_SCHEMA,
860 'DELETE',
861 'sqlite_sequence'
862 );
863 $this->query( $query, $fname );
864
865 $this->endAtomic( $fname );
866 }
867
868 public function setTableAliases( array $aliases ) {
869 parent::setTableAliases( $aliases );
870 if ( $this->isOpen() ) {
871 $this->attachDatabasesFromTableAliases();
872 }
873 }
874
878 private function attachDatabasesFromTableAliases() {
879 foreach ( $this->platform->getTableAliases() as $params ) {
880 if (
881 $params['dbname'] !== $this->getDBname() &&
882 !isset( $this->sessionAttachedDbs[$params['dbname']] )
883 ) {
884 $this->attachDatabase( $params['dbname'], false, __METHOD__ );
885 $this->sessionAttachedDbs[$params['dbname']] = true;
886 }
887 }
888 }
889
891 public function databasesAreIndependent() {
892 return true;
893 }
894
895 protected function doHandleSessionLossPreconnect() {
896 $this->sessionAttachedDbs = [];
897 // Release all locks, via FSLockManager::__destruct, as the base class expects;
898 $this->lockMgr = null;
899 // Create a new lock manager instance
900 $this->lockMgr = $this->makeLockManager();
901 }
902
904 protected function doFlushSession( $fname ) {
905 // Release all locks, via FSLockManager::__destruct, as the base class expects
906 $this->lockMgr = null;
907 // Create a new lock manager instance
908 $this->lockMgr = $this->makeLockManager();
909 }
910
914 protected function getBindingHandle() {
915 return parent::getBindingHandle();
916 }
917
919 protected function getInsertIdColumnForUpsert( $table ) {
920 $components = $this->platform->qualifiedTableComponents( $table );
921 $tableRaw = end( $components );
922 $query = new Query(
923 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
924 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
925 'PRAGMA'
926 );
927 $res = $this->query( $query, __METHOD__ );
928 foreach ( $res as $row ) {
929 if ( $row->pk && strtolower( $row->type ) === 'integer' ) {
930 return $row->name;
931 }
932 }
933
934 return null;
935 }
936}
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:22
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.
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.string
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....
static getAttributes()
array Map of (Database::ATTR_* constant => value) 1.31
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.In systems like mysql/mariadb,...
doLock(string $lockName, string $method, int $timeout)
lock()float|null UNIX timestamp of lock acquisition; null on failure
__construct(array $params)
Additional params include:
getPrimaryKeyColumns( $table, $fname=__METHOD__)
Get the primary key columns of a table.to be used by updater onlystring[] query}
string null $dbPath
Explicit path for the SQLite database file.
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.bool Whether the given query error was a c...
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.bool query}
isKnownStatementRollbackError( $errno)
bool Whether it is known that the last query error only caused statement rollback This is for backwar...
doSelectDomain(DatabaseDomain $domain)
1.32
doLockIsFree(string $lockName, string $method)
lockIsFree()bool Success
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__)
Get information about an index into an object.array<string,mixed>|false Index info map; false if it d...
doBegin( $fname='')
Issues the BEGIN command to the database server.Database::begin()
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)connectionParams} connectionParams} c...
doFlushSession( $fname)
Reset the server-side session state for named locks and table locks.Connection and query errors will ...
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.1.42
doSingleStatementQuery(string $sql)
Run a query and return a QueryStatus instance with the query result information.
getInsertIdColumnForUpsert( $table)
string|null The AUTO_INCREMENT/SERIAL column; null if not needed
doUnlock(string $lockName, string $method)
unlock()bool Success
static generateFileName( $dir, $dbName)
Generates a database file name.
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
A single concrete connection to a relational database.
Definition Database.php:39
newExceptionAfterConnectError( $error)
getDomainID()
Return the currently selected domain ID.Null components (database/schema) might change once a connect...
Definition Database.php:402
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:469
close( $fname=__METHOD__)
Close the database connection.This should only be called after any transactions have been resolved,...
Definition Database.php:478
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.bool
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.If a connection loss is detected,...
Definition Database.php:621
getDBname()
Get the current database name; null if there isn't one.string|null
Holds information on Query to be executed.
Definition Query.php:17
Raw SQL value to be used in query builders.
const QUERY_SILENCE_ERRORS
Ignore query errors and return false when they happen.