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 for the static function under test (provided by the build harness) */8extern void test_sqlite3ClearStatTables(Parse *pParse, int iDb, const char *zType, const char *zName);9 10typedef struct AuthEvent {11 int code;12 char db[64];13 char tbl[64];14} AuthEvent;15 16#define MAX_EVENTS 6417typedef struct AuthLog {18 int n;19 AuthEvent ev[MAX_EVENTS];20} AuthLog;21 22static int auth_cb(void *pArg, int code,23 const char *p1, const char *p2,24 const char *zDb, const char *zTrig){25 AuthLog *pLog = (AuthLog*)pArg;26 if( code==SQLITE_DELETE ){27 if( pLog->n < MAX_EVENTS ){28 AuthEvent *e = &pLog->ev[pLog->n++];29 e->code = code;30 e->db[0] = '\0';31 e->tbl[0] = '\0';32 if( zDb ) {33 strncpy(e->db, zDb, sizeof(e->db)-1);34 e->db[sizeof(e->db)-1] = '\0';35 }36 if( p1 ) {37 strncpy(e->tbl, p1, sizeof(e->tbl)-1);38 e->tbl[sizeof(e->tbl)-1] = '\0';39 }40 }41 }42 /* Always allow */43 return SQLITE_OK;44}45 46static int findDbIndex(sqlite3 *db, const char *zName){47 int i;48 for(i=0; i<db->nDb; i++){49 if( db->aDb[i].zDbSName && 0==sqlite3StrICmp(db->aDb[i].zDbSName, zName) ){50 return i;51 }52 }53 return -1;54}55 56static int stat_exists(sqlite3 *db, const char *zDbName, int n){57 char zTab[24];58 sqlite3_snprintf(sizeof(zTab), zTab, "sqlite_stat%d", n);59 return sqlite3FindTable(db, zTab, zDbName)!=0;60}61 62static int count_existing_stat_tables(sqlite3 *db, const char *zDbName){63 int i, c=0;64 for(i=1; i<=4; i++){65 if( stat_exists(db, zDbName, i) ) c++;66 }67 return c;68}69 70static void populate_main_stats(sqlite3 *db){71 /* Create objects and run ANALYZE to ensure sqlite_stat tables exist */72 char *errmsg = 0;73 int rc;74 rc = sqlite3_exec(db,75 "PRAGMA foreign_keys=OFF; "76 "CREATE TABLE IF NOT EXISTS t1(x); "77 "CREATE INDEX IF NOT EXISTS i1 ON t1(x); "78 "INSERT INTO t1(x) VALUES(1),(2),(3); "79 "ANALYZE;",80 0, 0, &errmsg81 );82 if( rc!=SQLITE_OK ){83 /* Try a simpler ANALYZE in case of errors */84 if( errmsg ) sqlite3_free(errmsg);85 sqlite3_exec(db, "ANALYZE;", 0, 0, 0);86 }else if( errmsg ){87 sqlite3_free(errmsg);88 }89}90 91static void populate_aux_stats(sqlite3 *db, const char *zAux){92 char *sql = sqlite3_mprintf(93 "ATTACH ':memory:' AS %Q; "94 "CREATE TABLE IF NOT EXISTS %Q.t2(y); "95 "INSERT INTO %Q.t2(y) VALUES(1),(2); "96 "ANALYZE %Q;", zAux, zAux, zAux, zAux);97 if( sql ){98 sqlite3_exec(db, sql, 0, 0, 0);99 sqlite3_free(sql);100 }101}102 103void setUp(void) {104 /* no-op */105}106 107void tearDown(void) {108 /* no-op */109}110 111static void init_parse(sqlite3 *db, Parse *p){112 memset(p, 0, sizeof(*p));113 p->db = db;114 p->rc = SQLITE_OK;115 p->nErr = 0;116}117 118/* Test 1: No stats present -> no DELETEs emitted */119void test_sqlite3ClearStatTables_no_stats_emits_nothing(void){120 sqlite3 *db = 0;121 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));122 AuthLog log; memset(&log, 0, sizeof(log));123 sqlite3_set_authorizer(db, auth_cb, &log);124 125 Parse p;126 init_parse(db, &p);127 128 int iMain = findDbIndex(db, "main");129 TEST_ASSERT_TRUE(iMain >= 0);130 131 test_sqlite3ClearStatTables(&p, iMain, "tbl", "t1");132 133 TEST_ASSERT_EQUAL_INT(0, log.n);134 TEST_ASSERT_EQUAL_INT(0, p.nErr);135 136 sqlite3_set_authorizer(db, 0, 0);137 sqlite3_close(db);138}139 140/* Test 2: Main DB stats present -> DELETE emitted for each existing sqlite_statN */141void test_sqlite3ClearStatTables_main_stats_deletes_issued(void){142 sqlite3 *db = 0;143 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));144 145 populate_main_stats(db);146 147 int expected = count_existing_stat_tables(db, "main");148 TEST_ASSERT_TRUE_MESSAGE(expected >= 1, "Expected at least one sqlite_stat table in main schema after ANALYZE");149 150 AuthLog log; memset(&log, 0, sizeof(log));151 sqlite3_set_authorizer(db, auth_cb, &log);152 153 Parse p;154 init_parse(db, &p);155 156 int iMain = findDbIndex(db, "main");157 TEST_ASSERT_TRUE(iMain >= 0);158 159 test_sqlite3ClearStatTables(&p, iMain, "tbl", "t1");160 161 TEST_ASSERT_EQUAL_INT(expected, log.n);162 for(int i=0; i<log.n; i++){163 TEST_ASSERT_EQUAL_INT(SQLITE_DELETE, log.ev[i].code);164 TEST_ASSERT_TRUE(!sqlite3StrICmp(log.ev[i].db, "main"));165 /* Table must be one of sqlite_stat1..sqlite_stat4 that exist */166 int seen = 0;167 for(int n=1; n<=4; n++){168 char zTab[24];169 sqlite3_snprintf(sizeof(zTab), zTab, "sqlite_stat%d", n);170 if( stat_exists(db, "main", n) && sqlite3StrICmp(log.ev[i].tbl, zTab)==0 ){171 seen = 1;172 break;173 }174 }175 TEST_ASSERT_TRUE_MESSAGE(seen, "DELETE issued for unexpected table name in main schema");176 }177 178 sqlite3_set_authorizer(db, 0, 0);179 sqlite3_close(db);180}181 182/* Test 3: Attached DB stats present -> DELETE emitted in correct schema */183void test_sqlite3ClearStatTables_attached_db_schema_correct(void){184 sqlite3 *db = 0;185 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));186 187 const char *zAux = "aux";188 populate_aux_stats(db, zAux);189 190 int expected = count_existing_stat_tables(db, zAux);191 TEST_ASSERT_TRUE_MESSAGE(expected >= 1, "Expected at least one sqlite_stat table in aux schema after ANALYZE aux");192 193 int iAux = findDbIndex(db, zAux);194 TEST_ASSERT_TRUE_MESSAGE(iAux >= 0, "Attached schema index not found");195 196 AuthLog log; memset(&log, 0, sizeof(log));197 sqlite3_set_authorizer(db, auth_cb, &log);198 199 Parse p;200 init_parse(db, &p);201 202 test_sqlite3ClearStatTables(&p, iAux, "idx", "i2_we_dont_care");203 204 TEST_ASSERT_EQUAL_INT(expected, log.n);205 for(int i=0; i<log.n; i++){206 TEST_ASSERT_EQUAL_INT(SQLITE_DELETE, log.ev[i].code);207 TEST_ASSERT_TRUE(!sqlite3StrICmp(log.ev[i].db, zAux));208 int seen = 0;209 for(int n=1; n<=4; n++){210 char zTab[24];211 sqlite3_snprintf(sizeof(zTab), zTab, "sqlite_stat%d", n);212 if( stat_exists(db, zAux, n) && sqlite3StrICmp(log.ev[i].tbl, zTab)==0 ){213 seen = 1;214 break;215 }216 }217 TEST_ASSERT_TRUE_MESSAGE(seen, "DELETE issued for unexpected table name in aux schema");218 }219 220 sqlite3_set_authorizer(db, 0, 0);221 sqlite3_close(db);222}223 224/* Test 4: Pre-existing parse error suppresses nested deletes */225void test_sqlite3ClearStatTables_preexisting_error_suppresses(void){226 sqlite3 *db = 0;227 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));228 229 populate_main_stats(db);230 int expected = count_existing_stat_tables(db, "main");231 TEST_ASSERT_TRUE(expected >= 1);232 233 AuthLog log; memset(&log, 0, sizeof(log));234 sqlite3_set_authorizer(db, auth_cb, &log);235 236 Parse p;237 init_parse(db, &p);238 p.nErr = 1; /* Simulate existing error */239 240 int iMain = findDbIndex(db, "main");241 test_sqlite3ClearStatTables(&p, iMain, "tbl", "t1");242 243 TEST_ASSERT_EQUAL_INT(0, log.n); /* No nested parse -> no authorizer hits */244 245 sqlite3_set_authorizer(db, 0, 0);246 sqlite3_close(db);247}248 249/* Test 5: Quoting-safe zName with embedded quotes and spaces */250void test_sqlite3ClearStatTables_weird_name_quoted_ok(void){251 sqlite3 *db = 0;252 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));253 254 populate_main_stats(db);255 int expected = count_existing_stat_tables(db, "main");256 TEST_ASSERT_TRUE(expected >= 1);257 258 AuthLog log; memset(&log, 0, sizeof(log));259 sqlite3_set_authorizer(db, auth_cb, &log);260 261 Parse p;262 init_parse(db, &p);263 264 int iMain = findDbIndex(db, "main");265 const char *zWeird = "weird ' name \" with spaces";266 test_sqlite3ClearStatTables(&p, iMain, "tbl", zWeird);267 268 TEST_ASSERT_EQUAL_INT(expected, log.n);269 TEST_ASSERT_EQUAL_INT(0, p.nErr);270 for(int i=0; i<log.n; i++){271 TEST_ASSERT_TRUE(!sqlite3StrICmp(log.ev[i].db, "main"));272 /* Table names still must be sqlite_statN */273 int ok = 0;274 for(int n=1; n<=4; n++){275 char zTab[24];276 sqlite3_snprintf(sizeof(zTab), zTab, "sqlite_stat%d", n);277 if( stat_exists(db, "main", n) && sqlite3StrICmp(log.ev[i].tbl, zTab)==0 ){278 ok = 1; break;279 }280 }281 TEST_ASSERT_TRUE(ok);282 }283 284 sqlite3_set_authorizer(db, 0, 0);285 sqlite3_close(db);286}287 288int main(void){289 UNITY_BEGIN();290 RUN_TEST(test_sqlite3ClearStatTables_no_stats_emits_nothing);291 RUN_TEST(test_sqlite3ClearStatTables_main_stats_deletes_issued);292 RUN_TEST(test_sqlite3ClearStatTables_attached_db_schema_correct);293 RUN_TEST(test_sqlite3ClearStatTables_preexisting_error_suppresses);294 RUN_TEST(test_sqlite3ClearStatTables_weird_name_quoted_ok);295 return UNITY_END();296}