AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <stdlib.h>5#include <string.h>6#include <stdio.h>7 8static int g_rb_hook_count = 0;9static void rollback_hook(void *pArg){10 int *pCnt = (int*)pArg;11 (*pCnt)++;12}13 14static void exec_ok(sqlite3 *db, const char *sql){15 char *zErr = 0;16 int rc = sqlite3_exec(db, sql, 0, 0, &zErr);17 if( rc!=SQLITE_OK ){18 const char *msg = zErr ? zErr : "sqlite3_exec failed";19 TEST_FAIL_MESSAGE(msg);20 }21 if( zErr ) sqlite3_free(zErr);22}23 24static int single_int_query(sqlite3 *db, const char *sql, int *pVal){25 sqlite3_stmt *pStmt = 0;26 int rc = sqlite3_prepare_v2(db, sql, -1, &pStmt, 0);27 if( rc!=SQLITE_OK ) return rc;28 rc = sqlite3_step(pStmt);29 if( rc==SQLITE_ROW ){30 *pVal = sqlite3_column_int(pStmt, 0);31 rc = SQLITE_OK;32 }else if( rc==SQLITE_DONE ){33 /* No rows - set 0 */34 *pVal = 0;35 rc = SQLITE_OK;36 }37 sqlite3_finalize(pStmt);38 return rc;39}40 41void setUp(void) {42 /* empty */43}44void tearDown(void) {45 /* empty */46}47 48/* Test 1: No active transaction -> no rollback hook; counters/flags cleared */49static void test_sqlite3RollbackAll_noop_no_callback(void){50 sqlite3 *db = 0;51 int rc = sqlite3_open(":memory:", &db);52 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);53 TEST_ASSERT_NOT_NULL(db);54 55 g_rb_hook_count = 0;56 sqlite3_rollback_hook(db, rollback_hook, &g_rb_hook_count);57 58 /* Seed internal counters/flags to verify clearing */59 sqlite3_mutex_enter(db->mutex);60 db->nDeferredCons = 5;61 db->nDeferredImmCons = 3;62 db->flags |= ((u64)SQLITE_DeferFKs) | ((u64)SQLITE_CorruptRdOnly);63 sqlite3RollbackAll(db, SQLITE_ABORT);64 sqlite3_mutex_leave(db->mutex);65 66 TEST_ASSERT_EQUAL_INT(0, g_rb_hook_count);67 TEST_ASSERT_EQUAL_INT(0, db->nDeferredCons);68 TEST_ASSERT_EQUAL_INT(0, db->nDeferredImmCons);69 TEST_ASSERT_EQUAL_UINT64(0, (db->flags & (((u64)SQLITE_DeferFKs)|((u64)SQLITE_CorruptRdOnly))));70 71 sqlite3_close(db);72}73 74/* Test 2: Write transaction is rolled back and rollback hook is invoked */75static void test_sqlite3RollbackAll_write_txn_rollback_and_callback(void){76 sqlite3 *db = 0;77 int rc = sqlite3_open(":memory:", &db);78 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);79 TEST_ASSERT_NOT_NULL(db);80 81 g_rb_hook_count = 0;82 sqlite3_rollback_hook(db, rollback_hook, &g_rb_hook_count);83 84 exec_ok(db, "BEGIN");85 exec_ok(db, "CREATE TABLE t(a)");86 exec_ok(db, "INSERT INTO t VALUES(1)");87 88 /* Verify table visible inside txn */89 int cnt = -1;90 rc = single_int_query(db, "SELECT count(*) FROM sqlite_master WHERE name='t'", &cnt);91 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);92 TEST_ASSERT_EQUAL_INT(1, cnt);93 94 /* Roll back all with trip code */95 sqlite3_mutex_enter(db->mutex);96 sqlite3RollbackAll(db, SQLITE_ABORT);97 sqlite3_mutex_leave(db->mutex);98 99 /* Rollback hook should have fired exactly once */100 TEST_ASSERT_EQUAL_INT(1, g_rb_hook_count);101 102 /* Table t should no longer exist */103 cnt = -1;104 rc = single_int_query(db, "SELECT count(*) FROM sqlite_master WHERE name='t'", &cnt);105 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);106 TEST_ASSERT_EQUAL_INT(0, cnt);107 108 /* Statement using t should now fail */109 sqlite3_stmt *pStmt = 0;110 rc = sqlite3_prepare_v2(db, "SELECT * FROM t", -1, &pStmt, 0);111 /* Prepare may fail already since schema no longer has 't' */112 if( rc==SQLITE_OK ){113 rc = sqlite3_step(pStmt);114 TEST_ASSERT_TRUE(rc==SQLITE_ERROR || rc==SQLITE_SCHEMA);115 }else{116 TEST_ASSERT_EQUAL_INT(SQLITE_ERROR, rc);117 }118 if( pStmt ) sqlite3_finalize(pStmt);119 120 sqlite3_close(db);121}122 123/* Test 3: Read-only transaction (no write btree) still invokes rollback hook */124static void test_sqlite3RollbackAll_read_only_txn_callback(void){125 sqlite3 *db = 0;126 int rc = sqlite3_open(":memory:", &db);127 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);128 TEST_ASSERT_NOT_NULL(db);129 130 g_rb_hook_count = 0;131 sqlite3_rollback_hook(db, rollback_hook, &g_rb_hook_count);132 133 exec_ok(db, "BEGIN"); /* Start a transaction but do not write */134 exec_ok(db, "SELECT 1"); /* Ensure the connection is active */135 136 sqlite3_mutex_enter(db->mutex);137 sqlite3RollbackAll(db, SQLITE_OK);138 sqlite3_mutex_leave(db->mutex);139 140 /* Even without a write txn, !autoCommit implies the hook is called */141 TEST_ASSERT_EQUAL_INT(1, g_rb_hook_count);142 143 sqlite3_close(db);144}145 146/* Test 4: Schema-change path expires prepared statements */147static void test_sqlite3RollbackAll_expires_prepared_statements_on_schema_change(void){148 sqlite3 *db = 0;149 int rc = sqlite3_open(":memory:", &db);150 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);151 TEST_ASSERT_NOT_NULL(db);152 153 /* Prepare a simple statement */154 sqlite3_stmt *pStmt = 0;155 rc = sqlite3_prepare_v2(db, "SELECT 1", -1, &pStmt, 0);156 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);157 TEST_ASSERT_NOT_NULL(pStmt);158 159 /* Force schemaChange path: set DBFLAG_SchemaChange while init.busy==0 */160 sqlite3_mutex_enter(db->mutex);161 db->mDbFlags |= DBFLAG_SchemaChange;162 /* Call rollback-all to trigger sqlite3ExpirePreparedStatements + schema reset */163 sqlite3RollbackAll(db, SQLITE_OK);164 sqlite3_mutex_leave(db->mutex);165 166 /* The prepared statement should now be expired -> first step returns SQLITE_SCHEMA */167 rc = sqlite3_step(pStmt);168 TEST_ASSERT_EQUAL_INT(SQLITE_SCHEMA, rc);169 sqlite3_finalize(pStmt);170 171 sqlite3_close(db);172}173 174/* Test 5: Counters and flags are reset by sqlite3RollbackAll */175static void test_sqlite3RollbackAll_resets_deferred_counters_and_flags(void){176 sqlite3 *db = 0;177 int rc = sqlite3_open(":memory:", &db);178 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);179 TEST_ASSERT_NOT_NULL(db);180 181 sqlite3_mutex_enter(db->mutex);182 db->nDeferredCons = 42;183 db->nDeferredImmCons = 7;184 db->flags |= ((u64)SQLITE_DeferFKs) | ((u64)SQLITE_CorruptRdOnly);185 sqlite3RollbackAll(db, SQLITE_OK);186 sqlite3_mutex_leave(db->mutex);187 188 TEST_ASSERT_EQUAL_INT(0, db->nDeferredCons);189 TEST_ASSERT_EQUAL_INT(0, db->nDeferredImmCons);190 TEST_ASSERT_EQUAL_UINT64(0, (db->flags & (((u64)SQLITE_DeferFKs)|((u64)SQLITE_CorruptRdOnly))));191 192 sqlite3_close(db);193}194 195int main(void){196 UNITY_BEGIN();197 RUN_TEST(test_sqlite3RollbackAll_noop_no_callback);198 RUN_TEST(test_sqlite3RollbackAll_write_txn_rollback_and_callback);199 RUN_TEST(test_sqlite3RollbackAll_read_only_txn_callback);200 RUN_TEST(test_sqlite3RollbackAll_expires_prepared_statements_on_schema_change);201 RUN_TEST(test_sqlite3RollbackAll_resets_deferred_counters_and_flags);202 return UNITY_END();203}