icydb_core/db/query/
dynamic.rs1use crate::db::query::{
7 builder::AggregateExpr,
8 expr::{FilterExpr, JunctionOperator, OrderTerm},
9};
10
11#[derive(Clone, Debug)]
20pub struct DynamicQuery {
21 entity: String,
22 filter: Option<FilterExpr>,
23 order: Vec<OrderTerm>,
24 fields: Vec<String>,
25 #[cfg(test)]
26 distinct: bool,
27 limit: Option<u32>,
28 group_fields: Vec<String>,
29 aggregates: Vec<AggregateExpr>,
30 grouped_limits: Option<(u32, u32)>,
31 cursor: Option<String>,
32}
33
34impl DynamicQuery {
35 #[must_use]
37 pub fn new(entity: impl Into<String>) -> Self {
38 Self {
39 entity: entity.into(),
40 filter: None,
41 order: Vec::new(),
42 fields: Vec::new(),
43 #[cfg(test)]
44 distinct: false,
45 limit: None,
46 group_fields: Vec::new(),
47 aggregates: Vec::new(),
48 grouped_limits: None,
49 cursor: None,
50 }
51 }
52
53 #[must_use]
55 pub fn filter(mut self, filter: impl Into<FilterExpr>) -> Self {
56 let appended = filter.into();
57 self.filter = Some(match self.filter.take() {
58 Some(FilterExpr::Junction {
59 operator: JunctionOperator::And,
60 mut filters,
61 }) => {
62 filters.push(appended);
63 FilterExpr::and(filters)
64 }
65 Some(existing) => FilterExpr::and(vec![existing, appended]),
66 None => appended,
67 });
68 self
69 }
70
71 #[must_use]
73 pub fn order_by(mut self, order: OrderTerm) -> Self {
74 self.order.push(order);
75 self
76 }
77
78 #[must_use]
83 pub fn select<I, S>(mut self, fields: I) -> Self
84 where
85 I: IntoIterator<Item = S>,
86 S: Into<String>,
87 {
88 self.fields = fields.into_iter().map(Into::into).collect();
89 self
90 }
91
92 #[must_use]
94 pub const fn limit(mut self, limit: u32) -> Self {
95 self.limit = Some(limit);
96 self
97 }
98
99 #[cfg(test)]
105 #[must_use]
106 pub(in crate::db) const fn distinct_for_internal_execution(mut self) -> Self {
107 self.distinct = true;
108 self
109 }
110
111 #[must_use]
113 pub fn group_by(mut self, field: impl Into<String>) -> Self {
114 self.group_fields.push(field.into());
115 self
116 }
117
118 #[must_use]
120 pub fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
121 self.aggregates.push(aggregate);
122 self
123 }
124
125 #[must_use]
130 pub const fn grouped_limits(mut self, max_groups: u32, max_group_bytes: u32) -> Self {
131 self.grouped_limits = Some((max_groups, max_group_bytes));
132 self
133 }
134
135 #[must_use]
137 pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
138 self.cursor = Some(cursor.into());
139 self
140 }
141
142 pub(in crate::db) const fn entity(&self) -> &str {
143 self.entity.as_str()
144 }
145
146 pub(in crate::db) const fn filter_expr(&self) -> Option<&FilterExpr> {
147 self.filter.as_ref()
148 }
149
150 pub(in crate::db) const fn order_terms(&self) -> &[OrderTerm] {
151 self.order.as_slice()
152 }
153
154 pub(in crate::db) const fn selected_fields(&self) -> &[String] {
155 self.fields.as_slice()
156 }
157
158 pub(in crate::db) const fn row_limit(&self) -> Option<u32> {
159 self.limit
160 }
161
162 #[cfg(test)]
163 pub(in crate::db) const fn projection_is_distinct(&self) -> bool {
164 self.distinct
165 }
166
167 pub(in crate::db) const fn has_grouping(&self) -> bool {
168 !self.group_fields.is_empty() || !self.aggregates.is_empty()
169 }
170
171 pub(in crate::db) const fn group_fields(&self) -> &[String] {
172 self.group_fields.as_slice()
173 }
174
175 pub(in crate::db) const fn aggregates(&self) -> &[AggregateExpr] {
176 self.aggregates.as_slice()
177 }
178
179 pub(in crate::db) const fn grouped_execution_limits(&self) -> Option<(u32, u32)> {
180 self.grouped_limits
181 }
182
183 pub(in crate::db) fn continuation_cursor(&self) -> Option<&str> {
184 self.cursor.as_deref()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::{DynamicQuery, FilterExpr};
191
192 #[test]
193 fn repeated_filters_accumulate_as_one_conjunction() {
194 let owner = FilterExpr::eq("owner_id", 1_u64);
195 let slot = FilterExpr::eq("slot", 2_u64);
196 let query = DynamicQuery::new("InventoryStack")
197 .filter(owner.clone())
198 .filter(slot.clone());
199
200 assert_eq!(
201 query.filter_expr(),
202 Some(&FilterExpr::and(vec![owner, slot]))
203 );
204 }
205}