AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5 6static sqlite3 *gDb = NULL;7 8/* Unity fixtures */9void setUp(void) {10 /* Initialize a fresh in-memory database for each test */11 int rc = sqlite3_open(":memory:", &gDb);12 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);13}14 15void tearDown(void) {16 if (gDb) {17 sqlite3_close(gDb);18 gDb = NULL;19 }20}21 22/* Helpers to create minimal Expr nodes for testing */23 24static Expr *make_expr_null(void){25 Expr *e = (Expr*)sqlite3DbMallocZero(gDb, sizeof(Expr));26 TEST_ASSERT_NOT_NULL(e);27 e->op = TK_NULL;28 /* Leave other fields zeroed */29 return e;30}31 32static Expr *make_expr_cast_with_aff(const char *affStr){33 /* Create a TK_CAST node with u.zToken=affStr so sqlite3ExprAffinity returns desired affinity */34 Expr *e = (Expr*)sqlite3DbMallocZero(gDb, sizeof(Expr));35 TEST_ASSERT_NOT_NULL(e);36 e->op = TK_CAST;37 e->u.zToken = (char*)affStr; /* constant string; safe for tests */38 return e;39}40 41static IndexedExpr *make_indexed_expr_node(Expr *expr, int iDataCur, int iIdxCur, int iIdxCol, u8 aff, int bMaybeNullRow, const char *zIdxName){42 IndexedExpr *p = (IndexedExpr*)sqlite3DbMallocZero(gDb, sizeof(IndexedExpr));43 TEST_ASSERT_NOT_NULL(p);44 p->pExpr = expr;45 p->iDataCur = iDataCur;46 p->iIdxCur = iIdxCur;47 p->iIdxCol = iIdxCol;48 p->aff = aff;49 p->bMaybeNullRow = bMaybeNullRow ? 1 : 0;50 p->zIdxName = (char*)(zIdxName ? zIdxName : "idx");51 p->pIENext = 0;52 return p;53}54 55/* Helper to initialize a Parse with a Vdbe attached */56static void init_parse(Parse *pParse){57 memset(pParse, 0, sizeof(*pParse));58 pParse->db = gDb;59 pParse->pVdbe = sqlite3VdbeCreate(gDb);60 TEST_ASSERT_NOT_NULL(pParse->pVdbe);61 pParse->nMem = 64; /* provide enough registers for codegen paths */62}63 64/* Cleanup Parse resources created in init_parse */65static void cleanup_parse(Parse *pParse){66 if (pParse->pVdbe) {67 sqlite3VdbeDelete(pParse->pVdbe);68 pParse->pVdbe = NULL;69 }70 /* pIdxEpr memory is from sqlite3DbMallocZero(gDb, ...); it will be71 reclaimed when connection is closed. For tests, it's acceptable. */72}73 74/* Convenience to get current number of ops emitted to Vdbe */75static int vdbe_op_count(Parse *pParse){76 Vdbe *v = pParse->pVdbe;77 TEST_ASSERT_NOT_NULL(v);78 return v->nOp;79}80 81/* TESTS */82 83/* 1) Simple match: expression matches IndexedExpr, affinity TEXT matches, OP_Column emitted, returns target */84void test_sqlite3IndexedExprLookup_simple_match(void){85 Parse parse;86 init_parse(&parse);87 88 Expr *e = make_expr_cast_with_aff("TEXT");89 IndexedExpr *node = make_indexed_expr_node(90 e, /* pExpr */91 1, /* iDataCur >= 0 */92 2, /* iIdxCur */93 0, /* iIdxCol */94 SQLITE_AFF_TEXT,/* aff matches "TEXT" */95 0, /* bMaybeNullRow */96 "i1"97 );98 parse.pIdxEpr = node;99 100 int before = vdbe_op_count(&parse);101 int target = 3;102 int rc = test_sqlite3IndexedExprLookup(&parse, e, target);103 104 TEST_ASSERT_EQUAL_INT(target, rc);105 TEST_ASSERT_EQUAL_INT(before + 1, vdbe_op_count(&parse)); /* Expect exactly one OP_Column emitted */106 107 cleanup_parse(&parse);108}109 110/* 2) No match due to affinity mismatch: expression TEXT, node affinity NUMERIC -> returns -1, no ops added */111void test_sqlite3IndexedExprLookup_affinity_mismatch(void){112 Parse parse;113 init_parse(&parse);114 115 Expr *e = make_expr_cast_with_aff("TEXT");116 IndexedExpr *node = make_indexed_expr_node(117 e,118 1,119 2,120 1,121 SQLITE_AFF_NUMERIC, /* mismatch */122 0,123 "i1"124 );125 parse.pIdxEpr = node;126 127 int before = vdbe_op_count(&parse);128 int rc = test_sqlite3IndexedExprLookup(&parse, e, 5);129 130 TEST_ASSERT_EQUAL_INT(-1, rc);131 TEST_ASSERT_EQUAL_INT(before, vdbe_op_count(&parse));132 133 cleanup_parse(&parse);134}135 136/* 3) iSelfTab filtering: only node with iDataCur == iSelfTab-1 is considered */137void test_sqlite3IndexedExprLookup_selftab_filter(void){138 Parse parse;139 init_parse(&parse);140 141 Expr *e = make_expr_cast_with_aff("TEXT");142 143 IndexedExpr *bad = make_indexed_expr_node(144 e,145 3, /* will be skipped since iSelfTab-1 != 3 */146 2,147 0,148 SQLITE_AFF_TEXT,149 0,150 "bad"151 );152 IndexedExpr *good = make_indexed_expr_node(153 e,154 4, /* matches iSelfTab-1 when iSelfTab=5 */155 2,156 1,157 SQLITE_AFF_TEXT,158 0,159 "good"160 );161 bad->pIENext = good;162 parse.pIdxEpr = bad;163 parse.iSelfTab = 5; /* only accept iDataCur == 4 */164 165 int before = vdbe_op_count(&parse);166 int rc = test_sqlite3IndexedExprLookup(&parse, e, 7);167 168 TEST_ASSERT_EQUAL_INT(7, rc);169 TEST_ASSERT_EQUAL_INT(before + 1, vdbe_op_count(&parse));170 171 cleanup_parse(&parse);172}173 174/* 4) bMaybeNullRow path: ensures IfNullRow/Column/Goto plus original expr code are emitted */175void test_sqlite3IndexedExprLookup_maybe_null_row_path(void){176 Parse parse;177 init_parse(&parse);178 179 /* Use TK_NULL so sqlite3ExprCode can handle it easily */180 Expr *e = make_expr_null();181 IndexedExpr *node = make_indexed_expr_node(182 e,183 1, /* valid iDataCur */184 2,185 0,186 SQLITE_AFF_BLOB, /* for TK_NULL, affinity test expects BLOB case */187 1, /* bMaybeNullRow = true to take special path */188 "i1"189 );190 parse.pIdxEpr = node;191 192 int before = vdbe_op_count(&parse);193 int rc = test_sqlite3IndexedExprLookup(&parse, e, 9);194 195 TEST_ASSERT_EQUAL_INT(9, rc);196 /* Expect multiple ops: IfNullRow, Column, Goto, and code for TK_NULL (Null) at least.197 So nOp should increase by >= 3. */198 TEST_ASSERT_TRUE(vdbe_op_count(&parse) >= before + 3);199 200 cleanup_parse(&parse);201}202 203/* 5) iDataCur < 0 nodes are ignored */204void test_sqlite3IndexedExprLookup_negative_iDataCur_ignored(void){205 Parse parse;206 init_parse(&parse);207 208 Expr *e = make_expr_cast_with_aff("TEXT");209 IndexedExpr *node = make_indexed_expr_node(210 e,211 -1, /* invalid: should be skipped */212 2,213 0,214 SQLITE_AFF_TEXT,215 0,216 "i1"217 );218 parse.pIdxEpr = node;219 220 int before = vdbe_op_count(&parse);221 int rc = test_sqlite3IndexedExprLookup(&parse, e, 4);222 223 TEST_ASSERT_EQUAL_INT(-1, rc);224 TEST_ASSERT_EQUAL_INT(before, vdbe_op_count(&parse));225 226 cleanup_parse(&parse);227}228 229/* 6) Multiple nodes: stops at first matching node */230void test_sqlite3IndexedExprLookup_stops_at_first_match(void){231 Parse parse;232 init_parse(&parse);233 234 Expr *e = make_expr_cast_with_aff("TEXT");235 236 IndexedExpr *first = make_indexed_expr_node(237 e, 0, 2, 0, SQLITE_AFF_TEXT, 0, "first"238 );239 IndexedExpr *second = make_indexed_expr_node(240 e, 0, 3, 1, SQLITE_AFF_TEXT, 0, "second"241 );242 first->pIENext = second;243 parse.pIdxEpr = first;244 245 int before = vdbe_op_count(&parse);246 int rc = test_sqlite3IndexedExprLookup(&parse, e, 6);247 248 TEST_ASSERT_EQUAL_INT(6, rc);249 /* Only one OP_Column should be emitted for the first match */250 TEST_ASSERT_EQUAL_INT(before + 1, vdbe_op_count(&parse));251 252 cleanup_parse(&parse);253}254 255/* 7) No match due to expression mismatch */256void test_sqlite3IndexedExprLookup_expression_mismatch(void){257 Parse parse;258 init_parse(&parse);259 260 Expr *want = make_expr_cast_with_aff("TEXT");261 Expr *have = make_expr_cast_with_aff("NUMERIC"); /* different token => different expr */262 263 IndexedExpr *node = make_indexed_expr_node(264 have,265 1,266 2,267 0,268 SQLITE_AFF_NUMERIC, /* matches have; but want != have */269 0,270 "i1"271 );272 parse.pIdxEpr = node;273 274 int before = vdbe_op_count(&parse);275 int rc = test_sqlite3IndexedExprLookup(&parse, want, 8);276 277 TEST_ASSERT_EQUAL_INT(-1, rc);278 TEST_ASSERT_EQUAL_INT(before, vdbe_op_count(&parse));279 280 cleanup_parse(&parse);281}282 283int main(void) {284 UNITY_BEGIN();285 RUN_TEST(test_sqlite3IndexedExprLookup_simple_match);286 RUN_TEST(test_sqlite3IndexedExprLookup_affinity_mismatch);287 RUN_TEST(test_sqlite3IndexedExprLookup_selftab_filter);288 RUN_TEST(test_sqlite3IndexedExprLookup_maybe_null_row_path);289 RUN_TEST(test_sqlite3IndexedExprLookup_negative_iDataCur_ignored);290 RUN_TEST(test_sqlite3IndexedExprLookup_stops_at_first_match);291 RUN_TEST(test_sqlite3IndexedExprLookup_expression_mismatch);292 return UNITY_END();293}