Skip to content
Merged
81 changes: 81 additions & 0 deletions crates/codegraph-core/src/edges_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! Bulk edge insertion via rusqlite — native replacement for the JS
//! `batchInsertEdges` helper.
//!
//! Used by the build-edges stage to write computed call/receiver/extends/
//! implements edges directly to SQLite without marshaling back to JS.

use napi_derive::napi;
use rusqlite::{Connection, OpenFlags};

/// A single edge row to insert: [source_id, target_id, kind, confidence, dynamic].
#[napi(object)]
#[derive(Debug, Clone)]
pub struct EdgeRow {
#[napi(js_name = "sourceId")]
pub source_id: u32,
#[napi(js_name = "targetId")]
pub target_id: u32,
pub kind: String,
pub confidence: f64,
pub dynamic: u32,
}

/// Bulk-insert edge rows into the database via rusqlite.
/// Runs all writes in a single SQLite transaction with chunked multi-value
/// INSERT statements for maximum throughput.
///
/// Returns `true` on success, `false` on any error so the JS caller can
/// fall back to the JS batch insert path.
#[napi]
pub fn bulk_insert_edges(db_path: String, edges: Vec<EdgeRow>) -> bool {
if edges.is_empty() {
return true;
}
let flags = OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX;
let mut conn = match Connection::open_with_flags(&db_path, flags) {
Ok(c) => c,
Err(_) => return false,
};
let _ = conn.execute_batch("PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000");
do_insert(&mut conn, &edges).is_ok()
}

/// 199 rows × 5 params = 995 bind parameters per statement, safely under
/// the legacy `SQLITE_MAX_VARIABLE_NUMBER` default of 999.
const CHUNK: usize = 199;

fn do_insert(conn: &mut Connection, edges: &[EdgeRow]) -> rusqlite::Result<()> {
let tx = conn.transaction()?;

for chunk in edges.chunks(CHUNK) {
let placeholders: Vec<String> = (0..chunk.len())
.map(|i| {
let base = i * 5;
format!(
"(?{},?{},?{},?{},?{})",
base + 1,
base + 2,
base + 3,
base + 4,
base + 5
)
})
.collect();
let sql = format!(
"INSERT OR IGNORE INTO edges (source_id, target_id, kind, confidence, dynamic) VALUES {}",
placeholders.join(",")
);
let mut stmt = tx.prepare_cached(&sql)?;
for (i, edge) in chunk.iter().enumerate() {
let base = i * 5;
stmt.raw_bind_parameter(base +1, edge.source_id)?;
stmt.raw_bind_parameter(base +2, edge.target_id)?;
stmt.raw_bind_parameter(base +3, edge.kind.as_str())?;
stmt.raw_bind_parameter(base +4, edge.confidence)?;
stmt.raw_bind_parameter(base +5, edge.dynamic)?;
}
stmt.raw_execute()?;
}

tx.commit()
}
2 changes: 2 additions & 0 deletions crates/codegraph-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ pub mod constants;
pub mod cycles;
pub mod dataflow;
pub mod edge_builder;
pub mod edges_db;
pub mod extractors;
pub mod import_resolution;
pub mod incremental;
pub mod insert_nodes;
pub mod parallel;
pub mod parser_registry;
pub mod roles_db;
pub mod types;

use napi_derive::napi;
Expand Down
Loading
Loading