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}
264
265impl RelationLoad {
266 pub fn new(name: impl Into<String>) -> Self {
267 Self {
268 name: name.into(),
269 query: None,
270 }
271 }
272
273 pub fn with_query(name: impl Into<String>, query: SelectQuery) -> Self {
274 Self {
275 name: name.into(),
276 query: Some(Box::new(query)),
277 }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq)]
282pub struct RelationAggregate {
283 pub relation_name: String,
284 pub alias: String,
285 pub query: SelectQuery,
286 pub single_result: bool,
287}
288
289impl RelationAggregate {
290 pub fn new(
291 relation_name: impl Into<String>,
292 alias: impl Into<String>,
293 query: SelectQuery,
294 single_result: bool,
295 ) -> Self {
296 Self {
297 relation_name: relation_name.into(),
298 alias: alias.into(),
299 query,
300 single_result,
301 }
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub struct RawSqlProjection {
307 pub property_name: String,
308 pub raw_sql_segment: String,
309}
310
311impl RawSqlProjection {
312 pub fn new(property_name: impl Into<String>, raw_sql_segment: impl Into<String>) -> Self {
313 Self {
314 property_name: property_name.into(),
315 raw_sql_segment: raw_sql_segment.into(),
316 }
317 }
318}
319
320#[derive(Debug, Clone, PartialEq)]
321pub struct ObjectGroupBy {
322 pub property_name: String,
323 pub storage_field: String,
324 pub query: SelectQuery,
325}
326
327impl ObjectGroupBy {
328 pub fn new(
329 property_name: impl Into<String>,
330 storage_field: impl Into<String>,
331 query: SelectQuery,
332 ) -> Self {
333 Self {
334 property_name: property_name.into(),
335 storage_field: storage_field.into(),
336 query,
337 }
338 }
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub struct AggregationCacheOptions {
343 pub enabled: bool,
344 pub cache_expired_millis: u64,
345 pub propagate: bool,
346 pub propagate_cache_expired_millis: u64,
347}
348
349impl AggregationCacheOptions {
350 pub fn enabled(cache_expired_millis: u64) -> Self {
351 Self {
352 enabled: true,
353 cache_expired_millis,
354 propagate: false,
355 propagate_cache_expired_millis: 0,
356 }
357 }
358
359 pub fn propagate(mut self, cache_expired_millis: u64) -> Self {
360 self.propagate = true;
361 self.propagate_cache_expired_millis = cache_expired_millis;
362 self
363 }
364}
365
366#[derive(Debug, Clone, PartialEq)]
367pub struct StreamConfig {
368 pub chunk_size: usize,
369}
370
371#[derive(Debug, Clone, PartialEq, Eq)]
372pub struct ContinuousPageFetchOptions {
373 pub namespace: String,
374 pub ttl_seconds: u64,
375}
376
377#[derive(Debug, Clone, PartialEq, Eq)]
378pub struct IdSetPaginationOptions {
379 pub namespace: String,
380 pub ttl_seconds: u64,
381 pub max_ids: u64,
382}
383
384impl IdSetPaginationOptions {
385 pub const DEFAULT_TTL_SECONDS: u64 = 600;
386 pub const DEFAULT_MAX_IDS: u64 = 3_000_000;
387
388 pub fn new(namespace: impl Into<String>, ttl_seconds: u64, max_ids: u64) -> Self {
389 let namespace = namespace.into();
390 assert!(
391 !namespace.trim().is_empty(),
392 "ID set pagination namespace must not be empty"
393 );
394 assert!(
395 ttl_seconds > 0,
396 "ID set pagination ttl_seconds must be positive"
397 );
398 assert!(max_ids > 0, "ID set pagination max_ids must be positive");
399 Self {
400 namespace,
401 ttl_seconds,
402 max_ids,
403 }
404 }
405}
406
407impl ContinuousPageFetchOptions {
408 pub const DEFAULT_TTL_SECONDS: u64 = 600;
409
410 pub fn new(namespace: impl Into<String>, ttl_seconds: u64) -> Self {
411 let namespace = namespace.into();
412 assert!(
413 !namespace.trim().is_empty(),
414 "continuous page namespace must not be empty"
415 );
416 assert!(
417 ttl_seconds > 0,
418 "continuous page ttl_seconds must be positive"
419 );
420 Self {
421 namespace,
422 ttl_seconds,
423 }
424 }
425}
426
427impl Default for StreamConfig {
428 fn default() -> Self {
429 Self { chunk_size: 1000 }
430 }
431}
432
433#[derive(Debug, Clone, PartialEq)]
434pub struct SelectQuery {
435 pub hard_limit: u64,
437 pub entity: String,
438 pub projection: Vec<String>,
439 pub expr_projection: Vec<NamedExpr>,
440 pub search_with_text: Option<String>,
441 pub filter: Option<Expr>,
442 pub having: Option<Expr>,
443 pub order_by: Vec<OrderBy>,
444 pub slice: Option<Slice>,
445 pub partition_by: Option<String>,
447 pub top_n_probe_parent_threshold: Option<usize>,
451 pub aggregates: Vec<Aggregate>,
452 pub group_by: Vec<String>,
453 pub relations: Vec<RelationLoad>,
454 pub facets: Vec<crate::request::FacetRequest>,
457 pub relation_aggregates: Vec<RelationAggregate>,
460 pub aggregation_cache: Option<AggregationCacheOptions>,
461 pub comment: Option<String>,
462 pub purpose: Option<String>,
464 pub trace_chain: Vec<crate::TraceNode>,
465 pub raw_sql: Option<String>,
466 pub raw_sql_search_criteria: Vec<String>,
467 pub dynamic_properties: Vec<RawSqlProjection>,
468 pub dynamic_property_definitions:
470 Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
471 pub dynamic_field_selection:
473 Option<std::sync::Arc<crate::dynamic_fields::DynamicFieldSelection>>,
474 pub raw_projections: Vec<RawSqlProjection>,
475 pub object_group_bys: Vec<ObjectGroupBy>,
476 pub child_enhancements: Vec<SelectQuery>,
477 pub stream_config: Option<StreamConfig>,
478 pub continuous_page_fetch: Option<ContinuousPageFetchOptions>,
480 pub id_set_pagination: Option<IdSetPaginationOptions>,
482}
483
484impl SelectQuery {
485 pub fn new(entity: impl Into<String>) -> Self {
486 Self {
487 hard_limit: 10_000,
488 entity: entity.into(),
489 projection: Vec::new(),
490 expr_projection: Vec::new(),
491 search_with_text: None,
492 filter: None,
493 having: None,
494 order_by: Vec::new(),
495 slice: None,
496 partition_by: None,
497 top_n_probe_parent_threshold: None,
498 aggregates: Vec::new(),
499 group_by: Vec::new(),
500 relations: Vec::new(),
501 facets: Vec::new(),
502 relation_aggregates: Vec::new(),
503 aggregation_cache: None,
504 comment: None,
505 purpose: None,
506 trace_chain: Vec::new(),
507 raw_sql: None,
508 raw_sql_search_criteria: Vec::new(),
509 dynamic_properties: Vec::new(),
510 dynamic_property_definitions: None,
511 dynamic_field_selection: None,
512 raw_projections: Vec::new(),
513 object_group_bys: Vec::new(),
514 child_enhancements: Vec::new(),
515 stream_config: None,
516 continuous_page_fetch: None,
517 id_set_pagination: None,
518 }
519 }
520
521 pub fn project(mut self, field: impl Into<String>) -> Self {
522 self.projection.push(field.into());
523 self
524 }
525
526 pub fn select_dynamic_fields(
527 mut self,
528 selection: crate::dynamic_fields::DynamicFieldSelection,
529 ) -> Self {
530 self.dynamic_field_selection = Some(std::sync::Arc::new(selection));
531 self
532 }
533
534 pub fn projects(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
535 self.projection.extend(fields.into_iter().map(Into::into));
536 self
537 }
538
539 pub fn project_expr(mut self, alias: impl Into<String>, expr: Expr) -> Self {
540 self.expr_projection.push(NamedExpr::new(alias, expr));
541 self
542 }
543
544 pub fn project_raw(
545 mut self,
546 alias: impl Into<String>,
547 raw_sql_segment: impl Into<String>,
548 ) -> Self {
549 self.raw_projections
550 .push(RawSqlProjection::new(alias, raw_sql_segment));
551 self
552 }
553
554 pub fn dynamic_property_raw(
555 mut self,
556 alias: impl Into<String>,
557 raw_sql_segment: impl Into<String>,
558 ) -> Self {
559 self.dynamic_properties
560 .push(RawSqlProjection::new(alias, raw_sql_segment));
561 self
562 }
563
564 pub fn with_dynamic_property_definitions(
565 mut self,
566 definitions: Arc<crate::dynamic_properties::DynamicPropertyDefinitions>,
567 ) -> Self {
568 self.dynamic_property_definitions = Some(definitions);
569 self
570 }
571
572 pub fn search_with_text(mut self, text: impl Into<String>) -> Self {
573 self.search_with_text = Some(text.into());
574 self
575 }
576
577 pub fn filter(mut self, filter: Expr) -> Self {
578 self.filter = Some(filter);
579 self
580 }
581
582 pub fn and_filter(mut self, filter: Expr) -> Self {
583 self.filter = Some(match self.filter.take() {
584 Some(existing) => existing.and_expr(filter),
585 None => filter,
586 });
587 self
588 }
589
590 pub fn or_filter(mut self, filter: Expr) -> Self {
591 self.filter = Some(match self.filter.take() {
592 Some(existing) => existing.or_expr(filter),
593 None => filter,
594 });
595 self
596 }
597
598 pub fn having(mut self, having: Expr) -> Self {
599 self.having = Some(having);
600 self
601 }
602
603 pub fn and_having(mut self, having: Expr) -> Self {
604 self.having = Some(match self.having.take() {
605 Some(existing) => existing.and_expr(having),
606 None => having,
607 });
608 self
609 }
610
611 pub fn or_having(mut self, having: Expr) -> Self {
612 self.having = Some(match self.having.take() {
613 Some(existing) => existing.or_expr(having),
614 None => having,
615 });
616 self
617 }
618
619 pub fn order_by(mut self, order: OrderBy) -> Self {
620 self.order_by.push(order);
621 self
622 }
623
624 pub fn order_asc(self, field: impl Into<String>) -> Self {
625 self.order_by(OrderBy::asc(field))
626 }
627
628 pub fn order_desc(self, field: impl Into<String>) -> Self {
629 self.order_by(OrderBy::desc(field))
630 }
631
632 pub fn order_expr_asc(self, expr: Expr) -> Self {
633 self.order_by(OrderBy::asc_expr(expr))
634 }
635
636 pub fn order_expr_desc(self, expr: Expr) -> Self {
637 self.order_by(OrderBy::desc_expr(expr))
638 }
639
640 pub fn order_gbk_asc(self, field: impl Into<String>) -> Self {
641 self.order_by(OrderBy::asc_gbk(field))
642 }
643
644 pub fn order_gbk_desc(self, field: impl Into<String>) -> Self {
645 self.order_by(OrderBy::desc_gbk(field))
646 }
647
648 pub fn group_by(mut self, field: impl Into<String>) -> Self {
649 self.group_by.push(field.into());
650 self
651 }
652
653 pub fn aggregate(mut self, aggregate: Aggregate) -> Self {
654 self.aggregates.push(aggregate);
655 self
656 }
657
658 pub fn count(self, alias: impl Into<String>) -> Self {
659 self.aggregate(Aggregate::count(alias))
660 }
661
662 pub fn count_field(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
663 self.aggregate(Aggregate::count_field(field, alias))
664 }
665
666 pub fn sum(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
667 self.aggregate(Aggregate::sum(field, alias))
668 }
669
670 pub fn avg(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
671 self.aggregate(Aggregate::avg(field, alias))
672 }
673
674 pub fn min(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
675 self.aggregate(Aggregate::min(field, alias))
676 }
677
678 pub fn max(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
679 self.aggregate(Aggregate::max(field, alias))
680 }
681
682 pub fn stddev(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
683 self.aggregate(Aggregate::stddev(field, alias))
684 }
685
686 pub fn stddev_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
687 self.aggregate(Aggregate::stddev_pop(field, alias))
688 }
689
690 pub fn var_samp(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
691 self.aggregate(Aggregate::var_samp(field, alias))
692 }
693
694 pub fn var_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
695 self.aggregate(Aggregate::var_pop(field, alias))
696 }
697
698 pub fn bit_and(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
699 self.aggregate(Aggregate::bit_and(field, alias))
700 }
701
702 pub fn bit_or(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
703 self.aggregate(Aggregate::bit_or(field, alias))
704 }
705
706 pub fn bit_xor(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
707 self.aggregate(Aggregate::bit_xor(field, alias))
708 }
709
710 pub fn enable_aggregation_cache(self) -> Self {
711 self.enable_aggregation_cache_for(0)
712 }
713
714 pub fn enable_aggregation_cache_for(mut self, cache_expired_millis: u64) -> Self {
715 self.aggregation_cache = Some(AggregationCacheOptions::enabled(cache_expired_millis));
716 self
717 }
718
719 pub fn propagate_aggregation_cache(mut self, cache_expired_millis: u64) -> Self {
720 self.aggregation_cache = Some(
721 self.aggregation_cache
722 .unwrap_or_else(|| AggregationCacheOptions::enabled(0))
723 .propagate(cache_expired_millis),
724 );
725 self
726 }
727
728 pub fn comment(mut self, comment: impl Into<String>) -> Self {
729 let comment_str = comment.into();
730 self.comment = Some(comment_str.clone());
731 self.trace_chain.push(crate::TraceNode {
732 kind: crate::TraceKind::Comment,
733 entity_type: self.entity.clone(),
734 entity_id: None,
735 comment: comment_str,
736 });
737 self
738 }
739
740 pub fn purpose(mut self, purpose: impl Into<String>) -> Self {
741 self.purpose = Some(purpose.into());
742 self
743 }
744
745 pub fn raw_sql(mut self, raw_sql: impl Into<String>) -> Self {
746 self.raw_sql = Some(raw_sql.into());
747 self
748 }
749
750 pub fn raw_sql_search_criteria(mut self, raw_sql: impl Into<String>) -> Self {
751 self.raw_sql_search_criteria.push(raw_sql.into());
752 self
753 }
754
755 pub fn object_group_by(
756 mut self,
757 property_name: impl Into<String>,
758 storage_field: impl Into<String>,
759 query: SelectQuery,
760 ) -> Self {
761 self.object_group_bys
762 .push(ObjectGroupBy::new(property_name, storage_field, query));
763 self
764 }
765
766 pub fn child_enhancement(mut self, query: SelectQuery) -> Self {
767 self.child_enhancements.push(query);
768 self
769 }
770
771 pub fn relation(mut self, name: impl Into<String>) -> Self {
772 self.relations.push(RelationLoad::new(name));
773 self
774 }
775
776 pub fn relation_query(mut self, name: impl Into<String>, query: SelectQuery) -> Self {
777 self.relations.push(RelationLoad::with_query(name, query));
778 self
779 }
780
781 pub fn limit(mut self, limit: u64) -> Self {
782 let slice = self.slice.get_or_insert(Slice {
783 limit: None,
784 offset: 0,
785 });
786 slice.limit = Some(limit);
787 self
788 }
789
790 pub fn hard_limit(mut self, hard_limit: u64) -> Self {
792 assert!(hard_limit > 0, "hard_limit must be positive");
793 self.hard_limit = hard_limit;
794 self
795 }
796
797 pub fn prepare_for_list(mut self) -> Result<Self, String> {
800 self.apply_list_limit(self.hard_limit, true)?;
801 Ok(self)
802 }
803
804 fn apply_list_limit(&mut self, ceiling: u64, outer: bool) -> Result<(), String> {
805 let slice = self.slice.get_or_insert(Slice {
806 limit: None,
807 offset: 0,
808 });
809 match slice.limit {
810 Some(limit) if limit > ceiling => {
811 return Err(format!(
812 "QUERY_HARD_LIMIT_EXCEEDED: requested limit {limit} exceeds hard limit {ceiling}"
813 ));
814 }
815 None => slice.limit = Some(ceiling),
816 _ => {}
817 }
818 for relation in &mut self.relations {
819 if let Some(query) = relation.query.as_mut() {
820 query.apply_list_limit(10_000, false)?;
821 }
822 }
823 for query in &mut self.child_enhancements {
824 query.apply_list_limit(10_000, false)?;
825 }
826 let _ = outer;
827 Ok(())
828 }
829
830 pub fn offset(mut self, offset: u64) -> Self {
831 let slice = self.slice.get_or_insert(Slice {
832 limit: None,
833 offset: 0,
834 });
835 slice.offset = offset;
836 self
837 }
838
839 pub fn page(self, offset: u64, limit: u64) -> Self {
840 self.offset(offset).limit(limit)
841 }
842
843 pub fn optimize_for_continuous_page_fetch(mut self) -> Self {
844 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(
845 "default",
846 ContinuousPageFetchOptions::DEFAULT_TTL_SECONDS,
847 ));
848 self
849 }
850
851 pub fn optimize_for_continuous_page_fetch_with(
852 mut self,
853 namespace: impl Into<String>,
854 ttl_seconds: u64,
855 ) -> Self {
856 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(namespace, ttl_seconds));
857 self
858 }
859
860 pub fn optimize_pagination_with_id_set(mut self) -> Self {
861 self.id_set_pagination = Some(IdSetPaginationOptions::new(
862 "default",
863 IdSetPaginationOptions::DEFAULT_TTL_SECONDS,
864 IdSetPaginationOptions::DEFAULT_MAX_IDS,
865 ));
866 self
867 }
868
869 pub fn optimize_pagination_with_id_set_config(
870 mut self,
871 namespace: impl Into<String>,
872 ttl_seconds: u64,
873 max_ids: u64,
874 ) -> Self {
875 self.id_set_pagination = Some(IdSetPaginationOptions::new(namespace, ttl_seconds, max_ids));
876 self
877 }
878
879 pub fn partition_by(mut self, field: impl Into<String>) -> Self {
885 self.partition_by = Some(field.into());
886 self
887 }
888
889 pub fn top_n_probe_parent_threshold(mut self, threshold: usize) -> Self {
894 self.top_n_probe_parent_threshold = Some(threshold);
895 self
896 }
897
898 pub fn stream(mut self, chunk_size: usize) -> Self {
901 self.stream_config = Some(StreamConfig { chunk_size });
902 self
903 }
904
905 pub fn stream_default(mut self) -> Self {
907 self.stream_config = Some(StreamConfig::default());
908 self
909 }
910}
911
912pub type Record = BTreeMap<String, Value>;
913
914#[derive(Debug, Clone, Default, PartialEq)]
917struct LoadedRelationResults {
918 lists: BTreeMap<String, crate::SmartList<CompactRow>>,
919 dynamic_fields: Option<crate::dynamic_fields::DynamicFieldValues>,
920}
921
922#[derive(Debug)]
928pub struct CompactRowLayout {
929 names: Arc<[String]>,
930 relation_names: Arc<[String]>,
931 dynamic_properties: Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
932 primary: std::sync::OnceLock<Arc<crate::LoadedSnapshot>>,
933 other_types:
934 std::sync::Mutex<std::collections::HashMap<usize, std::sync::Weak<crate::LoadedSnapshot>>>,
935}
936
937impl CompactRowLayout {
938 pub fn new(names: Arc<[String]>) -> Arc<Self> {
939 Self::with_relations(names, Arc::from([]))
940 }
941
942 fn with_relations(names: Arc<[String]>, relation_names: Arc<[String]>) -> Arc<Self> {
943 Self::with_metadata(names, relation_names, None)
944 }
945
946 fn with_metadata(
947 names: Arc<[String]>,
948 relation_names: Arc<[String]>,
949 dynamic_properties: Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
950 ) -> Arc<Self> {
951 Arc::new(Self {
952 names,
953 relation_names,
954 dynamic_properties,
955 primary: Default::default(),
956 other_types: Default::default(),
957 })
958 }
959
960 pub fn with_dynamic_property_definitions(
961 self: &Arc<Self>,
962 definitions: Arc<crate::dynamic_properties::DynamicPropertyDefinitions>,
963 ) -> Arc<Self> {
964 if self.dynamic_properties.as_ref() == Some(&definitions) {
965 return self.clone();
966 }
967 Self::with_metadata(
968 self.names.clone(),
969 self.relation_names.clone(),
970 Some(definitions),
971 )
972 }
973
974 fn load_state(&self, layout: Arc<crate::FieldLayout>) -> crate::eval::LoadState {
975 let primary = self.primary.get_or_init(|| {
976 crate::LoadedSnapshot::projection(
977 layout.clone(),
978 self.names
979 .iter()
980 .chain(self.relation_names.iter())
981 .map(String::as_str),
982 )
983 .with_property_schema(self.dynamic_properties.clone())
984 .into_shared()
985 });
986 if Arc::ptr_eq(primary.layout(), &layout) {
987 return crate::eval::LoadState::Indexed(primary.clone());
988 }
989 let key = Arc::as_ptr(&layout) as usize;
991 let mut states = self
992 .other_types
993 .lock()
994 .unwrap_or_else(std::sync::PoisonError::into_inner);
995 states.retain(|_, state| state.strong_count() > 0);
996 if let Some(state) = states.get(&key).and_then(std::sync::Weak::upgrade) {
997 return crate::eval::LoadState::Indexed(state);
998 }
999 let state = crate::LoadedSnapshot::projection(
1000 layout,
1001 self.names
1002 .iter()
1003 .chain(self.relation_names.iter())
1004 .map(String::as_str),
1005 )
1006 .with_property_schema(self.dynamic_properties.clone())
1007 .into_shared();
1008 states.insert(key, Arc::downgrade(&state));
1009 crate::eval::LoadState::Indexed(state)
1010 }
1011}
1012
1013impl std::ops::Deref for CompactRowLayout {
1014 type Target = [String];
1015 fn deref(&self) -> &Self::Target {
1016 &self.names
1017 }
1018}
1019
1020impl PartialEq for CompactRowLayout {
1021 fn eq(&self, other: &Self) -> bool {
1022 self.names == other.names
1023 && self.relation_names == other.relation_names
1024 && self.dynamic_properties == other.dynamic_properties
1025 }
1026}
1027
1028#[derive(Debug, Clone, PartialEq)]
1029pub struct CompactRow {
1030 columns: Arc<CompactRowLayout>,
1031 values: Vec<Value>,
1032 loaded_relations: Option<Box<LoadedRelationResults>>,
1035}
1036
1037#[doc(hidden)]
1040#[derive(Default)]
1041pub struct RelationShapeCache {
1042 relation: Option<String>,
1043 shapes: std::collections::HashMap<usize, (Arc<CompactRowLayout>, Arc<CompactRowLayout>)>,
1044}
1045
1046impl CompactRow {
1047 pub fn new(columns: Arc<[String]>, values: Vec<Value>) -> Self {
1048 Self::with_layout(CompactRowLayout::new(columns), values)
1049 }
1050
1051 pub fn with_layout(columns: Arc<CompactRowLayout>, values: Vec<Value>) -> Self {
1052 assert_eq!(
1053 columns.len(),
1054 values.len(),
1055 "row values must match the actual projection layout"
1056 );
1057 Self {
1058 columns,
1059 values,
1060 loaded_relations: None,
1061 }
1062 }
1063
1064 pub fn loaded_relation(&self, name: &str) -> Option<&crate::SmartList<CompactRow>> {
1065 self.loaded_relations.as_ref()?.lists.get(name)
1066 }
1067
1068 pub fn set_loaded_relation(&mut self, name: String, list: crate::SmartList<CompactRow>) {
1069 self.loaded_relations
1070 .get_or_insert_with(Default::default)
1071 .lists
1072 .insert(name, list);
1073 }
1074
1075 pub fn take_loaded_relation(&mut self, name: &str) -> Option<crate::SmartList<CompactRow>> {
1076 self.loaded_relations.as_mut()?.lists.remove(name)
1077 }
1078
1079 #[doc(hidden)]
1081 pub fn set_loaded_dynamic_fields(&mut self, values: crate::dynamic_fields::DynamicFieldValues) {
1082 self.loaded_relations
1083 .get_or_insert_with(Default::default)
1084 .dynamic_fields = Some(values);
1085 }
1086
1087 #[doc(hidden)]
1088 pub fn merge_loaded_dynamic_fields(
1089 &mut self,
1090 values: crate::dynamic_fields::DynamicFieldValues,
1091 shapes: &mut crate::dynamic_fields::DynamicFieldMergeShapes,
1092 ) -> Result<(), crate::dynamic_fields::DynamicFieldError> {
1093 let metadata = self.loaded_relations.get_or_insert_with(Default::default);
1094 if let Some(current) = metadata.dynamic_fields.as_mut() {
1095 current.merge_loaded(values, shapes)?;
1096 } else {
1097 metadata.dynamic_fields = Some(values);
1098 }
1099 Ok(())
1100 }
1101
1102 #[doc(hidden)]
1103 pub fn take_loaded_dynamic_fields(
1104 &mut self,
1105 ) -> Option<crate::dynamic_fields::DynamicFieldValues> {
1106 self.loaded_relations.as_mut()?.dynamic_fields.take()
1107 }
1108
1109 pub fn has_loaded_relations(&self) -> bool {
1110 self.loaded_relations
1111 .as_ref()
1112 .is_some_and(|relations| !relations.lists.is_empty())
1113 }
1114
1115 pub fn clear_loaded_relations(&mut self) {
1117 self.loaded_relations = None;
1118 }
1119
1120 pub fn get(&self, name: &str) -> Option<&Value> {
1121 self.columns
1122 .iter()
1123 .position(|column| column == name)
1124 .and_then(|index| self.values.get(index))
1125 }
1126
1127 pub fn shared_columns(&self) -> Arc<[String]> {
1128 self.columns.names.clone()
1129 }
1130
1131 pub fn shared_layout(&self) -> Arc<CompactRowLayout> {
1132 self.columns.clone()
1133 }
1134
1135 pub fn indexed_load_state(&self, layout: Arc<crate::FieldLayout>) -> crate::eval::LoadState {
1136 self.columns.load_state(layout)
1137 }
1138
1139 pub fn validate_dynamic_properties(&self) -> Result<(), String> {
1140 if let Some(definitions) = &self.columns.dynamic_properties {
1141 for (name, value) in self.iter() {
1142 definitions.validate_value(name, value)?;
1143 }
1144 }
1145 Ok(())
1146 }
1147
1148 pub fn attach_dynamic_property_definitions(
1150 rows: &mut [Self],
1151 definitions: Arc<crate::dynamic_properties::DynamicPropertyDefinitions>,
1152 ) {
1153 let mut layouts = std::collections::HashMap::new();
1154 for row in rows {
1155 let key = Arc::as_ptr(&row.columns) as usize;
1156 row.columns = layouts
1157 .entry(key)
1158 .or_insert_with(|| {
1159 (
1160 row.columns.clone(),
1161 row.columns
1162 .with_dynamic_property_definitions(definitions.clone()),
1163 )
1164 })
1165 .1
1166 .clone();
1167 }
1168 }
1169
1170 #[doc(hidden)]
1172 pub fn mark_relation_loaded(&mut self, name: &str, shapes: &mut RelationShapeCache) {
1173 if self.is_loaded_relation(name) {
1174 return;
1175 }
1176 let key = Arc::as_ptr(&self.columns) as usize;
1177 if shapes.relation.as_deref() != Some(name) {
1178 shapes.shapes.clear();
1179 shapes.relation = Some(name.to_owned());
1180 }
1181 self.columns = shapes
1182 .shapes
1183 .entry(key)
1184 .or_insert_with(|| {
1185 let mut relations = self.columns.relation_names.to_vec();
1186 relations.push(name.to_owned());
1187 (
1188 self.columns.clone(),
1189 CompactRowLayout::with_metadata(
1190 self.columns.names.clone(),
1191 relations.into(),
1192 self.columns.dynamic_properties.clone(),
1193 ),
1194 )
1195 })
1196 .1
1197 .clone();
1198 }
1199
1200 #[doc(hidden)]
1201 pub fn is_loaded_relation(&self, name: &str) -> bool {
1202 self.columns
1203 .relation_names
1204 .iter()
1205 .any(|relation| relation == name)
1206 }
1207
1208 pub fn share_layouts(rows: &mut [Self]) {
1211 let mut shapes = std::collections::HashMap::<
1212 (
1213 Arc<[String]>,
1214 Arc<[String]>,
1215 Option<Arc<crate::dynamic_properties::DynamicPropertyDefinitions>>,
1216 ),
1217 Arc<CompactRowLayout>,
1218 >::new();
1219 let mut previous: Option<Arc<CompactRowLayout>> = None;
1220 for row in rows {
1221 if previous
1222 .as_ref()
1223 .is_some_and(|layout| Arc::ptr_eq(layout, &row.columns))
1224 {
1225 continue;
1226 }
1227 let layout = shapes
1228 .entry((
1229 row.shared_columns(),
1230 row.columns.relation_names.clone(),
1231 row.columns.dynamic_properties.clone(),
1232 ))
1233 .or_insert_with(|| row.columns.clone())
1234 .clone();
1235 row.columns = layout.clone();
1236 previous = Some(layout);
1237 }
1238 }
1239
1240 pub fn get_mut(&mut self, name: &str) -> Option<&mut Value> {
1241 self.columns
1242 .iter()
1243 .position(|column| column == name)
1244 .and_then(|index| self.values.get_mut(index))
1245 }
1246
1247 pub fn insert(&mut self, name: String, value: Value) -> Option<Value> {
1250 if let Some(index) = self.columns.iter().position(|column| column == &name) {
1251 return Some(std::mem::replace(&mut self.values[index], value));
1252 }
1253 let mut columns = self.columns.to_vec();
1254 columns.push(name);
1255 self.columns = CompactRowLayout::with_metadata(
1256 columns.into(),
1257 self.columns.relation_names.clone(),
1258 self.columns.dynamic_properties.clone(),
1259 );
1260 self.values.push(value);
1261 None
1262 }
1263
1264 pub fn remove(&mut self, name: &str) -> Option<Value> {
1265 self.take_loaded_relation(name);
1266 let index = self.columns.iter().position(|column| column == name)?;
1267 let mut columns = self.columns.to_vec();
1268 columns.remove(index);
1269 self.columns = CompactRowLayout::with_metadata(
1270 columns.into(),
1271 self.columns.relation_names.clone(),
1272 self.columns.dynamic_properties.clone(),
1273 );
1274 Some(self.values.remove(index))
1275 }
1276
1277 pub fn extend(&mut self, other: CompactRow) {
1278 self.try_extend(other, &mut Default::default())
1279 .expect("incompatible CompactRow read metadata");
1280 }
1281
1282 #[doc(hidden)]
1285 pub fn try_extend(
1286 &mut self,
1287 other: CompactRow,
1288 shapes: &mut crate::dynamic_fields::DynamicFieldMergeShapes,
1289 ) -> Result<(), crate::dynamic_fields::DynamicFieldError> {
1290 let property_definitions = match (
1291 &self.columns.dynamic_properties,
1292 &other.columns.dynamic_properties,
1293 ) {
1294 (Some(left), Some(right)) => Some(
1295 crate::dynamic_properties::DynamicPropertyDefinitions::merge(left, right).map_err(
1296 |field| crate::dynamic_fields::DynamicFieldError {
1297 code: "DYNAMIC_PROPERTY_TYPE_MISMATCH",
1298 field,
1299 },
1300 )?,
1301 ),
1302 (left, right) => left.clone().or_else(|| right.clone()),
1303 };
1304 if let Some(definitions) = &property_definitions {
1305 for (name, value) in self
1307 .iter()
1308 .filter(|(name, _)| !other.contains_key(name))
1309 .chain(other.iter())
1310 {
1311 definitions.validate_value(name, value).map_err(|_| {
1312 crate::dynamic_fields::DynamicFieldError {
1313 code: "DYNAMIC_PROPERTY_TYPE_MISMATCH",
1314 field: name.clone(),
1315 }
1316 })?;
1317 }
1318 }
1319 if let (Some(left), Some(right)) = (
1320 self.loaded_relations
1321 .as_ref()
1322 .and_then(|metadata| metadata.dynamic_fields.as_ref()),
1323 other
1324 .loaded_relations
1325 .as_ref()
1326 .and_then(|metadata| metadata.dynamic_fields.as_ref()),
1327 ) {
1328 left.validate_merge(right)?;
1329 }
1330 let mut relation_names: Option<Vec<String>> = None;
1331 for name in other.columns.relation_names.iter() {
1332 let existing = relation_names
1333 .as_deref()
1334 .unwrap_or(&self.columns.relation_names);
1335 if !existing.contains(name) {
1336 relation_names
1337 .get_or_insert_with(|| self.columns.relation_names.to_vec())
1338 .push(name.clone());
1339 }
1340 }
1341 if let Some(relations) = other.loaded_relations {
1342 if let Some(values) = relations.dynamic_fields {
1343 self.merge_loaded_dynamic_fields(values, shapes)?;
1344 }
1345 self.loaded_relations
1346 .get_or_insert_with(Default::default)
1347 .lists
1348 .extend(relations.lists);
1349 }
1350 let mut columns: Option<Vec<String>> = None;
1351 for (name, value) in other.columns.iter().zip(other.values) {
1352 let names = columns.as_deref().unwrap_or(&self.columns.names);
1353 if let Some(index) = names.iter().position(|column| column == name) {
1354 self.values[index] = value;
1355 } else {
1356 columns
1357 .get_or_insert_with(|| self.columns.names.to_vec())
1358 .push(name.clone());
1359 self.values.push(value);
1360 }
1361 }
1362 if columns.is_some()
1363 || relation_names.is_some()
1364 || property_definitions != self.columns.dynamic_properties
1365 {
1366 self.columns = CompactRowLayout::with_metadata(
1367 columns
1368 .map(Arc::from)
1369 .unwrap_or_else(|| self.columns.names.clone()),
1370 relation_names
1371 .map(Arc::from)
1372 .unwrap_or_else(|| self.columns.relation_names.clone()),
1373 property_definitions,
1374 );
1375 }
1376 Ok(())
1377 }
1378
1379 pub fn contains_key(&self, name: &str) -> bool {
1380 self.columns.iter().any(|column| column == name)
1381 }
1382
1383 pub fn len(&self) -> usize {
1384 self.values.len()
1385 }
1386
1387 pub fn is_empty(&self) -> bool {
1388 self.values.is_empty()
1389 }
1390
1391 pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
1392 self.columns.iter().zip(self.values.iter())
1393 }
1394
1395 pub fn keys(&self) -> impl Iterator<Item = &String> {
1396 self.columns.iter()
1397 }
1398
1399 pub fn values(&self) -> impl Iterator<Item = &Value> {
1400 self.values.iter()
1401 }
1402
1403 pub fn into_map(self) -> BTreeMap<String, Value> {
1404 self.columns.iter().cloned().zip(self.values).collect()
1405 }
1406
1407 pub fn from_map(values_by_name: BTreeMap<String, Value>) -> Self {
1410 let (columns, values): (Vec<_>, Vec<_>) = values_by_name.into_iter().unzip();
1411 Self::new(columns.into(), values)
1412 }
1413}
1414
1415impl From<BTreeMap<String, Value>> for CompactRow {
1416 fn from(values: BTreeMap<String, Value>) -> Self {
1417 Self::from_map(values)
1418 }
1419}
1420
1421pub const PARTITION_RANK_PROPERTY: &str = "__teaql_partition_rank";
1424
1425pub fn record_to_json_value(record: &Record) -> serde_json::Value {
1426 serde_json::Value::Object(
1427 record
1428 .iter()
1429 .map(|(key, value)| (key.clone(), value.to_json_value()))
1430 .collect(),
1431 )
1432}
1433
1434pub fn compact_row_to_json_value(row: &CompactRow) -> serde_json::Value {
1435 serde_json::Value::Object(
1436 row.iter()
1437 .map(|(key, value)| (key.clone(), value.to_json_value()))
1438 .collect(),
1439 )
1440}