AryaWu/sqlite
0
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}