AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3#include <string.h>4#include <stdlib.h>5 6/* Wrapper for the static function provided by the build */7extern void test_convertToWithoutRowidTable(Parse *pParse, Table *pTab);8 9/* Helpers */10static sqlite3* open_db(void){11 sqlite3 *db = 0;12 int rc = sqlite3_open(":memory:", &db);13 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);14 TEST_ASSERT_NOT_NULL(db);15 return db;16}17 18static Table* alloc_table(sqlite3 *db, const char *zName, int nCol){19 Table *pTab = (Table*)sqlite3DbMallocZero(db, sizeof(Table));20 TEST_ASSERT_NOT_NULL(pTab);21 pTab->zName = sqlite3DbStrDup(db, zName);22 pTab->pSchema = db->aDb[0].pSchema;23 pTab->nCol = nCol;24 pTab->nNVCol = nCol; /* will adjust if virtuals present */25 pTab->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nCol);26 TEST_ASSERT_NOT_NULL(pTab->aCol);27 for(int i=0; i<nCol; i++){28 char namebuf[16];29 sqlite3_snprintf(sizeof(namebuf), namebuf, "c%d", i);30 pTab->aCol[i].zCnName = sqlite3DbStrDup(db, namebuf);31 pTab->aCol[i].notNull = OE_None;32 pTab->aCol[i].szEst = 1; /* small default */33 }34 return pTab;35}36 37static Index* alloc_index(sqlite3 *db, Table *pTab, int nCol){38 Index *pIdx = (Index*)sqlite3DbMallocZero(db, sizeof(Index));39 TEST_ASSERT_NOT_NULL(pIdx);40 pIdx->pTable = pTab;41 pIdx->nColumn = (u16)nCol;42 pIdx->azColl = (const char**)sqlite3DbMallocZero(db, sizeof(char*)*nCol);43 pIdx->aiColumn = (i16*)sqlite3DbMallocZero(db, sizeof(i16)*nCol);44 pIdx->aSortOrder = (u8*)sqlite3DbMallocZero(db, nCol);45 pIdx->aiRowLogEst = (LogEst*)sqlite3DbMallocZero(db, sizeof(LogEst)*nCol);46 TEST_ASSERT_NOT_NULL(pIdx->azColl);47 TEST_ASSERT_NOT_NULL(pIdx->aiColumn);48 TEST_ASSERT_NOT_NULL(pIdx->aSortOrder);49 TEST_ASSERT_NOT_NULL(pIdx->aiRowLogEst);50 return pIdx;51}52 53static void set_pk(Index *pPk, int nKeyCol, const int *cols, const u8 *sort){54 pPk->idxType = SQLITE_IDXTYPE_PRIMARYKEY;55 pPk->nKeyCol = nKeyCol;56 for(int i=0;i<nKeyCol;i++){57 pPk->aiColumn[i] = (i16)cols[i];58 pPk->azColl[i] = sqlite3StrBINARY;59 pPk->aSortOrder[i] = sort ? sort[i] : 0;60 }61}62 63void setUp(void) {64 /* empty */65}66 67void tearDown(void) {68 /* empty */69}70 71/* Test 1: NOT NULL enforcement, PK flags, covering PK, and tnum assignment */72void test_convertToWithoutRowidTable_basic_notnull_and_covering(void){73 sqlite3 *db = open_db();74 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = db;75 76 Table *pTab = alloc_table(db, "t", 3);77 pTab->tabFlags |= TF_WithoutRowid; /* already determined by parser */78 pTab->tnum = 7; /* table root-page */79 80 /* Mark columns 0 and 2 as part of PRIMARY KEY */81 pTab->aCol[0].colFlags |= COLFLAG_PRIMKEY;82 pTab->aCol[2].colFlags |= COLFLAG_PRIMKEY;83 84 /* Create PRIMARY KEY index with key columns (0,2) */85 Index *pPk = alloc_index(db, pTab, 2);86 int pkcols[2] = {0,2};87 set_pk(pPk, 2, pkcols, NULL);88 pTab->pIndex = pPk;89 90 /* Pre-conditions */91 TEST_ASSERT_EQUAL_INT(OE_None, pTab->aCol[0].notNull);92 TEST_ASSERT_EQUAL_INT(OE_None, pTab->aCol[2].notNull);93 TEST_ASSERT_TRUE((pTab->tabFlags & TF_HasNotNull)==0);94 TEST_ASSERT_NOT_NULL(sqlite3StrBINARY);95 96 test_convertToWithoutRowidTable(&parse, pTab);97 98 /* NOT NULL enforced for PK columns and TF_HasNotNull set */99 TEST_ASSERT_EQUAL_INT(OE_Abort, pTab->aCol[0].notNull);100 TEST_ASSERT_EQUAL_INT(OE_Abort, pTab->aCol[2].notNull);101 TEST_ASSERT_TRUE((pTab->tabFlags & TF_HasNotNull)!=0);102 103 /* PK flags */104 TEST_ASSERT_EQUAL_INT(1, pTab->pIndex->isCovering);105 TEST_ASSERT_EQUAL_INT(1, pTab->pIndex->uniqNotNull);106 107 /* PK rootpage becomes table rootpage */108 TEST_ASSERT_EQUAL_INT(pTab->tnum, pTab->pIndex->tnum);109 110 /* PK should be covering: includes all non-virtual columns */111 TEST_ASSERT_EQUAL_INT(3, pTab->pIndex->nColumn);112 TEST_ASSERT_EQUAL_INT(1, pTab->pIndex->aiColumn[2]);113 TEST_ASSERT_EQUAL_PTR(sqlite3StrBINARY, pTab->pIndex->azColl[2]);114 115 /* Verify colNotIdxed indicates column 1 is covered (bit cleared) */116 Bitmask mask1 = MASKBIT(1);117 TEST_ASSERT_EQUAL_UINT64(0, (pTab->pIndex->colNotIdxed & mask1));118}119 120/* Test 2: Deduplicate repeated columns in PRIMARY KEY */121void test_convertToWithoutRowidTable_deduplicates_pk_columns(void){122 sqlite3 *db = open_db();123 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = db;124 125 Table *pTab = alloc_table(db, "tdup", 3);126 pTab->tabFlags |= TF_WithoutRowid;127 pTab->tnum = 5;128 pTab->aCol[0].colFlags |= COLFLAG_PRIMKEY;129 pTab->aCol[1].colFlags |= COLFLAG_PRIMKEY;130 pTab->aCol[2].colFlags |= COLFLAG_PRIMKEY;131 132 /* PK index with duplicates: (0,1,0,1,2) */133 Index *pPk = alloc_index(db, pTab, 5);134 int cols[5] = {0,1,0,1,2};135 set_pk(pPk, 5, cols, NULL);136 pTab->pIndex = pPk;137 138 test_convertToWithoutRowidTable(&parse, pTab);139 140 /* PK columns deduplicated to (0,1,2) */141 TEST_ASSERT_EQUAL_INT(3, pTab->pIndex->nKeyCol);142 TEST_ASSERT_EQUAL_INT(0, pTab->pIndex->aiColumn[0]);143 TEST_ASSERT_EQUAL_INT(1, pTab->pIndex->aiColumn[1]);144 TEST_ASSERT_EQUAL_INT(2, pTab->pIndex->aiColumn[2]);145 146 /* Still covering all non-virtual columns after append */147 TEST_ASSERT_TRUE(pTab->pIndex->nColumn >= pTab->nNVCol);148}149 150/* Test 3: Replace rowid tail of other indexes with PRIMARY KEY columns */151void test_convertToWithoutRowidTable_updates_other_index_tails(void){152 sqlite3 *db = open_db();153 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = db;154 155 Table *pTab = alloc_table(db, "tix", 3);156 pTab->tabFlags |= TF_WithoutRowid;157 pTab->tnum = 9;158 159 /* PK on columns (0,2), with second column DESC to trigger bAscKeyBug */160 pTab->aCol[0].colFlags |= COLFLAG_PRIMKEY;161 pTab->aCol[2].colFlags |= COLFLAG_PRIMKEY;162 Index *pPk = alloc_index(db, pTab, 2);163 int pkcols[2] = {0,2};164 u8 sort[2] = {0,1};165 set_pk(pPk, 2, pkcols, sort);166 167 /* Another index on column 1, with implicit rowid tail */168 Index *pIdx = alloc_index(db, pTab, 2);169 pIdx->nKeyCol = 1;170 pIdx->aiColumn[0] = 1;171 pIdx->azColl[0] = sqlite3StrBINARY;172 pIdx->aSortOrder[0] = 0;173 pIdx->aiColumn[1] = XN_ROWID;174 pIdx->azColl[1] = sqlite3StrBINARY;175 176 /* Chain indexes: head at PK so IsPrimaryKeyIndex is encountered */177 pTab->pIndex = pPk;178 pPk->pNext = pIdx;179 180 test_convertToWithoutRowidTable(&parse, pTab);181 182 /* The other index tail should be replaced by PK cols (0,2) */183 TEST_ASSERT_EQUAL_INT(3, pIdx->nColumn);184 TEST_ASSERT_EQUAL_INT(1, pIdx->aiColumn[0]);185 TEST_ASSERT_EQUAL_INT(0, pIdx->aiColumn[1]);186 TEST_ASSERT_EQUAL_INT(2, pIdx->aiColumn[2]);187 TEST_ASSERT_EQUAL_PTR(pPk->azColl[0], pIdx->azColl[1]);188 TEST_ASSERT_EQUAL_PTR(pPk->azColl[1], pIdx->azColl[2]);189 /* bAscKeyBug set because PK column 2 had DESC */190 TEST_ASSERT_EQUAL_INT(1, pIdx->bAscKeyBug);191}192 193/* Test 4: VDBE opcode patches - CreateBtree P3 and Noop to Goto */194void test_convertToWithoutRowidTable_vdbe_patching(void){195 sqlite3 *db = open_db();196 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = db;197 198 Table *pTab = alloc_table(db, "tvdb", 2);199 pTab->tabFlags |= TF_WithoutRowid;200 pTab->aCol[0].colFlags |= COLFLAG_PRIMKEY;201 Index *pPk = alloc_index(db, pTab, 1);202 int pkc[1] = {0};203 set_pk(pPk, 1, pkc, NULL);204 pTab->pIndex = pPk;205 206 Vdbe *v = sqlite3GetVdbe(&parse);207 TEST_ASSERT_NOT_NULL(v);208 int addrNoop = sqlite3VdbeAddOp0(v, OP_Noop);209 int addrCr = sqlite3VdbeAddOp3(v, OP_CreateBtree, 0, 1, BTREE_INTKEY);210 /* Wire up parse state so convertToWithoutRowidTable can patch these */211 parse.u1.cr.addrCrTab = addrCr;212 pPk->tnum = (Pgno)addrNoop; /* convertToWithoutRowidTable will change opcode here */213 214 test_convertToWithoutRowidTable(&parse, pTab);215 216 const VdbeOp *opCr = sqlite3VdbeGetOp(v, addrCr);217 TEST_ASSERT_NOT_NULL(opCr);218 TEST_ASSERT_EQUAL_INT(OP_CreateBtree, opCr->opcode);219 TEST_ASSERT_EQUAL_INT(BTREE_BLOBKEY, opCr->p3);220 221 const VdbeOp *opNoop = sqlite3VdbeGetOp(v, addrNoop);222 TEST_ASSERT_NOT_NULL(opNoop);223 TEST_ASSERT_EQUAL_INT(OP_Goto, opNoop->opcode);224}225 226/* Test 5: imposterTable skips NOT NULL and uniqNotNull adjustments */227void test_convertToWithoutRowidTable_imposter_skips_notnull_and_uniq(void){228 sqlite3 *db = open_db();229 db->init.imposterTable = 1; /* signal imposter */230 Parse parse; memset(&parse, 0, sizeof(parse)); parse.db = db;231 232 Table *pTab = alloc_table(db, "timp", 2);233 pTab->tabFlags |= TF_WithoutRowid;234 pTab->aCol[0].colFlags |= COLFLAG_PRIMKEY;235 pTab->aCol[1].colFlags |= COLFLAG_PRIMKEY;236 237 Index *pPk = alloc_index(db, pTab, 2);238 int pkc[2] = {0,1};239 set_pk(pPk, 2, pkc, NULL);240 pPk->uniqNotNull = 0; /* pre-set */241 pTab->pIndex = pPk;242 243 test_convertToWithoutRowidTable(&parse, pTab);244 245 /* NOT NULL not forced for imposter table */246 TEST_ASSERT_EQUAL_INT(OE_None, pTab->aCol[0].notNull);247 TEST_ASSERT_EQUAL_INT(OE_None, pTab->aCol[1].notNull);248 /* uniqNotNull not forced to 1 */249 TEST_ASSERT_EQUAL_INT(0, pPk->uniqNotNull);250}251 252int main(void){253 UNITY_BEGIN();254 RUN_TEST(test_convertToWithoutRowidTable_basic_notnull_and_covering);255 RUN_TEST(test_convertToWithoutRowidTable_deduplicates_pk_columns);256 RUN_TEST(test_convertToWithoutRowidTable_updates_other_index_tails);257 RUN_TEST(test_convertToWithoutRowidTable_vdbe_patching);258 RUN_TEST(test_convertToWithoutRowidTable_imposter_skips_notnull_and_uniq);259 return UNITY_END();260}