use super::*;
fn all_identity() -> GraphAlgorithmIdentity {
GraphAlgorithmIdentity {
node_id: true,
node_label: true,
node_labels: true,
}
}
#[test]
fn identity_898_complete_sorted_labels_preserve_typed_ids_in_both_algorithms_and_paths() {
let mut fixture = Fixture::new();
let alpha = fixture.catalog.get_or_create_label("Alpha");
fixture.nodes[1].labels.insert(alpha);
fixture.definition.as_mut().unwrap().node_labels = vec!["Memory".into(), "Source".into()];
fixture.nodes[1]
.properties
.insert("id".into(), Value::Int(42));
fixture.nodes[2].properties.remove("id");
for algorithm in [GraphAlgorithmKind::PageRank, GraphAlgorithmKind::Louvain] {
let options = RunOptions {
algorithm,
identity: Some(all_identity()),
..RunOptions::default()
};
let mut reference = None;
for output in [
run(&fixture, &options, None),
streaming::run_external(&fixture, &options, None),
] {
assert_eq!(output.result.unwrap(), BatchControl::Continue);
let rows: Vec<_> = output.batches.into_iter().flatten().collect();
assert!(!rows.is_empty());
for row in &rows {
match row.values["node"] {
Value::Int(0) => {
assert_eq!(row.values["node_id"], Value::String("node-0".into()));
assert_eq!(
row.values["node_labels"],
Value::List(vec![Value::String("Memory".into())])
);
}
Value::Int(7) => {
assert_eq!(row.values["node_id"], Value::Int(42));
assert_eq!(row.values["node_label"], Value::String("Memory".into()));
assert_eq!(
row.values["node_labels"],
Value::List(
["Alpha", "Memory", "Source"]
.map(|s| Value::String(s.into()))
.to_vec()
)
);
}
Value::Int(-1) => assert_eq!(row.values["node_id"], Value::Null),
_ => panic!("unexpected algorithm node"),
}
}
if let Some(expected) = &reference {
assert_eq!(&rows, expected);
} else {
reference = Some(rows);
}
}
}
}
#[test]
fn identity_898_labels_only_never_hydrates_large_ids() {
let mut fixture = Fixture::new();
for node in &mut fixture.nodes {
node.properties
.insert("id".into(), Value::String("x".repeat(2 * 1024 * 1024)));
}
let mut options = RunOptions {
identity: Some(GraphAlgorithmIdentity {
node_labels: true,
..GraphAlgorithmIdentity::default()
}),
..RunOptions::default()
};
for output in [
run(&fixture, &options, None),
streaming::run_external(&fixture, &options, None),
] {
assert_eq!(output.result.unwrap(), BatchControl::Continue);
for row in output.batches.into_iter().flatten() {
assert!(row.values.contains_key("node_labels"));
assert!(!row.values.contains_key("node_id"));
assert!(!row.values.contains_key("node_label"));
}
}
options.identity = Some(all_identity());
for output in [
run(&fixture, &options, None),
streaming::run_external(&fixture, &options, None),
] {
assert!(output.result.is_err());
assert!(output.batches.is_empty());
}
}
#[test]
fn identity_898_late_label_payload_and_admission_fail_before_output() {
let mut fixture = Fixture::new();
fixture.relationships.clear();
let wide = fixture.catalog.get_or_create_label(&"Wide".repeat(1024));
fixture.nodes[2].labels.insert(wide);
let mut options = RunOptions {
identity: Some(all_identity()),
..RunOptions::default()
};
options.memory.batch_rows = nz(1);
options.memory.batch_payload_bytes = nz(1024);
for output in [
run(&fixture, &options, None),
streaming::run_external(&fixture, &options, None),
] {
let HawDBError::ResourceBudgetExceeded(error) = output.result.unwrap_err() else {
panic!("expected a typed per-row payload refusal")
};
assert_eq!(error.resource, ResourceBudgetKind::MemoryBytes);
assert_eq!(error.scope, ResourceBudgetScope::Operator);
assert_eq!(error.used, 0);
assert_eq!(error.budget, 1024);
assert!(error.requested > error.budget);
assert_eq!(
error.budget_option,
"ExecutionMemoryConfig.batch_payload_bytes"
);
assert_eq!(
error.memory_class,
Some(QueryMemoryClass::ResultMaterialization.as_str())
);
assert!(error.message.contains("per-row"));
assert!(output.batches.is_empty());
}
options.memory.batch_payload_bytes = nz(64 * 1024);
options.memory.query_memory_bytes = nz(4096);
for output in [
run(&fixture, &options, None),
streaming::run_external(&fixture, &options, None),
] {
assert!(output.result.is_err());
assert!(output.batches.is_empty());
}
}
#[test]
fn identity_898_missing_node_and_cancellation_emit_no_identity_rows() {
let mut fixture = Fixture::new();
let options = RunOptions {
identity: Some(all_identity()),
..RunOptions::default()
};
fixture.fail_identity_node = Some(NodeId(u64::MAX));
for output in [
run(&fixture, &options, None),
streaming::run_external(&fixture, &options, None),
] {
assert!(output
.result
.unwrap_err()
.to_string()
.contains("identity lookup sentinel"));
assert!(output.batches.is_empty());
}
fixture.fail_identity_node = None;
let cancellation = RuntimeCancellationToken::new();
cancellation.cancel();
let task = RuntimeTaskContext::without_deadline(cancellation);
for output in [
run(&fixture, &options, Some(&task)),
streaming::run_external(&fixture, &options, Some(&task)),
] {
assert!(output.result.is_err());
assert!(output.batches.is_empty());
}
}