Skip to main content

icydb_core/db/query/
dynamic.rs

1//! Module: db::query::dynamic
2//! Responsibility: entity-name-driven structural read requests and results.
3//! Does not own: accepted schema resolution, planning, or execution.
4//! Boundary: public dynamic inputs are lowered once against accepted authority.
5
6use crate::db::query::{
7    builder::AggregateExpr,
8    expr::{FilterExpr, OrderTerm},
9};
10
11///
12/// DynamicQuery
13///
14/// Entity-name-driven structural read request.
15/// The session resolves fields, ordering, indexes, and projection against the
16/// accepted schema; no generated entity descriptor participates.
17///
18
19#[derive(Clone, Debug)]
20pub struct DynamicQuery {
21    entity: String,
22    filter: Option<FilterExpr>,
23    order: Vec<OrderTerm>,
24    fields: Vec<String>,
25    limit: Option<u32>,
26    group_fields: Vec<String>,
27    aggregates: Vec<AggregateExpr>,
28    grouped_limits: Option<(u32, u32)>,
29    cursor: Option<String>,
30}
31
32impl DynamicQuery {
33    /// Start one dynamic read for an accepted entity name.
34    #[must_use]
35    pub fn new(entity: impl Into<String>) -> Self {
36        Self {
37            entity: entity.into(),
38            filter: None,
39            order: Vec::new(),
40            fields: Vec::new(),
41            limit: None,
42            group_fields: Vec::new(),
43            aggregates: Vec::new(),
44            grouped_limits: None,
45            cursor: None,
46        }
47    }
48
49    /// Add one filter expression.
50    #[must_use]
51    pub fn filter(mut self, filter: impl Into<FilterExpr>) -> Self {
52        self.filter = Some(filter.into());
53        self
54    }
55
56    /// Append one deterministic ordering term.
57    #[must_use]
58    pub fn order_by(mut self, order: OrderTerm) -> Self {
59        self.order.push(order);
60        self
61    }
62
63    /// Select explicit fields in scalar output order.
64    ///
65    /// Grouped execution rejects an explicit scalar selection because group
66    /// keys and aggregates define its output contract.
67    #[must_use]
68    pub fn select<I, S>(mut self, fields: I) -> Self
69    where
70        I: IntoIterator<Item = S>,
71        S: Into<String>,
72    {
73        self.fields = fields.into_iter().map(Into::into).collect();
74        self
75    }
76
77    /// Limit the number of returned rows.
78    #[must_use]
79    pub const fn limit(mut self, limit: u32) -> Self {
80        self.limit = Some(limit);
81        self
82    }
83
84    /// Append one accepted field to the grouped key in declaration order.
85    #[must_use]
86    pub fn group_by(mut self, field: impl Into<String>) -> Self {
87        self.group_fields.push(field.into());
88        self
89    }
90
91    /// Append one grouped aggregate in declaration order.
92    #[must_use]
93    pub fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
94        self.aggregates.push(aggregate);
95        self
96    }
97
98    /// Set explicit hard limits for grouped execution.
99    ///
100    /// Ordinary public reads additionally enforce their built-in admission
101    /// ceilings. Zero values are rejected before execution.
102    #[must_use]
103    pub const fn grouped_limits(mut self, max_groups: u32, max_group_bytes: u32) -> Self {
104        self.grouped_limits = Some((max_groups, max_group_bytes));
105        self
106    }
107
108    /// Continue a grouped page from one opaque cursor returned by IcyDB.
109    #[must_use]
110    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
111        self.cursor = Some(cursor.into());
112        self
113    }
114
115    pub(in crate::db) const fn entity(&self) -> &str {
116        self.entity.as_str()
117    }
118
119    pub(in crate::db) const fn filter_expr(&self) -> Option<&FilterExpr> {
120        self.filter.as_ref()
121    }
122
123    pub(in crate::db) const fn order_terms(&self) -> &[OrderTerm] {
124        self.order.as_slice()
125    }
126
127    pub(in crate::db) const fn selected_fields(&self) -> &[String] {
128        self.fields.as_slice()
129    }
130
131    pub(in crate::db) const fn row_limit(&self) -> Option<u32> {
132        self.limit
133    }
134
135    pub(in crate::db) const fn has_grouping(&self) -> bool {
136        !self.group_fields.is_empty() || !self.aggregates.is_empty()
137    }
138
139    pub(in crate::db) const fn group_fields(&self) -> &[String] {
140        self.group_fields.as_slice()
141    }
142
143    pub(in crate::db) const fn aggregates(&self) -> &[AggregateExpr] {
144        self.aggregates.as_slice()
145    }
146
147    pub(in crate::db) const fn grouped_execution_limits(&self) -> Option<(u32, u32)> {
148        self.grouped_limits
149    }
150
151    pub(in crate::db) fn continuation_cursor(&self) -> Option<&str> {
152        self.cursor.as_deref()
153    }
154}