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.
5mod paged;
6
7use crate::{
8    prelude::*,
9    traits::{AsView, EntityValue},
10    types::Id,
11    value::Value,
12};
13use thiserror::Error as ThisError;
14
15// re-exports
16pub use paged::{PagedLoadExecution, PagedLoadExecutionWithTrace};
17
18///
19/// Row
20///
21
22pub type Row<E> = (Id<E>, E);
23
24///
25/// ProjectedRow
26///
27/// One scalar projection output row emitted in planner declaration order.
28/// `values` carries evaluated expression outputs for this row.
29///
30
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct ProjectedRow<E: EntityKind> {
33    id: Id<E>,
34    values: Vec<Value>,
35}
36
37impl<E: EntityKind> ProjectedRow<E> {
38    /// Construct one projected scalar row.
39    #[must_use]
40    pub const fn new(id: Id<E>, values: Vec<Value>) -> Self {
41        Self { id, values }
42    }
43
44    /// Borrow the source row identifier.
45    #[must_use]
46    pub const fn id(&self) -> Id<E> {
47        self.id
48    }
49
50    /// Borrow projected scalar values in declaration order.
51    #[must_use]
52    pub const fn values(&self) -> &[Value] {
53        self.values.as_slice()
54    }
55
56    /// Consume and return `(id, projected_values)`.
57    #[must_use]
58    pub fn into_parts(self) -> (Id<E>, Vec<Value>) {
59        (self.id, self.values)
60    }
61}
62
63///
64/// ResponseError
65///
66
67#[derive(Debug, ThisError)]
68pub enum ResponseError {
69    #[error("expected exactly one row, found 0 (entity {entity})")]
70    NotFound { entity: &'static str },
71
72    #[error("expected exactly one row, found {count} (entity {entity})")]
73    NotUnique { entity: &'static str, count: u32 },
74}
75
76impl ResponseError {
77    const fn not_found<E: EntityKind>() -> Self {
78        Self::NotFound { entity: E::PATH }
79    }
80
81    const fn not_unique<E: EntityKind>(count: u32) -> Self {
82        Self::NotUnique {
83            entity: E::PATH,
84            count,
85        }
86    }
87}
88
89///
90/// Response
91///
92/// Materialized query result: ordered `(Id, Entity)` pairs.
93/// IDs are returned for correlation, reporting, and lookup; they are public values and do not imply
94/// authorization, ownership, or row existence outside this response payload.
95/// When scalar projection evaluation runs, `projected_rows()` exposes the
96/// evaluated projection payload in matching row order.
97///
98
99#[derive(Debug)]
100pub struct Response<E: EntityKind>(pub Vec<Row<E>>, Option<Vec<ProjectedRow<E>>>);
101
102impl<E: EntityKind> Response<E> {
103    /// Construct one entity-row response without projection payload.
104    #[must_use]
105    pub const fn from_rows(rows: Vec<Row<E>>) -> Self {
106        Self(rows, None)
107    }
108
109    /// Construct one entity-row response with optional scalar projection output.
110    #[must_use]
111    pub const fn from_rows_with_projection(
112        rows: Vec<Row<E>>,
113        projected_rows: Option<Vec<ProjectedRow<E>>>,
114    ) -> Self {
115        Self(rows, projected_rows)
116    }
117
118    /// Borrow optional projected scalar rows when projection materialization ran.
119    #[must_use]
120    pub fn projected_rows(&self) -> Option<&[ProjectedRow<E>]> {
121        self.1.as_deref()
122    }
123
124    /// Consume and return optional projected scalar rows.
125    #[must_use]
126    pub fn into_projected_rows(self) -> Option<Vec<ProjectedRow<E>>> {
127        self.1
128    }
129
130    // ------------------------------------------------------------------
131    // Introspection
132    // ------------------------------------------------------------------
133
134    /// Return the number of rows as a u32 API contract count.
135    #[must_use]
136    #[expect(clippy::cast_possible_truncation)]
137    pub const fn count(&self) -> u32 {
138        self.0.len() as u32
139    }
140
141    /// Return whether this response has no rows.
142    #[must_use]
143    pub const fn is_empty(&self) -> bool {
144        self.0.is_empty()
145    }
146
147    // ------------------------------------------------------------------
148    // Cardinality enforcement
149    // ------------------------------------------------------------------
150
151    /// Require exactly one row in this response.
152    pub const fn require_one(&self) -> Result<(), ResponseError> {
153        match self.count() {
154            1 => Ok(()),
155            0 => Err(ResponseError::not_found::<E>()),
156            n => Err(ResponseError::not_unique::<E>(n)),
157        }
158    }
159
160    /// Require at least one row in this response.
161    pub const fn require_some(&self) -> Result<(), ResponseError> {
162        if self.is_empty() {
163            Err(ResponseError::not_found::<E>())
164        } else {
165            Ok(())
166        }
167    }
168
169    // ------------------------------------------------------------------
170    // Rows
171    // ------------------------------------------------------------------
172
173    /// Consume and return `None` for empty, `Some(row)` for one row, or error for many rows.
174    #[expect(clippy::cast_possible_truncation)]
175    pub fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
176        match self.0.len() {
177            0 => Ok(None),
178            1 => Ok(Some(self.0.into_iter().next().unwrap())),
179            n => Err(ResponseError::not_unique::<E>(n as u32)),
180        }
181    }
182
183    /// Consume and return the single row, or fail on zero/many rows.
184    pub fn row(self) -> Result<Row<E>, ResponseError> {
185        self.try_row()?.ok_or_else(ResponseError::not_found::<E>)
186    }
187
188    /// Consume and return all rows in response order.
189    #[must_use]
190    pub fn rows(self) -> Vec<Row<E>> {
191        self.0
192    }
193
194    // ------------------------------------------------------------------
195    // Entities
196    // ------------------------------------------------------------------
197
198    /// Consume and return the single entity or `None`, failing on many rows.
199    pub fn try_entity(self) -> Result<Option<E>, ResponseError> {
200        Ok(self.try_row()?.map(|(_, e)| e))
201    }
202
203    /// Consume and return the single entity, failing on zero/many rows.
204    pub fn entity(self) -> Result<E, ResponseError> {
205        self.row().map(|(_, e)| e)
206    }
207
208    /// Consume and return all entities in response order.
209    #[must_use]
210    pub fn entities(self) -> Vec<E> {
211        self.0.into_iter().map(|(_, e)| e).collect()
212    }
213
214    // ------------------------------------------------------------------
215    // Ids (identity-level)
216    // ------------------------------------------------------------------
217
218    /// Return the first row identifier, if present.
219    ///
220    /// This identifier is a public value for correlation, reporting, and lookup only.
221    #[must_use]
222    pub fn id(&self) -> Option<Id<E>> {
223        self.0.first().map(|(id, _)| *id)
224    }
225
226    /// Require exactly one row and return its identifier.
227    pub fn require_id(self) -> Result<Id<E>, ResponseError> {
228        self.row().map(|(id, _)| id)
229    }
230
231    /// Return all row identifiers in response order for correlation/reporting/lookup.
232    #[must_use]
233    pub fn ids(&self) -> Vec<Id<E>> {
234        self.0.iter().map(|(id, _)| *id).collect()
235    }
236
237    /// Check whether the response contains the provided identifier.
238    pub fn contains_id(&self, id: &Id<E>) -> bool {
239        self.0.iter().any(|(k, _)| k == id)
240    }
241
242    // ------------------------------------------------------------------
243    // Views
244    // ------------------------------------------------------------------
245
246    /// Return the single-row view, failing on zero/many rows.
247    pub fn view(&self) -> Result<<E as AsView>::ViewType, ResponseError> {
248        self.require_one()?;
249        Ok(self.0[0].1.as_view())
250    }
251
252    /// Return an optional single-row view, failing on many rows.
253    pub fn view_opt(&self) -> Result<Option<<E as AsView>::ViewType>, ResponseError> {
254        match self.count() {
255            0 => Ok(None),
256            1 => Ok(Some(self.0[0].1.as_view())),
257            n => Err(ResponseError::not_unique::<E>(n)),
258        }
259    }
260
261    /// Return all row views in response order.
262    #[must_use]
263    pub fn views(&self) -> Vec<<E as AsView>::ViewType> {
264        self.0.iter().map(|(_, e)| e.as_view()).collect()
265    }
266}
267
268impl<E: EntityKind> IntoIterator for Response<E> {
269    type Item = Row<E>;
270    type IntoIter = std::vec::IntoIter<Self::Item>;
271
272    fn into_iter(self) -> Self::IntoIter {
273        self.0.into_iter()
274    }
275}
276
277///
278/// WriteResponse
279///
280/// Result of a single write operation.
281/// Provides explicit access to the stored entity and its identifier.
282///
283
284#[derive(Debug)]
285pub struct WriteResponse<E> {
286    entity: E,
287}
288
289impl<E> WriteResponse<E> {
290    /// Construct a write response from the stored entity.
291    #[must_use]
292    pub const fn new(entity: E) -> Self {
293        Self { entity }
294    }
295
296    /// Return the stored entity.
297    #[must_use]
298    pub fn entity(self) -> E {
299        self.entity
300    }
301
302    /// Return the stored entity's identity
303    #[must_use]
304    pub fn id(&self) -> Id<E>
305    where
306        E: EntityValue,
307    {
308        self.entity.id()
309    }
310
311    /// Return the stored entity as its view type.
312    #[must_use]
313    pub fn view(&self) -> <E as AsView>::ViewType
314    where
315        E: AsView,
316    {
317        self.entity.as_view()
318    }
319}
320
321///
322/// WriteBatchResponse
323///
324/// Result of a batch write operation.
325/// Provides explicit access to stored entities and their identifiers.
326///
327
328#[derive(Debug)]
329pub struct WriteBatchResponse<E> {
330    entries: Vec<WriteResponse<E>>,
331}
332
333impl<E> WriteBatchResponse<E> {
334    /// Construct a batch response from stored entities.
335    #[must_use]
336    pub fn new(entities: Vec<E>) -> Self {
337        Self {
338            entries: entities.into_iter().map(WriteResponse::new).collect(),
339        }
340    }
341
342    /// Return all write responses.
343    #[must_use]
344    pub fn entries(&self) -> &[WriteResponse<E>] {
345        &self.entries
346    }
347
348    /// Return the number of entries.
349    #[must_use]
350    pub const fn len(&self) -> usize {
351        self.entries.len()
352    }
353
354    /// Returns `true` if the batch is empty.
355    #[must_use]
356    pub const fn is_empty(&self) -> bool {
357        self.entries.is_empty()
358    }
359
360    /// Return all stored entities.
361    #[must_use]
362    pub fn entities(self) -> Vec<E> {
363        self.entries
364            .into_iter()
365            .map(WriteResponse::entity)
366            .collect()
367    }
368
369    /// Return all primary keys for correlation, reporting, and lookup.
370    #[must_use]
371    pub fn ids(&self) -> Vec<Id<E>>
372    where
373        E: EntityValue,
374    {
375        self.entries.iter().map(WriteResponse::id).collect()
376    }
377
378    /// Return all views.
379    #[must_use]
380    pub fn views(&self) -> Vec<<E as AsView>::ViewType>
381    where
382        E: AsView,
383    {
384        self.entries.iter().map(WriteResponse::view).collect()
385    }
386}
387
388impl<E> IntoIterator for WriteBatchResponse<E> {
389    type Item = WriteResponse<E>;
390    type IntoIter = std::vec::IntoIter<WriteResponse<E>>;
391
392    fn into_iter(self) -> Self::IntoIter {
393        self.entries.into_iter()
394    }
395}
396
397impl<E> WriteBatchResponse<E> {
398    /// Borrow an iterator over write entries in stable batch order.
399    pub fn iter(&self) -> std::slice::Iter<'_, WriteResponse<E>> {
400        self.entries.iter()
401    }
402}
403
404impl<'a, E> IntoIterator for &'a WriteBatchResponse<E> {
405    type Item = &'a WriteResponse<E>;
406    type IntoIter = std::slice::Iter<'a, WriteResponse<E>>;
407
408    fn into_iter(self) -> Self::IntoIter {
409        self.iter()
410    }
411}