AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <string.h>5#include <stdlib.h>6#include <stdio.h>7 8/* Wrapper for the static function under test (provided by source module) */9extern int test_modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType);10 11static sqlite3 *gDb = NULL;12 13/* Helpers */14static void open_inmemory_db(void){15 int rc = sqlite3_open(":memory:", &gDb);16 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);17}18 19static void close_db(void){20 if( gDb ){21 sqlite3_close(gDb);22 gDb = NULL;23 }24}25 26/* Create a raw table btree using the Btree API and return root pgno */27static Pgno create_raw_table(Btree *pBt){28 int rc;29 Pgno root = 0;30 rc = sqlite3BtreeBeginTrans(pBt, 1, 0);31 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);32 rc = sqlite3BtreeCreateTable(pBt, &root, BTREE_INTKEY|BTREE_LEAFDATA);33 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);34 TEST_ASSERT_TRUE(root>1);35 return root;36}37 38/* Insert a large payload row to force overflow pages */39static void insert_big_row(Btree *pBt, Pgno root, i64 rowid, int nData){40 int rc;41 BtCursor cur;42 memset(&cur, 0, sizeof(cur));43 rc = sqlite3BtreeCursor(pBt, root, BTREE_WRCSR, 0, &cur);44 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);45 46 void *buf = sqlite3_malloc(nData);47 TEST_ASSERT_NOT_NULL(buf);48 memset(buf, 0xAB, nData);49 50 BtreePayload X;51 memset(&X, 0, sizeof(X));52 X.pKey = NULL;53 X.nKey = rowid;54 X.pData = buf;55 X.nData = nData;56 X.nZero = 0;57 58 rc = sqlite3BtreeInsert(&cur, &X, 0, 0);59 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);60 61 sqlite3_free(buf);62 sqlite3BtreeCloseCursor(&cur);63}64 65/* Position cursor at rowid (table btree), return cursor (open) */66static void open_cursor_at_row(Btree *pBt, Pgno root, i64 rowid, BtCursor *pCurOut){67 int rc;68 int res = 0;69 rc = sqlite3BtreeCursor(pBt, root, BTREE_WRCSR, 0, pCurOut);70 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);71 rc = sqlite3BtreeTableMoveto(pCurOut, rowid, 0, &res);72 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);73 TEST_ASSERT_EQUAL_INT_MESSAGE(0, res, "Rowid not found");74 TEST_ASSERT_EQUAL_INT(CURSOR_VALID, pCurOut->eState);75}76 77/* Compute pCell pointer and CellInfo for current cursor position */78static void get_cell_and_info(BtCursor *pCur, u8 **ppCell, CellInfo *pInfo){79 MemPage *pLeaf = pCur->pPage;80 TEST_ASSERT_NOT_NULL(pLeaf);81 TEST_ASSERT_TRUE(pCur->ix < pLeaf->nCell);82 u8 *pCell = pLeaf->aData + ((pLeaf->maskPage & get2byteAligned(&pLeaf->aCellIdx[2*pCur->ix])));83 pLeaf->xParseCell(pLeaf, pCell, pInfo);84 *ppCell = pCell;85}86 87/* Force root to be internal by reducing page-size and inserting many rows */88static Pgno create_deep_tree(Btree *pBt, int pageSize, int nRows){89 int rc;90 /* Set page size before it is fixed (new db) */91 rc = sqlite3BtreeSetPageSize(pBt, pageSize, -1, 0);92 TEST_ASSERT_TRUE(rc==SQLITE_OK || rc==SQLITE_READONLY);93 94 Pgno root = create_raw_table(pBt);95 96 /* Open cursor and insert many small rows */97 BtCursor cur;98 memset(&cur, 0, sizeof(cur));99 rc = sqlite3BtreeCursor(pBt, root, BTREE_WRCSR, 0, &cur);100 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);101 102 BtreePayload X;103 memset(&X, 0, sizeof(X));104 /* Insert empty payload (0 bytes) to maximize number of rows per page */105 X.pKey = NULL;106 X.pData = NULL;107 X.nData = 0;108 X.nZero = 0;109 110 for(int i=1;i<=nRows;i++){111 X.nKey = i;112 rc = sqlite3BtreeInsert(&cur, &X, BTREE_APPEND, 0);113 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);114 }115 sqlite3BtreeCloseCursor(&cur);116 return root;117}118 119void setUp(void){120 /* Nothing by default; each test opens/closes its own db */121}122void tearDown(void){123 close_db();124}125 126/* Test: modifyPagePointer with PTRMAP_OVERFLOW1 successfully updates the overflow pointer in a leaf cell */127void test_modifyPagePointer_overflow1_success_and_mismatch(void){128 open_inmemory_db();129 /* Access main database btree */130 Btree *pBtH = gDb->aDb[0].pBt;131 TEST_ASSERT_NOT_NULL(pBtH);132 BtShared *pBt = pBtH->pBt;133 134 /* Create raw table and big row to ensure overflow */135 Pgno root = create_raw_table(pBtH);136 insert_big_row(pBtH, root, 1, 8000);137 138 /* Open cursor at row 1 */139 BtCursor cur;140 memset(&cur, 0, sizeof(cur));141 open_cursor_at_row(pBtH, root, 1, &cur);142 143 /* Prepare pointers */144 u8 *pCell = NULL;145 CellInfo info;146 memset(&info, 0, sizeof(info));147 get_cell_and_info(&cur, &pCell, &info);148 149 TEST_ASSERT_TRUE_MESSAGE(info.nPayload > info.nLocal, "Payload unexpectedly does not use overflow");150 151 /* Compute first overflow page number from cell */152 Pgno ovfl1 = get4byte(&pCell[info.nSize - 4]);153 154 /* Enter btree mutex and mark leaf page writeable */155 sqlite3BtreeEnter(pBtH);156 int rc = sqlite3PagerWrite(cur.pPage->pDbPage);157 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);158 159 /* New target value */160 Pgno newOvfl = ovfl1 + 1;161 162 /* Call function under test */163 rc = test_modifyPagePointer(cur.pPage, ovfl1, newOvfl, PTRMAP_OVERFLOW1);164 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);165 /* Verify cell last 4 bytes updated */166 TEST_ASSERT_EQUAL_UINT(newOvfl, get4byte(&pCell[info.nSize - 4]));167 168 /* Mismatch case: use a bogus iFrom, expect SQLITE_CORRUPT */169 rc = test_modifyPagePointer(cur.pPage, (Pgno)(newOvfl+123), newOvfl+321, PTRMAP_OVERFLOW1);170 TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, rc);171 172 sqlite3BtreeLeave(pBtH);173 sqlite3BtreeCloseCursor(&cur);174}175 176/* Test: modifyPagePointer with PTRMAP_OVERFLOW2 on an actual overflow page */177void test_modifyPagePointer_overflow2_success_and_mismatch(void){178 open_inmemory_db();179 Btree *pBtH = gDb->aDb[0].pBt;180 TEST_ASSERT_NOT_NULL(pBtH);181 BtShared *pBt = pBtH->pBt;182 183 Pgno root = create_raw_table(pBtH);184 insert_big_row(pBtH, root, 1, 12000);185 186 BtCursor cur;187 memset(&cur, 0, sizeof(cur));188 open_cursor_at_row(pBtH, root, 1, &cur);189 190 /* Get overflow page # from cell */191 u8 *pCell = NULL;192 CellInfo info;193 memset(&info, 0, sizeof(info));194 get_cell_and_info(&cur, &pCell, &info);195 TEST_ASSERT_TRUE(info.nPayload > info.nLocal);196 Pgno ovfl1 = get4byte(&pCell[info.nSize - 4]);197 198 /* Lookup overflow DbPage and wrap as MemPage with required fields */199 DbPage *pDbPage = NULL;200 int rc = sqlite3PagerGet(pBt->pPager, ovfl1, &pDbPage, 0);201 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);202 MemPage *pOvfl = (MemPage*)sqlite3PagerGetExtra(pDbPage);203 pOvfl->pBt = pBt;204 pOvfl->pDbPage = pDbPage;205 pOvfl->aData = (u8*)sqlite3PagerGetData(pDbPage);206 207 /* Read current next pointer and prepare new one */208 Pgno next = get4byte(pOvfl->aData);209 Pgno newNext = next + 2;210 211 sqlite3BtreeEnter(pBtH);212 /* Mark overflow page writeable */213 rc = sqlite3PagerWrite(pDbPage);214 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);215 216 rc = test_modifyPagePointer(pOvfl, next, newNext, PTRMAP_OVERFLOW2);217 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);218 TEST_ASSERT_EQUAL_UINT(newNext, get4byte(pOvfl->aData));219 220 /* Mismatch case */221 rc = test_modifyPagePointer(pOvfl, (Pgno)(newNext+123), (Pgno)(newNext+456), PTRMAP_OVERFLOW2);222 TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, rc);223 224 sqlite3BtreeLeave(pBtH);225 sqlite3PagerUnref(pDbPage);226 sqlite3BtreeCloseCursor(&cur);227}228 229/* Test: modifyPagePointer with PTRMAP_BTREE updates right-child pointer of an internal root page */230void test_modifyPagePointer_btree_success_and_mismatch(void){231 open_inmemory_db();232 Btree *pBtH = gDb->aDb[0].pBt;233 TEST_ASSERT_NOT_NULL(pBtH);234 BtShared *pBt = pBtH->pBt;235 236 /* Create a deeper tree by using small page size and many rows */237 Pgno root = create_deep_tree(pBtH, 512, 2000);238 239 /* Open a cursor and move to root. After moveToRoot(), apPage[0] is root. */240 BtCursor cur;241 memset(&cur, 0, sizeof(cur));242 int rc = sqlite3BtreeCursor(pBtH, root, BTREE_WRCSR, 0, &cur);243 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);244 rc = sqlite3BtreeFirst(&cur, &rc); /* moveToRoot is done inside; ensure valid */245 /* We just need apPage[0] root; ignore rc from First if table empty. */246 247 MemPage *pRoot = cur.apPage[0];248 TEST_ASSERT_NOT_NULL(pRoot);249 /* Ensure it is an internal page; if not, skip this test gracefully */250 if( pRoot->leaf ){251 /* Not enough rows to force an internal root, ignore this test. */252 sqlite3BtreeCloseCursor(&cur);253 TEST_IGNORE_MESSAGE("Root is a leaf; could not build internal root for PTRMAP_BTREE test");254 return;255 }256 257 /* Right child pointer at header+8 */258 Pgno rightChild = get4byte(&pRoot->aData[pRoot->hdrOffset+8]);259 Pgno newRightChild = rightChild + 10;260 261 sqlite3BtreeEnter(pBtH);262 rc = sqlite3PagerWrite(pRoot->pDbPage);263 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);264 265 rc = test_modifyPagePointer(pRoot, rightChild, newRightChild, PTRMAP_BTREE);266 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);267 TEST_ASSERT_EQUAL_UINT(newRightChild, get4byte(&pRoot->aData[pRoot->hdrOffset+8]));268 269 /* Mismatch: use a value not present */270 rc = test_modifyPagePointer(pRoot, (Pgno)(newRightChild+9999), (Pgno)(newRightChild+1), PTRMAP_BTREE);271 TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, rc);272 273 sqlite3BtreeLeave(pBtH);274 sqlite3BtreeCloseCursor(&cur);275}276 277int main(void){278 UNITY_BEGIN();279 RUN_TEST(test_modifyPagePointer_overflow1_success_and_mismatch);280 RUN_TEST(test_modifyPagePointer_overflow2_success_and_mismatch);281 RUN_TEST(test_modifyPagePointer_btree_success_and_mismatch);282 return UNITY_END();283}