Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile.cbm
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ TEST_STORE_SRCS = \
tests/test_store_edges.c \
tests/test_store_search.c \
tests/test_store_arch.c \
tests/test_store_bulk.c
tests/test_store_bulk.c \
tests/test_store_pragmas.c

TEST_CYPHER_SRCS = \
tests/test_cypher.c
Expand Down
23 changes: 22 additions & 1 deletion src/store/store.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,24 @@ static int create_user_indexes(cbm_store_t *s) {
return exec_sql(s, sql);
}

int64_t cbm_store_resolve_mmap_size(void) {
enum { MMAP_DEFAULT = 67108864, BASE_10 = 10 }; /* default 64 MB; decimal radix */
char buf[ST_BUF_64];
if (cbm_safe_getenv("CBM_SQLITE_MMAP_SIZE", buf, sizeof(buf), NULL) == NULL) {
return (int64_t)MMAP_DEFAULT;
}
char *end = NULL;
long long parsed = strtoll(buf, &end, BASE_10);
if (end == buf || *end != '\0') {
/* Malformed — fall back to default rather than fail the store open. */
return (int64_t)MMAP_DEFAULT;
}
if (parsed < 0) {
return 0;
}
return (int64_t)parsed;
}

static int configure_pragmas(cbm_store_t *s, bool in_memory) {
int rc;
rc = exec_sql(s, "PRAGMA foreign_keys = ON;");
Expand All @@ -323,7 +341,10 @@ static int configure_pragmas(cbm_store_t *s, bool in_memory) {
if (rc != CBM_STORE_OK) {
return rc;
}
rc = exec_sql(s, "PRAGMA mmap_size = 67108864;"); /* CBM_SZ_64 MB */
char mmap_sql[ST_BUF_64];
snprintf(mmap_sql, sizeof(mmap_sql), "PRAGMA mmap_size = %lld;",
(long long)cbm_store_resolve_mmap_size());
rc = exec_sql(s, mmap_sql);
}
return rc;
}
Expand Down
9 changes: 9 additions & 0 deletions src/store/store.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ int cbm_store_create_indexes(cbm_store_t *s);
/* Force WAL checkpoint + PRAGMA optimize. */
int cbm_store_checkpoint(cbm_store_t *s);

/* Resolve the mmap_size pragma value applied to on-disk stores from the
* CBM_SQLITE_MMAP_SIZE environment variable. Defaults to 67108864 (64 MB)
* when the variable is unset, malformed, or partially numeric. Negative
* values clamp to 0 (which disables mmap and reverts to read()/pread()
* I/O — recoverable SQLITE_IOERR instead of SIGBUS when concurrent
* processes truncate the DB file under live mappings). Exposed for
* testability. */
int64_t cbm_store_resolve_mmap_size(void);

/* ── Dump / Restore ─────────────────────────────────────────────── */

/* Dump in-memory database to a file. */
Expand Down
2 changes: 2 additions & 0 deletions tests/test_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ extern void suite_go_lsp(void);
extern void suite_c_lsp(void);
extern void suite_store_arch(void);
extern void suite_store_bulk(void);
extern void suite_store_pragmas(void);
extern void suite_traces(void);
extern void suite_configlink(void);
extern void suite_infrascan(void);
Expand Down Expand Up @@ -79,6 +80,7 @@ int main(void) {
RUN_SUITE(store_edges);
RUN_SUITE(store_search);
RUN_SUITE(store_bulk);
RUN_SUITE(store_pragmas);

/* Cypher (M6) */
RUN_SUITE(cypher);
Expand Down
97 changes: 97 additions & 0 deletions tests/test_store_pragmas.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void clear_mmap_env(void) {
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) {
setenv("CBM_SQLITE_MMAP_SIZE", "0", 1);
ASSERT_EQ(cbm_store_resolve_mmap_size(), 0LL);
clear_mmap_env();
PASS();
}

TEST(mmap_size_explicit_value) {
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) {
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) {
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) {
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) {
setenv("CBM_SQLITE_MMAP_SIZE", "0", 1);
char tmp_path[256];
snprintf(tmp_path, sizeof(tmp_path), "/tmp/cbm_test_pragmas_%d.db", (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();
}

SUITE(store_pragmas) {
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);
}