AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4 5/* Helper: create a trigger with a single empty step so fkTriggerDelete() can process safely */6static Trigger* makeTriggerWithOneStep(sqlite3 *db){7 Trigger *pTrig = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));8 TEST_ASSERT_NOT_NULL(pTrig);9 TriggerStep *pStep = (TriggerStep*)sqlite3DbMallocZero(db, sizeof(TriggerStep));10 TEST_ASSERT_NOT_NULL(pStep);11 pTrig->step_list = pStep; /* All subfields within pStep are NULL */12 /* pTrig->pWhen remains NULL */13 return pTrig;14}15 16/* Helper: allocate and initialize a minimal FKey pointing to parent name zTo */17static FKey* makeFKey(sqlite3 *db, const char *zTo, int isDeferred){18 FKey *p = (FKey*)sqlite3DbMallocZero(db, sizeof(FKey));19 TEST_ASSERT_NOT_NULL(p);20 p->zTo = zTo; /* Name of parent table */21 p->isDeferred = (u8)(isDeferred ? 1 : 0);22 /* p->pNextFrom, p->pPrevTo, p->pNextTo, p->apTrigger[] default NULL */23 return p;24}25 26/* Create a minimal ordinary Table bound to the main schema of db */27static void initOrdinaryTable(sqlite3 *db, Table *pTab){28 memset(pTab, 0, sizeof(*pTab));29 pTab->pSchema = db->aDb[0].pSchema;30 /* Zero-initialized Table should be considered an ordinary table by IsOrdinaryTable() */31}32 33/* Convenience: fetch current head in schema fkeyHash for given zTo */34static void* fkeyHashHead(Schema *pSchema, const char *zTo){35 return sqlite3HashFind(&pSchema->fkeyHash, zTo);36}37 38void setUp(void) {39 /* no-op */40}41void tearDown(void) {42 /* no-op */43}44 45/* Test: Removing the head of a to-chain with a successor:46 - Hash head should be updated to the successor.47 - Successor's pPrevTo should become NULL. */48void test_sqlite3FkDelete_updates_hash_when_head_removed_and_has_next(void){49 sqlite3 *db = 0;50 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));51 Schema *pSchema = db->aDb[0].pSchema;52 53 Table fromTab;54 initOrdinaryTable(db, &fromTab);55 56 /* Build chain fkA (owned by fromTab) -> fkB (owned by another table) for parent "parent" */57 FKey *fkA = makeFKey(db, "parent", 0);58 FKey *fkB = makeFKey(db, "parent", 1);59 fkA->pNextTo = fkB;60 fkB->pPrevTo = fkA;61 62 /* Give fkA some triggers to exercise fkTriggerDelete */63 fkA->apTrigger[0] = makeTriggerWithOneStep(db);64 fkA->apTrigger[1] = makeTriggerWithOneStep(db);65 66 /* Attach fkA to fromTab's FKey list */67 fromTab.u.tab.pFKey = fkA;68 69 /* Hash head points to fkA initially */70 sqlite3HashInsert(&pSchema->fkeyHash, "parent", fkA);71 TEST_ASSERT_EQUAL_PTR(fkA, fkeyHashHead(pSchema, "parent"));72 73 sqlite3_mutex_enter(db->mutex);74 sqlite3FkDelete(db, &fromTab);75 sqlite3_mutex_leave(db->mutex);76 77 /* After deletion, hash head should be fkB and fkB->pPrevTo should be NULL */78 TEST_ASSERT_EQUAL_PTR(fkB, fkeyHashHead(pSchema, "parent"));79 TEST_ASSERT_NULL(fkB->pPrevTo);80 81 /* Cleanup: remove hash entry and free the surviving fkB */82 sqlite3HashInsert(&pSchema->fkeyHash, "parent", 0);83 sqlite3DbFree(db, fkB);84 85 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db));86}87 88/* Test: Removing the last (and only) FKey for a parent name:89 - Hash entry should be removed (NULL). */90void test_sqlite3FkDelete_removes_hash_entry_when_last(void){91 sqlite3 *db = 0;92 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));93 Schema *pSchema = db->aDb[0].pSchema;94 95 Table fromTab;96 initOrdinaryTable(db, &fromTab);97 98 FKey *fkC = makeFKey(db, "solo", 0);99 fkC->apTrigger[0] = makeTriggerWithOneStep(db); /* Exercise trigger deletion */100 fromTab.u.tab.pFKey = fkC;101 102 sqlite3HashInsert(&pSchema->fkeyHash, "solo", fkC);103 TEST_ASSERT_EQUAL_PTR(fkC, fkeyHashHead(pSchema, "solo"));104 105 sqlite3_mutex_enter(db->mutex);106 sqlite3FkDelete(db, &fromTab);107 sqlite3_mutex_leave(db->mutex);108 109 /* The hash entry for "solo" should be gone */110 TEST_ASSERT_NULL(fkeyHashHead(pSchema, "solo"));111 112 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db));113}114 115/* Test: Removing a non-head node in a to-chain:116 - Head should remain the same in the hash.117 - Neighbor links should be updated (head->pNextTo becomes removed->pNextTo). */118void test_sqlite3FkDelete_updates_links_when_middle_removed_no_hash_change(void){119 sqlite3 *db = 0;120 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));121 Schema *pSchema = db->aDb[0].pSchema;122 123 Table fromTab;124 initOrdinaryTable(db, &fromTab);125 126 /* Build chain: fkHead (other table) -> fkMid (owned by fromTab) */127 FKey *fkHead = makeFKey(db, "chain2", 0);128 FKey *fkMid = makeFKey(db, "chain2", 0);129 130 fkHead->pNextTo = fkMid;131 fkMid->pPrevTo = fkHead;132 133 fromTab.u.tab.pFKey = fkMid;134 135 /* Hash head initially at fkHead */136 sqlite3HashInsert(&pSchema->fkeyHash, "chain2", fkHead);137 TEST_ASSERT_EQUAL_PTR(fkHead, fkeyHashHead(pSchema, "chain2"));138 139 sqlite3_mutex_enter(db->mutex);140 sqlite3FkDelete(db, &fromTab);141 sqlite3_mutex_leave(db->mutex);142 143 /* Head remains unchanged and should now have no next (fkMid removed) */144 TEST_ASSERT_EQUAL_PTR(fkHead, fkeyHashHead(pSchema, "chain2"));145 TEST_ASSERT_NULL(fkHead->pNextTo);146 147 /* Cleanup */148 sqlite3HashInsert(&pSchema->fkeyHash, "chain2", 0);149 sqlite3DbFree(db, fkHead);150 151 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db));152}153 154/* Test: When db->pnBytesFreed is non-NULL, hash updates are skipped:155 - Even if removing a head, the hash head pointer stays unchanged. */156void test_sqlite3FkDelete_skips_hash_changes_when_pnBytesFreed_nonnull(void){157 sqlite3 *db = 0;158 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));159 Schema *pSchema = db->aDb[0].pSchema;160 161 Table fromTab;162 initOrdinaryTable(db, &fromTab);163 164 FKey *fkA = makeFKey(db, "parent2", 0);165 FKey *fkB = makeFKey(db, "parent2", 0);166 fkA->pNextTo = fkB;167 fkB->pPrevTo = fkA;168 169 fromTab.u.tab.pFKey = fkA;170 sqlite3HashInsert(&pSchema->fkeyHash, "parent2", fkA);171 TEST_ASSERT_EQUAL_PTR(fkA, fkeyHashHead(pSchema, "parent2"));172 173 /* Set pnBytesFreed non-NULL to skip hash manipulation */174 int dummy = 0;175 db->pnBytesFreed = &dummy;176 177 sqlite3_mutex_enter(db->mutex);178 sqlite3FkDelete(db, &fromTab);179 sqlite3_mutex_leave(db->mutex);180 181 /* Hash head should remain pointing at the original head pointer value (fkA).182 Note: fkA has been freed, but we only compare the pointer value returned by the hash. */183 TEST_ASSERT_EQUAL_PTR(fkA, fkeyHashHead(pSchema, "parent2"));184 185 /* Restore pnBytesFreed and cleanup remaining allocations */186 db->pnBytesFreed = 0;187 sqlite3HashInsert(&pSchema->fkeyHash, "parent2", 0);188 sqlite3DbFree(db, fkB);189 190 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(db));191}192 193int main(void){194 UNITY_BEGIN();195 RUN_TEST(test_sqlite3FkDelete_updates_hash_when_head_removed_and_has_next);196 RUN_TEST(test_sqlite3FkDelete_removes_hash_entry_when_last);197 RUN_TEST(test_sqlite3FkDelete_updates_links_when_middle_removed_no_hash_change);198 RUN_TEST(test_sqlite3FkDelete_skips_hash_changes_when_pnBytesFreed_nonnull);199 return UNITY_END();200}