use crate::executor::base::{MemoryBudget, MemoryReservation};
use crate::executor::streaming::chunk::DataChunk;
use crate::executor::streaming::runtime::ExecutionRuntime;
use crate::storage::FlatVertexRecord;
use linkrs_core::error::QueryError;
use linkrs_core::types::storage_ids::VertexId;
use linkrs_core::wal::EntityRef;
use linkrs_core::{Edge, Value, Vertex};
use std::collections::HashMap;
use std::sync::Arc;
pub(crate) fn entity_ref_to_vertex_id(entity_ref: &EntityRef) -> Option<VertexId> {
match entity_ref {
EntityRef::Vertex(vid) => Some(*vid),
EntityRef::Edge { .. } => None,
}
}
pub(crate) fn make_flat_vertex_record_row(
record: FlatVertexRecord,
flatten: &[Arc<str>],
) -> Vec<Value> {
let properties: HashMap<Arc<str>, Value> = record.props.into_iter().collect();
let vertex = Vertex::new(
record.vid,
linkrs_core::Tag::new(record.tag_name, properties),
);
make_flat_vertex_row(vertex, flatten)
}
pub(crate) fn make_flat_vertex_record_identity_row(
record: FlatVertexRecord,
flatten: &[Arc<str>],
) -> Vec<Value> {
let null = Value::Null(linkrs_core::value::NullType::Null);
let mut row = Vec::with_capacity(flatten.len() + 1);
row.push(Value::VertexId(record.vid));
for prop in flatten {
let value = record
.props
.iter()
.find(|(name, _)| name.as_ref() == prop.as_ref())
.map(|(_, value)| value.clone())
.unwrap_or_else(|| null.clone());
row.push(value);
}
row
}
pub(crate) fn make_flat_vertex_row(vertex: Vertex, flatten: &[Arc<str>]) -> Vec<Value> {
let props: Vec<Value> = flatten
.iter()
.map(|prop| {
vertex
.property_value(prop)
.unwrap_or_else(|| Value::Null(linkrs_core::value::NullType::Null))
})
.collect();
let mut row = Vec::with_capacity(flatten.len() + 1);
row.push(Value::Vertex(Box::new(vertex)));
row.extend(props);
row
}
pub(crate) fn make_flat_covering_vertex_row(
entity_ref: &EntityRef,
tag: &str,
columns: Vec<(Arc<str>, Value)>,
flatten: &[Arc<str>],
) -> Option<Vec<Value>> {
let vertex_id = entity_ref_to_vertex_id(entity_ref)?;
let vertex = Vertex::new(
vertex_id,
linkrs_core::Tag::new(tag.to_string(), columns.into_iter().collect()),
);
Some(make_flat_vertex_row(vertex, flatten))
}
pub(crate) fn make_edge_row(edge: Edge) -> Vec<Value> {
vec![Value::Edge(Box::new(edge))]
}
pub(crate) fn make_flat_edge_identity_row(edge: Edge, flatten: &[Arc<str>]) -> Vec<Value> {
let null = Value::Null(linkrs_core::value::NullType::Null);
let mut row = Vec::with_capacity(flatten.len() + 1);
row.push(Value::edge_header(linkrs_core::EdgeHeader::from_edge(
&edge,
)));
for prop in flatten {
let value = edge
.properties()
.get(prop.as_ref())
.cloned()
.unwrap_or_else(|| null.clone());
row.push(value);
}
row
}
pub(crate) fn make_flat_edge_row(edge: Edge, flatten: &[Arc<str>]) -> Vec<Value> {
let props: Vec<Value> = flatten
.iter()
.map(|prop| {
edge.properties()
.get(prop.as_ref())
.cloned()
.unwrap_or_else(|| Value::Null(linkrs_core::value::NullType::Null))
})
.collect();
let mut row = Vec::with_capacity(flatten.len() + 1);
row.push(Value::Edge(Box::new(edge)));
row.extend(props);
row
}
pub(crate) fn make_flat_covering_edge_row(
entity_ref: &EntityRef,
columns: Vec<(Arc<str>, Value)>,
edge_type: String,
flatten: &[Arc<str>],
) -> Option<Vec<Value>> {
let EntityRef::Edge {
src, dst, ranking, ..
} = entity_ref
else {
return None;
};
let mut edge = Edge::new_empty(*src, *dst, edge_type, *ranking);
for (name, value) in columns {
edge.set_property(name, value);
}
Some(make_flat_edge_row(edge, flatten))
}
pub(crate) fn storage_error(
source: &str,
operation: &str,
space_name: &str,
error: impl std::fmt::Display,
) -> QueryError {
QueryError::execution(format!(
"{} {} failed for space '{}': {}",
source, operation, space_name, error
))
}
pub(crate) fn parse_vertex_id(value: &str) -> Result<VertexId, QueryError> {
if let Ok(parsed) = value.parse::<i64>() {
return VertexId::try_from_int64(parsed)
.map_err(|e| QueryError::execution(format!("Invalid vertex id '{value}': {e}")));
}
VertexId::try_from_string(value)
.map_err(|e| QueryError::execution(format!("Invalid vertex id '{value}': {e}")))
}
pub(crate) fn reserve_memory(
runtime: &Option<Arc<ExecutionRuntime>>,
rows: &[Vec<Value>],
) -> Result<Option<MemoryReservation>, QueryError> {
reserve_memory_with_extra(runtime, rows, 0)
}
pub(crate) fn reserve_memory_with_extra(
runtime: &Option<Arc<ExecutionRuntime>>,
rows: &[Vec<Value>],
extra_bytes: usize,
) -> Result<Option<MemoryReservation>, QueryError> {
let Some(runtime) = runtime.as_ref() else {
return Ok(None);
};
let bytes = MemoryBudget::estimate_rows_memory(rows).saturating_add(extra_bytes);
runtime.memory_budget.reserve(bytes).map(Some)
}
pub(crate) fn attach_columnar_stats(
runtime: &Option<Arc<ExecutionRuntime>>,
chunk: DataChunk,
) -> DataChunk {
match runtime.as_ref() {
Some(rt) => chunk.with_columnar_stats(rt.columnar_stats()),
None => chunk,
}
}
#[cfg(test)]
mod tests {
use super::*;
use linkrs_core::types::storage_ids::VertexId;
use linkrs_core::Tag;
use std::collections::HashMap;
fn test_vertex_with_props(props: Vec<(String, Value)>) -> Vertex {
let properties: HashMap<Arc<str>, Value> = props
.into_iter()
.map(|(name, value)| (Arc::from(name.as_str()), value))
.collect();
Vertex::new(
VertexId::try_from_int64(1).expect("valid vertex id"),
Tag::new(String::new(), properties),
)
}
#[test]
fn flat_vertex_row_contains_projected_properties_in_order() {
let vertex = Vertex::new(
VertexId::try_from_int64(1).expect("valid vertex id"),
Tag::new(
String::new(),
HashMap::from([
("age".into(), Value::BigInt(30)),
("name".into(), Value::string("Alice")),
]),
),
);
let row = make_flat_vertex_row(vertex, &[Arc::from("age"), Arc::from("name")]);
assert_eq!(row.len(), 3);
assert!(matches!(&row[0], Value::Vertex(_)));
assert_eq!(row[1], Value::BigInt(30));
assert_eq!(row[2], Value::string("Alice"));
}
#[test]
fn flat_vertex_row_empty_flatten_keeps_single_entity_column() {
let vertex = test_vertex_with_props(vec![]);
let row = make_flat_vertex_row(vertex, &[]);
assert_eq!(row.len(), 1);
assert!(matches!(&row[0], Value::Vertex(_)));
}
#[test]
fn flat_vertex_row_missing_property_is_null() {
let vertex = test_vertex_with_props(vec![]);
let row = make_flat_vertex_row(vertex, &[Arc::from("missing")]);
assert_eq!(row.len(), 2);
assert!(matches!(&row[1], Value::Null(_)));
}
#[test]
fn flat_vertex_row_reads_tag_properties_and_tag_name() {
let vertex = Vertex::new(
VertexId::try_from_int64(1).expect("valid vertex id"),
Tag::new(
"person".to_string(),
[("city".into(), Value::string("NYC"))]
.into_iter()
.collect(),
),
);
let row = make_flat_vertex_row(vertex.clone(), &[Arc::from("city")]);
assert_eq!(row[1], Value::string("NYC"));
let row = make_flat_vertex_row(vertex, &[Arc::from("person")]);
assert!(matches!(&row[1], Value::Null(_)));
}
#[test]
fn flat_edge_row_contains_projected_properties() {
let mut edge = Edge::new_empty(
VertexId::try_from_int64(1).expect("valid vertex id"),
VertexId::try_from_int64(2).expect("valid vertex id"),
"friend".to_string(),
0,
);
edge.set_property("since".into(), Value::BigInt(2024));
let row = make_flat_edge_row(edge, &[Arc::from("since"), Arc::from("missing")]);
assert_eq!(row.len(), 3);
assert!(matches!(&row[0], Value::Edge(_)));
assert_eq!(row[1], Value::BigInt(2024));
assert!(matches!(&row[2], Value::Null(_)));
}
#[test]
fn flat_vertex_record_row_rebuilds_vertex_and_flat_columns() {
let record = FlatVertexRecord {
vid: VertexId::try_from_int64(42).expect("valid vertex id"),
internal_id: 7,
tag_name: "person".to_string(),
props: vec![
("age".into(), Value::BigInt(30)),
("name".into(), Value::string("Alice")),
],
};
let row = make_flat_vertex_record_row(record, &[Arc::from("name"), Arc::from("age")]);
assert_eq!(row.len(), 3);
let Value::Vertex(vertex) = &row[0] else {
panic!("slot 0 must hold the rebuilt vertex");
};
assert_eq!(
vertex.vid,
VertexId::try_from_int64(42).expect("valid vertex id")
);
assert_eq!(vertex.tag.name, "person");
assert_eq!(vertex.tag_name(), "person");
assert_eq!(vertex.property_value("age"), Some(Value::BigInt(30)));
assert_eq!(row[1], Value::string("Alice"));
assert_eq!(row[2], Value::BigInt(30));
}
#[test]
fn flat_vertex_record_identity_row_emits_id_without_boxing() {
let record = FlatVertexRecord {
vid: VertexId::try_from_int64(42).expect("valid vertex id"),
internal_id: 7,
tag_name: "person".to_string(),
props: vec![
("age".into(), Value::BigInt(30)),
("name".into(), Value::string("Alice")),
],
};
let row = make_flat_vertex_record_identity_row(record, &[Arc::from("name")]);
assert_eq!(row.len(), 2);
assert_eq!(
row[0],
Value::VertexId(VertexId::try_from_int64(42).expect("valid vertex id"))
);
assert_eq!(row[1], Value::string("Alice"));
}
#[test]
fn flat_edge_identity_row_emits_header_without_boxing() {
let mut edge = Edge::new_empty(
VertexId::try_from_int64(1).expect("valid vertex id"),
VertexId::try_from_int64(2).expect("valid vertex id"),
"friend".to_string(),
3,
);
edge.set_property("since".into(), Value::BigInt(2024));
let row = make_flat_edge_identity_row(edge, &[Arc::from("since")]);
assert_eq!(row.len(), 2);
let Value::EdgeHeader(header) = &row[0] else {
panic!("slot 0 must hold the edge header");
};
assert_eq!(
header.src,
VertexId::try_from_int64(1).expect("valid vertex id")
);
assert_eq!(
header.dst,
VertexId::try_from_int64(2).expect("valid vertex id")
);
assert_eq!(header.edge_type, "friend");
assert_eq!(header.ranking, 3);
assert_eq!(row[1], Value::BigInt(2024));
}
#[test]
fn flat_vertex_record_row_missing_property_is_null() {
let record = FlatVertexRecord {
vid: VertexId::try_from_int64(42).expect("valid vertex id"),
internal_id: 7,
tag_name: "person".to_string(),
props: vec![("age".into(), Value::BigInt(30))],
};
let row = make_flat_vertex_record_row(record, &[Arc::from("missing")]);
assert_eq!(row.len(), 2);
assert!(matches!(&row[1], Value::Null(_)));
}
#[test]
fn flat_vertex_record_row_tag_name_equals_property_is_null() {
let record = FlatVertexRecord {
vid: VertexId::try_from_int64(42).expect("valid vertex id"),
internal_id: 7,
tag_name: "person".to_string(),
props: vec![("age".into(), Value::BigInt(30))],
};
let row = make_flat_vertex_record_row(record, &[Arc::from("person")]);
assert!(matches!(&row[1], Value::Null(_)));
}
}