icydb_core/db/query/
dynamic.rs1mod cleanup;
7
8use crate::db::query::{
9 builder::AggregateExpr,
10 expr::{FilterExpr, JunctionOperator, OrderTerm},
11};
12
13#[derive(Clone, Debug)]
26pub struct DynamicQuery {
27 entity: String,
28 filter: Option<FilterExpr>,
29 order: Vec<OrderTerm>,
30 fields: Vec<String>,
31 #[cfg(test)]
32 distinct: bool,
33 limit: Option<u32>,
34 group_fields: Vec<String>,
35 aggregates: Vec<AggregateExpr>,
36 grouped_limits: Option<(u32, u32)>,
37 cursor: Option<String>,
38}
39
40impl DynamicQuery {
41 #[must_use]
43 pub fn new(entity: impl Into<String>) -> Self {
44 Self {
45 entity: entity.into(),
46 filter: None,
47 order: Vec::new(),
48 fields: Vec::new(),
49 #[cfg(test)]
50 distinct: false,
51 limit: None,
52 group_fields: Vec::new(),
53 aggregates: Vec::new(),
54 grouped_limits: None,
55 cursor: None,
56 }
57 }
58
59 #[must_use]
61 pub fn filter(mut self, filter: impl Into<FilterExpr>) -> Self {
62 let appended = filter.into();
63 self.filter = Some(match self.filter.take() {
64 Some(FilterExpr::Junction {
65 operator: JunctionOperator::And,
66 mut filters,
67 }) => {
68 filters.push(appended);
69 FilterExpr::and(filters)
70 }
71 Some(existing) => FilterExpr::and(vec![existing, appended]),
72 None => appended,
73 });
74 self
75 }
76
77 #[must_use]
79 pub fn order_by(mut self, order: OrderTerm) -> Self {
80 self.order.push(order);
81 self
82 }
83
84 #[must_use]
89 pub fn select<I, S>(mut self, fields: I) -> Self
90 where
91 I: IntoIterator<Item = S>,
92 S: Into<String>,
93 {
94 self.fields = fields.into_iter().map(Into::into).collect();
95 self
96 }
97
98 #[must_use]
100 pub const fn limit(mut self, limit: u32) -> Self {
101 self.limit = Some(limit);
102 self
103 }
104
105 #[cfg(test)]
111 #[must_use]
112 pub(in crate::db) const fn distinct_for_internal_execution(mut self) -> Self {
113 self.distinct = true;
114 self
115 }
116
117 #[must_use]
119 pub fn group_by(mut self, field: impl Into<String>) -> Self {
120 self.group_fields.push(field.into());
121 self
122 }
123
124 #[must_use]
126 pub fn aggregate(mut self, aggregate: AggregateExpr) -> Self {
127 self.aggregates.push(aggregate);
128 self
129 }
130
131 #[must_use]
136 pub const fn grouped_limits(mut self, max_groups: u32, max_group_bytes: u32) -> Self {
137 self.grouped_limits = Some((max_groups, max_group_bytes));
138 self
139 }
140
141 #[must_use]
143 pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
144 self.cursor = Some(cursor.into());
145 self
146 }
147
148 pub(in crate::db) const fn entity(&self) -> &str {
149 self.entity.as_str()
150 }
151
152 pub(in crate::db) const fn filter_expr(&self) -> Option<&FilterExpr> {
153 self.filter.as_ref()
154 }
155
156 pub(in crate::db) const fn order_terms(&self) -> &[OrderTerm] {
157 self.order.as_slice()
158 }
159
160 pub(in crate::db) const fn selected_fields(&self) -> &[String] {
161 self.fields.as_slice()
162 }
163
164 pub(in crate::db) const fn row_limit(&self) -> Option<u32> {
165 self.limit
166 }
167
168 #[cfg(test)]
169 pub(in crate::db) const fn projection_is_distinct(&self) -> bool {
170 self.distinct
171 }
172
173 pub(in crate::db) const fn has_grouping(&self) -> bool {
174 !self.group_fields.is_empty() || !self.aggregates.is_empty()
175 }
176
177 pub(in crate::db) const fn group_fields(&self) -> &[String] {
178 self.group_fields.as_slice()
179 }
180
181 pub(in crate::db) const fn aggregates(&self) -> &[AggregateExpr] {
182 self.aggregates.as_slice()
183 }
184
185 pub(in crate::db) const fn grouped_execution_limits(&self) -> Option<(u32, u32)> {
186 self.grouped_limits
187 }
188
189 pub(in crate::db) fn continuation_cursor(&self) -> Option<&str> {
190 self.cursor.as_deref()
191 }
192}
193
194impl Drop for DynamicQuery {
195 fn drop(&mut self) {
196 cleanup::clear(self);
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::{DynamicQuery, FilterExpr};
204
205 #[test]
206 fn repeated_filters_accumulate_as_one_conjunction() {
207 let owner = FilterExpr::eq("owner_id", 1_u64);
208 let slot = FilterExpr::eq("slot", 2_u64);
209 let query = DynamicQuery::new("InventoryStack")
210 .filter(owner.clone())
211 .filter(slot.clone());
212
213 assert_eq!(
214 query.filter_expr(),
215 Some(&FilterExpr::and(vec![owner, slot]))
216 );
217 }
218}