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 value_ref_payload_bytes(value.as_ref())
346}
347
348pub fn value_ref_payload_bytes(value: hawdb_core::ValueRef<'_>) -> usize {
349 use hawdb_core::ValueRef;
350 match value {
351 ValueRef::Null => 0,
352 ValueRef::Bool(_) => std::mem::size_of::<bool>(),
353 ValueRef::Int(_) => std::mem::size_of::<i64>(),
354 ValueRef::Float(_) => std::mem::size_of::<f64>(),
355 ValueRef::Decimal(_) => 17,
356 ValueRef::String(value) => value.len(),
357 ValueRef::Binary(value) => value.len(),
358 ValueRef::Vector(value) => value.encoded().len(),
359 ValueRef::Uuid(_) => 16,
360 ValueRef::Date(_) => 4,
361 ValueRef::LocalTime(_) => 8,
362 ValueRef::LocalDateTime(_) => 12,
363 ValueRef::DateTime(_) => 16,
364 ValueRef::Duration(_) => 28,
365 ValueRef::List(values) => values.iter().fold(0usize, |total, value| {
366 total.saturating_add(value_payload_bytes(value))
367 }),
368 ValueRef::Map(values) => map_payload_bytes(values),
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use hawdb_core::RelTypeId;
376 use hawdb_storage::{NodeId, RelId};
377 use std::collections::BTreeSet;
378
379 #[test]
380 fn value_only_constructors_leave_graph_bindings_empty() {
381 let values = Binding::values(BTreeMap::from([("left".to_string(), Value::Int(1))]));
382 assert_eq!(values.values["left"], Value::Int(1));
383 assert!(values.nodes.is_empty());
384 assert!(values.relationships.is_empty());
385
386 let scalar = Binding::scalar("right", Value::Bool(true));
387 assert_eq!(scalar.values["right"], Value::Bool(true));
388 assert!(scalar.nodes.is_empty());
389 assert!(scalar.relationships.is_empty());
390 }
391
392 #[test]
393 fn binding_accounting_includes_nested_values_and_graph_records() {
394 let binding = Binding {
395 values: BTreeMap::from([(
396 "nested".to_string(),
397 Value::List(vec![Value::Int(1), Value::String("two".to_string())]),
398 )]),
399 nodes: BTreeMap::from([(
400 "n".to_string(),
401 NodeRecord {
402 id: NodeId(7),
403 labels: BTreeSet::from([LabelId(3)]),
404 properties: BTreeMap::new(),
405 },
406 )])
407 .into(),
408 relationships: BTreeMap::from([(
409 "r".to_string(),
410 RelRecord {
411 id: RelId(11),
412 source: NodeId(7),
413 target: NodeId(8),
414 rel_type: RelTypeId(4),
415 properties: BTreeMap::new(),
416 },
417 )])
418 .into(),
419 };
420
421 assert!(binding_payload_bytes(&binding) > "nested".len() + "two".len());
422 assert!(binding_memory_bytes(&binding) > binding_payload_bytes(&binding));
423 }
424
425 #[test]
426 fn projected_node_admission_matches_graph_slot_ownership() {
427 let node = NodeRecord {
428 id: NodeId(3),
429 labels: BTreeSet::from([LabelId(1)]),
430 properties: BTreeMap::from([
431 ("id".into(), Value::String("chosen".into())),
432 ("body".into(), Value::String("x".repeat(65536))),
433 ]),
434 };
435 let required = BTreeSet::from(["id".to_string()]);
436 let bytes = projected_node_binding_memory_bytes("n", &node, &required);
437 let mut projected = node.clone();
438 projected
439 .properties
440 .retain(|name, _| required.contains(name));
441 let binding = Binding {
442 values: BTreeMap::new(),
443 nodes: BTreeMap::from([("n".into(), projected)]).into(),
444 relationships: GraphSlots::new(),
445 };
446 assert_eq!(bytes, binding_memory_bytes(&binding));
447 }
448
449 #[test]
450 fn replacement_admission_matches_final_rows_without_mutating_input() {
451 let column = hawdb_plan_cypher::SCORING_RERANK_SCORE_COLUMN;
452 for previous in [
453 None,
454 Some(Value::Null),
455 Some(Value::Float(-1.0)),
456 Some(Value::List(vec![Value::Map(BTreeMap::from([(
457 "nested".into(),
458 Value::String("x".repeat(1_024)),
459 )]))])),
460 ] {
461 let mut input = Binding::scalar("score", Value::Float(0.5));
462 if let Some(previous) = previous {
463 input.values.insert(column.into(), previous);
464 }
465 let original = input.clone();
466 let estimate =
467 binding_memory_bytes_replacing_value(&input, column, &Value::Float(0.25));
468 assert_eq!(input, original, "admission must not alter the candidate");
469 let mut output = input;
470 output.values.insert(column.into(), Value::Float(0.25));
471 assert_eq!(estimate, binding_memory_bytes(&output));
472 }
473 }
474}