Team Ai
Modelpublic

AryaWu/sqlite

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