AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <stdlib.h>4#include <string.h>5 6/* Wrapper for calling the static function under test. */7extern int test_analysisLoader(void *pData, int argc, char **argv, char **NotUsed);8 9/* Use an opaque struct that matches the first fields of analysisInfo */10typedef struct {11 sqlite3 *db;12 const char *zDatabase;13} MyAnalysisInfo;14 15static sqlite3 *gDb = NULL;16 17static void execSql(sqlite3 *db, const char *zSql){18 char *zErr = NULL;19 int rc = sqlite3_exec(db, zSql, 0, 0, &zErr);20 if( rc!=SQLITE_OK ){21 /* Help debugging test failures */22 const char *msg = zErr ? zErr : "(no error message)";23 TEST_FAIL_MESSAGE(msg);24 }25 sqlite3_free(zErr);26}27 28void setUp(void) {29 int rc = sqlite3_open(":memory:", &gDb);30 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);31 TEST_ASSERT_NOT_NULL(gDb);32}33 34void tearDown(void) {35 if( gDb ){36 sqlite3_close(gDb);37 gDb = NULL;38 }39}40 41static char *dupz(const char *z){42 /* Allocate mutable copy for argv[2] */43 size_t n = strlen(z);44 char *out = (char*)sqlite3_malloc64(n+1);45 TEST_ASSERT_NOT_NULL(out);46 memcpy(out, z, n+1);47 return out;48}49 50/* Basic index stat row updates index and table */51void test_analysisLoader_index_basic(void){52 execSql(gDb, "CREATE TABLE t1(a, b);");53 execSql(gDb, "CREATE INDEX i1 ON t1(a, b);");54 55 Table *pTab = sqlite3FindTable(gDb, "t1", "main");56 Index *pIdx = sqlite3FindIndex(gDb, "i1", "main");57 TEST_ASSERT_NOT_NULL(pTab);58 TEST_ASSERT_NOT_NULL(pIdx);59 60 MyAnalysisInfo info = { gDb, "main" };61 char *azArg[3];62 azArg[0] = (char*)"t1";63 azArg[1] = (char*)"i1";64 azArg[2] = dupz("1000 10 1");65 66 int rc = test_analysisLoader(&info, 3, azArg, NULL);67 TEST_ASSERT_EQUAL_INT(0, rc);68 69 TEST_ASSERT_EQUAL_INT(1, pIdx->hasStat1);70 TEST_ASSERT_EQUAL_INT(0, pIdx->bUnordered);71 TEST_ASSERT_TRUE((pTab->tabFlags & TF_HasStat1)!=0);72 TEST_ASSERT_EQUAL_INT(pIdx->aiRowLogEst[0], pTab->nRowLogEst);73 74 sqlite3_free(azArg[2]);75}76 77/* "unordered" suffix sets bUnordered */78void test_analysisLoader_index_unordered(void){79 execSql(gDb, "CREATE TABLE t1(a, b);");80 execSql(gDb, "CREATE INDEX i1 ON t1(a, b);");81 82 Table *pTab = sqlite3FindTable(gDb, "t1", "main");83 Index *pIdx = sqlite3FindIndex(gDb, "i1", "main");84 TEST_ASSERT_NOT_NULL(pTab);85 TEST_ASSERT_NOT_NULL(pIdx);86 87 MyAnalysisInfo info = { gDb, "main" };88 char *azArg[3];89 azArg[0] = (char*)"t1";90 azArg[1] = (char*)"i1";91 azArg[2] = dupz("100 5 1 unordered");92 93 int rc = test_analysisLoader(&info, 3, azArg, NULL);94 TEST_ASSERT_EQUAL_INT(0, rc);95 96 TEST_ASSERT_EQUAL_INT(1, pIdx->hasStat1);97 TEST_ASSERT_EQUAL_INT(1, pIdx->bUnordered);98 TEST_ASSERT_TRUE((pTab->tabFlags & TF_HasStat1)!=0);99 TEST_ASSERT_EQUAL_INT(pIdx->aiRowLogEst[0], pTab->nRowLogEst);100 101 sqlite3_free(azArg[2]);102}103 104/* Table-only entry (argv[1]==NULL) sets TF_HasStat1 on table */105void test_analysisLoader_table_only_entry(void){106 execSql(gDb, "CREATE TABLE t2(x);");107 108 Table *pTab = sqlite3FindTable(gDb, "t2", "main");109 TEST_ASSERT_NOT_NULL(pTab);110 LogEst before = pTab->nRowLogEst;111 112 MyAnalysisInfo info = { gDb, "main" };113 char *azArg[3];114 azArg[0] = (char*)"t2";115 azArg[1] = NULL;116 azArg[2] = dupz("12345");117 118 int rc = test_analysisLoader(&info, 3, azArg, NULL);119 TEST_ASSERT_EQUAL_INT(0, rc);120 121 TEST_ASSERT_TRUE((pTab->tabFlags & TF_HasStat1)!=0);122 /* nRowLogEst may or may not equal before depending on decodeIntArray; ensure it is a valid LogEst */123 TEST_ASSERT_TRUE(pTab->nRowLogEst <= 60); /* sanity: LogEst upper bound */124 sqlite3_free(azArg[2]);125}126 127/* Non-existent table should be ignored (return 0, no crash) */128void test_analysisLoader_nonexistent_table(void){129 MyAnalysisInfo info = { gDb, "main" };130 char *azArg[3];131 azArg[0] = (char*)"nosuchtable";132 azArg[1] = (char*)"nosuchindex";133 azArg[2] = dupz("1");134 135 int rc = test_analysisLoader(&info, 3, azArg, NULL);136 TEST_ASSERT_EQUAL_INT(0, rc);137 138 sqlite3_free(azArg[2]);139}140 141/* NULL argv and NULL fields handling */142void test_analysisLoader_null_argv_variants(void){143 MyAnalysisInfo info = { gDb, "main" };144 145 /* argv==NULL */146 int rc = test_analysisLoader(&info, 3, NULL, NULL);147 TEST_ASSERT_EQUAL_INT(0, rc);148 149 /* argv[0]==NULL, argv[2]==NULL */150 char *azArgA[3] = { NULL, NULL, NULL };151 rc = test_analysisLoader(&info, 3, azArgA, NULL);152 TEST_ASSERT_EQUAL_INT(0, rc);153 154 /* argv[2]==NULL */155 execSql(gDb, "CREATE TABLE t1x(a);");156 execSql(gDb, "CREATE INDEX i1x ON t1x(a);");157 char *azArgB[3];158 azArgB[0] = (char*)"t1x";159 azArgB[1] = (char*)"i1x";160 azArgB[2] = NULL;161 rc = test_analysisLoader(&info, 3, azArgB, NULL);162 TEST_ASSERT_EQUAL_INT(0, rc);163}164 165/* argv[1]==argv[0] => use PRIMARY KEY index path */166void test_analysisLoader_pk_index_path(void){167 execSql(gDb, "CREATE TABLE t3(a INTEGER PRIMARY KEY, b);");168 169 Table *pTab = sqlite3FindTable(gDb, "t3", "main");170 TEST_ASSERT_NOT_NULL(pTab);171 Index *pPk = sqlite3PrimaryKeyIndex(pTab);172 TEST_ASSERT_NOT_NULL(pPk);173 174 MyAnalysisInfo info = { gDb, "main" };175 char *azArg[3];176 azArg[0] = (char*)"t3";177 azArg[1] = (char*)"t3"; /* triggers sqlite3PrimaryKeyIndex path */178 azArg[2] = dupz("100 1");179 180 int rc = test_analysisLoader(&info, 3, azArg, NULL);181 TEST_ASSERT_EQUAL_INT(0, rc);182 183 TEST_ASSERT_EQUAL_INT(1, pPk->hasStat1);184 TEST_ASSERT_TRUE((pTab->tabFlags & TF_HasStat1)!=0);185 TEST_ASSERT_EQUAL_INT(pPk->aiRowLogEst[0], pTab->nRowLogEst);186 187 sqlite3_free(azArg[2]);188}189 190/* Partial index stats must not overwrite table nRowLogEst */191void test_analysisLoader_partial_index_does_not_override_table_rowcount(void){192 execSql(gDb, "CREATE TABLE t4(a, b);");193 execSql(gDb, "CREATE INDEX ip ON t4(a) WHERE b IS NOT NULL;");194 195 Table *pTab = sqlite3FindTable(gDb, "t4", "main");196 Index *pPartial = sqlite3FindIndex(gDb, "ip", "main");197 TEST_ASSERT_NOT_NULL(pTab);198 TEST_ASSERT_NOT_NULL(pPartial);199 TEST_ASSERT_NOT_NULL(pPartial->pPartIdxWhere); /* ensure it is partial */200 201 MyAnalysisInfo info = { gDb, "main" };202 203 /* First, set table-only stat to establish a baseline */204 char *azTbl[3];205 azTbl[0] = (char*)"t4";206 azTbl[1] = NULL;207 azTbl[2] = dupz("100");208 int rc = test_analysisLoader(&info, 3, azTbl, NULL);209 TEST_ASSERT_EQUAL_INT(0, rc);210 LogEst base = pTab->nRowLogEst;211 sqlite3_free(azTbl[2]);212 213 /* Now, provide stats for the partial index; should not change table nRowLogEst */214 char *azIdx[3];215 azIdx[0] = (char*)"t4";216 azIdx[1] = (char*)"ip";217 azIdx[2] = dupz("1000 10");218 rc = test_analysisLoader(&info, 3, azIdx, NULL);219 TEST_ASSERT_EQUAL_INT(0, rc);220 TEST_ASSERT_EQUAL_INT(1, pPartial->hasStat1);221 TEST_ASSERT_EQUAL_INT(base, pTab->nRowLogEst); /* unchanged */222 223 sqlite3_free(azIdx[2]);224}225 226int main(void){227 UNITY_BEGIN();228 RUN_TEST(test_analysisLoader_index_basic);229 RUN_TEST(test_analysisLoader_index_unordered);230 RUN_TEST(test_analysisLoader_table_only_entry);231 RUN_TEST(test_analysisLoader_nonexistent_table);232 RUN_TEST(test_analysisLoader_null_argv_variants);233 RUN_TEST(test_analysisLoader_pk_index_path);234 RUN_TEST(test_analysisLoader_partial_index_does_not_override_table_rowcount);235 return UNITY_END();236}