mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
feat(store): claim overlay compactions
Add cbm_store_claim_ready_overlay_generation() as the narrow store lifecycle primitive for a future background compactor. The claim runs in the existing BEGIN IMMEDIATE transaction path, selects the oldest ready overlay generation, and moves it from overlay_ready to compacting with a guarded status update so two callers cannot claim the same generation. Cover oldest-ready ordering, non-ready filtering, invalid output handling, and claim-plus-compact composition in store_nodes. This keeps the change default-inert: no background thread, no default-on policy, and no shared store-handle concurrency are introduced. Validation: make -f Makefile.cbm -j8 build/c/test-runner; CBM_ONLY_SUITE=store_nodes build/c/test-runner; git diff --check; bash scripts/check-source-safety.sh src/store/store.c src/store/store.h tests/test_store_nodes.c; make -f Makefile.cbm -j8 cbm. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -3985,6 +3985,97 @@ int cbm_store_count_overlay_generations(cbm_store_t *s, const char *project,
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
|
||||
int cbm_store_claim_ready_overlay_generation(cbm_store_t *s, const char *project,
|
||||
int64_t *out_overlay_generation,
|
||||
int64_t *out_base_generation) {
|
||||
if (out_overlay_generation) {
|
||||
*out_overlay_generation = 0;
|
||||
}
|
||||
if (out_base_generation) {
|
||||
*out_base_generation = 0;
|
||||
}
|
||||
if (!s || !s->db || !project || !project[0] || !out_overlay_generation ||
|
||||
!out_base_generation) {
|
||||
if (s) {
|
||||
store_set_error(s, "claim_ready_overlay_generation: invalid argument");
|
||||
}
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
|
||||
int rc = cbm_store_begin(s);
|
||||
if (rc != CBM_STORE_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
static const char select_sql[] =
|
||||
"SELECT overlay_generation, base_generation FROM overlay_generations "
|
||||
"WHERE project = ?1 AND status = ?2 "
|
||||
"ORDER BY overlay_generation ASC LIMIT 1;";
|
||||
if (sqlite3_prepare_v2(s->db, select_sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK) {
|
||||
store_set_error_sqlite(s, "claim_ready_overlay_generation select prepare");
|
||||
(void)cbm_store_rollback(s);
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
bind_text(stmt, ST_COL_1, project);
|
||||
bind_text(stmt, ST_COL_2, CBM_STORE_OVERLAY_STATUS_READY);
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_DONE) {
|
||||
sqlite3_finalize(stmt);
|
||||
(void)cbm_store_rollback(s);
|
||||
return CBM_STORE_NOT_FOUND;
|
||||
}
|
||||
if (rc != SQLITE_ROW) {
|
||||
sqlite3_finalize(stmt);
|
||||
store_set_error_sqlite(s, "claim_ready_overlay_generation select");
|
||||
(void)cbm_store_rollback(s);
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
|
||||
int64_t overlay_generation = sqlite3_column_int64(stmt, 0);
|
||||
int64_t base_generation = sqlite3_column_int64(stmt, ST_COL_1);
|
||||
sqlite3_finalize(stmt);
|
||||
stmt = NULL;
|
||||
|
||||
static const char update_sql[] =
|
||||
"UPDATE overlay_generations SET status = ?3, updated_at = ?4 "
|
||||
"WHERE project = ?1 AND overlay_generation = ?2 AND status = ?5;";
|
||||
if (sqlite3_prepare_v2(s->db, update_sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK) {
|
||||
store_set_error_sqlite(s, "claim_ready_overlay_generation update prepare");
|
||||
(void)cbm_store_rollback(s);
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
char ts[CBM_SZ_64];
|
||||
iso_now(ts, sizeof(ts));
|
||||
bind_text(stmt, ST_COL_1, project);
|
||||
sqlite3_bind_int64(stmt, ST_COL_2, overlay_generation);
|
||||
bind_text(stmt, ST_COL_3, CBM_STORE_OVERLAY_STATUS_COMPACTING);
|
||||
bind_text(stmt, ST_COL_4, ts);
|
||||
bind_text(stmt, ST_COL_5, CBM_STORE_OVERLAY_STATUS_READY);
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
if (rc != SQLITE_DONE) {
|
||||
store_set_error_sqlite(s, "claim_ready_overlay_generation update");
|
||||
(void)cbm_store_rollback(s);
|
||||
return CBM_STORE_ERR;
|
||||
}
|
||||
if (sqlite3_changes(s->db) != 1) {
|
||||
store_set_error(s, "claim_ready_overlay_generation: ready generation not found");
|
||||
(void)cbm_store_rollback(s);
|
||||
return CBM_STORE_NOT_FOUND;
|
||||
}
|
||||
|
||||
rc = cbm_store_commit(s);
|
||||
if (rc != CBM_STORE_OK) {
|
||||
(void)cbm_store_rollback(s);
|
||||
return rc;
|
||||
}
|
||||
|
||||
*out_overlay_generation = overlay_generation;
|
||||
*out_base_generation = base_generation;
|
||||
return CBM_STORE_OK;
|
||||
}
|
||||
|
||||
static int store_resolve_node_id(cbm_store_t *s, const char *project, const char *qn,
|
||||
int64_t *out_id) {
|
||||
const char *qns[1] = {qn};
|
||||
|
||||
@@ -688,6 +688,12 @@ int cbm_store_set_overlay_generation_status(cbm_store_t *s, const char *project,
|
||||
const char *status);
|
||||
int cbm_store_count_overlay_generations(cbm_store_t *s, const char *project,
|
||||
const char *status, int *out_count);
|
||||
/* Atomically claim the oldest ready overlay generation for compaction by
|
||||
* moving it from overlay_ready to compacting. Returns CBM_STORE_NOT_FOUND when
|
||||
* no ready overlay generation exists for the project. */
|
||||
int cbm_store_claim_ready_overlay_generation(cbm_store_t *s, const char *project,
|
||||
int64_t *out_overlay_generation,
|
||||
int64_t *out_base_generation);
|
||||
|
||||
/* Publish one file's replacement facts into overlay storage. This does not
|
||||
* mutate canonical nodes/edges; active read paths decide later how to combine
|
||||
|
||||
@@ -1288,6 +1288,91 @@ TEST(store_overlay_generation_rejects_invalid_inputs) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(store_claim_ready_overlay_generation_claims_oldest_once) {
|
||||
enum { BASE_GENERATION = 9 };
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_EQ(cbm_store_upsert_project(s, "test", "/tmp/test"), CBM_STORE_OK);
|
||||
|
||||
int64_t first = 0;
|
||||
int64_t second = 0;
|
||||
ASSERT_EQ(cbm_store_reserve_overlay_generation(s, "test", BASE_GENERATION, &first),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(cbm_store_reserve_overlay_generation(s, "test", BASE_GENERATION + 1,
|
||||
&second),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(cbm_store_set_overlay_generation_status(s, "test", first,
|
||||
CBM_STORE_OVERLAY_STATUS_READY),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(cbm_store_set_overlay_generation_status(s, "test", second,
|
||||
CBM_STORE_OVERLAY_STATUS_READY),
|
||||
CBM_STORE_OK);
|
||||
|
||||
int64_t claimed = 0;
|
||||
int64_t base = 0;
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", &claimed, &base),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(claimed, first);
|
||||
ASSERT_EQ(base, BASE_GENERATION);
|
||||
ASSERT_EQ(store_count_overlay_generation_row(s, "test", first, BASE_GENERATION,
|
||||
CBM_STORE_OVERLAY_STATUS_COMPACTING),
|
||||
1);
|
||||
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", &claimed, &base),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(claimed, second);
|
||||
ASSERT_EQ(base, BASE_GENERATION + 1);
|
||||
ASSERT_EQ(store_count_overlay_generation_row(s, "test", second, BASE_GENERATION + 1,
|
||||
CBM_STORE_OVERLAY_STATUS_COMPACTING),
|
||||
1);
|
||||
|
||||
claimed = -1;
|
||||
base = -1;
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", &claimed, &base),
|
||||
CBM_STORE_NOT_FOUND);
|
||||
ASSERT_EQ(claimed, 0);
|
||||
ASSERT_EQ(base, 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(store_claim_ready_overlay_generation_ignores_nonready_and_validates_outputs) {
|
||||
enum { BASE_GENERATION = 3, SENTINEL = 42 };
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_EQ(cbm_store_upsert_project(s, "test", "/tmp/test"), CBM_STORE_OK);
|
||||
|
||||
int64_t reserved = 0;
|
||||
int64_t failed = 0;
|
||||
ASSERT_EQ(cbm_store_reserve_overlay_generation(s, "test", BASE_GENERATION,
|
||||
&reserved),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(cbm_store_reserve_overlay_generation(s, "test", BASE_GENERATION,
|
||||
&failed),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(cbm_store_set_overlay_generation_status(s, "test", failed,
|
||||
CBM_STORE_OVERLAY_STATUS_FAILED),
|
||||
CBM_STORE_OK);
|
||||
|
||||
int64_t claimed = SENTINEL;
|
||||
int64_t base = SENTINEL;
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", &claimed, &base),
|
||||
CBM_STORE_NOT_FOUND);
|
||||
ASSERT_EQ(claimed, 0);
|
||||
ASSERT_EQ(base, 0);
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", NULL, &base),
|
||||
CBM_STORE_ERR);
|
||||
ASSERT_EQ(base, 0);
|
||||
claimed = SENTINEL;
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", &claimed, NULL),
|
||||
CBM_STORE_ERR);
|
||||
ASSERT_EQ(claimed, 0);
|
||||
|
||||
cbm_store_close(s);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(store_overlay_file_delta_publish_rows_and_tombstone) {
|
||||
enum { BASE_GENERATION = 3 };
|
||||
cbm_store_t *s = cbm_store_open_memory();
|
||||
@@ -1874,6 +1959,13 @@ TEST(store_compact_overlay_generation_promotes_metadata_and_cleans_overlay) {
|
||||
ASSERT_EQ(cbm_store_reserve_index_generation(s, "test", NULL, NULL, &generation),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(generation, COMPACT_GENERATION);
|
||||
int64_t claimed_overlay = 0;
|
||||
int64_t claimed_base = 0;
|
||||
ASSERT_EQ(cbm_store_claim_ready_overlay_generation(s, "test", &claimed_overlay,
|
||||
&claimed_base),
|
||||
CBM_STORE_OK);
|
||||
ASSERT_EQ(claimed_overlay, overlay_generation);
|
||||
ASSERT_EQ(claimed_base, BASE_GENERATION);
|
||||
ASSERT_EQ(cbm_store_compact_overlay_generation(s, "test", overlay_generation, generation),
|
||||
CBM_STORE_OK);
|
||||
|
||||
@@ -5251,6 +5343,8 @@ SUITE(store_nodes) {
|
||||
RUN_TEST(store_index_generation_finish_failed_and_invalid_status);
|
||||
RUN_TEST(store_overlay_generation_reservation_status_and_counts);
|
||||
RUN_TEST(store_overlay_generation_rejects_invalid_inputs);
|
||||
RUN_TEST(store_claim_ready_overlay_generation_claims_oldest_once);
|
||||
RUN_TEST(store_claim_ready_overlay_generation_ignores_nonready_and_validates_outputs);
|
||||
RUN_TEST(store_overlay_file_delta_publish_rows_and_tombstone);
|
||||
RUN_TEST(store_delete_project_clears_overlay_fts);
|
||||
RUN_TEST(store_overlay_file_delta_publish_rejects_invalid_delta_without_rows);
|
||||
|
||||
Reference in New Issue
Block a user