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