AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdio.h>5 6/* External wrapper provided for the static function under test */7extern int test_querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock);8 9/* Convenience macros for lock types */10#ifndef READ_LOCK11#define READ_LOCK 112#endif13#ifndef WRITE_LOCK14#define WRITE_LOCK 215#endif16 17/* Path for on-disk database to ensure shared-cache */18static const char *kTestDbPath = "test_qsctl_lock.db";19 20typedef struct TestEnv {21 sqlite3 *db1;22 sqlite3 *db2;23 Btree *bt1; /* Writer */24 Btree *bt2; /* Reader */25 BtShared *pBt; /* Shared BtShared */26} TestEnv;27 28/* Helper: open two Btree handles on the same file in shared-cache mode */29static int open_shared_btrees(TestEnv *env){30 memset(env, 0, sizeof(*env));31 sqlite3_vfs *pVfs = sqlite3_vfs_find(0);32 int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_SHAREDCACHE;33 int rc;34 35 /* Open two SQLite connections (not strictly used for SQL, but needed for Btree) */36 rc = sqlite3_open(kTestDbPath, &env->db1);37 if( rc!=SQLITE_OK ) return rc;38 rc = sqlite3_open(kTestDbPath, &env->db2);39 if( rc!=SQLITE_OK ) return rc;40 41 /* Open two low-level Btree handles pointing at the same file in shared-cache */42 rc = sqlite3BtreeOpen(pVfs, kTestDbPath, env->db1, &env->bt1, 0, vfsFlags);43 if( rc!=SQLITE_OK ) return rc;44 rc = sqlite3BtreeOpen(pVfs, kTestDbPath, env->db2, &env->bt2, 0, vfsFlags);45 if( rc!=SQLITE_OK ) return rc;46 47 /* Ensure both are sharable and share the same BtShared */48 TEST_ASSERT_NOT_NULL(env->bt1);49 TEST_ASSERT_NOT_NULL(env->bt2);50 TEST_ASSERT_NOT_NULL(env->bt1->pBt);51 TEST_ASSERT_NOT_NULL(env->bt2->pBt);52 env->pBt = env->bt1->pBt;53 TEST_ASSERT_TRUE_MESSAGE(env->bt1->sharable!=0, "bt1 must be sharable");54 TEST_ASSERT_TRUE_MESSAGE(env->bt2->sharable!=0, "bt2 must be sharable");55 TEST_ASSERT_EQUAL_PTR_MESSAGE(env->bt1->pBt, env->bt2->pBt, "BtShared must match");56 return SQLITE_OK;57}58 59/* Helper: cleanup */60static void close_shared_btrees(TestEnv *env){61 if( env->bt1 ){62 sqlite3BtreeClose(env->bt1);63 env->bt1 = NULL;64 }65 if( env->bt2 ){66 sqlite3BtreeClose(env->bt2);67 env->bt2 = NULL;68 }69 if( env->db1 ){70 sqlite3_close(env->db1);71 env->db1 = NULL;72 }73 if( env->db2 ){74 sqlite3_close(env->db2);75 env->db2 = NULL;76 }77 /* Remove the on-disk test database */78 sqlite3_os_init(); /* Ensure VFS initialized for unlink on some platforms */79 remove(kTestDbPath);80}81 82/* Helper: Link a BtLock into pBt->pLock for a given Btree connection */83static BtLock* inject_table_lock(BtShared *pBt, Btree *pOwner, Pgno iTable, u8 eLock){84 BtLock *pNew = (BtLock*)sqlite3MallocZero(sizeof(BtLock));85 TEST_ASSERT_NOT_NULL_MESSAGE(pNew, "Failed to allocate BtLock");86 pNew->pBtree = pOwner;87 pNew->iTable = iTable;88 pNew->eLock = eLock;89 /* Link into head of lock list */90 pNew->pNext = pBt->pLock;91 pBt->pLock = pNew;92 return pNew;93}94 95/* Helper: Unlink and free an injected BtLock (only for those allocated here) */96static void remove_injected_lock(BtShared *pBt, BtLock *pLock){97 if( pLock==NULL ) return;98 BtLock **pp = &pBt->pLock;99 while( *pp ){100 if( *pp==pLock ){101 *pp = pLock->pNext;102 sqlite3_free(pLock);103 return;104 }105 pp = &((*pp)->pNext);106 }107 /* If not found, ignore */108}109 110/* Unity required */111void setUp(void) { }112void tearDown(void) { }113 114/* Test 1: No conflict for READ_LOCK returns SQLITE_OK */115void test_querySharedCacheTableLock_no_conflict_read_ok(void){116 TestEnv env; int rc;117 TEST_ASSERT_EQUAL_INT(SQLITE_OK, open_shared_btrees(&env));118 119 /* Query READ lock on schema table (1) with no locks present */120 sqlite3BtreeEnter(env.bt2);121 rc = test_querySharedCacheTableLock(env.bt2, 1, READ_LOCK);122 sqlite3BtreeLeave(env.bt2);123 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);124 125 close_shared_btrees(&env);126}127 128/* Test 2: Exclusive writer prevents other from obtaining READ_LOCK */129void test_querySharedCacheTableLock_exclusive_blocks_read(void){130 TestEnv env; int rc;131 TEST_ASSERT_EQUAL_INT(SQLITE_OK, open_shared_btrees(&env));132 133 /* Begin an exclusive write transaction on bt1 (writer) */134 rc = sqlite3BtreeBeginTrans(env.bt1, 2, 0); /* wrflag=2 => exclusive */135 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);136 137 sqlite3BtreeEnter(env.bt2);138 rc = test_querySharedCacheTableLock(env.bt2, 1, READ_LOCK);139 sqlite3BtreeLeave(env.bt2);140 TEST_ASSERT_EQUAL_INT(SQLITE_LOCKED_SHAREDCACHE, rc);141 142 /* Rollback writer to release exclusive */143 sqlite3BtreeRollback(env.bt1, SQLITE_OK, 0);144 close_shared_btrees(&env);145}146 147/* Test 3: READ_LOCK held by another connection does not block another READ_LOCK */148void test_querySharedCacheTableLock_readers_do_not_conflict(void){149 TestEnv env; int rc; BtLock *pL = NULL;150 TEST_ASSERT_EQUAL_INT(SQLITE_OK, open_shared_btrees(&env));151 152 /* Inject a READ_LOCK on table 1 for bt2 (reader) */153 pL = inject_table_lock(env.pBt, env.bt2, 1, READ_LOCK);154 155 /* Now bt1 queries for READ_LOCK on table 1 -> should be OK */156 sqlite3BtreeEnter(env.bt1);157 rc = test_querySharedCacheTableLock(env.bt1, 1, READ_LOCK);158 sqlite3BtreeLeave(env.bt1);159 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);160 161 /* Cleanup */162 remove_injected_lock(env.pBt, pL);163 close_shared_btrees(&env);164}165 166/* Test 4: READ_LOCK by another connection prevents WRITE_LOCK and sets BTS_PENDING */167void test_querySharedCacheTableLock_reader_blocks_writer_sets_pending(void){168 TestEnv env; int rc; BtLock *pL = NULL;169 TEST_ASSERT_EQUAL_INT(SQLITE_OK, open_shared_btrees(&env));170 171 /* Inject a READ_LOCK for bt2 (other connection) on table 1 */172 pL = inject_table_lock(env.pBt, env.bt2, 1, READ_LOCK);173 174 /* Begin write transaction on bt1 (writer) */175 rc = sqlite3BtreeBeginTrans(env.bt1, 1, 0); /* wrflag=1 */176 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);177 178 /* Query WRITE_LOCK on table 1 as writer */179 sqlite3BtreeEnter(env.bt1);180 rc = test_querySharedCacheTableLock(env.bt1, 1, WRITE_LOCK);181 sqlite3BtreeLeave(env.bt1);182 183 TEST_ASSERT_EQUAL_INT(SQLITE_LOCKED_SHAREDCACHE, rc);184 /* BTS_PENDING bit should be set */185 TEST_ASSERT_TRUE( (env.pBt->btsFlags & BTS_PENDING)!=0 );186 187 /* Cleanup: clear pending, remove lock, rollback writer */188 env.pBt->btsFlags &= ~BTS_PENDING;189 remove_injected_lock(env.pBt, pL);190 sqlite3BtreeRollback(env.bt1, SQLITE_OK, 0);191 192 close_shared_btrees(&env);193}194 195/* Test 5: Existing WRITE_LOCK on table blocks another READ_LOCK */196void test_querySharedCacheTableLock_writer_blocks_reader(void){197 TestEnv env; int rc; BtLock *pL = NULL;198 TEST_ASSERT_EQUAL_INT(SQLITE_OK, open_shared_btrees(&env));199 200 /* Inject a WRITE_LOCK for bt1 (or bt2) on table 1 to simulate writer holding a write lock */201 pL = inject_table_lock(env.pBt, env.bt1, 1, WRITE_LOCK);202 203 /* Now bt2 queries READ_LOCK on same table - should be locked */204 sqlite3BtreeEnter(env.bt2);205 rc = test_querySharedCacheTableLock(env.bt2, 1, READ_LOCK);206 sqlite3BtreeLeave(env.bt2);207 208 TEST_ASSERT_EQUAL_INT(SQLITE_LOCKED_SHAREDCACHE, rc);209 210 /* Cleanup */211 remove_injected_lock(env.pBt, pL);212 close_shared_btrees(&env);213}214 215/* Test 6: Locks on different tables do not conflict a WRITE request on another table */216void test_querySharedCacheTableLock_different_tables_ok(void){217 TestEnv env; int rc; BtLock *pL = NULL;218 TEST_ASSERT_EQUAL_INT(SQLITE_OK, open_shared_btrees(&env));219 220 /* Inject READ lock on table 5 */221 pL = inject_table_lock(env.pBt, env.bt2, 5, READ_LOCK);222 223 /* Begin write transaction on bt1 (writer) */224 rc = sqlite3BtreeBeginTrans(env.bt1, 1, 0);225 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);226 227 /* Query WRITE on table 1, should be OK since conflicting lock is on table 5 */228 sqlite3BtreeEnter(env.bt1);229 rc = test_querySharedCacheTableLock(env.bt1, 1, WRITE_LOCK);230 sqlite3BtreeLeave(env.bt1);231 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);232 233 /* Cleanup */234 sqlite3BtreeRollback(env.bt1, SQLITE_OK, 0);235 remove_injected_lock(env.pBt, pL);236 close_shared_btrees(&env);237}238 239int main(void){240 UNITY_BEGIN();241 RUN_TEST(test_querySharedCacheTableLock_no_conflict_read_ok);242 RUN_TEST(test_querySharedCacheTableLock_exclusive_blocks_read);243 RUN_TEST(test_querySharedCacheTableLock_readers_do_not_conflict);244 RUN_TEST(test_querySharedCacheTableLock_reader_blocks_writer_sets_pending);245 RUN_TEST(test_querySharedCacheTableLock_writer_blocks_reader);246 RUN_TEST(test_querySharedCacheTableLock_different_tables_ok);247 return UNITY_END();248}