Skip to main content

hawdb_executor/
binding.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Executor-owned row bindings and deterministic memory accounting.
16
17use hawdb_core::{LabelId, Value};
18use hawdb_plan::SortDirection;
19use hawdb_storage::{NodeRecord, RelRecord};
20use std::cmp::Ordering;
21use std::collections::BTreeMap;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Binding {
25    pub values: BTreeMap<String, Value>,
26    pub nodes: BTreeMap<String, NodeRecord>,
27    pub relationships: BTreeMap<String, RelRecord>,
28}
29
30impl Binding {
31    pub fn values(values: BTreeMap<String, Value>) -> Self {
32        Self {
33            values,
34            nodes: BTreeMap::new(),
35            relationships: BTreeMap::new(),
36        }
37    }
38
39    pub fn scalar(name: impl Into<String>, value: Value) -> Self {
40        Self::values(BTreeMap::from([(name.into(), value)]))
41    }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct TopNBinding {
46    pub sort_values: Vec<(Value, SortDirection)>,
47    pub ordinal: u64,
48    pub binding: Binding,
49}
50
51impl TopNBinding {
52    pub fn memory_bytes(&self) -> usize {
53        binding_memory_bytes(&self.binding).saturating_add(self.sort_values.iter().fold(
54            std::mem::size_of::<Vec<(Value, SortDirection)>>(),
55            |total, (value, _)| total.saturating_add(value_memory_bytes(value)),
56        ))
57    }
58}
59
60impl Ord for TopNBinding {
61    fn cmp(&self, other: &Self) -> Ordering {
62        for ((left, direction), (right, other_direction)) in
63            self.sort_values.iter().zip(&other.sort_values)
64        {
65            debug_assert_eq!(direction, other_direction);
66            let ordering = match direction {
67                SortDirection::Asc => left.cmp(right),
68                SortDirection::Desc => left.cmp(right).reverse(),
69            };
70            if ordering != Ordering::Equal {
71                return ordering;
72            }
73        }
74        self.ordinal.cmp(&other.ordinal)
75    }
76}
77
78impl PartialOrd for TopNBinding {
79    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
80        Some(self.cmp(other))
81    }
82}
83
84pub fn map_payload_bytes(values: &BTreeMap<String, Value>) -> usize {
85    values.iter().fold(0usize, |total, (name, value)| {
86        total
87            .saturating_add(name.len())
88            .saturating_add(value_payload_bytes(value))
89    })
90}
91
92pub fn map_memory_bytes(values: &BTreeMap<String, Value>) -> usize {
93    std::mem::size_of::<BTreeMap<String, Value>>().saturating_add(values.iter().fold(
94        0usize,
95        |total, (name, value)| {
96            total
97                .saturating_add(std::mem::size_of::<(String, Value)>() * 3)
98                .saturating_add(name.len())
99                .saturating_add(value_memory_bytes(value))
100        },
101    ))
102}
103
104pub fn value_memory_bytes(value: &Value) -> usize {
105    std::mem::size_of::<Value>().saturating_add(match value {
106        Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) => 0,
107        Value::String(value) => value.len(),
108        Value::Binary(value) => value.len(),
109        Value::Uuid(_) => 16,
110        Value::List(values) => values
111            .iter()
112            .fold(std::mem::size_of::<Vec<Value>>(), |total, value| {
113                total.saturating_add(value_memory_bytes(value))
114            }),
115        Value::Map(values) => map_memory_bytes(values),
116    })
117}
118
119pub fn binding_payload_bytes(binding: &Binding) -> usize {
120    map_payload_bytes(&binding.values)
121        .saturating_add(binding.nodes.iter().fold(0usize, |total, (name, node)| {
122            total
123                .saturating_add(name.len())
124                .saturating_add(std::mem::size_of_val(&node.id))
125                .saturating_add(
126                    node.labels
127                        .len()
128                        .saturating_mul(std::mem::size_of::<LabelId>()),
129                )
130                .saturating_add(map_payload_bytes(&node.properties))
131        }))
132        .saturating_add(
133            binding
134                .relationships
135                .iter()
136                .fold(0usize, |total, (name, relationship)| {
137                    total
138                        .saturating_add(name.len())
139                        .saturating_add(std::mem::size_of_val(&relationship.id))
140                        .saturating_add(std::mem::size_of_val(&relationship.source))
141                        .saturating_add(std::mem::size_of_val(&relationship.target))
142                        .saturating_add(std::mem::size_of_val(&relationship.rel_type))
143                        .saturating_add(map_payload_bytes(&relationship.properties))
144                }),
145        )
146}
147
148pub fn binding_memory_bytes(binding: &Binding) -> usize {
149    std::mem::size_of::<Binding>()
150        .saturating_add(binding_payload_bytes(binding))
151        .saturating_add(
152            binding
153                .values
154                .len()
155                .saturating_add(binding.nodes.len())
156                .saturating_add(binding.relationships.len())
157                .saturating_mul(std::mem::size_of::<usize>() * 6),
158        )
159}
160
161pub fn node_memory_bytes(node: &NodeRecord) -> usize {
162    std::mem::size_of::<NodeRecord>()
163        .saturating_add(
164            node.labels
165                .len()
166                .saturating_mul(std::mem::size_of::<LabelId>() * 3),
167        )
168        .saturating_add(map_memory_bytes(&node.properties))
169}
170
171pub fn relationship_memory_bytes(relationship: &RelRecord) -> usize {
172    std::mem::size_of::<RelRecord>().saturating_add(map_memory_bytes(&relationship.properties))
173}
174
175pub fn value_payload_bytes(value: &Value) -> usize {
176    match value {
177        Value::Null => 0,
178        Value::Bool(_) => std::mem::size_of::<bool>(),
179        Value::Int(_) => std::mem::size_of::<i64>(),
180        Value::Float(_) => std::mem::size_of::<f64>(),
181        Value::String(value) => value.len(),
182        Value::Binary(value) => value.len(),
183        Value::Uuid(_) => 16,
184        Value::List(values) => values.iter().fold(0usize, |total, value| {
185            total.saturating_add(value_payload_bytes(value))
186        }),
187        Value::Map(values) => map_payload_bytes(values),
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194    use hawdb_core::RelTypeId;
195    use hawdb_storage::{NodeId, RelId};
196    use std::collections::BTreeSet;
197
198    #[test]
199    fn value_only_constructors_leave_graph_bindings_empty() {
200        let values = Binding::values(BTreeMap::from([("left".to_string(), Value::Int(1))]));
201        assert_eq!(values.values["left"], Value::Int(1));
202        assert!(values.nodes.is_empty());
203        assert!(values.relationships.is_empty());
204
205        let scalar = Binding::scalar("right", Value::Bool(true));
206        assert_eq!(scalar.values["right"], Value::Bool(true));
207        assert!(scalar.nodes.is_empty());
208        assert!(scalar.relationships.is_empty());
209    }
210
211    #[test]
212    fn binding_accounting_includes_nested_values_and_graph_records() {
213        let binding = Binding {
214            values: BTreeMap::from([(
215                "nested".to_string(),
216                Value::List(vec![Value::Int(1), Value::String("two".to_string())]),
217            )]),
218            nodes: BTreeMap::from([(
219                "n".to_string(),
220                NodeRecord {
221                    id: NodeId(7),
222                    labels: BTreeSet::from([LabelId(3)]),
223                    properties: BTreeMap::new(),
224                },
225            )]),
226            relationships: BTreeMap::from([(
227                "r".to_string(),
228                RelRecord {
229                    id: RelId(11),
230                    source: NodeId(7),
231                    target: NodeId(8),
232                    rel_type: RelTypeId(4),
233                    properties: BTreeMap::new(),
234                },
235            )]),
236        };
237
238        assert!(binding_payload_bytes(&binding) > "nested".len() + "two".len());
239        assert!(binding_memory_bytes(&binding) > binding_payload_bytes(&binding));
240    }
241}