From 6e6ba8eb545e5432497a256ecd694505a92ff2e8 Mon Sep 17 00:00:00 2001 From: Liu Hongyang Date: Thu, 14 Aug 2025 11:06:09 +0800 Subject: [PATCH 1/2] fix binlog replay failed with single quotes Signed-off-by: Liu Hongyang --- patch/0006-Support-Binlog.patch | 34 ++++-- unittest/BUILD.gn | 4 + unittest/sqlite_binlog_test.cpp | 176 ++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 10 deletions(-) create mode 100644 unittest/sqlite_binlog_test.cpp diff --git a/patch/0006-Support-Binlog.patch b/patch/0006-Support-Binlog.patch index 99a55fa..faaee02 100644 --- a/patch/0006-Support-Binlog.patch +++ b/patch/0006-Support-Binlog.patch @@ -1,14 +1,14 @@ -From d169331fa1819949f671071677cf4ac45c05a21a Mon Sep 17 00:00:00 2001 -From: MartinChoo <214582617@qq.com> -Date: Wed, 23 Jul 2025 17:42:36 +0800 +From d9bf27e15f4d3ebe73de27fa608c06c5049d34e3 Mon Sep 17 00:00:00 2001 +From: Liu Hongyang +Date: Fri, 15 Aug 2025 14:28:12 +0800 Subject: [PATCH 06/12] Support-Binlog --- - src/sqlite3.c | 1508 ++++++++++++++++++++++++++++++++++++++++++++++++- - 1 file changed, 1499 insertions(+), 9 deletions(-) + src/sqlite3.c | 1522 ++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 1513 insertions(+), 9 deletions(-) diff --git a/src/sqlite3.c b/src/sqlite3.c -index e7e8b3c..3f1195a 100644 +index b433dfb..4105866 100644 --- a/src/sqlite3.c +++ b/src/sqlite3.c @@ -2938,7 +2938,9 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); @@ -680,7 +680,7 @@ index e7e8b3c..3f1195a 100644 *ppDb = db; #ifdef SQLITE_ENABLE_SQLLOG if( sqlite3GlobalConfig.xSqllog ){ -@@ -256913,6 +257323,1074 @@ static void walLogCheckpointInfo(Wal *pWal, sqlite3 *db, sqlite3_int64 startTime +@@ -256913,6 +257323,1088 @@ static void walLogCheckpointInfo(Wal *pWal, sqlite3 *db, sqlite3_int64 startTime } #endif @@ -1343,6 +1343,20 @@ index e7e8b3c..3f1195a 100644 + return zHex; +} + ++SQLITE_PRIVATE char *sqlite3BinlogGetEscapedText(sqlite3 *db, const char *originalStr) { ++ sqlite3_str *pStr = sqlite3_str_new(db); ++ size_t nOriginal = strlen(originalStr); ++ sqlite3_str_appendchar(pStr, 1, '\''); ++ for (size_t i=0; i= 0 ); @@ -1369,7 +1383,7 @@ index e7e8b3c..3f1195a 100644 + res = sqlite3_mprintf("'%s'", temp); + } + } else if (type == SQLITE_TEXT) { -+ res = sqlite3_mprintf("'%s'", temp); ++ res = sqlite3BinlogGetEscapedText(db, (const char *)temp); + } else if (type == SQLITE_NULL) { + res = sqlite3_mprintf("NULL"); + } else { @@ -1755,7 +1769,7 @@ index e7e8b3c..3f1195a 100644 // export the symbols #ifdef SQLITE_EXPORT_SYMBOLS #ifndef SQLITE_CKSUMVFS_STATIC -@@ -256942,6 +258420,9 @@ struct sqlite3_api_routines_extra { +@@ -256942,6 +258434,9 @@ struct sqlite3_api_routines_extra { int (*key_v2)(sqlite3*,const char*,const void*,int); int (*rekey)(sqlite3*,const void*,int); int (*rekey_v2)(sqlite3*,const char*,const void*,int); @@ -1765,7 +1779,7 @@ index e7e8b3c..3f1195a 100644 }; typedef struct sqlite3_api_routines_extra sqlite3_api_routines_extra; -@@ -256952,13 +258433,22 @@ static const sqlite3_api_routines_extra sqlite3ExtraApis = { +@@ -256952,13 +258447,22 @@ static const sqlite3_api_routines_extra sqlite3ExtraApis = { sqlite3_key, sqlite3_key_v2, sqlite3_rekey, diff --git a/unittest/BUILD.gn b/unittest/BUILD.gn index f5579b9..0a7b6a6 100644 --- a/unittest/BUILD.gn +++ b/unittest/BUILD.gn @@ -47,6 +47,10 @@ ohos_unittest("libsqlittest") { "SQLITE_SUPPORT_PAGE_CHECK_TEST", "SQLITE_SUPPORT_PAGE_COMPRESS_TEST" ] + + sources += [ + "./sqlite_binlog_test.cpp", + ] } configs = [ ":module_private_config" ] diff --git a/unittest/sqlite_binlog_test.cpp b/unittest/sqlite_binlog_test.cpp new file mode 100644 index 0000000..2f691ac --- /dev/null +++ b/unittest/sqlite_binlog_test.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include +#include +#include + +#include "common.h" +#include "sqlite3sym.h" + +using namespace testing::ext; +using namespace UnitTest::SQLiteTest; + +#define TEST_DIR "./sqlitebinlogtest" +#define TEST_DB (TEST_DIR "/test.db") +#define TEST_BACKUP_DB (TEST_DIR "/test_bak.db") +#define TEST_DATA_COUNT 1000 +#define TEST_DATA_REAL 16.1 + +static void UtSqliteLogPrint(const void *data, int err, const char *msg) +{ + std::cout << "SqliteBinlogTest SQLite xLog err:" << err << ", msg:" << msg << std::endl; +} + +static void UtPresetDb(const char *dbFile) +{ + /** + * @tc.steps: step1. Prepare compressed db + * @tc.expected: step1. Execute successfully + */ + sqlite3 *db = NULL; + std::string dbFileUri = "file:"; + dbFileUri += dbFile; + EXPECT_EQ(sqlite3_open(dbFileUri.c_str(), &db), SQLITE_OK); + /** + * @tc.steps: step2. Create table and fill it, make db size big enough + * @tc.expected: step2. Execute successfully + */ + static const char *UT_DDL_CREATE_TABLE = "CREATE TABLE salary(" + "entryId INTEGER PRIMARY KEY," + "entryName Text," + "salary REAL," + "class INTEGER);"; + EXPECT_EQ(sqlite3_exec(db, UT_DDL_CREATE_TABLE, NULL, NULL, NULL), SQLITE_OK); + sqlite3_close(db); +} + +static void UtEnableBinlog(sqlite3 *db) +{ + Sqlite3BinlogConfig cfg = { + .mode = Sqlite3BinlogMode::ROW, + .fullCallbackThreshold = 2, + .maxFileSize = 1024 * 1024 * 4, + .xErrorCallback = nullptr, + .xLogFullCallback = nullptr, + .callbackCtx = nullptr, + }; + EXPECT_EQ(sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_BINLOG, &cfg), SQLITE_OK); +} + +static int UtQueryResult(void *data, int argc, char **argv, char **azColName) +{ + int count = *static_cast(data); + *static_cast(data) = count + 1; + // 2 means 2 fields, entryId, entryName + EXPECT_EQ(argc, 2); + return SQLITE_OK; +} + +static int UtGetRecordCount(sqlite3 *db) +{ + int count = 0; + EXPECT_EQ(sqlite3_exec(db, "SELECT entryId, entryName FROM salary;", UtQueryResult, &count, nullptr), SQLITE_OK); + return count; +} + +class SqliteBinlogTest : public testing::Test { +public: + static void SetUpTestCase(void); + static void TearDownTestCase(void); + void SetUp(); + void TearDown(); +}; + +void SqliteBinlogTest::SetUpTestCase(void) +{ + Common::RemoveDir(TEST_DIR); + Common::MakeDir(TEST_DIR); +} + +void SqliteBinlogTest::TearDownTestCase(void) +{ +} + +void SqliteBinlogTest::SetUp(void) +{ + std::string command = "rm -rf "; + command += TEST_DIR "/*"; + system(command.c_str()); + sqlite3_config(SQLITE_CONFIG_LOG, &UtSqliteLogPrint, NULL); + UtPresetDb(TEST_DB); + UtPresetDb(TEST_BACKUP_DB); +} + +void SqliteBinlogTest::TearDown(void) +{ + sqlite3_config(SQLITE_CONFIG_LOG, NULL, NULL); +} + +/** + * @tc.name: BinlogReplayTest001 + * @tc.desc: Test replay sql with test value that has single quote + * @tc.type: FUNC + */ +HWTEST_F(SqliteBinlogTest, BinlogReplayTest001, TestSize.Level0) +{ + /** + * @tc.steps: step1. open db and set binlog + * @tc.expected: step1. ok + */ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open_v2(TEST_DB, &db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr), SQLITE_OK); + ASSERT_NE(db, nullptr); + UtEnableBinlog(db); + /** + * @tc.steps: step2. Insert records with single quotes + * @tc.expected: step2. Execute successfully + */ + static const char *UT_SQL_INSERT_DATA = + "INSERT INTO salary(entryId, entryName, salary, class) VALUES(?,?,?,?);"; + sqlite3_stmt *insertStmt = NULL; + EXPECT_EQ(sqlite3_prepare_v2(db, UT_SQL_INSERT_DATA, -1, &insertStmt, NULL), SQLITE_OK); + for (int i = 0; i < TEST_DATA_COUNT; i++) { + // bind parameters, 1, 2, 3, 4 are sequence number of fields + sqlite3_bind_int(insertStmt, 1, i + 1); + sqlite3_bind_text(insertStmt, 2, "'salary-entry-name'", -1, SQLITE_STATIC); + sqlite3_bind_double(insertStmt, 3, TEST_DATA_REAL + i); + sqlite3_bind_int(insertStmt, 4, i + 1); + EXPECT_EQ(sqlite3_step(insertStmt), SQLITE_DONE); + sqlite3_reset(insertStmt); + } + sqlite3_finalize(insertStmt); + /** + * @tc.steps: step3. binlog replay to write into backup db + * @tc.expected: step3. Return SQLITE_OK + */ + sqlite3 *backupDb = NULL; + EXPECT_EQ(sqlite3_open_v2(TEST_BACKUP_DB, &backupDb, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr), SQLITE_OK); + ASSERT_NE(backupDb, nullptr); + EXPECT_EQ(sqlite3_replay_binlog(db, backupDb), SQLITE_OK); + /** + * @tc.steps: step4. check db count + * @tc.expected: step4. both db have TEST_DATA_COUNT + */ + EXPECT_EQ(UtGetRecordCount(db), TEST_DATA_COUNT); + EXPECT_EQ(UtGetRecordCount(backupDb), TEST_DATA_COUNT); + sqlite3_close_v2(db); + sqlite3_close_v2(backupDb); +} -- Gitee From 39f84c7014e24997cb1e1a0ce2078a8768286f3d Mon Sep 17 00:00:00 2001 From: MartinChoo <214582617@qq.com> Date: Thu, 14 Aug 2025 20:08:14 +0800 Subject: [PATCH 2/2] Check file before open compress db Signed-off-by: MartinChoo <214582617@qq.com> --- BUILD.gn | 6 +- bundle.json | 3 - patch/0012-Bugfix-on-current-version.patch | 72 ++++-- unittest/sqlite_cksum_test.cpp | 2 +- unittest/sqlite_compress_test.cpp | 257 +++++++++++++++++++-- 5 files changed, 298 insertions(+), 42 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index c449c07..b6acc55 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -32,8 +32,10 @@ config("sqlite3_private_config") { group("libsqlite") { public_deps = [ ":sqlite", - ":sqliteicu", - ":sqlitecompressvfs", + ":sqliteicu" + ] + deps = [ + ":sqlitecompressvfs" ] } diff --git a/bundle.json b/bundle.json index 0b9360a..0948e65 100644 --- a/bundle.json +++ b/bundle.json @@ -27,9 +27,6 @@ "c_utils", "icu", "openssl" - ], - "third_party": [ - "openssl" ] }, "build": { diff --git a/patch/0012-Bugfix-on-current-version.patch b/patch/0012-Bugfix-on-current-version.patch index fc90a19..3519066 100644 --- a/patch/0012-Bugfix-on-current-version.patch +++ b/patch/0012-Bugfix-on-current-version.patch @@ -1,13 +1,13 @@ -From 0d849cdbe95e48a2a5e0a9414dca652fe73b0ba5 Mon Sep 17 00:00:00 2001 +From c5761b4ffbe2376f9f7ee1c67f139ecd214dd364 Mon Sep 17 00:00:00 2001 From: MartinChoo <214582617@qq.com> -Date: Mon, 11 Aug 2025 14:21:08 +0800 +Date: Thu, 14 Aug 2025 19:51:56 +0800 Subject: [PATCH] Bugfix on current version --- ext/misc/cksumvfs.c | 11 +- - src/compressvfs.c | 251 ++++++++++++++++++++++------------ + src/compressvfs.c | 270 ++++++++++++++++++++++++------------ src/sqlite3.c | 327 ++++++++++++++++++++++++++++++++++++-------- - 3 files changed, 439 insertions(+), 150 deletions(-) + 3 files changed, 458 insertions(+), 150 deletions(-) diff --git a/ext/misc/cksumvfs.c b/ext/misc/cksumvfs.c index 27b1028..e89edcd 100644 @@ -53,7 +53,7 @@ index 27b1028..e89edcd 100644 ((u8*)zBuf)[iAmt-CKSUMVFS_RESERVED_SIZE]=CKSUMVFS_MAGIC_NUM; ((u8*)zBuf)[iAmt-CKSUMVFS_RESERVED_SIZE+1]=p->verifyCksm ? CKSUMVFS_CALC_CHECKSUM : CKSUMVFS_WITHOUT_CHECKSUM; diff --git a/src/compressvfs.c b/src/compressvfs.c -index f2fe169..83e5249 100644 +index f2fe169..5f5ec20 100644 --- a/src/compressvfs.c +++ b/src/compressvfs.c @@ -151,6 +151,7 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ @@ -377,7 +377,47 @@ index f2fe169..83e5249 100644 } return rc; } -@@ -902,87 +940,128 @@ static int compressOpen( +@@ -888,6 +926,39 @@ static int compressUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage + return SQLITE_OK; + } + ++static int compressOpenLockFile(sqlite3_vfs *pVfs, const char *zName, int flags, sqlite3_file **pFile){ ++ const char *walPath = sqlite3_filename_wal(zName); ++ const char *lockPath = walPath + strlen(walPath) + 1; // For compress vfs, lock path place at thie position ++ int isLockExist = 0; ++ int rc = ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), lockPath, SQLITE_ACCESS_READWRITE, &isLockExist); ++ if( rc!=SQLITE_OK ){ ++ sqlite3_log(rc, "Access lock file failed, path:%s", lockPath); ++ return rc; ++ } ++ if( isLockExist==0 && ((flags&SQLITE_OPEN_CREATE)==0) ){ ++ sqlite3_log(SQLITE_WARNING_NOTCOMPRESSDB, "Check lock wrong, compress db should have lock file"); ++ return SQLITE_WARNING_NOTCOMPRESSDB; ++ } ++ if( pVfs->szOsFile<=(int)sizeof(CompressFile) ){ ++ sqlite3_log(SQLITE_CANTOPEN, "Unexpected file handle size(%d), vfs:%s", pVfs->szOsFile, pVfs->zName); ++ return SQLITE_CANTOPEN; ++ } ++ sqlite3_file *pLockFd = sqlite3_malloc(pVfs->szOsFile - sizeof(CompressFile)); ++ if( pLockFd==NULL ){ ++ rc = SQLITE_NOMEM; ++ sqlite3_log(rc, "Malloc file handle for lock wrong, size(%d)", (int)(pVfs->szOsFile - sizeof(CompressFile))); ++ return rc; ++ } ++ memset(pLockFd, 0, pVfs->szOsFile - sizeof(CompressFile)); ++ rc = ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), lockPath, pLockFd, flags, NULL); ++ if( rc!=SQLITE_OK ){ ++ sqlite3_log(rc, "Compress vfs lock file open wrong, path:%s, flag(%d)", lockPath, flags); ++ sqlite3_free(pLockFd); ++ } ++ *pFile = pLockFd; ++ return rc; ++} ++ + /* + ** Open a compress file.If this file is not a journal or wal file, + ** it will open a OutterDB and create vfs_pages and vfs_compression table +@@ -902,87 +973,114 @@ static int compressOpen( ){ sqlite3_file *pSubFile = ORIGFILE(pFile); CompressFile *pCompress = (CompressFile *)pFile; @@ -397,28 +437,14 @@ index f2fe169..83e5249 100644 rc = pSubFile->pMethods->xFileSize(pSubFile, &fileSize); if( rc!=SQLITE_OK ){ + sqlite3_log(rc, "Calculate compress file size wrong, path:%s", zName); -+ rc = SQLITE_CANTOPEN; -+ goto END_OUT; -+ } -+ if( pVfs->szOsFile<=(int)sizeof(CompressFile) ){ -+ sqlite3_log(rc, "Unexpected file handle size(%d), vfs:%s", pVfs->szOsFile, pVfs->zName); rc = SQLITE_CANTOPEN; - goto open_end; + goto END_OUT; } -+ pCompress->pLockFd = sqlite3_malloc(pVfs->szOsFile - sizeof(CompressFile)); -+ if( pCompress->pLockFd==NULL ){ -+ rc = SQLITE_NOMEM; -+ sqlite3_log(rc, "Malloc file handle for lock wrong, size(%d)", (int)(pVfs->szOsFile - sizeof(CompressFile))); -+ goto END_OUT; -+ } -+ memset(pCompress->pLockFd, 0, pVfs->szOsFile - sizeof(CompressFile)); -+ const char *walPath = sqlite3_filename_wal(zName); -+ const char *lockPath = walPath + strlen(walPath) + 1; // For compress vfs, lock path place at this position -+ rc = ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), lockPath, pCompress->pLockFd, flags, NULL); ++ rc = compressOpenLockFile(pVfs, zName, flags, &pCompress->pLockFd); + if( rc!=SQLITE_OK ){ -+ sqlite3_log(rc, "Compress vfs lock file open wrong, path:%s, flag(%d)", lockPath, flags); ++ sqlite3_log(rc, "Compress vfs lock file open wrong, path:%s, flag(%d)", zName, flags); + goto END_OUT; + } pFile->pMethods = &compress_io_methods; @@ -536,7 +562,7 @@ index f2fe169..83e5249 100644 } diff --git a/src/sqlite3.c b/src/sqlite3.c -index 0cdb348..5d8a6b0 100644 +index 0cdb348..399c6b1 100644 --- a/src/sqlite3.c +++ b/src/sqlite3.c @@ -5363,7 +5363,7 @@ SQLITE_API int sqlite3_set_droptable_handle(sqlite3*, void (*xFunc)(sqlite3*,con diff --git a/unittest/sqlite_cksum_test.cpp b/unittest/sqlite_cksum_test.cpp index 3ce26df..4dc0f1d 100644 --- a/unittest/sqlite_cksum_test.cpp +++ b/unittest/sqlite_cksum_test.cpp @@ -261,7 +261,7 @@ HWTEST_F(SQLiteCksumTest, CksumTest002, TestSize.Level0) */ hitCksmFault_ = 0; sqlite3 *db = nullptr; - EXPECT_EQ(sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READWRITE, "compressvfs"),SQLITE_OK); + EXPECT_EQ(sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READWRITE, "compressvfs"), SQLITE_OK); int count = 0; EXPECT_EQ(sqlite3_exec(db, "SELECT entryId, entryName FROM salary;", UtQueryResult, &count, nullptr), SQLITE_OK); EXPECT_EQ(hitCksmFault_, 1); diff --git a/unittest/sqlite_compress_test.cpp b/unittest/sqlite_compress_test.cpp index 38876c9..ee17c75 100644 --- a/unittest/sqlite_compress_test.cpp +++ b/unittest/sqlite_compress_test.cpp @@ -50,8 +50,12 @@ public: static void UtBackupDatabase(sqlite3 *srcDb, sqlite3 *destDb); static void UtBackupCompressDatabase(sqlite3 *srcDb, sqlite3 *destDb); static bool IsSupportPageCompress(void); + static void UtPresetDb(const std::string &dbFile, const std::string &vfsOption); + static int UtQueryPresetDbResult(void *data, int argc, char **argv, char **azColName); + static void UtCheckPresetDb(const std::string &dbPath, const std::string &vfsOption); - static sqlite3 *db; + static sqlite3 *db_; + static int resCnt_; static const std::string &UT_DDL_CREATE_DEMO; static const std::string &UT_DML_INSERT_DEMO; static const std::string &UT_SQL_SELECT_META; @@ -59,7 +63,8 @@ public: static const std::string &UT_PHONE_DESC; }; -sqlite3 *SQLiteCompressTest::db = nullptr; +sqlite3 *SQLiteCompressTest::db_ = nullptr; +int SQLiteCompressTest::resCnt_ = 0; const std::string &SQLiteCompressTest::UT_DDL_CREATE_DEMO = "CREATE TABLE demo(id INTEGER PRIMARY KEY, name TEXT);"; const std::string &SQLiteCompressTest::UT_DML_INSERT_DEMO = "INSERT INTO demo(id, name) VALUES(110, 'Call 110!');"; const std::string &SQLiteCompressTest::UT_SQL_SELECT_META = "SELECT COUNT(1) FROM sqlite_master;"; @@ -105,6 +110,82 @@ bool SQLiteCompressTest::IsSupportPageCompress(void) #endif } +void SQLiteCompressTest::UtPresetDb(const std::string &dbFile, const std::string &vfsOption) +{ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open_v2(dbFile.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + (vfsOption.empty()? nullptr : vfsOption.c_str())), SQLITE_OK); + if (vfsOption == "cksmvfs") { + EXPECT_EQ(sqlite3_exec(db, "PRAGMA checksum_persist_enable=ON;", NULL, NULL, NULL), SQLITE_OK); + } + EXPECT_EQ(sqlite3_exec(db, "PRAGMA meta_double_write=enabled;", NULL, NULL, NULL), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(db, "PRAGMA journal_mode=WAL;", NULL, NULL, NULL), SQLITE_OK); + static const char *UT_DDL_CREATE_TABLE = "CREATE TABLE salary" + "(entryId INTEGER PRIMARY KEY, entryName Text, salary REAL, class INTEGER);"; + EXPECT_EQ(sqlite3_exec(db, UT_DDL_CREATE_TABLE, NULL, NULL, NULL), SQLITE_OK); + std::string tmp = "CREATE TABLE test"; + for (size_t i = 0; i < 10; i++) { // 10 means the count of tables + std::string ddl = "CREATE TABLE test"; + ddl += std::to_string(i); + ddl += "(entryId INTEGER PRIMARY KEY, entryName Text, salary REAL, class INTEGER);"; + EXPECT_EQ(sqlite3_exec(db, ddl.c_str(), NULL, NULL, NULL), SQLITE_OK); + } + static const char *UT_SQL_INSERT_DATA = "INSERT INTO salary(entryId, entryName, salary, class) VALUES(?,?,?,?);"; + sqlite3_stmt *insertStmt = NULL; + EXPECT_EQ(sqlite3_prepare_v2(db, UT_SQL_INSERT_DATA, -1, &insertStmt, NULL), SQLITE_OK); + for (int i = 0; i < 1000; i++) { // 1000 means the total number of insert data + sqlite3_bind_int(insertStmt, 1, i + 1); // 1 is the seq number of 1st field + sqlite3_bind_text(insertStmt, 2, "salary-entry-name", -1, SQLITE_STATIC); // 2 is the seq number of 2nd field + sqlite3_bind_double(insertStmt, 3, i); // 3 is the seq number of 3rd field + sqlite3_bind_int(insertStmt, 4, i + 1); // 4 is the seq number of 4th field + EXPECT_EQ(sqlite3_step(insertStmt), SQLITE_DONE); + sqlite3_reset(insertStmt); + } + sqlite3_finalize(insertStmt); + sqlite3_close(db); +} + +int SQLiteCompressTest::UtQueryPresetDbResult(void *data, int argc, char **argv, char **azColName) +{ + int type = *static_cast(data); + EXPECT_EQ(argc, 1); + if (type==0) { // Check tables + std::string tableName = argv[0] ? argv[0] : nullptr; + std::string expectTables[] = { "salary", "test0", "test1", "test2", "test3", "test4", + "test5", "test6", "test7", "test8", "test9" }; + bool isExist = false; + for (size_t i = 0; i < 11; i++) { // 11 is the number of array:expectTables + if (expectTables[i] == tableName) { + isExist = true; + break; + } + } + EXPECT_TRUE(isExist) << ", tableName:" << tableName; + return SQLITE_OK; + } + + resCnt_++; + int entryId = atoi(argv[0]); + EXPECT_TRUE(entryId > 0 && entryId < 1001) << ", entryId:" << entryId; // 1001 is the max id of entryId + return SQLITE_OK; +} + +void SQLiteCompressTest::UtCheckPresetDb(const std::string &dbFile, const std::string &vfsOption) +{ + sqlite3 *db = NULL; + EXPECT_EQ(sqlite3_open_v2(dbFile.c_str(), &db, SQLITE_OPEN_READONLY, + (vfsOption.empty()? nullptr : vfsOption.c_str())), SQLITE_OK); + static const char *UT_SELECT_ALL_TABLES = "SELECT tbl_name FROM sqlite_master;"; + int type = 0; + EXPECT_EQ(sqlite3_exec(db, UT_SELECT_ALL_TABLES, &UtQueryPresetDbResult, &type, nullptr), SQLITE_OK); + type = 1; // 1 means query table: salary + resCnt_ = 0; + static const char *UT_SELECT_ALL_FROM_SALARY = "SELECT entryId FROM salary;"; + EXPECT_EQ(sqlite3_exec(db, UT_SELECT_ALL_FROM_SALARY, &UtQueryPresetDbResult, &type, nullptr), SQLITE_OK); + EXPECT_EQ(resCnt_, 1000); + sqlite3_close(db); +} + void SQLiteCompressTest::SetUpTestCase(void) { Common::RemoveDir(TEST_DIR); @@ -123,13 +204,13 @@ void SQLiteCompressTest::SetUp(void) command += TEST_DIR "/*"; system(command.c_str()); sqlite3_config(SQLITE_CONFIG_LOG, &SQLiteCompressTest::UtSqliteLogPrint, NULL); - EXPECT_EQ(sqlite3_open(TEST_DB, &db), SQLITE_OK); - EXPECT_EQ(sqlite3_exec(db, UT_DDL_CREATE_PHONE.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_open(TEST_DB, &db_), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(db_, UT_DDL_CREATE_PHONE.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); for (int i = 0; i < TEST_PRESET_TABLE_COUNT; i++) { std::string ddl = "CREATE TABLE IF NOT EXISTS test"; ddl += std::to_string(i + 1); ddl += "(id INTEGER PRIMARY KEY, field1 INTEGER, field2 REAL, field3 TEXT, field4 BLOB, field5 TEXT);"; - EXPECT_EQ(sqlite3_exec(db, ddl.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(db_, ddl.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); } std::vector phoneList = {"Huawei", "Samsung", "Apple", "Xiaomi", "Oppo", "Vivo", "Realme"}; for (int i = 0; i < TEST_PRESET_DATA_COUNT; i++) { @@ -139,16 +220,16 @@ void SQLiteCompressTest::SetUp(void) dml += std::to_string(i + 1) + phoneList[i%phoneList.size()] + "',"; dml += std::to_string(i + 1.0) + ","; dml += std::to_string(i + 1) + ",'" + UT_PHONE_DESC + UT_PHONE_DESC + UT_PHONE_DESC + UT_PHONE_DESC + "');"; - EXPECT_EQ(sqlite3_exec(db, dml.c_str(), nullptr, nullptr, nullptr), SQLITE_OK) << sqlite3_errmsg(db); + EXPECT_EQ(sqlite3_exec(db_, dml.c_str(), nullptr, nullptr, nullptr), SQLITE_OK) << sqlite3_errmsg(db_); } - sqlite3_close(db); - EXPECT_EQ(sqlite3_open(TEST_DB, &db), SQLITE_OK); + sqlite3_close(db_); + EXPECT_EQ(sqlite3_open(TEST_DB, &db_), SQLITE_OK); } void SQLiteCompressTest::TearDown(void) { - sqlite3_close(db); - db = nullptr; + sqlite3_close(db_); + db_ = nullptr; sqlite3_config(SQLITE_CONFIG_LOG, NULL, NULL); } @@ -233,7 +314,7 @@ HWTEST_F(SQLiteCompressTest, CompressTest003, TestSize.Level0) sqlite3 *compDb = nullptr; EXPECT_EQ(sqlite3_open_v2(dbPath1.c_str(), &compDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "compressvfs"), SQLITE_OK); - UtBackupDatabase(db, compDb); + UtBackupDatabase(db_, compDb); sqlite3_close_v2(compDb); UtCheckDb(dbPath1); @@ -360,10 +441,10 @@ HWTEST_F(SQLiteCompressTest, CompressTest006, TestSize.Level0) sqlite3 *compDb = nullptr; EXPECT_EQ(sqlite3_open_v2(dbPath1.c_str(), &compDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "compressvfs"), SQLITE_OK); - EXPECT_EQ(sqlite3_exec(compDb, UT_DDL_CREATE_DEMO.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); - EXPECT_EQ(sqlite3_exec(compDb, UT_DML_INSERT_DEMO.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); EXPECT_EQ(sqlite3_exec(compDb, "PRAGMA meta_double_write=enabled;", nullptr, nullptr, nullptr), SQLITE_OK); EXPECT_EQ(sqlite3_exec(compDb, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(compDb, UT_DDL_CREATE_DEMO.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(compDb, UT_DML_INSERT_DEMO.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); /** * @tc.steps: step2. Open multi sqlite3 which enable page compression at the same time * @tc.expected: step2. Execute successfully @@ -454,4 +535,154 @@ HWTEST_F(SQLiteCompressTest, CompressTest008, TestSize.Level0) EXPECT_FALSE(Common::IsFileExist(lock.c_str())); } +/** + * @tc.name: CompressTest009 + * @tc.desc: Test to open an non-cmopress db through vfs option:compress + * @tc.type: FUNC + */ +HWTEST_F(SQLiteCompressTest, CompressTest009, TestSize.Level0) +{ + if (!IsSupportPageCompress()) { + GTEST_SKIP() << "Current testcase is not compatible"; + } + /** + * @tc.steps: step1. Open the exist db through vfs option:compressvfs using SQLITE_OPEN_READONLY + * @tc.expected: step1. Execute successfully + */ + sqlite3 *db1 = nullptr; + EXPECT_EQ(sqlite3_open_v2(TEST_DB, &db1, SQLITE_OPEN_READONLY, "compressvfs"), SQLITE_WARNING); + // lock file not exist, return not a compress db + EXPECT_EQ(sqlite3_extended_errcode(db1), SQLITE_WARNING_NOTCOMPRESSDB); + sqlite3_close_v2(db1); +} + +/** + * @tc.name: CompressTest010 + * @tc.desc: Test to insert data while transaction executing + * @tc.type: FUNC + */ +HWTEST_F(SQLiteCompressTest, CompressTest010, TestSize.Level0) +{ + if (!IsSupportPageCompress()) { + GTEST_SKIP() << "Current testcase is not compatible"; + } + /** + * @tc.steps: step1. Create a brand new db while page compression enabled + * @tc.expected: step1. Execute successfully + */ + std::string dbPath = TEST_DIR "/compresstest010.db"; + UtPresetDb(dbPath, "compressvfs"); + /** + * @tc.steps: step2. Open the db used to begin transaction, delete 5 entries + * @tc.expected: step2. Execute successfully + */ + sqlite3 *db1 = nullptr; + EXPECT_EQ(sqlite3_open_v2(dbPath.c_str(), &db1, SQLITE_OPEN_READWRITE, "compressvfs"), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(db1, "BEGIN;", nullptr, nullptr, nullptr), SQLITE_OK); + // delete data from 996 ~ 1000 + EXPECT_EQ(sqlite3_exec(db1, "DELETE FROM salary WHERE entryId > 995;", nullptr, nullptr, nullptr), SQLITE_OK); + /** + * @tc.steps: step3. Open another db used to insert data, before commit transaction + * @tc.expected: step3. Execute successfully + */ + sqlite3 *db2 = nullptr; + EXPECT_EQ(sqlite3_open_v2(dbPath.c_str(), &db2, SQLITE_OPEN_READWRITE, "compressvfs"), SQLITE_OK); + static const char *UT_DDL_CREATE_TABLE = "CREATE TABLE salary123" + "(entryId INTEGER PRIMARY KEY, entryName Text, salary REAL, class INTEGER);"; + EXPECT_EQ(sqlite3_exec(db2, UT_DDL_CREATE_TABLE, NULL, NULL, NULL), SQLITE_BUSY); + sqlite3_close_v2(db2); + EXPECT_EQ(sqlite3_exec(db1, "COMMIT;", nullptr, nullptr, nullptr), SQLITE_OK); + sqlite3_close_v2(db1); +} + +/** + * @tc.name: CompressTest011 + * @tc.desc: Test to check lock file name + * @tc.type: FUNC + */ +HWTEST_F(SQLiteCompressTest, CompressTest011, TestSize.Level0) +{ + if (!IsSupportPageCompress()) { + GTEST_SKIP() << "Current testcase is not compatible"; + } + /** + * @tc.steps: step1. Get lock file name from non-compress db connection + * @tc.expected: step1. Execute successfully + */ + const char *dbName = sqlite3_db_filename(db_, "main"); + const char *walName = sqlite3_filename_wal(dbName); + const char *lockName = walName + strlen(walName) + 1; + std::string expectLockFilenameSuffix = "/sqlitecompresstest/test.db"; + std::string lockPathStr = lockName; + EXPECT_TRUE(lockPathStr.find(expectLockFilenameSuffix) != std::string::npos); + /** + * @tc.steps: step2. Create a brand new db while page compression enabled, check lock filename + * @tc.expected: step2. Execute successfully + */ + std::string dbPath = TEST_DIR "/compresstest011.db"; + UtPresetDb(dbPath, "compressvfs"); + sqlite3 *db1 = nullptr; + EXPECT_EQ(sqlite3_open_v2(dbPath.c_str(), &db1, SQLITE_OPEN_READWRITE, "compressvfs"), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(db1, UT_DDL_CREATE_PHONE.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + const char *dbName1 = sqlite3_db_filename(db1, "main"); + const char *walName1 = sqlite3_filename_wal(dbName1); + const char *lockName1 = walName1 + strlen(walName1) + 1; + std::string expectLockFilenameSuffix1 = "/sqlitecompresstest/compresstest011.db-lockcompress"; + lockPathStr = lockName1; + EXPECT_TRUE(lockPathStr.find(expectLockFilenameSuffix1) != std::string::npos); + sqlite3_close_v2(db1); +} + +/** + * @tc.name: CompressTest012 + * @tc.desc: Test to check lock file name + * @tc.type: FUNC + */ +HWTEST_F(SQLiteCompressTest, CompressTest012, TestSize.Level0) +{ + if (!IsSupportPageCompress()) { + GTEST_SKIP() << "Current testcase is not compatible"; + } + /** + * @tc.steps: step1. Create brand new db as main db, and a compress db as slave + * @tc.expected: step1. Execute successfully + */ + std::string dbPath = TEST_DIR "/test011.db"; + UtPresetDb(dbPath, ""); + sqlite3 *db = nullptr; + EXPECT_EQ(sqlite3_open_v2(dbPath.c_str(), &db, SQLITE_OPEN_READWRITE, nullptr), SQLITE_OK); + std::string slavePath = TEST_DIR "/test011_slave.db"; + sqlite3 *slaveDb = nullptr; + EXPECT_EQ(sqlite3_open_v2(slavePath.c_str(), &slaveDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "compressvfs"), + SQLITE_OK); + EXPECT_EQ(sqlite3_exec(slaveDb, "PRAGMA meta_double_write=enabled;", nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(slaveDb, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(slaveDb, UT_DDL_CREATE_DEMO.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + EXPECT_EQ(sqlite3_exec(slaveDb, UT_DML_INSERT_DEMO.c_str(), nullptr, nullptr, nullptr), SQLITE_OK); + int persistMode = 1; + EXPECT_EQ(sqlite3_file_control(slaveDb, "main", SQLITE_FCNTL_PERSIST_WAL, &persistMode), SQLITE_OK); + UtBackupDatabase(db, slaveDb); + /** + * @tc.steps: step2. Insert serveral data into slave db, then backup again + * @tc.expected: step2. Execute successfully + */ + static const char *UT_INSERT_DATA = "INSERT INTO salary(entryId, entryName, salary, class) VALUES(?,?,?,?);"; + sqlite3_stmt *insertStmt = NULL; + EXPECT_EQ(sqlite3_prepare_v2(slaveDb, UT_INSERT_DATA, -1, &insertStmt, NULL), SQLITE_OK); + for (int i = 0; i < 100; i++) { // 100 means the total number of insert data + sqlite3_bind_int(insertStmt, 1, i + 20000); // 20000 is the begining of entryId to insert + sqlite3_bind_text(insertStmt, 2, "salary-entry-name", -1, SQLITE_STATIC); // 2 is the seq number of 2nd field + sqlite3_bind_double(insertStmt, 3, i); // 3 is the seq number of 3rd field + sqlite3_bind_int(insertStmt, 4, i + 1); // 4 is the seq number of 4th field + EXPECT_EQ(sqlite3_step(insertStmt), SQLITE_DONE); + sqlite3_reset(insertStmt); + } + sqlite3_finalize(insertStmt); + // Backup again, all data insert above should be covered + UtBackupDatabase(db, slaveDb); + sqlite3_close_v2(slaveDb); + sqlite3_close_v2(db); + UtCheckPresetDb(slavePath, "compressvfs"); +} + } // namespace Test -- Gitee