1use std::collections::BTreeMap;
13use std::fmt::Debug;
14use std::sync::{Arc, RwLock};
15use std::time::Instant;
16
17use differential_dataflow::difference::Monoid;
18use differential_dataflow::lattice::Lattice;
19use mz_ore::cast::CastFrom;
20use mz_persist_types::columnar::data_type;
21use mz_persist_types::schema::{Migration, SchemaId, backward_compatible};
22use mz_persist_types::{Codec, Codec64};
23use timely::progress::Timestamp;
24
25use crate::internal::apply::Applier;
26use crate::internal::encoding::Schemas;
27use crate::internal::metrics::{SchemaCacheMetrics, SchemaMetrics};
28use crate::internal::state::{BatchPart, EncodedSchemas};
29
30#[derive(Debug)]
32#[cfg_attr(test, derive(PartialEq))]
33pub enum CaESchema<K: Codec, V: Codec> {
34 Ok(SchemaId),
36 Incompatible,
38 ExpectedMismatch {
41 schema_id: SchemaId,
43 key: K::Schema,
45 val: V::Schema,
47 },
48}
49
50#[derive(Debug)]
60pub(crate) struct SchemaCache<K: Codec, V: Codec, T, D> {
61 maps: Arc<SchemaCacheMaps<K, V>>,
62 applier: Applier<K, V, T, D>,
63 key_migration_by_ids: MigrationCacheMap,
64 val_migration_by_ids: MigrationCacheMap,
65}
66
67impl<K: Codec, V: Codec, T: Clone, D> Clone for SchemaCache<K, V, T, D> {
68 fn clone(&self) -> Self {
69 Self {
70 maps: Arc::clone(&self.maps),
71 applier: self.applier.clone(),
72 key_migration_by_ids: self.key_migration_by_ids.clone(),
73 val_migration_by_ids: self.val_migration_by_ids.clone(),
74 }
75 }
76}
77
78impl<K: Codec, V: Codec, T, D> Drop for SchemaCache<K, V, T, D> {
79 fn drop(&mut self) {
80 let dropped = u64::cast_from(
81 self.key_migration_by_ids.by_ids.len() + self.val_migration_by_ids.by_ids.len(),
82 );
83 self.applier
84 .metrics
85 .schema
86 .cache_migration
87 .dropped_count
88 .inc_by(dropped);
89 }
90}
91
92impl<K, V, T, D> SchemaCache<K, V, T, D>
93where
94 K: Debug + Codec,
95 V: Debug + Codec,
96 T: Timestamp + Lattice + Codec64 + Sync,
97 D: Monoid + Codec64,
98{
99 pub(crate) fn applier(&self) -> &Applier<K, V, T, D> {
101 &self.applier
102 }
103
104 pub fn new(maps: Arc<SchemaCacheMaps<K, V>>, applier: Applier<K, V, T, D>) -> Self {
105 let key_migration_by_ids = MigrationCacheMap {
106 metrics: applier.metrics.schema.cache_migration.clone(),
107 by_ids: BTreeMap::new(),
108 };
109 let val_migration_by_ids = MigrationCacheMap {
110 metrics: applier.metrics.schema.cache_migration.clone(),
111 by_ids: BTreeMap::new(),
112 };
113 SchemaCache {
114 maps,
115 applier,
116 key_migration_by_ids,
117 val_migration_by_ids,
118 }
119 }
120
121 async fn schemas(&self, id: &SchemaId) -> Option<Schemas<K, V>> {
122 let key = self
123 .get_or_try_init(&self.maps.key_by_id, id, |schemas| {
124 self.maps.key_by_id.metrics.computed_count.inc();
125 schemas.get(id).map(|x| K::decode_schema(&x.key))
126 })
127 .await?;
128 let val = self
129 .get_or_try_init(&self.maps.val_by_id, id, |schemas| {
130 self.maps.val_by_id.metrics.computed_count.inc();
131 schemas.get(id).map(|x| V::decode_schema(&x.val))
132 })
133 .await?;
134 Some(Schemas {
135 id: Some(*id),
136 key,
137 val,
138 })
139 }
140
141 fn key_migration(
142 &mut self,
143 write: &Schemas<K, V>,
144 read: &Schemas<K, V>,
145 ) -> Option<Arc<Migration>> {
146 let migration_fn = || Self::migration::<K>(&write.key, &read.key);
147 let (Some(write_id), Some(read_id)) = (write.id, read.id) else {
148 self.key_migration_by_ids.metrics.computed_count.inc();
152 return migration_fn().map(Arc::new);
153 };
154 self.key_migration_by_ids
155 .get_or_try_insert(write_id, read_id, migration_fn)
156 }
157
158 fn val_migration(
159 &mut self,
160 write: &Schemas<K, V>,
161 read: &Schemas<K, V>,
162 ) -> Option<Arc<Migration>> {
163 let migration_fn = || Self::migration::<V>(&write.val, &read.val);
164 let (Some(write_id), Some(read_id)) = (write.id, read.id) else {
165 self.val_migration_by_ids.metrics.computed_count.inc();
169 return migration_fn().map(Arc::new);
170 };
171 self.val_migration_by_ids
172 .get_or_try_insert(write_id, read_id, migration_fn)
173 }
174
175 fn migration<C: Codec>(write: &C::Schema, read: &C::Schema) -> Option<Migration> {
176 let write_dt = data_type::<C>(write).expect("valid schema");
177 let read_dt = data_type::<C>(read).expect("valid schema");
178 backward_compatible(&write_dt, &read_dt)
179 }
180
181 async fn get_or_try_init<MK: Clone + Ord, MV: PartialEq + Debug>(
182 &self,
183 map: &SchemaCacheMap<MK, MV>,
184 key: &MK,
185 f: impl Fn(&BTreeMap<SchemaId, EncodedSchemas>) -> Option<MV>,
186 ) -> Option<Arc<MV>> {
187 let ret = map.get_or_try_init(key, || {
188 self.applier
189 .schemas(|seqno, schemas| f(schemas).ok_or(seqno))
190 });
191 let seqno = match ret {
192 Ok(ret) => return Some(ret),
193 Err(seqno) => seqno,
194 };
195 self.applier.metrics.schema.cache_fetch_state_count.inc();
196 self.applier.fetch_and_update_state(Some(seqno)).await;
197 map.get_or_try_init(key, || {
198 self.applier
199 .schemas(|seqno, schemas| f(schemas).ok_or(seqno))
200 })
201 .ok()
202 }
203}
204
205#[derive(Debug)]
206pub(crate) struct SchemaCacheMaps<K: Codec, V: Codec> {
207 key_by_id: SchemaCacheMap<SchemaId, K::Schema>,
208 val_by_id: SchemaCacheMap<SchemaId, V::Schema>,
209}
210
211impl<K: Codec, V: Codec> SchemaCacheMaps<K, V> {
212 pub(crate) fn new(metrics: &SchemaMetrics) -> Self {
213 Self {
214 key_by_id: SchemaCacheMap {
215 metrics: metrics.cache_schema.clone(),
216 map: RwLock::new(BTreeMap::new()),
217 },
218 val_by_id: SchemaCacheMap {
219 metrics: metrics.cache_schema.clone(),
220 map: RwLock::new(BTreeMap::new()),
221 },
222 }
223 }
224}
225
226#[derive(Debug)]
227struct SchemaCacheMap<I, S> {
228 metrics: SchemaCacheMetrics,
229 map: RwLock<BTreeMap<I, Arc<S>>>,
230}
231
232impl<I: Clone + Ord, S: PartialEq + Debug> SchemaCacheMap<I, S> {
233 fn get_or_try_init<E>(
234 &self,
235 id: &I,
236 state_fn: impl FnOnce() -> Result<S, E>,
237 ) -> Result<Arc<S>, E> {
238 {
240 let map = self.map.read().expect("lock");
241 if let Some(ret) = map.get(id).map(Arc::clone) {
242 self.metrics.cached_count.inc();
243 return Ok(ret);
244 }
245 }
246 let ret = state_fn().map(Arc::new);
248 if let Ok(val) = ret.as_ref() {
249 let mut map = self.map.write().expect("lock");
250 let prev = map.insert(id.clone(), Arc::clone(val));
253 match prev {
254 Some(prev) => mz_ore::soft_assert_eq_no_log!(*val, prev),
255 None => self.metrics.added_count.inc(),
256 }
257 } else {
258 self.metrics.unavailable_count.inc();
259 }
260 ret
261 }
262}
263
264impl<I, K> Drop for SchemaCacheMap<I, K> {
265 fn drop(&mut self) {
266 let map = self.map.read().expect("lock");
267 self.metrics.dropped_count.inc_by(u64::cast_from(map.len()));
268 }
269}
270
271#[derive(Debug, Clone)]
272struct MigrationCacheMap {
273 metrics: SchemaCacheMetrics,
274 by_ids: BTreeMap<(SchemaId, SchemaId), Arc<Migration>>,
275}
276
277impl MigrationCacheMap {
278 fn get_or_try_insert(
279 &mut self,
280 write_id: SchemaId,
281 read_id: SchemaId,
282 migration_fn: impl FnOnce() -> Option<Migration>,
283 ) -> Option<Arc<Migration>> {
284 if let Some(migration) = self.by_ids.get(&(write_id, read_id)) {
285 self.metrics.cached_count.inc();
286 return Some(Arc::clone(migration));
287 };
288 self.metrics.computed_count.inc();
289 let migration = migration_fn().map(Arc::new);
290 if let Some(migration) = migration.as_ref() {
291 self.metrics.added_count.inc();
292 self.by_ids
295 .insert((write_id, read_id), Arc::clone(migration));
296 } else {
297 self.metrics.unavailable_count.inc();
298 }
299 migration
300 }
301}
302
303#[derive(Debug)]
304pub(crate) enum PartMigration<K: Codec, V: Codec> {
305 SameSchema { both: Schemas<K, V> },
307 Schemaless { read: Schemas<K, V> },
309 Either {
311 write: Schemas<K, V>,
312 read: Schemas<K, V>,
313 key_migration: Arc<Migration>,
314 val_migration: Arc<Migration>,
315 },
316}
317
318impl<K: Codec, V: Codec> Clone for PartMigration<K, V> {
319 fn clone(&self) -> Self {
320 match self {
321 Self::SameSchema { both } => Self::SameSchema { both: both.clone() },
322 Self::Schemaless { read } => Self::Schemaless { read: read.clone() },
323 Self::Either {
324 write,
325 read,
326 key_migration,
327 val_migration,
328 } => Self::Either {
329 write: write.clone(),
330 read: read.clone(),
331 key_migration: Arc::clone(key_migration),
332 val_migration: Arc::clone(val_migration),
333 },
334 }
335 }
336}
337
338impl<K, V> PartMigration<K, V>
339where
340 K: Debug + Codec,
341 V: Debug + Codec,
342{
343 pub(crate) async fn new<T, D>(
344 part: &BatchPart<T>,
345 read: Schemas<K, V>,
346 schema_cache: &mut SchemaCache<K, V, T, D>,
347 ) -> Result<Self, Schemas<K, V>>
348 where
349 T: Timestamp + Lattice + Codec64 + Sync,
350 D: Monoid + Codec64,
351 {
352 let write = match (part.schema_id(), part.deprecated_schema_id()) {
361 (Some(write_id), _) => Some(write_id),
362 (None, Some(deprecated_id))
363 if part.is_structured_only(&schema_cache.applier.metrics.columnar) =>
364 {
365 tracing::warn!(?deprecated_id, "falling back to deprecated schema ID");
366 Some(deprecated_id)
367 }
368 (None, _) => None,
369 };
370
371 match (write, read.id) {
372 (None, _) => Ok(PartMigration::Schemaless { read }),
373 (Some(w), Some(r)) if w == r => Ok(PartMigration::SameSchema { both: read }),
374 (Some(w), _) => {
375 let write = schema_cache
376 .schemas(&w)
377 .await
378 .expect("appended part should reference registered schema");
379 if write.key == read.key && write.val == read.val {
384 return Ok(PartMigration::SameSchema { both: read });
385 }
386
387 let start = Instant::now();
388 let key_migration = schema_cache
389 .key_migration(&write, &read)
390 .ok_or_else(|| read.clone())?;
391 let val_migration = schema_cache
392 .val_migration(&write, &read)
393 .ok_or_else(|| read.clone())?;
394 schema_cache
395 .applier
396 .metrics
397 .schema
398 .migration_new_count
399 .inc();
400 schema_cache
401 .applier
402 .metrics
403 .schema
404 .migration_new_seconds
405 .inc_by(start.elapsed().as_secs_f64());
406
407 Ok(PartMigration::Either {
408 write,
409 read,
410 key_migration,
411 val_migration,
412 })
413 }
414 }
415 }
416}
417
418impl<K: Codec, V: Codec> PartMigration<K, V> {
419 pub(crate) fn codec_read(&self) -> &Schemas<K, V> {
420 match self {
421 PartMigration::SameSchema { both } => both,
422 PartMigration::Schemaless { read } => read,
423 PartMigration::Either { read, .. } => read,
424 }
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use arrow::array::{
431 Array, ArrayBuilder, StringArray, StringBuilder, StructArray, as_string_array,
432 };
433 use arrow::datatypes::{DataType, Field};
434 use bytes::BufMut;
435 use futures::StreamExt;
436 use mz_dyncfg::ConfigUpdates;
437 use mz_persist_types::ShardId;
438 use mz_persist_types::arrow::ArrayOrd;
439 use mz_persist_types::codec_impls::UnitSchema;
440 use mz_persist_types::columnar::{ColumnDecoder, ColumnEncoder, Schema};
441 use mz_persist_types::stats::{NoneStats, StructStats};
442 use timely::progress::Antichain;
443
444 use async_trait::async_trait;
445 use mz_ore::metrics::MetricsRegistry;
446 use mz_persist::location::{
447 CaSResult, Consensus, ExternalError, ResultStream, SeqNo, VersionedData,
448 };
449 use mz_persist::mem::{MemBlob, MemBlobConfig, MemConsensus};
450 use std::sync::atomic::{AtomicBool, Ordering};
451
452 use crate::async_runtime::IsolatedRuntime;
453 use crate::cache::StateCache;
454 use crate::cli::admin::info_log_non_zero_metrics;
455 use crate::internal::metrics::Metrics;
456 use crate::read::ReadHandle;
457 use crate::rpc::NoopPubSubSender;
458 use crate::tests::new_test_client;
459 use crate::{Diagnostics, PersistClient, PersistConfig};
460
461 use super::*;
462
463 #[mz_ore::test]
464 fn schema_id() {
465 assert_eq!(SchemaId(1).to_string(), "h1");
466 assert_eq!(SchemaId::try_from("h1".to_owned()), Ok(SchemaId(1)));
467 assert!(SchemaId::try_from("nope".to_owned()).is_err());
468 }
469
470 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
471 struct Strings(Vec<String>);
472
473 impl Codec for Strings {
474 type Schema = StringsSchema;
475 type Storage = ();
476
477 fn codec_name() -> String {
478 "Strings".into()
479 }
480
481 fn encode<B: BufMut>(&self, buf: &mut B) {
482 buf.put_slice(self.0.join(",").as_bytes());
483 }
484 fn decode<'a>(buf: &'a [u8], schema: &Self::Schema) -> Result<Self, String> {
485 let buf = std::str::from_utf8(buf).map_err(|err| err.to_string())?;
486 let mut ret = buf.split(",").map(|x| x.to_owned()).collect::<Vec<_>>();
487 while schema.0.len() > ret.len() {
489 ret.push("".into());
490 }
491 while schema.0.len() < ret.len() {
492 ret.pop();
493 }
494 Ok(Strings(ret))
495 }
496
497 fn encode_schema(schema: &Self::Schema) -> bytes::Bytes {
498 schema
499 .0
500 .iter()
501 .map(|x| x.then_some('n').unwrap_or(' '))
502 .collect::<String>()
503 .into_bytes()
504 .into()
505 }
506 fn decode_schema(buf: &bytes::Bytes) -> Self::Schema {
507 let buf = std::str::from_utf8(buf).expect("valid schema");
508 StringsSchema(
509 buf.chars()
510 .map(|x| match x {
511 'n' => true,
512 ' ' => false,
513 _ => unreachable!(),
514 })
515 .collect(),
516 )
517 }
518 }
519
520 #[derive(Debug, Clone, Default, PartialEq)]
521 struct StringsSchema(Vec<bool>);
522
523 impl Schema<Strings> for StringsSchema {
524 type ArrowColumn = StructArray;
525 type Statistics = NoneStats;
526 type Decoder = StringsDecoder;
527 type Encoder = StringsEncoder;
528
529 fn decoder(&self, col: Self::ArrowColumn) -> Result<Self::Decoder, anyhow::Error> {
530 let mut cols = Vec::new();
531 for (idx, _) in self.0.iter().enumerate() {
532 cols.push(as_string_array(col.column_by_name(&idx.to_string()).unwrap()).clone());
533 }
534 Ok(StringsDecoder(cols))
535 }
536 fn encoder(&self) -> Result<Self::Encoder, anyhow::Error> {
537 let mut fields = Vec::new();
538 let mut arrays = Vec::new();
539 for (idx, nullable) in self.0.iter().enumerate() {
540 fields.push(Field::new(idx.to_string(), DataType::Utf8, *nullable));
541 arrays.push(StringBuilder::new());
542 }
543 Ok(StringsEncoder { fields, arrays })
544 }
545 }
546
547 #[derive(Debug)]
548 struct StringsDecoder(Vec<StringArray>);
549 impl ColumnDecoder<Strings> for StringsDecoder {
550 fn decode(&self, idx: usize, val: &mut Strings) {
551 val.0.clear();
552 for col in self.0.iter() {
553 if col.is_valid(idx) {
554 val.0.push(col.value(idx).into());
555 } else {
556 val.0.push("".into());
557 }
558 }
559 }
560 fn is_null(&self, _: usize) -> bool {
561 false
562 }
563 fn goodbytes(&self) -> usize {
564 self.0
565 .iter()
566 .map(|val| ArrayOrd::String(val.clone()).goodbytes())
567 .sum()
568 }
569 fn stats(&self) -> StructStats {
570 StructStats {
571 len: self.0[0].len(),
572 cols: Default::default(),
573 }
574 }
575 }
576
577 #[derive(Debug)]
578 struct StringsEncoder {
579 fields: Vec<Field>,
580 arrays: Vec<StringBuilder>,
581 }
582 impl ColumnEncoder<Strings> for StringsEncoder {
583 type FinishedColumn = StructArray;
584
585 fn goodbytes(&self) -> usize {
586 self.arrays.iter().map(|a| a.values_slice().len()).sum()
587 }
588
589 fn append(&mut self, val: &Strings) {
590 for (idx, val) in val.0.iter().enumerate() {
591 if val.is_empty() {
592 self.arrays[idx].append_null();
593 } else {
594 self.arrays[idx].append_value(val);
595 }
596 }
597 }
598 fn append_null(&mut self) {
599 unreachable!()
600 }
601 fn finish(self) -> Self::FinishedColumn {
602 assert_eq!(self.fields.len(), self.arrays.len(), "invalid schema");
603 if self.fields.is_empty() {
604 StructArray::new_empty_fields(0, None)
605 } else {
606 let arrays = self
607 .arrays
608 .into_iter()
609 .map(|mut x| ArrayBuilder::finish(&mut x))
610 .collect();
611 StructArray::new(self.fields.into(), arrays, None)
612 }
613 }
614 }
615
616 #[mz_persist_proc::test(tokio::test)]
617 #[cfg_attr(miri, ignore)]
618 async fn compare_and_evolve_schema(dyncfgs: ConfigUpdates) {
619 let client = new_test_client(&dyncfgs).await;
620 let d = Diagnostics::for_tests();
621 let shard_id = ShardId::new();
622 let schema0 = StringsSchema(vec![false]);
623 let schema1 = StringsSchema(vec![false, true]);
624
625 let mut write0 = client
626 .open_writer::<Strings, (), u64, i64>(
627 shard_id,
628 Arc::new(schema0.clone()),
629 Arc::new(UnitSchema),
630 d.clone(),
631 )
632 .await
633 .unwrap();
634
635 write0.try_register_schema().await;
636 assert_eq!(write0.write_schemas.id.unwrap(), SchemaId(0));
637
638 let res = client
641 .compare_and_evolve_schema::<Strings, (), u64, i64>(
642 shard_id,
643 SchemaId(0),
644 &StringsSchema(vec![]),
645 &UnitSchema,
646 d.clone(),
647 )
648 .await
649 .unwrap();
650 assert_eq!(res, CaESchema::Incompatible);
651
652 let res = client
654 .compare_and_evolve_schema::<Strings, (), u64, i64>(
655 shard_id,
656 SchemaId(1),
657 &schema1,
658 &UnitSchema,
659 d.clone(),
660 )
661 .await
662 .unwrap();
663 assert_eq!(
664 res,
665 CaESchema::ExpectedMismatch {
666 schema_id: SchemaId(0),
667 key: schema0,
668 val: UnitSchema
669 }
670 );
671
672 let res = client
674 .compare_and_evolve_schema::<Strings, (), u64, i64>(
675 shard_id,
676 SchemaId(0),
677 &schema1,
678 &UnitSchema,
679 d.clone(),
680 )
681 .await
682 .unwrap();
683 assert_eq!(res, CaESchema::Ok(SchemaId(1)));
684
685 let write1 = client
688 .open_writer::<Strings, (), u64, i64>(
689 shard_id,
690 Arc::new(schema1),
691 Arc::new(UnitSchema),
692 d.clone(),
693 )
694 .await
695 .unwrap();
696 assert_eq!(write1.write_schemas.id.unwrap(), SchemaId(1));
697 }
698
699 #[derive(Debug)]
708 struct LoseOneCasResponse {
709 inner: Arc<dyn Consensus>,
710 armed: Arc<AtomicBool>,
711 }
712
713 #[async_trait]
714 impl Consensus for LoseOneCasResponse {
715 fn list_keys(&self) -> ResultStream<'_, String> {
716 self.inner.list_keys()
717 }
718
719 async fn head(&self, key: &str) -> Result<Option<VersionedData>, ExternalError> {
720 self.inner.head(key).await
721 }
722
723 async fn compare_and_set(
724 &self,
725 key: &str,
726 new: VersionedData,
727 ) -> Result<CaSResult, ExternalError> {
728 let res = self.inner.compare_and_set(key, new).await;
731 if self.armed.swap(false, Ordering::SeqCst) {
732 return Err(ExternalError::new_timeout(Instant::now()));
733 }
734 res
735 }
736
737 async fn scan(
738 &self,
739 key: &str,
740 from: SeqNo,
741 limit: usize,
742 ) -> Result<Vec<VersionedData>, ExternalError> {
743 self.inner.scan(key, from, limit).await
744 }
745
746 async fn truncate(&self, key: &str, seqno: SeqNo) -> Result<Option<usize>, ExternalError> {
747 self.inner.truncate(key, seqno).await
748 }
749 }
750
751 #[mz_ore::test(tokio::test)]
757 #[cfg_attr(miri, ignore)]
758 async fn compare_and_evolve_schema_indeterminate_retry() {
759 let cfg = PersistConfig::new_for_tests();
760 let armed = Arc::new(AtomicBool::new(false));
761 let consensus = Arc::new(LoseOneCasResponse {
762 inner: Arc::new(MemConsensus::default()),
763 armed: Arc::clone(&armed),
764 });
765 let metrics = Arc::new(Metrics::new(&cfg, &MetricsRegistry::new()));
766 let client = PersistClient::new(
767 cfg,
768 Arc::new(MemBlob::open(MemBlobConfig::default())),
769 consensus,
770 Arc::clone(&metrics),
771 Arc::new(IsolatedRuntime::new_for_tests()),
772 Arc::new(StateCache::new_no_metrics()),
773 Arc::new(NoopPubSubSender),
774 )
775 .expect("client construction failed");
776
777 let d = Diagnostics::for_tests();
778 let shard_id = ShardId::new();
779 let schema0 = StringsSchema(vec![false]);
780 let schema1 = StringsSchema(vec![false, true]);
781
782 let mut write0 = client
783 .open_writer::<Strings, (), u64, i64>(
784 shard_id,
785 Arc::new(schema0),
786 Arc::new(UnitSchema),
787 d.clone(),
788 )
789 .await
790 .unwrap();
791 write0.try_register_schema().await;
792 assert_eq!(write0.write_schemas.id.unwrap(), SchemaId(0));
793
794 let retries_before = metrics.retries.idempotent_cmd.retries.get();
798 armed.store(true, Ordering::SeqCst);
799 let res = client
800 .compare_and_evolve_schema::<Strings, (), u64, i64>(
801 shard_id,
802 SchemaId(0),
803 &schema1,
804 &UnitSchema,
805 d.clone(),
806 )
807 .await
808 .unwrap();
809 assert!(
810 !armed.load(Ordering::SeqCst),
811 "indeterminate error was never injected"
812 );
813 assert!(
814 metrics.retries.idempotent_cmd.retries.get() > retries_before,
815 "evolve did not retry an indeterminate error"
816 );
817 assert_eq!(res, CaESchema::Ok(SchemaId(1)));
819 }
820
821 fn strings(xs: &[((Strings, ()), u64, i64)]) -> Vec<Vec<&str>> {
822 xs.iter()
823 .map(|((k, _), _, _)| k.0.iter().map(|x| x.as_str()).collect())
824 .collect()
825 }
826
827 #[mz_persist_proc::test(tokio::test)]
828 #[cfg_attr(miri, ignore)]
829 async fn schema_evolution(dyncfgs: ConfigUpdates) {
830 async fn snap_streaming(
831 as_of: u64,
832 read: &mut ReadHandle<Strings, (), u64, i64>,
833 ) -> Vec<((Strings, ()), u64, i64)> {
834 let mut ret = read
837 .snapshot_and_stream(Antichain::from_elem(as_of))
838 .await
839 .unwrap()
840 .collect::<Vec<_>>()
841 .await;
842 ret.sort();
843 ret
844 }
845
846 let client = new_test_client(&dyncfgs).await;
847 let d = Diagnostics::for_tests();
848 let shard_id = ShardId::new();
849 let schema0 = StringsSchema(vec![false]);
850 let schema1 = StringsSchema(vec![false, true]);
851
852 let (mut write0, mut read0) = client
854 .open::<Strings, (), u64, i64>(
855 shard_id,
856 Arc::new(schema0.clone()),
857 Arc::new(UnitSchema),
858 d.clone(),
859 true,
860 )
861 .await
862 .unwrap();
863 write0
864 .expect_compare_and_append(&[((Strings(vec!["0 before".into()]), ()), 0, 1)], 0, 1)
865 .await;
866 let expected = vec![vec!["0 before"]];
867 assert_eq!(strings(&snap_streaming(0, &mut read0).await), expected);
868 assert_eq!(strings(&read0.expect_snapshot_and_fetch(0).await), expected);
869
870 let res = client
872 .compare_and_evolve_schema::<Strings, (), u64, i64>(
873 shard_id,
874 SchemaId(0),
875 &schema1,
876 &UnitSchema,
877 d.clone(),
878 )
879 .await
880 .unwrap();
881 assert_eq!(res, CaESchema::Ok(SchemaId(1)));
882 let (mut write1, mut read1) = client
883 .open::<Strings, (), u64, i64>(
884 shard_id,
885 Arc::new(schema1.clone()),
886 Arc::new(UnitSchema),
887 d.clone(),
888 true,
889 )
890 .await
891 .unwrap();
892 write1
893 .expect_compare_and_append(
894 &[
895 ((Strings(vec!["1 null".into(), "".into()]), ()), 1, 1),
896 ((Strings(vec!["1 not".into(), "x".into()]), ()), 1, 1),
897 ],
898 1,
899 2,
900 )
901 .await;
902
903 write0
905 .expect_compare_and_append(&[((Strings(vec!["0 after".into()]), ()), 2, 1)], 2, 3)
906 .await;
907
908 let expected = vec![
910 vec!["0 after"],
911 vec!["0 before"],
912 vec!["1 not"],
913 vec!["1 null"],
914 ];
915 assert_eq!(strings(&snap_streaming(2, &mut read0).await), expected);
916 assert_eq!(strings(&read0.expect_snapshot_and_fetch(2).await), expected);
917
918 let expected = vec![
921 vec!["0 after", ""],
922 vec!["0 before", ""],
923 vec!["1 not", "x"],
924 vec!["1 null", ""],
925 ];
926 assert_eq!(strings(&snap_streaming(2, &mut read1).await), expected);
927 assert_eq!(strings(&read1.expect_snapshot_and_fetch(2).await), expected);
928
929 if false {
932 info_log_non_zero_metrics(&client.metrics.registry.gather());
933 }
934 }
935}