AryaWu/sqlite
0
1#include "sqliteInt.h"2#include "unity.h"3 4#include <stdio.h>5#include <string.h>6 7/* Helper: execute SQL and assert success */8static void exec_sql(sqlite3 *db, const char *sql){9 char *zErr = NULL;10 int rc = sqlite3_exec(db, sql, 0, 0, &zErr);11 if( rc!=SQLITE_OK ){12 fprintf(stderr, "SQL error: %s\n", zErr ? zErr : "(null)");13 }14 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);15 if( zErr ) sqlite3_free(zErr);16}17 18/* Helper: get root page number for a table by name */19static Pgno get_root_for_table(sqlite3 *db, const char *zName){20 const char *sql = "SELECT rootpage FROM sqlite_schema WHERE name=?1";21 sqlite3_stmt *pStmt = 0;22 int rc = sqlite3_prepare_v2(db, sql, -1, &pStmt, 0);23 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);24 rc = sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);25 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);26 rc = sqlite3_step(pStmt);27 TEST_ASSERT_EQUAL_INT(SQLITE_ROW, rc);28 int root = sqlite3_column_int(pStmt, 0);29 sqlite3_finalize(pStmt);30 return (Pgno)root;31}32 33/* Helper: insert N rows into table t1(b) */34static void insert_n_rows(sqlite3 *db, int n){35 sqlite3_stmt *pStmt = 0;36 int rc = sqlite3_prepare_v2(db, "INSERT INTO t1(b) VALUES(?1)", -1, &pStmt, 0);37 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);38 for(int i=0; i<n; i++){39 char buf[32];40 /* Keep payload small to encourage many records per page */41 snprintf(buf, sizeof(buf), "x%d", i);42 sqlite3_bind_text(pStmt, 1, buf, -1, SQLITE_TRANSIENT);43 rc = sqlite3_step(pStmt);44 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc==SQLITE_DONE?SQLITE_OK:rc);45 rc = sqlite3_reset(pStmt);46 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);47 sqlite3_clear_bindings(pStmt);48 }49 sqlite3_finalize(pStmt);50}51 52/* Helper: open a read cursor on given table name, returning Btree*, BtCursor*, root page */53static void open_table_cursor(sqlite3 *db, const char *zName, Btree **ppBt, BtCursor **ppCur, Pgno *pRoot){54 *ppBt = db->aDb[0].pBt;55 TEST_ASSERT_NOT_NULL(*ppBt);56 57 /* begin a read transaction */58 sqlite3_mutex_enter(db->mutex);59 int rc = sqlite3BtreeBeginTrans(*ppBt, 0, 0);60 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);61 62 *pRoot = get_root_for_table(db, zName);63 64 int sz = sqlite3BtreeCursorSize();65 BtCursor *pCur = (BtCursor*)sqlite3MallocZero(sz);66 TEST_ASSERT_NOT_NULL(pCur);67 sqlite3BtreeCursorZero(pCur);68 69 rc = sqlite3BtreeCursor(*ppBt, *pRoot, 0, NULL, pCur);70 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);71 72 *ppCur = pCur;73 /* leave db mutex entered; caller will operate and later commit/close */74}75 76/* Helper: close cursor and end transaction */77static void close_table_cursor(sqlite3 *db, Btree *pBt, BtCursor *pCur){78 int rc = sqlite3BtreeCloseCursor(pCur);79 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);80 sqlite3_free(pCur);81 rc = sqlite3BtreeCommit(pBt);82 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);83 sqlite3_mutex_leave(db->mutex);84}85 86void setUp(void) {87 /* Nothing */88}89void tearDown(void) {90 /* Nothing */91}92 93/* Test 1: Empty table -> CURSOR_INVALID -> sqlite3BtreeRowCountEst returns 0 */94void test_sqlite3BtreeRowCountEst_empty_table_returns_zero(void){95 sqlite3 *db = 0;96 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));97 98 exec_sql(db, "CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);");99 100 Btree *pBt = 0;101 BtCursor *pCur = 0;102 Pgno root = 0;103 open_table_cursor(db, "t1", &pBt, &pCur, &root);104 105 /* Move to first: for empty table, eState remains CURSOR_INVALID and rc==SQLITE_OK */106 int isEmpty = 0;107 int rc = sqlite3BtreeFirst(pCur, &isEmpty);108 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);109 TEST_ASSERT_EQUAL_INT(1, isEmpty);110 /* Now estimate should be 0 */111 sqlite3_int64 est = sqlite3BtreeRowCountEst(pCur);112 TEST_ASSERT_EQUAL_INT64(0, est);113 114 close_table_cursor(db, pBt, pCur);115 sqlite3_close(db);116}117 118/* Test 2: Single-page table (leaf-root). Estimate equals leaf nCell. */119void test_sqlite3BtreeRowCountEst_single_leaf_equals_leaf_nCell(void){120 sqlite3 *db = 0;121 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));122 123 exec_sql(db, "CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);");124 insert_n_rows(db, 10); /* small enough to fit in a single leaf/root page */125 126 Btree *pBt = 0;127 BtCursor *pCur = 0;128 Pgno root = 0;129 open_table_cursor(db, "t1", &pBt, &pCur, &root);130 131 int isEmpty = 0;132 int rc = sqlite3BtreeFirst(pCur, &isEmpty);133 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);134 TEST_ASSERT_EQUAL_INT(0, isEmpty);135 /* On single-page tree, the root is a leaf and iPage==0 */136 TEST_ASSERT_TRUE(pCur->pPage->leaf!=0);137 TEST_ASSERT_TRUE(pCur->iPage==0);138 139 sqlite3_int64 est = sqlite3BtreeRowCountEst(pCur);140 /* Expect equals to leaf nCell */141 sqlite3_int64 expected = pCur->pPage->nCell;142 TEST_ASSERT_EQUAL_INT64(expected, est);143 144 close_table_cursor(db, pBt, pCur);145 sqlite3_close(db);146}147 148/* Test 3: Multi-level tree, cursor forced to root (internal), expect -1 */149void test_sqlite3BtreeRowCountEst_internal_node_returns_negative_one(void){150 sqlite3 *db = 0;151 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));152 153 /* Use many rows to force tree height > 1 */154 exec_sql(db, "PRAGMA page_size=1024;"); /* smaller pages to increase depth faster */155 exec_sql(db, "CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);");156 insert_n_rows(db, 2000); /* should create multiple levels */157 158 Btree *pBt = 0;159 BtCursor *pCur = 0;160 Pgno root = 0;161 open_table_cursor(db, "t1", &pBt, &pCur, &root);162 163 int isEmpty = 0;164 int rc = sqlite3BtreeFirst(pCur, &isEmpty);165 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);166 TEST_ASSERT_EQUAL_INT(0, isEmpty);167 168 /* Ensure there is ancestry (iPage>0) */169 TEST_ASSERT_TRUE(pCur->iPage >= 1);170 171 /* Force cursor to point at root (internal page) and keep it valid */172 pCur->pPage = pCur->apPage[0];173 pCur->iPage = 0;174 pCur->eState = CURSOR_VALID;175 176 /* Root should be internal (non-leaf) now */177 TEST_ASSERT_TRUE(pCur->pPage->leaf==0);178 179 sqlite3_int64 est = sqlite3BtreeRowCountEst(pCur);180 TEST_ASSERT_EQUAL_INT64(-1, est);181 182 close_table_cursor(db, pBt, pCur);183 sqlite3_close(db);184}185 186/* Test 4: Multi-level tree leaf estimate equals product of ancestor (nCell+1) times leaf nCell */187void test_sqlite3BtreeRowCountEst_multilevel_leaf_product_matches(void){188 sqlite3 *db = 0;189 TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db));190 191 exec_sql(db, "PRAGMA page_size=1024;");192 exec_sql(db, "CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);");193 insert_n_rows(db, 1500);194 195 Btree *pBt = 0;196 BtCursor *pCur = 0;197 Pgno root = 0;198 open_table_cursor(db, "t1", &pBt, &pCur, &root);199 200 int isEmpty = 0;201 int rc = sqlite3BtreeLast(pCur, &isEmpty);202 TEST_ASSERT_EQUAL_INT(SQLITE_OK, rc);203 TEST_ASSERT_EQUAL_INT(0, isEmpty);204 205 /* Confirm on a leaf */206 TEST_ASSERT_TRUE(pCur->pPage->leaf!=0);207 208 /* Compute expected = leaf nCell * product of (ancestor nCell + 1) */209 sqlite3_int64 expected = pCur->pPage->nCell;210 for(u8 i=0; i<pCur->iPage; i++){211 expected *= (pCur->apPage[i]->nCell + 1);212 }213 214 sqlite3_int64 est = sqlite3BtreeRowCountEst(pCur);215 TEST_ASSERT_EQUAL_INT64(expected, est);216 217 close_table_cursor(db, pBt, pCur);218 sqlite3_close(db);219}220 221int main(void) {222 UNITY_BEGIN();223 RUN_TEST(test_sqlite3BtreeRowCountEst_empty_table_returns_zero);224 RUN_TEST(test_sqlite3BtreeRowCountEst_single_leaf_equals_leaf_nCell);225 RUN_TEST(test_sqlite3BtreeRowCountEst_internal_node_returns_negative_one);226 RUN_TEST(test_sqlite3BtreeRowCountEst_multilevel_leaf_product_matches);227 return UNITY_END();228}