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