AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5#include <stdio.h>6 7/* Wrapper declared in module */8void test_sqlite3ExprCodeIN(Parse *pParse, Expr *pExpr, int destIfFalse, int destIfNull);9 10/* Helpers to build expressions */11static Expr* makeIntExpr(sqlite3 *db, int val){12 char buf[40];13 Token t;14 sqlite3_snprintf(sizeof(buf), buf, "%d", val);15 t.z = buf;16 t.n = (int)strlen(buf);17 return sqlite3ExprAlloc(db, TK_INTEGER, &t, 0);18}19 20static Expr* makeInExprList(Parse *pParse, int lhsVal, const int *rhsVals, int nRhs){21 sqlite3 *db = pParse->db;22 Expr *pLhs = makeIntExpr(db, lhsVal);23 ExprList *pList = 0;24 for(int i=0; i<nRhs; i++){25 Expr *pItem = makeIntExpr(db, rhsVals[i]);26 pList = sqlite3ExprListAppend(pParse, pList, pItem);27 }28 Expr *pIn = sqlite3PExpr(pParse, TK_IN, pLhs, 0);29 /* Attach the RHS exprlist and mark union as list */30 pIn->x.pList = pList;31 ExprSetProperty(pIn, EP_xIsList);32 return pIn;33}34 35static void destroyExpr(sqlite3 *db, Expr *p){36 if(p) sqlite3ExprDelete(db, p);37}38 39static void initParse(sqlite3 *db, Parse *p){40 memset(p, 0, sizeof(*p));41 p->db = db;42 /* Create a VDBE for code generation */43 sqlite3VdbeCreate(p);44}45 46/* Scan helpers */47static int countOpcode(Vdbe *v, int op){48 int n = 0;49 int nOp = v->nOp;50 for(int i=0; i<nOp; i++){51 const VdbeOp *pOp = sqlite3VdbeGetOp(v, i);52 if(pOp->opcode==op) n++;53 }54 return n;55}56 57static int existsOpcodeWithP2(Vdbe *v, int op, int p2){58 int nOp = v->nOp;59 for(int i=0; i<nOp; i++){60 const VdbeOp *pOp = sqlite3VdbeGetOp(v, i);61 if(pOp->opcode==op && pOp->p2==p2) return 1;62 }63 return 0;64}65 66static int existsOpcode(Vdbe *v, int op){67 return countOpcode(v, op)>0;68}69 70void setUp(void) {71 /* empty */72}73 74void tearDown(void) {75 /* empty */76}77 78/* Test 1: RHS list with >2 constants -> ephemeral table path (Step 6 loop), distinct false/null labels */79void test_sqlite3ExprCodeIN_eph_step6_distinct_labels(void){80 sqlite3 *db = 0;81 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));82 83 Parse sParse; initParse(db, &sParse);84 Vdbe *v = sParse.pVdbe; TEST_ASSERT_NOT_NULL(v);85 86 /* Build expression: 1 IN (1,2,3) */87 int rhsVals[] = {1,2,3};88 Expr *pIn = makeInExprList(&sParse, 1, rhsVals, 3);89 90 /* Distinct labels for false and null */91 int destIfFalse = sqlite3VdbeMakeLabel(&sParse);92 int destIfNull = sqlite3VdbeMakeLabel(&sParse);93 94 test_sqlite3ExprCodeIN(&sParse, pIn, destIfFalse, destIfNull);95 96 /* Resolve our labels to known addresses: first null, then false */97 int aNull = sqlite3VdbeAddOp0(v, OP_Noop);98 sqlite3VdbeResolveLabel(&sParse, destIfNull);99 int aFalse = sqlite3VdbeAddOp0(v, OP_Noop);100 sqlite3VdbeResolveLabel(&sParse, destIfFalse);101 102 /* Validate generated opcodes */103 /* Expect Step 6 scan: OP_Rewind present, OP_Column and OP_Ne present, and a OP_Goto to destIfNull */104 TEST_ASSERT_TRUE_MESSAGE(existsOpcode(v, OP_Rewind), "Expected OP_Rewind for RHS scan");105 TEST_ASSERT_TRUE_MESSAGE(existsOpcode(v, OP_Column), "Expected OP_Column to read RHS row");106 TEST_ASSERT_TRUE_MESSAGE(existsOpcode(v, OP_Ne), "Expected OP_Ne to compare LHS/RHS");107 TEST_ASSERT_TRUE_MESSAGE(existsOpcodeWithP2(v, OP_Goto, aNull), "Expected OP_Goto -> destIfNull");108 /* Also expect there is some branch to destIfFalse */109 TEST_ASSERT_TRUE_MESSAGE(existsOpcodeWithP2(v, OP_Goto, aFalse) || existsOpcodeWithP2(v, OP_Rewind, aFalse),110 "Expected a path to destIfFalse");111 112 destroyExpr(db, pIn);113 sqlite3VdbeDelete(v);114 sqlite3_close(db);115}116 117/* Test 2: RHS list with 2 constants -> NOOP path (sequence of comparisons), distinct false/null labels */118void test_sqlite3ExprCodeIN_noop_list_two_terms(void){119 sqlite3 *db = 0;120 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));121 122 Parse sParse; initParse(db, &sParse);123 Vdbe *v = sParse.pVdbe; TEST_ASSERT_NOT_NULL(v);124 125 /* Build expression: 5 IN (1,2) */126 int rhsVals[] = {1,2};127 Expr *pIn = makeInExprList(&sParse, 5, rhsVals, 2);128 129 int destIfFalse = sqlite3VdbeMakeLabel(&sParse);130 int destIfNull = sqlite3VdbeMakeLabel(&sParse);131 132 test_sqlite3ExprCodeIN(&sParse, pIn, destIfFalse, destIfNull);133 134 /* Resolve labels: null first, then false, to distinct addresses */135 int aNull = sqlite3VdbeAddOp0(v, OP_Noop);136 sqlite3VdbeResolveLabel(&sParse, destIfNull);137 int aFalse = sqlite3VdbeAddOp0(v, OP_Noop);138 sqlite3VdbeResolveLabel(&sParse, destIfFalse);139 140 /* NOOP path should not use OP_Rewind */141 TEST_ASSERT_EQUAL_INT_MESSAGE(0, countOpcode(v, OP_Rewind), "Did not expect OP_Rewind in NOOP path");142 143 /* Should use OP_Eq comparisons and an OP_IsNull on the regCkNull when dests differ */144 TEST_ASSERT_TRUE_MESSAGE(existsOpcode(v, OP_Eq), "Expected OP_Eq comparisons in NOOP path");145 TEST_ASSERT_TRUE_MESSAGE(existsOpcodeWithP2(v, OP_IsNull, aNull), "Expected OP_IsNull -> destIfNull");146 TEST_ASSERT_TRUE_MESSAGE(existsOpcodeWithP2(v, OP_Goto, aFalse), "Expected OP_Goto -> destIfFalse");147 148 destroyExpr(db, pIn);149 sqlite3VdbeDelete(v);150 sqlite3_close(db);151}152 153/* Test 3: RHS list with >2 constants but destIfFalse==destIfNull -> combined NotFound */154void test_sqlite3ExprCodeIN_eph_equal_labels_notfound(void){155 sqlite3 *db = 0;156 TEST_ASSERT_EQUAL(SQLITE_OK, sqlite3_open(":memory:", &db));157 158 Parse sParse; initParse(db, &sParse);159 Vdbe *v = sParse.pVdbe; TEST_ASSERT_NOT_NULL(v);160 161 /* Build expression: 7 IN (1,2,3,4) */162 int rhsVals[] = {1,2,3,4};163 Expr *pIn = makeInExprList(&sParse, 7, rhsVals, 4);164 165 int destBoth = sqlite3VdbeMakeLabel(&sParse);166 167 test_sqlite3ExprCodeIN(&sParse, pIn, destBoth, destBoth);168 169 /* Resolve the shared label to a known address */170 int aDest = sqlite3VdbeAddOp0(v, OP_Noop);171 sqlite3VdbeResolveLabel(&sParse, destBoth);172 173 /* Expect OP_NotFound directly to destBoth and no OP_Rewind (since it should short-circuit) */174 TEST_ASSERT_TRUE_MESSAGE(existsOpcodeWithP2(v, OP_NotFound, aDest), "Expected OP_NotFound -> destBoth");175 /* In this combined path, Step 6 should be skipped entirely */176 TEST_ASSERT_EQUAL_INT_MESSAGE(0, countOpcode(v, OP_Rewind), "Did not expect OP_Rewind when dests are equal");177 178 destroyExpr(db, pIn);179 sqlite3VdbeDelete(v);180 sqlite3_close(db);181}182 183int main(void){184 UNITY_BEGIN();185 RUN_TEST(test_sqlite3ExprCodeIN_eph_step6_distinct_labels);186 RUN_TEST(test_sqlite3ExprCodeIN_noop_list_two_terms);187 RUN_TEST(test_sqlite3ExprCodeIN_eph_equal_labels_notfound);188 return UNITY_END();189}