Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
tests_delete_sqlite3GenerateRowDelete.c305 linesDownload Raw Back to tests
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}