oxgraph_db/read.rs
1//! Owned read-side views: whole-record reads with typed property access.
2//!
3//! [`Reader::element`](crate::Reader::element) and
4//! [`Reader::relation`](crate::Reader::relation) return these owned views in one
5//! call — id, labels, and every property together — replacing per-key `Cow`
6//! reads. Property access is typed through [`Key<T>`](crate::Key): a read of the
7//! wrong Rust type is a compile error.
8//!
9//! # Performance
10//!
11//! Building a view is `O(label count + property count)` for the subject; typed
12//! property access is `O(log property count)`.
13
14use std::collections::BTreeMap;
15
16use crate::{
17 ElementId, LabelId, PropertyKeyId, PropertyValue, RelationId, RelationTypeId,
18 error::DbError,
19 typed::{Key, Readable, ValueType},
20};
21
22/// A typed, owned property bag for one subject.
23///
24/// # Performance
25///
26/// `get`/`require` are `O(log property count)`; `value` is `O(log property
27/// count)`.
28#[derive(Clone, Debug, Default, Eq, PartialEq)]
29pub struct Properties {
30 /// Visible property values keyed by property key id, in key order.
31 values: BTreeMap<PropertyKeyId, PropertyValue>,
32}
33
34impl Properties {
35 /// Builds a property bag from `(key, value)` pairs.
36 ///
37 /// # Performance
38 ///
39 /// This function is `O(n log n)` in the pair count.
40 pub(crate) fn from_pairs(
41 pairs: impl IntoIterator<Item = (PropertyKeyId, PropertyValue)>,
42 ) -> Self {
43 Self {
44 values: pairs.into_iter().collect(),
45 }
46 }
47
48 /// Reads a typed property, or `None` when absent or type-mismatched.
49 ///
50 /// # Performance
51 ///
52 /// This method is `O(log property count)` (plus `O(value length)` for text).
53 #[must_use]
54 pub fn get<T: ValueType, V: Readable<T>>(&self, key: Key<T>) -> Option<V> {
55 self.values.get(&key.id()).and_then(V::read)
56 }
57
58 /// Reads a required typed property.
59 ///
60 /// # Errors
61 ///
62 /// Returns [`DbError::MissingProperty`] when the property is absent.
63 ///
64 /// # Performance
65 ///
66 /// This method is `O(log property count)`.
67 pub fn require<T: ValueType, V: Readable<T>>(&self, key: Key<T>) -> Result<V, DbError> {
68 self.get(key).ok_or_else(|| {
69 DbError::Query(crate::error::QueryError::MissingProperty { key: key.id() })
70 })
71 }
72
73 /// Returns the raw value for an untyped key.
74 ///
75 /// # Performance
76 ///
77 /// This method is `O(log property count)`.
78 #[must_use]
79 pub fn value(&self, key: PropertyKeyId) -> Option<&PropertyValue> {
80 self.values.get(&key)
81 }
82
83 /// Returns the number of visible properties.
84 ///
85 /// # Performance
86 ///
87 /// This method is `O(1)`.
88 #[must_use]
89 pub fn len(&self) -> usize {
90 self.values.len()
91 }
92
93 /// Returns whether there are no visible properties.
94 ///
95 /// # Performance
96 ///
97 /// This method is `O(1)`.
98 #[must_use]
99 pub fn is_empty(&self) -> bool {
100 self.values.is_empty()
101 }
102
103 /// Iterates `(key, value)` pairs in ascending key order.
104 ///
105 /// # Performance
106 ///
107 /// Iteration is `O(property count)`.
108 pub fn iter(&self) -> impl Iterator<Item = (PropertyKeyId, &PropertyValue)> {
109 self.values.iter().map(|(key, value)| (*key, value))
110 }
111}
112
113/// An owned element view: id, labels, and all properties in one read.
114///
115/// # Performance
116///
117/// Cloning is `O(label count + property count)`.
118#[derive(Clone, Debug, Eq, PartialEq)]
119pub struct Element {
120 /// Canonical element id.
121 pub id: ElementId,
122 /// Labels assigned to this element, ascending.
123 pub labels: Vec<LabelId>,
124 /// The element's property bag.
125 properties: Properties,
126}
127
128impl Element {
129 /// Assembles an owned element view.
130 ///
131 /// # Performance
132 ///
133 /// This function is `O(1)`.
134 pub(crate) const fn new(id: ElementId, labels: Vec<LabelId>, properties: Properties) -> Self {
135 Self {
136 id,
137 labels,
138 properties,
139 }
140 }
141
142 /// Returns whether this element carries `label`.
143 ///
144 /// # Performance
145 ///
146 /// This method is `O(log label count)`.
147 #[must_use]
148 pub fn has(&self, label: LabelId) -> bool {
149 self.labels.binary_search(&label).is_ok()
150 }
151
152 /// Returns this element's property bag.
153 ///
154 /// # Performance
155 ///
156 /// This method is `O(1)`.
157 #[must_use]
158 pub const fn properties(&self) -> &Properties {
159 &self.properties
160 }
161}
162
163/// An owned relation view: id, type, labels, and all properties in one read.
164///
165/// # Performance
166///
167/// Cloning is `O(label count + property count)`.
168#[derive(Clone, Debug, Eq, PartialEq)]
169pub struct Relation {
170 /// Canonical relation id.
171 pub id: RelationId,
172 /// The relation's type, if set.
173 pub relation_type: Option<RelationTypeId>,
174 /// Labels assigned to this relation, ascending.
175 pub labels: Vec<LabelId>,
176 /// The relation's property bag.
177 properties: Properties,
178}
179
180impl Relation {
181 /// Assembles an owned relation view.
182 ///
183 /// # Performance
184 ///
185 /// This function is `O(1)`.
186 pub(crate) const fn new(
187 id: RelationId,
188 relation_type: Option<RelationTypeId>,
189 labels: Vec<LabelId>,
190 properties: Properties,
191 ) -> Self {
192 Self {
193 id,
194 relation_type,
195 labels,
196 properties,
197 }
198 }
199
200 /// Returns whether this relation carries `label`.
201 ///
202 /// # Performance
203 ///
204 /// This method is `O(log label count)`.
205 #[must_use]
206 pub fn has(&self, label: LabelId) -> bool {
207 self.labels.binary_search(&label).is_ok()
208 }
209
210 /// Returns this relation's property bag.
211 ///
212 /// # Performance
213 ///
214 /// This method is `O(1)`.
215 #[must_use]
216 pub const fn properties(&self) -> &Properties {
217 &self.properties
218 }
219}