Skip to main content

hawdb_core/
ids.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
15use crate::{LabelId, RelTypeId, Value};
16use std::collections::{BTreeMap, BTreeSet};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct NodeId(pub u64);
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct NodeRecord {
23    pub id: NodeId,
24    pub labels: BTreeSet<LabelId>,
25    pub properties: BTreeMap<String, Value>,
26}
27
28/// A node identity with an explicitly selected property set.
29///
30/// This type is intentionally distinct from [`NodeRecord`]: an absent entry
31/// means that the property was not requested, not that the canonical node is
32/// missing the property.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ProjectedNodeRecord {
35    pub id: NodeId,
36    pub labels: BTreeSet<LabelId>,
37    pub properties: BTreeMap<String, Value>,
38}
39
40#[doc(hidden)]
41pub fn project_node_record(
42    node: NodeRecord,
43    required_properties: &BTreeSet<String>,
44) -> ProjectedNodeRecord {
45    let properties = required_properties
46        .iter()
47        .filter_map(|property| {
48            node.properties
49                .get(property)
50                .cloned()
51                .map(|value| (property.clone(), value))
52        })
53        .collect();
54    ProjectedNodeRecord {
55        id: node.id,
56        labels: node.labels,
57        properties,
58    }
59}
60
61/// Clone only selected columns from a borrowed node, without copying unrelated
62/// payloads. Callers that own a query budget admit the estimate before cloning.
63#[doc(hidden)]
64pub fn project_node_record_ref(
65    node: &NodeRecord,
66    required_properties: &BTreeSet<String>,
67) -> ProjectedNodeRecord {
68    ProjectedNodeRecord {
69        id: node.id,
70        labels: node.labels.clone(),
71        properties: required_properties
72            .iter()
73            .filter_map(|name| {
74                node.properties
75                    .get(name)
76                    .map(|value| (name.clone(), value.clone()))
77            })
78            .collect(),
79    }
80}
81
82/// Conservative allocation bound for the selected record, including container
83/// overhead and recursively owned values, computed without cloning values.
84#[doc(hidden)]
85pub fn projected_node_allocation_bytes(
86    node: &NodeRecord,
87    required_properties: &BTreeSet<String>,
88) -> usize {
89    std::mem::size_of::<ProjectedNodeRecord>()
90        .saturating_add(node.labels.len().saturating_mul(128))
91        .saturating_add(properties_allocation_bytes(
92            required_properties.iter().filter_map(|name| {
93                node.properties
94                    .get(name)
95                    .map(|value| (name.as_str(), value))
96            }),
97        ))
98}
99
100/// Full-record allocation bound, evaluated while all values remain borrowed.
101#[doc(hidden)]
102pub fn node_allocation_bytes(node: &NodeRecord) -> usize {
103    std::mem::size_of::<NodeRecord>()
104        .saturating_add(node.labels.len().saturating_mul(128))
105        .saturating_add(properties_allocation_bytes(
106            node.properties
107                .iter()
108                .map(|(name, value)| (name.as_str(), value)),
109        ))
110}
111
112fn properties_allocation_bytes<'a>(
113    properties: impl Iterator<Item = (&'a str, &'a Value)>,
114) -> usize {
115    let (count, payload) = properties.fold((0usize, 0usize), |(count, payload), (name, value)| {
116        (
117            count.saturating_add(1),
118            payload
119                .saturating_add(name.len())
120                .saturating_add(projected_value_allocation_bytes(value)),
121        )
122    });
123    if count == 0 {
124        return 0;
125    }
126    payload.saturating_add(property_container_allocation_bytes(count))
127}
128
129/// Container and projection-collection bound shared with encoded preflight.
130/// Values and owned key strings are charged separately, before decoding.
131#[doc(hidden)]
132pub fn property_container_allocation_bytes(count: usize) -> usize {
133    if count == 0 {
134        return 0;
135    }
136    // The pinned std B-tree stores up to eleven pairs per node and at least
137    // five pairs per non-root node. Include internal links/header even for a
138    // leaf, plus the collecting vector used by projected FromIterator reads.
139    let pair_bytes = std::mem::size_of::<(String, Value)>();
140    let node_bytes = pair_bytes
141        .saturating_mul(11)
142        .saturating_add(std::mem::size_of::<usize>().saturating_mul(12))
143        .saturating_add(32);
144    let nodes = if count <= 11 {
145        1
146    } else {
147        1usize.saturating_add((count - 1) / 5)
148    };
149    nodes
150        .saturating_mul(node_bytes)
151        .saturating_add(count.saturating_mul(2).max(4).saturating_mul(pair_bytes))
152}
153
154/// Conservative ownership bound shared by projected records and copied index keys.
155#[doc(hidden)]
156pub fn projected_value_allocation_bytes(value: &Value) -> usize {
157    std::mem::size_of::<Value>().saturating_add(match value {
158        Value::String(value) => value.len(),
159        Value::Binary(value) => value.len(),
160        Value::Vector(value) => value.encoded().len(),
161        Value::List(values) => values.iter().fold(32usize, |total, value| {
162            total.saturating_add(projected_value_allocation_bytes(value).saturating_mul(2))
163        }),
164        Value::Map(values) => {
165            properties_allocation_bytes(values.iter().map(|(key, value)| (key.as_str(), value)))
166        }
167        _ => 0,
168    })
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
172pub struct RelId(pub u64);
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct RelRecord {
176    pub id: RelId,
177    pub source: NodeId,
178    pub target: NodeId,
179    pub rel_type: RelTypeId,
180    pub properties: BTreeMap<String, Value>,
181}
182
183/// Full relationship ownership bound, measured without copying its properties.
184#[doc(hidden)]
185pub fn relationship_allocation_bytes(relationship: &RelRecord) -> usize {
186    std::mem::size_of::<RelRecord>().saturating_add(properties_allocation_bytes(
187        relationship
188            .properties
189            .iter()
190            .map(|(name, value)| (name.as_str(), value)),
191    ))
192}