use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use arrow::util::pretty::print_batches;
use arrow_array::{Int64Array, RecordBatch, RecordBatchIterator, StringArray};
use arrow_schema::{DataType, Field, Schema};
use lance::dataset::{Dataset, WriteMode, WriteParams};
use lance_csr::query::with_execution_deadline;
use lance_csr::{CypherEngine, EngineSource, GraphConfig};
use tokio::time::{timeout_at, Instant};
fn knows(src: Vec<i64>, dst: Vec<i64>) -> lance_csr::Result<RecordBatch> {
let schema = Arc::new(Schema::new(vec![
Field::new("src_id", DataType::Int64, false),
Field::new("dst_id", DataType::Int64, false),
]));
let columns = vec![
Arc::new(Int64Array::from(src)) as _,
Arc::new(Int64Array::from(dst)) as _,
];
Ok(RecordBatch::try_new(schema, columns)?)
}
async fn write_lance(batch: RecordBatch, uri: &str, mode: WriteMode) -> lance_csr::Result<()> {
let schema = batch.schema();
let params = WriteParams {
mode,
..Default::default()
};
Dataset::write(
RecordBatchIterator::new([Ok(batch)], schema),
uri,
Some(params),
)
.await?;
Ok(())
}
#[tokio::main]
async fn main() -> lance_csr::Result<()> {
let dir = tempfile::tempdir().expect("create a temporary directory");
let person_uri = dir.path().join("person.lance").display().to_string();
let knows_uri = dir.path().join("knows.lance").display().to_string();
let person = RecordBatch::try_new(
Arc::new(Schema::new(vec![
Field::new("id", DataType::Int64, false),
Field::new("name", DataType::Utf8, false),
])),
vec![
Arc::new(Int64Array::from(vec![1, 2, 3, 4])),
Arc::new(StringArray::from(vec!["Alice", "Bob", "Carol", "David"])),
],
)?;
write_lance(person, &person_uri, WriteMode::Create).await?;
write_lance(
knows(vec![1, 2, 2, 3], vec![2, 3, 4, 4])?,
&knows_uri,
WriteMode::Create,
)
.await?;
let config = GraphConfig::builder()
.node_table("Person", "id")
.rel_table("KNOWS", "Person", "Person", "src_id", "dst_id")
.build()?;
let sources = HashMap::from([
(
"Person".to_string(),
EngineSource::open_lance(person_uri, None, HashMap::new()).await?,
),
(
"KNOWS".to_string(),
EngineSource::open_lance(knows_uri.clone(), None, HashMap::new()).await?,
),
]);
let engine = CypherEngine::new(config, sources, true).await?;
let query = "MATCH (a:Person {name: $name})-[:KNOWS]->()-[:KNOWS]->(c:Person)
RETURN c.name AS friend_of_friend ORDER BY friend_of_friend";
let params = || HashMap::from([("name".to_string(), serde_json::json!("Alice"))]);
let result = engine.execute(query, params()).await?;
print_batches(&[result])?;
write_lance(knows(vec![2], vec![1])?, &knows_uri, WriteMode::Append).await?;
engine.refresh().await?;
let result = engine.execute(query, params()).await?;
print_batches(&[result])?;
let stats = &engine.csr_stats()["KNOWS"];
println!(
"{} edges, update={}, version={:?}",
stats.edges, stats.update, stats.version
);
let deadline = Instant::now() + Duration::from_secs(5);
let pairs = engine.execute(
"MATCH (a:Person), (b:Person) RETURN count(*) AS pairs",
HashMap::new(),
);
match timeout_at(deadline, with_execution_deadline(Some(deadline), pairs)).await {
Ok(result) => print_batches(&[result?])?,
Err(_) => println!("query timed out"),
}
Ok(())
}