use std::fs::File;
use std::sync::Arc;
use arrow::array::Array;
use datafusion::prelude::SessionContext;
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;
use tempfile::TempDir;
use graphforge_core::uuid::new_v7;
use graphforge_core::{OntologyMode, TypeId};
use graphforge_ir::{IrLiteral, RuntimeCatalog};
use graphforge_storage::{GraphCatalog, GraphWriter, read_topology_generation};
const TS: i64 = 1_700_000_000_000_000;
async fn count(ctx: &SessionContext, sql: &str) -> usize {
let df = ctx.sql(sql).await.unwrap();
let batches = df.collect().await.unwrap();
batches
.iter()
.map(arrow::array::RecordBatch::num_rows)
.sum()
}
#[tokio::test]
async fn strict_mode_round_trip_nodes_and_edges() {
let dir = TempDir::new().unwrap();
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
let a = new_v7();
let b = new_v7();
let c = new_v7();
w.create_node(a, TypeId(0)).unwrap();
w.create_node(b, TypeId(0)).unwrap();
w.create_node(c, TypeId(0)).unwrap();
assert_eq!(w.create_edge(new_v7(), "KNOWS", &a, &b).unwrap(), 1);
assert_eq!(w.create_edge(new_v7(), "KNOWS", &b, &c).unwrap(), 2);
w.flush().unwrap();
assert!(dir.path().join("topology/nodes.parquet").exists());
assert!(dir.path().join("topology/edges/KNOWS.parquet").exists());
let mut rc = RuntimeCatalog::new();
rc.intern_relation_type("KNOWS");
let gc = GraphCatalog::open(dir.path(), None, &rc).unwrap();
let ctx = SessionContext::new();
ctx.register_catalog("graph", Arc::new(gc));
assert_eq!(
count(&ctx, "SELECT node_id FROM graph.graph.topology_nodes").await,
3
);
assert_eq!(
count(&ctx, r#"SELECT edge_id FROM graph.graph."edges_KNOWS""#).await,
2
);
}
#[tokio::test]
async fn exploratory_mode_routes_to_catch_all_files() {
let dir = TempDir::new().unwrap();
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
let src = new_v7();
let dst = new_v7();
w.create_node(src, TypeId(0)).unwrap();
w.create_node(dst, TypeId(0)).unwrap();
w.set_properties(
&src,
None,
std::collections::HashMap::from([
("name".to_owned(), IrLiteral::Str("alice".to_owned())),
("age".to_owned(), IrLiteral::Int(30)),
]),
)
.unwrap();
w.create_edge(new_v7(), "UNKNOWN_REL", &src, &dst).unwrap();
w.flush().unwrap();
assert!(
dir.path()
.join("topology/edges/_exploratory.parquet")
.exists()
);
assert!(dir.path().join("properties/_untyped.parquet").exists());
let gc = GraphCatalog::open(dir.path(), None, &RuntimeCatalog::new()).unwrap();
let ctx = SessionContext::new();
ctx.register_catalog("graph", Arc::new(gc));
let df = ctx
.sql("SELECT rel_type_name FROM graph.graph.edges__exploratory")
.await
.unwrap();
let batches = df.collect().await.unwrap();
let total: usize = batches
.iter()
.map(arrow::array::RecordBatch::num_rows)
.sum();
assert_eq!(total, 1);
let col = batches[0]
.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.unwrap();
assert_eq!(col.value(0), "UNKNOWN_REL");
let path = dir.path().join("properties/_untyped.parquet");
let file = File::open(&path).unwrap();
let builder = ParquetRecordBatchReaderBuilder::try_new(file).unwrap();
let schema = builder.schema().clone();
assert!(schema.field_with_name("node_uuid").is_ok());
assert!(schema.field_with_name("name").is_ok());
assert!(schema.field_with_name("age").is_ok());
let mut reader = builder.build().unwrap();
let batch = reader.next().unwrap().unwrap();
assert_eq!(batch.num_rows(), 1);
}
#[tokio::test]
async fn localdatetime_property_round_trips_through_storage() {
use arrow::array::{Int64Array, StructArray, Time64NanosecondArray};
let dir = TempDir::new().unwrap();
let node = new_v7();
let (days, nanos) = (5_393_i64, 45_074_645_876_123_i64);
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w.create_node(node, TypeId(0)).unwrap();
w.set_properties(
&node,
None,
std::collections::HashMap::from([(
"ts".to_owned(),
IrLiteral::LocalDateTime { days, nanos },
)]),
)
.unwrap();
w.flush().unwrap();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w2.set_properties(
&node,
None,
std::collections::HashMap::from([("n".to_owned(), IrLiteral::Int(7))]),
)
.unwrap();
w2.flush().unwrap();
let batch = read_property_batch(dir.path(), "_untyped");
let ts = batch.column_by_name("ts").expect("ts column");
let s = ts.as_any().downcast_ref::<StructArray>().expect("struct");
let date = s
.column(0)
.as_any()
.downcast_ref::<Int64Array>()
.expect("date child");
let time = s
.column(1)
.as_any()
.downcast_ref::<Time64NanosecondArray>()
.expect("time child");
assert_eq!(
date.value(0),
days,
"localdatetime date survives round-trip"
);
assert_eq!(
time.value(0),
nanos,
"localdatetime time survives round-trip"
);
}
#[tokio::test]
async fn datetime_and_time_properties_round_trip_through_storage() {
use arrow::array::{Int32Array, Int64Array, StringArray, StructArray, Time64NanosecondArray};
let dir = TempDir::new().unwrap();
let node = new_v7();
let (days, nanos, offset) = (5_393_i64, 45_074_645_876_123_i64, -3_600_i32);
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w.create_node(node, TypeId(0)).unwrap();
w.set_properties(
&node,
None,
std::collections::HashMap::from([
(
"dt".to_owned(),
IrLiteral::ZonedDateTime {
days,
nanos,
offset,
zone: None,
},
),
("t".to_owned(), IrLiteral::Time(nanos)),
]),
)
.unwrap();
w.flush().unwrap();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w2.set_properties(
&node,
None,
std::collections::HashMap::from([("n".to_owned(), IrLiteral::Int(7))]),
)
.unwrap();
w2.flush().unwrap();
let batch = read_property_batch(dir.path(), "_untyped");
let t = batch.column_by_name("t").expect("t column");
let tarr = t
.as_any()
.downcast_ref::<Time64NanosecondArray>()
.expect("time64");
assert_eq!(tarr.value(0), nanos);
let dt = batch.column_by_name("dt").expect("dt column");
let s = dt.as_any().downcast_ref::<StructArray>().expect("struct");
assert_eq!(
s.column(0)
.as_any()
.downcast_ref::<Int64Array>()
.unwrap()
.value(0),
days
);
assert_eq!(
s.column(1)
.as_any()
.downcast_ref::<Time64NanosecondArray>()
.unwrap()
.value(0),
nanos
);
assert_eq!(
s.column(2)
.as_any()
.downcast_ref::<Int32Array>()
.unwrap()
.value(0),
offset
);
let zone = s.column(3).as_any().downcast_ref::<StringArray>().unwrap();
assert!(zone.is_null(0), "offset-only datetime stores a NULL zone");
}
#[tokio::test]
async fn list_of_temporals_property_round_trips_through_storage() {
use arrow::array::{Int64Array, ListArray, StructArray};
let dir = TempDir::new().unwrap();
let node = new_v7();
let (d0, d1) = (5_393_i64, 5_394_i64);
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w.create_node(node, TypeId(0)).unwrap();
w.set_properties(
&node,
None,
std::collections::HashMap::from([(
"dates".to_owned(),
IrLiteral::List(vec![IrLiteral::Date(d0), IrLiteral::Date(d1)]),
)]),
)
.unwrap();
w.flush().unwrap();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w2.set_properties(
&node,
None,
std::collections::HashMap::from([("n".to_owned(), IrLiteral::Int(7))]),
)
.unwrap();
w2.flush().unwrap();
let batch = read_property_batch(dir.path(), "_untyped");
let col = batch.column_by_name("dates").expect("dates column");
let list = col.as_any().downcast_ref::<ListArray>().expect("list");
let elems = list.value(0);
let dates = elems
.as_any()
.downcast_ref::<StructArray>()
.expect("date elements");
assert_eq!(dates.len(), 2, "list length survives round-trip");
let epoch_day = dates
.column(0)
.as_any()
.downcast_ref::<Int64Array>()
.expect("epoch_day child");
assert_eq!(epoch_day.value(0), d0);
assert_eq!(epoch_day.value(1), d1);
}
fn read_property_batch(dir: &std::path::Path, stem: &str) -> arrow::array::RecordBatch {
let path = dir.join(format!("properties/{stem}.parquet"));
let file = File::open(&path).unwrap_or_else(|e| panic!("open {path:?}: {e}"));
let mut reader = ParquetRecordBatchReaderBuilder::try_new(file)
.unwrap()
.build()
.unwrap();
reader.next().unwrap().unwrap()
}
fn read_edge_property_batch(dir: &std::path::Path, stem: &str) -> arrow::array::RecordBatch {
let path = dir.join(format!("edge_properties/{stem}.parquet"));
let file = File::open(&path).unwrap_or_else(|e| panic!("open {path:?}: {e}"));
let mut reader = ParquetRecordBatchReaderBuilder::try_new(file)
.unwrap()
.build()
.unwrap();
reader.next().unwrap().unwrap()
}
#[tokio::test]
async fn append_round_trip_continues_surrogates() {
let dir = TempDir::new().unwrap();
let (a, b, c) = (new_v7(), new_v7(), new_v7());
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
assert_eq!(w.create_node(a, TypeId(0)).unwrap(), 1);
assert_eq!(w.create_node(b, TypeId(0)).unwrap(), 2);
assert_eq!(w.create_node(c, TypeId(0)).unwrap(), 3);
assert_eq!(w.create_edge(new_v7(), "KNOWS", &a, &b).unwrap(), 1);
assert_eq!(w.create_edge(new_v7(), "KNOWS", &b, &c).unwrap(), 2);
w.flush().unwrap();
let (d, e) = (new_v7(), new_v7());
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
assert_eq!(
w2.create_node(d, TypeId(0)).unwrap(),
4,
"node ids continue"
);
assert_eq!(w2.create_node(e, TypeId(0)).unwrap(), 5);
assert_eq!(
w2.create_edge(new_v7(), "KNOWS", &d, &e).unwrap(),
3,
"edge ids continue"
);
w2.flush().unwrap();
let mut rc = RuntimeCatalog::new();
rc.intern_relation_type("KNOWS");
let gc = GraphCatalog::open(dir.path(), None, &rc).unwrap();
let ctx = SessionContext::new();
ctx.register_catalog("graph", Arc::new(gc));
assert_eq!(
count(&ctx, "SELECT node_id FROM graph.graph.topology_nodes").await,
5
);
assert_eq!(
count(&ctx, r#"SELECT edge_id FROM graph.graph."edges_KNOWS""#).await,
3
);
assert_eq!(
count(
&ctx,
r#"SELECT t.node_id FROM graph.graph.topology_nodes t JOIN graph.graph."edges_KNOWS" e ON t.node_id = e.src_id WHERE e.edge_id = 3"#,
)
.await,
1
);
}
#[tokio::test]
async fn append_property_merge_adds_new_column() {
let dir = TempDir::new().unwrap();
let a = new_v7();
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w.create_node(a, TypeId(0)).unwrap();
w.set_properties(
&a,
None,
std::collections::HashMap::from([("name".to_owned(), IrLiteral::Str("A".to_owned()))]),
)
.unwrap();
w.flush().unwrap();
let b = new_v7();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w2.create_node(b, TypeId(0)).unwrap();
w2.set_properties(
&b,
None,
std::collections::HashMap::from([
("name".to_owned(), IrLiteral::Str("B".to_owned())),
("age".to_owned(), IrLiteral::Int(10)),
]),
)
.unwrap();
w2.flush().unwrap();
let batch = read_property_batch(dir.path(), "_untyped");
assert_eq!(batch.num_rows(), 2, "both rows retained");
let age = batch
.column_by_name("age")
.expect("age column added on the second flush")
.as_any()
.downcast_ref::<arrow::array::Int64Array>()
.unwrap();
assert!(age.is_null(0));
assert_eq!(age.value(1), 10);
}
#[tokio::test]
async fn append_property_merge_preserves_heterogeneous_scalar_types() {
let dir = TempDir::new().unwrap();
let a = new_v7();
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w.create_node(a, TypeId(0)).unwrap();
w.set_properties(
&a,
None,
std::collections::HashMap::from([("x".to_owned(), IrLiteral::Int(1))]),
)
.unwrap();
w.flush().unwrap();
let b = new_v7();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w2.create_node(b, TypeId(0)).unwrap();
w2.set_properties(
&b,
None,
std::collections::HashMap::from([("x".to_owned(), IrLiteral::Str("two".to_owned()))]),
)
.unwrap();
w2.flush().unwrap();
let batch = read_property_batch(dir.path(), "_untyped");
let x = batch
.column_by_name("x")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::StructArray>()
.expect("x uses tagged heterogeneous scalars");
let tags = x
.column_by_name("__het_tag")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::Int8Array>()
.unwrap();
assert_eq!((tags.value(0), tags.value(1)), (0, 2));
assert_eq!(
x.column_by_name("__het_int")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::Int64Array>()
.unwrap()
.value(0),
1
);
assert_eq!(
x.column_by_name("__het_str")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.unwrap()
.value(1),
"two"
);
}
#[tokio::test]
async fn append_property_merge_null_first_across_flushes() {
let dir = TempDir::new().unwrap();
let a = new_v7();
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w.create_node(a, TypeId(0)).unwrap();
w.set_properties(
&a,
None,
std::collections::HashMap::from([("score".to_owned(), IrLiteral::Null)]),
)
.unwrap();
w.flush().unwrap();
let b = new_v7();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
w2.create_node(b, TypeId(0)).unwrap();
w2.set_properties(
&b,
None,
std::collections::HashMap::from([("score".to_owned(), IrLiteral::Int(42))]),
)
.unwrap();
w2.flush().unwrap();
let batch = read_property_batch(dir.path(), "_untyped");
let score = batch
.column_by_name("score")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::Int64Array>()
.expect("score infers Int64, not pinned to Utf8 by the leading null");
assert_eq!(batch.num_rows(), 2);
assert_eq!(score.null_count(), 1);
}
#[tokio::test]
async fn append_edge_stems_are_isolated() {
let dir = TempDir::new().unwrap();
let (a, b) = (new_v7(), new_v7());
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
w.create_node(a, TypeId(0)).unwrap();
w.create_node(b, TypeId(0)).unwrap();
w.create_edge(new_v7(), "KNOWS", &a, &b).unwrap();
w.flush().unwrap();
let mut w2 = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
w2.create_node(a, TypeId(0)).unwrap();
w2.create_node(b, TypeId(0)).unwrap();
w2.create_edge(new_v7(), "LIKES", &a, &b).unwrap();
w2.flush().unwrap();
let mut rc = RuntimeCatalog::new();
rc.intern_relation_type("KNOWS");
rc.intern_relation_type("LIKES");
let gc = GraphCatalog::open(dir.path(), None, &rc).unwrap();
let ctx = SessionContext::new();
ctx.register_catalog("graph", Arc::new(gc));
assert_eq!(
count(&ctx, r#"SELECT edge_id FROM graph.graph."edges_KNOWS""#).await,
1,
"KNOWS untouched by the LIKES-only flush"
);
assert_eq!(
count(&ctx, r#"SELECT edge_id FROM graph.graph."edges_LIKES""#).await,
1
);
}
fn write_knows_edge_with_props(
dir: &std::path::Path,
mode: OntologyMode,
props: std::collections::HashMap<String, IrLiteral>,
) {
let (a, b) = (new_v7(), new_v7());
let mut w = GraphWriter::open_at(dir, mode, TS).unwrap();
w.create_node(a, TypeId(0)).unwrap();
w.create_node(b, TypeId(0)).unwrap();
let edge = new_v7();
w.create_edge(edge, "KNOWS", &a, &b).unwrap();
w.set_edge_properties(&edge, Some("KNOWS"), props).unwrap();
w.flush().unwrap();
}
#[tokio::test]
async fn edge_property_round_trip_persists_value() {
let dir = TempDir::new().unwrap();
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("since".to_owned(), IrLiteral::Int(2020))]),
);
assert!(dir.path().join("edge_properties/KNOWS.parquet").exists());
assert!(!dir.path().join("properties/KNOWS.parquet").exists());
let batch = read_edge_property_batch(dir.path(), "KNOWS");
assert_eq!(batch.num_rows(), 1);
assert!(
batch.schema().field_with_name("edge_uuid").is_ok(),
"edge-property file is keyed by edge_uuid"
);
let since = batch
.column_by_name("since")
.expect("since column")
.as_any()
.downcast_ref::<arrow::array::Int64Array>()
.expect("since is Int64");
assert_eq!(since.value(0), 2020);
}
#[tokio::test]
async fn edge_property_routes_by_rel_name_even_in_exploratory_mode() {
let dir = TempDir::new().unwrap();
write_knows_edge_with_props(
dir.path(),
OntologyMode::Exploratory,
std::collections::HashMap::from([("since".to_owned(), IrLiteral::Int(1999))]),
);
assert!(dir.path().join("edge_properties/KNOWS.parquet").exists());
assert!(!dir.path().join("edge_properties/_untyped.parquet").exists());
}
#[tokio::test]
async fn append_edge_property_merge_adds_new_column() {
let dir = TempDir::new().unwrap();
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("since".to_owned(), IrLiteral::Int(2020))]),
);
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([
("since".to_owned(), IrLiteral::Int(2021)),
("weight".to_owned(), IrLiteral::Float(0.5)),
]),
);
let batch = read_edge_property_batch(dir.path(), "KNOWS");
assert_eq!(batch.num_rows(), 2, "both edge rows retained");
let weight = batch
.column_by_name("weight")
.expect("weight column added on the second flush")
.as_any()
.downcast_ref::<arrow::array::Float64Array>()
.unwrap();
assert!(weight.is_null(0));
assert_eq!(weight.value(1), 0.5);
}
#[tokio::test]
async fn append_edge_property_merge_preserves_heterogeneous_scalar_types() {
let dir = TempDir::new().unwrap();
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("x".to_owned(), IrLiteral::Int(1))]),
);
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("x".to_owned(), IrLiteral::Str("two".to_owned()))]),
);
let batch = read_edge_property_batch(dir.path(), "KNOWS");
let x = batch
.column_by_name("x")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::StructArray>()
.expect("x uses tagged heterogeneous scalars");
let tags = x
.column_by_name("__het_tag")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::Int8Array>()
.unwrap();
assert_eq!((tags.value(0), tags.value(1)), (0, 2));
}
#[tokio::test]
async fn append_edge_property_merge_null_first_across_flushes() {
let dir = TempDir::new().unwrap();
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("score".to_owned(), IrLiteral::Null)]),
);
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("score".to_owned(), IrLiteral::Int(42))]),
);
let batch = read_edge_property_batch(dir.path(), "KNOWS");
let score = batch
.column_by_name("score")
.unwrap()
.as_any()
.downcast_ref::<arrow::array::Int64Array>()
.expect("score infers Int64, not pinned to Utf8 by the leading null");
assert_eq!(batch.num_rows(), 2);
assert_eq!(score.null_count(), 1);
}
#[tokio::test]
async fn edge_property_stems_are_isolated() {
let dir = TempDir::new().unwrap();
write_knows_edge_with_props(
dir.path(),
OntologyMode::Strict,
std::collections::HashMap::from([("since".to_owned(), IrLiteral::Int(2020))]),
);
let (a, b) = (new_v7(), new_v7());
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
w.create_node(a, TypeId(0)).unwrap();
w.create_node(b, TypeId(0)).unwrap();
let edge = new_v7();
w.create_edge(edge, "LIKES", &a, &b).unwrap();
w.set_edge_properties(
&edge,
Some("LIKES"),
std::collections::HashMap::from([("rating".to_owned(), IrLiteral::Int(5))]),
)
.unwrap();
w.flush().unwrap();
let knows = read_edge_property_batch(dir.path(), "KNOWS");
assert_eq!(knows.num_rows(), 1);
assert!(knows.schema().field_with_name("since").is_ok());
assert!(
knows.schema().field_with_name("rating").is_err(),
"KNOWS file must not gain LIKES' column"
);
let likes = read_edge_property_batch(dir.path(), "LIKES");
assert_eq!(likes.num_rows(), 1);
assert!(likes.schema().field_with_name("rating").is_ok());
}
#[test]
fn flush_bumps_topology_generation_once_per_topology_flush() {
let dir = TempDir::new().unwrap();
assert_eq!(read_topology_generation(dir.path()).unwrap(), 0);
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Strict, TS).unwrap();
let (a, b) = (new_v7(), new_v7());
w.create_node(a, TypeId(0)).unwrap();
w.create_node(b, TypeId(0)).unwrap();
w.create_edge(new_v7(), "KNOWS", &a, &b).unwrap();
w.flush().unwrap();
assert_eq!(
read_topology_generation(dir.path()).unwrap(),
1,
"one bump per committed batch, however many topology files it staged"
);
w.create_node(new_v7(), TypeId(0)).unwrap();
w.flush().unwrap();
assert_eq!(read_topology_generation(dir.path()).unwrap(), 2);
}
#[test]
fn property_only_flush_does_not_bump_topology_generation() {
let dir = TempDir::new().unwrap();
let mut w = GraphWriter::open_at(dir.path(), OntologyMode::Exploratory, TS).unwrap();
let a = new_v7();
w.create_node(a, TypeId(0)).unwrap();
w.flush().unwrap();
assert_eq!(read_topology_generation(dir.path()).unwrap(), 1);
w.set_properties(
&a,
None,
std::collections::HashMap::from([("name".to_owned(), IrLiteral::Str("A".into()))]),
)
.unwrap();
w.flush().unwrap();
assert!(dir.path().join("properties/_untyped.parquet").exists());
assert_eq!(read_topology_generation(dir.path()).unwrap(), 1);
w.flush().unwrap();
assert_eq!(read_topology_generation(dir.path()).unwrap(), 1);
}