AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <sqlite3.h>4#include <string.h>5 6/* Global database handle for tests */7static sqlite3 *gDb = NULL;8 9/* Unity setUp/tearDown */10void setUp(void) {11 int rc = sqlite3_open(":memory:", &gDb);12 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);13 14 /* Create some tables and indexes used by tests */15 const char *sql =16 "PRAGMA foreign_keys=OFF;"17 "CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);"18 "CREATE INDEX t1_b ON t1(b);"19 "CREATE TABLE t2(a INTEGER PRIMARY KEY, b TEXT);"20 "CREATE INDEX t2_b ON t2(b);"21 "CREATE TABLE wr(a INT, b INT, PRIMARY KEY(a,b)) WITHOUT ROWID;"22 "CREATE VIEW v1 AS SELECT * FROM t1;";23 char *err = NULL;24 rc = sqlite3_exec(gDb, sql, 0, 0, &err);25 if( rc!=SQLITE_OK ){26 TEST_FAIL_MESSAGE(err ? err : "Failed to initialize schema");27 }28 sqlite3_free(err);29}30 31void tearDown(void) {32 if( gDb ){33 sqlite3_close(gDb);34 gDb = NULL;35 }36}37 38/* Helpers */39 40/* Initialize a Parse object bound to gDb and ready for codegen */41static void initParse(Parse *p){42 memset(p, 0, sizeof(*p));43 p->db = gDb;44 gDb->pParse = p;45}46 47/* Build a SrcList referring to zDb.zTab for sqlite3DeleteFrom.48 Caller must not free the result; sqlite3DeleteFrom frees it. */49static SrcList *makeSrc(Parse *pParse, const char *zDb, const char *zTab){50 SrcList *p = sqlite3SrcListAppend(pParse, 0, 0, 0);51 TEST_ASSERT_NOT_NULL_MESSAGE(p, "sqlite3SrcListAppend failed");52 /* Fill in table and database names */53 p->a[0].zName = sqlite3DbStrDup(pParse->db, zTab);54 if( zDb ){55 p->a[0].u4.zDatabase = sqlite3DbStrDup(pParse->db, zDb);56 }57 return p;58}59 60/* Count occurrences of a specific opcode in the current VDBE for a Parse */61static int countOp(Parse *pParse, int op){62 Vdbe *v = pParse->pVdbe;63 if( v==0 ) return 0;64 int n = sqlite3VdbeCurrentAddr(pParse);65 int cnt = 0;66 for( int i = 0; i < n; i++ ){67 Op *o = sqlite3VdbeGetOp(v, i);68 if( o && o->opcode == op ) cnt++;69 }70 return cnt;71}72 73/* Optional: clean up VDBE after assertions to avoid holding onto memory */74static void freeVdbe(Parse *pParse){75 if( pParse->pVdbe ){76 sqlite3VdbeDelete(pParse->pVdbe);77 pParse->pVdbe = 0;78 }79}80 81/* Authorizer callbacks for tests */82static int authDeny(void *pUser, int action, const char *z1, const char *z2,83 const char *z3, const char *z4){84 (void)pUser; (void)z1; (void)z2; (void)z3; (void)z4;85 if( action==SQLITE_DELETE ) return SQLITE_DENY;86 return SQLITE_OK;87}88static int authIgnore(void *pUser, int action, const char *z1, const char *z2,89 const char *z3, const char *z4){90 (void)pUser; (void)z1; (void)z2; (void)z3; (void)z4;91 if( action==SQLITE_DELETE ) return SQLITE_IGNORE;92 return SQLITE_OK;93}94 95/* Tests */96 97/* Truncate optimization: DELETE FROM t1; generates OP_Clear ops and no OP_Delete */98void test_sqlite3DeleteFrom_truncate_optimization_generates_clear_ops(void){99 Parse parse;100 initParse(&parse);101 102 /* Ensure no count-changes flag to avoid extra tail ops */103 gDb->flags &= ~SQLITE_CountRows;104 105 SrcList *pSrc = makeSrc(&parse, "main", "t1");106 sqlite3DeleteFrom(&parse, pSrc, 0, 0, 0);107 108 /* Vdbe should be created */109 TEST_ASSERT_NOT_NULL(parse.pVdbe);110 111 int nClear = countOp(&parse, OP_Clear);112 int nDelete = countOp(&parse, OP_Delete);113 114 /* Expect at least two clears: table and one index */115 TEST_ASSERT_TRUE_MESSAGE(nClear >= 2, "Expected OP_Clear for table and index");116 TEST_ASSERT_EQUAL_INT_MESSAGE(0, nDelete, "Truncate optimization should not emit OP_Delete");117 118 freeVdbe(&parse);119}120 121/* Count changes enabled adds OP_ResultRow at end (DELETE FROM t2;) */122void test_sqlite3DeleteFrom_count_changes_adds_resultrow(void){123 Parse parse;124 initParse(&parse);125 126 /* Enable count-changes */127 unsigned priorFlags = gDb->flags;128 gDb->flags |= SQLITE_CountRows;129 130 SrcList *pSrc = makeSrc(&parse, "main", "t2");131 sqlite3DeleteFrom(&parse, pSrc, 0, 0, 0);132 133 TEST_ASSERT_NOT_NULL(parse.pVdbe);134 135 int nClear = countOp(&parse, OP_Clear);136 int nResultRow = countOp(&parse, OP_ResultRow);137 138 TEST_ASSERT_TRUE_MESSAGE(nClear >= 2, "Expected OP_Clear for table and index");139 TEST_ASSERT_EQUAL_INT_MESSAGE(1, nResultRow, "Expected one OP_ResultRow for count-changes");140 141 /* Restore flags */142 gDb->flags = priorFlags;143 144 freeVdbe(&parse);145}146 147/* Authorizer DENY causes early abort: no VDBE created */148void test_sqlite3DeleteFrom_authorizer_deny_aborts(void){149 Parse parse;150 initParse(&parse);151 152 int rc = sqlite3_set_authorizer(gDb, authDeny, 0);153 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);154 155 SrcList *pSrc = makeSrc(&parse, "main", "t1");156 sqlite3DeleteFrom(&parse, pSrc, 0, 0, 0);157 158 /* Expect no VDBE when denied */159 TEST_ASSERT_NULL(parse.pVdbe);160 161 /* Clear authorizer */162 sqlite3_set_authorizer(gDb, 0, 0);163}164 165/* Authorizer IGNORE disables truncate optimization, generating row-by-row delete (OP_Delete) and no OP_Clear */166void test_sqlite3DeleteFrom_authorizer_ignore_disables_truncate(void){167 Parse parse;168 initParse(&parse);169 170 int rc = sqlite3_set_authorizer(gDb, authIgnore, 0);171 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);172 173 /* No WHERE clause but IGNORE should disable truncate optimization */174 SrcList *pSrc = makeSrc(&parse, "main", "t1");175 sqlite3DeleteFrom(&parse, pSrc, 0, 0, 0);176 177 TEST_ASSERT_NOT_NULL(parse.pVdbe);178 179 int nClear = countOp(&parse, OP_Clear);180 int nDelete = countOp(&parse, OP_Delete);181 182 TEST_ASSERT_EQUAL_INT_MESSAGE(0, nClear, "Truncate optimization should be disabled by SQLITE_IGNORE");183 TEST_ASSERT_TRUE_MESSAGE(nDelete >= 1, "Expected OP_Delete for row-by-row deletion");184 185 sqlite3_set_authorizer(gDb, 0, 0);186 187 freeVdbe(&parse);188}189 190/* Attempt to delete from a view without INSTEAD OF triggers should be rejected (read-only) */191void test_sqlite3DeleteFrom_readonly_view_rejected(void){192 Parse parse;193 initParse(&parse);194 195 SrcList *pSrc = makeSrc(&parse, "main", "v1"); /* v1 is a simple view on t1 */196 sqlite3DeleteFrom(&parse, pSrc, 0, 0, 0);197 198 /* Should abort early with no VDBE generated */199 TEST_ASSERT_NULL(parse.pVdbe);200}201 202/* Main runner */203int main(void){204 UNITY_BEGIN();205 RUN_TEST(test_sqlite3DeleteFrom_truncate_optimization_generates_clear_ops);206 RUN_TEST(test_sqlite3DeleteFrom_count_changes_adds_resultrow);207 RUN_TEST(test_sqlite3DeleteFrom_authorizer_deny_aborts);208 RUN_TEST(test_sqlite3DeleteFrom_authorizer_ignore_disables_truncate);209 RUN_TEST(test_sqlite3DeleteFrom_readonly_view_rejected);210 return UNITY_END();211}