mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
search_code - Scan the indexed files in process instead of through grep (POSIX) or a PowerShell Select-String pipeline (Windows), with the same grep -f semantics: one pattern per line, ERE regex, binary-file and NUL handling, -Hn records. The subprocess runner stays for the unindexed recursive fallback and a Windows file_pattern it cannot prefilter. - Windows now matches like macOS and Linux: Select-String matched case-insensitively, used .NET regex syntax and read NUL-containing files as text. A trailing CR is still ignored there, so `foo$` keeps matching CRLF checkouts. - Result merge, root containment and source reads no longer scale with hits x results: a node-id index, one canonical root per request with a per-file verdict cache, and fread spans instead of per-byte fgetc. query_graph - ORDER BY (RETURN and WITH) was a bubble sort. A stable merge sort keeps the same comparisons and so the same order; WITH keys are resolved once per row instead of twice per comparison. - GROUP BY, DISTINCT (RETURN and WITH) and COUNT(DISTINCT) looked every row up in a linear list. They hash the same keys now. Store reads - Read tools run their statements in one deferred read transaction, renewed every 100 ms, instead of taking the database file lock once per statement. list_projects - Resolve only the branch the listing prints: 2 git processes per project instead of 7. On macOS and Linux every response is byte-identical to before: search_code 30/30 (django), 28/28 (this repo) and 28/28 (Linux, GNU grep); all tools and the Cypher cases 86/86 on shared django and redis indexes. On django: search_code 908 -> 355 ms, WITH ... ORDER BY 41 s -> 62 ms, count(*) 508 -> 184 ms, list_projects 94 -> 40 ms. On the Windows ARM VM: search_code 1.6-3.4 s -> 0.2-0.5 s. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
430 lines
17 KiB
C
430 lines
17 KiB
C
/*
|
|
* test_store_pragmas.c — Tests for SQLite pragma resolution.
|
|
*
|
|
* Validates that the CBM_SQLITE_MMAP_SIZE env var controls the mmap_size
|
|
* pragma applied to on-disk stores. Default behavior (env unset) must
|
|
* remain 64 MB. Setting the env to 0 disables memory-mapped I/O so
|
|
* concurrent processes that truncate the DB file under a sibling's live
|
|
* mapping return SQLITE_IOERR instead of crashing the process with SIGBUS.
|
|
*/
|
|
#include "../src/foundation/compat.h"
|
|
#include "test_framework.h"
|
|
#include "test_helpers.h"
|
|
#include <store/store.h>
|
|
#include "sqlite3.h" /* vendored/sqlite3 — read pragmas back on the store's own handle */
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
static void clear_mmap_env(void) {
|
|
cbm_unsetenv("CBM_SQLITE_MMAP_SIZE");
|
|
}
|
|
|
|
TEST(mmap_size_default_when_unset) {
|
|
clear_mmap_env();
|
|
ASSERT_EQ(cbm_store_resolve_mmap_size(), 67108864LL);
|
|
PASS();
|
|
}
|
|
|
|
TEST(mmap_size_zero_disables_mmap) {
|
|
cbm_setenv("CBM_SQLITE_MMAP_SIZE", "0", 1);
|
|
ASSERT_EQ(cbm_store_resolve_mmap_size(), 0LL);
|
|
clear_mmap_env();
|
|
PASS();
|
|
}
|
|
|
|
TEST(mmap_size_explicit_value) {
|
|
cbm_setenv("CBM_SQLITE_MMAP_SIZE", "1048576", 1);
|
|
ASSERT_EQ(cbm_store_resolve_mmap_size(), 1048576LL);
|
|
clear_mmap_env();
|
|
PASS();
|
|
}
|
|
|
|
TEST(mmap_size_negative_clamped_to_zero) {
|
|
cbm_setenv("CBM_SQLITE_MMAP_SIZE", "-1", 1);
|
|
ASSERT_EQ(cbm_store_resolve_mmap_size(), 0LL);
|
|
clear_mmap_env();
|
|
PASS();
|
|
}
|
|
|
|
TEST(mmap_size_garbage_falls_back_to_default) {
|
|
cbm_setenv("CBM_SQLITE_MMAP_SIZE", "not-a-number", 1);
|
|
ASSERT_EQ(cbm_store_resolve_mmap_size(), 67108864LL);
|
|
clear_mmap_env();
|
|
PASS();
|
|
}
|
|
|
|
TEST(mmap_size_partial_garbage_falls_back_to_default) {
|
|
cbm_setenv("CBM_SQLITE_MMAP_SIZE", "123abc", 1);
|
|
ASSERT_EQ(cbm_store_resolve_mmap_size(), 67108864LL);
|
|
clear_mmap_env();
|
|
PASS();
|
|
}
|
|
|
|
/* Integration smoke: opening a file-backed store with mmap_size=0 must
|
|
* succeed. Proves the resolver is wired through configure_pragmas(). */
|
|
TEST(store_open_with_mmap_disabled) {
|
|
cbm_setenv("CBM_SQLITE_MMAP_SIZE", "0", 1);
|
|
char tmp_path[256];
|
|
snprintf(tmp_path, sizeof(tmp_path), "%s/cbm_test_pragmas_%d.db", cbm_tmpdir(), (int)getpid());
|
|
unlink(tmp_path);
|
|
|
|
cbm_store_t *s = cbm_store_open_path(tmp_path);
|
|
ASSERT(s != NULL);
|
|
cbm_store_close(s);
|
|
|
|
unlink(tmp_path);
|
|
/* WAL/SHM siblings created by the open */
|
|
char tmp_wal[300];
|
|
char tmp_shm[300];
|
|
snprintf(tmp_wal, sizeof(tmp_wal), "%s-wal", tmp_path);
|
|
snprintf(tmp_shm, sizeof(tmp_shm), "%s-shm", tmp_path);
|
|
unlink(tmp_wal);
|
|
unlink(tmp_shm);
|
|
|
|
clear_mmap_env();
|
|
PASS();
|
|
}
|
|
|
|
/* #1083: on-disk write connections must bound the WAL via journal_size_limit
|
|
* so a checkpoint-starved log is physically reclaimed once a checkpoint can
|
|
* reset it. On main this is UNSET (-1 = unlimited), so the -wal file only ever
|
|
* grows (all our checkpoints are PASSIVE and never ftruncate). Read the pragma
|
|
* back on the SAME connection — it's per-connection and not persisted. */
|
|
TEST(journal_size_limit_bounds_wal_issue1083) {
|
|
char tmp_path[256];
|
|
snprintf(tmp_path, sizeof(tmp_path), "%s/cbm_test_jsl_%d.db", cbm_tmpdir(), (int)getpid());
|
|
unlink(tmp_path);
|
|
|
|
cbm_store_t *s = cbm_store_open_path(tmp_path);
|
|
ASSERT(s != NULL);
|
|
/* 256 MiB — far above the healthy WAL (~64 MiB: 1000 autocheckpoint pages
|
|
* of 64 KiB), so no truncate/regrow churn in normal operation; it only
|
|
* fires after abnormal (starved) growth. */
|
|
ASSERT(cbm_store_journal_size_limit(s) == (int64_t)268435456);
|
|
cbm_store_close(s);
|
|
|
|
unlink(tmp_path);
|
|
char tmp_wal[300];
|
|
char tmp_shm[300];
|
|
snprintf(tmp_wal, sizeof(tmp_wal), "%s-wal", tmp_path);
|
|
snprintf(tmp_shm, sizeof(tmp_shm), "%s-shm", tmp_path);
|
|
unlink(tmp_wal);
|
|
unlink(tmp_shm);
|
|
PASS();
|
|
}
|
|
|
|
/* #1419: effective pragmas per store role, read on the handle itself —
|
|
* synchronous is per-connection and never persisted, so an external sqlite3
|
|
* shell reports its own default (FULL), not what the indexer runs with. */
|
|
static int role_pragma_int(cbm_store_t *s, const char *sql) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int value = -1;
|
|
if (sqlite3_prepare_v2(cbm_store_get_db(s), sql, -1, &stmt, NULL) == SQLITE_OK &&
|
|
sqlite3_step(stmt) == SQLITE_ROW) {
|
|
value = sqlite3_column_int(stmt, 0);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return value;
|
|
}
|
|
|
|
static bool role_journal_mode_is(cbm_store_t *s, const char *want) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
bool match = false;
|
|
if (sqlite3_prepare_v2(cbm_store_get_db(s), "PRAGMA journal_mode;", -1, &stmt, NULL) ==
|
|
SQLITE_OK &&
|
|
sqlite3_step(stmt) == SQLITE_ROW) {
|
|
const char *mode = (const char *)sqlite3_column_text(stmt, 0);
|
|
match = mode && strcmp(mode, want) == 0;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return match;
|
|
}
|
|
|
|
TEST(store_role_pragmas_issue1419) {
|
|
enum {
|
|
SYNC_OFF = 0,
|
|
SYNC_NORMAL = 1,
|
|
SYNC_FULL = 2,
|
|
/* 64 MiB = 1024 of the 64 KiB pages every index is written with. */
|
|
WRITE_CACHE_KIB = -65536,
|
|
SQLITE_DEFAULT_CACHE_KIB = -2000,
|
|
};
|
|
char tmp_path[256];
|
|
snprintf(tmp_path, sizeof(tmp_path), "%s/cbm_test_roles_%d.db", cbm_tmpdir(), (int)getpid());
|
|
unlink(tmp_path);
|
|
|
|
/* Read-write role (live ADR writes, staging generations): WAL at NORMAL,
|
|
* with a page cache that holds more than a few 64 KiB pages. */
|
|
cbm_store_t *s = cbm_store_open_path(tmp_path);
|
|
ASSERT(s != NULL);
|
|
ASSERT_TRUE(role_journal_mode_is(s, "wal"));
|
|
ASSERT_EQ(role_pragma_int(s, "PRAGMA synchronous;"), SYNC_NORMAL);
|
|
ASSERT_EQ(role_pragma_int(s, "PRAGMA cache_size;"), WRITE_CACHE_KIB);
|
|
/* Bulk role: sync off for the write burst, then back to the read-write
|
|
* settings rather than SQLite's defaults. */
|
|
ASSERT_EQ(cbm_store_begin_bulk(s), CBM_STORE_OK);
|
|
ASSERT_EQ(role_pragma_int(s, "PRAGMA synchronous;"), SYNC_OFF);
|
|
ASSERT_EQ(cbm_store_end_bulk(s), CBM_STORE_OK);
|
|
ASSERT_EQ(role_pragma_int(s, "PRAGMA synchronous;"), SYNC_NORMAL);
|
|
ASSERT_EQ(role_pragma_int(s, "PRAGMA cache_size;"), WRITE_CACHE_KIB);
|
|
/* Seal role: the durable checkpoint before the atomic rename runs at FULL
|
|
* and leaves a self-contained DELETE-mode file. */
|
|
ASSERT_EQ(cbm_store_seal_for_atomic_publish(s), CBM_STORE_OK);
|
|
ASSERT_EQ(role_pragma_int(s, "PRAGMA synchronous;"), SYNC_FULL);
|
|
ASSERT_TRUE(role_journal_mode_is(s, "delete"));
|
|
cbm_store_close(s);
|
|
|
|
/* Query role: read-only, never switches a sealed file back to WAL, and
|
|
* keeps SQLite's small default cache (one per request, so it stays cheap). */
|
|
cbm_store_t *q = cbm_store_open_path_query(tmp_path);
|
|
ASSERT(q != NULL);
|
|
ASSERT_EQ(sqlite3_db_readonly(cbm_store_get_db(q), "main"), 1);
|
|
ASSERT_TRUE(role_journal_mode_is(q, "delete"));
|
|
ASSERT_EQ(role_pragma_int(q, "PRAGMA cache_size;"), SQLITE_DEFAULT_CACHE_KIB);
|
|
cbm_store_close(q);
|
|
|
|
unlink(tmp_path);
|
|
char tmp_wal[300];
|
|
char tmp_shm[300];
|
|
snprintf(tmp_wal, sizeof(tmp_wal), "%s-wal", tmp_path);
|
|
snprintf(tmp_shm, sizeof(tmp_shm), "%s-shm", tmp_path);
|
|
unlink(tmp_wal);
|
|
unlink(tmp_shm);
|
|
PASS();
|
|
}
|
|
|
|
/* Pagination-cursor generation: minted per DB file, bumped per index run.
|
|
* Same store + reads only -> stable; upsert_project (every index run's choke
|
|
* point) -> changes; two distinct DB files can never share a generation even
|
|
* at the same counter value (random db_uid). */
|
|
TEST(store_generation_tracks_mutations) {
|
|
char g1[128];
|
|
char g2[128];
|
|
char g3[128];
|
|
cbm_store_t *a = cbm_store_open_memory();
|
|
ASSERT(a != NULL);
|
|
ASSERT_EQ(cbm_store_upsert_project(a, "p", "/tmp/p"), CBM_STORE_OK);
|
|
ASSERT_EQ(cbm_store_generation(a, g1, sizeof(g1)), CBM_STORE_OK);
|
|
ASSERT(strncmp(g1, "u", 1) == 0); /* seeded, not legacy */
|
|
ASSERT_EQ(cbm_store_generation(a, g2, sizeof(g2)), CBM_STORE_OK);
|
|
ASSERT(strcmp(g1, g2) == 0); /* reads are stable */
|
|
ASSERT_EQ(cbm_store_upsert_project(a, "p", "/tmp/p"), CBM_STORE_OK);
|
|
ASSERT_EQ(cbm_store_generation(a, g3, sizeof(g3)), CBM_STORE_OK);
|
|
ASSERT(strcmp(g1, g3) != 0); /* index run bumps */
|
|
|
|
cbm_store_t *b = cbm_store_open_memory();
|
|
ASSERT(b != NULL);
|
|
ASSERT_EQ(cbm_store_upsert_project(b, "p", "/tmp/p"), CBM_STORE_OK);
|
|
char gb[128];
|
|
ASSERT_EQ(cbm_store_generation(b, gb, sizeof(gb)), CBM_STORE_OK);
|
|
ASSERT(strcmp(g1, gb) != 0); /* distinct DBs never alias (random uid) */
|
|
cbm_store_close(a);
|
|
cbm_store_close(b);
|
|
PASS();
|
|
}
|
|
|
|
/* Existing store_meta is authoritative: a partially-written or malformed
|
|
* table is corruption, not a legacy database. Generation reads and the
|
|
* project-upsert mutation choke point must both fail closed, and a failed
|
|
* generation advance must not leave the project row committed. */
|
|
TEST(store_generation_rejects_malformed_metadata_atomically) {
|
|
cbm_store_t *s = cbm_store_open_memory();
|
|
ASSERT_NOT_NULL(s);
|
|
ASSERT_EQ(cbm_store_exec(s, "CREATE TABLE store_meta (k TEXT PRIMARY KEY, v TEXT);"
|
|
"INSERT INTO store_meta VALUES('db_uid','0123456789abcdef');"),
|
|
CBM_STORE_OK);
|
|
|
|
char generation[128] = {0};
|
|
ASSERT_EQ(cbm_store_generation(s, generation, sizeof(generation)), CBM_STORE_ERR);
|
|
ASSERT_EQ(cbm_store_upsert_project(s, "must-not-commit", "/tmp/must-not-commit"),
|
|
CBM_STORE_ERR);
|
|
cbm_project_t project = {0};
|
|
ASSERT_EQ(cbm_store_get_project(s, "must-not-commit", &project), CBM_STORE_NOT_FOUND);
|
|
|
|
ASSERT_EQ(cbm_store_exec(s, "INSERT INTO store_meta VALUES('mutation_gen','not-a-number');"),
|
|
CBM_STORE_OK);
|
|
ASSERT_EQ(cbm_store_generation(s, generation, sizeof(generation)), CBM_STORE_ERR);
|
|
ASSERT_EQ(cbm_store_exec(s, "UPDATE store_meta SET v=CAST(X'31006a756e6b' AS TEXT) "
|
|
"WHERE k='mutation_gen';"),
|
|
CBM_STORE_OK);
|
|
ASSERT_EQ(cbm_store_generation(s, generation, sizeof(generation)), CBM_STORE_ERR);
|
|
cbm_store_close(s);
|
|
PASS();
|
|
}
|
|
|
|
/* #896: a row-scan that dies mid-stream (SQLITE_CORRUPT) must surface a
|
|
* loud store error, not masquerade as a clean end of results. Counts are
|
|
* answered from covering indexes (still correct) while row fetches die at
|
|
* the first corrupt table page — the old loops discarded the terminal
|
|
* sqlite3_step code, so every query surface returned plausible
|
|
* truncated/empty answers with no error. */
|
|
TEST(corrupt_page_scan_returns_error_not_truncation) {
|
|
enum { CORRUPT_NODES = 2000, ZERO_PAGES = 40 };
|
|
char *td = th_mktempdir("cbm_corrupt");
|
|
char db_path[512];
|
|
snprintf(db_path, sizeof(db_path), "%s/c.db", td);
|
|
|
|
cbm_store_t *s = cbm_store_open_path(db_path);
|
|
ASSERT_NOT_NULL(s);
|
|
cbm_store_upsert_project(s, "corr", "/tmp/corr");
|
|
for (int i = 0; i < CORRUPT_NODES; i++) {
|
|
char name[64];
|
|
char qn[256];
|
|
snprintf(name, sizeof(name), "corrupt_probe_fn_%04d", i);
|
|
snprintf(qn, sizeof(qn),
|
|
"corr.some.rather.long.module.path.to.fill.table.pages.%s_padding_padding", name);
|
|
cbm_node_t n = {.project = "corr",
|
|
.label = "Function",
|
|
.name = name,
|
|
.qualified_name = qn,
|
|
.file_path = "src/corrupt_probe.py",
|
|
.start_line = i + 1,
|
|
.end_line = i + 2};
|
|
ASSERT_TRUE(cbm_store_upsert_node(s, &n) > 0);
|
|
}
|
|
/* Precondition: a full scan works on the healthy file. */
|
|
cbm_search_params_t params = {.project = "corr", .label = "Function", .limit = 50};
|
|
cbm_search_output_t out = {0};
|
|
ASSERT_EQ(cbm_store_search(s, ¶ms, &out), CBM_STORE_OK);
|
|
ASSERT_EQ(out.total, CORRUPT_NODES);
|
|
cbm_store_search_free(&out);
|
|
cbm_store_close(s);
|
|
|
|
/* Zero a band of mid-file pages (the report's dd repro): page 25%..
|
|
* covers nodes-table leaves on a file this shape. */
|
|
FILE *f = fopen(db_path, "rb+");
|
|
ASSERT_NOT_NULL(f);
|
|
(void)fseek(f, 0, SEEK_END);
|
|
long fsize = ftell(f);
|
|
enum { PAGE = 4096 };
|
|
long page_count = fsize / PAGE;
|
|
ASSERT_TRUE(page_count > ZERO_PAGES + 8);
|
|
char zero[PAGE];
|
|
memset(zero, 0, sizeof(zero));
|
|
(void)fseek(f, (page_count / 4) * (long)PAGE, SEEK_SET);
|
|
for (int i = 0; i < ZERO_PAGES; i++) {
|
|
ASSERT_EQ(fwrite(zero, 1, PAGE, f), (size_t)PAGE);
|
|
}
|
|
(void)fclose(f);
|
|
|
|
/* The scans must now fail LOUDLY (CBM_STORE_ERR), not truncate. */
|
|
cbm_store_t *s2 = cbm_store_open_path(db_path);
|
|
ASSERT_NOT_NULL(s2);
|
|
/* The scan must CROSS the corrupt band: request every row. */
|
|
cbm_search_params_t all_params = {
|
|
.project = "corr", .label = "Function", .limit = CORRUPT_NODES};
|
|
cbm_search_output_t out2 = {0};
|
|
int rc_search = cbm_store_search(s2, &all_params, &out2);
|
|
if (rc_search == CBM_STORE_OK && out2.count == CORRUPT_NODES) {
|
|
/* Vacuous-guard: a complete, healthy scan means corruption missed
|
|
* the table pages — rebuild the fixture, don't relax the assert. */
|
|
FAIL("fixture failed to hit table pages (full scan healthy)");
|
|
}
|
|
/* THE BUG (#896): OK + silently truncated rows. Fixed = loud ERR. */
|
|
ASSERT_EQ(rc_search, CBM_STORE_ERR);
|
|
cbm_store_search_free(&out2);
|
|
|
|
/* Point lookups may legitimately succeed when their row's page
|
|
* escaped the corrupt band — the class contract is about SCANS. A
|
|
* second scan surface (qn-suffix, different SQL path) must also err. */
|
|
cbm_node_t *hits = NULL;
|
|
int hit_count = 0;
|
|
int rc_suffix =
|
|
cbm_store_find_nodes_by_qn_suffix(s2, "corr", "padding_padding", &hits, &hit_count);
|
|
if (rc_suffix == CBM_STORE_OK && hit_count == CORRUPT_NODES) {
|
|
FAIL("suffix scan healthy — fixture failed to hit table pages");
|
|
}
|
|
ASSERT_EQ(rc_suffix, CBM_STORE_ERR);
|
|
cbm_store_free_nodes(hits, hit_count);
|
|
cbm_store_close(s2);
|
|
|
|
unlink(db_path);
|
|
PASS();
|
|
}
|
|
|
|
/* A read tool's statements share one read batch instead of taking the file
|
|
* lock once per statement. The batch reads one snapshot until it renews
|
|
* (every 100 ms, forced here through the seam), then sees later commits. */
|
|
static char *read_batch_name(cbm_store_t *s) {
|
|
cbm_node_t found = {0};
|
|
if (cbm_store_find_node_by_qn(s, "p", "p.a", &found) != CBM_STORE_OK) {
|
|
return NULL;
|
|
}
|
|
char *name = found.name ? strdup(found.name) : NULL;
|
|
cbm_node_free_fields(&found);
|
|
return name;
|
|
}
|
|
|
|
static bool read_batch_rename(cbm_store_t *writer, const char *name) {
|
|
cbm_node_t node = {
|
|
.project = "p", .label = "Function", .name = name, .qualified_name = "p.a", .file_path = "a.c"};
|
|
return cbm_store_upsert_node(writer, &node) > 0;
|
|
}
|
|
|
|
TEST(store_read_batch_snapshot_renews) {
|
|
char *td = th_mktempdir("cbm_read_batch");
|
|
ASSERT_NOT_NULL(td);
|
|
char db_path[512];
|
|
snprintf(db_path, sizeof(db_path), "%s/batch.db", td);
|
|
cbm_store_t *writer = cbm_store_open_path(db_path); /* WAL: commits beside a reader */
|
|
ASSERT_NOT_NULL(writer);
|
|
ASSERT_EQ(cbm_store_upsert_project(writer, "p", td), CBM_STORE_OK);
|
|
ASSERT_TRUE(read_batch_rename(writer, "before"));
|
|
cbm_store_t *reader = cbm_store_open_path_query(db_path);
|
|
ASSERT_NOT_NULL(reader);
|
|
|
|
ASSERT_TRUE(cbm_store_read_batch_begin(reader));
|
|
ASSERT_FALSE(cbm_store_read_batch_begin(reader)); /* one batch at a time */
|
|
char *name = read_batch_name(reader);
|
|
ASSERT_STR_EQ(name, "before");
|
|
free(name);
|
|
|
|
/* Inside the batch the reader keeps its snapshot... */
|
|
ASSERT_TRUE(read_batch_rename(writer, "after"));
|
|
name = read_batch_name(reader);
|
|
ASSERT_STR_EQ(name, "before");
|
|
free(name);
|
|
|
|
/* ...until the batch renews at its next statement. */
|
|
cbm_store_read_batch_expire_for_test(reader);
|
|
name = read_batch_name(reader);
|
|
ASSERT_STR_EQ(name, "after");
|
|
free(name);
|
|
|
|
/* Ended, every statement reads the latest commit again. */
|
|
cbm_store_read_batch_end(reader);
|
|
ASSERT_TRUE(read_batch_rename(writer, "latest"));
|
|
name = read_batch_name(reader);
|
|
ASSERT_STR_EQ(name, "latest");
|
|
free(name);
|
|
|
|
/* Begin refuses while another transaction is open; close ends a batch. */
|
|
ASSERT_EQ(cbm_store_begin(writer), CBM_STORE_OK);
|
|
ASSERT_FALSE(cbm_store_read_batch_begin(writer));
|
|
ASSERT_EQ(cbm_store_rollback(writer), CBM_STORE_OK);
|
|
ASSERT_TRUE(cbm_store_read_batch_begin(reader));
|
|
cbm_store_close(reader);
|
|
cbm_store_close(writer);
|
|
th_rmtree(td);
|
|
PASS();
|
|
}
|
|
|
|
SUITE(store_pragmas) {
|
|
RUN_TEST(store_read_batch_snapshot_renews);
|
|
RUN_TEST(journal_size_limit_bounds_wal_issue1083);
|
|
RUN_TEST(store_role_pragmas_issue1419);
|
|
RUN_TEST(store_generation_tracks_mutations);
|
|
RUN_TEST(store_generation_rejects_malformed_metadata_atomically);
|
|
RUN_TEST(corrupt_page_scan_returns_error_not_truncation);
|
|
RUN_TEST(mmap_size_default_when_unset);
|
|
RUN_TEST(mmap_size_zero_disables_mmap);
|
|
RUN_TEST(mmap_size_explicit_value);
|
|
RUN_TEST(mmap_size_negative_clamped_to_zero);
|
|
RUN_TEST(mmap_size_garbage_falls_back_to_default);
|
|
RUN_TEST(mmap_size_partial_garbage_falls_back_to_default);
|
|
RUN_TEST(store_open_with_mmap_disabled);
|
|
}
|