AryaWu/sqlite
0
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}