1use std::collections::{BTreeMap, BTreeSet};
13use std::future::Future;
14use std::sync::Arc;
15
16use bytes::Bytes;
17use mz_compute_types::dataflows::DataflowDescription;
18use mz_durable_cache::{DurableCache, DurableCacheCodec};
19use mz_dyncfg::ConfigSet;
20use mz_expr::OptimizedMirRelationExpr;
21use mz_ore::channel::trigger;
22use mz_ore::soft_panic_or_log;
23use mz_ore::task::spawn;
24use mz_persist_client::PersistClient;
25use mz_persist_client::cli::admin::{
26 EXPRESSION_CACHE_FORCE_COMPACTION_FUEL, EXPRESSION_CACHE_FORCE_COMPACTION_WAIT,
27};
28use mz_persist_types::codec_impls::VecU8Schema;
29use mz_persist_types::{Codec, ShardId};
30use mz_repr::optimize::OptimizerFeatures;
31use mz_repr::{GlobalId, RelationVersion};
32use mz_transform::dataflow::DataflowMetainfo;
33use mz_transform::notice::OptimizerNotice;
34use semver::Version;
35use serde::{Deserialize, Serialize};
36use tokio::sync::mpsc;
37use tracing::{debug, info, warn};
38
39#[derive(
40 Debug,
41 Clone,
42 PartialEq,
43 Eq,
44 PartialOrd,
45 Ord,
46 Hash,
47 Serialize,
48 Deserialize
49)]
50enum ExpressionType {
51 Local,
52 Global,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct LocalExpressions {
58 pub local_mir: OptimizedMirRelationExpr,
59 pub optimizer_features: OptimizerFeatures,
60 pub item_version: RelationVersion,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct GlobalExpressions {
67 pub global_mir: DataflowDescription<OptimizedMirRelationExpr>,
68 pub physical_plan: mz_compute_types::dataflows::LirDataflowDescription,
69 pub dataflow_metainfos: DataflowMetainfo<Arc<OptimizerNotice>>,
70 pub optimizer_features: OptimizerFeatures,
71 pub item_version: RelationVersion,
73}
74
75impl GlobalExpressions {
76 fn index_imports(&self) -> impl Iterator<Item = &GlobalId> {
77 self.global_mir
78 .index_imports
79 .keys()
80 .chain(self.physical_plan.index_imports.keys())
81 }
82}
83
84pub fn latest_item_version(versions: &BTreeMap<RelationVersion, GlobalId>) -> RelationVersion {
86 versions
87 .last_key_value()
88 .map_or_else(RelationVersion::root, |(version, _)| *version)
89}
90
91#[derive(
92 Debug,
93 Clone,
94 PartialEq,
95 Eq,
96 PartialOrd,
97 Ord,
98 Hash,
99 Serialize,
100 Deserialize
101)]
102struct CacheKey {
103 build_version: String,
104 id: GlobalId,
105 expr_type: ExpressionType,
106}
107
108#[derive(Debug, PartialEq, Eq)]
109struct ExpressionCodec;
110
111impl DurableCacheCodec for ExpressionCodec {
112 type Key = CacheKey;
113 type Val = Bytes;
116 type KeyCodec = Bytes;
117 type ValCodec = Bytes;
118
119 fn schemas() -> (
120 <Self::KeyCodec as Codec>::Schema,
121 <Self::ValCodec as Codec>::Schema,
122 ) {
123 (VecU8Schema::default(), VecU8Schema::default())
124 }
125
126 fn encode(key: &Self::Key, val: &Self::Val) -> (Self::KeyCodec, Self::ValCodec) {
127 let key = bincode::serialize(key).expect("must serialize");
128 (Bytes::from(key), val.clone())
129 }
130
131 fn decode(key: &Self::KeyCodec, val: &Self::ValCodec) -> (Self::Key, Self::Val) {
132 let key = bincode::deserialize(key).expect("must deserialize");
133 (key, val.clone())
134 }
135}
136
137#[derive(Debug, Default)]
139struct RemovedEntries {
140 unparsable_build_version: usize,
141 dropped_item: usize,
142 item_version_mismatch: usize,
143 dropped_index_import: usize,
144 prior_build_version: usize,
145}
146
147#[derive(Debug, Clone)]
149pub struct ExpressionCacheConfig {
150 pub build_version: Version,
151 pub persist: PersistClient,
152 pub shard_id: ShardId,
153 pub current_items: BTreeMap<GlobalId, RelationVersion>,
157 pub remove_prior_versions: bool,
159 pub compact_shard: bool,
160 pub dyncfgs: ConfigSet,
161}
162
163pub struct ExpressionCache {
165 build_version: Version,
166 durable_cache: DurableCache<ExpressionCodec>,
167}
168
169impl ExpressionCache {
170 pub async fn open(
195 ExpressionCacheConfig {
196 build_version,
197 persist,
198 shard_id,
199 current_items,
200 remove_prior_versions,
201 compact_shard,
202 dyncfgs,
203 }: ExpressionCacheConfig,
204 ) -> (
205 Self,
206 BTreeMap<GlobalId, LocalExpressions>,
207 BTreeMap<GlobalId, GlobalExpressions>,
208 ) {
209 let durable_cache = DurableCache::new(&persist, shard_id, "expressions").await;
210 let mut cache = Self {
211 build_version,
212 durable_cache,
213 };
214
215 const RETRIES: usize = 100;
216 for _ in 0..RETRIES {
217 match cache
218 .try_open(
219 ¤t_items,
220 remove_prior_versions,
221 compact_shard,
222 &dyncfgs,
223 )
224 .await
225 {
226 Ok((local_expressions, global_expressions)) => {
227 return (cache, local_expressions, global_expressions);
228 }
229 Err(err) => debug!("failed to open cache: {err} ... retrying"),
230 }
231 }
232
233 panic!("Unable to open expression cache after {RETRIES} retries");
234 }
235
236 async fn try_open(
237 &mut self,
238 current_items: &BTreeMap<GlobalId, RelationVersion>,
239 remove_prior_versions: bool,
240 compact_shard: bool,
241 dyncfgs: &ConfigSet,
242 ) -> Result<
243 (
244 BTreeMap<GlobalId, LocalExpressions>,
245 BTreeMap<GlobalId, GlobalExpressions>,
246 ),
247 mz_durable_cache::Error,
248 > {
249 let mut keys_to_remove = Vec::new();
250 let mut local_expressions = BTreeMap::new();
251 let mut global_expressions = BTreeMap::new();
252 let mut removed = RemovedEntries::default();
253 let mut unreadable = 0;
254
255 for (key, expressions) in self.durable_cache.entries_local() {
256 let build_version = match key.build_version.parse::<Version>() {
257 Ok(build_version) => build_version,
258 Err(err) => {
259 warn!("unable to parse build version: {key:?}: {err:?}");
260 removed.unparsable_build_version += 1;
261 keys_to_remove.push((key.clone(), None));
262 continue;
263 }
264 };
265 if build_version == self.build_version {
266 match key.expr_type {
268 ExpressionType::Local => {
269 let expressions: LocalExpressions = match bincode::deserialize(expressions)
270 {
271 Ok(expressions) => expressions,
272 Err(err) => {
273 soft_panic_or_log!(
274 "unable to deserialize local expressions: ({key:?}, {expressions:?}): {err:?}"
275 );
276 unreadable += 1;
277 continue;
278 }
279 };
280 match current_items.get(&key.id) {
281 None => {
282 removed.dropped_item += 1;
283 keys_to_remove.push((key.clone(), None));
284 }
285 Some(version) if *version != expressions.item_version => {
287 removed.item_version_mismatch += 1;
288 keys_to_remove.push((key.clone(), None));
289 }
290 Some(_) => {
291 local_expressions.insert(key.id, expressions);
292 }
293 }
294 }
295 ExpressionType::Global => {
296 let expressions: GlobalExpressions = match bincode::deserialize(expressions)
297 {
298 Ok(expressions) => expressions,
299 Err(err) => {
300 soft_panic_or_log!(
301 "unable to deserialize global expressions: ({key:?}, {expressions:?}): {err:?}"
302 );
303 unreadable += 1;
304 continue;
305 }
306 };
307 match current_items.get(&key.id) {
308 None => {
309 removed.dropped_item += 1;
310 keys_to_remove.push((key.clone(), None));
311 }
312 Some(version) if *version != expressions.item_version => {
313 removed.item_version_mismatch += 1;
314 keys_to_remove.push((key.clone(), None));
315 }
316 Some(_)
319 if !expressions
320 .index_imports()
321 .all(|id| current_items.contains_key(id)) =>
322 {
323 removed.dropped_index_import += 1;
324 keys_to_remove.push((key.clone(), None));
325 }
326 Some(_) => {
327 global_expressions.insert(key.id, expressions);
328 }
329 }
330 }
331 }
332 } else if remove_prior_versions {
333 removed.prior_build_version += 1;
334 keys_to_remove.push((key.clone(), None));
335 }
336 }
337 let keys_to_remove: Vec<_> = keys_to_remove
338 .iter()
339 .map(|(key, expressions)| (key, expressions.as_ref()))
340 .collect();
341 self.durable_cache.try_set_many(&keys_to_remove).await?;
342 let RemovedEntries {
343 unparsable_build_version,
344 dropped_item,
345 item_version_mismatch,
346 dropped_index_import,
347 prior_build_version,
348 } = removed;
349 info!(
350 build_version = %self.build_version,
351 remove_prior_versions,
352 kept_local = local_expressions.len(),
353 kept_global = global_expressions.len(),
354 skipped_unreadable = unreadable,
355 removed_unparsable_build_version = unparsable_build_version,
356 removed_dropped_item = dropped_item,
357 removed_item_version_mismatch = item_version_mismatch,
358 removed_dropped_index_import = dropped_index_import,
359 removed_prior_build_version = prior_build_version,
360 "reconciled the expression cache"
361 );
362
363 if remove_prior_versions {
364 self.durable_cache.upgrade_version().await;
367 }
368
369 if compact_shard {
370 let fuel = EXPRESSION_CACHE_FORCE_COMPACTION_FUEL.handle(dyncfgs);
371 let wait = EXPRESSION_CACHE_FORCE_COMPACTION_WAIT.handle(dyncfgs);
372 self.durable_cache
373 .dangerous_compact_shard(move || fuel.get(), move || wait.get())
374 .await;
375 }
376
377 Ok((local_expressions, global_expressions))
378 }
379
380 async fn update(
386 &mut self,
387 new_local_expressions: Vec<(GlobalId, LocalExpressions)>,
388 new_global_expressions: Vec<(GlobalId, GlobalExpressions)>,
389 invalidate_ids: BTreeSet<GlobalId>,
390 ) {
391 let mut entries = BTreeMap::new();
392 let build_version = self.build_version.to_string();
393 for id in invalidate_ids {
396 entries.insert(
397 CacheKey {
398 id,
399 build_version: build_version.clone(),
400 expr_type: ExpressionType::Local,
401 },
402 None,
403 );
404 entries.insert(
405 CacheKey {
406 id,
407 build_version: build_version.clone(),
408 expr_type: ExpressionType::Global,
409 },
410 None,
411 );
412 }
413 for (id, expressions) in new_local_expressions {
414 let expressions = match bincode::serialize(&expressions) {
415 Ok(expressions) => Bytes::from(expressions),
416 Err(err) => {
417 soft_panic_or_log!(
418 "unable to serialize local expressions: {expressions:?}: {err:?}"
419 );
420 continue;
421 }
422 };
423 entries.insert(
424 CacheKey {
425 id,
426 build_version: build_version.clone(),
427 expr_type: ExpressionType::Local,
428 },
429 Some(expressions),
430 );
431 }
432 for (id, expressions) in new_global_expressions {
433 let expressions = match bincode::serialize(&expressions) {
434 Ok(expressions) => Bytes::from(expressions),
435 Err(err) => {
436 soft_panic_or_log!(
437 "unable to serialize global expressions: {expressions:?}: {err:?}"
438 );
439 continue;
440 }
441 };
442 entries.insert(
443 CacheKey {
444 id,
445 build_version: build_version.clone(),
446 expr_type: ExpressionType::Global,
447 },
448 Some(expressions),
449 );
450 }
451 let entries: Vec<_> = entries
452 .iter()
453 .map(|(key, expressions)| (key, expressions.as_ref()))
454 .collect();
455 self.durable_cache.set_many(&entries).await
456 }
457}
458
459enum CacheOperation {
461 Update {
463 new_local_expressions: Vec<(GlobalId, LocalExpressions)>,
464 new_global_expressions: Vec<(GlobalId, GlobalExpressions)>,
465 invalidate_ids: BTreeSet<GlobalId>,
466 trigger: trigger::Trigger,
467 },
468}
469
470#[derive(Debug, Clone)]
471pub struct ExpressionCacheHandle {
472 tx: mpsc::UnboundedSender<CacheOperation>,
473}
474
475impl ExpressionCacheHandle {
476 pub async fn spawn_expression_cache(
480 config: ExpressionCacheConfig,
481 ) -> (
482 Self,
483 BTreeMap<GlobalId, LocalExpressions>,
484 BTreeMap<GlobalId, GlobalExpressions>,
485 ) {
486 let (mut cache, local_expressions, global_expressions) =
487 ExpressionCache::open(config).await;
488 let (tx, mut rx) = mpsc::unbounded_channel();
489 spawn(|| "expression-cache-task", async move {
490 while let Some(op) = rx.recv().await {
491 match op {
492 CacheOperation::Update {
493 new_local_expressions,
494 new_global_expressions,
495 invalidate_ids,
496 trigger: _trigger,
497 } => {
498 cache
499 .update(
500 new_local_expressions,
501 new_global_expressions,
502 invalidate_ids,
503 )
504 .await
505 }
506 }
507 }
508 });
509
510 (Self { tx }, local_expressions, global_expressions)
511 }
512
513 pub fn update(
514 &self,
515 new_local_expressions: Vec<(GlobalId, LocalExpressions)>,
516 new_global_expressions: Vec<(GlobalId, GlobalExpressions)>,
517 invalidate_ids: BTreeSet<GlobalId>,
518 ) -> impl Future<Output = ()> + use<> {
519 let (trigger, trigger_rx) = trigger::channel();
520 let op = CacheOperation::Update {
521 new_local_expressions,
522 new_global_expressions,
523 invalidate_ids,
524 trigger,
525 };
526 let _ = self.tx.send(op);
528 trigger_rx
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use std::collections::{BTreeMap, BTreeSet};
535
536 use bytes::Bytes;
537 use mz_compute_types::dataflows::{DataflowDescription, IndexDesc, IndexImport};
538 use mz_durable_cache::DurableCacheCodec;
539 use mz_dyncfg::ConfigSet;
540 use mz_expr::{MirRelationExpr, OptimizedMirRelationExpr};
541 use mz_persist_client::PersistClient;
542 use mz_persist_types::ShardId;
543 use mz_repr::{Datum, GlobalId, ReprRelationType, ReprScalarType};
544 use semver::Version;
545
546 use super::*;
547
548 #[mz_ore::test(tokio::test)]
549 #[cfg_attr(miri, ignore)] async fn expression_cache() {
551 let first_build_version = Version::new(0, 1, 0);
552 let second_build_version = Version::new(0, 2, 0);
553 let persist = PersistClient::new_for_tests().await;
554 let shard_id = ShardId::new();
555
556 let mut current_items = BTreeMap::new();
557 let mut remove_prior_versions = false;
558 let compact_shard = false;
560 let dyncfgs = &mz_persist_client::cfg::all_dyncfgs(ConfigSet::default());
561 let spawn = |build_version: &Version,
562 current_items: &BTreeMap<GlobalId, RelationVersion>,
563 remove_prior_versions: bool| {
564 ExpressionCacheHandle::spawn_expression_cache(ExpressionCacheConfig {
565 build_version: build_version.clone(),
566 persist: persist.clone(),
567 shard_id,
568 current_items: current_items.clone(),
569 remove_prior_versions,
570 compact_shard,
571 dyncfgs: dyncfgs.clone(),
572 })
573 };
574
575 let mut next_id = 0;
576
577 let (mut local_exps, mut global_exps) = {
578 let (cache, local_exprs, global_exprs) =
580 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
581 assert_eq!(local_exprs, BTreeMap::new(), "new cache should be empty");
582 assert_eq!(global_exprs, BTreeMap::new(), "new cache should be empty");
583
584 let mut local_exps = BTreeMap::new();
586 let mut global_exps = BTreeMap::new();
587 for _ in 0..4 {
588 let id = GlobalId::User(next_id);
589 let local_exp = gen_local_expressions();
590 let global_exp = gen_global_expressions();
591
592 cache
593 .update(
594 vec![(id, local_exp.clone())],
595 vec![(id, global_exp.clone())],
596 BTreeSet::new(),
597 )
598 .await;
599
600 current_items.insert(id, RelationVersion::root());
601 current_items.extend(
602 global_exp
603 .index_imports()
604 .map(|id| (*id, RelationVersion::root())),
605 );
606 local_exps.insert(id, local_exp);
607 global_exps.insert(id, global_exp);
608
609 next_id += 1;
610 }
611 (local_exps, global_exps)
612 };
613
614 {
615 let (_cache, local_entries, global_entries) =
617 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
618 assert_eq!(
619 local_entries, local_exps,
620 "local expression with non-matching optimizer features should be removed during reconciliation"
621 );
622 assert_eq!(
623 global_entries, global_exps,
624 "global expression with non-matching optimizer features should be removed during reconciliation"
625 );
626 }
627
628 {
629 let id_to_remove = local_exps.keys().next().expect("not empty").clone();
631 current_items.remove(&id_to_remove);
632 let _removed_local_exp = local_exps.remove(&id_to_remove);
633 let _removed_global_exp = global_exps.remove(&id_to_remove);
634
635 let (_cache, local_entries, global_entries) =
637 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
638 assert_eq!(
639 local_entries, local_exps,
640 "dropped local objects should be removed during reconciliation"
641 );
642 assert_eq!(
643 global_entries, global_exps,
644 "dropped global objects should be removed during reconciliation"
645 );
646 }
647
648 {
649 let id_to_bump = global_exps.keys().next_back().expect("not empty").clone();
651 current_items.insert(id_to_bump, RelationVersion::root().bump());
652 let _removed_local_exp = local_exps.remove(&id_to_bump);
653 let _removed_global_exp = global_exps.remove(&id_to_bump);
654
655 let (_cache, local_entries, global_entries) =
657 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
658 assert_eq!(
659 local_entries, local_exps,
660 "local expressions of an earlier item version should be removed during reconciliation"
661 );
662 assert_eq!(
663 global_entries, global_exps,
664 "global expressions of an earlier item version should be removed during reconciliation"
665 );
666
667 current_items.insert(id_to_bump, RelationVersion::root());
669 let (_cache, local_entries, global_entries) =
670 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
671 assert_eq!(
672 local_entries, local_exps,
673 "local expressions of an earlier item version should be durably removed"
674 );
675 assert_eq!(
676 global_entries, global_exps,
677 "global expressions of an earlier item version should be durably removed"
678 );
679 }
680
681 {
682 let global_exp_to_remove = global_exps.keys().next().expect("not empty").clone();
684 let removed_global_exp = global_exps
685 .remove(&global_exp_to_remove)
686 .expect("known to exist");
687 let dependency_to_remove = removed_global_exp
688 .index_imports()
689 .next()
690 .expect("generator always makes non-empty index imports");
691 current_items.remove(dependency_to_remove);
692
693 let _removed_local_exp = local_exps.remove(dependency_to_remove);
695 let _removed_global_exp = global_exps.remove(dependency_to_remove);
696 global_exps.retain(|_, exp| {
698 let index_imports: BTreeSet<_> = exp.index_imports().collect();
699 !index_imports.contains(&dependency_to_remove)
700 });
701
702 let (_cache, local_entries, global_entries) =
704 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
705 assert_eq!(
706 local_entries, local_exps,
707 "dropped object dependencies should NOT remove local expressions"
708 );
709 assert_eq!(
710 global_entries, global_exps,
711 "dropped object dependencies should remove global expressions"
712 );
713 }
714
715 let (new_gen_local_exps, new_gen_global_exps) = {
716 let (cache, local_entries, global_entries) =
718 spawn(&second_build_version, ¤t_items, remove_prior_versions).await;
719 assert_eq!(
720 local_entries,
721 BTreeMap::new(),
722 "new build version should be empty"
723 );
724 assert_eq!(
725 global_entries,
726 BTreeMap::new(),
727 "new build version should be empty"
728 );
729
730 let mut local_exps = BTreeMap::new();
732 let mut global_exps = BTreeMap::new();
733 for _ in 0..2 {
734 let id = GlobalId::User(next_id);
735 let local_exp = gen_local_expressions();
736 let global_exp = gen_global_expressions();
737
738 cache
739 .update(
740 vec![(id, local_exp.clone())],
741 vec![(id, global_exp.clone())],
742 BTreeSet::new(),
743 )
744 .await;
745
746 current_items.insert(id, RelationVersion::root());
747 current_items.extend(
748 global_exp
749 .index_imports()
750 .map(|id| (*id, RelationVersion::root())),
751 );
752 local_exps.insert(id, local_exp);
753 global_exps.insert(id, global_exp);
754
755 next_id += 1;
756 }
757 (local_exps, global_exps)
758 };
759
760 {
761 let (_cache, local_entries, global_entries) =
763 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
764 assert_eq!(
765 local_entries, local_exps,
766 "Previous build version local expressions should still exist"
767 );
768 assert_eq!(
769 global_entries, global_exps,
770 "Previous build version global expressions should still exist"
771 );
772 }
773
774 {
775 remove_prior_versions = true;
777 let (_cache, local_entries, global_entries) =
778 spawn(&second_build_version, ¤t_items, remove_prior_versions).await;
779 assert_eq!(
780 local_entries, new_gen_local_exps,
781 "new build version local expressions should be persisted"
782 );
783 assert_eq!(
784 global_entries, new_gen_global_exps,
785 "new build version global expressions should be persisted"
786 );
787 }
788
789 {
790 let (_cache, local_entries, global_entries) =
792 spawn(&first_build_version, ¤t_items, remove_prior_versions).await;
793 assert_eq!(
794 local_entries,
795 BTreeMap::new(),
796 "Previous build version local expressions should be cleared"
797 );
798 assert_eq!(
799 global_entries,
800 BTreeMap::new(),
801 "Previous build version global expressions should be cleared"
802 );
803 }
804 }
805
806 #[mz_ore::test]
807 fn local_expr_cache_roundtrip() {
808 let key = CacheKey {
809 id: GlobalId::User(1),
810 build_version: "1.2.3".into(),
811 expr_type: ExpressionType::Local,
812 };
813 let val = gen_local_expressions();
814
815 let bincode_val = Bytes::from(bincode::serialize(&val).expect("must serialize"));
816 let (encoded_key, encoded_val) = ExpressionCodec::encode(&key, &bincode_val);
817 let (decoded_key, decoded_val) = ExpressionCodec::decode(&encoded_key, &encoded_val);
818 let decoded_val: LocalExpressions =
819 bincode::deserialize(&decoded_val).expect("local expressions should roundtrip");
820
821 assert_eq!(key, decoded_key);
822 assert_eq!(val, decoded_val);
823 }
824
825 #[mz_ore::test]
826 fn global_expr_cache_roundtrip() {
827 let key = CacheKey {
828 id: GlobalId::User(1),
829 build_version: "1.2.3".into(),
830 expr_type: ExpressionType::Global,
831 };
832 let val = gen_global_expressions();
833
834 let bincode_val = Bytes::from(bincode::serialize(&val).expect("must serialize"));
835 let (encoded_key, encoded_val) = ExpressionCodec::encode(&key, &bincode_val);
836 let (decoded_key, decoded_val) = ExpressionCodec::decode(&encoded_key, &encoded_val);
837 let decoded_val: GlobalExpressions =
838 bincode::deserialize(&decoded_val).expect("global expressions should roundtrip");
839
840 assert_eq!(key, decoded_key);
841 assert_eq!(val, decoded_val);
842 }
843
844 fn gen_local_expressions() -> LocalExpressions {
850 let datum = Datum::UInt64(rand::random());
851
852 LocalExpressions {
853 local_mir: OptimizedMirRelationExpr(MirRelationExpr::constant(
854 vec![vec![datum]],
855 ReprRelationType::new(vec![ReprScalarType::UInt64.nullable(false)]),
856 )),
857 optimizer_features: Default::default(),
858 item_version: RelationVersion::root(),
859 }
860 }
861
862 fn gen_global_expressions() -> GlobalExpressions {
868 let name = format!("test-{}", rand::random::<u64>());
869
870 let mut global_mir = DataflowDescription::new(name.clone());
871 let mut physical_plan = DataflowDescription::new(name);
872
873 let index_imports = BTreeMap::from_iter([(
875 GlobalId::User(2),
876 IndexImport {
877 desc: IndexDesc {
878 on_id: GlobalId::User(1),
879 key: Default::default(),
880 },
881 typ: ReprRelationType::empty(),
882 monotonic: false,
883 with_snapshot: true,
884 },
885 )]);
886 global_mir.index_imports = index_imports.clone();
887 physical_plan.index_imports = index_imports;
888
889 GlobalExpressions {
890 global_mir,
891 physical_plan,
892 dataflow_metainfos: Default::default(),
893 optimizer_features: Default::default(),
894 item_version: RelationVersion::root(),
895 }
896 }
897}