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