Implement chrs-graph: Dolt-backed versioned state management
This commit is contained in:
14
chrs-graph/Cargo.toml
Normal file
14
chrs-graph/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "chrs-graph"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3"
|
||||||
110
chrs-graph/src/lib.rs
Normal file
110
chrs-graph/src/lib.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use chrono::Utc;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::{path::Path, process::Command};
|
||||||
|
use thiserror::Error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum GraphError {
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Command failed: {0}")]
|
||||||
|
CommandFailed(String),
|
||||||
|
#[error("Serde JSON error: {0}")]
|
||||||
|
SerdeJson(#[from] serde_json::Error),
|
||||||
|
#[error("Other error: {0}")]
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DoltGraph {
|
||||||
|
repo_path: std::path::PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DoltGraph {
|
||||||
|
pub fn init(path: &Path) -> Result<Self, GraphError> {
|
||||||
|
let status = Command::new("dolt")
|
||||||
|
.arg("init")
|
||||||
|
.current_dir(path)
|
||||||
|
.status()?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(GraphError::CommandFailed(format!(
|
||||||
|
"dolt init failed with status {:?}",
|
||||||
|
status
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
repo_path: path.to_path_buf(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_cmd(&self, args: &[&str]) -> Result<(), GraphError> {
|
||||||
|
let output = Command::new("dolt")
|
||||||
|
.args(args)
|
||||||
|
.current_dir(&self.repo_path)
|
||||||
|
.output()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(GraphError::CommandFailed(stderr.to_string()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(&self, message: &str) -> Result<(), GraphError> {
|
||||||
|
self.run_cmd(&["add", "."])?;
|
||||||
|
self.run_cmd(&["commit", "-m", message])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_table(&self, table_name: &str, schema: &str) -> Result<(), GraphError> {
|
||||||
|
let query = format!("CREATE TABLE {} ({})", table_name, schema);
|
||||||
|
self.run_cmd(&["sql", "-q", &query])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_node(&self, table: &str, data: Value) -> Result<(), GraphError> {
|
||||||
|
let obj = data
|
||||||
|
.as_object()
|
||||||
|
.ok_or_else(|| GraphError::Other("Data must be a JSON object".into()))?;
|
||||||
|
let columns: Vec<String> = obj.keys().cloned().collect();
|
||||||
|
let mut values: Vec<String> = Vec::new();
|
||||||
|
for key in &columns {
|
||||||
|
let v = &obj[key];
|
||||||
|
let sql_val = match v {
|
||||||
|
Value::String(s) => format!("'{}'", s.replace('\'', "''")),
|
||||||
|
Value::Number(n) => n.to_string(),
|
||||||
|
Value::Bool(b) => {
|
||||||
|
if *b {
|
||||||
|
"TRUE".into()
|
||||||
|
} else {
|
||||||
|
"FALSE".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Null => "NULL".into(),
|
||||||
|
_ => return Err(GraphError::Other("Unsupported JSON value type".into())),
|
||||||
|
};
|
||||||
|
values.push(sql_val);
|
||||||
|
}
|
||||||
|
let query = format!(
|
||||||
|
"INSERT INTO {} ({}) VALUES ({})",
|
||||||
|
table,
|
||||||
|
columns.join(", "),
|
||||||
|
values.join(", ")
|
||||||
|
);
|
||||||
|
self.run_cmd(&["sql", "-q", &query])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_create_table_and_commit() {
|
||||||
|
let dir = TempDir::new().unwrap();
|
||||||
|
let graph = DoltGraph::init(dir.path()).expect("init failed");
|
||||||
|
graph.create_table("nodes", "id INT PRIMARY KEY, name TEXT").expect("create table failed");
|
||||||
|
graph.commit("initial commit with table").expect("commit failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user