AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5 6/* Wrapper for the static function under test (provided by the build) */7extern void test_openStatTable(8 Parse *pParse,9 int iDb,10 int iStatCur,11 const char *zWhere,12 const char *zWhereType13);14 15/* Unity fixtures */16void setUp(void) {17 /* empty */18}19void tearDown(void) {20 /* empty */21}22 23/* Helper: initialize a Parse object for a given db */24static void initParse(sqlite3 *db, Parse *pParse){25 memset(pParse, 0, sizeof(*pParse));26 pParse->db = db;27}28 29/* Helper: call test_openStatTable and return the Vdbe pointer */30static Vdbe* call_openStatTable(sqlite3 *db, int iDb, int iStatCur,31 const char *zWhere, const char *zWhereType){32 Parse p;33 initParse(db, &p);34 35 /* Satisfy internal asserts in openStatTable */36 sqlite3BtreeEnterAll(db);37 test_openStatTable(&p, iDb, iStatCur, zWhere, zWhereType);38 sqlite3BtreeLeaveAll(db);39 40 return p.pVdbe;41}42 43/* Helper: find the last OP_OpenWrite for a given cursor number */44static Op* find_last_openwrite_for_cursor(Vdbe *v, int cursor){45 if( v==NULL || v->nOp<=0 ) return NULL;46 for(int i=v->nOp-1; i>=0; i--){47 Op *op = &v->aOp[i];48 if( op->opcode==OP_OpenWrite && op->p1==cursor ){49 return op;50 }51 }52 return NULL;53}54 55/* Helper: count occurrences of an opcode in the program */56static int count_opcode(Vdbe *v, int opcode){57 if( v==NULL || v->nOp<=0 ) return 0;58 int cnt = 0;59 for(int i=0; i<v->nOp; i++){60 if( v->aOp[i].opcode == opcode ) cnt++;61 }62 return cnt;63}64 65/* Test: When stat tables do not exist, they are created and opened with P5 flag set (OPFLAG_P2ISREG). */66static void test_openStatTable_creates_and_opens_stat_tables(void){67 sqlite3 *db = NULL;68 int rc = sqlite3_open(":memory:", &db);69 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);70 TEST_ASSERT_NOT_NULL(db);71 72 int iDb = 0; /* main database */73 int iStatCur = 12; /* arbitrary cursor number */74 Vdbe *v = call_openStatTable(db, iDb, iStatCur, NULL, "tbl");75 TEST_ASSERT_NOT_NULL(v);76 TEST_ASSERT_TRUE(v->nOp > 0);77 78 /* The function should end by opening sqlite_stat1 (and sqlite_stat4 if enabled) */79 Op *ow0 = find_last_openwrite_for_cursor(v, iStatCur);80 TEST_ASSERT_NOT_NULL_MESSAGE(ow0, "Did not find OP_OpenWrite for iStatCur");81 TEST_ASSERT_EQUAL_INT(OP_OpenWrite, ow0->opcode);82 TEST_ASSERT_EQUAL_INT(iStatCur, ow0->p1);83 /* Since table did not exist, P5 should indicate P2 is a register (non-zero) */84 TEST_ASSERT_TRUE_MESSAGE(ow0->p5 != 0, "Expected OP_OpenWrite P5 non-zero for created table");85 86 /* If STAT4 is enabled and OptimizationEnabled(Stat4) is true, a second OpenWrite exists for iStatCur+1 */87 Op *ow1 = find_last_openwrite_for_cursor(v, iStatCur+1);88 if( ow1 ){89 TEST_ASSERT_EQUAL_INT(OP_OpenWrite, ow1->opcode);90 TEST_ASSERT_EQUAL_INT(iStatCur+1, ow1->p1);91 TEST_ASSERT_TRUE_MESSAGE(ow1->p5 != 0, "Expected OP_OpenWrite P5 non-zero for created sqlite_stat4");92 }93 94 sqlite3_close(db);95}96 97/* Test: If sqlite_stat1 exists and zWhere==NULL, openStatTable clears the table (emits OP_Clear)98 and opens it with P5==0 (root page number in P2, not a register). */99static void test_openStatTable_existing_table_clear_all(void){100 sqlite3 *db = NULL;101 int rc = sqlite3_open(":memory:", &db);102 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);103 TEST_ASSERT_NOT_NULL(db);104 105 /* Pre-create sqlite_stat1 so that openStatTable finds it existing */106 rc = sqlite3_exec(db, "CREATE TABLE sqlite_stat1(tbl,idx,stat);", 0, 0, 0);107 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);108 109 int iDb = 0;110 int iStatCur = 7;111 Vdbe *v = call_openStatTable(db, iDb, iStatCur, NULL, "tbl");112 TEST_ASSERT_NOT_NULL(v);113 114 /* Expect an OP_Clear since zWhere==NULL and table exists */115 int nClear = count_opcode(v, OP_Clear);116 TEST_ASSERT_TRUE_MESSAGE(nClear >= 1, "Expected at least one OP_Clear for existing sqlite_stat1 and zWhere==NULL");117 118 /* For existing tables the final OpenWrite should have P5==0 */119 Op *ow0 = find_last_openwrite_for_cursor(v, iStatCur);120 TEST_ASSERT_NOT_NULL(ow0);121 TEST_ASSERT_EQUAL_INT(OP_OpenWrite, ow0->opcode);122 TEST_ASSERT_EQUAL_INT(iStatCur, ow0->p1);123 TEST_ASSERT_EQUAL_UINT8(0, ow0->p5);124 125 sqlite3_close(db);126}127 128/* Test: If sqlite_stat1 exists and zWhere!=NULL, openStatTable should not emit OP_Clear129 (it uses a nested DELETE statement). OpenWrite should have P5==0. */130static void test_openStatTable_where_clause_deletes_specific(void){131 sqlite3 *db = NULL;132 int rc = sqlite3_open(":memory:", &db);133 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);134 TEST_ASSERT_NOT_NULL(db);135 136 /* Pre-create sqlite_stat1 and insert some data */137 rc = sqlite3_exec(db,138 "CREATE TABLE sqlite_stat1(tbl,idx,stat);"139 "INSERT INTO sqlite_stat1 VALUES('t1','idx1','1 2 3');"140 "INSERT INTO sqlite_stat1 VALUES('t2','idx2','4 5 6');",141 0, 0, 0);142 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);143 144 int iDb = 0;145 int iStatCur = 25;146 Vdbe *v = call_openStatTable(db, iDb, iStatCur, "t1", "tbl");147 TEST_ASSERT_NOT_NULL(v);148 149 /* Since zWhere!=NULL, OP_Clear should not be generated by openStatTable */150 int nClear = count_opcode(v, OP_Clear);151 TEST_ASSERT_EQUAL_INT_MESSAGE(0, nClear, "Did not expect OP_Clear when zWhere!=NULL");152 153 /* OpenWrite should be present and P5==0 for existing table */154 Op *ow0 = find_last_openwrite_for_cursor(v, iStatCur);155 TEST_ASSERT_NOT_NULL(ow0);156 TEST_ASSERT_EQUAL_INT(OP_OpenWrite, ow0->opcode);157 TEST_ASSERT_EQUAL_INT(iStatCur, ow0->p1);158 TEST_ASSERT_EQUAL_UINT8(0, ow0->p5);159 160 sqlite3_close(db);161}162 163/* Test: Verify that the provided iStatCur is honored for cursor numbers. */164static void test_openStatTable_uses_requested_cursors(void){165 sqlite3 *db = NULL;166 int rc = sqlite3_open(":memory:", &db);167 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);168 TEST_ASSERT_NOT_NULL(db);169 170 int iDb = 0;171 int iStatCur = 42;172 Vdbe *v = call_openStatTable(db, iDb, iStatCur, NULL, "tbl");173 TEST_ASSERT_NOT_NULL(v);174 175 Op *ow0 = find_last_openwrite_for_cursor(v, iStatCur);176 TEST_ASSERT_NOT_NULL(ow0);177 TEST_ASSERT_EQUAL_INT(iStatCur, ow0->p1);178 179 /* If sqlite_stat4 is opened too, it must use iStatCur+1 */180 Op *ow1 = find_last_openwrite_for_cursor(v, iStatCur+1);181 if( ow1 ){182 TEST_ASSERT_EQUAL_INT(iStatCur+1, ow1->p1);183 }184 185 sqlite3_close(db);186}187 188int main(void){189 UNITY_BEGIN();190 RUN_TEST(test_openStatTable_creates_and_opens_stat_tables);191 RUN_TEST(test_openStatTable_existing_table_clear_all);192 RUN_TEST(test_openStatTable_where_clause_deletes_specific);193 RUN_TEST(test_openStatTable_uses_requested_cursors);194 return UNITY_END();195}