diff --git a/includes/libs/rdbms/database/DatabaseSqlite.php b/includes/libs/rdbms/database/DatabaseSqlite.php index 3b18819fa74..acb787e47b3 100644 --- a/includes/libs/rdbms/database/DatabaseSqlite.php +++ b/includes/libs/rdbms/database/DatabaseSqlite.php @@ -197,9 +197,13 @@ class DatabaseSqlite extends Database { $this->platform->setPrefix( $tablePrefix ); try { - $flags = self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY; // Enforce LIKE to be case sensitive, just like MySQL - $this->query( 'PRAGMA case_sensitive_like = 1', __METHOD__, $flags ); + $query = new Query( + 'PRAGMA case_sensitive_like = 1', + self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY, + 'PRAGMA' + ); + $this->query( $query, __METHOD__ ); // Set any connection-level custom PRAGMA options $pragmas = array_intersect_key( $this->connectionVariables, self::VALID_PRAGMAS ); $pragmas += $this->getDefaultPragmas(); @@ -209,7 +213,14 @@ class DatabaseSqlite extends Database { ( is_array( $allowed ) && in_array( $value, $allowed, true ) ) || ( is_string( $allowed ) && gettype( $value ) === $allowed ) ) { - $this->query( "PRAGMA $name = $value", __METHOD__, $flags ); + $query = new Query( + "PRAGMA $name = $value", + self::QUERY_CHANGE_TRX | self::QUERY_NO_RETRY, + 'PRAGMA', + null, + "PRAGMA $name = '?'" + ); + $this->query( $query, __METHOD__ ); } } $this->attachDatabasesFromTableAliases(); @@ -369,12 +380,12 @@ class DatabaseSqlite extends Database { public function attachDatabase( $name, $file = false, $fname = __METHOD__ ) { $file = is_string( $file ) ? $file : self::generateFileName( $this->dbDir, $name ); $encFile = $this->addQuotes( $file ); - - return $this->query( + $query = new Query( "ATTACH DATABASE $encFile AS $name", - $fname, - self::QUERY_CHANGE_TRX + self::QUERY_CHANGE_TRX, + 'ATTACH' ); + return $this->query( $query, $fname ); } protected function doSingleStatementQuery( string $sql ): QueryStatus { @@ -463,11 +474,12 @@ class DatabaseSqlite extends Database { } $encTable = $this->addQuotes( $tableRaw ); - $res = $this->query( + $query = new Query( "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable", - __METHOD__, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'SELECT' ); + $res = $this->query( $query, __METHOD__ ); return (bool)$res->numRows(); } @@ -483,8 +495,12 @@ class DatabaseSqlite extends Database { * @return array|false */ public function indexInfo( $table, $index, $fname = __METHOD__ ) { - $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->platform->indexName( $index ) ) . ')'; - $res = $this->query( $sql, $fname, self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE ); + $query = new Query( + 'PRAGMA index_info(' . $this->addQuotes( $this->platform->indexName( $index ) ) . ')', + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'PRAGMA' + ); + $res = $this->query( $query, $fname ); if ( !$res || $res->numRows() == 0 ) { return false; } @@ -533,11 +549,13 @@ class DatabaseSqlite extends Database { // https://sqlite.org/lang_insert.html // Note that any auto-increment columns on conflicting rows will be reassigned // due to combined DELETE+INSERT semantics. This will be reflected in insertId(). - $this->query( + $query = new Query( "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples", - $fname, - self::QUERY_CHANGE_ROWS + self::QUERY_CHANGE_ROWS, + 'REPLACE', + $table ); + $this->query( $query, $fname ); } /** @@ -614,11 +632,12 @@ class DatabaseSqlite extends Database { */ public function fieldInfo( $table, $field ) { $tableRaw = $this->tableName( $table, 'raw' ); - $res = $this->query( + $query = new Query( 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')', - __METHOD__, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'PRAGMA' ); + $res = $this->query( $query, __METHOD__ ); foreach ( $res as $row ) { if ( $row->name == $field ) { return new SQLiteField( $row, $tableRaw ); @@ -630,10 +649,12 @@ class DatabaseSqlite extends Database { protected function doBegin( $fname = '' ) { if ( $this->trxMode != '' ) { - $this->query( "BEGIN {$this->trxMode}", $fname, self::QUERY_CHANGE_TRX ); + $sql = "BEGIN {$this->trxMode}"; } else { - $this->query( 'BEGIN', $fname, self::QUERY_CHANGE_TRX ); + $sql = 'BEGIN'; } + $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'BEGIN' ); + $this->query( $query, $fname ); } /** @@ -734,12 +755,13 @@ class DatabaseSqlite extends Database { public function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) { - $res = $this->query( + $query = new Query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", - $fname, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'SELECT' ); + $res = $this->query( $query, $fname ); $obj = $res->fetchObject(); if ( !$obj ) { throw new RuntimeException( "Couldn't retrieve structure for table $oldName" ); @@ -768,18 +790,22 @@ class DatabaseSqlite extends Database { } } - $res = $this->query( + $query = new Query( $sqlCreateTable, - $fname, - $flags + $flags, + $temporary ? 'CREATE TEMPORARY' : 'CREATE', + // Use a dot to avoid double-prefixing in Database::getTempTableWrites() + '.' . $newName ); + $res = $this->query( $query, $fname ); - // Take over indexes - $indexList = $this->query( + $query = new Query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')', - $fname, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'PRAGMA' ); + // Take over indexes + $indexList = $this->query( $query, $fname ); foreach ( $indexList as $index ) { if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) { continue; @@ -795,11 +821,12 @@ class DatabaseSqlite extends Database { $sqlIndex .= ' ' . $this->platform->addIdentifierQuotes( $indexName ) . ' ON ' . $this->platform->addIdentifierQuotes( $newName ); - $indexInfo = $this->query( + $query = new Query( 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')', - $fname, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'PRAGMA' ); + $indexInfo = $this->query( $query, $fname ); $fields = []; foreach ( $indexInfo as $indexInfoRow ) { $fields[$indexInfoRow->seqno] = $this->addQuotes( $indexInfoRow->name ); @@ -807,11 +834,13 @@ class DatabaseSqlite extends Database { $sqlIndex .= '(' . implode( ',', $fields ) . ')'; - $this->query( + $query = new Query( $sqlIndex, - __METHOD__, - self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT + self::QUERY_CHANGE_SCHEMA | self::QUERY_PSEUDO_PERMANENT, + 'CREATE', + $newName ); + $this->query( $query, __METHOD__ ); } return $res; @@ -826,11 +855,12 @@ class DatabaseSqlite extends Database { * @return array */ public function listTables( $prefix = null, $fname = __METHOD__ ) { - $result = $this->query( + $query = new Query( "SELECT name FROM sqlite_master WHERE type = 'table'", - $fname, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'SELECT' ); + $result = $this->query( $query, $fname ); $endArray = []; @@ -854,18 +884,25 @@ class DatabaseSqlite extends Database { $encSeqNames = []; foreach ( $tables as $table ) { // Use "truncate" optimization; https://www.sqlite.org/lang_delete.html - $sql = "DELETE FROM " . $this->tableName( $table ); - $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA ); + $query = new Query( + "DELETE FROM " . $this->tableName( $table ), + self::QUERY_CHANGE_SCHEMA, + 'DELETE', + $table + ); + $this->query( $query, $fname ); $encSeqNames[] = $this->addQuotes( $this->tableName( $table, 'raw' ) ); } $encMasterTable = $this->platform->addIdentifierQuotes( 'sqlite_sequence' ); - $this->query( + $query = new Query( "DELETE FROM $encMasterTable WHERE name IN(" . implode( ',', $encSeqNames ) . ")", - $fname, - self::QUERY_CHANGE_SCHEMA + self::QUERY_CHANGE_SCHEMA, + 'DELETE', + 'sqlite_sequence' ); + $this->query( $query, $fname ); $this->endAtomic( $fname ); } @@ -920,11 +957,12 @@ class DatabaseSqlite extends Database { protected function getInsertIdColumnForUpsert( $table ) { $tableRaw = $this->tableName( $table, 'raw' ); - $res = $this->query( + $query = new Query( 'PRAGMA table_info(' . $this->addQuotes( $tableRaw ) . ')', - __METHOD__, - self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE + self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE, + 'PRAGMA' ); + $res = $this->query( $query, __METHOD__ ); foreach ( $res as $row ) { if ( $row->pk && strtolower( $row->type ) === 'integer' ) { return $row->name; diff --git a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php index 69c2d0a74b0..bd201b9b435 100644 --- a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php +++ b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema1Test.php @@ -48,6 +48,8 @@ class MediaWikiIntegrationTestCaseSchema1Test extends MediaWikiIntegrationTestCa } public function testTableWasAltered() { + // The sql file does ALTER which doesn't exist in sqlite + $this->markTestSkippedIfDbType( 'sqlite' ); // Make sure pagelinks was altered $this->assertTrue( $this->db->tableExists( 'pagelinks' ) ); $this->assertTrue( $this->db->fieldExists( 'pagelinks', 'pl_frobnitz' ) ); diff --git a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php index 39c948493fd..2c93fcc975f 100644 --- a/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php +++ b/tests/phpunit/tests/MediaWikiIntegrationTestCaseSchema2Test.php @@ -43,6 +43,8 @@ class MediaWikiIntegrationTestCaseSchema2Test extends MediaWikiIntegrationTestCa } public function testAlteredTableWasRestored() { + // The sql file does ALTER which doesn't exist in sqlite + $this->markTestSkippedIfDbType( 'sqlite' ); // Make sure pagelinks altered by MediaWikiIntegrationTestCaseSchema1Test // was restored to the original schema before executing MediaWikiIntegrationTestCaseSchema2Test. $this->assertTrue( $this->db->tableExists( 'pagelinks' ) );