Skip to main content

icydb_core/db/response/
mod.rs

1//! Module: response
2//! Responsibility: materialized query/write response payload contracts.
3//! Does not own: execution routing, planning policy, or cursor token protocol.
4//! Boundary: Tier-2 db API DTO surface returned by session execution.
5//! Architecture: `Response<R>` is transport-only and row-shape-agnostic.
6//! Query semantics (for example cardinality checks) must live in query/session
7//! extension traits rather than inherent response DTO methods.
8
9mod grouped;
10mod paged;
11
12#[cfg(test)]
13use crate::value::Value;
14use crate::{prelude::*, traits::EntityValue, types::Id, value::OutputValue};
15use thiserror::Error as ThisError;
16
17mod private {
18    ///
19    /// Sealed
20    ///
21    /// Internal marker used to seal response row-shape marker implementations.
22    ///
23
24    pub trait Sealed {}
25}
26
27pub(in crate::db) use grouped::RuntimeGroupedRow;
28pub use grouped::{GroupedRow, PagedGroupedExecution, PagedGroupedExecutionWithTrace};
29pub use paged::{PagedLoadExecution, PagedLoadExecutionWithTrace};
30
31///
32/// ResponseRow
33///
34/// Marker trait for row-shape DTOs that are valid payloads for `Response<R>`.
35/// This trait is sealed to keep row-shape admission local to the response layer.
36///
37
38pub trait ResponseRow: private::Sealed {}
39
40impl ResponseRow for GroupedRow {}
41
42impl private::Sealed for GroupedRow {}
43
44///
45/// Row
46///
47/// Materialized entity row with explicit identity and payload accessors.
48///
49
50#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct Row<E: EntityKind> {
52    id: Id<E>,
53    entity: E,
54}
55
56impl<E: EntityKind> Row<E> {
57    /// Construct one row from identity and entity payload.
58    #[must_use]
59    pub const fn new(id: Id<E>, entity: E) -> Self {
60        Self { id, entity }
61    }
62
63    /// Borrow this row's identity.
64    #[must_use]
65    pub const fn id(&self) -> Id<E> {
66        self.id
67    }
68
69    /// Consume and return this row's entity payload.
70    #[must_use]
71    pub fn entity(self) -> E {
72        self.entity
73    }
74
75    /// Borrow this row's entity payload.
76    #[must_use]
77    pub const fn entity_ref(&self) -> &E {
78        &self.entity
79    }
80
81    /// Consume and return `(id, entity)` parts.
82    #[must_use]
83    pub fn into_parts(self) -> (Id<E>, E) {
84        (self.id, self.entity)
85    }
86}
87
88impl<E: EntityKind> From<(Id<E>, E)> for Row<E> {
89    fn from(value: (Id<E>, E)) -> Self {
90        Self::new(value.0, value.1)
91    }
92}
93
94impl<E: EntityKind> private::Sealed for Row<E> {}
95
96impl<E: EntityKind> ResponseRow for Row<E> {}
97
98///
99/// ProjectedRow
100///
101/// One scalar projection output row emitted in planner declaration order.
102/// `values` carries evaluated expression outputs for this row.
103///
104
105#[derive(Clone, Debug, Eq, PartialEq)]
106pub struct ProjectedRow<E: EntityKind> {
107    id: Id<E>,
108    values: Vec<OutputValue>,
109}
110
111impl<E: EntityKind> ProjectedRow<E> {
112    /// Construct one projected scalar row.
113    #[must_use]
114    pub const fn new(id: Id<E>, values: Vec<OutputValue>) -> Self {
115        Self { id, values }
116    }
117
118    /// Build one projected row from runtime values at the public output boundary.
119    #[cfg(test)]
120    #[must_use]
121    pub(in crate::db) fn from_runtime_values(id: Id<E>, values: Vec<Value>) -> Self {
122        Self::new(id, values.into_iter().map(OutputValue::from).collect())
123    }
124
125    /// Borrow the source row identifier.
126    #[must_use]
127    pub const fn id(&self) -> Id<E> {
128        self.id
129    }
130
131    /// Borrow projected scalar values in declaration order.
132    #[must_use]
133    pub const fn values(&self) -> &[OutputValue] {
134        self.values.as_slice()
135    }
136
137    /// Consume and return `(id, projected_values)`.
138    #[must_use]
139    pub fn into_parts(self) -> (Id<E>, Vec<OutputValue>) {
140        (self.id, self.values)
141    }
142}
143
144impl<E: EntityKind> private::Sealed for ProjectedRow<E> {}
145
146impl<E: EntityKind> ResponseRow for ProjectedRow<E> {}
147
148///
149/// ResponseError
150///
151
152#[derive(Debug, ThisError)]
153pub enum ResponseError {
154    #[error("expected exactly one row, found 0 (entity {entity})")]
155    NotFound { entity: &'static str },
156
157    #[error("expected exactly one row, found {count} (entity {entity})")]
158    NotUnique { entity: &'static str, count: u32 },
159}
160
161impl ResponseError {
162    /// Construct one response not-found cardinality error.
163    #[must_use]
164    pub const fn not_found(entity: &'static str) -> Self {
165        Self::NotFound { entity }
166    }
167
168    /// Construct one response not-unique cardinality error.
169    #[must_use]
170    pub const fn not_unique(entity: &'static str, count: u32) -> Self {
171        Self::NotUnique { entity, count }
172    }
173}
174
175///
176/// Response
177///
178/// Generic response transport container for one row shape `R`.
179///
180
181#[derive(Debug)]
182pub struct Response<R: ResponseRow>(Vec<R>);
183
184///
185/// EntityResponse
186///
187/// Entity-row response transport alias.
188///
189
190pub type EntityResponse<E> = Response<Row<E>>;
191
192///
193/// ProjectionResponse
194///
195/// Scalar projection response transport alias.
196///
197
198pub type ProjectionResponse<E> = Response<ProjectedRow<E>>;
199
200impl<R: ResponseRow> Response<R> {
201    /// Construct one response from ordered rows.
202    #[must_use]
203    pub const fn new(rows: Vec<R>) -> Self {
204        Self(rows)
205    }
206
207    /// Construct one response from rows convertible into `R`.
208    #[must_use]
209    pub fn from_rows<T>(rows: Vec<T>) -> Self
210    where
211        T: Into<R>,
212    {
213        Self(rows.into_iter().map(Into::into).collect())
214    }
215
216    /// Return the number of rows.
217    #[must_use]
218    pub const fn len(&self) -> usize {
219        self.0.len()
220    }
221
222    /// Return the number of rows as a u32 API contract count.
223    #[must_use]
224    #[expect(clippy::cast_possible_truncation)]
225    pub const fn count(&self) -> u32 {
226        self.0.len() as u32
227    }
228
229    /// Return whether this response has no rows.
230    #[must_use]
231    pub const fn is_empty(&self) -> bool {
232        self.0.is_empty()
233    }
234
235    /// Consume and return all rows in response order.
236    #[must_use]
237    pub fn rows(self) -> Vec<R> {
238        self.0
239    }
240
241    /// Borrow an iterator over rows in response order.
242    pub fn iter(&self) -> std::slice::Iter<'_, R> {
243        self.0.iter()
244    }
245}
246
247impl<R: ResponseRow> AsRef<[R]> for Response<R> {
248    fn as_ref(&self) -> &[R] {
249        self.0.as_slice()
250    }
251}
252
253impl<R: ResponseRow> std::ops::Deref for Response<R> {
254    type Target = [R];
255
256    fn deref(&self) -> &Self::Target {
257        self.0.as_slice()
258    }
259}
260
261impl<E: EntityKind> Response<Row<E>> {
262    /// Return the first row identifier, if present.
263    #[must_use]
264    pub fn id(&self) -> Option<Id<E>> {
265        self.0.first().map(Row::id)
266    }
267
268    /// Consume and return all entities in response order.
269    #[must_use]
270    pub fn entities(self) -> Vec<E> {
271        self.0.into_iter().map(Row::entity).collect()
272    }
273
274    /// Borrow an iterator over row identifiers in response order.
275    pub fn ids(&self) -> impl Iterator<Item = Id<E>> + '_ {
276        self.0.iter().map(Row::id)
277    }
278
279    /// Check whether the response contains the provided identifier.
280    pub fn contains_id(&self, id: &Id<E>) -> bool {
281        self.0.iter().any(|row| row.id() == *id)
282    }
283}
284
285impl<R: ResponseRow> IntoIterator for Response<R> {
286    type Item = R;
287    type IntoIter = std::vec::IntoIter<Self::Item>;
288
289    fn into_iter(self) -> Self::IntoIter {
290        self.0.into_iter()
291    }
292}
293
294impl<'a, R: ResponseRow> IntoIterator for &'a Response<R> {
295    type Item = &'a R;
296    type IntoIter = std::slice::Iter<'a, R>;
297
298    fn into_iter(self) -> Self::IntoIter {
299        self.iter()
300    }
301}
302
303///
304/// WriteBatchResponse
305///
306/// Result of a batch write operation.
307/// Provides explicit access to stored entities and their identifiers.
308///
309
310#[derive(Debug)]
311pub struct WriteBatchResponse<E> {
312    entities: Vec<E>,
313}
314
315impl<E> WriteBatchResponse<E> {
316    /// Construct a batch response from stored entities.
317    #[must_use]
318    pub const fn new(entities: Vec<E>) -> Self {
319        Self { entities }
320    }
321
322    /// Return the number of entries.
323    #[must_use]
324    pub const fn len(&self) -> usize {
325        self.entities.len()
326    }
327
328    /// Returns `true` if the batch is empty.
329    #[must_use]
330    pub const fn is_empty(&self) -> bool {
331        self.entities.is_empty()
332    }
333
334    /// Return all stored entities.
335    #[must_use]
336    pub fn entities(self) -> Vec<E> {
337        self.entities
338    }
339
340    /// Borrow an iterator over primary keys in stable batch order.
341    pub fn ids(&self) -> impl Iterator<Item = Id<E>> + '_
342    where
343        E: EntityValue,
344    {
345        self.entities.iter().map(EntityValue::id)
346    }
347
348    /// Borrow an iterator over write entries in stable batch order.
349    pub fn iter(&self) -> std::slice::Iter<'_, E> {
350        self.entities.iter()
351    }
352}
353
354impl<E> IntoIterator for WriteBatchResponse<E> {
355    type Item = E;
356    type IntoIter = std::vec::IntoIter<E>;
357
358    fn into_iter(self) -> Self::IntoIter {
359        self.entities.into_iter()
360    }
361}
362
363impl<'a, E> IntoIterator for &'a WriteBatchResponse<E> {
364    type Item = &'a E;
365    type IntoIter = std::slice::Iter<'a, E>;
366
367    fn into_iter(self) -> Self::IntoIter {
368        self.iter()
369    }
370}