AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5 6/* Forward decl of wrapper */7void test_fkScanChildren(Parse *pParse, SrcList *pSrc, Table *pTab, Index *pIdx, FKey *pFKey, int *aiCol, int regData, int nIncr);8 9static sqlite3 *gDb = NULL;10 11void setUp(void) {12 /* Fresh in-memory DB for each test */13 int rc = sqlite3_open(":memory:", &gDb);14 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);15 /* Enable foreign keys to ensure FKey structures are built as usual */16 rc = sqlite3_exec(gDb, "PRAGMA foreign_keys=ON;", 0, 0, 0);17 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);18}19 20void tearDown(void) {21 if (gDb) {22 sqlite3_close(gDb);23 gDb = NULL;24 }25}26 27/* Helper: create a simple SrcList for a single table */28static SrcList* makeSingleSrc(sqlite3 *db, Parse *pParse, Table *pTab){29 SrcList *p = (SrcList*)sqlite3DbMallocZero(db, sizeof(SrcList));30 TEST_ASSERT_NOT_NULL(p);31 p->nSrc = 1;32 p->a[0].pTab = pTab;33 p->a[0].zName = pTab->zName;34 p->a[0].pSchema = pTab->pSchema;35 /* Assign a fresh cursor id */36 p->a[0].iCursor = pParse->nTab++;37 return p;38}39 40/* Helper: find a unique index on parent that has nKeyCol columns */41static Index* findCoveringIndexForNCols(Table *pTab, int nCol){42 Index *pIdx;43 for(pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext){44 if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) ){45 return pIdx;46 }47 }48 return NULL;49}50 51/* Helper: scan Vdbe ops for a specific opcode in [start,end) range.52 Returns count of matches and can optionally return first/last pointers. */53static int countOpcodeInRange(Vdbe *v, int start, int end, int opcode, const VdbeOp **pFirst, const VdbeOp **pLast){54 int count = 0;55 const VdbeOp *first = NULL;56 const VdbeOp *last = NULL;57 for(int i = start; i < end; i++){58 VdbeOp *op = &v->aOp[i];59 if( op->opcode == opcode ){60 if( first==NULL ) first = op;61 last = op;62 count++;63 }64 }65 if(pFirst) *pFirst = first;66 if(pLast) *pLast = last;67 return count;68}69 70/* Helper: setup Parse and Vdbe and return pre-opcount */71static Vdbe* prepareParseAndVdbe(sqlite3 *db, Parse *pParse, int *pStartOp){72 memset(pParse, 0, sizeof(Parse));73 pParse->db = db;74 Vdbe *v = sqlite3GetVdbe(pParse);75 TEST_ASSERT_NOT_NULL(v);76 if(pStartOp) *pStartOp = v->nOp;77 return v;78}79 80/* Helper: finalize Parse Vdbe after a generation step */81static void cleanupParseVdbe(Parse *pParse){82 if( pParse->pVdbe ){83 sqlite3VdbeDelete(pParse->pVdbe);84 pParse->pVdbe = NULL;85 }86}87 88/* Test 1: Immediate FK, delete-like path (nIncr=+1), child != parent */89void test_fkScanChildren_immediate_delete(void){90 int rc;91 rc = sqlite3_exec(gDb,92 "CREATE TABLE parent(a INTEGER PRIMARY KEY);"93 "CREATE TABLE child(x INTEGER, FOREIGN KEY(x) REFERENCES parent(a));",94 0,0,0);95 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);96 97 Table *pParent = sqlite3FindTable(gDb, "parent", "main");98 Table *pChild = sqlite3FindTable(gDb, "child", "main");99 TEST_ASSERT_NOT_NULL(pParent);100 TEST_ASSERT_NOT_NULL(pChild);101 FKey *pFKey = pChild->pFKey;102 TEST_ASSERT_NOT_NULL(pFKey);103 TEST_ASSERT_EQUAL_INT(0, pFKey->isDeferred);104 105 Parse sParse;106 int startOp = 0;107 Vdbe *v = prepareParseAndVdbe(gDb, &sParse, &startOp);108 109 /* Build SrcList for child */110 SrcList *pSrc = makeSingleSrc(gDb, &sParse, pChild);111 112 /* Call target wrapper: pIdx==NULL case */113 test_fkScanChildren(&sParse, pSrc, pParent, NULL, pFKey, NULL, /*regData*/1, /*nIncr*/+1);114 115 /* Inspect generated bytecode */116 int endOp = v->nOp;117 TEST_ASSERT_TRUE(endOp > startOp);118 119 const VdbeOp *pFkCtr = NULL;120 int nCtr = countOpcodeInRange(v, startOp, endOp, OP_FkCounter, &pFkCtr, NULL);121 TEST_ASSERT_EQUAL_INT(1, nCtr);122 TEST_ASSERT_NOT_NULL(pFkCtr);123 TEST_ASSERT_EQUAL_INT(pFKey->isDeferred, pFkCtr->p1);124 TEST_ASSERT_EQUAL_INT(+1, pFkCtr->p2);125 126 int nIfZero = countOpcodeInRange(v, startOp, endOp, OP_FkIfZero, NULL, NULL);127 TEST_ASSERT_EQUAL_INT(0, nIfZero);128 129 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);130 131 sqlite3SrcListDelete(gDb, pSrc);132 cleanupParseVdbe(&sParse);133}134 135/* Test 2: Immediate FK, insert-like path (nIncr=-1) -> OP_FkIfZero present */136void test_fkScanChildren_immediate_insert(void){137 int rc;138 rc = sqlite3_exec(gDb,139 "CREATE TABLE parent(a INTEGER PRIMARY KEY);"140 "CREATE TABLE child(x INTEGER, FOREIGN KEY(x) REFERENCES parent(a));",141 0,0,0);142 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);143 144 Table *pParent = sqlite3FindTable(gDb, "parent", "main");145 Table *pChild = sqlite3FindTable(gDb, "child", "main");146 TEST_ASSERT_NOT_NULL(pParent);147 TEST_ASSERT_NOT_NULL(pChild);148 FKey *pFKey = pChild->pFKey;149 TEST_ASSERT_NOT_NULL(pFKey);150 TEST_ASSERT_EQUAL_INT(0, pFKey->isDeferred);151 152 Parse sParse;153 int startOp = 0;154 Vdbe *v = prepareParseAndVdbe(gDb, &sParse, &startOp);155 156 SrcList *pSrc = makeSingleSrc(gDb, &sParse, pChild);157 158 test_fkScanChildren(&sParse, pSrc, pParent, NULL, pFKey, NULL, /*regData*/1, /*nIncr*/-1);159 160 int endOp = v->nOp;161 TEST_ASSERT_TRUE(endOp > startOp);162 163 const VdbeOp *pFkCtr = NULL;164 int nCtr = countOpcodeInRange(v, startOp, endOp, OP_FkCounter, &pFkCtr, NULL);165 TEST_ASSERT_EQUAL_INT(1, nCtr);166 TEST_ASSERT_NOT_NULL(pFkCtr);167 TEST_ASSERT_EQUAL_INT(pFKey->isDeferred, pFkCtr->p1);168 TEST_ASSERT_EQUAL_INT(-1, pFkCtr->p2);169 170 const VdbeOp *pIfZero = NULL;171 int nIfZero = countOpcodeInRange(v, startOp, endOp, OP_FkIfZero, &pIfZero, NULL);172 TEST_ASSERT_EQUAL_INT(1, nIfZero);173 TEST_ASSERT_NOT_NULL(pIfZero);174 TEST_ASSERT_EQUAL_INT(pFKey->isDeferred, pIfZero->p1);175 176 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);177 178 sqlite3SrcListDelete(gDb, pSrc);179 cleanupParseVdbe(&sParse);180}181 182/* Test 3: Deferred FK (DEFERRABLE INITIALLY DEFERRED) */183void test_fkScanChildren_deferred_paths(void){184 int rc;185 rc = sqlite3_exec(gDb,186 "CREATE TABLE dparent(a INTEGER PRIMARY KEY);"187 "CREATE TABLE dchild(x INTEGER, FOREIGN KEY(x) REFERENCES dparent(a) DEFERRABLE INITIALLY DEFERRED);",188 0,0,0);189 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);190 191 Table *pParent = sqlite3FindTable(gDb, "dparent", "main");192 Table *pChild = sqlite3FindTable(gDb, "dchild", "main");193 TEST_ASSERT_NOT_NULL(pParent);194 TEST_ASSERT_NOT_NULL(pChild);195 FKey *pFKey = pChild->pFKey;196 TEST_ASSERT_NOT_NULL(pFKey);197 TEST_ASSERT_EQUAL_INT(1, pFKey->isDeferred);198 199 /* nIncr=+1 */200 {201 Parse sParse;202 int startOp = 0;203 Vdbe *v = prepareParseAndVdbe(gDb, &sParse, &startOp);204 205 SrcList *pSrc = makeSingleSrc(gDb, &sParse, pChild);206 207 test_fkScanChildren(&sParse, pSrc, pParent, NULL, pFKey, NULL, 1, +1);208 209 int endOp = v->nOp;210 TEST_ASSERT_TRUE(endOp > startOp);211 212 const VdbeOp *pFkCtr = NULL;213 int nCtr = countOpcodeInRange(v, startOp, endOp, OP_FkCounter, &pFkCtr, NULL);214 TEST_ASSERT_EQUAL_INT(1, nCtr);215 TEST_ASSERT_NOT_NULL(pFkCtr);216 TEST_ASSERT_EQUAL_INT(1, pFkCtr->p1);217 TEST_ASSERT_EQUAL_INT(+1, pFkCtr->p2);218 219 int nIfZero = countOpcodeInRange(v, startOp, endOp, OP_FkIfZero, NULL, NULL);220 TEST_ASSERT_EQUAL_INT(0, nIfZero);221 222 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);223 224 sqlite3SrcListDelete(gDb, pSrc);225 cleanupParseVdbe(&sParse);226 }227 228 /* nIncr=-1 */229 {230 Parse sParse;231 int startOp = 0;232 Vdbe *v = prepareParseAndVdbe(gDb, &sParse, &startOp);233 234 SrcList *pSrc = makeSingleSrc(gDb, &sParse, pChild);235 236 test_fkScanChildren(&sParse, pSrc, pParent, NULL, pFKey, NULL, 1, -1);237 238 int endOp = v->nOp;239 TEST_ASSERT_TRUE(endOp > startOp);240 241 const VdbeOp *pFkCtr = NULL;242 int nCtr = countOpcodeInRange(v, startOp, endOp, OP_FkCounter, &pFkCtr, NULL);243 TEST_ASSERT_EQUAL_INT(1, nCtr);244 TEST_ASSERT_NOT_NULL(pFkCtr);245 TEST_ASSERT_EQUAL_INT(1, pFkCtr->p1);246 TEST_ASSERT_EQUAL_INT(-1, pFkCtr->p2);247 248 const VdbeOp *pIfZero = NULL;249 int nIfZero = countOpcodeInRange(v, startOp, endOp, OP_FkIfZero, &pIfZero, NULL);250 TEST_ASSERT_EQUAL_INT(1, nIfZero);251 TEST_ASSERT_NOT_NULL(pIfZero);252 TEST_ASSERT_EQUAL_INT(1, pIfZero->p1);253 254 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);255 256 sqlite3SrcListDelete(gDb, pSrc);257 cleanupParseVdbe(&sParse);258 }259}260 261/* Test 4: Self-referential FK (parent == child), nIncr>0 triggers self-scan exclusion terms */262void test_fkScanChildren_self_reference_rowid(void){263 int rc;264 rc = sqlite3_exec(gDb,265 "CREATE TABLE self(a INTEGER PRIMARY KEY, parent INTEGER, FOREIGN KEY(parent) REFERENCES self(a));",266 0,0,0);267 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);268 269 Table *pSelf = sqlite3FindTable(gDb, "self", "main");270 TEST_ASSERT_NOT_NULL(pSelf);271 FKey *pFKey = pSelf->pFKey;272 TEST_ASSERT_NOT_NULL(pFKey);273 /* Default immediate */274 TEST_ASSERT_EQUAL_INT(0, pFKey->isDeferred);275 276 Parse sParse;277 int startOp = 0;278 Vdbe *v = prepareParseAndVdbe(gDb, &sParse, &startOp);279 280 SrcList *pSrc = makeSingleSrc(gDb, &sParse, pSelf);281 282 /* pIdx==NULL is valid for single-column FK to rowid parent */283 test_fkScanChildren(&sParse, pSrc, pSelf, NULL, pFKey, NULL, 1, +1);284 285 int endOp = v->nOp;286 TEST_ASSERT_TRUE(endOp > startOp);287 288 const VdbeOp *pFkCtr = NULL;289 int nCtr = countOpcodeInRange(v, startOp, endOp, OP_FkCounter, &pFkCtr, NULL);290 TEST_ASSERT_EQUAL_INT(1, nCtr);291 TEST_ASSERT_NOT_NULL(pFkCtr);292 TEST_ASSERT_EQUAL_INT(0, pFkCtr->p1);293 TEST_ASSERT_EQUAL_INT(+1, pFkCtr->p2);294 295 /* No OP_FkIfZero for nIncr>0 */296 int nIfZero = countOpcodeInRange(v, startOp, endOp, OP_FkIfZero, NULL, NULL);297 TEST_ASSERT_EQUAL_INT(0, nIfZero);298 299 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);300 301 sqlite3SrcListDelete(gDb, pSrc);302 cleanupParseVdbe(&sParse);303}304 305/* Test 5: Composite FK with covering parent index (pIdx non-NULL) and aiCol mapping */306void test_fkScanChildren_composite_with_index(void){307 int rc;308 rc = sqlite3_exec(gDb,309 "CREATE TABLE p3(a INTEGER, b INTEGER, PRIMARY KEY(a,b));"310 "CREATE TABLE c3(x INTEGER, y INTEGER, FOREIGN KEY(x,y) REFERENCES p3(a,b));",311 0,0,0);312 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);313 314 Table *pParent = sqlite3FindTable(gDb, "p3", "main");315 Table *pChild = sqlite3FindTable(gDb, "c3", "main");316 TEST_ASSERT_NOT_NULL(pParent);317 TEST_ASSERT_NOT_NULL(pChild);318 319 /* Get FK from child */320 FKey *pFKey = pChild->pFKey;321 TEST_ASSERT_NOT_NULL(pFKey);322 TEST_ASSERT_TRUE(pFKey->nCol >= 2);323 324 /* Find a covering index on parent */325 Index *pIdx = findCoveringIndexForNCols(pParent, pFKey->nCol);326 TEST_ASSERT_NOT_NULL(pIdx);327 328 /* Build aiCol mapping from parent-index order to child columns (iFrom) */329 int n = pFKey->nCol;330 int *aiCol = (int*)sqlite3_malloc(n * sizeof(int));331 TEST_ASSERT_NOT_NULL(aiCol);332 for(int i=0; i<n; i++){333 aiCol[i] = pFKey->aCol[i].iFrom;334 }335 336 Parse sParse;337 int startOp = 0;338 Vdbe *v = prepareParseAndVdbe(gDb, &sParse, &startOp);339 340 SrcList *pSrc = makeSingleSrc(gDb, &sParse, pChild);341 342 test_fkScanChildren(&sParse, pSrc, pParent, pIdx, pFKey, aiCol, 1, +1);343 344 int endOp = v->nOp;345 TEST_ASSERT_TRUE(endOp > startOp);346 347 const VdbeOp *pFkCtr = NULL;348 int nCtr = countOpcodeInRange(v, startOp, endOp, OP_FkCounter, &pFkCtr, NULL);349 TEST_ASSERT_EQUAL_INT(1, nCtr);350 TEST_ASSERT_NOT_NULL(pFkCtr);351 TEST_ASSERT_EQUAL_INT(pFKey->isDeferred, pFkCtr->p1);352 TEST_ASSERT_EQUAL_INT(+1, pFkCtr->p2);353 354 TEST_ASSERT_EQUAL_INT(0, sParse.nErr);355 356 sqlite3_free(aiCol);357 sqlite3SrcListDelete(gDb, pSrc);358 cleanupParseVdbe(&sParse);359}360 361int main(void) {362 UNITY_BEGIN();363 RUN_TEST(test_fkScanChildren_immediate_delete);364 RUN_TEST(test_fkScanChildren_immediate_insert);365 RUN_TEST(test_fkScanChildren_deferred_paths);366 RUN_TEST(test_fkScanChildren_self_reference_rowid);367 RUN_TEST(test_fkScanChildren_composite_with_index);368 return UNITY_END();369}