Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
tests_fkey_sqlite3FkActions.c226 linesDownload Raw Back to tests
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5 6/* Helpers */7 8static void exec_sql_or_fail(sqlite3 *db, const char *sql){9  char *zErr = 0;10  int rc = sqlite3_exec(db, sql, 0, 0, &zErr);11  if( rc!=SQLITE_OK ){12    /* We cannot use Unity asserts here because this helper may be used13       before we run any assertions in a given test context. */14    fprintf(stderr, "SQL error (%d): %s while executing: %s\n", rc, zErr ? zErr : "(null)", sql);15    sqlite3_free(zErr);16    TEST_FAIL_MESSAGE("sqlite3_exec failed");17  }18}19 20static int find_column_index(Table *pTab, const char *zName){21  int i;22  for(i=0; i<pTab->nCol; i++){23    const char *zCol = pTab->aCol[i].zCnName;24    if( zCol && sqlite3StrICmp(zCol, zName)==0 ) return i;25  }26  return -1;27}28 29static void init_parse_with_vdbe(Parse *pParse, sqlite3 *db){30  memset(pParse, 0, sizeof(Parse));31  pParse->db = db;32  pParse->pVdbe = sqlite3VdbeCreate(pParse);33  TEST_ASSERT_NOT_NULL_MESSAGE(pParse->pVdbe, "sqlite3VdbeCreate returned NULL");34}35 36static void destroy_parse_vdbe(Parse *pParse){37  if( pParse->pVdbe ){38    sqlite3VdbeDelete(pParse->pVdbe);39    pParse->pVdbe = 0;40  }41}42 43/* Unity fixtures */44void setUp(void) {45  /* No global setup required */46}47 48void tearDown(void) {49  /* No global teardown required */50}51 52/* Tests */53 54/* Foreign keys disabled: sqlite3FkActions must be a no-op and not dereference pTab */55void test_sqlite3FkActions_fk_disabled_noop(void){56  sqlite3 *db = 0;57  TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));58  exec_sql_or_fail(db, "PRAGMA foreign_keys=OFF"); /* ensure disabled */59 60  Parse parse;61  init_parse_with_vdbe(&parse, db);62 63  int startOps = sqlite3VdbeCurrentAddr(parse.pVdbe);64 65  /* pTab and others can be NULL or zero since function should early-out */66  sqlite3FkActions(&parse, NULL, NULL, 0, NULL, 0);67 68  int endOps = sqlite3VdbeCurrentAddr(parse.pVdbe);69  TEST_ASSERT_EQUAL_INT(startOps, endOps);70 71  destroy_parse_vdbe(&parse);72  sqlite3_close(db);73}74 75/* Foreign keys enabled, but no referencing FKs: should not emit code */76void test_sqlite3FkActions_enabled_no_referencing_fk_noop(void){77  sqlite3 *db = 0;78  TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));79  exec_sql_or_fail(db, "PRAGMA foreign_keys=ON");80  exec_sql_or_fail(db, "CREATE TABLE p(a INTEGER PRIMARY KEY, b)");81  /* No child tables referencing p */82 83  Table *pTab = sqlite3FindTable(db, "p", "main");84  TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Failed to find table 'p'");85 86  Parse parse;87  init_parse_with_vdbe(&parse, db);88 89  int startOps = sqlite3VdbeCurrentAddr(parse.pVdbe);90 91  /* Simulate DELETE (pChanges==NULL, aChange==NULL) */92  sqlite3FkActions(&parse, pTab, NULL, /*regOld*/1, NULL, /*bChngRowid*/0);93 94  int endOps = sqlite3VdbeCurrentAddr(parse.pVdbe);95  TEST_ASSERT_EQUAL_INT(startOps, endOps);96 97  destroy_parse_vdbe(&parse);98  sqlite3_close(db);99}100 101/* DELETE with ON DELETE CASCADE child FK should emit code */102void test_sqlite3FkActions_delete_cascade_emits_code(void){103  sqlite3 *db = 0;104  TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));105  exec_sql_or_fail(db, "PRAGMA foreign_keys=ON");106  exec_sql_or_fail(db, "CREATE TABLE p(a INTEGER PRIMARY KEY, b)");107  exec_sql_or_fail(db, "CREATE TABLE c(ca, cb, FOREIGN KEY(ca) REFERENCES p(a) ON DELETE CASCADE)");108 109  Table *pTab = sqlite3FindTable(db, "p", "main");110  TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Failed to find table 'p'");111 112  Parse parse;113  init_parse_with_vdbe(&parse, db);114 115  int startOps = sqlite3VdbeCurrentAddr(parse.pVdbe);116 117  /* DELETE path: pChanges==NULL, aChange==NULL */118  sqlite3FkActions(&parse, pTab, NULL, /*regOld*/1, NULL, /*bChngRowid*/0);119 120  int endOps = sqlite3VdbeCurrentAddr(parse.pVdbe);121  TEST_ASSERT_TRUE_MESSAGE(endOps > startOps, "Expected VDBE ops to be emitted for ON DELETE CASCADE");122 123  destroy_parse_vdbe(&parse);124  sqlite3_close(db);125}126 127/* UPDATE where only non-key columns change: no code should be emitted even with ON UPDATE CASCADE */128void test_sqlite3FkActions_update_nonkey_change_noop(void){129  sqlite3 *db = 0;130  TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));131  exec_sql_or_fail(db, "PRAGMA foreign_keys=ON");132  exec_sql_or_fail(db, "CREATE TABLE p(a INTEGER PRIMARY KEY, b, c)");133  exec_sql_or_fail(db, "CREATE TABLE d(dx, da, FOREIGN KEY(da) REFERENCES p(a) ON UPDATE CASCADE)");134 135  Table *pTab = sqlite3FindTable(db, "p", "main");136  TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Failed to find table 'p'");137 138  Parse parse;139  init_parse_with_vdbe(&parse, db);140 141  /* Build a trivial ExprList to indicate UPDATE context */142  Expr *pConst = sqlite3Expr(parse.db, TK_INTEGER, "0");143  ExprList *pChanges = sqlite3ExprListAppend(&parse, 0, pConst);144  TEST_ASSERT_NOT_NULL_MESSAGE(pChanges, "Failed to build ExprList for UPDATE simulation");145 146  /* Build aChange array: mark only non-key column 'b' as changed */147  int nCol = pTab->nCol;148  int *aChange = (int*)sqlite3DbMallocZero(db, sizeof(int)*nCol);149  TEST_ASSERT_NOT_NULL(aChange);150  for(int i=0; i<nCol; i++) aChange[i] = -1;151  int idxB = find_column_index(pTab, "b");152  TEST_ASSERT_TRUE_MESSAGE(idxB >= 0, "Column 'b' not found");153  aChange[idxB] = 0; /* changed */154  int idxA = find_column_index(pTab, "a");155  TEST_ASSERT_TRUE_MESSAGE(idxA >= 0, "Column 'a' not found");156  /* ensure key column is marked unmodified */157  aChange[idxA] = -1;158 159  int startOps = sqlite3VdbeCurrentAddr(parse.pVdbe);160 161  sqlite3FkActions(&parse, pTab, pChanges, /*regOld*/1, aChange, /*bChngRowid*/0);162 163  int endOps = sqlite3VdbeCurrentAddr(parse.pVdbe);164  TEST_ASSERT_EQUAL_INT_MESSAGE(startOps, endOps, "No ops expected when parent key not modified");165 166  sqlite3ExprListDelete(db, pChanges);167  sqlite3DbFree(db, aChange);168  destroy_parse_vdbe(&parse);169  sqlite3_close(db);170}171 172/* UPDATE where parent key column changes and ON UPDATE CASCADE exists: should emit code */173void test_sqlite3FkActions_update_key_change_emits_code(void){174  sqlite3 *db = 0;175  TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));176  exec_sql_or_fail(db, "PRAGMA foreign_keys=ON");177  exec_sql_or_fail(db, "CREATE TABLE p(a INTEGER PRIMARY KEY, b)");178  exec_sql_or_fail(db, "CREATE TABLE e(ex, ea, FOREIGN KEY(ea) REFERENCES p(a) ON UPDATE CASCADE)");179 180  Table *pTab = sqlite3FindTable(db, "p", "main");181  TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Failed to find table 'p'");182 183  Parse parse;184  init_parse_with_vdbe(&parse, db);185 186  /* Build a minimal ExprList to indicate UPDATE context */187  Expr *pConst = sqlite3Expr(parse.db, TK_INTEGER, "1");188  ExprList *pChanges = sqlite3ExprListAppend(&parse, 0, pConst);189  TEST_ASSERT_NOT_NULL_MESSAGE(pChanges, "Failed to build ExprList for UPDATE simulation");190 191  /* Set aChange: mark key column 'a' as changed */192  int nCol = pTab->nCol;193  int *aChange = (int*)sqlite3DbMallocZero(db, sizeof(int)*nCol);194  TEST_ASSERT_NOT_NULL(aChange);195  for(int i=0; i<nCol; i++) aChange[i] = -1;196  int idxA = find_column_index(pTab, "a");197  TEST_ASSERT_TRUE_MESSAGE(idxA >= 0, "Column 'a' not found");198  aChange[idxA] = 0; /* changed */199  int idxB = find_column_index(pTab, "b");200  if( idxB >= 0 ){201    aChange[idxB] = -1; /* unchanged */202  }203 204  int startOps = sqlite3VdbeCurrentAddr(parse.pVdbe);205 206  sqlite3FkActions(&parse, pTab, pChanges, /*regOld*/1, aChange, /*bChngRowid*/0);207 208  int endOps = sqlite3VdbeCurrentAddr(parse.pVdbe);209  TEST_ASSERT_TRUE_MESSAGE(endOps > startOps, "Expected VDBE ops to be emitted for ON UPDATE CASCADE when key changed");210 211  sqlite3ExprListDelete(db, pChanges);212  sqlite3DbFree(db, aChange);213  destroy_parse_vdbe(&parse);214  sqlite3_close(db);215}216 217/* Unity main */218int main(void) {219  UNITY_BEGIN();220  RUN_TEST(test_sqlite3FkActions_fk_disabled_noop);221  RUN_TEST(test_sqlite3FkActions_enabled_no_referencing_fk_noop);222  RUN_TEST(test_sqlite3FkActions_delete_cascade_emits_code);223  RUN_TEST(test_sqlite3FkActions_update_nonkey_change_noop);224  RUN_TEST(test_sqlite3FkActions_update_key_change_emits_code);225  return UNITY_END();226}