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
12use crate::{entity::EntityValue, prelude::*, types::Id};
13
14mod private {
15    ///
16    /// Sealed
17    ///
18    /// Internal marker used to seal response row-shape marker implementations.
19    ///
20
21    pub trait Sealed {}
22}
23
24pub use grouped::{GroupedRow, PagedGroupedExecution, PagedGroupedExecutionWithTrace};
25pub use paged::{PagedLoadExecution, PagedLoadExecutionWithTrace};
26
27///
28/// ResponseRow
29///
30/// Marker trait for row-shape DTOs that are valid payloads for `Response<R>`.
31/// This trait is sealed to keep row-shape admission local to the response layer.
32///
33
34pub trait ResponseRow: private::Sealed {}
35
36impl ResponseRow for GroupedRow {}
37
38impl private::Sealed for GroupedRow {}
39
40///
41/// Row
42///
43/// Materialized entity row with explicit identity and payload accessors.
44///
45
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct Row<E: EntityKind> {
48    id: Id<E>,
49    entity: E,
50}
51
52impl<E: EntityKind> Row<E> {
53    /// Construct one row from identity and entity payload.
54    #[must_use]
55    pub const fn new(id: Id<E>, entity: E) -> Self {
56        Self { id, entity }
57    }
58
59    /// Borrow this row's identity.
60    #[must_use]
61    pub const fn id(&self) -> Id<E> {
62        self.id
63    }
64
65    /// Consume and return this row's entity payload.
66    #[must_use]
67    pub fn entity(self) -> E {
68        self.entity
69    }
70
71    /// Borrow this row's entity payload.
72    #[must_use]
73    pub const fn entity_ref(&self) -> &E {
74        &self.entity
75    }
76
77    /// Consume and return row identity plus entity payload.
78    #[must_use]
79    pub fn into_id_and_entity(self) -> (Id<E>, E) {
80        (self.id, self.entity)
81    }
82}
83
84impl<E: EntityKind> From<(Id<E>, E)> for Row<E> {
85    fn from(value: (Id<E>, E)) -> Self {
86        Self::new(value.0, value.1)
87    }
88}
89
90impl<E: EntityKind> private::Sealed for Row<E> {}
91
92impl<E: EntityKind> ResponseRow for Row<E> {}
93
94///
95/// ResponseError
96///
97
98#[derive(Debug)]
99pub enum ResponseError {
100    NotFound { entity: &'static str },
101
102    NotUnique { entity: &'static str, count: u32 },
103}
104
105impl ResponseError {
106    /// Construct one response not-found cardinality error.
107    #[must_use]
108    pub const fn not_found(entity: &'static str) -> Self {
109        Self::NotFound { entity }
110    }
111
112    /// Construct one response not-unique cardinality error.
113    #[must_use]
114    pub const fn not_unique(entity: &'static str, count: u32) -> Self {
115        Self::NotUnique { entity, count }
116    }
117}
118
119///
120/// Response
121///
122/// Generic response transport container for one row shape `R`.
123///
124
125#[derive(Debug)]
126pub struct Response<R: ResponseRow>(Vec<R>);
127
128///
129/// EntityResponse
130///
131/// Entity-row response transport alias.
132///
133
134pub type EntityResponse<E> = Response<Row<E>>;
135
136impl<R: ResponseRow> Response<R> {
137    /// Construct one response from ordered rows.
138    #[must_use]
139    pub const fn new(rows: Vec<R>) -> Self {
140        Self(rows)
141    }
142
143    /// Return the number of rows.
144    #[must_use]
145    pub const fn len(&self) -> usize {
146        self.0.len()
147    }
148
149    /// Return the number of rows as a u32 API contract count.
150    #[must_use]
151    #[expect(clippy::cast_possible_truncation)]
152    pub const fn count(&self) -> u32 {
153        self.0.len() as u32
154    }
155
156    /// Return whether this response has no rows.
157    #[must_use]
158    pub const fn is_empty(&self) -> bool {
159        self.0.is_empty()
160    }
161
162    /// Consume and return all rows in response order.
163    #[must_use]
164    pub fn rows(self) -> Vec<R> {
165        self.0
166    }
167
168    /// Borrow rows as a slice in response order.
169    #[must_use]
170    pub const fn as_slice(&self) -> &[R] {
171        self.0.as_slice()
172    }
173
174    /// Borrow an iterator over rows in response order.
175    pub fn iter(&self) -> std::slice::Iter<'_, R> {
176        self.0.iter()
177    }
178}
179
180impl<E: EntityKind> Response<Row<E>> {
181    /// Consume and return all entities in response order.
182    #[must_use]
183    pub fn entities(self) -> Vec<E> {
184        self.0.into_iter().map(Row::entity).collect()
185    }
186
187    /// Borrow an iterator over row identifiers in response order.
188    pub fn ids(&self) -> impl Iterator<Item = Id<E>> + '_ {
189        self.0.iter().map(Row::id)
190    }
191
192    /// Check whether the response contains the provided identifier.
193    pub fn contains_id(&self, id: &Id<E>) -> bool {
194        self.0.iter().any(|row| row.id() == *id)
195    }
196}
197
198impl<R: ResponseRow> IntoIterator for Response<R> {
199    type Item = R;
200    type IntoIter = std::vec::IntoIter<Self::Item>;
201
202    fn into_iter(self) -> Self::IntoIter {
203        self.0.into_iter()
204    }
205}
206
207impl<'a, R: ResponseRow> IntoIterator for &'a Response<R> {
208    type Item = &'a R;
209    type IntoIter = std::slice::Iter<'a, R>;
210
211    fn into_iter(self) -> Self::IntoIter {
212        self.iter()
213    }
214}
215
216///
217/// WriteBatchResponse
218///
219/// Result of a batch write operation.
220/// Provides explicit access to stored entities and their identifiers.
221///
222
223#[derive(Debug)]
224pub struct WriteBatchResponse<E> {
225    entities: Vec<E>,
226}
227
228impl<E> WriteBatchResponse<E> {
229    /// Construct a batch response from stored entities.
230    #[must_use]
231    pub(in crate::db) const fn new(entities: Vec<E>) -> Self {
232        Self { entities }
233    }
234
235    /// Return the number of entries.
236    #[must_use]
237    pub const fn len(&self) -> usize {
238        self.entities.len()
239    }
240
241    /// Returns `true` if the batch is empty.
242    #[must_use]
243    pub const fn is_empty(&self) -> bool {
244        self.entities.is_empty()
245    }
246
247    /// Return all stored entities.
248    #[must_use]
249    pub fn entities(self) -> Vec<E> {
250        self.entities
251    }
252
253    /// Borrow an iterator over primary keys in stable batch order.
254    pub fn ids(&self) -> impl Iterator<Item = Id<E>> + '_
255    where
256        E: EntityValue,
257    {
258        self.entities.iter().map(EntityValue::id)
259    }
260
261    /// Borrow an iterator over write entries in stable batch order.
262    pub fn iter(&self) -> std::slice::Iter<'_, E> {
263        self.entities.iter()
264    }
265}
266
267impl<E> IntoIterator for WriteBatchResponse<E> {
268    type Item = E;
269    type IntoIter = std::vec::IntoIter<E>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        self.entities.into_iter()
273    }
274}
275
276impl<'a, E> IntoIterator for &'a WriteBatchResponse<E> {
277    type Item = &'a E;
278    type IntoIter = std::slice::Iter<'a, E>;
279
280    fn into_iter(self) -> Self::IntoIter {
281        self.iter()
282    }
283}