AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4 5/* Unity setup/teardown */6void setUp(void) {7 /* No global setup needed */8}9void tearDown(void) {10 /* No global teardown needed */11}12 13/* Helper: Execute SQL and assert success */14static void exec_sql_ok(sqlite3 *db, const char *zSql){15 char *zErr = 0;16 int rc = sqlite3_exec(db, zSql, 0, 0, &zErr);17 if( rc!=SQLITE_OK ){18 /* Provide better error message if available */19 const char *msg = zErr ? zErr : "sqlite3_exec failed";20 TEST_FAIL_MESSAGE(msg);21 }22 sqlite3_free(zErr);23}24 25/* Helper: Find database index by schema name (e.g., "main", "temp", "aux") */26static int get_db_index(sqlite3 *db, const char *zDb){27 int i;28 for(i=0; i<db->nDb; i++){29 if( db->aDb[i].zDbSName && 0==strcmp(db->aDb[i].zDbSName, zDb) ){30 return i;31 }32 }33 return -1;34}35 36/* Helper: Find a Table* by name and schema */37static Table* find_table(sqlite3 *db, const char *zDb, const char *zTab){38 Table *pTab = sqlite3FindTable(db, zTab, zDb);39 return pTab;40}41 42/* Helper: For all FKs of a table, populate apTrigger[0] and [1] with allocated Trigger objects */43static int populate_fk_triggers(sqlite3 *db, const char *zDb, const char *zTab){44 Table *pTab = find_table(db, zDb, zTab);45 TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Table not found in schema");46 int nSet = 0;47 for(FKey *pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){48 /* Allocate Trigger and minimal TriggerStep so fkTriggerDelete() can safely delete sub-components */49 Trigger *t0 = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));50 Trigger *t1 = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));51 TEST_ASSERT_NOT_NULL(t0);52 TEST_ASSERT_NOT_NULL(t1);53 TriggerStep *s0 = (TriggerStep*)sqlite3DbMallocZero(db, sizeof(TriggerStep));54 TriggerStep *s1 = (TriggerStep*)sqlite3DbMallocZero(db, sizeof(TriggerStep));55 TEST_ASSERT_NOT_NULL(s0);56 TEST_ASSERT_NOT_NULL(s1);57 58 t0->step_list = s0;59 t1->step_list = s1;60 61 pFKey->apTrigger[0] = t0;62 pFKey->apTrigger[1] = t1;63 nSet++;64 }65 return nSet;66}67 68/* Helper: Assert that for all FKs of a table, apTrigger[*] are NULL or non-NULL according to expectNull */69static void assert_fk_triggers_state(sqlite3 *db, const char *zDb, const char *zTab, int expectNull){70 Table *pTab = find_table(db, zDb, zTab);71 TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Table not found in schema");72 int nFk = 0;73 for(FKey *pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){74 nFk++;75 if( expectNull ){76 TEST_ASSERT_NULL(pFKey->apTrigger[0]);77 TEST_ASSERT_NULL(pFKey->apTrigger[1]);78 }else{79 TEST_ASSERT_NOT_NULL(pFKey->apTrigger[0]);80 TEST_ASSERT_NOT_NULL(pFKey->apTrigger[1]);81 }82 }83 /* Ensure table actually has FKs when checking non-NULL state */84 if( !expectNull ){85 TEST_ASSERT_MESSAGE(nFk>0, "Expected at least one foreign key on table");86 }87}88 89/* Test 1: Clears triggers for a single FK on main schema */90static void test_sqlite3FkClearTriggerCache_clears_triggers_for_main(void){91 sqlite3 *db = 0;92 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));93 94 exec_sql_ok(db, "PRAGMA foreign_keys=ON;");95 exec_sql_ok(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");96 exec_sql_ok(db, "CREATE TABLE c(id INT, pid INT, FOREIGN KEY(pid) REFERENCES p(id) ON DELETE CASCADE ON UPDATE CASCADE);");97 98 int nSet = populate_fk_triggers(db, "main", "c");99 TEST_ASSERT_MESSAGE(nSet>0, "No foreign keys found on table c to populate triggers");100 assert_fk_triggers_state(db, "main", "c", 0);101 102 sqlite3FkClearTriggerCache(db, 0); /* 0 is always 'main' */103 assert_fk_triggers_state(db, "main", "c", 1);104 105 sqlite3_close(db);106}107 108/* Test 2: Handles multiple tables and multiple FKs in main schema */109static void test_sqlite3FkClearTriggerCache_multiple_tables_and_fks(void){110 sqlite3 *db = 0;111 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));112 113 exec_sql_ok(db, "PRAGMA foreign_keys=ON;");114 exec_sql_ok(db, "CREATE TABLE p1(id INTEGER PRIMARY KEY);");115 exec_sql_ok(db, "CREATE TABLE p2(id INTEGER PRIMARY KEY);");116 exec_sql_ok(db, "CREATE TABLE c1(x INT, r1 INT, FOREIGN KEY(r1) REFERENCES p1(id));");117 /* c2 has two FKs */118 exec_sql_ok(db, "CREATE TABLE c2(a INT, r1 INT, r2 INT, "119 "FOREIGN KEY(r1) REFERENCES p1(id) ON DELETE CASCADE, "120 "FOREIGN KEY(r2) REFERENCES p2(id) ON UPDATE SET NULL);");121 122 int nSet1 = populate_fk_triggers(db, "main", "c1");123 int nSet2 = populate_fk_triggers(db, "main", "c2");124 TEST_ASSERT_MESSAGE(nSet1>0, "Expected FK on c1");125 TEST_ASSERT_MESSAGE(nSet2>=2, "Expected at least 2 FKs on c2");126 127 assert_fk_triggers_state(db, "main", "c1", 0);128 assert_fk_triggers_state(db, "main", "c2", 0);129 130 sqlite3FkClearTriggerCache(db, 0);131 132 assert_fk_triggers_state(db, "main", "c1", 1);133 assert_fk_triggers_state(db, "main", "c2", 1);134 135 sqlite3_close(db);136}137 138/* Test 3: Only clears triggers in specified database (scope by iDb) */139static void test_sqlite3FkClearTriggerCache_scoped_to_specified_db(void){140 sqlite3 *db = 0;141 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));142 143 exec_sql_ok(db, "PRAGMA foreign_keys=ON;");144 /* Main schema tables */145 exec_sql_ok(db, "CREATE TABLE p_main(id INTEGER PRIMARY KEY);");146 exec_sql_ok(db, "CREATE TABLE c_main(r INT, FOREIGN KEY(r) REFERENCES p_main(id));");147 /* Attach an auxiliary in-memory database and create FK there as well */148 exec_sql_ok(db, "ATTACH ':memory:' AS aux;");149 exec_sql_ok(db, "CREATE TABLE aux.p_aux(id INTEGER PRIMARY KEY);");150 exec_sql_ok(db, "CREATE TABLE aux.c_aux(r INT, FOREIGN KEY(r) REFERENCES p_aux(id) ON DELETE CASCADE);");151 152 int nSetMain = populate_fk_triggers(db, "main", "c_main");153 int nSetAux = populate_fk_triggers(db, "aux", "c_aux");154 TEST_ASSERT_MESSAGE(nSetMain>0, "Expected FK on c_main");155 TEST_ASSERT_MESSAGE(nSetAux>0, "Expected FK on c_aux");156 157 assert_fk_triggers_state(db, "main", "c_main", 0);158 assert_fk_triggers_state(db, "aux", "c_aux", 0);159 160 /* Clear only main (iDb=0) */161 sqlite3FkClearTriggerCache(db, 0);162 assert_fk_triggers_state(db, "main", "c_main", 1);163 /* Aux should remain intact */164 assert_fk_triggers_state(db, "aux", "c_aux", 0);165 166 /* Now clear aux specifically */167 int iAux = get_db_index(db, "aux");168 TEST_ASSERT_MESSAGE(iAux>=0, "Could not locate 'aux' database index");169 sqlite3FkClearTriggerCache(db, iAux);170 assert_fk_triggers_state(db, "aux", "c_aux", 1);171 172 sqlite3_close(db);173}174 175/* Test 4: No-op safety when there are no FKs or triggers; also with a non-ordinary object present */176static void test_sqlite3FkClearTriggerCache_noop_when_no_fk(void){177 sqlite3 *db = 0;178 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));179 180 exec_sql_ok(db, "CREATE TABLE t0(a INTEGER);");181 exec_sql_ok(db, "CREATE VIEW v0 AS SELECT a FROM t0;");182 183 Table *pT0 = find_table(db, "main", "t0");184 TEST_ASSERT_NOT_NULL(pT0);185 TEST_ASSERT_NULL(pT0->u.tab.pFKey); /* No foreign keys on t0 */186 187 /* Should not crash or change anything */188 sqlite3FkClearTriggerCache(db, 0);189 190 /* Still no foreign keys */191 TEST_ASSERT_NULL(pT0->u.tab.pFKey);192 193 sqlite3_close(db);194}195 196/* Test 5: Idempotence - safe to call multiple times */197static void test_sqlite3FkClearTriggerCache_idempotent(void){198 sqlite3 *db = 0;199 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));200 201 exec_sql_ok(db, "PRAGMA foreign_keys=ON;");202 exec_sql_ok(db, "CREATE TABLE p(id INTEGER PRIMARY KEY);");203 exec_sql_ok(db, "CREATE TABLE c(r INT, FOREIGN KEY(r) REFERENCES p(id));");204 205 int nSet = populate_fk_triggers(db, "main", "c");206 TEST_ASSERT_MESSAGE(nSet>0, "Expected FK on c");207 assert_fk_triggers_state(db, "main", "c", 0);208 209 sqlite3FkClearTriggerCache(db, 0);210 assert_fk_triggers_state(db, "main", "c", 1);211 212 /* Call again - should remain NULL and not crash */213 sqlite3FkClearTriggerCache(db, 0);214 assert_fk_triggers_state(db, "main", "c", 1);215 216 sqlite3_close(db);217}218 219int main(void){220 UNITY_BEGIN();221 RUN_TEST(test_sqlite3FkClearTriggerCache_clears_triggers_for_main);222 RUN_TEST(test_sqlite3FkClearTriggerCache_multiple_tables_and_fks);223 RUN_TEST(test_sqlite3FkClearTriggerCache_scoped_to_specified_db);224 RUN_TEST(test_sqlite3FkClearTriggerCache_noop_when_no_fk);225 RUN_TEST(test_sqlite3FkClearTriggerCache_idempotent);226 return UNITY_END();227}