Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
tests_fkey_sqlite3FkDelete.c200 linesDownload Raw Back to tests
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}