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->setCurrentDomain( $this->currentDomain );
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__,
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->setCurrentDomain( $this->currentDomain );
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->setCurrentDomain( $domain );
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 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
474 if ( isset( $this->sessionTempTables[$db][$pt] ) ) {
475 return true; // already known to exist
476 }
477
478 $encTable = $this->addQuotes( $pt );
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
489 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
490 $indexName = $this->platform->indexName( $index );
491 $components = $this->platform->qualifiedTableComponents( $table );
492 $tableRaw = end( $components );
493 $query = new Query(
494 'PRAGMA index_list(' . $this->addQuotes( $tableRaw ) . ')',
495 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
496 'PRAGMA'
497 );
498 $res = $this->query( $query, $fname );
499
500 foreach ( $res as $row ) {
501 if ( $row->name === $indexName ) {
502 return [ 'unique' => (bool)$row->unique ];
503 }
504 }
505
506 return false;
507 }
508
509 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
510 $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
511 if ( !$rows ) {
512 return;
513 }
514 $encTable = $this->tableName( $table );
515 [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
516 // https://sqlite.org/lang_insert.html
517 // Note that any auto-increment columns on conflicting rows will be reassigned
518 // due to combined DELETE+INSERT semantics. This will be reflected in insertId().
519 $query = new Query(
520 "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
521 self::QUERY_CHANGE_ROWS,
522 'REPLACE',
523 $table
524 );
525 $this->query( $query, $fname );
526 }
527
528 protected function isConnectionError( $errno ) {
529 return $errno == 17; // SQLITE_SCHEMA;
530 }
531
532 protected function isKnownStatementRollbackError( $errno ) {
533 // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
534 // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
535 // https://sqlite.org/lang_createtable.html#uniqueconst
536 // https://sqlite.org/lang_conflict.html
537 return false;
538 }
539
540 public function serverIsReadOnly() {
541 $this->assertHasConnectionHandle();
542
543 $path = $this->getDbFilePath();
544
545 return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
546 }
547
551 public function getSoftwareLink() {
552 return "[{{int:version-db-sqlite-url}} SQLite]";
553 }
554
558 public function getServerVersion() {
559 if ( $this->version === null ) {
560 $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
561 }
562
563 return $this->version;
564 }
565
574 public function fieldInfo( $table, $field ) {
575 $components = $this->platform->qualifiedTableComponents( $table );
576 $tableRaw = end( $components );
577 $query = new Query(
578 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
579 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
580 'PRAGMA'
581 );
582 $res = $this->query( $query, __METHOD__ );
583 foreach ( $res as $row ) {
584 if ( $row->name == $field ) {
585 return new SQLiteField( $row, $tableRaw );
586 }
587 }
588
589 return false;
590 }
591
592 protected function doBegin( $fname = '' ) {
593 if ( $this->trxMode != '' ) {
594 $sql = "BEGIN {$this->trxMode}";
595 } else {
596 $sql = 'BEGIN';
597 }
598 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'BEGIN' );
599 $this->query( $query, $fname );
600 }
601
606 public function strencode( $s ) {
607 return substr( $this->addQuotes( $s ), 1, -1 );
608 }
609
614 public function encodeBlob( $b ) {
615 return new Blob( $b );
616 }
617
622 public function decodeBlob( $b ) {
623 if ( $b instanceof Blob ) {
624 $b = $b->fetch();
625 }
626 if ( $b === null ) {
627 // An empty blob is decoded as null in PHP before PHP 8.1.
628 // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
629 $b = '';
630 }
631
632 return $b;
633 }
634
635 public function addQuotes( $s ) {
636 if ( $s instanceof RawSQLValue ) {
637 return $s->toSql();
638 }
639 if ( $s instanceof Blob ) {
640 return "x'" . bin2hex( $s->fetch() ) . "'";
641 } elseif ( is_bool( $s ) ) {
642 return (string)(int)$s;
643 } elseif ( is_int( $s ) ) {
644 return (string)$s;
645 } elseif ( strpos( (string)$s, "\0" ) !== false ) {
646 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
647 // This is a known limitation of SQLite's mprintf function which PDO
648 // should work around, but doesn't. I have reported this to php.net as bug #63419:
649 // https://bugs.php.net/bug.php?id=63419
650 // There was already a similar report for SQLite3::escapeString, bug #62361:
651 // https://bugs.php.net/bug.php?id=62361
652 // There is an additional bug regarding sorting this data after insert
653 // on older versions of sqlite shipped with ubuntu 12.04
654 // https://phabricator.wikimedia.org/T74367
655 $this->logger->debug(
656 __FUNCTION__ .
657 ': Quoting value containing null byte. ' .
658 'For consistency all binary data should have been ' .
659 'first processed with self::encodeBlob()'
660 );
661 return "x'" . bin2hex( (string)$s ) . "'";
662 } else {
663 return $this->getBindingHandle()->quote( (string)$s );
664 }
665 }
666
667 public function doLockIsFree( string $lockName, string $method ) {
668 // Only locks by this thread will be checked
669 return true;
670 }
671
672 public function doLock( string $lockName, string $method, int $timeout ) {
673 $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
674 if (
675 $this->lockMgr instanceof FSLockManager &&
676 $status->hasMessage( 'lockmanager-fail-openlock' )
677 ) {
678 throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
679 }
680
681 return $status->isOK() ? microtime( true ) : null;
682 }
683
684 public function doUnlock( string $lockName, string $method ) {
685 return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
686 }
687
696 public function duplicateTableStructure(
697 $oldName, $newName, $temporary = false, $fname = __METHOD__
698 ) {
699 $query = new Query(
700 "SELECT sql FROM sqlite_master WHERE tbl_name=" .
701 $this->addQuotes( $oldName ) . " AND type='table'",
702 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
703 'SELECT'
704 );
705 $res = $this->query( $query, $fname );
706 $obj = $res->fetchObject();
707 if ( !$obj ) {
708 throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
709 }
710 $sqlCreateTable = $obj->sql;
711 $sqlCreateTable = preg_replace(
712 '/(?<=\W)"?' .
713 preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
714 '"?(?=\W)/',
715 $this->platform->addIdentifierQuotes( $newName ),
716 $sqlCreateTable,
717 1
718 );
719 $flags = self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT;
720 if ( $temporary ) {
721 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
722 $this->logger->debug(
723 "Table $oldName is virtual, can't create a temporary duplicate." );
724 } else {
725 $sqlCreateTable = str_replace(
726 'CREATE TABLE',
727 'CREATE TEMPORARY TABLE',
728 $sqlCreateTable
729 );
730 }
731 }
732
733 $query = new Query(
734 $sqlCreateTable,
735 $flags,
736 $temporary ? 'CREATE TEMPORARY' : 'CREATE',
737 // Use a dot to avoid double-prefixing in Database::getTempTableWrites()
738 '.' . $newName
739 );
740 $res = $this->query( $query, $fname );
741
742 $query = new Query(
743 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
744 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
745 'PRAGMA'
746 );
747 // Take over indexes
748 $indexList = $this->query( $query, $fname );
749 foreach ( $indexList as $index ) {
750 if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
751 continue;
752 }
753
754 if ( $index->unique ) {
755 $sqlIndex = 'CREATE UNIQUE INDEX';
756 } else {
757 $sqlIndex = 'CREATE INDEX';
758 }
759 // Try to come up with a new index name, given indexes have database scope in SQLite
760 $indexName = $newName . '_' . $index->name;
761 $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
762 ' ON ' . $this->platform->addIdentifierQuotes( $newName );
763
764 $query = new Query(
765 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
766 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
767 'PRAGMA'
768 );
769 $indexInfo = $this->query( $query, $fname );
770 $fields = [];
771 foreach ( $indexInfo as $indexInfoRow ) {
772 $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
773 }
774
775 $sqlIndex .= '(' . implode( ',', $fields ) . ')';
776
777 $query = new Query(
778 $sqlIndex,
779 self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT,
780 'CREATE',
781 $newName
782 );
783 $this->query( $query, __METHOD__ );
784 }
785
786 return $res;
787 }
788
797 public function listTables( $prefix = null, $fname = __METHOD__ ) {
798 $query = new Query(
799 "SELECT name FROM sqlite_master WHERE type = 'table'",
800 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
801 'SELECT'
802 );
803 $result = $this->query( $query, $fname );
804
805 $endArray = [];
806
807 foreach ( $result as $table ) {
808 $vars = get_object_vars( $table );
809 $table = array_pop( $vars );
810
811 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
812 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
813 $endArray[] = $table;
814 }
815 }
816 }
817
818 return $endArray;
819 }
820
821 public function truncateTable( $table, $fname = __METHOD__ ) {
822 $this->startAtomic( $fname );
823 // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
824 $query = new Query(
825 "DELETE FROM " . $this->tableName( $table ),
826 self::QUERY_CHANGE_SCHEMA,
827 'DELETE',
828 $table
829 );
830 $this->query( $query, $fname );
831
832 $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
833 $encSequenceName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
834 $query = new Query(
835 "DELETE FROM $encMasterTable WHERE name = $encSequenceName",
836 self::QUERY_CHANGE_SCHEMA,
837 'DELETE',
838 'sqlite_sequence'
839 );
840 $this->query( $query, $fname );
841
842 $this->endAtomic( $fname );
843 }
844
845 public function setTableAliases( array $aliases ) {
846 parent::setTableAliases( $aliases );
847 if ( $this->isOpen() ) {
848 $this->attachDatabasesFromTableAliases();
849 }
850 }
851
855 private function attachDatabasesFromTableAliases() {
856 foreach ( $this->platform->getTableAliases() as $params ) {
857 if (
858 $params['dbname'] !== $this->getDBname() &&
859 !isset( $this->sessionAttachedDbs[$params['dbname']] )
860 ) {
861 $this->attachDatabase( $params['dbname'], false, __METHOD__ );
862 $this->sessionAttachedDbs[$params['dbname']] = true;
863 }
864 }
865 }
866
867 public function databasesAreIndependent() {
868 return true;
869 }
870
871 protected function doHandleSessionLossPreconnect() {
872 $this->sessionAttachedDbs = [];
873 // Release all locks, via FSLockManager::__destruct, as the base class expects;
874 $this->lockMgr = null;
875 // Create a new lock manager instance
876 $this->lockMgr = $this->makeLockManager();
877 }
878
879 protected function doFlushSession( $fname ) {
880 // Release all locks, via FSLockManager::__destruct, as the base class expects
881 $this->lockMgr = null;
882 // Create a new lock manager instance
883 $this->lockMgr = $this->makeLockManager();
884 }
885
889 protected function getBindingHandle() {
890 return parent::getBindingHandle();
891 }
892
893 protected function getInsertIdColumnForUpsert( $table ) {
894 $components = $this->platform->qualifiedTableComponents( $table );
895 $tableRaw = end( $components );
896 $query = new Query(
897 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
898 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
899 'PRAGMA'
900 );
901 $res = $this->query( $query, __METHOD__ );
902 foreach ( $res as $row ) {
903 if ( $row->pk && strtolower( $row->type ) === 'integer' ) {
904 return $row->name;
905 }
906 }
907
908 return null;
909 }
910}
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.
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
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.
doLock(string $lockName, string $method, int $timeout)
__construct(array $params)
Additional params include:
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.
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__)
Get information about an index into an object.
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:44
newExceptionAfterConnectError( $error)
getDomainID()
Return the currently selected domain ID.
Definition Database.php:400
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:467
close( $fname=__METHOD__)
Close the database connection.
Definition Database.php:478
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:622
getDBname()
Get the current database name; null if there isn't one.
Holds information on Query to be executed.
Definition Query.php:31
Raw SQL value to be used in query builders.
const QUERY_SILENCE_ERRORS
Ignore query errors and return false when they happen.