AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5#include <stdio.h>6 7/* Global database handle used by tests */8static sqlite3 *gDb = NULL;9 10/* Helpers */11static void execSql(const char *zSql){12 char *zErr = NULL;13 int rc = sqlite3_exec(gDb, zSql, 0, 0, &zErr);14 if( zErr ){15 /* Ensure error string freed even on assert failure */16 char *zCopy = sqlite3_mprintf("%s", zErr);17 sqlite3_free(zErr);18 TEST_ASSERT_EQUAL_MESSAGE(SQLITE_OK, rc, zCopy ? zCopy : "SQL error");19 if( zCopy ) sqlite3_free(zCopy);20 }else{21 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);22 }23}24 25static Table* findTable(const char *zName){26 Table *pTab = sqlite3FindTable(gDb, zName, "main");27 TEST_ASSERT_NOT_NULL_MESSAGE(pTab, "Table/View not found in schema");28 return pTab;29}30 31static void initParse(Parse *pParse, Vdbe **ppVdbeOut){32 memset(pParse, 0, sizeof(Parse));33 pParse->db = gDb;34 Vdbe *v = sqlite3VdbeCreate(gDb);35 TEST_ASSERT_NOT_NULL(v);36 pParse->pVdbe = v;37 if( ppVdbeOut ) *ppVdbeOut = v;38}39 40static int countOpcode(Vdbe *v, int opcode){41 int n = 0;42 for(int i=0; i<v->nOp; i++){43 if( v->aOp[i].opcode == opcode ) n++;44 }45 return n;46}47 48static int countOpcodeWithP1(Vdbe *v, int opcode, int p1){49 int n = 0;50 for(int i=0; i<v->nOp; i++){51 if( v->aOp[i].opcode == opcode && v->aOp[i].p1 == p1 ) n++;52 }53 return n;54}55 56static VdbeOp* findLastOpByOpcode(Vdbe *v, int opcode){57 for(int i=v->nOp-1; i>=0; i--){58 if( v->aOp[i].opcode == opcode ) return &v->aOp[i];59 }60 return NULL;61}62 63static VdbeOp* findLastOpByOpcodeAndP1(Vdbe *v, int opcode, int p1){64 for(int i=v->nOp-1; i>=0; i--){65 if( v->aOp[i].opcode == opcode && v->aOp[i].p1 == p1 ) return &v->aOp[i];66 }67 return NULL;68}69 70static int countSchemaIndexes(Table *pTab){71 int n = 0;72 for(Index *pIdx = pTab->pIndex; pIdx; pIdx = pIdx->pNext) n++;73 return n;74}75 76/* Unity setup/teardown */77void setUp(void) {78 int rc = sqlite3_open(":memory:", &gDb);79 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);80 /* Keep FK off by default to simplify tests unless specifically needed */81 execSql("PRAGMA foreign_keys=OFF;");82}83 84void tearDown(void) {85 if( gDb ){86 sqlite3_close(gDb);87 gDb = NULL;88 }89}90 91/* Tests */92 93void test_sqlite3GenerateRowDelete_basic_rowid_onepass_off(void){94 execSql("CREATE TABLE t1(a,b,c);"95 "CREATE INDEX t1_b ON t1(b);");96 Table *pTab = findTable("t1");97 98 Parse parse;99 Vdbe *v = NULL;100 initParse(&parse, &v);101 102 /* Args */103 Trigger *pTrigger = NULL;104 int iDataCur = 0;105 int iIdxCur = 1; /* First index cursor (for t1_b) */106 int iPk = 3; /* Register number holding ROWID */107 i16 nPk = 1; /* Number of PK registers (ROWID) */108 u8 count = 1; /* Count row-change */109 u8 onconf = OE_Default; /* Default ON CONFLICT */110 u8 eMode = ONEPASS_OFF;111 int iIdxNoSeek = -1;112 113 sqlite3GenerateRowDelete(&parse, pTab, pTrigger,114 iDataCur, iIdxCur, iPk, nPk,115 count, onconf, eMode, iIdxNoSeek);116 117 /* Expect a seek op for rowid tables (OP_NotExists) exactly once */118 TEST_ASSERT_GREATER_OR_EQUAL_INT(1, v->nOp);119 TEST_ASSERT_EQUAL_INT(1, countOpcode(v, OP_NotExists));120 121 /* Expect one OP_IdxDelete per schema index */122 int nIdxSchema = countSchemaIndexes(pTab);123 TEST_ASSERT_EQUAL_INT(nIdxSchema, countOpcode(v, OP_IdxDelete));124 125 /* Expect one OP_Delete on the data cursor with OPFLAG_NCHANGE in P2 and P5==0 */126 VdbeOp *pDelData = findLastOpByOpcodeAndP1(v, OP_Delete, iDataCur);127 TEST_ASSERT_NOT_NULL(pDelData);128 TEST_ASSERT_EQUAL_INT(OPFLAG_NCHANGE, pDelData->p2);129 TEST_ASSERT_EQUAL_INT(0, pDelData->p5);130 131 /* No OP_Delete on the index cursor because iIdxNoSeek < 0 */132 TEST_ASSERT_EQUAL_INT(0, countOpcodeWithP1(v, OP_Delete, iIdxCur));133}134 135void test_sqlite3GenerateRowDelete_onepass_single_auxdelete_flag(void){136 execSql("CREATE TABLE t2(a,b);"137 "CREATE INDEX t2_b ON t2(b);");138 Table *pTab = findTable("t2");139 140 Parse parse;141 Vdbe *v = NULL;142 initParse(&parse, &v);143 144 Trigger *pTrigger = NULL;145 int iDataCur = 2;146 int iIdxCur = 7;147 int iPk = 5;148 i16 nPk = 1;149 u8 count = 0; /* no NCHANGE */150 u8 onconf = OE_Default;151 u8 eMode = ONEPASS_SINGLE;152 int iIdxNoSeek = -1;153 154 sqlite3GenerateRowDelete(&parse, pTab, pTrigger,155 iDataCur, iIdxCur, iPk, nPk,156 count, onconf, eMode, iIdxNoSeek);157 158 /* No initial seek when ONEPASS_SINGLE */159 TEST_ASSERT_EQUAL_INT(0, countOpcode(v, OP_NotExists));160 TEST_ASSERT_EQUAL_INT(0, countOpcode(v, OP_NotFound));161 162 /* Data cursor delete present, P2==0, P5 has AUXDELETE set */163 VdbeOp *pDelData = findLastOpByOpcodeAndP1(v, OP_Delete, iDataCur);164 TEST_ASSERT_NOT_NULL(pDelData);165 TEST_ASSERT_EQUAL_INT(0, pDelData->p2);166 TEST_ASSERT_EQUAL_INT(OPFLAG_AUXDELETE, pDelData->p5);167 168 /* No extra index cursor delete since iIdxNoSeek < 0 */169 TEST_ASSERT_EQUAL_INT(0, countOpcodeWithP1(v, OP_Delete, iIdxCur));170}171 172void test_sqlite3GenerateRowDelete_onepass_multi_saveposition_no_indexnoseek(void){173 execSql("CREATE TABLE t3(x,y);"174 "CREATE INDEX t3_y ON t3(y);");175 Table *pTab = findTable("t3");176 177 Parse parse;178 Vdbe *v = NULL;179 initParse(&parse, &v);180 181 Trigger *pTrigger = NULL;182 int iDataCur = 1;183 int iIdxCur = 3;184 int iPk = 10;185 i16 nPk = 1;186 u8 count = 1;187 u8 onconf = OE_Default;188 u8 eMode = ONEPASS_MULTI;189 int iIdxNoSeek = -1; /* no index to preserve */190 191 sqlite3GenerateRowDelete(&parse, pTab, pTrigger,192 iDataCur, iIdxCur, iPk, nPk,193 count, onconf, eMode, iIdxNoSeek);194 195 /* No initial seek when ONEPASS_MULTI */196 TEST_ASSERT_EQUAL_INT(0, countOpcode(v, OP_NotExists));197 TEST_ASSERT_EQUAL_INT(0, countOpcode(v, OP_NotFound));198 199 /* Only one OP_Delete (data cursor), final P5 should be SAVEPOSITION (and not AUXDELETE) */200 TEST_ASSERT_EQUAL_INT(1, countOpcode(v, OP_Delete));201 VdbeOp *pDelData = findLastOpByOpcodeAndP1(v, OP_Delete, iDataCur);202 TEST_ASSERT_NOT_NULL(pDelData);203 TEST_ASSERT_EQUAL_INT(OPFLAG_SAVEPOSITION, pDelData->p5);204 205 /* No OP_Delete on index cursor */206 TEST_ASSERT_EQUAL_INT(0, countOpcodeWithP1(v, OP_Delete, iIdxCur));207}208 209void test_sqlite3GenerateRowDelete_onepass_multi_saveposition_with_indexnoseek(void){210 execSql("CREATE TABLE t4(p,q);"211 "CREATE INDEX t4_q ON t4(q);");212 Table *pTab = findTable("t4");213 214 Parse parse;215 Vdbe *v = NULL;216 initParse(&parse, &v);217 218 Trigger *pTrigger = NULL;219 int iDataCur = 4;220 int iIdxCur = 6;221 int iPk = 2;222 i16 nPk = 1;223 u8 count = 1;224 u8 onconf = OE_Default;225 u8 eMode = ONEPASS_MULTI;226 int iIdxNoSeek = iIdxCur; /* preserve this index cursor */227 228 sqlite3GenerateRowDelete(&parse, pTab, pTrigger,229 iDataCur, iIdxCur, iPk, nPk,230 count, onconf, eMode, iIdxNoSeek);231 232 /* Expect two OP_Delete: one on data (with AUXDELETE) then one on index (with SAVEPOSITION) */233 TEST_ASSERT_GREATER_OR_EQUAL_INT(2, countOpcode(v, OP_Delete));234 235 VdbeOp *pDelIdx = findLastOpByOpcodeAndP1(v, OP_Delete, iIdxCur);236 TEST_ASSERT_NOT_NULL(pDelIdx);237 TEST_ASSERT_EQUAL_INT(OPFLAG_SAVEPOSITION, pDelIdx->p5);238 239 VdbeOp *pDelData = findLastOpByOpcodeAndP1(v, OP_Delete, iDataCur);240 TEST_ASSERT_NOT_NULL(pDelData);241 TEST_ASSERT_EQUAL_INT(OPFLAG_AUXDELETE, pDelData->p5);242}243 244void test_sqlite3GenerateRowDelete_skips_deletes_for_view(void){245 execSql("CREATE VIEW v1 AS SELECT 1 AS x;");246 Table *pView = findTable("v1"); /* View has no storage */247 248 Parse parse;249 Vdbe *v = NULL;250 initParse(&parse, &v);251 252 /* Use arbitrary cursor/register numbers */253 sqlite3GenerateRowDelete(&parse, pView, NULL,254 0, 1, 3, 1, 1, OE_Default, ONEPASS_OFF, -1);255 256 /* Should not generate OP_Delete or OP_IdxDelete for views */257 TEST_ASSERT_EQUAL_INT(0, countOpcode(v, OP_Delete));258 TEST_ASSERT_EQUAL_INT(0, countOpcode(v, OP_IdxDelete));259 /* Note: A seek may still be generated; we do not assert on seek count here. */260}261 262void test_sqlite3GenerateRowDelete_before_trigger_reseek_and_disable_idxnoseek(void){263 execSql("CREATE TABLE t5(a,b);"264 "CREATE INDEX t5_b ON t5(b);"265 "CREATE TRIGGER tr_bef BEFORE DELETE ON t5 BEGIN SELECT 1; END;");266 Table *pTab = findTable("t5");267 TEST_ASSERT_NOT_NULL(pTab->pTrigger);268 269 Parse parse;270 Vdbe *v = NULL;271 initParse(&parse, &v);272 273 int iDataCur = 0;274 int iIdxCur = 1;275 int iPk = 7;276 i16 nPk = 1;277 278 /* Pass BEFORE trigger list; eMode OFF to force initial seek */279 sqlite3GenerateRowDelete(&parse, pTab, pTab->pTrigger,280 iDataCur, iIdxCur, iPk, nPk,281 0, OE_Default, ONEPASS_OFF, iIdxCur /* attempt no-seek */);282 283 /* Expect two seeks: one before triggers, one after BEFORE triggers */284 int nSeek = countOpcode(v, OP_NotExists) + countOpcode(v, OP_NotFound);285 TEST_ASSERT_EQUAL_INT(2, nSeek);286 287 /* iIdxNoSeek should have been disabled -> no index cursor OP_Delete generated */288 TEST_ASSERT_EQUAL_INT(0, countOpcodeWithP1(v, OP_Delete, iIdxCur));289 290 /* But data cursor OP_Delete should exist */291 VdbeOp *pDelData = findLastOpByOpcodeAndP1(v, OP_Delete, iDataCur);292 TEST_ASSERT_NOT_NULL(pDelData);293}294 295/* main */296int main(void) {297 UNITY_BEGIN();298 RUN_TEST(test_sqlite3GenerateRowDelete_basic_rowid_onepass_off);299 RUN_TEST(test_sqlite3GenerateRowDelete_onepass_single_auxdelete_flag);300 RUN_TEST(test_sqlite3GenerateRowDelete_onepass_multi_saveposition_no_indexnoseek);301 RUN_TEST(test_sqlite3GenerateRowDelete_onepass_multi_saveposition_with_indexnoseek);302 RUN_TEST(test_sqlite3GenerateRowDelete_skips_deletes_for_view);303 RUN_TEST(test_sqlite3GenerateRowDelete_before_trigger_reseek_and_disable_idxnoseek);304 return UNITY_END();305}