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)
69 .ok_or_else(|| DbError::MissingProperty { key: key.id() })
70 }
71
72 /// Returns the raw value for an untyped key.
73 ///
74 /// # Performance
75 ///
76 /// This method is `O(log property count)`.
77 #[must_use]
78 pub fn value(&self, key: PropertyKeyId) -> Option<&PropertyValue> {
79 self.values.get(&key)
80 }
81
82 /// Returns the number of visible properties.
83 ///
84 /// # Performance
85 ///
86 /// This method is `O(1)`.
87 #[must_use]
88 pub fn len(&self) -> usize {
89 self.values.len()
90 }
91
92 /// Returns whether there are no visible properties.
93 ///
94 /// # Performance
95 ///
96 /// This method is `O(1)`.
97 #[must_use]
98 pub fn is_empty(&self) -> bool {
99 self.values.is_empty()
100 }
101
102 /// Iterates `(key, value)` pairs in ascending key order.
103 ///
104 /// # Performance
105 ///
106 /// Iteration is `O(property count)`.
107 pub fn iter(&self) -> impl Iterator<Item = (PropertyKeyId, &PropertyValue)> {
108 self.values.iter().map(|(key, value)| (*key, value))
109 }
110}
111
112/// An owned element view: id, labels, and all properties in one read.
113///
114/// # Performance
115///
116/// Cloning is `O(label count + property count)`.
117#[derive(Clone, Debug, Eq, PartialEq)]
118pub struct Element {
119 /// Canonical element id.
120 pub id: ElementId,
121 /// Labels assigned to this element, ascending.
122 pub labels: Vec<LabelId>,
123 /// The element's property bag.
124 properties: Properties,
125}
126
127impl Element {
128 /// Assembles an owned element view.
129 ///
130 /// # Performance
131 ///
132 /// This function is `O(1)`.
133 pub(crate) const fn new(id: ElementId, labels: Vec<LabelId>, properties: Properties) -> Self {
134 Self {
135 id,
136 labels,
137 properties,
138 }
139 }
140
141 /// Returns whether this element carries `label`.
142 ///
143 /// # Performance
144 ///
145 /// This method is `O(log label count)`.
146 #[must_use]
147 pub fn has(&self, label: LabelId) -> bool {
148 self.labels.binary_search(&label).is_ok()
149 }
150
151 /// Returns this element's property bag.
152 ///
153 /// # Performance
154 ///
155 /// This method is `O(1)`.
156 #[must_use]
157 pub const fn properties(&self) -> &Properties {
158 &self.properties
159 }
160}
161
162/// An owned relation view: id, type, labels, and all properties in one read.
163///
164/// # Performance
165///
166/// Cloning is `O(label count + property count)`.
167#[derive(Clone, Debug, Eq, PartialEq)]
168pub struct Relation {
169 /// Canonical relation id.
170 pub id: RelationId,
171 /// The relation's type, if set.
172 pub relation_type: Option<RelationTypeId>,
173 /// Labels assigned to this relation, ascending.
174 pub labels: Vec<LabelId>,
175 /// The relation's property bag.
176 properties: Properties,
177}
178
179impl Relation {
180 /// Assembles an owned relation view.
181 ///
182 /// # Performance
183 ///
184 /// This function is `O(1)`.
185 pub(crate) const fn new(
186 id: RelationId,
187 relation_type: Option<RelationTypeId>,
188 labels: Vec<LabelId>,
189 properties: Properties,
190 ) -> Self {
191 Self {
192 id,
193 relation_type,
194 labels,
195 properties,
196 }
197 }
198
199 /// Returns whether this relation carries `label`.
200 ///
201 /// # Performance
202 ///
203 /// This method is `O(log label count)`.
204 #[must_use]
205 pub fn has(&self, label: LabelId) -> bool {
206 self.labels.binary_search(&label).is_ok()
207 }
208
209 /// Returns this relation's property bag.
210 ///
211 /// # Performance
212 ///
213 /// This method is `O(1)`.
214 #[must_use]
215 pub const fn properties(&self) -> &Properties {
216 &self.properties
217 }
218}