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
84 public function __construct( array $params ) {
85 if ( isset( $params['dbFilePath'] ) ) {
86 $this->dbPath = $params['dbFilePath'];
87 if ( !isset( $params['dbname'] ) || $params['dbname'] === '' ) {
88 $params['dbname'] = self::generateDatabaseName( $this->dbPath );
89 }
90 } elseif ( isset( $params['dbDirectory'] ) ) {
91 $this->dbDir = $params['dbDirectory'];
92 }
93
94 parent::__construct( $params );
95
96 $this->trxMode = strtoupper( $params['trxMode'] ?? '' );
97
98 $this->lockMgr = $this->makeLockManager();
99 $this->platform = new SqlitePlatform(
100 $this,
101 $this->logger,
102 $this->currentDomain,
103 $this->errorLogger
104 );
105 $this->replicationReporter = new ReplicationReporter(
106 $params['topologyRole'],
107 $this->logger,
108 $params['srvCache']
109 );
110 }
111
112 public static function getAttributes() {
113 return [
114 self::ATTR_DB_IS_FILE => true,
115 self::ATTR_DB_LEVEL_LOCKING => true
116 ];
117 }
118
128 public static function newStandaloneInstance( $filename, array $p = [] ) {
129 $p['dbFilePath'] = $filename;
130 $p['schema'] = null;
131 $p['tablePrefix'] = '';
133 $db = ( new DatabaseFactory() )->create( 'sqlite', $p );
134 '@phan-var DatabaseSqlite $db';
135
136 return $db;
137 }
138
142 public function getType() {
143 return 'sqlite';
144 }
145
146 protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
147 $this->close( __METHOD__ );
148
149 // Note that for SQLite, $server, $user, and $pass are ignored
150
151 if ( $schema !== null ) {
152 throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
153 }
154
155 if ( $this->dbPath !== null ) {
157 } elseif ( $this->dbDir !== null ) {
158 $path = self::generateFileName( $this->dbDir, $db );
159 } else {
160 throw $this->newExceptionAfterConnectError( "DB path or directory required" );
161 }
162
163 // Check if the database file already exists but is non-readable
164 if ( !self::isProcessMemoryPath( $path ) && is_file( $path ) && !is_readable( $path ) ) {
165 throw $this->newExceptionAfterConnectError( 'SQLite database file is not readable' );
166 } elseif ( !in_array( $this->trxMode, self::VALID_TRX_MODES, true ) ) {
167 throw $this->newExceptionAfterConnectError( "Got mode '{$this->trxMode}' for BEGIN" );
168 }
169
170 $attributes = [
171 PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
172 // Starting with PHP 8.1, The SQLite PDO returns proper types instead
173 // of strings or null for everything. We cast every non-null value to
174 // string to restore the old behavior.
175 PDO::ATTR_STRINGIFY_FETCHES => true
176 ];
177 if ( $this->getFlag( self::DBO_PERSISTENT ) ) {
178 // Persistent connections can avoid some schema index reading overhead.
179 // On the other hand, they can cause horrible contention with DBO_TRX.
180 if ( $this->getFlag( self::DBO_TRX ) || $this->getFlag( self::DBO_DEFAULT ) ) {
181 $this->logger->warning(
182 __METHOD__ . ": ignoring DBO_PERSISTENT due to DBO_TRX or DBO_DEFAULT",
183 $this->getLogContext()
184 );
185 } else {
186 $attributes[PDO::ATTR_PERSISTENT] = true;
187 }
188 }
189
190 try {
191 // Open the database file, creating it if it does not yet exist
192 $this->conn = new PDO( "sqlite:$path", null, null, $attributes );
193 } catch ( PDOException $e ) {
194 throw $this->newExceptionAfterConnectError( $e->getMessage() );
195 }
196
197 $this->currentDomain = new DatabaseDomain( $db, null, $tablePrefix );
198 $this->platform->setCurrentDomain( $this->currentDomain );
199
200 try {
201 // Enforce LIKE to be case sensitive, just like MySQL
202 $query = new Query(
203 'PRAGMA case_sensitive_like = 1',
204 self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY,
205 'PRAGMA'
206 );
207 $this->query( $query, __METHOD__ );
208 // Set any connection-level custom PRAGMA options
209 $pragmas = array_intersect_key( $this->connectionVariables, self::VALID_PRAGMAS );
210 $pragmas += $this->getDefaultPragmas();
211 foreach ( $pragmas as $name => $value ) {
212 $allowed = self::VALID_PRAGMAS[$name];
213 if (
214 ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) ||
215 ( is_string( $allowed ) && gettype( $value ) === $allowed )
216 ) {
217 $query = new Query(
218 "PRAGMA $name = $value",
219 self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY,
220 'PRAGMA',
221 null,
222 "PRAGMA $name = '?'"
223 );
224 $this->query( $query, __METHOD__ );
225 }
226 }
227 $this->attachDatabasesFromTableAliases();
228 } catch ( RuntimeException $e ) {
229 throw $this->newExceptionAfterConnectError( $e->getMessage() );
230 }
231 }
232
236 private function getDefaultPragmas() {
237 $variables = [];
238
239 if ( !$this->cliMode ) {
240 $variables['temp_store'] = 'MEMORY';
241 }
242
243 return $variables;
244 }
245
251 public function getDbFilePath() {
252 return $this->dbPath ?? self::generateFileName( $this->dbDir, $this->getDBname() );
253 }
254
258 public function getLockFileDirectory() {
259 if ( $this->dbPath !== null && !self::isProcessMemoryPath( $this->dbPath ) ) {
260 return dirname( $this->dbPath ) . '/locks';
261 } elseif ( $this->dbDir !== null && !self::isProcessMemoryPath( $this->dbDir ) ) {
262 return $this->dbDir . '/locks';
263 }
264
265 return null;
266 }
267
273 private function makeLockManager(): LockManager {
274 $lockDirectory = $this->getLockFileDirectory();
275 if ( $lockDirectory !== null ) {
276 return new FSLockManager( [
277 'domain' => $this->getDomainID(),
278 'lockDirectory' => $lockDirectory,
279 ] );
280 } else {
281 return new NullLockManager( [ 'domain' => $this->getDomainID() ] );
282 }
283 }
284
289 protected function closeConnection() {
290 $this->conn = null;
291 // Release all locks, via FSLockManager::__destruct, as the base class expects
292 $this->lockMgr = null;
293
294 return true;
295 }
296
304 public static function generateFileName( $dir, $dbName ) {
305 if ( $dir == '' ) {
306 throw new DBUnexpectedError( null, __CLASS__ . ": no DB directory specified" );
307 } elseif ( self::isProcessMemoryPath( $dir ) ) {
308 throw new DBUnexpectedError(
309 null,
310 __CLASS__ . ": cannot use process memory directory '$dir'"
311 );
312 } elseif ( !strlen( $dbName ) ) {
313 throw new DBUnexpectedError( null, __CLASS__ . ": no DB name specified" );
314 }
315
316 return "$dir/$dbName.sqlite";
317 }
318
323 private static function generateDatabaseName( $path ) {
324 if ( preg_match( '/^(:memory:$|file::memory:)/', $path ) ) {
325 // E.g. "file::memory:?cache=shared" => ":memory":
326 return ':memory:';
327 } elseif ( preg_match( '/^file::([^?]+)\?mode=memory(&|$)/', $path, $m ) ) {
328 // E.g. "file:memdb1?mode=memory" => ":memdb1:"
329 return ":{$m[1]}:";
330 } else {
331 // E.g. "/home/.../some_db.sqlite3" => "some_db"
332 return preg_replace( '/\.sqlite\d?$/', '', basename( $path ) );
333 }
334 }
335
340 private static function isProcessMemoryPath( $path ) {
341 return preg_match( '/^(:memory:$|file:(:memory:|[^?]+\?mode=memory(&|$)))/', $path );
342 }
343
348 public static function getFulltextSearchModule() {
349 static $cachedResult = null;
350 if ( $cachedResult !== null ) {
351 return $cachedResult;
352 }
353 $cachedResult = false;
354 $table = 'dummy_search_test';
355
356 $db = self::newStandaloneInstance( ':memory:' );
357 if ( $db->query(
358 "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)",
359 __METHOD__,
361 ) ) {
362 $cachedResult = 'FTS3';
363 }
364 $db->close( __METHOD__ );
365
366 return $cachedResult;
367 }
368
381 public function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
382 $file = is_string( $file ) ? $file : self::generateFileName( $this->dbDir, $name );
383 $encFile = $this->addQuotes( $file );
384 $query = new Query(
385 "ATTACH DATABASE $encFile AS $name",
386 self::QUERY_CHANGE_TRX,
387 'ATTACH'
388 );
389 return $this->query( $query, $fname );
390 }
391
392 protected function doSingleStatementQuery( string $sql ): QueryStatus {
393 $res = $this->getBindingHandle()->query( $sql );
394 // Note that rowCount() returns 0 for SELECT for SQLite
395 return new QueryStatus(
396 $res instanceof PDOStatement ? new SqliteResultWrapper( $res ) : $res,
397 $res ? $res->rowCount() : 0,
398 $this->lastError(),
399 $this->lastErrno()
400 );
401 }
402
403 protected function doSelectDomain( DatabaseDomain $domain ) {
404 if ( $domain->getSchema() !== null ) {
405 throw new DBExpectedError(
406 $this,
407 __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
408 );
409 }
410
411 $database = $domain->getDatabase();
412 // A null database means "don't care" so leave it as is and update the table prefix
413 if ( $database === null ) {
414 $this->currentDomain = new DatabaseDomain(
415 $this->currentDomain->getDatabase(),
416 null,
417 $domain->getTablePrefix()
418 );
419 $this->platform->setCurrentDomain( $this->currentDomain );
420
421 return true;
422 }
423
424 if ( $database !== $this->getDBname() ) {
425 throw new DBExpectedError(
426 $this,
427 __CLASS__ . ": cannot change database (got '$database')"
428 );
429 }
430
431 // Update that domain fields on success (no exception thrown)
432 $this->currentDomain = $domain;
433 $this->platform->setCurrentDomain( $domain );
434
435 return true;
436 }
437
438 protected function lastInsertId() {
439 // PDO::lastInsertId yields a string :(
440 return (int)$this->getBindingHandle()->lastInsertId();
441 }
442
446 public function lastError() {
447 if ( is_object( $this->conn ) ) {
448 $e = $this->conn->errorInfo();
449
450 return $e[2] ?? $this->lastConnectError;
451 }
452
453 return 'No database connection';
454 }
455
459 public function lastErrno() {
460 if ( is_object( $this->conn ) ) {
461 $info = $this->conn->errorInfo();
462
463 if ( isset( $info[1] ) ) {
464 return $info[1];
465 }
466 }
467
468 return 0;
469 }
470
471 public function tableExists( $table, $fname = __METHOD__ ) {
472 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
473 if ( isset( $this->sessionTempTables[$db][$pt] ) ) {
474 return true; // already known to exist
475 }
476
477 $encTable = $this->addQuotes( $pt );
478 $query = new Query(
479 "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable",
480 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
481 'SELECT'
482 );
483 $res = $this->query( $query, __METHOD__ );
484
485 return (bool)$res->numRows();
486 }
487
488 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
489 $indexName = $this->platform->indexName( $index );
490 $components = $this->platform->qualifiedTableComponents( $table );
491 $tableRaw = end( $components );
492 $query = new Query(
493 'PRAGMA index_list(' . $this->addQuotes( $tableRaw ) . ')',
494 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
495 'PRAGMA'
496 );
497 $res = $this->query( $query, $fname );
498
499 foreach ( $res as $row ) {
500 if ( $row->name === $indexName ) {
501 return [ 'unique' => (bool)$row->unique ];
502 }
503 }
504
505 return false;
506 }
507
508 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
509 $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
510 if ( !$rows ) {
511 return;
512 }
513 $encTable = $this->tableName( $table );
514 [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
515 // https://sqlite.org/lang_insert.html
516 // Note that any auto-increment columns on conflicting rows will be reassigned
517 // due to combined DELETE+INSERT semantics. This will be reflected in insertId().
518 $query = new Query(
519 "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples",
520 self::QUERY_CHANGE_ROWS,
521 'REPLACE',
522 $table
523 );
524 $this->query( $query, $fname );
525 }
526
527 protected function isConnectionError( $errno ) {
528 return $errno == 17; // SQLITE_SCHEMA;
529 }
530
531 protected function isKnownStatementRollbackError( $errno ) {
532 // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
533 // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
534 // https://sqlite.org/lang_createtable.html#uniqueconst
535 // https://sqlite.org/lang_conflict.html
536 return false;
537 }
538
539 public function serverIsReadOnly() {
540 $this->assertHasConnectionHandle();
541
542 $path = $this->getDbFilePath();
543
544 return ( !self::isProcessMemoryPath( $path ) && !is_writable( $path ) );
545 }
546
550 public function getSoftwareLink() {
551 return "[{{int:version-db-sqlite-url}} SQLite]";
552 }
553
557 public function getServerVersion() {
558 if ( $this->version === null ) {
559 $this->version = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
560 }
561
562 return $this->version;
563 }
564
573 public function fieldInfo( $table, $field ) {
574 $components = $this->platform->qualifiedTableComponents( $table );
575 $tableRaw = end( $components );
576 $query = new Query(
577 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
578 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
579 'PRAGMA'
580 );
581 $res = $this->query( $query, __METHOD__ );
582 foreach ( $res as $row ) {
583 if ( $row->name == $field ) {
584 return new SQLiteField( $row, $tableRaw );
585 }
586 }
587
588 return false;
589 }
590
591 protected function doBegin( $fname = '' ) {
592 if ( $this->trxMode != '' ) {
593 $sql = "BEGIN {$this->trxMode}";
594 } else {
595 $sql = 'BEGIN';
596 }
597 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'BEGIN' );
598 $this->query( $query, $fname );
599 }
600
605 public function strencode( $s ) {
606 return substr( $this->addQuotes( $s ), 1, -1 );
607 }
608
613 public function encodeBlob( $b ) {
614 return new Blob( $b );
615 }
616
621 public function decodeBlob( $b ) {
622 if ( $b instanceof Blob ) {
623 $b = $b->fetch();
624 }
625 if ( $b === null ) {
626 // An empty blob is decoded as null in PHP before PHP 8.1.
627 // It was probably fixed as a side-effect of caa710037e663fd78f67533b29611183090068b2
628 $b = '';
629 }
630
631 return $b;
632 }
633
634 public function addQuotes( $s ) {
635 if ( $s instanceof RawSQLValue ) {
636 return $s->toSql();
637 }
638 if ( $s instanceof Blob ) {
639 return "x'" . bin2hex( $s->fetch() ) . "'";
640 } elseif ( is_bool( $s ) ) {
641 return (string)(int)$s;
642 } elseif ( is_int( $s ) ) {
643 return (string)$s;
644 } elseif ( strpos( (string)$s, "\0" ) !== false ) {
645 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
646 // This is a known limitation of SQLite's mprintf function which PDO
647 // should work around, but doesn't. I have reported this to php.net as bug #63419:
648 // https://bugs.php.net/bug.php?id=63419
649 // There was already a similar report for SQLite3::escapeString, bug #62361:
650 // https://bugs.php.net/bug.php?id=62361
651 // There is an additional bug regarding sorting this data after insert
652 // on older versions of sqlite shipped with ubuntu 12.04
653 // https://phabricator.wikimedia.org/T74367
654 $this->logger->debug(
655 __FUNCTION__ .
656 ': Quoting value containing null byte. ' .
657 'For consistency all binary data should have been ' .
658 'first processed with self::encodeBlob()'
659 );
660 return "x'" . bin2hex( (string)$s ) . "'";
661 } else {
662 return $this->getBindingHandle()->quote( (string)$s );
663 }
664 }
665
666 public function doLockIsFree( string $lockName, string $method ) {
667 // Only locks by this thread will be checked
668 return true;
669 }
670
671 public function doLock( string $lockName, string $method, int $timeout ) {
672 $status = $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout );
673 if (
674 $this->lockMgr instanceof FSLockManager &&
675 $status->hasMessage( 'lockmanager-fail-openlock' )
676 ) {
677 throw new DBError( $this, "Cannot create directory \"{$this->getLockFileDirectory()}\"" );
678 }
679
680 return $status->isOK() ? microtime( true ) : null;
681 }
682
683 public function doUnlock( string $lockName, string $method ) {
684 return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isGood();
685 }
686
695 public function duplicateTableStructure(
696 $oldName, $newName, $temporary = false, $fname = __METHOD__
697 ) {
698 $query = new Query(
699 "SELECT sql FROM sqlite_master WHERE tbl_name=" .
700 $this->addQuotes( $oldName ) . " AND type='table'",
701 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
702 'SELECT'
703 );
704 $res = $this->query( $query, $fname );
705 $obj = $res->fetchObject();
706 if ( !$obj ) {
707 throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
708 }
709 $sqlCreateTable = $obj->sql;
710 $sqlCreateTable = preg_replace(
711 '/(?<=\W)"?' .
712 preg_quote( trim( $this->platform->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
713 '"?(?=\W)/',
714 $this->platform->addIdentifierQuotes( $newName ),
715 $sqlCreateTable,
716 1
717 );
718 $flags = self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT;
719 if ( $temporary ) {
720 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sqlCreateTable ) ) {
721 $this->logger->debug(
722 "Table $oldName is virtual, can't create a temporary duplicate." );
723 } else {
724 $sqlCreateTable = str_replace(
725 'CREATE TABLE',
726 'CREATE TEMPORARY TABLE',
727 $sqlCreateTable
728 );
729 }
730 }
731
732 $query = new Query(
733 $sqlCreateTable,
734 $flags,
735 $temporary ? 'CREATE TEMPORARY' : 'CREATE',
736 // Use a dot to avoid double-prefixing in Database::getTempTableWrites()
737 '.' . $newName
738 );
739 $res = $this->query( $query, $fname );
740
741 $query = new Query(
742 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')',
743 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
744 'PRAGMA'
745 );
746 // Take over indexes
747 $indexList = $this->query( $query, $fname );
748 foreach ( $indexList as $index ) {
749 if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
750 continue;
751 }
752
753 if ( $index->unique ) {
754 $sqlIndex = 'CREATE UNIQUE INDEX';
755 } else {
756 $sqlIndex = 'CREATE INDEX';
757 }
758 // Try to come up with a new index name, given indexes have database scope in SQLite
759 $indexName = $newName . '_' . $index->name;
760 $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) .
761 ' ON ' . $this->platform->addIdentifierQuotes( $newName );
762
763 $query = new Query(
764 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')',
765 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
766 'PRAGMA'
767 );
768 $indexInfo = $this->query( $query, $fname );
769 $fields = [];
770 foreach ( $indexInfo as $indexInfoRow ) {
771 $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name );
772 }
773
774 $sqlIndex .= '(' . implode( ',', $fields ) . ')';
775
776 $query = new Query(
777 $sqlIndex,
778 self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT,
779 'CREATE',
780 $newName
781 );
782 $this->query( $query, __METHOD__ );
783 }
784
785 return $res;
786 }
787
796 public function listTables( $prefix = null, $fname = __METHOD__ ) {
797 $query = new Query(
798 "SELECT name FROM sqlite_master WHERE type = 'table'",
799 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
800 'SELECT'
801 );
802 $result = $this->query( $query, $fname );
803
804 $endArray = [];
805
806 foreach ( $result as $table ) {
807 $vars = get_object_vars( $table );
808 $table = array_pop( $vars );
809
810 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
811 if ( strpos( $table, 'sqlite_' ) !== 0 ) {
812 $endArray[] = $table;
813 }
814 }
815 }
816
817 return $endArray;
818 }
819
820 public function truncateTable( $table, $fname = __METHOD__ ) {
821 $this->startAtomic( $fname );
822 // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html
823 $query = new Query(
824 "DELETE FROM " . $this->tableName( $table ),
825 self::QUERY_CHANGE_SCHEMA,
826 'DELETE',
827 $table
828 );
829 $this->query( $query, $fname );
830
831 $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' );
832 $encSequenceName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
833 $query = new Query(
834 "DELETE FROM $encMasterTable WHERE name = $encSequenceName",
835 self::QUERY_CHANGE_SCHEMA,
836 'DELETE',
837 'sqlite_sequence'
838 );
839 $this->query( $query, $fname );
840
841 $this->endAtomic( $fname );
842 }
843
844 public function setTableAliases( array $aliases ) {
845 parent::setTableAliases( $aliases );
846 if ( $this->isOpen() ) {
847 $this->attachDatabasesFromTableAliases();
848 }
849 }
850
854 private function attachDatabasesFromTableAliases() {
855 foreach ( $this->platform->getTableAliases() as $params ) {
856 if (
857 $params['dbname'] !== $this->getDBname() &&
858 !isset( $this->sessionAttachedDbs[$params['dbname']] )
859 ) {
860 $this->attachDatabase( $params['dbname'], false, __METHOD__ );
861 $this->sessionAttachedDbs[$params['dbname']] = true;
862 }
863 }
864 }
865
866 public function databasesAreIndependent() {
867 return true;
868 }
869
870 protected function doHandleSessionLossPreconnect() {
871 $this->sessionAttachedDbs = [];
872 // Release all locks, via FSLockManager::__destruct, as the base class expects;
873 $this->lockMgr = null;
874 // Create a new lock manager instance
875 $this->lockMgr = $this->makeLockManager();
876 }
877
878 protected function doFlushSession( $fname ) {
879 // Release all locks, via FSLockManager::__destruct, as the base class expects
880 $this->lockMgr = null;
881 // Create a new lock manager instance
882 $this->lockMgr = $this->makeLockManager();
883 }
884
888 protected function getBindingHandle() {
889 return parent::getBindingHandle();
890 }
891
892 protected function getInsertIdColumnForUpsert( $table ) {
893 $components = $this->platform->qualifiedTableComponents( $table );
894 $tableRaw = end( $components );
895 $query = new Query(
896 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')',
897 self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE,
898 'PRAGMA'
899 );
900 $res = $this->query( $query, __METHOD__ );
901 foreach ( $res as $row ) {
902 if ( $row->pk && strtolower( $row->type ) === 'integer' ) {
903 return $row->name;
904 }
905 }
906
907 return null;
908 }
909}
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__)
A single concrete connection to a relational database.
Definition Database.php:51
newExceptionAfterConnectError( $error)
getDomainID()
Return the currently selected domain ID.
Definition Database.php:406
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:473
close( $fname=__METHOD__)
Close the database connection.
Definition Database.php:484
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:626
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.