use anyhow::{Context, Result};
use rusqlite::{params, Connection};
use std::collections::HashMap;
use crate::cfg::{BlockId, Path, PathKind};
pub fn store_paths(conn: &mut Connection, function_id: i64, paths: &[Path]) -> Result<()> {
conn.execute("BEGIN IMMEDIATE TRANSACTION", [])
.context("Failed to begin transaction for store_paths")?;
let mut insert_path_stmt = conn.prepare_cached(
"INSERT INTO cfg_paths (path_id, function_id, path_kind, entry_block, exit_block, length, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
).context("Failed to prepare cfg_paths insert statement")?;
let mut insert_element_stmt = conn
.prepare_cached(
"INSERT INTO cfg_path_elements (path_id, sequence_order, block_id)
VALUES (?1, ?2, ?3)",
)
.context("Failed to prepare cfg_path_elements insert statement")?;
let now = chrono::Utc::now().timestamp();
for path in paths {
let kind_str = path_kind_to_str(path.kind);
insert_path_stmt
.execute(params![
&path.path_id,
function_id,
kind_str,
path.entry as i64,
path.exit as i64,
path.len() as i64,
now,
])
.with_context(|| format!("Failed to insert path {}", path.path_id))?;
for (idx, &block_id) in path.blocks.iter().enumerate() {
insert_element_stmt
.execute(params![&path.path_id, idx as i64, block_id as i64,])
.with_context(|| {
format!("Failed to insert element {} for path {}", idx, path.path_id)
})?;
}
}
conn.execute("COMMIT", [])
.context("Failed to commit transaction for store_paths")?;
Ok(())
}
const BATCH_SIZE: usize = 20;
pub fn store_paths_batch(conn: &mut Connection, function_id: i64, paths: &[Path]) -> Result<()> {
conn.execute("BEGIN IMMEDIATE TRANSACTION", [])
.context("Failed to begin transaction for store_paths_batch")?;
let _old_journal: String = conn
.query_row("PRAGMA journal_mode", [], |row| row.get(0))
.unwrap_or_else(|_| "delete".to_string());
let old_sync: i64 = conn
.query_row("PRAGMA synchronous", [], |row| row.get(0))
.unwrap_or(2);
conn.execute("PRAGMA cache_size = -64000", [])
.context("Failed to set cache_size")?;
let now = chrono::Utc::now().timestamp();
for path in paths {
let kind_str = path_kind_to_str(path.kind);
{
let mut insert_path_stmt = conn.prepare_cached(
"INSERT INTO cfg_paths (path_id, function_id, path_kind, entry_block, exit_block, length, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
).context("Failed to prepare cfg_paths insert statement")?;
insert_path_stmt
.execute(params![
&path.path_id,
function_id,
kind_str,
path.entry as i64,
path.exit as i64,
path.len() as i64,
now,
])
.with_context(|| format!("Failed to insert path {}", path.path_id))?;
}
insert_elements_batch(conn, &path.path_id, &path.blocks)?;
}
let _ = conn.execute(&format!("PRAGMA synchronous = {}", old_sync), []);
conn.execute("COMMIT", [])
.context("Failed to commit transaction for store_paths_batch")?;
Ok(())
}
fn insert_elements_batch(conn: &mut Connection, path_id: &str, blocks: &[BlockId]) -> Result<()> {
if blocks.is_empty() {
return Ok(());
}
for chunk in blocks.chunks(BATCH_SIZE) {
let mut sql = String::from(
"INSERT INTO cfg_path_elements (path_id, sequence_order, block_id) VALUES ",
);
for (i, _) in chunk.iter().enumerate() {
if i > 0 {
sql.push_str(", ");
}
sql.push_str("(?, ?, ?)");
}
let mut flat_params: Vec<rusqlite::types::Value> = Vec::new();
for (i, &block_id) in chunk.iter().enumerate() {
flat_params.push(rusqlite::types::Value::Text(path_id.to_string()));
flat_params.push(rusqlite::types::Value::Integer(i as i64));
flat_params.push(rusqlite::types::Value::Integer(block_id as i64));
}
let params_ref: Vec<&dyn rusqlite::ToSql> = flat_params
.iter()
.map(|v| v as &dyn rusqlite::ToSql)
.collect();
conn.execute(&sql, params_ref.as_slice())
.with_context(|| format!("Failed to batch insert {} elements", chunk.len()))?;
}
Ok(())
}
pub(super) fn path_kind_to_str(kind: PathKind) -> &'static str {
match kind {
PathKind::Normal => "Normal",
PathKind::Error => "Error",
PathKind::Degenerate => "Degenerate",
PathKind::Unreachable => "Unreachable",
}
}
pub(super) fn str_to_path_kind(s: &str) -> Result<PathKind> {
match s {
"Normal" => Ok(PathKind::Normal),
"Error" => Ok(PathKind::Error),
"Degenerate" => Ok(PathKind::Degenerate),
"Unreachable" => Ok(PathKind::Unreachable),
_ => anyhow::bail!("Invalid path_kind in database: {}", s),
}
}
pub fn get_cached_paths(conn: &mut Connection, function_id: i64) -> Result<Vec<Path>> {
let mut stmt = conn
.prepare_cached(
"SELECT p.path_id, p.path_kind, p.entry_block, p.exit_block,
pe.block_id, pe.sequence_order
FROM cfg_paths p
JOIN cfg_path_elements pe ON p.path_id = pe.path_id
WHERE p.function_id = ?1
ORDER BY p.path_id, pe.sequence_order",
)
.context("Failed to prepare get_cached_paths query")?;
let mut path_data: HashMap<String, PathData> = HashMap::new();
let rows = stmt
.query_map(params![function_id], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, i64>(2)?,
row.get::<_, i64>(3)?,
row.get::<_, i64>(4)?,
row.get::<_, i64>(5)?,
))
})
.context("Failed to execute get_cached_paths query")?;
for row in rows {
let (path_id, kind_str, _entry_block, _exit_block, block_id, _sequence_order) = row?;
let kind = str_to_path_kind(&kind_str)
.with_context(|| format!("Invalid path_kind '{}' in database", kind_str))?;
path_data
.entry(path_id)
.or_insert_with(|| PathData {
kind,
blocks: Vec::new(),
})
.blocks
.push(block_id as BlockId);
}
let mut paths = Vec::new();
for (path_id, data) in path_data {
let path = Path::with_id(path_id, data.blocks, data.kind);
paths.push(path);
}
Ok(paths)
}
struct PathData {
kind: PathKind,
blocks: Vec<BlockId>,
}
pub fn invalidate_function_paths(conn: &mut Connection, function_id: i64) -> Result<()> {
conn.execute("BEGIN IMMEDIATE TRANSACTION", [])
.context("Failed to begin transaction for invalidate_function_paths")?;
conn.execute(
"DELETE FROM cfg_path_elements
WHERE path_id IN (SELECT path_id FROM cfg_paths WHERE function_id = ?1)",
params![function_id],
)
.context("Failed to delete cfg_path_elements")?;
conn.execute(
"DELETE FROM cfg_paths WHERE function_id = ?1",
params![function_id],
)
.context("Failed to delete cfg_paths")?;
conn.execute("COMMIT", [])
.context("Failed to commit transaction for invalidate_function_paths")?;
Ok(())
}
pub fn update_function_paths_if_changed(
conn: &mut Connection,
function_id: i64,
_new_hash: &str,
paths: &[Path],
) -> Result<bool> {
invalidate_function_paths(conn, function_id)?;
store_paths(conn, function_id, paths)?;
Ok(true)
}