1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use crate::{Expr, Value};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SortDirection {
8 Asc,
9 Desc,
10}
11
12#[cfg(test)]
13mod hard_limit_tests {
14 use super::*;
15
16 #[test]
17 fn list_limit_defaults_rejects_and_allows_explicit_override() {
18 assert_eq!(
19 SelectQuery::new("Order")
20 .prepare_for_list()
21 .unwrap()
22 .slice
23 .unwrap()
24 .limit,
25 Some(10_000)
26 );
27 assert!(
28 SelectQuery::new("Order")
29 .limit(10_001)
30 .prepare_for_list()
31 .is_err()
32 );
33 assert!(
34 SelectQuery::new("Order")
35 .limit(10_001)
36 .hard_limit(20_000)
37 .prepare_for_list()
38 .is_ok()
39 );
40 }
41
42 #[test]
43 fn continuous_page_fetch_is_explicit_and_validated() {
44 assert!(SelectQuery::new("Order").continuous_page_fetch.is_none());
45 let query =
46 SelectQuery::new("Order").optimize_for_continuous_page_fetch_with("recent-orders", 30);
47 let options = query.continuous_page_fetch.unwrap();
48 assert_eq!(options.namespace, "recent-orders");
49 assert_eq!(options.ttl_seconds, 30);
50 }
51
52 #[test]
53 #[should_panic(expected = "continuous page namespace must not be empty")]
54 fn continuous_page_fetch_rejects_empty_namespace() {
55 let _ = SelectQuery::new("Order").optimize_for_continuous_page_fetch_with(" ", 30);
56 }
57
58 #[test]
59 fn id_set_pagination_is_explicit_and_validated() {
60 assert!(SelectQuery::new("Order").id_set_pagination.is_none());
61 let query = SelectQuery::new("Order").optimize_pagination_with_id_set_config(
62 "recent-orders",
63 30,
64 5_000,
65 );
66 let options = query.id_set_pagination.expect("ID set options");
67 assert_eq!(options.namespace, "recent-orders");
68 assert_eq!(options.ttl_seconds, 30);
69 assert_eq!(options.max_ids, 5_000);
70 }
71
72 #[test]
73 #[should_panic(expected = "ID set pagination max_ids must be positive")]
74 fn id_set_pagination_rejects_zero_limit() {
75 let _ = SelectQuery::new("Order").optimize_pagination_with_id_set_config("orders", 30, 0);
76 }
77
78 #[test]
79 fn topn_001_002_003_probe_threshold_is_explicit_and_bounded() {
80 assert!(
81 SelectQuery::new("Trip")
82 .top_n_probe_parent_threshold
83 .is_none()
84 );
85 assert_eq!(
86 SelectQuery::new("Trip")
87 .top_n_probe_parent_threshold(32)
88 .top_n_probe_parent_threshold,
89 Some(32)
90 );
91 assert_eq!(
92 SelectQuery::new("Trip")
93 .top_n_probe_parent_threshold(0)
94 .top_n_probe_parent_threshold,
95 Some(0)
96 );
97 }
98}
99
100#[derive(Debug, Clone, PartialEq)]
101pub struct NamedExpr {
102 pub alias: String,
103 pub expr: Expr,
104}
105
106impl NamedExpr {
107 pub fn new(alias: impl Into<String>, expr: Expr) -> Self {
108 Self {
109 alias: alias.into(),
110 expr,
111 }
112 }
113}
114
115#[derive(Debug, Clone, PartialEq)]
116pub struct OrderBy {
117 pub field: String,
118 pub expr: Option<Expr>,
119 pub direction: SortDirection,
120}
121
122impl OrderBy {
123 pub fn new(field: impl Into<String>, direction: SortDirection) -> Self {
124 Self {
125 field: field.into(),
126 expr: None,
127 direction,
128 }
129 }
130
131 pub fn expr(expr: Expr, direction: SortDirection) -> Self {
132 Self {
133 field: String::new(),
134 expr: Some(expr),
135 direction,
136 }
137 }
138
139 pub fn asc(field: impl Into<String>) -> Self {
140 Self::new(field, SortDirection::Asc)
141 }
142
143 pub fn desc(field: impl Into<String>) -> Self {
144 Self::new(field, SortDirection::Desc)
145 }
146
147 pub fn asc_expr(expr: Expr) -> Self {
148 Self::expr(expr, SortDirection::Asc)
149 }
150
151 pub fn desc_expr(expr: Expr) -> Self {
152 Self::expr(expr, SortDirection::Desc)
153 }
154
155 pub fn asc_gbk(field: impl Into<String>) -> Self {
156 Self::asc_expr(Expr::gbk(Expr::column(field)))
157 }
158
159 pub fn desc_gbk(field: impl Into<String>) -> Self {
160 Self::desc_expr(Expr::gbk(Expr::column(field)))
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub enum AggregateFunction {
166 Count,
167 Sum,
168 Avg,
169 Min,
170 Max,
171 Stddev,
172 StddevPop,
173 VarSamp,
174 VarPop,
175 BitAnd,
176 BitOr,
177 BitXor,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct Aggregate {
182 pub function: AggregateFunction,
183 pub field: String,
184 pub alias: String,
185}
186
187impl Aggregate {
188 pub fn new(
189 function: AggregateFunction,
190 field: impl Into<String>,
191 alias: impl Into<String>,
192 ) -> Self {
193 Self {
194 function,
195 field: field.into(),
196 alias: alias.into(),
197 }
198 }
199
200 pub fn count(alias: impl Into<String>) -> Self {
201 Self::new(AggregateFunction::Count, "*", alias)
202 }
203
204 pub fn count_field(field: impl Into<String>, alias: impl Into<String>) -> Self {
205 Self::new(AggregateFunction::Count, field, alias)
206 }
207
208 pub fn sum(field: impl Into<String>, alias: impl Into<String>) -> Self {
209 Self::new(AggregateFunction::Sum, field, alias)
210 }
211
212 pub fn avg(field: impl Into<String>, alias: impl Into<String>) -> Self {
213 Self::new(AggregateFunction::Avg, field, alias)
214 }
215
216 pub fn min(field: impl Into<String>, alias: impl Into<String>) -> Self {
217 Self::new(AggregateFunction::Min, field, alias)
218 }
219
220 pub fn max(field: impl Into<String>, alias: impl Into<String>) -> Self {
221 Self::new(AggregateFunction::Max, field, alias)
222 }
223
224 pub fn stddev(field: impl Into<String>, alias: impl Into<String>) -> Self {
225 Self::new(AggregateFunction::Stddev, field, alias)
226 }
227
228 pub fn stddev_pop(field: impl Into<String>, alias: impl Into<String>) -> Self {
229 Self::new(AggregateFunction::StddevPop, field, alias)
230 }
231
232 pub fn var_samp(field: impl Into<String>, alias: impl Into<String>) -> Self {
233 Self::new(AggregateFunction::VarSamp, field, alias)
234 }
235
236 pub fn var_pop(field: impl Into<String>, alias: impl Into<String>) -> Self {
237 Self::new(AggregateFunction::VarPop, field, alias)
238 }
239
240 pub fn bit_and(field: impl Into<String>, alias: impl Into<String>) -> Self {
241 Self::new(AggregateFunction::BitAnd, field, alias)
242 }
243
244 pub fn bit_or(field: impl Into<String>, alias: impl Into<String>) -> Self {
245 Self::new(AggregateFunction::BitOr, field, alias)
246 }
247
248 pub fn bit_xor(field: impl Into<String>, alias: impl Into<String>) -> Self {
249 Self::new(AggregateFunction::BitXor, field, alias)
250 }
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254pub struct Slice {
255 pub limit: Option<u64>,
256 pub offset: u64,
257}
258
259#[derive(Debug, Clone, PartialEq)]
260pub struct RelationLoad {
261 pub name: String,
262 pub query: Option<Box<SelectQuery>>,
263 pub operation: Option<crate::QueryOperation>,
264}
265
266impl RelationLoad {
267 pub fn new(name: impl Into<String>) -> Self {
268 Self {
269 name: name.into(),
270 query: None,
271 operation: None,
272 }
273 }
274
275 pub fn with_query(name: impl Into<String>, query: SelectQuery) -> Self {
276 Self {
277 name: name.into(),
278 query: Some(Box::new(query)),
279 operation: None,
280 }
281 }
282 pub fn with_operation(mut self, operation: crate::QueryOperation) -> Self {
283 self.operation = Some(operation);
284 self
285 }
286}
287
288#[derive(Debug, Clone, PartialEq)]
289pub struct RelationAggregate {
290 pub relation_name: String,
291 pub alias: String,
292 pub query: SelectQuery,
293 pub single_result: bool,
294 pub operation: Option<crate::QueryOperation>,
295}
296
297impl RelationAggregate {
298 pub fn new(
299 relation_name: impl Into<String>,
300 alias: impl Into<String>,
301 query: SelectQuery,
302 single_result: bool,
303 ) -> Self {
304 Self {
305 relation_name: relation_name.into(),
306 alias: alias.into(),
307 query,
308 single_result,
309 operation: None,
310 }
311 }
312 pub fn with_operation(mut self, operation: crate::QueryOperation) -> Self {
313 self.operation = Some(operation);
314 self
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct RawSqlProjection {
320 pub property_name: String,
321 pub raw_sql_segment: String,
322}
323
324impl RawSqlProjection {
325 pub fn new(property_name: impl Into<String>, raw_sql_segment: impl Into<String>) -> Self {
326 Self {
327 property_name: property_name.into(),
328 raw_sql_segment: raw_sql_segment.into(),
329 }
330 }
331}
332
333#[derive(Debug, Clone, PartialEq)]
334pub struct ObjectGroupBy {
335 pub property_name: String,
336 pub storage_field: String,
337 pub query: SelectQuery,
338}
339
340impl ObjectGroupBy {
341 pub fn new(
342 property_name: impl Into<String>,
343 storage_field: impl Into<String>,
344 query: SelectQuery,
345 ) -> Self {
346 Self {
347 property_name: property_name.into(),
348 storage_field: storage_field.into(),
349 query,
350 }
351 }
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub struct AggregationCacheOptions {
356 pub enabled: bool,
357 pub cache_expired_millis: u64,
358 pub propagate: bool,
359 pub propagate_cache_expired_millis: u64,
360}
361
362impl AggregationCacheOptions {
363 pub fn enabled(cache_expired_millis: u64) -> Self {
364 Self {
365 enabled: true,
366 cache_expired_millis,
367 propagate: false,
368 propagate_cache_expired_millis: 0,
369 }
370 }
371
372 pub fn propagate(mut self, cache_expired_millis: u64) -> Self {
373 self.propagate = true;
374 self.propagate_cache_expired_millis = cache_expired_millis;
375 self
376 }
377}
378
379#[derive(Debug, Clone, PartialEq)]
380pub struct StreamConfig {
381 pub chunk_size: usize,
382}
383
384#[derive(Debug, Clone, PartialEq, Eq)]
385pub struct ContinuousPageFetchOptions {
386 pub namespace: String,
387 pub ttl_seconds: u64,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
391pub struct IdSetPaginationOptions {
392 pub namespace: String,
393 pub ttl_seconds: u64,
394 pub max_ids: u64,
395}
396
397impl IdSetPaginationOptions {
398 pub const DEFAULT_TTL_SECONDS: u64 = 600;
399 pub const DEFAULT_MAX_IDS: u64 = 3_000_000;
400
401 pub fn new(namespace: impl Into<String>, ttl_seconds: u64, max_ids: u64) -> Self {
402 let namespace = namespace.into();
403 assert!(
404 !namespace.trim().is_empty(),
405 "ID set pagination namespace must not be empty"
406 );
407 assert!(
408 ttl_seconds > 0,
409 "ID set pagination ttl_seconds must be positive"
410 );
411 assert!(max_ids > 0, "ID set pagination max_ids must be positive");
412 Self {
413 namespace,
414 ttl_seconds,
415 max_ids,
416 }
417 }
418}
419
420impl ContinuousPageFetchOptions {
421 pub const DEFAULT_TTL_SECONDS: u64 = 600;
422
423 pub fn new(namespace: impl Into<String>, ttl_seconds: u64) -> Self {
424 let namespace = namespace.into();
425 assert!(
426 !namespace.trim().is_empty(),
427 "continuous page namespace must not be empty"
428 );
429 assert!(
430 ttl_seconds > 0,
431 "continuous page ttl_seconds must be positive"
432 );
433 Self {
434 namespace,
435 ttl_seconds,
436 }
437 }
438}
439
440impl Default for StreamConfig {
441 fn default() -> Self {
442 Self { chunk_size: 1000 }
443 }
444}
445
446#[derive(Debug, Clone, PartialEq)]
447pub struct SelectQuery {
448 request_scope: Option<crate::QueryRequestScope>,
450 pub hard_limit: u64,
452 pub entity: String,
453 pub projection: Vec<String>,
454 pub expr_projection: Vec<NamedExpr>,
455 pub search_with_text: Option<String>,
456 pub filter: Option<Expr>,
457 pub having: Option<Expr>,
458 pub order_by: Vec<OrderBy>,
459 pub slice: Option<Slice>,
460 pub partition_by: Option<String>,
462 pub top_n_probe_parent_threshold: Option<usize>,
466 pub aggregates: Vec<Aggregate>,
467 pub group_by: Vec<String>,
468 pub relations: Vec<RelationLoad>,
469 pub facets: Vec<crate::request::FacetRequest>,
472 pub relation_aggregates: Vec<RelationAggregate>,
475 pub aggregation_cache: Option<AggregationCacheOptions>,
476 pub comment: Option<String>,
477 pub purpose: Option<String>,
479 pub trace_chain: Vec<crate::TraceNode>,
480 pub raw_sql: Option<String>,
481 pub raw_sql_search_criteria: Vec<String>,
482 pub dynamic_properties: Vec<RawSqlProjection>,
483 pub dynamic_property_definitions:
485 Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
486 pub dynamic_field_selection:
488 Option<std::sync::Arc<crate::dynamic_fields::DynamicFieldSelection>>,
489 pub raw_projections: Vec<RawSqlProjection>,
490 pub object_group_bys: Vec<ObjectGroupBy>,
491 pub child_enhancements: Vec<SelectQuery>,
492 pub stream_config: Option<StreamConfig>,
493 pub continuous_page_fetch: Option<ContinuousPageFetchOptions>,
495 pub id_set_pagination: Option<IdSetPaginationOptions>,
497}
498
499impl SelectQuery {
500 pub fn new(entity: impl Into<String>) -> Self {
501 Self {
502 hard_limit: 10_000,
503 request_scope: None,
504 entity: entity.into(),
505 projection: Vec::new(),
506 expr_projection: Vec::new(),
507 search_with_text: None,
508 filter: None,
509 having: None,
510 order_by: Vec::new(),
511 slice: None,
512 partition_by: None,
513 top_n_probe_parent_threshold: None,
514 aggregates: Vec::new(),
515 group_by: Vec::new(),
516 relations: Vec::new(),
517 facets: Vec::new(),
518 relation_aggregates: Vec::new(),
519 aggregation_cache: None,
520 comment: None,
521 purpose: None,
522 trace_chain: Vec::new(),
523 raw_sql: None,
524 raw_sql_search_criteria: Vec::new(),
525 dynamic_properties: Vec::new(),
526 dynamic_property_definitions: None,
527 dynamic_field_selection: None,
528 raw_projections: Vec::new(),
529 object_group_bys: Vec::new(),
530 child_enhancements: Vec::new(),
531 stream_config: None,
532 continuous_page_fetch: None,
533 id_set_pagination: None,
534 }
535 }
536
537 pub fn request_scope(&self) -> Option<&crate::QueryRequestScope> {
538 self.request_scope.as_ref()
539 }
540
541 #[doc(hidden)]
542 pub fn with_request_scope(mut self, scope: crate::QueryRequestScope) -> Self {
543 self.comment = Some(scope.intent().comment().to_owned());
544 self.purpose = Some(scope.purpose().to_owned());
545 self.request_scope = Some(scope);
546 self
547 }
548
549 #[doc(hidden)]
550 pub fn clear_request_scope(&mut self) {
551 self.request_scope = None;
552 }
553
554 pub fn project(mut self, field: impl Into<String>) -> Self {
555 self.projection.push(field.into());
556 self
557 }
558
559 pub fn select_dynamic_fields(
560 mut self,
561 selection: crate::dynamic_fields::DynamicFieldSelection,
562 ) -> Self {
563 self.dynamic_field_selection = Some(std::sync::Arc::new(selection));
564 self
565 }
566
567 pub fn projects(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
568 self.projection.extend(fields.into_iter().map(Into::into));
569 self
570 }
571
572 pub fn project_expr(mut self, alias: impl Into<String>, expr: Expr) -> Self {
573 self.expr_projection.push(NamedExpr::new(alias, expr));
574 self
575 }
576
577 pub fn project_raw(
578 mut self,
579 alias: impl Into<String>,
580 raw_sql_segment: impl Into<String>,
581 ) -> Self {
582 self.raw_projections
583 .push(RawSqlProjection::new(alias, raw_sql_segment));
584 self
585 }
586
587 pub fn dynamic_property_raw(
588 mut self,
589 alias: impl Into<String>,
590 raw_sql_segment: impl Into<String>,
591 ) -> Self {
592 self.dynamic_properties
593 .push(RawSqlProjection::new(alias, raw_sql_segment));
594 self
595 }
596
597 pub fn with_dynamic_property_definitions(
598 mut self,
599 definitions: Arc<crate::dynamic_properties::DynamicPropertyDefinitions>,
600 ) -> Self {
601 self.dynamic_property_definitions = Some(definitions);
602 self
603 }
604
605 pub fn search_with_text(mut self, text: impl Into<String>) -> Self {
606 self.search_with_text = Some(text.into());
607 self
608 }
609
610 pub fn filter(mut self, filter: Expr) -> Self {
611 self.filter = Some(filter);
612 self
613 }
614
615 pub fn and_filter(mut self, filter: Expr) -> Self {
616 self.filter = Some(match self.filter.take() {
617 Some(existing) => existing.and_expr(filter),
618 None => filter,
619 });
620 self
621 }
622
623 pub fn or_filter(mut self, filter: Expr) -> Self {
624 self.filter = Some(match self.filter.take() {
625 Some(existing) => existing.or_expr(filter),
626 None => filter,
627 });
628 self
629 }
630
631 pub fn having(mut self, having: Expr) -> Self {
632 self.having = Some(having);
633 self
634 }
635
636 pub fn and_having(mut self, having: Expr) -> Self {
637 self.having = Some(match self.having.take() {
638 Some(existing) => existing.and_expr(having),
639 None => having,
640 });
641 self
642 }
643
644 pub fn or_having(mut self, having: Expr) -> Self {
645 self.having = Some(match self.having.take() {
646 Some(existing) => existing.or_expr(having),
647 None => having,
648 });
649 self
650 }
651
652 pub fn order_by(mut self, order: OrderBy) -> Self {
653 self.order_by.push(order);
654 self
655 }
656
657 pub fn order_asc(self, field: impl Into<String>) -> Self {
658 self.order_by(OrderBy::asc(field))
659 }
660
661 pub fn order_desc(self, field: impl Into<String>) -> Self {
662 self.order_by(OrderBy::desc(field))
663 }
664
665 pub fn order_expr_asc(self, expr: Expr) -> Self {
666 self.order_by(OrderBy::asc_expr(expr))
667 }
668
669 pub fn order_expr_desc(self, expr: Expr) -> Self {
670 self.order_by(OrderBy::desc_expr(expr))
671 }
672
673 pub fn order_gbk_asc(self, field: impl Into<String>) -> Self {
674 self.order_by(OrderBy::asc_gbk(field))
675 }
676
677 pub fn order_gbk_desc(self, field: impl Into<String>) -> Self {
678 self.order_by(OrderBy::desc_gbk(field))
679 }
680
681 pub fn group_by(mut self, field: impl Into<String>) -> Self {
682 self.group_by.push(field.into());
683 self
684 }
685
686 pub fn aggregate(mut self, aggregate: Aggregate) -> Self {
687 self.aggregates.push(aggregate);
688 self
689 }
690
691 pub fn count(self, alias: impl Into<String>) -> Self {
692 self.aggregate(Aggregate::count(alias))
693 }
694
695 pub fn count_field(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
696 self.aggregate(Aggregate::count_field(field, alias))
697 }
698
699 pub fn sum(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
700 self.aggregate(Aggregate::sum(field, alias))
701 }
702
703 pub fn avg(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
704 self.aggregate(Aggregate::avg(field, alias))
705 }
706
707 pub fn min(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
708 self.aggregate(Aggregate::min(field, alias))
709 }
710
711 pub fn max(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
712 self.aggregate(Aggregate::max(field, alias))
713 }
714
715 pub fn stddev(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
716 self.aggregate(Aggregate::stddev(field, alias))
717 }
718
719 pub fn stddev_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
720 self.aggregate(Aggregate::stddev_pop(field, alias))
721 }
722
723 pub fn var_samp(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
724 self.aggregate(Aggregate::var_samp(field, alias))
725 }
726
727 pub fn var_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
728 self.aggregate(Aggregate::var_pop(field, alias))
729 }
730
731 pub fn bit_and(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
732 self.aggregate(Aggregate::bit_and(field, alias))
733 }
734
735 pub fn bit_or(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
736 self.aggregate(Aggregate::bit_or(field, alias))
737 }
738
739 pub fn bit_xor(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
740 self.aggregate(Aggregate::bit_xor(field, alias))
741 }
742
743 pub fn enable_aggregation_cache(self) -> Self {
744 self.enable_aggregation_cache_for(0)
745 }
746
747 pub fn enable_aggregation_cache_for(mut self, cache_expired_millis: u64) -> Self {
748 self.aggregation_cache = Some(AggregationCacheOptions::enabled(cache_expired_millis));
749 self
750 }
751
752 pub fn propagate_aggregation_cache(mut self, cache_expired_millis: u64) -> Self {
753 self.aggregation_cache = Some(
754 self.aggregation_cache
755 .unwrap_or_else(|| AggregationCacheOptions::enabled(0))
756 .propagate(cache_expired_millis),
757 );
758 self
759 }
760
761 pub fn comment(mut self, comment: impl Into<String>) -> Self {
762 let comment_str = comment.into();
763 self.comment = Some(comment_str.clone());
764 self.trace_chain.push(crate::TraceNode {
765 kind: crate::TraceKind::Comment,
766 entity_type: self.entity.clone(),
767 entity_id: None,
768 comment: comment_str,
769 });
770 self
771 }
772
773 pub fn purpose(mut self, purpose: impl Into<String>) -> Self {
774 self.purpose = Some(purpose.into());
775 self
776 }
777
778 pub fn raw_sql(mut self, raw_sql: impl Into<String>) -> Self {
779 self.raw_sql = Some(raw_sql.into());
780 self
781 }
782
783 pub fn raw_sql_search_criteria(mut self, raw_sql: impl Into<String>) -> Self {
784 self.raw_sql_search_criteria.push(raw_sql.into());
785 self
786 }
787
788 pub fn object_group_by(
789 mut self,
790 property_name: impl Into<String>,
791 storage_field: impl Into<String>,
792 query: SelectQuery,
793 ) -> Self {
794 self.object_group_bys
795 .push(ObjectGroupBy::new(property_name, storage_field, query));
796 self
797 }
798
799 pub fn child_enhancement(mut self, query: SelectQuery) -> Self {
800 self.child_enhancements.push(query);
801 self
802 }
803
804 pub fn relation(mut self, name: impl Into<String>) -> Self {
805 self.relations.push(RelationLoad::new(name));
806 self
807 }
808
809 pub fn relation_query(mut self, name: impl Into<String>, query: SelectQuery) -> Self {
810 self.relations.push(RelationLoad::with_query(name, query));
811 self
812 }
813
814 pub fn limit(mut self, limit: u64) -> Self {
815 let slice = self.slice.get_or_insert(Slice {
816 limit: None,
817 offset: 0,
818 });
819 slice.limit = Some(limit);
820 self
821 }
822
823 pub fn hard_limit(mut self, hard_limit: u64) -> Self {
825 assert!(hard_limit > 0, "hard_limit must be positive");
826 self.hard_limit = hard_limit;
827 self
828 }
829
830 pub fn prepare_for_list(mut self) -> Result<Self, String> {
833 self.apply_list_limit(self.hard_limit, true)?;
834 Ok(self)
835 }
836
837 fn apply_list_limit(&mut self, ceiling: u64, outer: bool) -> Result<(), String> {
838 let slice = self.slice.get_or_insert(Slice {
839 limit: None,
840 offset: 0,
841 });
842 match slice.limit {
843 Some(limit) if limit > ceiling => {
844 return Err(format!(
845 "QUERY_HARD_LIMIT_EXCEEDED: requested limit {limit} exceeds hard limit {ceiling}"
846 ));
847 }
848 None => slice.limit = Some(ceiling),
849 _ => {}
850 }
851 for relation in &mut self.relations {
852 if let Some(query) = relation.query.as_mut() {
853 query.apply_list_limit(10_000, false)?;
854 }
855 }
856 for query in &mut self.child_enhancements {
857 query.apply_list_limit(10_000, false)?;
858 }
859 let _ = outer;
860 Ok(())
861 }
862
863 pub fn offset(mut self, offset: u64) -> Self {
864 let slice = self.slice.get_or_insert(Slice {
865 limit: None,
866 offset: 0,
867 });
868 slice.offset = offset;
869 self
870 }
871
872 pub fn page(self, offset: u64, limit: u64) -> Self {
873 self.offset(offset).limit(limit)
874 }
875
876 pub fn optimize_for_continuous_page_fetch(mut self) -> Self {
877 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(
878 "default",
879 ContinuousPageFetchOptions::DEFAULT_TTL_SECONDS,
880 ));
881 self
882 }
883
884 pub fn optimize_for_continuous_page_fetch_with(
885 mut self,
886 namespace: impl Into<String>,
887 ttl_seconds: u64,
888 ) -> Self {
889 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(namespace, ttl_seconds));
890 self
891 }
892
893 pub fn optimize_pagination_with_id_set(mut self) -> Self {
894 self.id_set_pagination = Some(IdSetPaginationOptions::new(
895 "default",
896 IdSetPaginationOptions::DEFAULT_TTL_SECONDS,
897 IdSetPaginationOptions::DEFAULT_MAX_IDS,
898 ));
899 self
900 }
901
902 pub fn optimize_pagination_with_id_set_config(
903 mut self,
904 namespace: impl Into<String>,
905 ttl_seconds: u64,
906 max_ids: u64,
907 ) -> Self {
908 self.id_set_pagination = Some(IdSetPaginationOptions::new(namespace, ttl_seconds, max_ids));
909 self
910 }
911
912 pub fn partition_by(mut self, field: impl Into<String>) -> Self {
918 self.partition_by = Some(field.into());
919 self
920 }
921
922 pub fn top_n_probe_parent_threshold(mut self, threshold: usize) -> Self {
927 self.top_n_probe_parent_threshold = Some(threshold);
928 self
929 }
930
931 pub fn stream(mut self, chunk_size: usize) -> Self {
934 self.stream_config = Some(StreamConfig { chunk_size });
935 self
936 }
937
938 pub fn stream_default(mut self) -> Self {
940 self.stream_config = Some(StreamConfig::default());
941 self
942 }
943}
944
945pub type Record = BTreeMap<String, Value>;
946
947#[derive(Debug, Clone, Default, PartialEq)]
950struct LoadedRelationResults {
951 lists: BTreeMap<String, crate::SmartList<CompactRow>>,
952 dynamic_fields: Option<crate::dynamic_fields::DynamicFieldValues>,
953}
954
955#[derive(Debug)]
961pub struct CompactRowLayout {
962 names: Arc<[String]>,
963 relation_names: Arc<[String]>,
964 dynamic_properties: Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
965 primary: std::sync::OnceLock<Arc<crate::LoadedSnapshot>>,
966 other_types:
967 std::sync::Mutex<std::collections::HashMap<usize, std::sync::Weak<crate::LoadedSnapshot>>>,
968}
969
970impl CompactRowLayout {
971 pub fn new(names: Arc<[String]>) -> Arc<Self> {
972 Self::with_relations(names, Arc::from([]))
973 }
974
975 fn with_relations(names: Arc<[String]>, relation_names: Arc<[String]>) -> Arc<Self> {
976 Self::with_metadata(names, relation_names, None)
977 }
978
979 fn with_metadata(
980 names: Arc<[String]>,
981 relation_names: Arc<[String]>,
982 dynamic_properties: Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
983 ) -> Arc<Self> {
984 Arc::new(Self {
985 names,
986 relation_names,
987 dynamic_properties,
988 primary: Default::default(),
989 other_types: Default::default(),
990 })
991 }
992
993 pub fn with_dynamic_property_definitions(
994 self: &Arc<Self>,
995 definitions: Arc<crate::dynamic_properties::DynamicPropertyDefinitions>,
996 ) -> Arc<Self> {
997 if self.dynamic_properties.as_ref() == Some(&definitions) {
998 return self.clone();
999 }
1000 Self::with_metadata(
1001 self.names.clone(),
1002 self.relation_names.clone(),
1003 Some(definitions),
1004 )
1005 }
1006
1007 fn load_state(&self, layout: Arc<crate::FieldLayout>) -> crate::eval::LoadState {
1008 let primary = self.primary.get_or_init(|| {
1009 crate::LoadedSnapshot::projection(
1010 layout.clone(),
1011 self.names
1012 .iter()
1013 .chain(self.relation_names.iter())
1014 .map(String::as_str),
1015 )
1016 .with_property_schema(self.dynamic_properties.clone())
1017 .into_shared()
1018 });
1019 if Arc::ptr_eq(primary.layout(), &layout) {
1020 return crate::eval::LoadState::Indexed(primary.clone());
1021 }
1022 let key = Arc::as_ptr(&layout) as usize;
1024 let mut states = self
1025 .other_types
1026 .lock()
1027 .unwrap_or_else(std::sync::PoisonError::into_inner);
1028 states.retain(|_, state| state.strong_count() > 0);
1029 if let Some(state) = states.get(&key).and_then(std::sync::Weak::upgrade) {
1030 return crate::eval::LoadState::Indexed(state);
1031 }
1032 let state = crate::LoadedSnapshot::projection(
1033 layout,
1034 self.names
1035 .iter()
1036 .chain(self.relation_names.iter())
1037 .map(String::as_str),
1038 )
1039 .with_property_schema(self.dynamic_properties.clone())
1040 .into_shared();
1041 states.insert(key, Arc::downgrade(&state));
1042 crate::eval::LoadState::Indexed(state)
1043 }
1044}
1045
1046impl std::ops::Deref for CompactRowLayout {
1047 type Target = [String];
1048 fn deref(&self) -> &Self::Target {
1049 &self.names
1050 }
1051}
1052
1053impl PartialEq for CompactRowLayout {
1054 fn eq(&self, other: &Self) -> bool {
1055 self.names == other.names
1056 && self.relation_names == other.relation_names
1057 && self.dynamic_properties == other.dynamic_properties
1058 }
1059}
1060
1061#[derive(Debug, Clone, PartialEq)]
1062pub struct CompactRow {
1063 columns: Arc<CompactRowLayout>,
1064 values: Vec<Value>,
1065 loaded_relations: Option<Box<LoadedRelationResults>>,
1068}
1069
1070#[doc(hidden)]
1073#[derive(Default)]
1074pub struct RelationShapeCache {
1075 relation: Option<String>,
1076 shapes: std::collections::HashMap<usize, (Arc<CompactRowLayout>, Arc<CompactRowLayout>)>,
1077}
1078
1079impl CompactRow {
1080 pub fn new(columns: Arc<[String]>, values: Vec<Value>) -> Self {
1081 Self::with_layout(CompactRowLayout::new(columns), values)
1082 }
1083
1084 pub fn with_layout(columns: Arc<CompactRowLayout>, values: Vec<Value>) -> Self {
1085 assert_eq!(
1086 columns.len(),
1087 values.len(),
1088 "row values must match the actual projection layout"
1089 );
1090 Self {
1091 columns,
1092 values,
1093 loaded_relations: None,
1094 }
1095 }
1096
1097 pub fn loaded_relation(&self, name: &str) -> Option<&crate::SmartList<CompactRow>> {
1098 self.loaded_relations.as_ref()?.lists.get(name)
1099 }
1100
1101 pub fn set_loaded_relation(&mut self, name: String, list: crate::SmartList<CompactRow>) {
1102 self.loaded_relations
1103 .get_or_insert_with(Default::default)
1104 .lists
1105 .insert(name, list);
1106 }
1107
1108 pub fn take_loaded_relation(&mut self, name: &str) -> Option<crate::SmartList<CompactRow>> {
1109 self.loaded_relations.as_mut()?.lists.remove(name)
1110 }
1111
1112 #[doc(hidden)]
1114 pub fn set_loaded_dynamic_fields(&mut self, values: crate::dynamic_fields::DynamicFieldValues) {
1115 self.loaded_relations
1116 .get_or_insert_with(Default::default)
1117 .dynamic_fields = Some(values);
1118 }
1119
1120 #[doc(hidden)]
1121 pub fn merge_loaded_dynamic_fields(
1122 &mut self,
1123 values: crate::dynamic_fields::DynamicFieldValues,
1124 shapes: &mut crate::dynamic_fields::DynamicFieldMergeShapes,
1125 ) -> Result<(), crate::dynamic_fields::DynamicFieldError> {
1126 let metadata = self.loaded_relations.get_or_insert_with(Default::default);
1127 if let Some(current) = metadata.dynamic_fields.as_mut() {
1128 current.merge_loaded(values, shapes)?;
1129 } else {
1130 metadata.dynamic_fields = Some(values);
1131 }
1132 Ok(())
1133 }
1134
1135 #[doc(hidden)]
1136 pub fn take_loaded_dynamic_fields(
1137 &mut self,
1138 ) -> Option<crate::dynamic_fields::DynamicFieldValues> {
1139 self.loaded_relations.as_mut()?.dynamic_fields.take()
1140 }
1141
1142 pub fn has_loaded_relations(&self) -> bool {
1143 self.loaded_relations
1144 .as_ref()
1145 .is_some_and(|relations| !relations.lists.is_empty())
1146 }
1147
1148 pub fn clear_loaded_relations(&mut self) {
1150 self.loaded_relations = None;
1151 }
1152
1153 pub fn get(&self, name: &str) -> Option<&Value> {
1154 self.columns
1155 .iter()
1156 .position(|column| column == name)
1157 .and_then(|index| self.values.get(index))
1158 }
1159
1160 pub fn shared_columns(&self) -> Arc<[String]> {
1161 self.columns.names.clone()
1162 }
1163
1164 pub fn shared_layout(&self) -> Arc<CompactRowLayout> {
1165 self.columns.clone()
1166 }
1167
1168 pub fn indexed_load_state(&self, layout: Arc<crate::FieldLayout>) -> crate::eval::LoadState {
1169 self.columns.load_state(layout)
1170 }
1171
1172 pub fn validate_dynamic_properties(&self) -> Result<(), String> {
1173 if let Some(definitions) = &self.columns.dynamic_properties {
1174 for (name, value) in self.iter() {
1175 definitions.validate_value(name, value)?;
1176 }
1177 }
1178 Ok(())
1179 }
1180
1181 pub fn attach_dynamic_property_definitions(
1183 rows: &mut [Self],
1184 definitions: Arc<crate::dynamic_properties::DynamicPropertyDefinitions>,
1185 ) {
1186 let mut layouts = std::collections::HashMap::new();
1187 for row in rows {
1188 let key = Arc::as_ptr(&row.columns) as usize;
1189 row.columns = layouts
1190 .entry(key)
1191 .or_insert_with(|| {
1192 (
1193 row.columns.clone(),
1194 row.columns
1195 .with_dynamic_property_definitions(definitions.clone()),
1196 )
1197 })
1198 .1
1199 .clone();
1200 }
1201 }
1202
1203 #[doc(hidden)]
1205 pub fn mark_relation_loaded(&mut self, name: &str, shapes: &mut RelationShapeCache) {
1206 if self.is_loaded_relation(name) {
1207 return;
1208 }
1209 let key = Arc::as_ptr(&self.columns) as usize;
1210 if shapes.relation.as_deref() != Some(name) {
1211 shapes.shapes.clear();
1212 shapes.relation = Some(name.to_owned());
1213 }
1214 self.columns = shapes
1215 .shapes
1216 .entry(key)
1217 .or_insert_with(|| {
1218 let mut relations = self.columns.relation_names.to_vec();
1219 relations.push(name.to_owned());
1220 (
1221 self.columns.clone(),
1222 CompactRowLayout::with_metadata(
1223 self.columns.names.clone(),
1224 relations.into(),
1225 self.columns.dynamic_properties.clone(),
1226 ),
1227 )
1228 })
1229 .1
1230 .clone();
1231 }
1232
1233 #[doc(hidden)]
1234 pub fn is_loaded_relation(&self, name: &str) -> bool {
1235 self.columns
1236 .relation_names
1237 .iter()
1238 .any(|relation| relation == name)
1239 }
1240
1241 pub fn share_layouts(rows: &mut [Self]) {
1244 let mut shapes = std::collections::HashMap::<
1245 (
1246 Arc<[String]>,
1247 Arc<[String]>,
1248 Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
1249 ),
1250 Arc<CompactRowLayout>,
1251 >::new();
1252 let mut previous: Option<Arc<CompactRowLayout>> = None;
1253 for row in rows {
1254 if previous
1255 .as_ref()
1256 .is_some_and(|layout| Arc::ptr_eq(layout, &row.columns))
1257 {
1258 continue;
1259 }
1260 let layout = shapes
1261 .entry((
1262 row.shared_columns(),
1263 row.columns.relation_names.clone(),
1264 row.columns.dynamic_properties.clone(),
1265 ))
1266 .or_insert_with(|| row.columns.clone())
1267 .clone();
1268 row.columns = layout.clone();
1269 previous = Some(layout);
1270 }
1271 }
1272
1273 pub fn get_mut(&mut self, name: &str) -> Option<&mut Value> {
1274 self.columns
1275 .iter()
1276 .position(|column| column == name)
1277 .and_then(|index| self.values.get_mut(index))
1278 }
1279
1280 pub fn insert(&mut self, name: String, value: Value) -> Option<Value> {
1283 if let Some(index) = self.columns.iter().position(|column| column == &name) {
1284 return Some(std::mem::replace(&mut self.values[index], value));
1285 }
1286 let mut columns = self.columns.to_vec();
1287 columns.push(name);
1288 self.columns = CompactRowLayout::with_metadata(
1289 columns.into(),
1290 self.columns.relation_names.clone(),
1291 self.columns.dynamic_properties.clone(),
1292 );
1293 self.values.push(value);
1294 None
1295 }
1296
1297 pub fn remove(&mut self, name: &str) -> Option<Value> {
1298 self.take_loaded_relation(name);
1299 let index = self.columns.iter().position(|column| column == name)?;
1300 let mut columns = self.columns.to_vec();
1301 columns.remove(index);
1302 self.columns = CompactRowLayout::with_metadata(
1303 columns.into(),
1304 self.columns.relation_names.clone(),
1305 self.columns.dynamic_properties.clone(),
1306 );
1307 Some(self.values.remove(index))
1308 }
1309
1310 pub fn extend(&mut self, other: CompactRow) {
1311 self.try_extend(other, &mut Default::default())
1312 .expect("incompatible CompactRow read metadata");
1313 }
1314
1315 #[doc(hidden)]
1318 pub fn try_extend(
1319 &mut self,
1320 other: CompactRow,
1321 shapes: &mut crate::dynamic_fields::DynamicFieldMergeShapes,
1322 ) -> Result<(), crate::dynamic_fields::DynamicFieldError> {
1323 let property_definitions = match (
1324 &self.columns.dynamic_properties,
1325 &other.columns.dynamic_properties,
1326 ) {
1327 (Some(left), Some(right)) => Some(
1328 crate::dynamic_properties::DynamicPropertyDefinitions::merge(left, right).map_err(
1329 |field| crate::dynamic_fields::DynamicFieldError {
1330 code: "DYNAMIC_PROPERTY_TYPE_MISMATCH",
1331 field,
1332 },
1333 )?,
1334 ),
1335 (left, right) => left.clone().or_else(|| right.clone()),
1336 };
1337 if let Some(definitions) = &property_definitions {
1338 for (name, value) in self
1340 .iter()
1341 .filter(|(name, _)| !other.contains_key(name))
1342 .chain(other.iter())
1343 {
1344 definitions.validate_value(name, value).map_err(|_| {
1345 crate::dynamic_fields::DynamicFieldError {
1346 code: "DYNAMIC_PROPERTY_TYPE_MISMATCH",
1347 field: name.clone(),
1348 }
1349 })?;
1350 }
1351 }
1352 if let (Some(left), Some(right)) = (
1353 self.loaded_relations
1354 .as_ref()
1355 .and_then(|metadata| metadata.dynamic_fields.as_ref()),
1356 other
1357 .loaded_relations
1358 .as_ref()
1359 .and_then(|metadata| metadata.dynamic_fields.as_ref()),
1360 ) {
1361 left.validate_merge(right)?;
1362 }
1363 let mut relation_names: Option<Vec<String>> = None;
1364 for name in other.columns.relation_names.iter() {
1365 let existing = relation_names
1366 .as_deref()
1367 .unwrap_or(&self.columns.relation_names);
1368 if !existing.contains(name) {
1369 relation_names
1370 .get_or_insert_with(|| self.columns.relation_names.to_vec())
1371 .push(name.clone());
1372 }
1373 }
1374 if let Some(relations) = other.loaded_relations {
1375 if let Some(values) = relations.dynamic_fields {
1376 self.merge_loaded_dynamic_fields(values, shapes)?;
1377 }
1378 self.loaded_relations
1379 .get_or_insert_with(Default::default)
1380 .lists
1381 .extend(relations.lists);
1382 }
1383 let mut columns: Option<Vec<String>> = None;
1384 for (name, value) in other.columns.iter().zip(other.values) {
1385 let names = columns.as_deref().unwrap_or(&self.columns.names);
1386 if let Some(index) = names.iter().position(|column| column == name) {
1387 self.values[index] = value;
1388 } else {
1389 columns
1390 .get_or_insert_with(|| self.columns.names.to_vec())
1391 .push(name.clone());
1392 self.values.push(value);
1393 }
1394 }
1395 if columns.is_some()
1396 || relation_names.is_some()
1397 || property_definitions != self.columns.dynamic_properties
1398 {
1399 self.columns = CompactRowLayout::with_metadata(
1400 columns
1401 .map(Arc::from)
1402 .unwrap_or_else(|| self.columns.names.clone()),
1403 relation_names
1404 .map(Arc::from)
1405 .unwrap_or_else(|| self.columns.relation_names.clone()),
1406 property_definitions,
1407 );
1408 }
1409 Ok(())
1410 }
1411
1412 pub fn contains_key(&self, name: &str) -> bool {
1413 self.columns.iter().any(|column| column == name)
1414 }
1415
1416 pub fn len(&self) -> usize {
1417 self.values.len()
1418 }
1419
1420 pub fn is_empty(&self) -> bool {
1421 self.values.is_empty()
1422 }
1423
1424 pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
1425 self.columns.iter().zip(self.values.iter())
1426 }
1427
1428 pub fn keys(&self) -> impl Iterator<Item = &String> {
1429 self.columns.iter()
1430 }
1431
1432 pub fn values(&self) -> impl Iterator<Item = &Value> {
1433 self.values.iter()
1434 }
1435
1436 pub fn into_map(self) -> BTreeMap<String, Value> {
1437 self.columns.iter().cloned().zip(self.values).collect()
1438 }
1439
1440 pub fn from_map(values_by_name: BTreeMap<String, Value>) -> Self {
1443 let (columns, values): (Vec<_>, Vec<_>) = values_by_name.into_iter().unzip();
1444 Self::new(columns.into(), values)
1445 }
1446}
1447
1448impl From<BTreeMap<String, Value>> for CompactRow {
1449 fn from(values: BTreeMap<String, Value>) -> Self {
1450 Self::from_map(values)
1451 }
1452}
1453
1454pub const PARTITION_RANK_PROPERTY: &str = "__teaql_partition_rank";
1457
1458pub fn record_to_json_value(record: &Record) -> serde_json::Value {
1459 serde_json::Value::Object(
1460 record
1461 .iter()
1462 .map(|(key, value)| (key.clone(), value.to_json_value()))
1463 .collect(),
1464 )
1465}
1466
1467pub fn compact_row_to_json_value(row: &CompactRow) -> serde_json::Value {
1468 serde_json::Value::Object(
1469 row.iter()
1470 .map(|(key, value)| (key.clone(), value.to_json_value()))
1471 .collect(),
1472 )
1473}