AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5#include <stdio.h>6 7/* Helper: create a SrcList for a given table in "main" schema */8static SrcList* makeSrcListForTable(sqlite3 *db, Table *pTab){9 SrcList *p = (SrcList*)sqlite3DbMallocZero(db, sizeof(SrcList));10 TEST_ASSERT_NOT_NULL_MESSAGE(p, "Failed to allocate SrcList");11 p->nSrc = 1;12 p->nAlloc = 1;13 p->a[0].zName = sqlite3DbStrDup(db, pTab->zName);14 p->a[0].zDatabase = sqlite3DbStrDup(db, "main");15 p->a[0].pTab = pTab;16 p->a[0].pSchema = pTab->pSchema;17 return p;18}19 20/* Helper: count OP_FkIfZero instructions with a given P1 value */21static int countFkIfZeroOps(Vdbe *v, int p1){22 if( v==NULL ) return 0;23 int cnt = 0;24 int nOp = v->nOp;25 VdbeOp *aOp = v->aOp;26 for(int i=0; i<nOp; i++){27 if( aOp[i].opcode==OP_FkIfZero && aOp[i].p1==p1 ){28 cnt++;29 }30 }31 return cnt;32}33 34/* Helper: open an in-memory db and create schema, return db via out param */35static sqlite3* openDb(void){36 sqlite3 *db = 0;37 int rc = sqlite3_open(":memory:", &db);38 TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, "sqlite3_open failed");39 return db;40}41 42/* Helper: exec SQL and assert OK */43static void execSQL(sqlite3 *db, const char *zSql){44 char *zErr = 0;45 int rc = sqlite3_exec(db, zSql, 0, 0, &zErr);46 if( rc!=SQLITE_OK ){47 char msg[512];48 snprintf(msg, sizeof(msg), "SQL error (%d): %s while executing: %s", rc, zErr?zErr:"(null)", zSql);49 sqlite3_free(zErr);50 TEST_FAIL_MESSAGE(msg);51 }52}53 54/* Helper: find a Table* in main schema */55static Table* findTable(sqlite3 *db, const char *zName){56 Table *pTab = sqlite3FindTable(db, zName, "main");57 TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "sqlite3FindTable returned NULL");58 return pTab;59}60 61/* Helper: cleanup VDBE if allocated */62static void destroyVdbe(Parse *pParse){63 if( pParse && pParse->pVdbe ){64 sqlite3VdbeDelete(pParse->pVdbe);65 pParse->pVdbe = 0;66 }67}68 69/* Unity fixtures */70void setUp(void) {71 /* No global setup needed */72}73void tearDown(void) {74 /* No global teardown needed */75}76 77/* Test 1: No FKs at all; with foreign_keys ON -> no VDBE generated */78void test_sqlite3FkDropTable_no_fk_no_vdbe(void){79 sqlite3 *db = openDb();80 execSQL(db, "CREATE TABLE t1(a);");81 82 /* Enable FK enforcement flag */83 db->flags |= SQLITE_ForeignKeys;84 85 Table *pTab = findTable(db, "t1");86 Parse parse;87 memset(&parse, 0, sizeof(parse));88 parse.db = db;89 90 SrcList *pName = makeSrcListForTable(db, pTab);91 92 sqlite3FkDropTable(&parse, pName, pTab);93 94 TEST_ASSERT_NULL(parse.pVdbe); /* No code generated */95 TEST_ASSERT_EQUAL_INT(0, parse.disableTriggers);96 97 sqlite3SrcListDelete(db, pName);98 sqlite3_close(db);99}100 101/* Test 2: Parent table referenced by a child; FK ON -> VDBE with post-delete immediate-check, no initial skip-check */102void test_sqlite3FkDropTable_parent_generates_delete_and_immediate_check(void){103 sqlite3 *db = openDb();104 execSQL(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");105 execSQL(db, "CREATE TABLE c(x, pid REFERENCES p(id));");106 107 db->flags |= SQLITE_ForeignKeys;108 109 Table *pTab = findTable(db, "p"); /* Parent table */110 Parse parse;111 memset(&parse, 0, sizeof(parse));112 parse.db = db;113 114 SrcList *pName = makeSrcListForTable(db, pTab);115 116 sqlite3FkDropTable(&parse, pName, pTab);117 118 TEST_ASSERT_NOT_NULL(parse.pVdbe);119 /* Should NOT have initial skip-check (P1==1) */120 TEST_ASSERT_EQUAL_INT(0, countFkIfZeroOps(parse.pVdbe, 1));121 /* Should have post-delete immediate-check (P1==0) if SQLITE_DeferFKs not set */122 TEST_ASSERT_TRUE(countFkIfZeroOps(parse.pVdbe, 0) >= 1);123 TEST_ASSERT_EQUAL_INT(0, parse.disableTriggers);124 125 destroyVdbe(&parse);126 sqlite3SrcListDelete(db, pName);127 sqlite3_close(db);128}129 130/* Test 3: Child table with DEFERRABLE INITIALLY DEFERRED; FK ON -> initial skip-check and post-delete immediate-check */131void test_sqlite3FkDropTable_child_deferred_has_skip_and_immediate_check(void){132 sqlite3 *db = openDb();133 execSQL(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");134 execSQL(db, "CREATE TABLE cd(x, pid REFERENCES p(id) DEFERRABLE INITIALLY DEFERRED);");135 136 db->flags |= SQLITE_ForeignKeys;137 db->flags &= ~SQLITE_DeferFKs; /* Ensure not set so immediate-check is added */138 139 Table *pTab = findTable(db, "cd"); /* Child table with deferred fk */140 Parse parse;141 memset(&parse, 0, sizeof(parse));142 parse.db = db;143 144 SrcList *pName = makeSrcListForTable(db, pTab);145 146 sqlite3FkDropTable(&parse, pName, pTab);147 148 TEST_ASSERT_NOT_NULL(parse.pVdbe);149 /* Should have initial skip-check (P1==1) */150 TEST_ASSERT_TRUE(countFkIfZeroOps(parse.pVdbe, 1) >= 1);151 /* Should have post-delete immediate-check (P1==0) */152 TEST_ASSERT_TRUE(countFkIfZeroOps(parse.pVdbe, 0) >= 1);153 TEST_ASSERT_EQUAL_INT(0, parse.disableTriggers);154 155 destroyVdbe(&parse);156 sqlite3SrcListDelete(db, pName);157 sqlite3_close(db);158}159 160/* Test 4: Child table with immediate (non-deferred) FK; FK ON and no SQLITE_DeferFKs -> no code generated */161void test_sqlite3FkDropTable_child_immediate_no_code(void){162 sqlite3 *db = openDb();163 execSQL(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");164 execSQL(db, "CREATE TABLE ci(x, pid REFERENCES p(id));"); /* immediate by default */165 166 db->flags |= SQLITE_ForeignKeys;167 db->flags &= ~SQLITE_DeferFKs;168 169 Table *pTab = findTable(db, "ci"); /* Child immediate FK */170 Parse parse;171 memset(&parse, 0, sizeof(parse));172 parse.db = db;173 174 SrcList *pName = makeSrcListForTable(db, pTab);175 176 sqlite3FkDropTable(&parse, pName, pTab);177 178 TEST_ASSERT_NULL(parse.pVdbe);179 TEST_ASSERT_EQUAL_INT(0, parse.disableTriggers);180 181 sqlite3SrcListDelete(db, pName);182 sqlite3_close(db);183}184 185/* Test 5: Child with immediate FK but SQLITE_DeferFKs set -> initial skip-check generated */186void test_sqlite3FkDropTable_child_immediate_with_DeferFKs_generates_skip(void){187 sqlite3 *db = openDb();188 execSQL(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");189 execSQL(db, "CREATE TABLE ci2(x, pid REFERENCES p(id));"); /* immediate */190 191 db->flags |= SQLITE_ForeignKeys;192 db->flags |= SQLITE_DeferFKs;193 194 Table *pTab = findTable(db, "ci2");195 Parse parse;196 memset(&parse, 0, sizeof(parse));197 parse.db = db;198 199 SrcList *pName = makeSrcListForTable(db, pTab);200 201 sqlite3FkDropTable(&parse, pName, pTab);202 203 TEST_ASSERT_NOT_NULL(parse.pVdbe);204 TEST_ASSERT_TRUE(countFkIfZeroOps(parse.pVdbe, 1) >= 1);205 /* Immediate-check block is skipped when SQLITE_DeferFKs is set; we don't assert its absence */206 TEST_ASSERT_EQUAL_INT(0, parse.disableTriggers);207 208 destroyVdbe(&parse);209 sqlite3SrcListDelete(db, pName);210 sqlite3_close(db);211}212 213/* Test 6: Foreign keys OFF even for parent -> no VDBE generated */214void test_sqlite3FkDropTable_fk_off_no_code_even_parent(void){215 sqlite3 *db = openDb();216 execSQL(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");217 execSQL(db, "CREATE TABLE c(x, pid REFERENCES p(id));");218 219 db->flags &= ~SQLITE_ForeignKeys; /* FK enforcement off */220 221 Table *pTab = findTable(db, "p"); /* Parent table */222 Parse parse;223 memset(&parse, 0, sizeof(parse));224 parse.db = db;225 226 SrcList *pName = makeSrcListForTable(db, pTab);227 228 sqlite3FkDropTable(&parse, pName, pTab);229 230 TEST_ASSERT_NULL(parse.pVdbe);231 TEST_ASSERT_EQUAL_INT(0, parse.disableTriggers);232 233 sqlite3SrcListDelete(db, pName);234 sqlite3_close(db);235}236 237int main(void){238 UNITY_BEGIN();239 RUN_TEST(test_sqlite3FkDropTable_no_fk_no_vdbe);240 RUN_TEST(test_sqlite3FkDropTable_parent_generates_delete_and_immediate_check);241 RUN_TEST(test_sqlite3FkDropTable_child_deferred_has_skip_and_immediate_check);242 RUN_TEST(test_sqlite3FkDropTable_child_immediate_no_code);243 RUN_TEST(test_sqlite3FkDropTable_child_immediate_with_DeferFKs_generates_skip);244 RUN_TEST(test_sqlite3FkDropTable_fk_off_no_code_even_parent);245 return UNITY_END();246}