1mod graph_slots;
18pub use graph_slots::GraphSlots;
19
20use hawdb_core::{LabelId, Value};
21use hawdb_plan_cypher::SortDirection;
22use hawdb_storage::{NodeRecord, RelRecord};
23use std::cmp::Ordering;
24use std::collections::{BTreeMap, BTreeSet};
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Binding {
28 pub values: BTreeMap<String, Value>,
29 pub nodes: GraphSlots<NodeRecord>,
30 pub relationships: GraphSlots<RelRecord>,
31}
32
33impl Binding {
34 pub fn values(values: BTreeMap<String, Value>) -> Self {
35 Self {
36 values,
37 nodes: GraphSlots::new(),
38 relationships: GraphSlots::new(),
39 }
40 }
41
42 pub fn scalar(name: impl Into<String>, value: Value) -> Self {
43 Self::values(BTreeMap::from([(name.into(), value)]))
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct TopNBinding {
49 pub sort_values: Vec<(Value, SortDirection)>,
50 pub ordinal: u64,
51 pub binding: Binding,
52}
53
54impl TopNBinding {
55 pub fn memory_bytes(&self) -> usize {
56 binding_memory_bytes(&self.binding)
57 .saturating_add(sort_values_memory_bytes(&self.sort_values))
58 }
59}
60
61pub(crate) fn sort_values_memory_bytes(values: &Vec<(Value, SortDirection)>) -> usize {
62 let layout = std::mem::size_of::<Vec<(Value, SortDirection)>>().saturating_add(
63 values
64 .capacity()
65 .saturating_mul(std::mem::size_of::<(Value, SortDirection)>()),
66 );
67 values.iter().fold(layout, |total, (value, _)| {
68 total.saturating_add(value_memory_bytes(value).saturating_sub(std::mem::size_of::<Value>()))
69 })
70}
71
72impl Ord for TopNBinding {
73 fn cmp(&self, other: &Self) -> Ordering {
74 for ((left, direction), (right, other_direction)) in
75 self.sort_values.iter().zip(&other.sort_values)
76 {
77 debug_assert_eq!(direction, other_direction);
78 let ordering = match direction {
79 SortDirection::Asc => left.cmp(right),
80 SortDirection::Desc => left.cmp(right).reverse(),
81 };
82 if ordering != Ordering::Equal {
83 return ordering;
84 }
85 }
86 self.ordinal.cmp(&other.ordinal)
87 }
88}
89
90impl PartialOrd for TopNBinding {
91 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92 Some(self.cmp(other))
93 }
94}
95
96pub fn map_payload_bytes(values: &BTreeMap<String, Value>) -> usize {
97 values.iter().fold(0usize, |total, (name, value)| {
98 total
99 .saturating_add(name.len())
100 .saturating_add(value_payload_bytes(value))
101 })
102}
103
104pub fn map_memory_bytes(values: &BTreeMap<String, Value>) -> usize {
105 map_memory_bytes_with_entries(values.iter().map(|(name, value)| (name.as_str(), value)))
106}
107
108fn map_memory_bytes_with_entries<'a>(values: impl Iterator<Item = (&'a str, &'a Value)>) -> usize {
109 std::mem::size_of::<BTreeMap<String, Value>>().saturating_add(values.fold(
110 0usize,
111 |total, (name, value)| {
112 total
113 .saturating_add(std::mem::size_of::<(String, Value)>() * 3)
114 .saturating_add(name.len())
115 .saturating_add(value_memory_bytes(value))
116 },
117 ))
118}
119
120pub(crate) fn projected_node_binding_memory_bytes(
123 variable: &str,
124 node: &NodeRecord,
125 required_properties: &BTreeSet<String>,
126) -> usize {
127 std::mem::size_of::<Binding>()
128 .saturating_add(variable.len())
129 .saturating_add(std::mem::size_of_val(&node.id))
130 .saturating_add(
131 node.labels
132 .len()
133 .saturating_mul(std::mem::size_of::<LabelId>()),
134 )
135 .saturating_add(map_memory_bytes_with_entries(
136 required_properties.iter().filter_map(|property| {
137 node.properties
138 .get(property)
139 .map(|value| (property.as_str(), value))
140 }),
141 ))
142 .saturating_add(graph_slot_memory_bytes(1))
143}
144
145pub fn value_memory_bytes(value: &Value) -> usize {
146 std::mem::size_of::<Value>().saturating_add(match value {
147 Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::Decimal(_) => 0,
148 Value::String(value) => value.len(),
149 Value::Binary(value) => value.len(),
150 Value::Vector(value) => value.encoded().len(),
151 Value::Uuid(_) => 16,
152 Value::Date(_) | Value::LocalTime(_) | Value::LocalDateTime(_) | Value::DateTime(_) => 0,
155 Value::Duration(_) => std::mem::size_of::<hawdb_core::temporal::Duration>(),
156 Value::List(values) => values
157 .iter()
158 .fold(std::mem::size_of::<Vec<Value>>(), |total, value| {
159 total.saturating_add(value_memory_bytes(value))
160 }),
161 Value::Map(values) => map_memory_bytes(values),
162 })
163}
164
165pub fn binding_payload_bytes(binding: &Binding) -> usize {
166 map_payload_bytes(&binding.values).saturating_add(graph_binding_payload_bytes(binding))
167}
168
169fn graph_binding_payload_bytes(binding: &Binding) -> usize {
170 graph_binding_bytes(binding, map_payload_bytes)
171}
172
173fn graph_binding_bytes(
174 binding: &Binding,
175 property_bytes: fn(&BTreeMap<String, Value>) -> usize,
176) -> usize {
177 graph_binding_parts_bytes(
178 binding
179 .nodes
180 .iter()
181 .map(|(name, node)| (name.as_str(), node)),
182 binding
183 .relationships
184 .iter()
185 .map(|(name, row)| (name.as_str(), row)),
186 property_bytes,
187 )
188}
189
190fn graph_binding_parts_bytes<'a>(
191 nodes: impl Iterator<Item = (&'a str, &'a NodeRecord)>,
192 relationships: impl Iterator<Item = (&'a str, &'a RelRecord)>,
193 property_bytes: fn(&BTreeMap<String, Value>) -> usize,
194) -> usize {
195 nodes
196 .fold(0usize, |total, (name, node)| {
197 total
198 .saturating_add(name.len())
199 .saturating_add(std::mem::size_of_val(&node.id))
200 .saturating_add(
201 node.labels
202 .len()
203 .saturating_mul(std::mem::size_of::<LabelId>()),
204 )
205 .saturating_add(property_bytes(&node.properties))
206 })
207 .saturating_add(relationships.fold(0usize, |total, (name, row)| {
208 total
209 .saturating_add(name.len())
210 .saturating_add(std::mem::size_of_val(&row.id))
211 .saturating_add(std::mem::size_of_val(&row.source))
212 .saturating_add(std::mem::size_of_val(&row.target))
213 .saturating_add(std::mem::size_of_val(&row.rel_type))
214 .saturating_add(property_bytes(&row.properties))
215 }))
216}
217
218pub(crate) fn binding_payload_bytes_with_parts<'a>(
219 values: impl Iterator<Item = (&'a str, &'a Value)>,
220 nodes: impl Iterator<Item = (&'a str, &'a NodeRecord)>,
221 relationships: impl Iterator<Item = (&'a str, &'a RelRecord)>,
222) -> usize {
223 values
224 .fold(0usize, |total, (name, value)| {
225 total
226 .saturating_add(name.len())
227 .saturating_add(value_payload_bytes(value))
228 })
229 .saturating_add(graph_binding_parts_bytes(
230 nodes,
231 relationships,
232 map_payload_bytes,
233 ))
234}
235
236pub fn binding_memory_bytes(binding: &Binding) -> usize {
237 binding_memory_bytes_with_values(
238 binding,
239 binding
240 .values
241 .iter()
242 .map(|(name, value)| (name.as_str(), value)),
243 )
244}
245
246pub(crate) fn binding_memory_bytes_replacing_value(
249 binding: &Binding,
250 name: &str,
251 value: &Value,
252) -> usize {
253 binding_memory_bytes_with_values(
254 binding,
255 binding
256 .values
257 .iter()
258 .filter(|(key, _)| key.as_str() != name)
259 .map(|(key, value)| (key.as_str(), value))
260 .chain(std::iter::once((name, value))),
261 )
262}
263
264pub(crate) fn binding_memory_bytes_with_values<'a>(
267 binding: &'a Binding,
268 values: impl Iterator<Item = (&'a str, &'a Value)>,
269) -> usize {
270 binding_memory_bytes_with_parts(
271 values,
272 binding
273 .nodes
274 .iter()
275 .map(|(name, node)| (name.as_str(), node)),
276 binding
277 .relationships
278 .iter()
279 .map(|(name, row)| (name.as_str(), row)),
280 )
281}
282
283pub(crate) fn binding_memory_bytes_with_parts<'a>(
284 values: impl Iterator<Item = (&'a str, &'a Value)>,
285 nodes: impl Iterator<Item = (&'a str, &'a NodeRecord)>,
286 relationships: impl Iterator<Item = (&'a str, &'a RelRecord)>,
287) -> usize {
288 let nodes_count = std::cell::Cell::new(0usize);
289 let relationships_count = std::cell::Cell::new(0usize);
290 let graph_bytes = graph_binding_parts_bytes(
291 nodes.inspect(|_| nodes_count.set(nodes_count.get().saturating_add(1))),
292 relationships
293 .inspect(|_| relationships_count.set(relationships_count.get().saturating_add(1))),
294 map_memory_bytes,
295 );
296 std::mem::size_of::<Binding>()
297 .saturating_add(graph_bytes)
298 .saturating_add(graph_slot_memory_bytes(nodes_count.get()))
302 .saturating_add(graph_slot_memory_bytes(relationships_count.get()))
303 .saturating_add(values.fold(0usize, |total, (name, value)| {
304 total
305 .saturating_add(name.len())
306 .saturating_add(match value {
307 Value::List(_) | Value::Map(_) => {
308 value_memory_bytes(value).saturating_sub(std::mem::size_of::<Value>())
309 }
310 _ => value_payload_bytes(value),
311 })
312 .saturating_add(std::mem::size_of::<usize>() * 6)
313 }))
314}
315
316pub(crate) fn graph_slot_memory_bytes(entries: usize) -> usize {
317 if entries == 0 {
318 return 0;
319 }
320 std::mem::size_of::<Vec<usize>>()
322 .saturating_add(2 * std::mem::size_of::<usize>())
323 .saturating_add(
324 entries
325 .saturating_mul(2)
326 .max(4)
327 .saturating_mul(2 * std::mem::size_of::<usize>()),
328 )
329 .saturating_add(entries.saturating_mul(10 * std::mem::size_of::<usize>()))
330}
331
332pub fn node_memory_bytes(node: &NodeRecord) -> usize {
333 std::mem::size_of::<NodeRecord>()
334 .saturating_add(
335 node.labels
336 .len()
337 .saturating_mul(std::mem::size_of::<LabelId>() * 3),
338 )
339 .saturating_add(map_memory_bytes(&node.properties))
340}
341
342pub fn relationship_memory_bytes(relationship: &RelRecord) -> usize {
343 std::mem::size_of::<RelRecord>().saturating_add(map_memory_bytes(&relationship.properties))
344}
345
346pub fn value_payload_bytes(value: &Value) -> usize {
347 value_ref_payload_bytes(value.as_ref())
348}
349
350pub fn value_ref_payload_bytes(value: hawdb_core::ValueRef<'_>) -> usize {
351 use hawdb_core::ValueRef;
352 match value {
353 ValueRef::Null => 0,
354 ValueRef::Bool(_) => std::mem::size_of::<bool>(),
355 ValueRef::Int(_) => std::mem::size_of::<i64>(),
356 ValueRef::Float(_) => std::mem::size_of::<f64>(),
357 ValueRef::Decimal(_) => 17,
358 ValueRef::String(value) => value.len(),
359 ValueRef::Binary(value) => value.len(),
360 ValueRef::Vector(value) => value.encoded().len(),
361 ValueRef::Uuid(_) => 16,
362 ValueRef::Date(_) => 4,
363 ValueRef::LocalTime(_) => 8,
364 ValueRef::LocalDateTime(_) => 12,
365 ValueRef::DateTime(_) => 16,
366 ValueRef::Duration(_) => 28,
367 ValueRef::List(values) => values.iter().fold(0usize, |total, value| {
368 total.saturating_add(value_payload_bytes(value))
369 }),
370 ValueRef::Map(values) => map_payload_bytes(values),
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377 use hawdb_core::RelTypeId;
378 use hawdb_storage::{NodeId, RelId};
379 use std::collections::BTreeSet;
380
381 #[test]
382 fn value_only_constructors_leave_graph_bindings_empty() {
383 let values = Binding::values(BTreeMap::from([("left".to_string(), Value::Int(1))]));
384 assert_eq!(values.values["left"], Value::Int(1));
385 assert!(values.nodes.is_empty());
386 assert!(values.relationships.is_empty());
387
388 let scalar = Binding::scalar("right", Value::Bool(true));
389 assert_eq!(scalar.values["right"], Value::Bool(true));
390 assert!(scalar.nodes.is_empty());
391 assert!(scalar.relationships.is_empty());
392 }
393
394 #[test]
395 fn binding_accounting_includes_nested_values_and_graph_records() {
396 let binding = Binding {
397 values: BTreeMap::from([(
398 "nested".to_string(),
399 Value::List(vec![Value::Int(1), Value::String("two".to_string())]),
400 )]),
401 nodes: BTreeMap::from([(
402 "n".to_string(),
403 NodeRecord {
404 id: NodeId(7),
405 labels: BTreeSet::from([LabelId(3)]),
406 properties: BTreeMap::new(),
407 },
408 )])
409 .into(),
410 relationships: BTreeMap::from([(
411 "r".to_string(),
412 RelRecord {
413 id: RelId(11),
414 source: NodeId(7),
415 target: NodeId(8),
416 rel_type: RelTypeId(4),
417 properties: BTreeMap::new(),
418 },
419 )])
420 .into(),
421 };
422
423 assert!(binding_payload_bytes(&binding) > "nested".len() + "two".len());
424 assert!(binding_memory_bytes(&binding) > binding_payload_bytes(&binding));
425 }
426
427 #[test]
428 fn projected_node_admission_matches_graph_slot_ownership() {
429 let node = NodeRecord {
430 id: NodeId(3),
431 labels: BTreeSet::from([LabelId(1)]),
432 properties: BTreeMap::from([
433 ("id".into(), Value::String("chosen".into())),
434 ("body".into(), Value::String("x".repeat(65536))),
435 ]),
436 };
437 let required = BTreeSet::from(["id".to_string()]);
438 let bytes = projected_node_binding_memory_bytes("n", &node, &required);
439 let mut projected = node.clone();
440 projected
441 .properties
442 .retain(|name, _| required.contains(name));
443 let binding = Binding {
444 values: BTreeMap::new(),
445 nodes: BTreeMap::from([("n".into(), projected)]).into(),
446 relationships: GraphSlots::new(),
447 };
448 assert_eq!(bytes, binding_memory_bytes(&binding));
449 }
450
451 #[test]
452 fn replacement_admission_matches_final_rows_without_mutating_input() {
453 let column = hawdb_plan_cypher::SCORING_RERANK_SCORE_COLUMN;
454 for previous in [
455 None,
456 Some(Value::Null),
457 Some(Value::Float(-1.0)),
458 Some(Value::List(vec![Value::Map(BTreeMap::from([(
459 "nested".into(),
460 Value::String("x".repeat(1_024)),
461 )]))])),
462 ] {
463 let mut input = Binding::scalar("score", Value::Float(0.5));
464 if let Some(previous) = previous {
465 input.values.insert(column.into(), previous);
466 }
467 let original = input.clone();
468 let estimate =
469 binding_memory_bytes_replacing_value(&input, column, &Value::Float(0.25));
470 assert_eq!(input, original, "admission must not alter the candidate");
471 let mut output = input;
472 output.values.insert(column.into(), Value::Float(0.25));
473 assert_eq!(estimate, binding_memory_bytes(&output));
474 }
475 }
476}