Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
tests_btree_sqlite3BtreeRowCountEst.c228 linesDownload Raw Back to tests
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}