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 aggregation_cache: Option<AggregationCacheOptions>,
455 pub comment: Option<String>,
456 pub trace_chain: Vec<crate::TraceNode>,
457 pub raw_sql: Option<String>,
458 pub raw_sql_search_criteria: Vec<String>,
459 pub dynamic_properties: Vec<RawSqlProjection>,
460 pub raw_projections: Vec<RawSqlProjection>,
461 pub object_group_bys: Vec<ObjectGroupBy>,
462 pub child_enhancements: Vec<SelectQuery>,
463 pub stream_config: Option<StreamConfig>,
464 pub continuous_page_fetch: Option<ContinuousPageFetchOptions>,
466 pub id_set_pagination: Option<IdSetPaginationOptions>,
468}
469
470impl SelectQuery {
471 pub fn new(entity: impl Into<String>) -> Self {
472 Self {
473 hard_limit: 10_000,
474 entity: entity.into(),
475 projection: Vec::new(),
476 expr_projection: Vec::new(),
477 search_with_text: None,
478 filter: None,
479 having: None,
480 order_by: Vec::new(),
481 slice: None,
482 partition_by: None,
483 top_n_probe_parent_threshold: None,
484 aggregates: Vec::new(),
485 group_by: Vec::new(),
486 relations: Vec::new(),
487 aggregation_cache: None,
488 comment: None,
489 trace_chain: Vec::new(),
490 raw_sql: None,
491 raw_sql_search_criteria: Vec::new(),
492 dynamic_properties: Vec::new(),
493 raw_projections: Vec::new(),
494 object_group_bys: Vec::new(),
495 child_enhancements: Vec::new(),
496 stream_config: None,
497 continuous_page_fetch: None,
498 id_set_pagination: None,
499 }
500 }
501
502 pub fn project(mut self, field: impl Into<String>) -> Self {
503 self.projection.push(field.into());
504 self
505 }
506
507 pub fn projects(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
508 self.projection.extend(fields.into_iter().map(Into::into));
509 self
510 }
511
512 pub fn project_expr(mut self, alias: impl Into<String>, expr: Expr) -> Self {
513 self.expr_projection.push(NamedExpr::new(alias, expr));
514 self
515 }
516
517 pub fn project_raw(
518 mut self,
519 alias: impl Into<String>,
520 raw_sql_segment: impl Into<String>,
521 ) -> Self {
522 self.raw_projections
523 .push(RawSqlProjection::new(alias, raw_sql_segment));
524 self
525 }
526
527 pub fn dynamic_property_raw(
528 mut self,
529 alias: impl Into<String>,
530 raw_sql_segment: impl Into<String>,
531 ) -> Self {
532 self.dynamic_properties
533 .push(RawSqlProjection::new(alias, raw_sql_segment));
534 self
535 }
536
537 pub fn search_with_text(mut self, text: impl Into<String>) -> Self {
538 self.search_with_text = Some(text.into());
539 self
540 }
541
542 pub fn filter(mut self, filter: Expr) -> Self {
543 self.filter = Some(filter);
544 self
545 }
546
547 pub fn and_filter(mut self, filter: Expr) -> Self {
548 self.filter = Some(match self.filter.take() {
549 Some(existing) => existing.and_expr(filter),
550 None => filter,
551 });
552 self
553 }
554
555 pub fn or_filter(mut self, filter: Expr) -> Self {
556 self.filter = Some(match self.filter.take() {
557 Some(existing) => existing.or_expr(filter),
558 None => filter,
559 });
560 self
561 }
562
563 pub fn having(mut self, having: Expr) -> Self {
564 self.having = Some(having);
565 self
566 }
567
568 pub fn and_having(mut self, having: Expr) -> Self {
569 self.having = Some(match self.having.take() {
570 Some(existing) => existing.and_expr(having),
571 None => having,
572 });
573 self
574 }
575
576 pub fn or_having(mut self, having: Expr) -> Self {
577 self.having = Some(match self.having.take() {
578 Some(existing) => existing.or_expr(having),
579 None => having,
580 });
581 self
582 }
583
584 pub fn order_by(mut self, order: OrderBy) -> Self {
585 self.order_by.push(order);
586 self
587 }
588
589 pub fn order_asc(self, field: impl Into<String>) -> Self {
590 self.order_by(OrderBy::asc(field))
591 }
592
593 pub fn order_desc(self, field: impl Into<String>) -> Self {
594 self.order_by(OrderBy::desc(field))
595 }
596
597 pub fn order_expr_asc(self, expr: Expr) -> Self {
598 self.order_by(OrderBy::asc_expr(expr))
599 }
600
601 pub fn order_expr_desc(self, expr: Expr) -> Self {
602 self.order_by(OrderBy::desc_expr(expr))
603 }
604
605 pub fn order_gbk_asc(self, field: impl Into<String>) -> Self {
606 self.order_by(OrderBy::asc_gbk(field))
607 }
608
609 pub fn order_gbk_desc(self, field: impl Into<String>) -> Self {
610 self.order_by(OrderBy::desc_gbk(field))
611 }
612
613 pub fn group_by(mut self, field: impl Into<String>) -> Self {
614 self.group_by.push(field.into());
615 self
616 }
617
618 pub fn aggregate(mut self, aggregate: Aggregate) -> Self {
619 self.aggregates.push(aggregate);
620 self
621 }
622
623 pub fn count(self, alias: impl Into<String>) -> Self {
624 self.aggregate(Aggregate::count(alias))
625 }
626
627 pub fn count_field(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
628 self.aggregate(Aggregate::count_field(field, alias))
629 }
630
631 pub fn sum(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
632 self.aggregate(Aggregate::sum(field, alias))
633 }
634
635 pub fn avg(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
636 self.aggregate(Aggregate::avg(field, alias))
637 }
638
639 pub fn min(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
640 self.aggregate(Aggregate::min(field, alias))
641 }
642
643 pub fn max(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
644 self.aggregate(Aggregate::max(field, alias))
645 }
646
647 pub fn stddev(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
648 self.aggregate(Aggregate::stddev(field, alias))
649 }
650
651 pub fn stddev_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
652 self.aggregate(Aggregate::stddev_pop(field, alias))
653 }
654
655 pub fn var_samp(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
656 self.aggregate(Aggregate::var_samp(field, alias))
657 }
658
659 pub fn var_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
660 self.aggregate(Aggregate::var_pop(field, alias))
661 }
662
663 pub fn bit_and(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
664 self.aggregate(Aggregate::bit_and(field, alias))
665 }
666
667 pub fn bit_or(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
668 self.aggregate(Aggregate::bit_or(field, alias))
669 }
670
671 pub fn bit_xor(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
672 self.aggregate(Aggregate::bit_xor(field, alias))
673 }
674
675 pub fn enable_aggregation_cache(self) -> Self {
676 self.enable_aggregation_cache_for(0)
677 }
678
679 pub fn enable_aggregation_cache_for(mut self, cache_expired_millis: u64) -> Self {
680 self.aggregation_cache = Some(AggregationCacheOptions::enabled(cache_expired_millis));
681 self
682 }
683
684 pub fn propagate_aggregation_cache(mut self, cache_expired_millis: u64) -> Self {
685 self.aggregation_cache = Some(
686 self.aggregation_cache
687 .unwrap_or_else(|| AggregationCacheOptions::enabled(0))
688 .propagate(cache_expired_millis),
689 );
690 self
691 }
692
693 pub fn comment(mut self, comment: impl Into<String>) -> Self {
694 let comment_str = comment.into();
695 self.comment = Some(comment_str.clone());
696 self.trace_chain.push(crate::TraceNode {
697 kind: crate::TraceKind::Comment,
698 entity_type: self.entity.clone(),
699 entity_id: None,
700 comment: comment_str,
701 });
702 self
703 }
704
705 pub fn raw_sql(mut self, raw_sql: impl Into<String>) -> Self {
706 self.raw_sql = Some(raw_sql.into());
707 self
708 }
709
710 pub fn raw_sql_search_criteria(mut self, raw_sql: impl Into<String>) -> Self {
711 self.raw_sql_search_criteria.push(raw_sql.into());
712 self
713 }
714
715 pub fn object_group_by(
716 mut self,
717 property_name: impl Into<String>,
718 storage_field: impl Into<String>,
719 query: SelectQuery,
720 ) -> Self {
721 self.object_group_bys
722 .push(ObjectGroupBy::new(property_name, storage_field, query));
723 self
724 }
725
726 pub fn child_enhancement(mut self, query: SelectQuery) -> Self {
727 self.child_enhancements.push(query);
728 self
729 }
730
731 pub fn relation(mut self, name: impl Into<String>) -> Self {
732 self.relations.push(RelationLoad::new(name));
733 self
734 }
735
736 pub fn relation_query(mut self, name: impl Into<String>, query: SelectQuery) -> Self {
737 self.relations.push(RelationLoad::with_query(name, query));
738 self
739 }
740
741 pub fn limit(mut self, limit: u64) -> Self {
742 let slice = self.slice.get_or_insert(Slice {
743 limit: None,
744 offset: 0,
745 });
746 slice.limit = Some(limit);
747 self
748 }
749
750 pub fn hard_limit(mut self, hard_limit: u64) -> Self {
752 assert!(hard_limit > 0, "hard_limit must be positive");
753 self.hard_limit = hard_limit;
754 self
755 }
756
757 pub fn prepare_for_list(mut self) -> Result<Self, String> {
760 self.apply_list_limit(self.hard_limit, true)?;
761 Ok(self)
762 }
763
764 fn apply_list_limit(&mut self, ceiling: u64, outer: bool) -> Result<(), String> {
765 let slice = self.slice.get_or_insert(Slice {
766 limit: None,
767 offset: 0,
768 });
769 match slice.limit {
770 Some(limit) if limit > ceiling => {
771 return Err(format!(
772 "QUERY_HARD_LIMIT_EXCEEDED: requested limit {limit} exceeds hard limit {ceiling}"
773 ));
774 }
775 None => slice.limit = Some(ceiling),
776 _ => {}
777 }
778 for relation in &mut self.relations {
779 if let Some(query) = relation.query.as_mut() {
780 query.apply_list_limit(10_000, false)?;
781 }
782 }
783 for query in &mut self.child_enhancements {
784 query.apply_list_limit(10_000, false)?;
785 }
786 let _ = outer;
787 Ok(())
788 }
789
790 pub fn offset(mut self, offset: u64) -> Self {
791 let slice = self.slice.get_or_insert(Slice {
792 limit: None,
793 offset: 0,
794 });
795 slice.offset = offset;
796 self
797 }
798
799 pub fn page(self, offset: u64, limit: u64) -> Self {
800 self.offset(offset).limit(limit)
801 }
802
803 pub fn optimize_for_continuous_page_fetch(mut self) -> Self {
804 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(
805 "default",
806 ContinuousPageFetchOptions::DEFAULT_TTL_SECONDS,
807 ));
808 self
809 }
810
811 pub fn optimize_for_continuous_page_fetch_with(
812 mut self,
813 namespace: impl Into<String>,
814 ttl_seconds: u64,
815 ) -> Self {
816 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(namespace, ttl_seconds));
817 self
818 }
819
820 pub fn optimize_pagination_with_id_set(mut self) -> Self {
821 self.id_set_pagination = Some(IdSetPaginationOptions::new(
822 "default",
823 IdSetPaginationOptions::DEFAULT_TTL_SECONDS,
824 IdSetPaginationOptions::DEFAULT_MAX_IDS,
825 ));
826 self
827 }
828
829 pub fn optimize_pagination_with_id_set_config(
830 mut self,
831 namespace: impl Into<String>,
832 ttl_seconds: u64,
833 max_ids: u64,
834 ) -> Self {
835 self.id_set_pagination = Some(IdSetPaginationOptions::new(namespace, ttl_seconds, max_ids));
836 self
837 }
838
839 pub fn partition_by(mut self, field: impl Into<String>) -> Self {
845 self.partition_by = Some(field.into());
846 self
847 }
848
849 pub fn top_n_probe_parent_threshold(mut self, threshold: usize) -> Self {
854 self.top_n_probe_parent_threshold = Some(threshold);
855 self
856 }
857
858 pub fn stream(mut self, chunk_size: usize) -> Self {
861 self.stream_config = Some(StreamConfig { chunk_size });
862 self
863 }
864
865 pub fn stream_default(mut self) -> Self {
867 self.stream_config = Some(StreamConfig::default());
868 self
869 }
870}
871
872pub type Record = BTreeMap<String, Value>;
873
874#[derive(Debug, Clone, PartialEq)]
879pub struct CompactRow {
880 columns: Arc<[String]>,
881 values: Vec<Value>,
882}
883
884impl CompactRow {
885 pub fn new(columns: Arc<[String]>, values: Vec<Value>) -> Self {
886 debug_assert_eq!(columns.len(), values.len());
887 Self { columns, values }
888 }
889
890 pub fn get(&self, name: &str) -> Option<&Value> {
891 self.columns
892 .iter()
893 .position(|column| column == name)
894 .and_then(|index| self.values.get(index))
895 }
896
897 pub fn shared_columns(&self) -> Arc<[String]> {
898 self.columns.clone()
899 }
900
901 pub fn get_mut(&mut self, name: &str) -> Option<&mut Value> {
902 self.columns
903 .iter()
904 .position(|column| column == name)
905 .and_then(|index| self.values.get_mut(index))
906 }
907
908 pub fn insert(&mut self, name: String, value: Value) -> Option<Value> {
911 if let Some(index) = self.columns.iter().position(|column| column == &name) {
912 return Some(std::mem::replace(&mut self.values[index], value));
913 }
914 let mut columns = self.columns.to_vec();
915 columns.push(name);
916 self.columns = columns.into();
917 self.values.push(value);
918 None
919 }
920
921 pub fn remove(&mut self, name: &str) -> Option<Value> {
922 let index = self.columns.iter().position(|column| column == name)?;
923 let mut columns = self.columns.to_vec();
924 columns.remove(index);
925 self.columns = columns.into();
926 Some(self.values.remove(index))
927 }
928
929 pub fn extend(&mut self, other: CompactRow) {
930 for (name, value) in other.columns.iter().cloned().zip(other.values) {
931 self.insert(name, value);
932 }
933 }
934
935 pub fn contains_key(&self, name: &str) -> bool {
936 self.columns.iter().any(|column| column == name)
937 }
938
939 pub fn len(&self) -> usize {
940 self.values.len()
941 }
942
943 pub fn is_empty(&self) -> bool {
944 self.values.is_empty()
945 }
946
947 pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
948 self.columns.iter().zip(self.values.iter())
949 }
950
951 pub fn keys(&self) -> impl Iterator<Item = &String> {
952 self.columns.iter()
953 }
954
955 pub fn values(&self) -> impl Iterator<Item = &Value> {
956 self.values.iter()
957 }
958
959 pub fn into_map(self) -> BTreeMap<String, Value> {
960 self.columns.iter().cloned().zip(self.values).collect()
961 }
962
963 pub fn from_map(values_by_name: BTreeMap<String, Value>) -> Self {
966 let (columns, values): (Vec<_>, Vec<_>) = values_by_name.into_iter().unzip();
967 Self::new(columns.into(), values)
968 }
969}
970
971impl From<BTreeMap<String, Value>> for CompactRow {
972 fn from(values: BTreeMap<String, Value>) -> Self {
973 Self::from_map(values)
974 }
975}
976
977pub const PARTITION_RANK_PROPERTY: &str = "__teaql_partition_rank";
980
981pub fn record_to_json_value(record: &Record) -> serde_json::Value {
982 serde_json::Value::Object(
983 record
984 .iter()
985 .map(|(key, value)| (key.clone(), value.to_json_value()))
986 .collect(),
987 )
988}
989
990pub fn compact_row_to_json_value(row: &CompactRow) -> serde_json::Value {
991 serde_json::Value::Object(
992 row.iter()
993 .map(|(key, value)| (key.clone(), value.to_json_value()))
994 .collect(),
995 )
996}