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 = direction.compare(left, right);
80 if ordering != Ordering::Equal {
81 return ordering;
82 }
83 }
84 self.ordinal.cmp(&other.ordinal)
85 }
86}
87
88impl PartialOrd for TopNBinding {
89 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
90 Some(self.cmp(other))
91 }
92}
93
94pub fn map_payload_bytes(values: &BTreeMap<String, Value>) -> usize {
95 values.iter().fold(0usize, |total, (name, value)| {
96 total
97 .saturating_add(name.len())
98 .saturating_add(value_payload_bytes(value))
99 })
100}
101
102pub fn map_memory_bytes(values: &BTreeMap<String, Value>) -> usize {
103 map_memory_bytes_with_entries(values.iter().map(|(name, value)| (name.as_str(), value)))
104}
105
106fn map_memory_bytes_with_entries<'a>(values: impl Iterator<Item = (&'a str, &'a Value)>) -> usize {
107 std::mem::size_of::<BTreeMap<String, Value>>().saturating_add(values.fold(
108 0usize,
109 |total, (name, value)| {
110 total
111 .saturating_add(std::mem::size_of::<(String, Value)>() * 3)
112 .saturating_add(name.len())
113 .saturating_add(value_memory_bytes(value))
114 },
115 ))
116}
117
118pub(crate) fn projected_node_binding_memory_bytes(
121 variable: &str,
122 node: &NodeRecord,
123 required_properties: &BTreeSet<String>,
124) -> usize {
125 std::mem::size_of::<Binding>()
126 .saturating_add(variable.len())
127 .saturating_add(std::mem::size_of_val(&node.id))
128 .saturating_add(
129 node.labels
130 .len()
131 .saturating_mul(std::mem::size_of::<LabelId>()),
132 )
133 .saturating_add(map_memory_bytes_with_entries(
134 required_properties.iter().filter_map(|property| {
135 node.properties
136 .get(property)
137 .map(|value| (property.as_str(), value))
138 }),
139 ))
140 .saturating_add(graph_slot_memory_bytes(1))
141}
142
143pub fn value_memory_bytes(value: &Value) -> usize {
144 std::mem::size_of::<Value>().saturating_add(match value {
145 Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::Decimal(_) => 0,
146 Value::String(value) => value.len(),
147 Value::Binary(value) => value.len(),
148 Value::Vector(value) => value.encoded().len(),
149 Value::Uuid(_) => 16,
150 Value::Date(_) | Value::LocalTime(_) | Value::LocalDateTime(_) | Value::DateTime(_) => 0,
153 Value::Duration(_) => std::mem::size_of::<hawdb_core::temporal::Duration>(),
154 Value::List(values) => values
155 .iter()
156 .fold(std::mem::size_of::<Vec<Value>>(), |total, value| {
157 total.saturating_add(value_memory_bytes(value))
158 }),
159 Value::Map(values) => map_memory_bytes(values),
160 })
161}
162
163pub fn binding_payload_bytes(binding: &Binding) -> usize {
164 map_payload_bytes(&binding.values).saturating_add(graph_binding_payload_bytes(binding))
165}
166
167fn graph_binding_payload_bytes(binding: &Binding) -> usize {
168 graph_binding_bytes(binding, map_payload_bytes)
169}
170
171fn graph_binding_bytes(
172 binding: &Binding,
173 property_bytes: fn(&BTreeMap<String, Value>) -> usize,
174) -> usize {
175 graph_binding_parts_bytes(
176 binding
177 .nodes
178 .iter()
179 .map(|(name, node)| (name.as_str(), node)),
180 binding
181 .relationships
182 .iter()
183 .map(|(name, row)| (name.as_str(), row)),
184 property_bytes,
185 )
186}
187
188fn graph_binding_parts_bytes<'a>(
189 nodes: impl Iterator<Item = (&'a str, &'a NodeRecord)>,
190 relationships: impl Iterator<Item = (&'a str, &'a RelRecord)>,
191 property_bytes: fn(&BTreeMap<String, Value>) -> usize,
192) -> usize {
193 nodes
194 .fold(0usize, |total, (name, node)| {
195 total
196 .saturating_add(name.len())
197 .saturating_add(std::mem::size_of_val(&node.id))
198 .saturating_add(
199 node.labels
200 .len()
201 .saturating_mul(std::mem::size_of::<LabelId>()),
202 )
203 .saturating_add(property_bytes(&node.properties))
204 })
205 .saturating_add(relationships.fold(0usize, |total, (name, row)| {
206 total
207 .saturating_add(name.len())
208 .saturating_add(std::mem::size_of_val(&row.id))
209 .saturating_add(std::mem::size_of_val(&row.source))
210 .saturating_add(std::mem::size_of_val(&row.target))
211 .saturating_add(std::mem::size_of_val(&row.rel_type))
212 .saturating_add(property_bytes(&row.properties))
213 }))
214}
215
216pub(crate) fn binding_payload_bytes_with_parts<'a>(
217 values: impl Iterator<Item = (&'a str, &'a Value)>,
218 nodes: impl Iterator<Item = (&'a str, &'a NodeRecord)>,
219 relationships: impl Iterator<Item = (&'a str, &'a RelRecord)>,
220) -> usize {
221 values
222 .fold(0usize, |total, (name, value)| {
223 total
224 .saturating_add(name.len())
225 .saturating_add(value_payload_bytes(value))
226 })
227 .saturating_add(graph_binding_parts_bytes(
228 nodes,
229 relationships,
230 map_payload_bytes,
231 ))
232}
233
234pub fn binding_memory_bytes(binding: &Binding) -> usize {
235 binding_memory_bytes_with_values(
236 binding,
237 binding
238 .values
239 .iter()
240 .map(|(name, value)| (name.as_str(), value)),
241 )
242}
243
244pub(crate) fn binding_memory_bytes_replacing_value(
247 binding: &Binding,
248 name: &str,
249 value: &Value,
250) -> usize {
251 binding_memory_bytes_with_values(
252 binding,
253 binding
254 .values
255 .iter()
256 .filter(|(key, _)| key.as_str() != name)
257 .map(|(key, value)| (key.as_str(), value))
258 .chain(std::iter::once((name, value))),
259 )
260}
261
262pub(crate) fn binding_memory_bytes_with_values<'a>(
265 binding: &'a Binding,
266 values: impl Iterator<Item = (&'a str, &'a Value)>,
267) -> usize {
268 binding_memory_bytes_with_parts(
269 values,
270 binding
271 .nodes
272 .iter()
273 .map(|(name, node)| (name.as_str(), node)),
274 binding
275 .relationships
276 .iter()
277 .map(|(name, row)| (name.as_str(), row)),
278 )
279}
280
281pub(crate) fn binding_memory_bytes_with_parts<'a>(
282 values: impl Iterator<Item = (&'a str, &'a Value)>,
283 nodes: impl Iterator<Item = (&'a str, &'a NodeRecord)>,
284 relationships: impl Iterator<Item = (&'a str, &'a RelRecord)>,
285) -> usize {
286 let nodes_count = std::cell::Cell::new(0usize);
287 let relationships_count = std::cell::Cell::new(0usize);
288 let graph_bytes = graph_binding_parts_bytes(
289 nodes.inspect(|_| nodes_count.set(nodes_count.get().saturating_add(1))),
290 relationships
291 .inspect(|_| relationships_count.set(relationships_count.get().saturating_add(1))),
292 map_memory_bytes,
293 );
294 std::mem::size_of::<Binding>()
295 .saturating_add(graph_bytes)
296 .saturating_add(graph_slot_memory_bytes(nodes_count.get()))
300 .saturating_add(graph_slot_memory_bytes(relationships_count.get()))
301 .saturating_add(values.fold(0usize, |total, (name, value)| {
302 total
303 .saturating_add(name.len())
304 .saturating_add(match value {
305 Value::List(_) | Value::Map(_) => {
306 value_memory_bytes(value).saturating_sub(std::mem::size_of::<Value>())
307 }
308 _ => value_payload_bytes(value),
309 })
310 .saturating_add(std::mem::size_of::<usize>() * 6)
311 }))
312}
313
314pub(crate) fn graph_slot_memory_bytes(entries: usize) -> usize {
315 if entries == 0 {
316 return 0;
317 }
318 std::mem::size_of::<Vec<usize>>()
320 .saturating_add(2 * std::mem::size_of::<usize>())
321 .saturating_add(
322 entries
323 .saturating_mul(2)
324 .max(4)
325 .saturating_mul(2 * std::mem::size_of::<usize>()),
326 )
327 .saturating_add(entries.saturating_mul(10 * std::mem::size_of::<usize>()))
328}
329
330pub fn node_memory_bytes(node: &NodeRecord) -> usize {
331 std::mem::size_of::<NodeRecord>()
332 .saturating_add(
333 node.labels
334 .len()
335 .saturating_mul(std::mem::size_of::<LabelId>() * 3),
336 )
337 .saturating_add(map_memory_bytes(&node.properties))
338}
339
340pub fn relationship_memory_bytes(relationship: &RelRecord) -> usize {
341 std::mem::size_of::<RelRecord>().saturating_add(map_memory_bytes(&relationship.properties))
342}
343
344pub fn value_payload_bytes(value: &Value) -> usize {
345 match value {
348 Value::Null => 0,
349 Value::Bool(_) => std::mem::size_of::<bool>(),
350 Value::Int(_) => std::mem::size_of::<i64>(),
351 Value::Float(_) => std::mem::size_of::<f64>(),
352 Value::Decimal(_) => 17,
353 Value::String(value) => value.len(),
354 Value::Binary(value) => value.len(),
355 Value::Vector(value) => value.encoded().len(),
356 Value::Uuid(_) => 16,
357 Value::Date(_) => 4,
358 Value::LocalTime(_) => 8,
359 Value::LocalDateTime(_) => 12,
360 Value::DateTime(_) => 16,
361 Value::Duration(_) => 28,
362 Value::List(values) => values.iter().fold(0usize, |total, value| {
363 total.saturating_add(value_payload_bytes(value))
364 }),
365 Value::Map(values) => map_payload_bytes(values),
366 }
367}
368
369#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
372pub(crate) struct BindingSizes {
373 pub(crate) memory: usize,
374 pub(crate) payload: usize,
375}
376
377pub(crate) fn binding_sizes(binding: &Binding) -> BindingSizes {
381 let mut graph_memory = 0usize;
382 let mut graph_payload = 0usize;
383 let mut nodes = 0usize;
384 for (name, node) in binding.nodes.iter() {
385 nodes = nodes.saturating_add(1);
386 let fixed = |total: usize| {
387 total
388 .saturating_add(name.len())
389 .saturating_add(std::mem::size_of_val(&node.id))
390 .saturating_add(
391 node.labels
392 .len()
393 .saturating_mul(std::mem::size_of::<LabelId>()),
394 )
395 };
396 let (memory, payload) = map_sizes(&node.properties);
397 graph_memory = fixed(graph_memory).saturating_add(memory);
398 graph_payload = fixed(graph_payload).saturating_add(payload);
399 }
400 let mut relationships = 0usize;
401 for (name, row) in binding.relationships.iter() {
402 relationships = relationships.saturating_add(1);
403 let fixed = |total: usize| {
404 total
405 .saturating_add(name.len())
406 .saturating_add(std::mem::size_of_val(&row.id))
407 .saturating_add(std::mem::size_of_val(&row.source))
408 .saturating_add(std::mem::size_of_val(&row.target))
409 .saturating_add(std::mem::size_of_val(&row.rel_type))
410 };
411 let (memory, payload) = map_sizes(&row.properties);
412 graph_memory = fixed(graph_memory).saturating_add(memory);
413 graph_payload = fixed(graph_payload).saturating_add(payload);
414 }
415 let mut values_memory = 0usize;
416 let mut values_payload = 0usize;
417 for (name, value) in &binding.values {
418 let (resident, payload) = match value {
419 Value::List(_) | Value::Map(_) => {
420 let (memory, payload) = value_sizes(value);
421 (memory.saturating_sub(std::mem::size_of::<Value>()), payload)
422 }
423 _ => {
424 let payload = value_payload_bytes(value);
425 (payload, payload)
426 }
427 };
428 values_memory = values_memory
429 .saturating_add(name.len())
430 .saturating_add(resident)
431 .saturating_add(std::mem::size_of::<usize>() * 6);
432 values_payload = values_payload
433 .saturating_add(name.len())
434 .saturating_add(payload);
435 }
436 BindingSizes {
437 memory: std::mem::size_of::<Binding>()
438 .saturating_add(graph_memory)
439 .saturating_add(graph_slot_memory_bytes(nodes))
440 .saturating_add(graph_slot_memory_bytes(relationships))
441 .saturating_add(values_memory),
442 payload: values_payload.saturating_add(graph_payload),
443 }
444}
445
446fn value_sizes(value: &Value) -> (usize, usize) {
448 match value {
449 Value::List(values) => {
450 let (memory, payload) = values.iter().fold(
451 (std::mem::size_of::<Vec<Value>>(), 0usize),
452 |(memory, payload), value| {
453 let (value_memory, value_payload) = value_sizes(value);
454 (
455 memory.saturating_add(value_memory),
456 payload.saturating_add(value_payload),
457 )
458 },
459 );
460 (std::mem::size_of::<Value>().saturating_add(memory), payload)
461 }
462 Value::Map(values) => {
463 let (memory, payload) = map_sizes(values);
464 (std::mem::size_of::<Value>().saturating_add(memory), payload)
465 }
466 scalar => (value_memory_bytes(scalar), value_payload_bytes(scalar)),
468 }
469}
470
471fn map_sizes(values: &BTreeMap<String, Value>) -> (usize, usize) {
473 values.iter().fold(
474 (std::mem::size_of::<BTreeMap<String, Value>>(), 0usize),
475 |(memory, payload), (name, value)| {
476 let (value_memory, value_payload) = value_sizes(value);
477 (
478 memory
479 .saturating_add(std::mem::size_of::<(String, Value)>() * 3)
480 .saturating_add(name.len())
481 .saturating_add(value_memory),
482 payload
483 .saturating_add(name.len())
484 .saturating_add(value_payload),
485 )
486 },
487 )
488}
489
490pub fn value_ref_payload_bytes(value: hawdb_core::ValueRef<'_>) -> usize {
491 use hawdb_core::ValueRef;
492 match value {
493 ValueRef::Null => 0,
494 ValueRef::Bool(_) => std::mem::size_of::<bool>(),
495 ValueRef::Int(_) => std::mem::size_of::<i64>(),
496 ValueRef::Float(_) => std::mem::size_of::<f64>(),
497 ValueRef::Decimal(_) => 17,
498 ValueRef::String(value) => value.len(),
499 ValueRef::Binary(value) => value.len(),
500 ValueRef::Vector(value) => value.encoded().len(),
501 ValueRef::Uuid(_) => 16,
502 ValueRef::Date(_) => 4,
503 ValueRef::LocalTime(_) => 8,
504 ValueRef::LocalDateTime(_) => 12,
505 ValueRef::DateTime(_) => 16,
506 ValueRef::Duration(_) => 28,
507 ValueRef::List(values) => values.iter().fold(0usize, |total, value| {
508 total.saturating_add(value_payload_bytes(value))
509 }),
510 ValueRef::Map(values) => map_payload_bytes(values),
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use hawdb_core::RelTypeId;
518 use hawdb_storage::{NodeId, RelId};
519 use std::collections::BTreeSet;
520
521 #[test]
522 fn value_only_constructors_leave_graph_bindings_empty() {
523 let values = Binding::values(BTreeMap::from([("left".to_string(), Value::Int(1))]));
524 assert_eq!(values.values["left"], Value::Int(1));
525 assert!(values.nodes.is_empty());
526 assert!(values.relationships.is_empty());
527
528 let scalar = Binding::scalar("right", Value::Bool(true));
529 assert_eq!(scalar.values["right"], Value::Bool(true));
530 assert!(scalar.nodes.is_empty());
531 assert!(scalar.relationships.is_empty());
532 }
533
534 #[test]
535 fn binding_accounting_includes_nested_values_and_graph_records() {
536 let binding = Binding {
537 values: BTreeMap::from([(
538 "nested".to_string(),
539 Value::List(vec![Value::Int(1), Value::String("two".to_string())]),
540 )]),
541 nodes: BTreeMap::from([(
542 "n".to_string(),
543 NodeRecord {
544 id: NodeId(7),
545 labels: BTreeSet::from([LabelId(3)]),
546 properties: BTreeMap::new(),
547 },
548 )])
549 .into(),
550 relationships: BTreeMap::from([(
551 "r".to_string(),
552 RelRecord {
553 id: RelId(11),
554 source: NodeId(7),
555 target: NodeId(8),
556 rel_type: RelTypeId(4),
557 properties: BTreeMap::new(),
558 },
559 )])
560 .into(),
561 };
562
563 assert!(binding_payload_bytes(&binding) > "nested".len() + "two".len());
564 assert!(binding_memory_bytes(&binding) > binding_payload_bytes(&binding));
565 }
566
567 #[test]
568 fn projected_node_admission_matches_graph_slot_ownership() {
569 let node = NodeRecord {
570 id: NodeId(3),
571 labels: BTreeSet::from([LabelId(1)]),
572 properties: BTreeMap::from([
573 ("id".into(), Value::String("chosen".into())),
574 ("body".into(), Value::String("x".repeat(65536))),
575 ]),
576 };
577 let required = BTreeSet::from(["id".to_string()]);
578 let bytes = projected_node_binding_memory_bytes("n", &node, &required);
579 let mut projected = node.clone();
580 projected
581 .properties
582 .retain(|name, _| required.contains(name));
583 let binding = Binding {
584 values: BTreeMap::new(),
585 nodes: BTreeMap::from([("n".into(), projected)]).into(),
586 relationships: GraphSlots::new(),
587 };
588 assert_eq!(bytes, binding_memory_bytes(&binding));
589 }
590
591 #[test]
592 fn replacement_admission_matches_final_rows_without_mutating_input() {
593 let column = hawdb_plan_cypher::SCORING_RERANK_SCORE_COLUMN;
594 for previous in [
595 None,
596 Some(Value::Null),
597 Some(Value::Float(-1.0)),
598 Some(Value::List(vec![Value::Map(BTreeMap::from([(
599 "nested".into(),
600 Value::String("x".repeat(1_024)),
601 )]))])),
602 ] {
603 let mut input = Binding::scalar("score", Value::Float(0.5));
604 if let Some(previous) = previous {
605 input.values.insert(column.into(), previous);
606 }
607 let original = input.clone();
608 let estimate =
609 binding_memory_bytes_replacing_value(&input, column, &Value::Float(0.25));
610 assert_eq!(input, original, "admission must not alter the candidate");
611 let mut output = input;
612 output.values.insert(column.into(), Value::Float(0.25));
613 assert_eq!(estimate, binding_memory_bytes(&output));
614 }
615 }
616
617 #[test]
622 fn row_mutation_previews_equal_full_walks_and_settle_the_ledger() {
623 use crate::memory_ledger::{QueryMemoryClass, QueryMemoryLedger};
624 use std::num::NonZeroUsize;
625
626 let budget = NonZeroUsize::new(1 << 20).unwrap();
627 let ledger = QueryMemoryLedger::new(budget);
628 let account = ledger.account(QueryMemoryClass::PipelineBatch, "rows", budget);
629 let mut row = Binding {
630 values: BTreeMap::from([("id".to_string(), Value::Int(1))]),
631 nodes: BTreeMap::from([(
632 "n".to_string(),
633 NodeRecord {
634 id: NodeId(1),
635 labels: BTreeSet::from([LabelId(2)]),
636 properties: BTreeMap::from([("name".into(), Value::String("ann".into()))]),
637 },
638 )])
639 .into(),
640 relationships: GraphSlots::new(),
641 };
642 let mut lease = account.reserve(binding_memory_bytes(&row)).unwrap();
643 let mut list = Vec::new();
644 let mut map = BTreeMap::new();
645 for step in 0..24usize {
646 let (name, value) = match step % 4 {
648 0 => ("id", Value::String("x".repeat(step))),
649 1 => {
650 list.push(Value::List(vec![Value::Int(step as i64); step % 3]));
651 ("list", Value::List(list.clone()))
652 }
653 2 => {
654 map.insert(format!("k{step}"), Value::String("v".repeat(step)));
655 ("map", Value::Map(map.clone()))
656 }
657 _ => ("id", Value::Null),
658 };
659 let preview = binding_memory_bytes_replacing_value(&row, name, &value);
660 row.values.insert(name.to_string(), value);
661 let walked = binding_memory_bytes(&row);
662 assert_eq!(preview, walked, "step {step}: replacement");
663 match walked.checked_sub(lease.bytes()) {
665 Some(growth) => lease.grow(growth).unwrap(),
666 None => lease.shrink(lease.bytes() - walked),
667 }
668 assert_eq!(ledger.snapshot().used_bytes, walked, "step {step}");
669
670 let computed = Value::Map(BTreeMap::from([(
672 "wrapped".into(),
673 Value::List(list.clone()),
674 )]));
675 let projected: Vec<(&str, &Value)> = row
676 .values
677 .iter()
678 .filter(|(key, _)| key.as_str() != "id")
679 .map(|(key, value)| (key.as_str(), value))
680 .chain(std::iter::once(("computed", &computed)))
681 .collect();
682 let preview = binding_memory_bytes_with_values(&row, projected.iter().copied());
683 let mut materialized = row.clone();
684 materialized.values = projected
685 .iter()
686 .map(|(key, value)| (key.to_string(), (*value).clone()))
687 .collect();
688 assert_eq!(
689 preview,
690 binding_memory_bytes(&materialized),
691 "step {step}: projection"
692 );
693 }
694 for name in ["map", "list", "id"] {
696 row.values.remove(name);
697 lease.shrink(lease.bytes() - binding_memory_bytes(&row));
698 assert_eq!(ledger.snapshot().used_bytes, binding_memory_bytes(&row));
699 }
700 row.nodes.remove("n");
701 assert_eq!(binding_memory_bytes(&row), std::mem::size_of::<Binding>());
702 drop(lease);
703 assert_eq!(ledger.snapshot().used_bytes, 0);
704 }
705
706 fn sized_values() -> Vec<Value> {
709 use hawdb_core::temporal;
710 let scalars = vec![
711 Value::Null,
712 Value::Bool(true),
713 Value::Int(-7),
714 Value::Float(2.5),
715 Value::Decimal(hawdb_core::Decimal::new(12_345, 2).unwrap()),
716 Value::String(String::new()),
717 Value::String("text".repeat(33)),
718 Value::Binary(vec![0xa5; 19]),
719 Value::Vector(hawdb_core::Vector::new(&[1.0, -2.0, 3.5]).unwrap()),
720 Value::Uuid(hawdb_core::Uuid::from_bytes([0x5a; 16])),
721 Value::Date(temporal::parse_date("2026-11-09").unwrap()),
722 Value::LocalTime(temporal::parse_local_time("23:59:59.999999999").unwrap()),
723 Value::LocalDateTime(temporal::parse_local_datetime("2026-10-30T12:00").unwrap()),
724 Value::DateTime(temporal::parse_datetime("2026-10-30T12:00:00-05:30").unwrap()),
725 Value::duration(temporal::parse_duration("-P1Y2M3DT4.5S").unwrap()),
726 ];
727 let mut catalog = hawdb_core::Catalog::default();
728 let label = catalog.get_or_create_label("Post");
729 let rel_type = catalog.get_or_create_rel_type("HAS_CREATOR");
730 let node = NodeRecord {
731 id: NodeId(41),
732 labels: BTreeSet::from([label]),
733 properties: BTreeMap::from([
734 ("content".into(), Value::String("x".repeat(300))),
735 ("tags".into(), Value::List(scalars.clone())),
736 ("empty".into(), Value::Null),
737 ]),
738 };
739 let relationship = RelRecord {
740 id: RelId(9),
741 source: NodeId(41),
742 target: NodeId(42),
743 rel_type,
744 properties: BTreeMap::from([("since".into(), Value::Int(2010))]),
745 };
746 let node_value = crate::expression::node_value(&node, &catalog);
747 let relationship_value = crate::expression::relationship_value(&relationship, &catalog);
748 let path = crate::expression::path_value(
749 vec![node_value.clone(), node_value.clone()],
750 vec![relationship_value.clone()],
751 );
752 let nested_map = Value::Map(
753 scalars
754 .iter()
755 .enumerate()
756 .map(|(index, value)| (format!("k{index}"), value.clone()))
757 .collect(),
758 );
759 let deep = Value::List(vec![
760 Value::List(vec![]),
761 Value::Map(BTreeMap::new()),
762 Value::List(vec![nested_map.clone(), Value::List(scalars.clone())]),
763 Value::Map(BTreeMap::from([("inner".into(), path.clone())])),
764 ]);
765 scalars
766 .into_iter()
767 .chain([
768 Value::List(Vec::with_capacity(8)),
769 nested_map,
770 node_value,
771 relationship_value,
772 path,
773 deep,
774 ])
775 .collect()
776 }
777
778 #[test]
779 fn single_walk_sizes_equal_separate_walks_across_value_kinds() {
780 let values = sized_values();
781 for value in &values {
782 assert_eq!(
783 value_payload_bytes(value),
784 value_ref_payload_bytes(value.as_ref()),
785 "{value:?}"
786 );
787 assert_eq!(
788 value_sizes(value),
789 (value_memory_bytes(value), value_payload_bytes(value)),
790 "{value:?}"
791 );
792 if let Value::Map(map) = value {
793 assert_eq!(
794 map_sizes(map),
795 (map_memory_bytes(map), map_payload_bytes(map))
796 );
797 }
798 }
799 let record = |id: u64, properties: BTreeMap<String, Value>| NodeRecord {
800 id: NodeId(id),
801 labels: BTreeSet::from([LabelId(1), LabelId(2)]),
802 properties,
803 };
804 let mut rows = vec![
805 Binding::values(BTreeMap::new()),
806 Binding::scalar("only", Value::Null),
807 ];
808 for (index, value) in values.iter().enumerate() {
809 let mut properties = BTreeMap::from([("p".to_string(), value.clone())]);
810 properties.insert("name".into(), Value::String(format!("row-{index}")));
811 rows.push(Binding {
812 values: BTreeMap::from([
813 (format!("v{index}"), value.clone()),
814 ("all".to_string(), Value::List(values.clone())),
815 ]),
816 nodes: BTreeMap::from([
817 ("a".to_string(), record(index as u64, properties.clone())),
818 ("b".to_string(), record(7, BTreeMap::new())),
819 ])
820 .into(),
821 relationships: BTreeMap::from([(
822 "r".to_string(),
823 RelRecord {
824 id: RelId(index as u64),
825 source: NodeId(1),
826 target: NodeId(2),
827 rel_type: RelTypeId(3),
828 properties,
829 },
830 )])
831 .into(),
832 });
833 }
834 for row in &rows {
835 assert_eq!(
836 binding_sizes(row),
837 BindingSizes {
838 memory: binding_memory_bytes(row),
839 payload: binding_payload_bytes(row),
840 },
841 "{row:?}"
842 );
843 }
844 }
845}