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 source as noted) */8extern int test_btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX);9 10static sqlite3 *gDb = NULL;11 12/* Helpers */13 14static int exec_sql(const char *zSql){15 char *zErr = NULL;16 int rc = sqlite3_exec(gDb, zSql, 0, 0, &zErr);17 if( zErr ){18 /* Useful to see errors if running interactively */19 /* fprintf(stderr, "SQL error: %s\n", zErr); */20 sqlite3_free(zErr);21 }22 return rc;23}24 25static Pgno get_rootpage_for(const char *zName){26 Pgno root = 0;27 sqlite3_stmt *pStmt = NULL;28 int rc = sqlite3_prepare_v2(gDb,29 "SELECT rootpage FROM sqlite_schema WHERE name=?1", -1, &pStmt, 0);30 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);31 rc = sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);32 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);33 if( sqlite3_step(pStmt)==SQLITE_ROW ){34 root = (Pgno)sqlite3_column_int(pStmt, 0);35 }36 sqlite3_finalize(pStmt);37 return root;38}39 40static void open_write_cursor(Btree *pBt, Pgno root, BtCursor *pCur){41 int rc;42 sqlite3BtreeEnter(pBt);43 rc = sqlite3BtreeBeginTrans(pBt, 1, 0);44 sqlite3BtreeLeave(pBt);45 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);46 47 sqlite3BtreeEnter(pBt);48 rc = sqlite3BtreeCursor(pBt, root, BTREE_WRCSR, 0, pCur);49 sqlite3BtreeLeave(pBt);50 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);51}52 53static void close_cursor(BtCursor *pCur){54 int rc = sqlite3BtreeCloseCursor(pCur);55 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);56}57 58static void move_to_row(BtCursor *pCur, i64 rowid){59 int rc, res = 0;60 sqlite3BtreeEnter(pCur->pBtree);61 rc = sqlite3BtreeTableMoveto(pCur, rowid, 0, &res);62 sqlite3BtreeLeave(pCur->pBtree);63 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);64 TEST_ASSERT_EQUAL_INT(0, res); /* exact match */65}66 67static u32 payload_size(BtCursor *pCur){68 u32 n;69 sqlite3BtreeEnter(pCur->pBtree);70 n = sqlite3BtreePayloadSize(pCur);71 sqlite3BtreeLeave(pCur->pBtree);72 return n;73}74 75static void read_payload(BtCursor *pCur, u32 off, u32 amt, void *pBuf){76 int rc;77 sqlite3BtreeEnter(pCur->pBtree);78 rc = sqlite3BtreePayload(pCur, off, amt, pBuf);79 sqlite3BtreeLeave(pCur->pBtree);80 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);81}82 83static void build_payload(BtreePayload *px, const void *pData, int nData){84 memset(px, 0, sizeof(*px));85 px->pKey = NULL;86 px->nKey = 0;87 px->pData = pData;88 px->nData = nData;89 px->nZero = 0;90 px->nMem = 0;91 px->aMem = NULL;92}93 94/* Unity hooks */95 96void setUp(void) {97 int rc = sqlite3_initialize();98 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);99 rc = sqlite3_open(":memory:", &gDb);100 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);101 /* Ensure a predictable pagesize (default usually 4096, but set explicitly) */102 TEST_ASSERT_EQUAL_INT(SQLITE_OK, exec_sql("PRAGMA page_size=4096"));103 TEST_ASSERT_EQUAL_INT(SQLITE_OK, exec_sql("PRAGMA journal_mode=OFF"));104}105 106void tearDown(void) {107 if( gDb ){108 sqlite3_close(gDb);109 gDb = NULL;110 }111}112 113/* Tests */114 115void test_btreeOverwriteCell_local_updates_data(void){116 /* Create table and insert small blob that fits locally (no overflow). */117 TEST_ASSERT_EQUAL_INT(SQLITE_OK, exec_sql("CREATE TABLE t(x BLOB)"));118 TEST_ASSERT_EQUAL_INT(SQLITE_OK,119 exec_sql("INSERT INTO t(rowid,x) VALUES(1, zeroblob(100))"));120 121 Btree *pBt = gDb->aDb[0].pBt;122 Pgno root = get_rootpage_for("t");123 TEST_ASSERT_TRUE(root>0);124 125 BtCursor cur;126 open_write_cursor(pBt, root, &cur);127 move_to_row(&cur, 1);128 129 /* Populate cur.info by calling payload-size API. */130 u32 nOrig = payload_size(&cur);131 TEST_ASSERT_EQUAL_UINT32(100u, nOrig);132 133 /* Overwrite with new 100-byte pattern */134 unsigned char newData[100];135 for(int i=0;i<100;i++) newData[i] = (unsigned char)(i ^ 0x5A);136 137 BtreePayload px;138 build_payload(&px, newData, sizeof(newData));139 140 int rc = test_btreeOverwriteCell(&cur, &px);141 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);142 143 /* Verify the payload was updated */144 unsigned char got[100];145 read_payload(&cur, 0, sizeof(got), got);146 TEST_ASSERT_EQUAL_UINT8_ARRAY(newData, got, sizeof(got));147 148 close_cursor(&cur);149 150 /* Commit */151 sqlite3BtreeEnter(pBt);152 rc = sqlite3BtreeCommit(pBt);153 sqlite3BtreeLeave(pBt);154 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);155}156 157void test_btreeOverwriteCell_overflow_updates_data(void){158 /* Create a table and insert a large blob that forces overflow. */159 TEST_ASSERT_EQUAL_INT(SQLITE_OK, exec_sql("CREATE TABLE t2(x BLOB)"));160 /* 5000 bytes likely exceeds maxLocal for 4KiB pages, forcing overflow */161 TEST_ASSERT_EQUAL_INT(SQLITE_OK,162 exec_sql("INSERT INTO t2(rowid,x) VALUES(2, zeroblob(5000))"));163 164 Btree *pBt = gDb->aDb[0].pBt;165 Pgno root = get_rootpage_for("t2");166 TEST_ASSERT_TRUE(root>0);167 168 BtCursor cur;169 open_write_cursor(pBt, root, &cur);170 move_to_row(&cur, 2);171 172 u32 nOrig = payload_size(&cur);173 TEST_ASSERT_TRUE(nOrig >= 5000u);174 175 /* Build a new data buffer of equal size with a distinct pattern. */176 unsigned char *newBig = (unsigned char*)sqlite3_malloc(nOrig);177 TEST_ASSERT_NOT_NULL(newBig);178 for(u32 i=0;i<nOrig;i++){179 newBig[i] = (unsigned char)((i*131) & 0xFF);180 }181 182 BtreePayload px;183 build_payload(&px, newBig, (int)nOrig);184 185 int rc = test_btreeOverwriteCell(&cur, &px);186 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);187 188 /* Verify the entire payload matches */189 unsigned char *got = (unsigned char*)sqlite3_malloc(nOrig);190 TEST_ASSERT_NOT_NULL(got);191 read_payload(&cur, 0, nOrig, got);192 TEST_ASSERT_EQUAL_UINT8_ARRAY(newBig, got, nOrig);193 194 sqlite3_free(got);195 sqlite3_free(newBig);196 197 close_cursor(&cur);198 199 sqlite3BtreeEnter(pBt);200 rc = sqlite3BtreeCommit(pBt);201 sqlite3BtreeLeave(pBt);202 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);203}204 205void test_btreeOverwriteCell_invalid_bounds_returns_corrupt(void){206 /* Set up a small-row table and cursor, then corrupt pCur->info to trigger207 the bounds checks in btreeOverwriteCell(). */208 TEST_ASSERT_EQUAL_INT(SQLITE_OK, exec_sql("CREATE TABLE t3(x BLOB)"));209 TEST_ASSERT_EQUAL_INT(SQLITE_OK,210 exec_sql("INSERT INTO t3(rowid,x) VALUES(3, zeroblob(32))"));211 212 Btree *pBt = gDb->aDb[0].pBt;213 Pgno root = get_rootpage_for("t3");214 TEST_ASSERT_TRUE(root>0);215 216 BtCursor cur;217 open_write_cursor(pBt, root, &cur);218 move_to_row(&cur, 3);219 220 /* Initialize info */221 (void)payload_size(&cur);222 223 /* Corrupt the nLocal so that pPayload + nLocal > aDataEnd */224 sqlite3BtreeEnter(pBt);225 u8 *end = cur.pPage->aDataEnd;226 uptr payloadEnd = (uptr)cur.info.pPayload + (uptr)cur.info.nLocal;227 if( payloadEnd <= (uptr)end ){228 /* Inflate nLocal to exceed aDataEnd by at least 1 */229 cur.info.nLocal += (int)((uptr)end - payloadEnd) + 1;230 }231 sqlite3BtreeLeave(pBt);232 233 unsigned char buf[32];234 memset(buf, 0xAB, sizeof(buf));235 BtreePayload px;236 build_payload(&px, buf, sizeof(buf));237 238 int rc = test_btreeOverwriteCell(&cur, &px);239 /* Expect a corruption error */240 TEST_ASSERT_EQUAL_INT(SQLITE_CORRUPT, (rc & 0xff));241 242 close_cursor(&cur);243 244 sqlite3BtreeEnter(pBt);245 rc = sqlite3BtreeCommit(pBt);246 sqlite3BtreeLeave(pBt);247 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);248}249 250void test_btreeOverwriteCell_same_data_noop_ok(void){251 /* Insert a local-only blob and overwrite with identical content. */252 TEST_ASSERT_EQUAL_INT(SQLITE_OK, exec_sql("CREATE TABLE t4(x BLOB)"));253 TEST_ASSERT_EQUAL_INT(SQLITE_OK,254 exec_sql("INSERT INTO t4(rowid,x) VALUES(4, randomblob(64))"));255 256 Btree *pBt = gDb->aDb[0].pBt;257 Pgno root = get_rootpage_for("t4");258 TEST_ASSERT_TRUE(root>0);259 260 BtCursor cur;261 open_write_cursor(pBt, root, &cur);262 move_to_row(&cur, 4);263 264 u32 n = payload_size(&cur);265 TEST_ASSERT_EQUAL_UINT32(64u, n);266 267 unsigned char orig[64];268 read_payload(&cur, 0, sizeof(orig), orig);269 270 BtreePayload px;271 build_payload(&px, orig, sizeof(orig));272 273 int rc = test_btreeOverwriteCell(&cur, &px);274 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);275 276 /* Verify unchanged content */277 unsigned char got[64];278 read_payload(&cur, 0, sizeof(got), got);279 TEST_ASSERT_EQUAL_UINT8_ARRAY(orig, got, sizeof(orig));280 281 close_cursor(&cur);282 283 sqlite3BtreeEnter(pBt);284 rc = sqlite3BtreeCommit(pBt);285 sqlite3BtreeLeave(pBt);286 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);287}288 289int main(void){290 UNITY_BEGIN();291 RUN_TEST(test_btreeOverwriteCell_local_updates_data);292 RUN_TEST(test_btreeOverwriteCell_overflow_updates_data);293 RUN_TEST(test_btreeOverwriteCell_invalid_bounds_returns_corrupt);294 RUN_TEST(test_btreeOverwriteCell_same_data_noop_ok);295 return UNITY_END();296}