AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <stdlib.h>5#include <string.h>6#include <stddef.h>7 8/* Global database handle for allocations via sqlite3DbMalloc* */9static sqlite3 *gDb = NULL;10 11/* Helper: create a Table with given columns and IPK index */12static Table* makeTable(sqlite3 *db, const char *zName, int nCol, const char **azColName, int iPKey){13 Table *pTab = (Table*)sqlite3DbMallocZero(db, sizeof(Table));14 TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "OOM creating Table");15 pTab->zName = (char*)zName;16 pTab->nCol = (i16)nCol;17 pTab->iPKey = (i16)iPKey;18 if( nCol>0 ){19 Column *aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nCol);20 TEST_ASSERT_NOT_NULL_MESSAGE(aCol, "OOM creating columns");21 for(int i=0;i<nCol;i++){22 aCol[i].zCnName = (char*)azColName[i];23 /* Leave zColl NULL to imply default "BINARY" unless tests set otherwise */24 aCol[i].zColl = 0;25 }26 pTab->aCol = aCol;27 }28 return pTab;29}30 31/* Helper: create and link an Index on pTab */32static Index* makeIndex(33 sqlite3 *db, const char *zName, Table *pTab,34 int nKeyCol, const int *aiCol, const char **azColl,35 int idxType, int onError, int isPartial36){37 Index *pIdx = (Index*)sqlite3DbMallocZero(db, sizeof(Index));38 TEST_ASSERT_NOT_NULL_MESSAGE(pIdx, "OOM creating Index");39 pIdx->zName = (char*)zName;40 pIdx->pTable = pTab;41 pIdx->nKeyCol = (i16)nKeyCol;42 pIdx->nColumn = (i16)nKeyCol;43 pIdx->aiColumn = (i16*)sqlite3DbMallocZero(db, sizeof(i16)*nKeyCol);44 TEST_ASSERT_NOT_NULL_MESSAGE(pIdx->aiColumn, "OOM creating aiColumn");45 pIdx->azColl = (char**)sqlite3DbMallocZero(db, sizeof(char*)*nKeyCol);46 TEST_ASSERT_NOT_NULL_MESSAGE(pIdx->azColl, "OOM creating azColl");47 for(int i=0;i<nKeyCol;i++){48 pIdx->aiColumn[i] = (i16)aiCol[i];49 pIdx->azColl[i] = (char*)(azColl ? azColl[i] : "BINARY");50 }51 pIdx->idxType = (u8)idxType;52 pIdx->onError = (u8)onError;53 pIdx->pPartIdxWhere = isPartial ? (Expr*)0x1 : 0;54 /* Link at head of list */55 pIdx->pNext = pTab->pIndex;56 pTab->pIndex = pIdx;57 return pIdx;58}59 60/* Helper: create an FKey with nCol entries */61static FKey* makeFKey(62 sqlite3 *db, Table *pFrom, const char *zTo,63 int nCol, const char **azParentCol, const int *aiFrom64){65 size_t sz = offsetof(FKey, aCol) + nCol * sizeof(((FKey*)0)->aCol[0]);66 FKey *pFKey = (FKey*)sqlite3DbMallocZero(db, (int)sz);67 TEST_ASSERT_NOT_NULL_MESSAGE(pFKey, "OOM creating FKey");68 pFKey->pFrom = pFrom;69 pFKey->zTo = (char*)zTo;70 pFKey->nCol = nCol;71 for(int i=0;i<nCol;i++){72 pFKey->aCol[i].zCol = (char*)azParentCol[i];73 pFKey->aCol[i].iFrom = aiFrom[i];74 }75 return pFKey;76}77 78/* Helper: free an Index list */79static void freeIndexList(sqlite3 *db, Index *pIdx){80 while(pIdx){81 Index *pNext = pIdx->pNext;82 if( pIdx->aiColumn ) sqlite3DbFree(db, pIdx->aiColumn);83 if( pIdx->azColl ) sqlite3DbFree(db, pIdx->azColl);84 sqlite3DbFree(db, pIdx);85 pIdx = pNext;86 }87}88 89/* Helper: free a Table (columns and indexes) */90static void freeTable(sqlite3 *db, Table *pTab){91 if( !pTab ) return;92 if( pTab->pIndex ) freeIndexList(db, pTab->pIndex);93 if( pTab->aCol ) sqlite3DbFree(db, pTab->aCol);94 sqlite3DbFree(db, pTab);95}96 97/* Helper: free an FKey */98static void freeFKey(sqlite3 *db, FKey *pFKey){99 if( pFKey ) sqlite3DbFree(db, pFKey);100}101 102void setUp(void) {103 int rc = sqlite3_open(":memory:", &gDb);104 TEST_ASSERT_EQUAL_INT(0, rc);105 TEST_ASSERT_NOT_NULL(gDb);106}107 108void tearDown(void) {109 if( gDb ){110 sqlite3_close(gDb);111 gDb = NULL;112 }113}114 115/* Test 1: Single-column FK implicitly mapping to INTEGER PRIMARY KEY */116void test_sqlite3FkLocateIndex_singlecol_implicit_ipk(void){117 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;118 119 const char *colsP[] = { "id" };120 Table *pParent = makeTable(gDb, "parent", 1, colsP, 0); /* iPKey=0 */121 const char *colsC[] = { "cid" };122 Table *pChild = makeTable(gDb, "child", 1, colsC, -1);123 124 const char *azParentCol[] = { NULL }; /* implicit to PK */125 int aiFrom[] = { 0 };126 FKey *pFKey = makeFKey(gDb, pChild, "parent", 1, azParentCol, aiFrom);127 128 Index *pIdx = NULL;129 int *aiCol = NULL;130 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);131 TEST_ASSERT_EQUAL_INT(0, rc);132 TEST_ASSERT_NULL(pIdx);133 TEST_ASSERT_NULL(aiCol);134 135 freeFKey(gDb, pFKey);136 freeTable(gDb, pChild);137 freeTable(gDb, pParent);138}139 140/* Test 2: Single-column FK explicitly naming the INTEGER PRIMARY KEY */141void test_sqlite3FkLocateIndex_singlecol_explicit_ipk(void){142 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;143 144 const char *colsP[] = { "id" };145 Table *pParent = makeTable(gDb, "parent", 1, colsP, 0);146 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);147 148 const char *azParentCol[] = { "id" }; /* explicit PK column */149 int aiFrom[] = { 2 };150 FKey *pFKey = makeFKey(gDb, pChild, "parent", 1, azParentCol, aiFrom);151 152 Index *pIdx = NULL;153 int *aiCol = NULL;154 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);155 TEST_ASSERT_EQUAL_INT(0, rc);156 TEST_ASSERT_NULL(pIdx);157 TEST_ASSERT_NULL(aiCol);158 159 freeFKey(gDb, pFKey);160 freeTable(gDb, pChild);161 freeTable(gDb, pParent);162}163 164/* Test 3: Composite FK implicitly mapping to PRIMARY KEY index */165void test_sqlite3FkLocateIndex_composite_implicit_pk(void){166 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;167 168 const char *colsP[] = { "a", "b" };169 Table *pParent = makeTable(gDb, "parent", 2, colsP, -1); /* no IPK for composite */170 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);171 172 int aiIndexCols[] = { 0, 1 };173 const char *azColl[] = { "BINARY", "BINARY" };174 Index *pPK = makeIndex(gDb, "pk_parent", pParent, 2, aiIndexCols, azColl,175 SQLITE_IDXTYPE_PRIMARYKEY, OE_Abort, 0);176 177 const char *azParentCol[] = { NULL, NULL }; /* implicit to PK */178 int aiFrom[] = { 4, 7 };179 FKey *pFKey = makeFKey(gDb, pChild, "parent", 2, azParentCol, aiFrom);180 181 Index *pIdx = NULL;182 int *aiCol = NULL;183 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);184 TEST_ASSERT_EQUAL_INT(0, rc);185 TEST_ASSERT_EQUAL_PTR(pPK, pIdx);186 TEST_ASSERT_NOT_NULL(aiCol);187 TEST_ASSERT_EQUAL_INT(4, aiCol[0]);188 TEST_ASSERT_EQUAL_INT(7, aiCol[1]);189 190 sqlite3DbFree(gDb, aiCol);191 freeFKey(gDb, pFKey);192 freeTable(gDb, pChild);193 freeTable(gDb, pParent);194}195 196/* Test 4: Explicit mapping to UNIQUE non-PK index with matching collations */197void test_sqlite3FkLocateIndex_explicit_unique_index_collation_ok(void){198 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;199 200 const char *colsP[] = { "a", "b", "c" };201 Table *pParent = makeTable(gDb, "parent", 3, colsP, -1);202 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);203 204 int aiIndexCols[] = { 1, 0 }; /* index on (b, a) */205 const char *azColl[] = { "BINARY", "BINARY" };206 Index *pU = makeIndex(gDb, "u_b_a", pParent, 2, aiIndexCols, azColl,207 SQLITE_IDXTYPE_APPDEF, OE_Abort, 0);208 209 const char *azParentCol[] = { "b", "a" };210 int aiFrom[] = { 5, 6 };211 FKey *pFKey = makeFKey(gDb, pChild, "parent", 2, azParentCol, aiFrom);212 213 Index *pIdx = NULL;214 int *aiCol = NULL;215 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);216 TEST_ASSERT_EQUAL_INT(0, rc);217 TEST_ASSERT_EQUAL_PTR(pU, pIdx);218 TEST_ASSERT_NOT_NULL(aiCol);219 TEST_ASSERT_EQUAL_INT(5, aiCol[0]);220 TEST_ASSERT_EQUAL_INT(6, aiCol[1]);221 222 sqlite3DbFree(gDb, aiCol);223 freeFKey(gDb, pFKey);224 freeTable(gDb, pChild);225 freeTable(gDb, pParent);226}227 228/* Test 5: Explicit mapping fails due to index using non-default collation */229void test_sqlite3FkLocateIndex_explicit_wrong_collation_fail_and_error(void){230 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;231 232 const char *colsP[] = { "a", "b" };233 Table *pParent = makeTable(gDb, "parent", 2, colsP, -1);234 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);235 236 int aiIndexCols[] = { 0, 1 };237 const char *azCollBad[] = { "BINARY", "NOCASE" }; /* second column wrong */238 makeIndex(gDb, "u_a_b", pParent, 2, aiIndexCols, azCollBad,239 SQLITE_IDXTYPE_APPDEF, OE_Abort, 0);240 241 const char *azParentCol[] = { "a", "b" };242 int aiFrom[] = { 1, 2 };243 FKey *pFKey = makeFKey(gDb, pChild, "parent", 2, azParentCol, aiFrom);244 245 Index *pIdx = NULL;246 int *aiCol = NULL;247 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);248 249 TEST_ASSERT_EQUAL_INT(1, rc);250 TEST_ASSERT_NULL(pIdx);251 /* aiCol is freed inside on failure; do not dereference or free. Reset to NULL to avoid accidental use. */252 aiCol = NULL;253 254 TEST_ASSERT_NOT_NULL(parse.zErrMsg);255 TEST_ASSERT_NOT_EQUAL(-1, strstr(parse.zErrMsg, "foreign key mismatch") ? 0 : -1);256 sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = NULL;257 258 freeFKey(gDb, pFKey);259 freeTable(gDb, pChild);260 freeTable(gDb, pParent);261}262 263/* Test 6: Implicit composite mapping requires PRIMARY KEY, not just UNIQUE index */264void test_sqlite3FkLocateIndex_implicit_needs_pk_not_unique_idx_fail(void){265 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;266 267 const char *colsP[] = { "x", "y" };268 Table *pParent = makeTable(gDb, "parent", 2, colsP, -1);269 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);270 271 int aiIndexCols[] = { 0, 1 };272 const char *azColl[] = { "BINARY", "BINARY" };273 /* Create UNIQUE index, but not PRIMARY KEY */274 makeIndex(gDb, "u_x_y", pParent, 2, aiIndexCols, azColl,275 SQLITE_IDXTYPE_APPDEF, OE_Abort, 0);276 277 const char *azParentCol[] = { NULL, NULL }; /* implicit to PK */278 int aiFrom[] = { 10, 11 };279 FKey *pFKey = makeFKey(gDb, pChild, "parent", 2, azParentCol, aiFrom);280 281 Index *pIdx = NULL;282 int *aiCol = NULL;283 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);284 285 TEST_ASSERT_EQUAL_INT(1, rc);286 TEST_ASSERT_NULL(pIdx);287 aiCol = NULL; /* freed internally on failure */288 TEST_ASSERT_NOT_NULL(parse.zErrMsg);289 sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = NULL;290 291 freeFKey(gDb, pFKey);292 freeTable(gDb, pChild);293 freeTable(gDb, pParent);294}295 296/* Test 7: Explicit mapping fails for expression index (aiColumn < 0) */297void test_sqlite3FkLocateIndex_explicit_expression_index_fail(void){298 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;299 300 const char *colsP[] = { "a" };301 Table *pParent = makeTable(gDb, "parent", 1, colsP, -1);302 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);303 304 int aiIndexCols[] = { -2 }; /* expression index */305 const char *azColl[] = { "BINARY" };306 makeIndex(gDb, "u_expr", pParent, 1, aiIndexCols, azColl,307 SQLITE_IDXTYPE_APPDEF, OE_Abort, 0);308 309 const char *azParentCol[] = { "a" };310 int aiFrom[] = { 0 };311 FKey *pFKey = makeFKey(gDb, pChild, "parent", 1, azParentCol, aiFrom);312 313 Index *pIdx = NULL;314 int *aiCol = NULL;315 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);316 317 TEST_ASSERT_EQUAL_INT(1, rc);318 TEST_ASSERT_NULL(pIdx);319 aiCol = NULL; /* freed internally on failure */320 TEST_ASSERT_NOT_NULL(parse.zErrMsg);321 sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = NULL;322 323 freeFKey(gDb, pFKey);324 freeTable(gDb, pChild);325 freeTable(gDb, pParent);326}327 328/* Test 8: Explicit mapping fails when named parent columns do not exist in any usable index */329void test_sqlite3FkLocateIndex_explicit_nonexistent_cols_fail(void){330 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = gDb;331 332 const char *colsP[] = { "p", "q" };333 Table *pParent = makeTable(gDb, "parent", 2, colsP, -1);334 Table *pChild = makeTable(gDb, "child", 0, NULL, -1);335 336 /* Create a usable UNIQUE index, but on different columns than referenced */337 int aiIndexCols[] = { 0, 1 };338 const char *azColl[] = { "BINARY", "BINARY" };339 makeIndex(gDb, "u_p_q", pParent, 2, aiIndexCols, azColl,340 SQLITE_IDXTYPE_APPDEF, OE_Abort, 0);341 342 const char *azParentCol[] = { "x", "y" }; /* do not exist */343 int aiFrom[] = { 2, 3 };344 FKey *pFKey = makeFKey(gDb, pChild, "parent", 2, azParentCol, aiFrom);345 346 Index *pIdx = NULL;347 int *aiCol = NULL;348 int rc = sqlite3FkLocateIndex(&parse, pParent, pFKey, &pIdx, &aiCol);349 350 TEST_ASSERT_EQUAL_INT(1, rc);351 TEST_ASSERT_NULL(pIdx);352 aiCol = NULL; /* freed internally on failure */353 TEST_ASSERT_NOT_NULL(parse.zErrMsg);354 sqlite3DbFree(gDb, parse.zErrMsg); parse.zErrMsg = NULL;355 356 freeFKey(gDb, pFKey);357 freeTable(gDb, pChild);358 freeTable(gDb, pParent);359}360 361int main(void) {362 UNITY_BEGIN();363 RUN_TEST(test_sqlite3FkLocateIndex_singlecol_implicit_ipk);364 RUN_TEST(test_sqlite3FkLocateIndex_singlecol_explicit_ipk);365 RUN_TEST(test_sqlite3FkLocateIndex_composite_implicit_pk);366 RUN_TEST(test_sqlite3FkLocateIndex_explicit_unique_index_collation_ok);367 RUN_TEST(test_sqlite3FkLocateIndex_explicit_wrong_collation_fail_and_error);368 RUN_TEST(test_sqlite3FkLocateIndex_implicit_needs_pk_not_unique_idx_fail);369 RUN_TEST(test_sqlite3FkLocateIndex_explicit_expression_index_fail);370 RUN_TEST(test_sqlite3FkLocateIndex_explicit_nonexistent_cols_fail);371 return UNITY_END();372}