Skip to main content

icydb_core/db/response/
page.rs

1//! Module: response::page
2//! Responsibility: public bounded scalar-page response payloads.
3//! Does not own: cursor validation, planning, or source revision proofs.
4//! Boundary: executor progress -> Candid-safe live page DTO.
5
6use crate::value::OutputValue;
7use candid::CandidType;
8use serde::Deserialize;
9
10/// Bounded work observed while producing one scalar page.
11#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
12pub struct ScalarPageWork {
13    /// Immutable identity of the page-work envelope bound into continuation.
14    pub envelope_identity: u64,
15    /// Physical keys or index entries visited by this page execution.
16    pub entries_visited: u64,
17    /// Logical rows returned to the caller.
18    pub result_rows: u32,
19}
20
21/// One revision-tolerant scalar keyset page.
22///
23/// A non-null continuation means traversal has not been proven exhausted. The
24/// token is authenticated but not encrypted and must be treated as opaque.
25#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
26pub struct LiveQueryPageOutput {
27    /// Accepted entity name used for the read.
28    pub entity: String,
29    /// Selected output-column names in row order.
30    pub columns: Vec<String>,
31    /// Row-oriented output values.
32    pub rows: Vec<Vec<OutputValue>>,
33    /// Number of returned rows.
34    pub row_count: u32,
35    /// Authenticated continuation, or `None` after proven exhaustion.
36    pub continuation: Option<String>,
37    /// Bounded work observed while producing this page.
38    pub work: ScalarPageWork,
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[derive(CandidType)]
46    struct FrozenScalarPageWorkWire {
47        envelope_identity: u64,
48        entries_visited: u64,
49        result_rows: u32,
50    }
51
52    #[derive(CandidType)]
53    struct FrozenLiveQueryPageOutputWire {
54        entity: String,
55        columns: Vec<String>,
56        rows: Vec<Vec<OutputValue>>,
57        row_count: u32,
58        continuation: Option<String>,
59        work: FrozenScalarPageWorkWire,
60    }
61
62    #[test]
63    fn live_query_page_output_preserves_its_initial_candid_record_shape() {
64        let current = LiveQueryPageOutput {
65            entity: "example".to_string(),
66            columns: vec!["id".to_string()],
67            rows: vec![vec![OutputValue::Nat64(7)]],
68            row_count: 1,
69            continuation: Some("opaque".to_string()),
70            work: ScalarPageWork {
71                envelope_identity: 11,
72                entries_visited: 2,
73                result_rows: 1,
74            },
75        };
76        let frozen = FrozenLiveQueryPageOutputWire {
77            entity: current.entity.clone(),
78            columns: current.columns.clone(),
79            rows: current.rows.clone(),
80            row_count: current.row_count,
81            continuation: current.continuation.clone(),
82            work: FrozenScalarPageWorkWire {
83                envelope_identity: current.work.envelope_identity,
84                entries_visited: current.work.entries_visited,
85                result_rows: current.work.result_rows,
86            },
87        };
88
89        assert_eq!(
90            candid::encode_one(&current).expect("current live page should encode"),
91            candid::encode_one(&frozen).expect("frozen live page should encode"),
92        );
93    }
94}