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