use crate::error::{IntentError, Result};
use neo4rs::{query, Graph};
const SCHEMA_VERSION: &str = "0.1.0";
pub async fn ensure_schema(graph: &Graph, project_id: &str) -> Result<()> {
if is_schema_current(graph, project_id).await? {
return Ok(());
}
run_ddl(
graph,
"CREATE CONSTRAINT task_unique IF NOT EXISTS \
FOR (t:Task) REQUIRE (t.project_id, t.id) IS UNIQUE",
)
.await?;
run_ddl(
graph,
"CREATE CONSTRAINT event_unique IF NOT EXISTS \
FOR (e:Event) REQUIRE (e.project_id, e.id) IS UNIQUE",
)
.await?;
run_ddl(
graph,
"CREATE CONSTRAINT session_unique IF NOT EXISTS \
FOR (s:Session) REQUIRE (s.project_id, s.session_id) IS UNIQUE",
)
.await?;
run_ddl(
graph,
"CREATE CONSTRAINT counter_unique IF NOT EXISTS \
FOR (c:Counter) REQUIRE (c.project_id, c.entity) IS UNIQUE",
)
.await?;
run_ddl(
graph,
"CREATE FULLTEXT INDEX task_fulltext IF NOT EXISTS \
FOR (t:Task) ON EACH [t.name, t.spec]",
)
.await?;
run_ddl(
graph,
"CREATE FULLTEXT INDEX event_fulltext IF NOT EXISTS \
FOR (e:Event) ON EACH [e.discussion_data]",
)
.await?;
graph
.run(
query(
"MERGE (c:Counter {project_id: $pid, entity: 'task'}) \
ON CREATE SET c.next_id = 1",
)
.param("pid", project_id.to_string()),
)
.await
.map_err(|e| neo4j_err("create task counter", e))?;
graph
.run(
query(
"MERGE (c:Counter {project_id: $pid, entity: 'event'}) \
ON CREATE SET c.next_id = 1",
)
.param("pid", project_id.to_string()),
)
.await
.map_err(|e| neo4j_err("create event counter", e))?;
graph
.run(
query(
"MERGE (sv:SchemaVersion {project_id: $pid}) \
SET sv.version = $version",
)
.param("pid", project_id.to_string())
.param("version", SCHEMA_VERSION.to_string()),
)
.await
.map_err(|e| neo4j_err("stamp schema version", e))?;
Ok(())
}
async fn is_schema_current(graph: &Graph, project_id: &str) -> Result<bool> {
let mut result = graph
.execute(
query(
"OPTIONAL MATCH (sv:SchemaVersion {project_id: $pid}) \
RETURN sv.version AS version",
)
.param("pid", project_id.to_string()),
)
.await
.map_err(|e| neo4j_err("check schema version", e))?;
match result
.next()
.await
.map_err(|e| neo4j_err("read schema version", e))?
{
Some(row) => {
let version: Option<String> = row.get("version").ok();
Ok(version.as_deref() == Some(SCHEMA_VERSION))
},
None => Ok(false),
}
}
async fn run_ddl(graph: &Graph, cypher: &str) -> Result<()> {
graph.run(query(cypher)).await.map_err(|e| {
neo4j_err(
&format!("schema DDL: {}", &cypher[..cypher.len().min(60)]),
e,
)
})
}
fn neo4j_err(context: &str, e: impl std::fmt::Display) -> IntentError {
IntentError::OtherError(anyhow::anyhow!("Neo4j {}: {}", context, e))
}