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
79#[derive(Debug, Clone, PartialEq)]
80pub struct NamedExpr {
81 pub alias: String,
82 pub expr: Expr,
83}
84
85impl NamedExpr {
86 pub fn new(alias: impl Into<String>, expr: Expr) -> Self {
87 Self {
88 alias: alias.into(),
89 expr,
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
95pub struct OrderBy {
96 pub field: String,
97 pub expr: Option<Expr>,
98 pub direction: SortDirection,
99}
100
101impl OrderBy {
102 pub fn new(field: impl Into<String>, direction: SortDirection) -> Self {
103 Self {
104 field: field.into(),
105 expr: None,
106 direction,
107 }
108 }
109
110 pub fn expr(expr: Expr, direction: SortDirection) -> Self {
111 Self {
112 field: String::new(),
113 expr: Some(expr),
114 direction,
115 }
116 }
117
118 pub fn asc(field: impl Into<String>) -> Self {
119 Self::new(field, SortDirection::Asc)
120 }
121
122 pub fn desc(field: impl Into<String>) -> Self {
123 Self::new(field, SortDirection::Desc)
124 }
125
126 pub fn asc_expr(expr: Expr) -> Self {
127 Self::expr(expr, SortDirection::Asc)
128 }
129
130 pub fn desc_expr(expr: Expr) -> Self {
131 Self::expr(expr, SortDirection::Desc)
132 }
133
134 pub fn asc_gbk(field: impl Into<String>) -> Self {
135 Self::asc_expr(Expr::gbk(Expr::column(field)))
136 }
137
138 pub fn desc_gbk(field: impl Into<String>) -> Self {
139 Self::desc_expr(Expr::gbk(Expr::column(field)))
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum AggregateFunction {
145 Count,
146 Sum,
147 Avg,
148 Min,
149 Max,
150 Stddev,
151 StddevPop,
152 VarSamp,
153 VarPop,
154 BitAnd,
155 BitOr,
156 BitXor,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct Aggregate {
161 pub function: AggregateFunction,
162 pub field: String,
163 pub alias: String,
164}
165
166impl Aggregate {
167 pub fn new(
168 function: AggregateFunction,
169 field: impl Into<String>,
170 alias: impl Into<String>,
171 ) -> Self {
172 Self {
173 function,
174 field: field.into(),
175 alias: alias.into(),
176 }
177 }
178
179 pub fn count(alias: impl Into<String>) -> Self {
180 Self::new(AggregateFunction::Count, "*", alias)
181 }
182
183 pub fn count_field(field: impl Into<String>, alias: impl Into<String>) -> Self {
184 Self::new(AggregateFunction::Count, field, alias)
185 }
186
187 pub fn sum(field: impl Into<String>, alias: impl Into<String>) -> Self {
188 Self::new(AggregateFunction::Sum, field, alias)
189 }
190
191 pub fn avg(field: impl Into<String>, alias: impl Into<String>) -> Self {
192 Self::new(AggregateFunction::Avg, field, alias)
193 }
194
195 pub fn min(field: impl Into<String>, alias: impl Into<String>) -> Self {
196 Self::new(AggregateFunction::Min, field, alias)
197 }
198
199 pub fn max(field: impl Into<String>, alias: impl Into<String>) -> Self {
200 Self::new(AggregateFunction::Max, field, alias)
201 }
202
203 pub fn stddev(field: impl Into<String>, alias: impl Into<String>) -> Self {
204 Self::new(AggregateFunction::Stddev, field, alias)
205 }
206
207 pub fn stddev_pop(field: impl Into<String>, alias: impl Into<String>) -> Self {
208 Self::new(AggregateFunction::StddevPop, field, alias)
209 }
210
211 pub fn var_samp(field: impl Into<String>, alias: impl Into<String>) -> Self {
212 Self::new(AggregateFunction::VarSamp, field, alias)
213 }
214
215 pub fn var_pop(field: impl Into<String>, alias: impl Into<String>) -> Self {
216 Self::new(AggregateFunction::VarPop, field, alias)
217 }
218
219 pub fn bit_and(field: impl Into<String>, alias: impl Into<String>) -> Self {
220 Self::new(AggregateFunction::BitAnd, field, alias)
221 }
222
223 pub fn bit_or(field: impl Into<String>, alias: impl Into<String>) -> Self {
224 Self::new(AggregateFunction::BitOr, field, alias)
225 }
226
227 pub fn bit_xor(field: impl Into<String>, alias: impl Into<String>) -> Self {
228 Self::new(AggregateFunction::BitXor, field, alias)
229 }
230}
231
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub struct Slice {
234 pub limit: Option<u64>,
235 pub offset: u64,
236}
237
238#[derive(Debug, Clone, PartialEq)]
239pub struct RelationLoad {
240 pub name: String,
241 pub query: Option<Box<SelectQuery>>,
242}
243
244impl RelationLoad {
245 pub fn new(name: impl Into<String>) -> Self {
246 Self {
247 name: name.into(),
248 query: None,
249 }
250 }
251
252 pub fn with_query(name: impl Into<String>, query: SelectQuery) -> Self {
253 Self {
254 name: name.into(),
255 query: Some(Box::new(query)),
256 }
257 }
258}
259
260#[derive(Debug, Clone, PartialEq)]
261pub struct RelationAggregate {
262 pub relation_name: String,
263 pub alias: String,
264 pub query: SelectQuery,
265 pub single_result: bool,
266}
267
268impl RelationAggregate {
269 pub fn new(
270 relation_name: impl Into<String>,
271 alias: impl Into<String>,
272 query: SelectQuery,
273 single_result: bool,
274 ) -> Self {
275 Self {
276 relation_name: relation_name.into(),
277 alias: alias.into(),
278 query,
279 single_result,
280 }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct RawSqlProjection {
286 pub property_name: String,
287 pub raw_sql_segment: String,
288}
289
290impl RawSqlProjection {
291 pub fn new(property_name: impl Into<String>, raw_sql_segment: impl Into<String>) -> Self {
292 Self {
293 property_name: property_name.into(),
294 raw_sql_segment: raw_sql_segment.into(),
295 }
296 }
297}
298
299#[derive(Debug, Clone, PartialEq)]
300pub struct ObjectGroupBy {
301 pub property_name: String,
302 pub storage_field: String,
303 pub query: SelectQuery,
304}
305
306impl ObjectGroupBy {
307 pub fn new(
308 property_name: impl Into<String>,
309 storage_field: impl Into<String>,
310 query: SelectQuery,
311 ) -> Self {
312 Self {
313 property_name: property_name.into(),
314 storage_field: storage_field.into(),
315 query,
316 }
317 }
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub struct AggregationCacheOptions {
322 pub enabled: bool,
323 pub cache_expired_millis: u64,
324 pub propagate: bool,
325 pub propagate_cache_expired_millis: u64,
326}
327
328impl AggregationCacheOptions {
329 pub fn enabled(cache_expired_millis: u64) -> Self {
330 Self {
331 enabled: true,
332 cache_expired_millis,
333 propagate: false,
334 propagate_cache_expired_millis: 0,
335 }
336 }
337
338 pub fn propagate(mut self, cache_expired_millis: u64) -> Self {
339 self.propagate = true;
340 self.propagate_cache_expired_millis = cache_expired_millis;
341 self
342 }
343}
344
345#[derive(Debug, Clone, PartialEq)]
346pub struct StreamConfig {
347 pub chunk_size: usize,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
351pub struct ContinuousPageFetchOptions {
352 pub namespace: String,
353 pub ttl_seconds: u64,
354}
355
356#[derive(Debug, Clone, PartialEq, Eq)]
357pub struct IdSetPaginationOptions {
358 pub namespace: String,
359 pub ttl_seconds: u64,
360 pub max_ids: u64,
361}
362
363impl IdSetPaginationOptions {
364 pub const DEFAULT_TTL_SECONDS: u64 = 600;
365 pub const DEFAULT_MAX_IDS: u64 = 3_000_000;
366
367 pub fn new(namespace: impl Into<String>, ttl_seconds: u64, max_ids: u64) -> Self {
368 let namespace = namespace.into();
369 assert!(
370 !namespace.trim().is_empty(),
371 "ID set pagination namespace must not be empty"
372 );
373 assert!(
374 ttl_seconds > 0,
375 "ID set pagination ttl_seconds must be positive"
376 );
377 assert!(max_ids > 0, "ID set pagination max_ids must be positive");
378 Self {
379 namespace,
380 ttl_seconds,
381 max_ids,
382 }
383 }
384}
385
386impl ContinuousPageFetchOptions {
387 pub const DEFAULT_TTL_SECONDS: u64 = 600;
388
389 pub fn new(namespace: impl Into<String>, ttl_seconds: u64) -> Self {
390 let namespace = namespace.into();
391 assert!(
392 !namespace.trim().is_empty(),
393 "continuous page namespace must not be empty"
394 );
395 assert!(
396 ttl_seconds > 0,
397 "continuous page ttl_seconds must be positive"
398 );
399 Self {
400 namespace,
401 ttl_seconds,
402 }
403 }
404}
405
406impl Default for StreamConfig {
407 fn default() -> Self {
408 Self { chunk_size: 1000 }
409 }
410}
411
412#[derive(Debug, Clone, PartialEq)]
413pub struct SelectQuery {
414 pub hard_limit: u64,
416 pub entity: String,
417 pub projection: Vec<String>,
418 pub expr_projection: Vec<NamedExpr>,
419 pub search_with_text: Option<String>,
420 pub filter: Option<Expr>,
421 pub having: Option<Expr>,
422 pub order_by: Vec<OrderBy>,
423 pub slice: Option<Slice>,
424 pub partition_by: Option<String>,
426 pub aggregates: Vec<Aggregate>,
427 pub group_by: Vec<String>,
428 pub relations: Vec<RelationLoad>,
429 pub aggregation_cache: Option<AggregationCacheOptions>,
430 pub comment: Option<String>,
431 pub trace_chain: Vec<crate::TraceNode>,
432 pub raw_sql: Option<String>,
433 pub raw_sql_search_criteria: Vec<String>,
434 pub dynamic_properties: Vec<RawSqlProjection>,
435 pub raw_projections: Vec<RawSqlProjection>,
436 pub object_group_bys: Vec<ObjectGroupBy>,
437 pub child_enhancements: Vec<SelectQuery>,
438 pub stream_config: Option<StreamConfig>,
439 pub continuous_page_fetch: Option<ContinuousPageFetchOptions>,
441 pub id_set_pagination: Option<IdSetPaginationOptions>,
443}
444
445impl SelectQuery {
446 pub fn new(entity: impl Into<String>) -> Self {
447 Self {
448 hard_limit: 10_000,
449 entity: entity.into(),
450 projection: Vec::new(),
451 expr_projection: Vec::new(),
452 search_with_text: None,
453 filter: None,
454 having: None,
455 order_by: Vec::new(),
456 slice: None,
457 partition_by: None,
458 aggregates: Vec::new(),
459 group_by: Vec::new(),
460 relations: Vec::new(),
461 aggregation_cache: None,
462 comment: None,
463 trace_chain: Vec::new(),
464 raw_sql: None,
465 raw_sql_search_criteria: Vec::new(),
466 dynamic_properties: Vec::new(),
467 raw_projections: Vec::new(),
468 object_group_bys: Vec::new(),
469 child_enhancements: Vec::new(),
470 stream_config: None,
471 continuous_page_fetch: None,
472 id_set_pagination: None,
473 }
474 }
475
476 pub fn project(mut self, field: impl Into<String>) -> Self {
477 self.projection.push(field.into());
478 self
479 }
480
481 pub fn projects(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
482 self.projection.extend(fields.into_iter().map(Into::into));
483 self
484 }
485
486 pub fn project_expr(mut self, alias: impl Into<String>, expr: Expr) -> Self {
487 self.expr_projection.push(NamedExpr::new(alias, expr));
488 self
489 }
490
491 pub fn project_raw(
492 mut self,
493 alias: impl Into<String>,
494 raw_sql_segment: impl Into<String>,
495 ) -> Self {
496 self.raw_projections
497 .push(RawSqlProjection::new(alias, raw_sql_segment));
498 self
499 }
500
501 pub fn dynamic_property_raw(
502 mut self,
503 alias: impl Into<String>,
504 raw_sql_segment: impl Into<String>,
505 ) -> Self {
506 self.dynamic_properties
507 .push(RawSqlProjection::new(alias, raw_sql_segment));
508 self
509 }
510
511 pub fn search_with_text(mut self, text: impl Into<String>) -> Self {
512 self.search_with_text = Some(text.into());
513 self
514 }
515
516 pub fn filter(mut self, filter: Expr) -> Self {
517 self.filter = Some(filter);
518 self
519 }
520
521 pub fn and_filter(mut self, filter: Expr) -> Self {
522 self.filter = Some(match self.filter.take() {
523 Some(existing) => existing.and_expr(filter),
524 None => filter,
525 });
526 self
527 }
528
529 pub fn or_filter(mut self, filter: Expr) -> Self {
530 self.filter = Some(match self.filter.take() {
531 Some(existing) => existing.or_expr(filter),
532 None => filter,
533 });
534 self
535 }
536
537 pub fn having(mut self, having: Expr) -> Self {
538 self.having = Some(having);
539 self
540 }
541
542 pub fn and_having(mut self, having: Expr) -> Self {
543 self.having = Some(match self.having.take() {
544 Some(existing) => existing.and_expr(having),
545 None => having,
546 });
547 self
548 }
549
550 pub fn or_having(mut self, having: Expr) -> Self {
551 self.having = Some(match self.having.take() {
552 Some(existing) => existing.or_expr(having),
553 None => having,
554 });
555 self
556 }
557
558 pub fn order_by(mut self, order: OrderBy) -> Self {
559 self.order_by.push(order);
560 self
561 }
562
563 pub fn order_asc(self, field: impl Into<String>) -> Self {
564 self.order_by(OrderBy::asc(field))
565 }
566
567 pub fn order_desc(self, field: impl Into<String>) -> Self {
568 self.order_by(OrderBy::desc(field))
569 }
570
571 pub fn order_expr_asc(self, expr: Expr) -> Self {
572 self.order_by(OrderBy::asc_expr(expr))
573 }
574
575 pub fn order_expr_desc(self, expr: Expr) -> Self {
576 self.order_by(OrderBy::desc_expr(expr))
577 }
578
579 pub fn order_gbk_asc(self, field: impl Into<String>) -> Self {
580 self.order_by(OrderBy::asc_gbk(field))
581 }
582
583 pub fn order_gbk_desc(self, field: impl Into<String>) -> Self {
584 self.order_by(OrderBy::desc_gbk(field))
585 }
586
587 pub fn group_by(mut self, field: impl Into<String>) -> Self {
588 self.group_by.push(field.into());
589 self
590 }
591
592 pub fn aggregate(mut self, aggregate: Aggregate) -> Self {
593 self.aggregates.push(aggregate);
594 self
595 }
596
597 pub fn count(self, alias: impl Into<String>) -> Self {
598 self.aggregate(Aggregate::count(alias))
599 }
600
601 pub fn count_field(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
602 self.aggregate(Aggregate::count_field(field, alias))
603 }
604
605 pub fn sum(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
606 self.aggregate(Aggregate::sum(field, alias))
607 }
608
609 pub fn avg(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
610 self.aggregate(Aggregate::avg(field, alias))
611 }
612
613 pub fn min(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
614 self.aggregate(Aggregate::min(field, alias))
615 }
616
617 pub fn max(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
618 self.aggregate(Aggregate::max(field, alias))
619 }
620
621 pub fn stddev(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
622 self.aggregate(Aggregate::stddev(field, alias))
623 }
624
625 pub fn stddev_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
626 self.aggregate(Aggregate::stddev_pop(field, alias))
627 }
628
629 pub fn var_samp(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
630 self.aggregate(Aggregate::var_samp(field, alias))
631 }
632
633 pub fn var_pop(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
634 self.aggregate(Aggregate::var_pop(field, alias))
635 }
636
637 pub fn bit_and(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
638 self.aggregate(Aggregate::bit_and(field, alias))
639 }
640
641 pub fn bit_or(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
642 self.aggregate(Aggregate::bit_or(field, alias))
643 }
644
645 pub fn bit_xor(self, field: impl Into<String>, alias: impl Into<String>) -> Self {
646 self.aggregate(Aggregate::bit_xor(field, alias))
647 }
648
649 pub fn enable_aggregation_cache(self) -> Self {
650 self.enable_aggregation_cache_for(0)
651 }
652
653 pub fn enable_aggregation_cache_for(mut self, cache_expired_millis: u64) -> Self {
654 self.aggregation_cache = Some(AggregationCacheOptions::enabled(cache_expired_millis));
655 self
656 }
657
658 pub fn propagate_aggregation_cache(mut self, cache_expired_millis: u64) -> Self {
659 self.aggregation_cache = Some(
660 self.aggregation_cache
661 .unwrap_or_else(|| AggregationCacheOptions::enabled(0))
662 .propagate(cache_expired_millis),
663 );
664 self
665 }
666
667 pub fn comment(mut self, comment: impl Into<String>) -> Self {
668 let comment_str = comment.into();
669 self.comment = Some(comment_str.clone());
670 self.trace_chain.push(crate::TraceNode {
671 entity_type: self.entity.clone(),
672 entity_id: None,
673 comment: comment_str,
674 });
675 self
676 }
677
678 pub fn raw_sql(mut self, raw_sql: impl Into<String>) -> Self {
679 self.raw_sql = Some(raw_sql.into());
680 self
681 }
682
683 pub fn raw_sql_search_criteria(mut self, raw_sql: impl Into<String>) -> Self {
684 self.raw_sql_search_criteria.push(raw_sql.into());
685 self
686 }
687
688 pub fn object_group_by(
689 mut self,
690 property_name: impl Into<String>,
691 storage_field: impl Into<String>,
692 query: SelectQuery,
693 ) -> Self {
694 self.object_group_bys
695 .push(ObjectGroupBy::new(property_name, storage_field, query));
696 self
697 }
698
699 pub fn child_enhancement(mut self, query: SelectQuery) -> Self {
700 self.child_enhancements.push(query);
701 self
702 }
703
704 pub fn relation(mut self, name: impl Into<String>) -> Self {
705 self.relations.push(RelationLoad::new(name));
706 self
707 }
708
709 pub fn relation_query(mut self, name: impl Into<String>, query: SelectQuery) -> Self {
710 self.relations.push(RelationLoad::with_query(name, query));
711 self
712 }
713
714 pub fn limit(mut self, limit: u64) -> Self {
715 let slice = self.slice.get_or_insert(Slice {
716 limit: None,
717 offset: 0,
718 });
719 slice.limit = Some(limit);
720 self
721 }
722
723 pub fn hard_limit(mut self, hard_limit: u64) -> Self {
725 assert!(hard_limit > 0, "hard_limit must be positive");
726 self.hard_limit = hard_limit;
727 self
728 }
729
730 pub fn prepare_for_list(mut self) -> Result<Self, String> {
733 self.apply_list_limit(self.hard_limit, true)?;
734 Ok(self)
735 }
736
737 fn apply_list_limit(&mut self, ceiling: u64, outer: bool) -> Result<(), String> {
738 let slice = self.slice.get_or_insert(Slice {
739 limit: None,
740 offset: 0,
741 });
742 match slice.limit {
743 Some(limit) if limit > ceiling => {
744 return Err(format!(
745 "QUERY_HARD_LIMIT_EXCEEDED: requested limit {limit} exceeds hard limit {ceiling}"
746 ));
747 }
748 None => slice.limit = Some(ceiling),
749 _ => {}
750 }
751 for relation in &mut self.relations {
752 if let Some(query) = relation.query.as_mut() {
753 query.apply_list_limit(10_000, false)?;
754 }
755 }
756 for query in &mut self.child_enhancements {
757 query.apply_list_limit(10_000, false)?;
758 }
759 let _ = outer;
760 Ok(())
761 }
762
763 pub fn offset(mut self, offset: u64) -> Self {
764 let slice = self.slice.get_or_insert(Slice {
765 limit: None,
766 offset: 0,
767 });
768 slice.offset = offset;
769 self
770 }
771
772 pub fn page(self, offset: u64, limit: u64) -> Self {
773 self.offset(offset).limit(limit)
774 }
775
776 pub fn optimize_for_continuous_page_fetch(mut self) -> Self {
777 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(
778 "default",
779 ContinuousPageFetchOptions::DEFAULT_TTL_SECONDS,
780 ));
781 self
782 }
783
784 pub fn optimize_for_continuous_page_fetch_with(
785 mut self,
786 namespace: impl Into<String>,
787 ttl_seconds: u64,
788 ) -> Self {
789 self.continuous_page_fetch = Some(ContinuousPageFetchOptions::new(namespace, ttl_seconds));
790 self
791 }
792
793 pub fn optimize_pagination_with_id_set(mut self) -> Self {
794 self.id_set_pagination = Some(IdSetPaginationOptions::new(
795 "default",
796 IdSetPaginationOptions::DEFAULT_TTL_SECONDS,
797 IdSetPaginationOptions::DEFAULT_MAX_IDS,
798 ));
799 self
800 }
801
802 pub fn optimize_pagination_with_id_set_config(
803 mut self,
804 namespace: impl Into<String>,
805 ttl_seconds: u64,
806 max_ids: u64,
807 ) -> Self {
808 self.id_set_pagination = Some(IdSetPaginationOptions::new(namespace, ttl_seconds, max_ids));
809 self
810 }
811
812 pub fn partition_by(mut self, field: impl Into<String>) -> Self {
818 self.partition_by = Some(field.into());
819 self
820 }
821
822 pub fn stream(mut self, chunk_size: usize) -> Self {
825 self.stream_config = Some(StreamConfig { chunk_size });
826 self
827 }
828
829 pub fn stream_default(mut self) -> Self {
831 self.stream_config = Some(StreamConfig::default());
832 self
833 }
834}
835
836pub type Record = BTreeMap<String, Value>;
837
838#[derive(Debug, Clone, PartialEq)]
843pub struct CompactRow {
844 columns: Arc<[String]>,
845 values: Vec<Value>,
846}
847
848impl CompactRow {
849 pub fn new(columns: Arc<[String]>, values: Vec<Value>) -> Self {
850 debug_assert_eq!(columns.len(), values.len());
851 Self { columns, values }
852 }
853
854 pub fn get(&self, name: &str) -> Option<&Value> {
855 self.columns
856 .iter()
857 .position(|column| column == name)
858 .and_then(|index| self.values.get(index))
859 }
860
861 pub fn shared_columns(&self) -> Arc<[String]> {
862 self.columns.clone()
863 }
864
865 pub fn get_mut(&mut self, name: &str) -> Option<&mut Value> {
866 self.columns
867 .iter()
868 .position(|column| column == name)
869 .and_then(|index| self.values.get_mut(index))
870 }
871
872 pub fn insert(&mut self, name: String, value: Value) -> Option<Value> {
875 if let Some(index) = self.columns.iter().position(|column| column == &name) {
876 return Some(std::mem::replace(&mut self.values[index], value));
877 }
878 let mut columns = self.columns.to_vec();
879 columns.push(name);
880 self.columns = columns.into();
881 self.values.push(value);
882 None
883 }
884
885 pub fn remove(&mut self, name: &str) -> Option<Value> {
886 let index = self.columns.iter().position(|column| column == name)?;
887 let mut columns = self.columns.to_vec();
888 columns.remove(index);
889 self.columns = columns.into();
890 Some(self.values.remove(index))
891 }
892
893 pub fn extend(&mut self, other: CompactRow) {
894 for (name, value) in other.columns.iter().cloned().zip(other.values) {
895 self.insert(name, value);
896 }
897 }
898
899 pub fn contains_key(&self, name: &str) -> bool {
900 self.columns.iter().any(|column| column == name)
901 }
902
903 pub fn len(&self) -> usize {
904 self.values.len()
905 }
906
907 pub fn is_empty(&self) -> bool {
908 self.values.is_empty()
909 }
910
911 pub fn iter(&self) -> impl Iterator<Item = (&String, &Value)> {
912 self.columns.iter().zip(self.values.iter())
913 }
914
915 pub fn keys(&self) -> impl Iterator<Item = &String> {
916 self.columns.iter()
917 }
918
919 pub fn values(&self) -> impl Iterator<Item = &Value> {
920 self.values.iter()
921 }
922
923 pub fn into_map(self) -> BTreeMap<String, Value> {
924 self.columns.iter().cloned().zip(self.values).collect()
925 }
926
927 pub fn from_map(values_by_name: BTreeMap<String, Value>) -> Self {
930 let (columns, values): (Vec<_>, Vec<_>) = values_by_name.into_iter().unzip();
931 Self::new(columns.into(), values)
932 }
933}
934
935impl From<BTreeMap<String, Value>> for CompactRow {
936 fn from(values: BTreeMap<String, Value>) -> Self {
937 Self::from_map(values)
938 }
939}
940
941pub const PARTITION_RANK_PROPERTY: &str = "__teaql_partition_rank";
944
945pub fn record_to_json_value(record: &Record) -> serde_json::Value {
946 serde_json::Value::Object(
947 record
948 .iter()
949 .map(|(key, value)| (key.clone(), value.to_json_value()))
950 .collect(),
951 )
952}
953
954pub fn compact_row_to_json_value(row: &CompactRow) -> serde_json::Value {
955 serde_json::Value::Object(
956 row.iter()
957 .map(|(key, value)| (key.clone(), value.to_json_value()))
958 .collect(),
959 )
960}