1use std::collections::BTreeMap;
2
3use crate::{Expr, Value};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SortDirection {
7 Asc,
8 Desc,
9}
10
11#[cfg(test)]
12mod hard_limit_tests {
13 use super::*;
14
15 #[test]
16 fn list_limit_defaults_rejects_and_allows_explicit_override() {
17 assert_eq!(
18 SelectQuery::new("Order")
19 .prepare_for_list()
20 .unwrap()
21 .slice
22 .unwrap()
23 .limit,
24 Some(10_000)
25 );
26 assert!(
27 SelectQuery::new("Order")
28 .limit(10_001)
29 .prepare_for_list()
30 .is_err()
31 );
32 assert!(
33 SelectQuery::new("Order")
34 .limit(10_001)
35 .hard_limit(20_000)
36 .prepare_for_list()
37 .is_ok()
38 );
39 }
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub struct NamedExpr {
44 pub alias: String,
45 pub expr: Expr,
46}
47
48impl NamedExpr {
49 pub fn new(alias: impl Into<String>, expr: Expr) -> Self {
50 Self {
51 alias: alias.into(),
52 expr,
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq)]
58pub struct OrderBy {
59 pub field: String,
60 pub expr: Option<Expr>,
61 pub direction: SortDirection,
62}
63
64impl OrderBy {
65 pub fn new(field: impl Into<String>, direction: SortDirection) -> Self {
66 Self {
67 field: field.into(),
68 expr: None,
69 direction,
70 }
71 }
72
73 pub fn expr(expr: Expr, direction: SortDirection) -> Self {
74 Self {
75 field: String::new(),
76 expr: Some(expr),
77 direction,
78 }
79 }
80
81 pub fn asc(field: impl Into<String>) -> Self {
82 Self::new(field, SortDirection::Asc)
83 }
84
85 pub fn desc(field: impl Into<String>) -> Self {
86 Self::new(field, SortDirection::Desc)
87 }
88
89 pub fn asc_expr(expr: Expr) -> Self {
90 Self::expr(expr, SortDirection::Asc)
91 }
92
93 pub fn desc_expr(expr: Expr) -> Self {
94 Self::expr(expr, SortDirection::Desc)
95 }
96
97 pub fn asc_gbk(field: impl Into<String>) -> Self {
98 Self::asc_expr(Expr::gbk(Expr::column(field)))
99 }
100
101 pub fn desc_gbk(field: impl Into<String>) -> Self {
102 Self::desc_expr(Expr::gbk(Expr::column(field)))
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum AggregateFunction {
108 Count,
109 Sum,
110 Avg,
111 Min,
112 Max,
113 Stddev,
114 StddevPop,
115 VarSamp,
116 VarPop,
117 BitAnd,
118 BitOr,
119 BitXor,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct Aggregate {
124 pub function: AggregateFunction,
125 pub field: String,
126 pub alias: String,
127}
128
129impl Aggregate {
130 pub fn new(
131 function: AggregateFunction,
132 field: impl Into<String>,
133 alias: impl Into<String>,
134 ) -> Self {
135 Self {
136 function,
137 field: field.into(),
138 alias: alias.into(),
139 }
140 }
141
142 pub fn count(alias: impl Into<String>) -> Self {
143 Self::new(AggregateFunction::Count, "*", alias)
144 }
145
146 pub fn count_field(field: impl Into<String>, alias: impl Into<String>) -> Self {
147 Self::new(AggregateFunction::Count, field, alias)
148 }
149
150 pub fn sum(field: impl Into<String>, alias: impl Into<String>) -> Self {
151 Self::new(AggregateFunction::Sum, field, alias)
152 }
153
154 pub fn avg(field: impl Into<String>, alias: impl Into<String>) -> Self {
155 Self::new(AggregateFunction::Avg, field, alias)
156 }
157
158 pub fn min(field: impl Into<String>, alias: impl Into<String>) -> Self {
159 Self::new(AggregateFunction::Min, field, alias)
160 }
161
162 pub fn max(field: impl Into<String>, alias: impl Into<String>) -> Self {
163 Self::new(AggregateFunction::Max, field, alias)
164 }
165
166 pub fn stddev(field: impl Into<String>, alias: impl Into<String>) -> Self {
167 Self::new(AggregateFunction::Stddev, field, alias)
168 }
169
170 pub fn stddev_pop(field: impl Into<String>, alias: impl Into<String>) -> Self {
171 Self::new(AggregateFunction::StddevPop, field, alias)
172 }
173
174 pub fn var_samp(field: impl Into<String>, alias: impl Into<String>) -> Self {
175 Self::new(AggregateFunction::VarSamp, field, alias)
176 }
177
178 pub fn var_pop(field: impl Into<String>, alias: impl Into<String>) -> Self {
179 Self::new(AggregateFunction::VarPop, field, alias)
180 }
181
182 pub fn bit_and(field: impl Into<String>, alias: impl Into<String>) -> Self {
183 Self::new(AggregateFunction::BitAnd, field, alias)
184 }
185
186 pub fn bit_or(field: impl Into<String>, alias: impl Into<String>) -> Self {
187 Self::new(AggregateFunction::BitOr, field, alias)
188 }
189
190 pub fn bit_xor(field: impl Into<String>, alias: impl Into<String>) -> Self {
191 Self::new(AggregateFunction::BitXor, field, alias)
192 }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct Slice {
197 pub limit: Option<u64>,
198 pub offset: u64,
199}
200
201#[derive(Debug, Clone, PartialEq)]
202pub struct RelationLoad {
203 pub name: String,
204 pub query: Option<Box<SelectQuery>>,
205}
206
207impl RelationLoad {
208 pub fn new(name: impl Into<String>) -> Self {
209 Self {
210 name: name.into(),
211 query: None,
212 }
213 }
214
215 pub fn with_query(name: impl Into<String>, query: SelectQuery) -> Self {
216 Self {
217 name: name.into(),
218 query: Some(Box::new(query)),
219 }
220 }
221}
222
223#[derive(Debug, Clone, PartialEq)]
224pub struct RelationAggregate {
225 pub relation_name: String,
226 pub alias: String,
227 pub query: SelectQuery,
228 pub single_result: bool,
229}
230
231impl RelationAggregate {
232 pub fn new(
233 relation_name: impl Into<String>,
234 alias: impl Into<String>,
235 query: SelectQuery,
236 single_result: bool,
237 ) -> Self {
238 Self {
239 relation_name: relation_name.into(),
240 alias: alias.into(),
241 query,
242 single_result,
243 }
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, Eq)]
248pub struct RawSqlProjection {
249 pub property_name: String,
250 pub raw_sql_segment: String,
251}
252
253impl RawSqlProjection {
254 pub fn new(property_name: impl Into<String>, raw_sql_segment: impl Into<String>) -> Self {
255 Self {
256 property_name: property_name.into(),
257 raw_sql_segment: raw_sql_segment.into(),
258 }
259 }
260}
261
262#[derive(Debug, Clone, PartialEq)]
263pub struct ObjectGroupBy {
264 pub property_name: String,
265 pub storage_field: String,
266 pub query: SelectQuery,
267}
268
269impl ObjectGroupBy {
270 pub fn new(
271 property_name: impl Into<String>,
272 storage_field: impl Into<String>,
273 query: SelectQuery,
274 ) -> Self {
275 Self {
276 property_name: property_name.into(),
277 storage_field: storage_field.into(),
278 query,
279 }
280 }
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub struct AggregationCacheOptions {
285 pub enabled: bool,
286 pub cache_expired_millis: u64,
287 pub propagate: bool,
288 pub propagate_cache_expired_millis: u64,
289}
290
291impl AggregationCacheOptions {
292 pub fn enabled(cache_expired_millis: u64) -> Self {
293 Self {
294 enabled: true,
295 cache_expired_millis,
296 propagate: false,
297 propagate_cache_expired_millis: 0,
298 }
299 }
300
301 pub fn propagate(mut self, cache_expired_millis: u64) -> Self {
302 self.propagate = true;
303 self.propagate_cache_expired_millis = cache_expired_millis;
304 self
305 }
306}
307
308#[derive(Debug, Clone, PartialEq)]
309pub struct StreamConfig {
310 pub chunk_size: usize,
311}
312
313impl Default for StreamConfig {
314 fn default() -> Self {
315 Self { chunk_size: 1000 }
316 }
317}
318
319#[derive(Debug, Clone, PartialEq)]
320pub struct SelectQuery {
321 pub hard_limit: u64,
323 pub entity: String,
324 pub projection: Vec<String>,
325 pub expr_projection: Vec<NamedExpr>,
326 pub search_with_text: Option<String>,
327 pub filter: Option<Expr>,
328 pub having: Option<Expr>,
329 pub order_by: Vec<OrderBy>,
330 pub slice: Option<Slice>,
331 pub partition_by: Option<String>,
333 pub aggregates: Vec<Aggregate>,
334 pub group_by: Vec<String>,
335 pub relations: Vec<RelationLoad>,
336 pub aggregation_cache: Option<AggregationCacheOptions>,
337 pub comment: Option<String>,
338 pub trace_chain: Vec<crate::TraceNode>,
339 pub raw_sql: Option<String>,
340 pub raw_sql_search_criteria: Vec<String>,
341 pub dynamic_properties: Vec<RawSqlProjection>,
342 pub raw_projections: Vec<RawSqlProjection>,
343 pub object_group_bys: Vec<ObjectGroupBy>,
344 pub child_enhancements: Vec<SelectQuery>,
345 pub stream_config: Option<StreamConfig>,
346}
347
348impl SelectQuery {
349 pub fn new(entity: impl Into<String>) -> Self {
350 Self {
351 hard_limit: 10_000,
352 entity: entity.into(),
353 projection: Vec::new(),
354 expr_projection: Vec::new(),
355 search_with_text: None,
356 filter: None,
357 having: None,
358 order_by: Vec::new(),
359 slice: None,
360 partition_by: None,
361 aggregates: Vec::new(),
362 group_by: Vec::new(),
363 relations: Vec::new(),
364 aggregation_cache: None,
365 comment: None,
366 trace_chain: Vec::new(),
367 raw_sql: None,
368 raw_sql_search_criteria: Vec::new(),
369 dynamic_properties: Vec::new(),
370 raw_projections: Vec::new(),
371 object_group_bys: Vec::new(),
372 child_enhancements: Vec::new(),
373 stream_config: None,
374 }
375 }
376
377 pub fn project(mut self, field: impl Into<String>) -> Self {
378 self.projection.push(field.into());
379 self
380 }
381
382 pub fn projects(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
383 self.projection.extend(fields.into_iter().map(Into::into));
384 self
385 }
386
387 pub fn project_expr(mut self, alias: impl Into<String>, expr: Expr) -> Self {
388 self.expr_projection.push(NamedExpr::new(alias, expr));
389 self
390 }
391
392 pub fn project_raw(
393 mut self,
394 alias: impl Into<String>,
395 raw_sql_segment: impl Into<String>,
396 ) -> Self {
397 self.raw_projections
398 .push(RawSqlProjection::new(alias, raw_sql_segment));
399 self
400 }
401
402 pub fn dynamic_property_raw(
403 mut self,
404 alias: impl Into<String>,
405 raw_sql_segment: impl Into<String>,
406 ) -> Self {
407 self.dynamic_properties
408 .push(RawSqlProjection::new(alias, raw_sql_segment));
409 self
410 }
411
412 pub fn search_with_text(mut self, text: impl Into<String>) -> Self {
413 self.search_with_text = Some(text.into());
414 self
415 }
416
417 pub fn filter(mut self, filter: Expr) -> Self {
418 self.filter = Some(filter);
419 self
420 }
421
422 pub fn and_filter(mut self, filter: Expr) -> Self {
423 self.filter = Some(match self.filter.take() {
424 Some(existing) => existing.and_expr(filter),
425 None => filter,
426 });
427 self
428 }
429
430 pub fn or_filter(mut self, filter: Expr) -> Self {
431 self.filter = Some(match self.filter.take() {
432 Some(existing) => existing.or_expr(filter),
433 None => filter,
434 });
435 self
436 }
437
438 pub fn having(mut self, having: Expr) -> Self {
439 self.having = Some(having);
440 self
441 }
442
443 pub fn and_having(mut self, having: Expr) -> Self {
444 self.having = Some(match self.having.take() {
445 Some(existing) => existing.and_expr(having),
446 None => having,
447 });
448 self
449 }
450
451 pub fn or_having(mut self, having: Expr) -> Self {
452 self.having = Some(match self.having.take() {
453 Some(existing) => existing.or_expr(having),
454 None => having,
455 });
456 self
457 }
458
459 pub fn order_by(mut self, order: OrderBy) -> Self {
460 self.order_by.push(order);
461 self
462 }
463
464 pub fn order_asc(self, field: impl Into<String>) -> Self {
465 self.order_by(OrderBy::asc(field))
466 }
467
468 pub fn order_desc(self, field: impl Into<String>) -> Self {
469 self.order_by(OrderBy::desc(field))
470 }
471
472 pub fn order_expr_asc(self, expr: Expr) -> Self {
473 self.order_by(OrderBy::asc_expr(expr))
474 }
475
476 pub fn order_expr_desc(self, expr: Expr) -> Self {
477 self.order_by(OrderBy::desc_expr(expr))
478 }
479
480 pub fn order_gbk_asc(self, field: impl Into<String>) -> Self {
481 self.order_by(OrderBy::asc_gbk(field))
482 }
483
484 pub fn order_gbk_desc(self, field: impl Into<String>) -> Self {
485 self.order_by(OrderBy::desc_gbk(field))
486 }
487
488 pub fn group_by(mut self, field: impl Into<String>) -> Self {
489 self.group_by.push(field.into());
490 self
491 }
492
493 pub fn aggregate(mut self, aggregate: Aggregate) -> Self {
494 self.aggregates.push(aggregate);
495 self
496 }
497
498 pub fn count(self, alias: impl Into<String>) -> Self {
499 self.aggregate(Aggregate::count(alias))
500 }
501
502 pub fn count_field(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
503 self.aggregate(Aggregate::count_field(field, alias))
504 }
505
506 pub fn sum(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
507 self.aggregate(Aggregate::sum(field, alias))
508 }
509
510 pub fn avg(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
511 self.aggregate(Aggregate::avg(field, alias))
512 }
513
514 pub fn min(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
515 self.aggregate(Aggregate::min(field, alias))
516 }
517
518 pub fn max(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
519 self.aggregate(Aggregate::max(field, alias))
520 }
521
522 pub fn stddev(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
523 self.aggregate(Aggregate::stddev(field, alias))
524 }
525
526 pub fn stddev_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
527 self.aggregate(Aggregate::stddev_pop(field, alias))
528 }
529
530 pub fn var_samp(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
531 self.aggregate(Aggregate::var_samp(field, alias))
532 }
533
534 pub fn var_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
535 self.aggregate(Aggregate::var_pop(field, alias))
536 }
537
538 pub fn bit_and(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
539 self.aggregate(Aggregate::bit_and(field, alias))
540 }
541
542 pub fn bit_or(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
543 self.aggregate(Aggregate::bit_or(field, alias))
544 }
545
546 pub fn bit_xor(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
547 self.aggregate(Aggregate::bit_xor(field, alias))
548 }
549
550 pub fn enable_aggregation_cache(self) -> Self {
551 self.enable_aggregation_cache_for(0)
552 }
553
554 pub fn enable_aggregation_cache_for(mut self, cache_expired_millis: u64) -> Self {
555 self.aggregation_cache = Some(AggregationCacheOptions::enabled(cache_expired_millis));
556 self
557 }
558
559 pub fn propagate_aggregation_cache(mut self, cache_expired_millis: u64) -> Self {
560 self.aggregation_cache = Some(
561 self.aggregation_cache
562 .unwrap_or_else(|| AggregationCacheOptions::enabled(0))
563 .propagate(cache_expired_millis),
564 );
565 self
566 }
567
568 pub fn comment(mut self, comment: impl Into<String>) -> Self {
569 let comment_str = comment.into();
570 self.comment = Some(comment_str.clone());
571 self.trace_chain.push(crate::TraceNode {
572 entity_type: self.entity.clone(),
573 entity_id: None,
574 comment: comment_str,
575 });
576 self
577 }
578
579 pub fn raw_sql(mut self, raw_sql: impl Into<String>) -> Self {
580 self.raw_sql = Some(raw_sql.into());
581 self
582 }
583
584 pub fn raw_sql_search_criteria(mut self, raw_sql: impl Into<String>) -> Self {
585 self.raw_sql_search_criteria.push(raw_sql.into());
586 self
587 }
588
589 pub fn object_group_by(
590 mut self,
591 property_name: impl Into<String>,
592 storage_field: impl Into<String>,
593 query: SelectQuery,
594 ) -> Self {
595 self.object_group_bys
596 .push(ObjectGroupBy::new(property_name, storage_field, query));
597 self
598 }
599
600 pub fn child_enhancement(mut self, query: SelectQuery) -> Self {
601 self.child_enhancements.push(query);
602 self
603 }
604
605 pub fn relation(mut self, name: impl Into<String>) -> Self {
606 self.relations.push(RelationLoad::new(name));
607 self
608 }
609
610 pub fn relation_query(mut self, name: impl Into<String>, query: SelectQuery) -> Self {
611 self.relations.push(RelationLoad::with_query(name, query));
612 self
613 }
614
615 pub fn limit(mut self, limit: u64) -> Self {
616 let slice = self.slice.get_or_insert(Slice {
617 limit: None,
618 offset: 0,
619 });
620 slice.limit = Some(limit);
621 self
622 }
623
624 pub fn hard_limit(mut self, hard_limit: u64) -> Self {
626 assert!(hard_limit > 0, "hard_limit must be positive");
627 self.hard_limit = hard_limit;
628 self
629 }
630
631 pub fn prepare_for_list(mut self) -> Result<Self, String> {
634 self.apply_list_limit(self.hard_limit, true)?;
635 Ok(self)
636 }
637
638 fn apply_list_limit(&mut self, ceiling: u64, outer: bool) -> Result<(), String> {
639 let slice = self.slice.get_or_insert(Slice {
640 limit: None,
641 offset: 0,
642 });
643 match slice.limit {
644 Some(limit) if limit > ceiling => {
645 return Err(format!(
646 "QUERY_HARD_LIMIT_EXCEEDED: requested limit {limit} exceeds hard limit {ceiling}"
647 ));
648 }
649 None => slice.limit = Some(ceiling),
650 _ => {}
651 }
652 for relation in &mut self.relations {
653 if let Some(query) = relation.query.as_mut() {
654 query.apply_list_limit(10_000, false)?;
655 }
656 }
657 for query in &mut self.child_enhancements {
658 query.apply_list_limit(10_000, false)?;
659 }
660 let _ = outer;
661 Ok(())
662 }
663
664 pub fn offset(mut self, offset: u64) -> Self {
665 let slice = self.slice.get_or_insert(Slice {
666 limit: None,
667 offset: 0,
668 });
669 slice.offset = offset;
670 self
671 }
672
673 pub fn page(self, offset: u64, limit: u64) -> Self {
674 self.offset(offset).limit(limit)
675 }
676
677 pub fn partition_by(mut self, field: impl Into<String>) -> Self {
683 self.partition_by = Some(field.into());
684 self
685 }
686
687 pub fn stream(mut self, chunk_size: usize) -> Self {
690 self.stream_config = Some(StreamConfig { chunk_size });
691 self
692 }
693
694 pub fn stream_default(mut self) -> Self {
696 self.stream_config = Some(StreamConfig::default());
697 self
698 }
699}
700
701pub type Record = BTreeMap<String, Value>;
702
703pub const PARTITION_RANK_PROPERTY: &str = "__teaql_partition_rank";
706
707pub fn record_to_json_value(record: &Record) -> serde_json::Value {
708 serde_json::Value::Object(
709 record
710 .iter()
711 .map(|(key, value)| (key.clone(), value.to_json_value()))
712 .collect(),
713 )
714}