6012f4626e
Distills PR #770 (graph-buffer dedup key) and completes the edge-uniqueness contract it left partial: the contract lives in THREE places, and changing only the buffer ships databases that violate their own UNIQUE constraint (PRAGMA integrity_check: non-unique entry in sqlite_autoindex_edges_1). A single 'import { A, B } from ./lib' produced ONE IMPORTS edge: the graph buffer dedups edges on (source_id, target_id, type) and merge-replaces properties on collision, so the second symbol silently overwrote the first. pass_calls.c, pass_usages.c, pass_semantic.c and pass_lsp_cross.c parse one local_name per IMPORTS edge for cross-file resolution, so the dropped symbol's calls failed to resolve too — not just "who imports X" queries. Changes, kept in sync across all three sites: - graph_buffer.c (from #770): make_edge_key() folds local_name into the dedup key for IMPORTS edges only; all three key call-sites updated. Hardened beyond #770: EDGE_KEY_BUF bumped to 256 and oversized local_names are re-keyed with an FNV-1a hash of the full name instead of being silently truncated (two long names sharing a prefix must not collide back into one edge). - store.c: edges gains local_name_gen, a VIRTUAL generated column (IMPORTS -> coalesce(json_extract(properties,'$.local_name'),''), else '' — NOT NULL because NULLs never conflict in a UNIQUE index); uniqueness widened to UNIQUE(source_id, target_id, type, local_name_gen) and the insert upsert's conflict target matches. init_schema probes pre-#768 DBs (no local_name_gen) and fails the open: SQLite cannot ALTER a table constraint in place, and an unopenable DB already takes the existing repair path — full index deletes + rebuilds, artifact import refuses and falls back to a reindex. Read-only query opens skip init_schema and keep working. - sqlite_writer.c/.h: dump DDL matches the widened schema; the hand-built sqlite_autoindex_edges_1 comparator and entry builder include the local_name column; CBMDumpEdge carries local_name extracted via real JSON parsing (yyjson) so index entries match json_extract's unescaped values exactly, per the idx_edges_url_path precedent. - artifact.h: CBM_ARTIFACT_SCHEMA_VERSION 1 -> 2 so old binaries refuse artifacts carrying the widened schema (their 3-column conflict target can no longer prepare against it). Tests (reproduce-first, all red on the unfixed code): - gbuf tier: multi-symbol import -> 2 IMPORTS edges; long-local_name truncation guard. - store tier: distinct local_name coexists as 2 rows, same local_name still upserts, non-IMPORTS dedup unchanged. - writer tier: dumped DB passes integrity_check with 2 sibling imports and exposes matching local_name_gen values. - end-to-end: TS fixture through the real pipeline -> 2 queryable IMPORTS edges AND integrity_check ok; with only the buffer half of the fix this test still fails on integrity_check, proving the schema half is required. Closes #768. Co-authored-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
94 lines
4.2 KiB
C
94 lines
4.2 KiB
C
#ifndef CBM_SQLITE_WRITER_H
|
|
#define CBM_SQLITE_WRITER_H
|
|
|
|
#include <stdint.h>
|
|
|
|
// --- Input structs (flat, borrowed strings) ---
|
|
|
|
typedef struct {
|
|
int64_t id; // sequential ID (1..N), assigned by Go
|
|
const char *project;
|
|
const char *label;
|
|
const char *name;
|
|
const char *qualified_name;
|
|
const char *file_path;
|
|
int start_line;
|
|
int end_line;
|
|
const char *properties; // JSON string
|
|
} CBMDumpNode;
|
|
|
|
typedef struct {
|
|
int64_t id; // sequential ID (1..M), assigned by Go
|
|
const char *project;
|
|
int64_t source_id; // final sequential ID (1..N)
|
|
int64_t target_id; // final sequential ID (1..N)
|
|
const char *type;
|
|
const char *properties; // JSON string
|
|
const char *url_path; // extracted from properties by Go (for idx_edges_url_path)
|
|
const char *local_name; // for IMPORTS edges: the UNESCAPED
|
|
// json_extract(properties,'$.local_name') value; ""/NULL
|
|
// otherwise. Feeds sqlite_autoindex_edges_1 — must match
|
|
// what SQLite computes for the local_name_gen column or
|
|
// integrity_check reports the row missing from the index.
|
|
} CBMDumpEdge;
|
|
|
|
typedef struct {
|
|
int64_t node_id; // final sequential ID (matches nodes.id)
|
|
const char *project;
|
|
const uint8_t *vector; // int8-quantized vector blob
|
|
int vector_len; // length in bytes (e.g. 256 for d=256)
|
|
} CBMDumpVector;
|
|
|
|
typedef struct {
|
|
int64_t id; // sequential ID (1..T)
|
|
const char *project;
|
|
const char *token; // the token string
|
|
const uint8_t *vector; // int8-quantized enriched RI vector blob
|
|
int vector_len; // length in bytes (e.g. 256 for d=256)
|
|
float idf; // inverse document frequency weight
|
|
} CBMDumpTokenVec;
|
|
|
|
// --- Public API ---
|
|
|
|
// Write a complete SQLite .db file from sorted in-memory data.
|
|
// Constructs B-tree pages directly — no SQL parser, no INSERTs.
|
|
// Returns 0 on success, non-zero on error.
|
|
// vectors/vector_count and token_vecs/token_vec_count may be NULL/0.
|
|
int cbm_write_db(const char *path, const char *project, const char *root_path,
|
|
const char *indexed_at, CBMDumpNode *nodes, int node_count, CBMDumpEdge *edges,
|
|
int edge_count, CBMDumpVector *vectors, int vector_count,
|
|
CBMDumpTokenVec *token_vecs, int token_vec_count);
|
|
|
|
// --- Streaming writer: incremental bulk node-table append ---
|
|
//
|
|
// Lets the indexer flush node rows (including heavy `properties`) to the DB in
|
|
// batches, mid-pipeline, freeing heavy memory — while preserving the direct-page
|
|
// bulk write (no per-row INSERTs). The nodes table is built across append calls
|
|
// via a persistent page builder; everything else (edges, vectors, metadata,
|
|
// indexes, sqlite_master) is written at finalize. cbm_write_db() above is a
|
|
// one-shot wrapper over this API (open -> append all nodes -> finalize) and
|
|
// produces byte-identical output.
|
|
//
|
|
// Usage: w = cbm_writer_open(path);
|
|
// cbm_writer_append_nodes(w, batch, n) x N (ascending, contiguous ids);
|
|
// cbm_writer_finalize(w, ...); // consumes + frees w, closes the file.
|
|
typedef struct cbm_db_writer cbm_db_writer_t;
|
|
|
|
cbm_db_writer_t *cbm_writer_open(const char *path);
|
|
|
|
// Append a batch of node records. Heavy `properties` are consumed here, so the
|
|
// caller may free them after this returns. Node ids must be ascending and
|
|
// contiguous across the whole sequence of append calls. Returns 0 on success.
|
|
int cbm_writer_append_nodes(cbm_db_writer_t *w, const CBMDumpNode *nodes, int count);
|
|
|
|
// Finalize: build the nodes-table interior, write edges/vectors/token_vectors,
|
|
// metadata, all indexes, and sqlite_master + header. The node/edge/vector arrays
|
|
// supply the (light) columns the index builders sort on; node `properties` are
|
|
// NOT read here (already written during append). Frees w and closes the file.
|
|
int cbm_writer_finalize(cbm_db_writer_t *w, const char *project, const char *root_path,
|
|
const char *indexed_at, CBMDumpNode *nodes, int node_count,
|
|
CBMDumpEdge *edges, int edge_count, CBMDumpVector *vectors,
|
|
int vector_count, CBMDumpTokenVec *token_vecs, int token_vec_count);
|
|
|
|
#endif // CBM_SQLITE_WRITER_H
|