Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
tests_fkey_sqlite3FkRequired.c365 linesDownload Raw Back to tests
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5 6/* Global handles for each test */7static sqlite3 *gDb = NULL;8static Schema *gSchema = NULL;9 10/* Helpers to create and free objects needed by tests */11static Table* makeTable(const char *zName, const char **azCol, int nCol, int iPKey, int setPkFlag){12  Table *pTab = (Table*)sqlite3_malloc(sizeof(Table));13  TEST_ASSERT_NOT_NULL(pTab);14  memset(pTab, 0, sizeof(Table));15  pTab->zName = sqlite3_mprintf("%s", zName);16  TEST_ASSERT_NOT_NULL(pTab->zName);17  pTab->nCol = nCol;18  pTab->aCol = (Column*)sqlite3_malloc(sizeof(Column)*nCol);19  TEST_ASSERT_NOT_NULL(pTab->aCol);20  memset(pTab->aCol, 0, sizeof(Column)*nCol);21  for(int i=0; i<nCol; i++){22    pTab->aCol[i].zCnName = sqlite3_mprintf("%s", azCol[i]);23    TEST_ASSERT_NOT_NULL(pTab->aCol[i].zCnName);24    if( setPkFlag && i==iPKey ){25      pTab->aCol[i].colFlags |= COLFLAG_PRIMKEY;26    }27  }28  pTab->iPKey = iPKey;29  pTab->pSchema = gSchema;30  pTab->tabFlags = 0; /* ordinary table */31  /* Ensure union access is safe */32  pTab->u.tab.pFKey = NULL;33  return pTab;34}35 36static void freeFKey(FKey *pFk){37  if( !pFk ) return;38  /* nCol is set, aCol is inline flexible array */39  for(int i=0;i<pFk->nCol;i++){40    if( pFk->aCol[i].zCol ) sqlite3_free(pFk->aCol[i].zCol);41  }42  if( pFk->zTo ) sqlite3_free(pFk->zTo);43  sqlite3_free(pFk);44}45 46static void freeTable(Table *pTab){47  if( !pTab ) return;48  /* Free any child FKeys linked from this table */49  FKey *p = pTab->u.tab.pFKey;50  while(p){51    FKey *pNext = p->pNextFrom;52    freeFKey(p);53    p = pNext;54  }55  if( pTab->aCol ){56    for(int i=0;i<pTab->nCol;i++){57      if( pTab->aCol[i].zCnName ) sqlite3_free(pTab->aCol[i].zCnName);58    }59    sqlite3_free(pTab->aCol);60  }61  if( pTab->zName ) sqlite3_free(pTab->zName);62  sqlite3_free(pTab);63}64 65static FKey* addChildFk(Table *pChild, const char *zParent, int iChildFrom, const char *zParentColName){66  /* Allocate FKey with space for 1 column map (nCol=1) */67  FKey *pFk = (FKey*)sqlite3_malloc(sizeof(FKey));68  TEST_ASSERT_NOT_NULL(pFk);69  memset(pFk, 0, sizeof(FKey));70  pFk->nCol = 1;71  pFk->zTo = sqlite3_mprintf("%s", zParent);72  TEST_ASSERT_NOT_NULL(pFk->zTo);73  pFk->aCol[0].iFrom = iChildFrom;74  if( zParentColName ){75    pFk->aCol[0].zCol = sqlite3_mprintf("%s", zParentColName);76    TEST_ASSERT_NOT_NULL(pFk->aCol[0].zCol);77  }else{78    pFk->aCol[0].zCol = NULL;79  }80  pFk->aAction[0] = OE_None;81  pFk->aAction[1] = OE_None;82  pFk->pNextFrom = pChild->u.tab.pFKey;83  pChild->u.tab.pFKey = pFk;84  return pFk;85}86 87/* Insert a parent reference into Schema->fkeyHash so sqlite3FkReferences(pTab) finds it.88   Returns heap-allocated key string the hash owns; use removeParentRefFromHash to clean. */89static char* addParentRefToHash(const char *zParentName, FKey *pFk){90  char *zKey = sqlite3_mprintf("%s", zParentName);91  TEST_ASSERT_NOT_NULL(zKey);92  /* No previous entry expected in these tests */93  void *pOld = sqlite3HashInsert(&gSchema->fkeyHash, zKey, pFk);94  TEST_ASSERT_NULL(pOld);95  return zKey;96}97 98static FKey* removeParentRefFromHash(char *zKey){99  /* Remove and return data pointer, then free key */100  void *pData = sqlite3HashInsert(&gSchema->fkeyHash, zKey, 0);101  sqlite3_free(zKey);102  return (FKey*)pData;103}104 105/* Unity fixtures */106void setUp(void) {107  int rc = sqlite3_open(":memory:", &gDb);108  TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);109  /* Create a detached Schema just for fkeyHash */110  gSchema = (Schema*)sqlite3_malloc(sizeof(Schema));111  TEST_ASSERT_NOT_NULL(gSchema);112  memset(gSchema, 0, sizeof(Schema));113  sqlite3HashInit(&gSchema->fkeyHash);114  /* Other hash tables unused */115}116 117void tearDown(void) {118  if( gSchema ){119    /* Clear hash entries (only frees keys; we free FKey data explicitly per test) */120    sqlite3HashClear(&gSchema->fkeyHash);121    sqlite3_free(gSchema);122    gSchema = NULL;123  }124  if( gDb ){125    sqlite3_close(gDb);126    gDb = NULL;127  }128}129 130/* Tests */131 132void test_sqlite3FkRequired_no_fk_flag(void){133  /* Foreign keys disabled: always 0 */134  const char *cols[] = {"c0","c1"};135  Table *pTab = makeTable("t1", cols, 2, 0, 0);136  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;137  /* Ensure flags do not include SQLITE_ForeignKeys */138  gDb->flags &= ~SQLITE_ForeignKeys;139 140  int rcDel = sqlite3FkRequired(&sParse, pTab, NULL, 0);141  TEST_ASSERT_EQUAL_INT(0, rcDel);142 143  int aChange[2] = {-1,-1};144  int rcUpd = sqlite3FkRequired(&sParse, pTab, aChange, 0);145  TEST_ASSERT_EQUAL_INT(0, rcUpd);146 147  freeTable(pTab);148}149 150void test_sqlite3FkRequired_delete_no_fk(void){151  const char *cols[] = {"c0","c1"};152  Table *pTab = makeTable("t1", cols, 2, 0, 0);153  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;154  gDb->flags |= SQLITE_ForeignKeys;155 156  int rc = sqlite3FkRequired(&sParse, pTab, NULL, 0);157  TEST_ASSERT_EQUAL_INT(0, rc);158 159  freeTable(pTab);160}161 162void test_sqlite3FkRequired_delete_has_child_fk(void){163  const char *cols[] = {"c0","c1"};164  Table *pTab = makeTable("t1", cols, 2, 0, 0);165  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;166  gDb->flags |= SQLITE_ForeignKeys;167 168  FKey *pChild = addChildFk(pTab, "parent", 0, "pkey");169  int rc = sqlite3FkRequired(&sParse, pTab, NULL, 0);170  TEST_ASSERT_EQUAL_INT(1, rc);171 172  /* Cleanup */173  /* Detach from table so freeTable frees only pChild once */174  pTab->u.tab.pFKey = NULL;175  freeFKey(pChild);176  freeTable(pTab);177}178 179void test_sqlite3FkRequired_delete_has_parent_fk(void){180  const char *cols[] = {"c0","c1"};181  Table *pTab = makeTable("t1", cols, 2, 0, 0);182  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;183  gDb->flags |= SQLITE_ForeignKeys;184 185  /* Build a parent reference list for "t1" */186  FKey *pPar = (FKey*)sqlite3_malloc(sizeof(FKey));187  TEST_ASSERT_NOT_NULL(pPar);188  memset(pPar, 0, sizeof(FKey));189  pPar->nCol = 1;190  pPar->zTo = sqlite3_mprintf("%s", "t1"); /* parent table name */191  pPar->aAction[0] = OE_None; pPar->aAction[1] = OE_None;192 193  char *zKey = addParentRefToHash("t1", pPar);194  int rc = sqlite3FkRequired(&sParse, pTab, NULL, 0);195  TEST_ASSERT_EQUAL_INT(1, rc);196 197  /* Cleanup */198  FKey *pOld = removeParentRefFromHash(zKey);199  freeFKey(pOld);200  freeTable(pTab);201}202 203void test_sqlite3FkRequired_update_no_relevant_change(void){204  const char *cols[] = {"c0","c1"};205  Table *pTab = makeTable("t1", cols, 2, 0, 0);206  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;207  gDb->flags |= SQLITE_ForeignKeys;208 209  int aChange[2] = {-1,-1}; /* no column modified */210  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 0);211  TEST_ASSERT_EQUAL_INT(0, rc);212 213  freeTable(pTab);214}215 216void test_sqlite3FkRequired_update_child_modified_nonself(void){217  const char *cols[] = {"c0","c1"};218  Table *pTab = makeTable("child", cols, 2, 0, 0);219  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;220  gDb->flags |= SQLITE_ForeignKeys;221 222  /* child FK references different parent "parent" */223  FKey *pChild = addChildFk(pTab, "parent", 0, "pkey");224  int aChange[2] = {0,-1}; /* c0 modified */225  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 0);226  TEST_ASSERT_EQUAL_INT(1, rc);227 228  /* Cleanup */229  pTab->u.tab.pFKey = NULL;230  freeFKey(pChild);231  freeTable(pTab);232}233 234void test_sqlite3FkRequired_update_child_modified_self_referential(void){235  const char *cols[] = {"c0","c1"};236  Table *pTab = makeTable("selftab", cols, 2, 0, 0);237  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;238  gDb->flags |= SQLITE_ForeignKeys;239 240  /* self-referential FK: parent name equals table name */241  FKey *pChild = addChildFk(pTab, "selftab", 0, "pkey");242  int aChange[2] = {0,-1}; /* c0 modified */243  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 0);244  TEST_ASSERT_EQUAL_INT(2, rc);245 246  /* Cleanup */247  pTab->u.tab.pFKey = NULL;248  freeFKey(pChild);249  freeTable(pTab);250}251 252void test_sqlite3FkRequired_update_parent_modified_action_not_noaction(void){253  const char *cols[] = {"pkcol","c1"};254  Table *pTab = makeTable("parenttab", cols, 2, 0, 1); /* pkcol is primary key (flag on) */255  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;256  gDb->flags |= SQLITE_ForeignKeys;257  gDb->flags &= ~SQLITE_FkNoAction; /* allow non-NO ACTION to trigger 2 */258 259  /* Parent reference list for "parenttab"; child maps to column "pkcol" */260  FKey *pPar = (FKey*)sqlite3_malloc(sizeof(FKey));261  TEST_ASSERT_NOT_NULL(pPar);262  memset(pPar, 0, sizeof(FKey));263  pPar->nCol = 1;264  pPar->zTo = sqlite3_mprintf("%s", "parenttab");265  pPar->aCol[0].zCol = sqlite3_mprintf("%s", "pkcol");266  pPar->aAction[1] = OE_Cascade; /* not OE_None -> should return 2 if modified */267  char *zKey = addParentRefToHash("parenttab", pPar);268 269  int aChange[2] = {0,-1}; /* pkcol modified */270  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 0);271  TEST_ASSERT_EQUAL_INT(2, rc);272 273  /* Cleanup */274  FKey *pOld = removeParentRefFromHash(zKey);275  freeFKey(pOld);276  freeTable(pTab);277}278 279void test_sqlite3FkRequired_update_parent_modified_but_noaction_flag_set(void){280  const char *cols[] = {"pkcol","c1"};281  Table *pTab = makeTable("ptab", cols, 2, 0, 1);282  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;283  gDb->flags |= SQLITE_ForeignKeys;284  gDb->flags |= SQLITE_FkNoAction; /* force NO ACTION behavior */285 286  FKey *pPar = (FKey*)sqlite3_malloc(sizeof(FKey));287  TEST_ASSERT_NOT_NULL(pPar);288  memset(pPar, 0, sizeof(FKey));289  pPar->nCol = 1;290  pPar->zTo = sqlite3_mprintf("%s", "ptab");291  pPar->aCol[0].zCol = sqlite3_mprintf("%s", "pkcol");292  pPar->aAction[1] = OE_Cascade; /* would trigger 2 if flag not set */293  char *zKey = addParentRefToHash("ptab", pPar);294 295  int aChange[2] = {0,-1};296  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 0);297  TEST_ASSERT_EQUAL_INT(1, rc); /* processing required but not return 2 */298 299  /* Cleanup */300  FKey *pOld = removeParentRefFromHash(zKey);301  freeFKey(pOld);302  freeTable(pTab);303}304 305void test_sqlite3FkRequired_update_rowid_change_affects_child_pk(void){306  const char *cols[] = {"rowidcol","c1"};307  /* Mark column 0 as pk for rowid mapping purposes */308  Table *pTab = makeTable("child2", cols, 2, 0, 1);309  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;310  gDb->flags |= SQLITE_ForeignKeys;311 312  /* Child FK uses iFrom == iPKey; parent is different to avoid self-triggering 2 */313  FKey *pChild = addChildFk(pTab, "otherparent", 0, "pkey");314  int aChange[2] = {-1,-1}; /* no explicit column modified */315  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 1 /* chngRowid */);316  TEST_ASSERT_EQUAL_INT(1, rc);317 318  /* Cleanup */319  pTab->u.tab.pFKey = NULL;320  freeFKey(pChild);321  freeTable(pTab);322}323 324void test_sqlite3FkRequired_update_rowid_change_affects_parent_pk_zcol_null(void){325  const char *cols[] = {"pkcol","c1"};326  Table *pTab = makeTable("parent2", cols, 2, 0, 1); /* pk at index 0; flagged primary key */327  Parse sParse; memset(&sParse, 0, sizeof(sParse)); sParse.db = gDb;328  gDb->flags |= SQLITE_ForeignKeys;329  gDb->flags &= ~SQLITE_FkNoAction;330 331  /* Parent reference with zCol == NULL indicates referencing parent primary key */332  FKey *pPar = (FKey*)sqlite3_malloc(sizeof(FKey));333  TEST_ASSERT_NOT_NULL(pPar);334  memset(pPar, 0, sizeof(FKey));335  pPar->nCol = 1;336  pPar->zTo = sqlite3_mprintf("%s", "parent2");337  pPar->aCol[0].zCol = NULL; /* refer to PK */338  pPar->aAction[1] = OE_SetNull; /* not OE_None -> should return 2 */339  char *zKey = addParentRefToHash("parent2", pPar);340 341  int aChange[2] = {-1,-1}; /* no explicit column modified */342  int rc = sqlite3FkRequired(&sParse, pTab, aChange, 1 /* rowid changed */);343  TEST_ASSERT_EQUAL_INT(2, rc);344 345  /* Cleanup */346  FKey *pOld = removeParentRefFromHash(zKey);347  freeFKey(pOld);348  freeTable(pTab);349}350 351int main(void) {352  UNITY_BEGIN();353  RUN_TEST(test_sqlite3FkRequired_no_fk_flag);354  RUN_TEST(test_sqlite3FkRequired_delete_no_fk);355  RUN_TEST(test_sqlite3FkRequired_delete_has_child_fk);356  RUN_TEST(test_sqlite3FkRequired_delete_has_parent_fk);357  RUN_TEST(test_sqlite3FkRequired_update_no_relevant_change);358  RUN_TEST(test_sqlite3FkRequired_update_child_modified_nonself);359  RUN_TEST(test_sqlite3FkRequired_update_child_modified_self_referential);360  RUN_TEST(test_sqlite3FkRequired_update_parent_modified_action_not_noaction);361  RUN_TEST(test_sqlite3FkRequired_update_parent_modified_but_noaction_flag_set);362  RUN_TEST(test_sqlite3FkRequired_update_rowid_change_affects_child_pk);363  RUN_TEST(test_sqlite3FkRequired_update_rowid_change_affects_parent_pk_zcol_null);364  return UNITY_END();365}