From c1a55a8d3d48892c9adcafbe24130a8be4da96a4 Mon Sep 17 00:00:00 2001 From: Liu Hongyang Date: Thu, 14 Aug 2025 11:06:09 +0800 Subject: [PATCH] fix binlog replay failed with single quotes Signed-off-by: Liu Hongyang --- patch/0006-Support-Binlog.patch | 22 ++-- unittest/BUILD.gn | 7 ++ unittest/sqlite_binlog_test.cpp | 175 ++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 12 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..cb27ed7 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 288765c775965ee42d05816668658a2bac326889 Mon Sep 17 00:00:00 2001 +From: Liu Hongyang +Date: Thu, 14 Aug 2025 11:03:58 +0800 Subject: [PATCH 06/12] Support-Binlog --- - src/sqlite3.c | 1508 ++++++++++++++++++++++++++++++++++++++++++++++++- - 1 file changed, 1499 insertions(+), 9 deletions(-) + src/sqlite3.c | 1506 ++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 1497 insertions(+), 9 deletions(-) diff --git a/src/sqlite3.c b/src/sqlite3.c -index e7e8b3c..3f1195a 100644 +index b433dfb..2558d0d 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,1072 @@ static void walLogCheckpointInfo(Wal *pWal, sqlite3 *db, sqlite3_int64 startTime } #endif @@ -1360,7 +1360,7 @@ index e7e8b3c..3f1195a 100644 + const unsigned char *temp = sqlite3_value_text(tempVal); + + char *res = NULL; -+ if (type == SQLITE_BLOB) { ++ if (type == SQLITE_BLOB || type == SQLITE_TEXT) { + char *hex = sqlite3BinlogGetHexFromBlobOrStr(db, tempVal); + if (hex != NULL) { + res = sqlite3_mprintf("x'%s'", hex); @@ -1368,8 +1368,6 @@ index e7e8b3c..3f1195a 100644 + } else { + res = sqlite3_mprintf("'%s'", temp); + } -+ } else if (type == SQLITE_TEXT) { -+ res = sqlite3_mprintf("'%s'", temp); + } else if (type == SQLITE_NULL) { + res = sqlite3_mprintf("NULL"); + } else { @@ -1755,7 +1753,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 +258418,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 +1763,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 +258431,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 93864a1..3b21177 100644 --- a/unittest/BUILD.gn +++ b/unittest/BUILD.gn @@ -41,6 +41,13 @@ ohos_unittest("libsqlittest") { "./sqlite_test.cpp", ] + if (is_ohos && (!defined(global_parts_info) || + defined(global_parts_info.distributeddatamgr_arkdata_database_core))) { + sources += [ + "./sqlite_binlog_test.cpp", + ] + } + configs = [ ":module_private_config" ] external_deps = [ diff --git a/unittest/sqlite_binlog_test.cpp b/unittest/sqlite_binlog_test.cpp new file mode 100644 index 0000000..9405328 --- /dev/null +++ b/unittest/sqlite_binlog_test.cpp @@ -0,0 +1,175 @@ +/* + * 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 *insertDml = "INSERT INTO salary(entryId, entryName, salary, class) VALUES(?,?,?,?);"; + sqlite3_stmt *insertStmt = NULL; + EXPECT_EQ(sqlite3_prepare_v2(db, insertDml, -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