Skip to main content

mz_repr/
relation.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::collections::{BTreeMap, BTreeSet};
11#[cfg(any(test, feature = "proptest"))]
12use std::rc::Rc;
13use std::{fmt, vec};
14
15use anyhow::bail;
16use itertools::Itertools;
17use mz_ore::cast::CastFrom;
18use mz_ore::soft_panic_or_log;
19use mz_ore::str::StrExt;
20use mz_ore::{assert_none, assert_ok};
21use mz_persist_types::schema::SchemaId;
22use mz_proto::{IntoRustIfSome, ProtoType, RustType, TryFromProtoError};
23#[cfg(any(test, feature = "proptest"))]
24use proptest::prelude::*;
25#[cfg(any(test, feature = "proptest"))]
26use proptest::strategy::{Strategy, Union};
27#[cfg(any(test, feature = "proptest"))]
28use proptest_derive::Arbitrary;
29use serde::{Deserialize, Serialize};
30
31#[cfg(any(test, feature = "proptest"))]
32use crate::Row;
33#[cfg(any(test, feature = "proptest"))]
34use crate::arb_datum_for_column;
35use crate::relation_and_scalar::proto_relation_type::ProtoKey;
36pub use crate::relation_and_scalar::{
37    ProtoColumnMetadata, ProtoColumnName, ProtoColumnType, ProtoRelationDesc, ProtoRelationType,
38    ProtoRelationVersion,
39};
40use crate::{Datum, ReprScalarType, SqlScalarType};
41
42/// The type of a [`Datum`].
43///
44/// [`SqlColumnType`] bundles information about the scalar type of a datum (e.g.,
45/// Int32 or String) with its nullability.
46///
47/// To construct a column type, either initialize the struct directly, or
48/// use the [`SqlScalarType::nullable`] method.
49#[derive(
50    Clone,
51    Debug,
52    Eq,
53    PartialEq,
54    Ord,
55    PartialOrd,
56    Serialize,
57    Deserialize,
58    Hash
59)]
60#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
61pub struct SqlColumnType {
62    /// The underlying scalar type (e.g., Int32 or String) of this column.
63    pub scalar_type: SqlScalarType,
64    /// Whether this datum can be null.
65    #[serde(default = "return_true")]
66    pub nullable: bool,
67}
68
69/// This method exists solely for the purpose of making SqlColumnType nullable by
70/// default in unit tests. The default value of a bool is false, and the only
71/// way to make an object take on any other value by default is to pass it a
72/// function that returns the desired default value. See
73/// <https://github.com/serde-rs/serde/issues/1030>
74#[inline(always)]
75fn return_true() -> bool {
76    true
77}
78
79impl SqlColumnType {
80    /// Compute the least upper bound of many column types, returning an error on
81    /// incompatible types or an empty iterator.
82    /// See [`SqlColumnType::try_union`] for details.
83    pub fn try_union_many<'a>(
84        typs: impl IntoIterator<Item = &'a Self>,
85    ) -> Result<Self, anyhow::Error> {
86        let mut iter = typs.into_iter();
87        let Some(typ) = iter.next() else {
88            bail!("Cannot union empty iterator");
89        };
90        iter.try_fold(typ.clone(), |a, b| a.try_union(b))
91    }
92
93    /// Compute the least upper bound of many column types.
94    /// See [`SqlColumnType::try_union`] for details.
95    ///
96    /// Panics on incompatible types or an empty iterator.
97    pub fn union_many<'a>(typs: impl IntoIterator<Item = &'a Self>) -> Self {
98        Self::try_union_many(typs).expect("Cannot union empty iterator")
99    }
100
101    /// Backports nullability information from `backport_typ` into `self`,
102    /// affecting the outer `.nullable` field but also record fields deeper
103    /// into the type.
104    pub fn backport_nullability(&mut self, backport_typ: &ReprColumnType) {
105        self.scalar_type
106            .backport_nullability(&backport_typ.scalar_type);
107        self.nullable = backport_typ.nullable;
108    }
109
110    /// Compute the least upper bound of two column types at the SQL level.
111    ///
112    /// Nullability is the disjunction of the two inputs, at every nesting depth.
113    /// See [`SqlScalarType::sql_union`] for which types are compatible.
114    ///
115    /// Returns an error for incompatible types, e.g. `Text` and `Int32`, or
116    /// `Text` and `VarChar`. See [`SqlColumnType::try_union`] for a fallback
117    /// that handles the latter via repr-level union.
118    pub fn sql_union(&self, other: &Self) -> Result<Self, anyhow::Error> {
119        Ok(SqlColumnType {
120            scalar_type: self.scalar_type.sql_union(&other.scalar_type)?,
121            nullable: self.nullable || other.nullable,
122        })
123    }
124
125    /// Compute the least upper bound of two column types.
126    ///
127    /// Attempts [`SqlColumnType::sql_union`] first, which preserves SQL-level type
128    /// information (e.g. modifiers). Falls back to a repr-level union via
129    /// [`ReprColumnType::union`] when the SQL types are incompatible but the
130    /// underlying repr types are compatible.
131    ///
132    /// The resulting nullability is the disjunction of the two input
133    /// nullabilities.
134    pub fn try_union(&self, other: &Self) -> Result<Self, anyhow::Error> {
135        self.sql_union(other).or_else(|e| {
136            let repr_self = ReprColumnType::from(self);
137            let repr_other = ReprColumnType::from(other);
138            match repr_self.union(&repr_other) {
139                Ok(typ) => {
140                    // sql_union failed but repr union succeeded — this indicates
141                    // a repr-type canonicalization gap that we want CI visibility for.
142                    soft_panic_or_log!("repr type error: sql_union({self:?}, {other:?}): {e}");
143                    Ok(SqlColumnType::from_repr(&typ))
144                }
145                Err(_) => {
146                    // Both sql_union and repr union failed — genuine type mismatch,
147                    // not a canonicalization issue. Just propagate the original error.
148                    Err(e)
149                }
150            }
151        })
152    }
153
154    /// Compute the least upper bound of two column types.
155    /// See [`SqlColumnType::try_union`] for details.
156    ///
157    /// Panics on incompatible types.
158    pub fn union(&self, other: &Self) -> Self {
159        self.try_union(other).unwrap_or_else(|e| {
160            panic!("repr type error: after sql_union({self:?}, {other:?}) error: {e}")
161        })
162    }
163
164    /// Consumes this `SqlColumnType` and returns a new `SqlColumnType` with its
165    /// nullability set to the specified boolean.
166    pub fn nullable(mut self, nullable: bool) -> Self {
167        self.nullable = nullable;
168        self
169    }
170}
171
172impl RustType<ProtoColumnType> for SqlColumnType {
173    fn into_proto(&self) -> ProtoColumnType {
174        ProtoColumnType {
175            nullable: self.nullable,
176            scalar_type: Some(self.scalar_type.into_proto()),
177        }
178    }
179
180    fn from_proto(proto: ProtoColumnType) -> Result<Self, TryFromProtoError> {
181        Ok(SqlColumnType {
182            nullable: proto.nullable,
183            scalar_type: proto
184                .scalar_type
185                .into_rust_if_some("ProtoColumnType::scalar_type")?,
186        })
187    }
188}
189
190impl fmt::Display for SqlColumnType {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        let nullable = if self.nullable { "Null" } else { "NotNull" };
193        f.write_fmt(format_args!("{:?}:{}", self.scalar_type, nullable))
194    }
195}
196
197/// The type of a relation.
198#[derive(
199    Clone,
200    Debug,
201    Eq,
202    PartialEq,
203    Ord,
204    PartialOrd,
205    Serialize,
206    Deserialize,
207    Hash
208)]
209#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
210pub struct SqlRelationType {
211    /// The type for each column, in order.
212    pub column_types: Vec<SqlColumnType>,
213    /// Sets of indices that are "keys" for the collection.
214    ///
215    /// Each element in this list is a set of column indices, each with the
216    /// property that the collection contains at most one record with each
217    /// distinct set of values for each column. Alternately, for a specific set
218    /// of values assigned to the these columns there is at most one record.
219    ///
220    /// A collection can contain multiple sets of keys, although it is common to
221    /// have either zero or one sets of key indices.
222    #[serde(default)]
223    pub keys: Vec<Vec<usize>>,
224}
225
226impl SqlRelationType {
227    /// Constructs a `SqlRelationType` representing the relation with no columns and
228    /// no keys.
229    pub fn empty() -> Self {
230        SqlRelationType::new(vec![])
231    }
232
233    /// Constructs a new `SqlRelationType` from specified column types.
234    ///
235    /// The `SqlRelationType` will have no keys.
236    pub fn new(column_types: Vec<SqlColumnType>) -> Self {
237        SqlRelationType {
238            column_types,
239            keys: Vec::new(),
240        }
241    }
242
243    /// Adds a new key for the relation.
244    pub fn with_key(mut self, mut indices: Vec<usize>) -> Self {
245        indices.sort_unstable();
246        if !self.keys.contains(&indices) {
247            self.keys.push(indices);
248        }
249        self
250    }
251
252    pub fn with_keys(mut self, keys: Vec<Vec<usize>>) -> Self {
253        for key in keys {
254            self = self.with_key(key)
255        }
256        self
257    }
258
259    /// Computes the number of columns in the relation.
260    pub fn arity(&self) -> usize {
261        self.column_types.len()
262    }
263
264    /// Gets the index of the columns used when creating a default index.
265    pub fn default_key(&self) -> Vec<usize> {
266        if let Some(key) = self.keys.first() {
267            if key.is_empty() {
268                (0..self.column_types.len()).collect()
269            } else {
270                key.clone()
271            }
272        } else {
273            (0..self.column_types.len()).collect()
274        }
275    }
276
277    /// Returns all the [`SqlColumnType`]s, in order, for this relation.
278    pub fn columns(&self) -> &[SqlColumnType] {
279        &self.column_types
280    }
281
282    /// Adopts the nullability and keys from another `SqlRelationType`.
283    ///
284    /// Panics if the number of columns does not match.
285    pub fn backport_nullability_and_keys(&mut self, backport_typ: &ReprRelationType) {
286        assert_eq!(
287            backport_typ.column_types.len(),
288            self.column_types.len(),
289            "HIR and MIR types should have the same number of columns"
290        );
291        for (backport_col, sql_col) in backport_typ
292            .column_types
293            .iter()
294            .zip_eq(self.column_types.iter_mut())
295        {
296            sql_col.backport_nullability(backport_col);
297        }
298
299        self.keys = backport_typ.keys.clone();
300    }
301
302    /// Constructs a `SqlRelationType` from a `ReprRelationType` by converting
303    /// each column type via [`SqlColumnType::from_repr`]. This is a lossy
304    /// inverse of `ReprRelationType::from(&SqlRelationType)`.
305    pub fn from_repr(repr: &ReprRelationType) -> Self {
306        SqlRelationType {
307            column_types: repr
308                .column_types
309                .iter()
310                .map(SqlColumnType::from_repr)
311                .collect(),
312            keys: repr.keys.clone(),
313        }
314    }
315}
316
317impl RustType<ProtoRelationType> for SqlRelationType {
318    fn into_proto(&self) -> ProtoRelationType {
319        ProtoRelationType {
320            column_types: self.column_types.into_proto(),
321            keys: self.keys.into_proto(),
322        }
323    }
324
325    fn from_proto(proto: ProtoRelationType) -> Result<Self, TryFromProtoError> {
326        Ok(SqlRelationType {
327            column_types: proto.column_types.into_rust()?,
328            keys: proto.keys.into_rust()?,
329        })
330    }
331}
332
333impl RustType<ProtoKey> for Vec<usize> {
334    fn into_proto(&self) -> ProtoKey {
335        ProtoKey {
336            keys: self.into_proto(),
337        }
338    }
339
340    fn from_proto(proto: ProtoKey) -> Result<Self, TryFromProtoError> {
341        proto.keys.into_rust()
342    }
343}
344
345/// The type of a relation.
346#[derive(
347    Clone,
348    Debug,
349    Eq,
350    PartialEq,
351    Ord,
352    PartialOrd,
353    Serialize,
354    Deserialize,
355    Hash
356)]
357pub struct ReprRelationType {
358    /// The type for each column, in order.
359    pub column_types: Vec<ReprColumnType>,
360    /// Sets of indices that are "keys" for the collection.
361    ///
362    /// Each element in this list is a set of column indices, each with the
363    /// property that the collection contains at most one record with each
364    /// distinct set of values for each column. Alternately, for a specific set
365    /// of values assigned to the these columns there is at most one record.
366    ///
367    /// A collection can contain multiple sets of keys, although it is common to
368    /// have either zero or one sets of key indices.
369    #[serde(default)]
370    pub keys: Vec<Vec<usize>>,
371}
372
373impl ReprRelationType {
374    /// Constructs a `ReprRelationType` representing the relation with no columns and
375    /// no keys.
376    pub fn empty() -> Self {
377        ReprRelationType::new(vec![])
378    }
379
380    /// Constructs a new `ReprRelationType` from specified column types.
381    ///
382    /// The `ReprRelationType` will have no keys.
383    pub fn new(column_types: Vec<ReprColumnType>) -> Self {
384        ReprRelationType {
385            column_types,
386            keys: Vec::new(),
387        }
388    }
389
390    /// Adds a new key for the relation.
391    pub fn with_key(mut self, mut indices: Vec<usize>) -> Self {
392        indices.sort_unstable();
393        if !self.keys.contains(&indices) {
394            self.keys.push(indices);
395        }
396        self
397    }
398
399    pub fn with_keys(mut self, keys: Vec<Vec<usize>>) -> Self {
400        for key in keys {
401            self = self.with_key(key)
402        }
403        self
404    }
405
406    /// Computes the number of columns in the relation.
407    pub fn arity(&self) -> usize {
408        self.column_types.len()
409    }
410
411    /// Gets the index of the columns used when creating a default index.
412    pub fn default_key(&self) -> Vec<usize> {
413        if let Some(key) = self.keys.first() {
414            if key.is_empty() {
415                (0..self.column_types.len()).collect()
416            } else {
417                key.clone()
418            }
419        } else {
420            (0..self.column_types.len()).collect()
421        }
422    }
423
424    /// Returns all the column types in order, for this relation.
425    pub fn columns(&self) -> &[ReprColumnType] {
426        &self.column_types
427    }
428}
429
430impl From<&SqlRelationType> for ReprRelationType {
431    fn from(sql_relation_type: &SqlRelationType) -> Self {
432        ReprRelationType {
433            column_types: sql_relation_type
434                .column_types
435                .iter()
436                .map(ReprColumnType::from)
437                .collect(),
438            keys: sql_relation_type.keys.clone(),
439        }
440    }
441}
442
443#[derive(
444    Clone,
445    Debug,
446    Eq,
447    PartialEq,
448    Ord,
449    PartialOrd,
450    Serialize,
451    Deserialize,
452    Hash
453)]
454pub struct ReprColumnType {
455    /// The underlying representation scalar type (e.g., Int32 or String) of this column.
456    pub scalar_type: ReprScalarType,
457    /// Whether this datum can be null.
458    #[serde(default = "return_true")]
459    pub nullable: bool,
460}
461
462impl std::fmt::Display for ReprColumnType {
463    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464        write!(f, "{}", self.scalar_type)?;
465        if self.nullable {
466            write!(f, "?")?;
467        }
468        Ok(())
469    }
470}
471
472impl ReprColumnType {
473    /// Compute the least upper bound of two column types at the repr level.
474    ///
475    /// More permissive than [`SqlColumnType::sql_union`] because it operates
476    /// on the underlying representation types, ignoring SQL-level distinctions
477    /// such as modifiers.
478    /// The resulting nullability is the disjunction of the two inputs.
479    pub fn union(&self, col: &ReprColumnType) -> Result<Self, anyhow::Error> {
480        let scalar_type = self.scalar_type.union(&col.scalar_type)?;
481        let nullable = self.nullable || col.nullable;
482
483        Ok(ReprColumnType {
484            scalar_type,
485            nullable,
486        })
487    }
488}
489
490impl From<&SqlColumnType> for ReprColumnType {
491    fn from(sql_column_type: &SqlColumnType) -> Self {
492        let scalar_type = &sql_column_type.scalar_type;
493        let scalar_type = scalar_type.into();
494        let nullable = sql_column_type.nullable;
495
496        ReprColumnType {
497            scalar_type,
498            nullable,
499        }
500    }
501}
502
503impl SqlColumnType {
504    /// Lossily translates a [`ReprColumnType`] back to a [`SqlColumnType`].
505    ///
506    /// See [`SqlScalarType::from_repr`] for an example of lossiness.
507    pub fn from_repr(repr: &ReprColumnType) -> Self {
508        let scalar_type = &repr.scalar_type;
509        let scalar_type = SqlScalarType::from_repr(scalar_type);
510        let nullable = repr.nullable;
511
512        SqlColumnType {
513            scalar_type,
514            nullable,
515        }
516    }
517}
518
519/// The name of a column in a [`RelationDesc`].
520#[derive(
521    Clone,
522    Debug,
523    Eq,
524    PartialEq,
525    Ord,
526    PartialOrd,
527    Serialize,
528    Deserialize,
529    Hash
530)]
531pub struct ColumnName(Box<str>);
532
533impl ColumnName {
534    /// Returns this column name as a `str`.
535    #[inline(always)]
536    pub fn as_str(&self) -> &str {
537        &*self
538    }
539
540    /// Returns this column name as a `&mut Box<str>`.
541    pub fn as_mut_boxed_str(&mut self) -> &mut Box<str> {
542        &mut self.0
543    }
544
545    /// Returns if this [`ColumnName`] is similar to the provided one.
546    pub fn is_similar(&self, other: &ColumnName) -> bool {
547        const SIMILARITY_THRESHOLD: f64 = 0.6;
548
549        let a_lowercase = self.to_lowercase();
550        let b_lowercase = other.to_lowercase();
551
552        strsim::normalized_levenshtein(&a_lowercase, &b_lowercase) >= SIMILARITY_THRESHOLD
553    }
554}
555
556impl std::ops::Deref for ColumnName {
557    type Target = str;
558
559    #[inline(always)]
560    fn deref(&self) -> &Self::Target {
561        &self.0
562    }
563}
564
565impl fmt::Display for ColumnName {
566    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
567        f.write_str(&self.0)
568    }
569}
570
571impl From<String> for ColumnName {
572    fn from(s: String) -> ColumnName {
573        ColumnName(s.into())
574    }
575}
576
577impl From<&str> for ColumnName {
578    fn from(s: &str) -> ColumnName {
579        ColumnName(s.into())
580    }
581}
582
583impl From<&ColumnName> for ColumnName {
584    fn from(n: &ColumnName) -> ColumnName {
585        n.clone()
586    }
587}
588
589impl RustType<ProtoColumnName> for ColumnName {
590    fn into_proto(&self) -> ProtoColumnName {
591        ProtoColumnName {
592            value: Some(self.0.to_string()),
593        }
594    }
595
596    fn from_proto(proto: ProtoColumnName) -> Result<Self, TryFromProtoError> {
597        Ok(ColumnName(
598            proto
599                .value
600                .ok_or_else(|| TryFromProtoError::missing_field("ProtoColumnName::value"))?
601                .into(),
602        ))
603    }
604}
605
606#[cfg(any(test, feature = "proptest"))]
607impl proptest::arbitrary::Arbitrary for ColumnName {
608    type Parameters = ();
609    type Strategy = BoxedStrategy<ColumnName>;
610
611    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
612        // Long column names are generally uninteresting, and can greatly
613        // increase the runtime for a test case, so bound the max length.
614        let mut weights = vec![(50, Just(1..8)), (20, Just(8..16))];
615        if std::env::var("PROPTEST_LARGE_DATA").is_ok() {
616            weights.extend([
617                (5, Just(16..128)),
618                (1, Just(128..1024)),
619                (1, Just(1024..4096)),
620            ]);
621        }
622        let name_length = Union::new_weighted(weights);
623
624        // Non-ASCII characters are also generally uninteresting and can make
625        // debugging harder.
626        let char_strat = Rc::new(Union::new_weighted(vec![
627            (50, proptest::char::range('A', 'z').boxed()),
628            (1, any::<char>().boxed()),
629        ]));
630
631        name_length
632            .prop_flat_map(move |length| proptest::collection::vec(Rc::clone(&char_strat), length))
633            .prop_map(|chars| ColumnName(chars.into_iter().collect::<Box<str>>()))
634            .no_shrink()
635            .boxed()
636    }
637}
638
639/// Default name of a column (when no other information is known).
640pub const UNKNOWN_COLUMN_NAME: &str = "?column?";
641
642/// Stable index of a column in a [`RelationDesc`].
643#[derive(
644    Clone,
645    Copy,
646    Debug,
647    Eq,
648    PartialEq,
649    PartialOrd,
650    Ord,
651    Serialize,
652    Deserialize,
653    Hash
654)]
655pub struct ColumnIndex(usize);
656
657#[cfg(any(test, feature = "proptest"))]
658static_assertions::assert_not_impl_all!(ColumnIndex: Arbitrary);
659
660impl ColumnIndex {
661    /// Returns a stable identifier for this [`ColumnIndex`].
662    pub fn to_stable_name(&self) -> String {
663        self.0.to_string()
664    }
665
666    pub fn to_raw(&self) -> usize {
667        self.0
668    }
669
670    pub fn from_raw(val: usize) -> Self {
671        ColumnIndex(val)
672    }
673}
674
675/// The version a given column was added at.
676#[derive(
677    Clone,
678    Copy,
679    Debug,
680    Eq,
681    PartialEq,
682    PartialOrd,
683    Ord,
684    Serialize,
685    Deserialize,
686    Hash
687)]
688#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
689pub struct RelationVersion(u64);
690
691impl RelationVersion {
692    /// Returns the "root" or "initial" version of a [`RelationDesc`].
693    pub fn root() -> Self {
694        RelationVersion(0)
695    }
696
697    /// Returns an instance of [`RelationVersion`] which is "one" higher than `self`.
698    pub fn bump(&self) -> Self {
699        let next_version = self
700            .0
701            .checked_add(1)
702            .expect("added more than u64::MAX columns?");
703        RelationVersion(next_version)
704    }
705
706    /// Consume a [`RelationVersion`] returning the raw value.
707    ///
708    /// Should __only__ be used for serialization.
709    pub fn into_raw(self) -> u64 {
710        self.0
711    }
712
713    /// Create a [`RelationVersion`] from a raw value.
714    ///
715    /// Should __only__ be used for serialization.
716    pub fn from_raw(val: u64) -> RelationVersion {
717        RelationVersion(val)
718    }
719}
720
721impl From<RelationVersion> for SchemaId {
722    fn from(value: RelationVersion) -> Self {
723        SchemaId(usize::cast_from(value.0))
724    }
725}
726
727impl fmt::Display for RelationVersion {
728    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729        write!(f, "v{}", self.0)
730    }
731}
732
733impl RustType<ProtoRelationVersion> for RelationVersion {
734    fn into_proto(&self) -> ProtoRelationVersion {
735        ProtoRelationVersion { value: self.0 }
736    }
737
738    fn from_proto(proto: ProtoRelationVersion) -> Result<Self, TryFromProtoError> {
739        Ok(RelationVersion(proto.value))
740    }
741}
742
743/// Semantic type annotation for a column in a builtin catalog relation.
744///
745/// These are compile-time metadata used by the catalog ontology layer to
746/// describe the meaning of a column (e.g., that it contains a catalog item ID
747/// or a role ID). Possible values correspond to the entries in
748/// `SEMANTIC_TYPE_DEFS` in the `mz-catalog` crate.
749#[derive(
750    Clone,
751    Copy,
752    Debug,
753    PartialEq,
754    Eq,
755    PartialOrd,
756    Ord,
757    Hash,
758    serde::Serialize
759)]
760pub enum SemanticType {
761    CatalogItemId,
762    GlobalId,
763    ClusterId,
764    ReplicaId,
765    SchemaId,
766    DatabaseId,
767    RoleId,
768    NetworkPolicyId,
769    ShardId,
770    OID,
771    ObjectType,
772    ConnectionType,
773    SourceType,
774    MzTimestamp,
775    WallclockTimestamp,
776    ByteCount,
777    RecordCount,
778    CreditRate,
779    SqlDefinition,
780    RedactedSqlDefinition,
781}
782
783impl fmt::Display for SemanticType {
784    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
785        let s = match self {
786            SemanticType::CatalogItemId => "CatalogItemId",
787            SemanticType::GlobalId => "GlobalId",
788            SemanticType::ClusterId => "ClusterId",
789            SemanticType::ReplicaId => "ReplicaId",
790            SemanticType::SchemaId => "SchemaId",
791            SemanticType::DatabaseId => "DatabaseId",
792            SemanticType::RoleId => "RoleId",
793            SemanticType::NetworkPolicyId => "NetworkPolicyId",
794            SemanticType::ShardId => "ShardId",
795            SemanticType::OID => "OID",
796            SemanticType::ObjectType => "ObjectType",
797            SemanticType::ConnectionType => "ConnectionType",
798            SemanticType::SourceType => "SourceType",
799            SemanticType::MzTimestamp => "MzTimestamp",
800            SemanticType::WallclockTimestamp => "WallclockTimestamp",
801            SemanticType::ByteCount => "ByteCount",
802            SemanticType::RecordCount => "RecordCount",
803            SemanticType::CreditRate => "CreditRate",
804            SemanticType::SqlDefinition => "SqlDefinition",
805            SemanticType::RedactedSqlDefinition => "RedactedSqlDefinition",
806        };
807        f.write_str(s)
808    }
809}
810
811/// Metadata (other than type) for a column in a [`RelationDesc`].
812#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
813struct ColumnMetadata {
814    /// Name of the column.
815    name: ColumnName,
816    /// Index into a [`SqlRelationType`] for this column.
817    typ_idx: usize,
818    /// Version this column was added at.
819    added: RelationVersion,
820    /// Version this column was dropped at.
821    dropped: Option<RelationVersion>,
822}
823
824/// A description of the shape of a relation.
825///
826/// It bundles a [`SqlRelationType`] with `ColumnMetadata` for each column in
827/// the relation.
828///
829/// # Examples
830///
831/// A `RelationDesc`s is typically constructed via its builder API:
832///
833/// ```
834/// use mz_repr::{SqlColumnType, RelationDesc, SqlScalarType};
835///
836/// let desc = RelationDesc::builder()
837///     .with_column("id", SqlScalarType::Int64.nullable(false))
838///     .with_column("price", SqlScalarType::Float64.nullable(true))
839///     .finish();
840/// ```
841///
842/// In more complicated cases, like when constructing a `RelationDesc` in
843/// response to user input, it may be more convenient to construct a relation
844/// type first, and imbue it with column names to form a `RelationDesc` later:
845///
846/// ```
847/// use mz_repr::RelationDesc;
848///
849/// # fn plan_query(_: &str) -> mz_repr::SqlRelationType { mz_repr::SqlRelationType::new(vec![]) }
850/// let relation_type = plan_query("SELECT * FROM table");
851/// let names = (0..relation_type.arity()).map(|i| match i {
852///     0 => "first",
853///     1 => "second",
854///     _ => "unknown",
855/// });
856/// let desc = RelationDesc::new(relation_type, names);
857/// ```
858///
859/// Next to the [`SqlRelationType`] we maintain a map of `ColumnIndex` to
860/// `ColumnMetadata`, where [`ColumnIndex`] is a stable identifier for a
861/// column throughout the lifetime of the relation. This allows a
862/// [`RelationDesc`] to represent a projection over a version of itself.
863///
864/// ```
865/// use std::collections::BTreeSet;
866/// use mz_repr::{ColumnIndex, RelationDesc, SqlScalarType};
867///
868/// let desc = RelationDesc::builder()
869///     .with_column("name", SqlScalarType::String.nullable(false))
870///     .with_column("email", SqlScalarType::String.nullable(false))
871///     .finish();
872///
873/// // Project away the second column.
874/// let demands = BTreeSet::from([1]);
875/// let proj = desc.apply_demand(&demands);
876///
877/// // We projected away the first column.
878/// assert!(!proj.contains_index(&ColumnIndex::from_raw(0)));
879/// // But retained the second.
880/// assert!(proj.contains_index(&ColumnIndex::from_raw(1)));
881///
882/// // The underlying `SqlRelationType` also contains a single column.
883/// assert_eq!(proj.typ().arity(), 1);
884/// ```
885///
886/// To maintain this stable mapping and track the lifetime of a column (e.g.
887/// when adding or dropping a column) we use `ColumnMetadata`. It maintains
888/// the index in [`SqlRelationType`] that corresponds to a given column, and the
889/// version at which this column was added or dropped.
890///
891#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
892pub struct RelationDesc {
893    typ: SqlRelationType,
894    metadata: BTreeMap<ColumnIndex, ColumnMetadata>,
895}
896
897impl RustType<ProtoRelationDesc> for RelationDesc {
898    // NOTE: `ProtoRelationDesc` has no field for the `ColumnIndex` keys, only the values in
899    // `ColumnIndex` order, so a desc whose indexes are sparse (what
900    // `VersionedRelationDesc::at_version` returns once a column has been dropped, and what
901    // `RelationDesc::apply_demand` returns) comes back from `from_proto` renumbered to `0..n`.
902    // `ColumnIndex::to_stable_name` is the arrow field name that `RowColumnarEncoder` and
903    // `RowColumnarDecoder` agree on, so encoding a sparse desc as the schema of data written
904    // under the original indexes builds a decoder that looks up the wrong fields. Only dense
905    // descs may be handed to `Codec::encode_schema`.
906    fn into_proto(&self) -> ProtoRelationDesc {
907        let (names, metadata): (Vec<_>, Vec<_>) = self
908            .metadata
909            .values()
910            .map(|meta| {
911                let metadata = ProtoColumnMetadata {
912                    added: Some(meta.added.into_proto()),
913                    dropped: meta.dropped.map(|v| v.into_proto()),
914                };
915                (meta.name.into_proto(), metadata)
916            })
917            .unzip();
918
919        // `metadata` Migration Logic: We wrote some `ProtoRelationDesc`s into Persist before the
920        // metadata field was added. To make sure our serialization roundtrips the same as before
921        // we added the field, we omit `metadata` if all of the values are equal to the default.
922        //
923        // Note: This logic needs to exist approximately forever.
924        let is_all_default_metadata = metadata.iter().all(|meta| {
925            meta.added == Some(RelationVersion::root().into_proto()) && meta.dropped == None
926        });
927        let metadata = if is_all_default_metadata {
928            Vec::new()
929        } else {
930            metadata
931        };
932
933        ProtoRelationDesc {
934            typ: Some(self.typ.into_proto()),
935            names,
936            metadata,
937        }
938    }
939
940    fn from_proto(proto: ProtoRelationDesc) -> Result<Self, TryFromProtoError> {
941        let typ: SqlRelationType = proto.typ.into_rust_if_some("ProtoRelationDesc::typ")?;
942
943        // Reject shapes that `VersionedRelationDesc::validate` calls corruption. Nothing
944        // downstream catches them: they decode and re-encode cleanly, and only panic at first
945        // use, e.g. `iter()` indexing `typ.columns()[typ_idx]` out of bounds, or `into_iter()`
946        // tripping `zip_eq`. Both are reachable from untrusted proto bytes.
947        if proto.names.len() != typ.column_types.len() {
948            return Err(TryFromProtoError::InvalidFieldError(format!(
949                "ProtoRelationDesc: names ({}) and column_types ({}) length mismatch",
950                proto.names.len(),
951                typ.column_types.len()
952            )));
953        }
954        if let Some(key) = typ
955            .keys
956            .iter()
957            .flatten()
958            .find(|key| **key >= typ.column_types.len())
959        {
960            return Err(TryFromProtoError::InvalidFieldError(format!(
961                "ProtoRelationDesc: key index {key} out of bounds for {} columns",
962                typ.column_types.len()
963            )));
964        }
965
966        // `metadata` Migration Logic: We wrote some `ProtoRelationDesc`s into Persist before the
967        // metadata field was added. If the field doesn't exist we fill it in with default values,
968        // and when converting into_proto we omit these fields so the serialized bytes roundtrip.
969        //
970        // Note: This logic needs to exist approximately forever.
971        let proto_metadata: Box<dyn Iterator<Item = _>> = if proto.metadata.is_empty() {
972            let val = ProtoColumnMetadata {
973                added: Some(RelationVersion::root().into_proto()),
974                dropped: None,
975            };
976            Box::new(itertools::repeat_n(val, proto.names.len()))
977        } else {
978            // Reject mismatched lengths explicitly rather than panicking via
979            // `zip_eq` below, since this branch is reachable from untrusted
980            // proto bytes.
981            if proto.names.len() != proto.metadata.len() {
982                return Err(TryFromProtoError::InvalidFieldError(format!(
983                    "ProtoRelationDesc: names ({}) and metadata ({}) length mismatch",
984                    proto.names.len(),
985                    proto.metadata.len()
986                )));
987            }
988            Box::new(proto.metadata.into_iter())
989        };
990
991        let metadata = proto
992            .names
993            .into_iter()
994            .zip_eq(proto_metadata)
995            .enumerate()
996            .map(|(idx, (name, metadata))| {
997                let meta = ColumnMetadata {
998                    name: name.into_rust()?,
999                    typ_idx: idx,
1000                    added: metadata.added.into_rust_if_some("ColumnMetadata::added")?,
1001                    dropped: metadata.dropped.into_rust()?,
1002                };
1003                Ok::<_, TryFromProtoError>((ColumnIndex(idx), meta))
1004            })
1005            .collect::<Result<_, _>>()?;
1006
1007        Ok(RelationDesc { typ, metadata })
1008    }
1009}
1010
1011impl RelationDesc {
1012    /// Returns a [`RelationDescBuilder`] that can be used to construct a [`RelationDesc`].
1013    pub fn builder() -> RelationDescBuilder {
1014        RelationDescBuilder::default()
1015    }
1016
1017    /// Constructs a new `RelationDesc` that represents the empty relation
1018    /// with no columns and no keys.
1019    pub fn empty() -> Self {
1020        RelationDesc {
1021            typ: SqlRelationType::empty(),
1022            metadata: BTreeMap::default(),
1023        }
1024    }
1025
1026    /// Check if the `RelationDesc` is empty.
1027    pub fn is_empty(&self) -> bool {
1028        self == &Self::empty()
1029    }
1030
1031    /// Returns the number of columns in this [`RelationDesc`].
1032    pub fn len(&self) -> usize {
1033        self.typ().column_types.len()
1034    }
1035
1036    /// Constructs a new `RelationDesc` from a `SqlRelationType` and an iterator
1037    /// over column names.
1038    ///
1039    /// # Panics
1040    ///
1041    /// Panics if the arity of the `SqlRelationType` is not equal to the number of
1042    /// items in `names`.
1043    pub fn new<I, N>(typ: SqlRelationType, names: I) -> Self
1044    where
1045        I: IntoIterator<Item = N>,
1046        N: Into<ColumnName>,
1047    {
1048        let metadata: BTreeMap<_, _> = names
1049            .into_iter()
1050            .enumerate()
1051            .map(|(idx, name)| {
1052                let col_idx = ColumnIndex(idx);
1053                let metadata = ColumnMetadata {
1054                    name: name.into(),
1055                    typ_idx: idx,
1056                    added: RelationVersion::root(),
1057                    dropped: None,
1058                };
1059                (col_idx, metadata)
1060            })
1061            .collect();
1062
1063        // TODO(parkmycar): Add better validation here.
1064        assert_eq!(typ.column_types.len(), metadata.len());
1065
1066        RelationDesc { typ, metadata }
1067    }
1068
1069    pub fn from_names_and_types<I, T, N>(iter: I) -> Self
1070    where
1071        I: IntoIterator<Item = (N, T)>,
1072        T: Into<SqlColumnType>,
1073        N: Into<ColumnName>,
1074    {
1075        let (names, types): (Vec<_>, Vec<_>) = iter.into_iter().unzip();
1076        let types = types.into_iter().map(Into::into).collect();
1077        let typ = SqlRelationType::new(types);
1078        Self::new(typ, names)
1079    }
1080
1081    /// Concatenates a `RelationDesc` onto the end of this `RelationDesc`.
1082    ///
1083    /// # Panics
1084    ///
1085    /// Panics if either `self` or `other` have columns that were added at a
1086    /// [`RelationVersion`] other than [`RelationVersion::root`] or if any
1087    /// columns were dropped.
1088    ///
1089    /// TODO(parkmycar): Move this method to [`RelationDescBuilder`].
1090    pub fn concat(mut self, other: Self) -> Self {
1091        let self_len = self.typ.column_types.len();
1092
1093        for (typ, (_col_idx, meta)) in other.typ.column_types.into_iter().zip_eq(other.metadata) {
1094            assert_eq!(meta.added, RelationVersion::root());
1095            assert_none!(meta.dropped);
1096
1097            let new_idx = self.typ.columns().len();
1098            let new_meta = ColumnMetadata {
1099                name: meta.name,
1100                typ_idx: new_idx,
1101                added: RelationVersion::root(),
1102                dropped: None,
1103            };
1104
1105            self.typ.column_types.push(typ);
1106            let prev = self.metadata.insert(ColumnIndex(new_idx), new_meta);
1107
1108            assert_eq!(self.metadata.len(), self.typ.columns().len());
1109            assert_none!(prev);
1110        }
1111
1112        for k in other.typ.keys {
1113            let k = k.into_iter().map(|idx| idx + self_len).collect();
1114            self = self.with_key(k);
1115        }
1116        self
1117    }
1118
1119    /// Adds a new key for the relation.
1120    pub fn with_key(mut self, indices: Vec<usize>) -> Self {
1121        self.typ = self.typ.with_key(indices);
1122        self
1123    }
1124
1125    /// Drops all existing keys.
1126    pub fn without_keys(mut self) -> Self {
1127        self.typ.keys.clear();
1128        self
1129    }
1130
1131    /// Builds a new relation description with the column names replaced with
1132    /// new names.
1133    ///
1134    /// # Panics
1135    ///
1136    /// Panics if the arity of the relation type does not match the number of
1137    /// items in `names`.
1138    pub fn with_names<I, N>(self, names: I) -> Self
1139    where
1140        I: IntoIterator<Item = N>,
1141        N: Into<ColumnName>,
1142    {
1143        Self::new(self.typ, names)
1144    }
1145
1146    /// Computes the number of columns in the relation.
1147    pub fn arity(&self) -> usize {
1148        self.typ.arity()
1149    }
1150
1151    /// Returns the relation type underlying this relation description.
1152    pub fn typ(&self) -> &SqlRelationType {
1153        &self.typ
1154    }
1155
1156    /// Returns the owned relation type underlying this relation description.
1157    pub fn into_typ(self) -> SqlRelationType {
1158        self.typ
1159    }
1160
1161    /// Returns an iterator over the columns in this relation.
1162    pub fn iter(&self) -> impl Iterator<Item = (&ColumnName, &SqlColumnType)> {
1163        self.metadata.values().map(|meta| {
1164            let typ = &self.typ.columns()[meta.typ_idx];
1165            (&meta.name, typ)
1166        })
1167    }
1168
1169    /// Returns an iterator over the types of the columns in this relation.
1170    pub fn iter_types(&self) -> impl Iterator<Item = &SqlColumnType> {
1171        self.typ.column_types.iter()
1172    }
1173
1174    /// Returns an iterator over the names of the columns in this relation.
1175    pub fn iter_names(&self) -> impl Iterator<Item = &ColumnName> {
1176        self.metadata.values().map(|meta| &meta.name)
1177    }
1178
1179    /// Returns an iterator over the columns in this relation, with all their metadata.
1180    pub fn iter_all(&self) -> impl Iterator<Item = (&ColumnIndex, &ColumnName, &SqlColumnType)> {
1181        self.metadata.iter().map(|(col_idx, metadata)| {
1182            let col_typ = &self.typ.columns()[metadata.typ_idx];
1183            (col_idx, &metadata.name, col_typ)
1184        })
1185    }
1186
1187    /// Returns an iterator over the names of the columns in this relation that are "similar" to
1188    /// the provided `name`.
1189    pub fn iter_similar_names<'a>(
1190        &'a self,
1191        name: &'a ColumnName,
1192    ) -> impl Iterator<Item = &'a ColumnName> {
1193        self.iter_names().filter(|n| n.is_similar(name))
1194    }
1195
1196    /// Returns whether this [`RelationDesc`] contains a column at the specified index.
1197    pub fn contains_index(&self, idx: &ColumnIndex) -> bool {
1198        self.metadata.contains_key(idx)
1199    }
1200
1201    /// Finds a column by name.
1202    ///
1203    /// Returns the index and type of the column named `name`. If no column with
1204    /// the specified name exists, returns `None`. If multiple columns have the
1205    /// specified name, the leftmost column is returned.
1206    pub fn get_by_name(&self, name: &ColumnName) -> Option<(usize, &SqlColumnType)> {
1207        self.iter_names()
1208            .position(|n| n == name)
1209            .map(|i| (i, &self.typ.column_types[i]))
1210    }
1211
1212    /// Gets the name of the `i`th column.
1213    ///
1214    /// # Panics
1215    ///
1216    /// Panics if `i` is not a valid column index.
1217    ///
1218    /// TODO(parkmycar): Migrate all uses of this to [`RelationDesc::get_name_idx`].
1219    pub fn get_name(&self, i: usize) -> &ColumnName {
1220        // TODO(parkmycar): Refactor this to use `ColumnIndex`.
1221        self.get_name_idx(&ColumnIndex(i))
1222    }
1223
1224    /// Gets the name of the column at `idx`.
1225    ///
1226    /// # Panics
1227    ///
1228    /// Panics if no column exists at `idx`.
1229    pub fn get_name_idx(&self, idx: &ColumnIndex) -> &ColumnName {
1230        &self.metadata.get(idx).expect("should exist").name
1231    }
1232
1233    /// Mutably gets the name of the `i`th column.
1234    ///
1235    /// # Panics
1236    ///
1237    /// Panics if `i` is not a valid column index.
1238    pub fn get_name_mut(&mut self, i: usize) -> &mut ColumnName {
1239        // TODO(parkmycar): Refactor this to use `ColumnIndex`.
1240        &mut self
1241            .metadata
1242            .get_mut(&ColumnIndex(i))
1243            .expect("should exist")
1244            .name
1245    }
1246
1247    /// Gets the [`SqlColumnType`] of the column at `idx`.
1248    ///
1249    /// # Panics
1250    ///
1251    /// Panics if no column exists at `idx`.
1252    pub fn get_type(&self, idx: &ColumnIndex) -> &SqlColumnType {
1253        let typ_idx = self.metadata.get(idx).expect("should exist").typ_idx;
1254        &self.typ.column_types[typ_idx]
1255    }
1256
1257    /// Gets the name of the `i`th column if that column name is unambiguous.
1258    ///
1259    /// If at least one other column has the same name as the `i`th column,
1260    /// returns `None`. If the `i`th column has no name, returns `None`.
1261    ///
1262    /// # Panics
1263    ///
1264    /// Panics if `i` is not a valid column index.
1265    pub fn get_unambiguous_name(&self, i: usize) -> Option<&ColumnName> {
1266        let name = self.get_name(i);
1267        if self.iter_names().filter(|n| *n == name).count() == 1 {
1268            Some(name)
1269        } else {
1270            None
1271        }
1272    }
1273
1274    /// Verifies that `d` meets all of the constraints for the `i`th column of `self`.
1275    ///
1276    /// n.b. The only constraint MZ currently supports in NOT NULL, but this
1277    /// structure will be simple to extend.
1278    pub fn constraints_met(&self, i: usize, d: &Datum) -> Result<(), NotNullViolation> {
1279        let name = self.get_name(i);
1280        let typ = &self.typ.column_types[i];
1281        if d == &Datum::Null && !typ.nullable {
1282            Err(NotNullViolation(name.clone()))
1283        } else {
1284            Ok(())
1285        }
1286    }
1287
1288    /// Computes the differences between two [`RelationDesc`]s.
1289    ///
1290    /// Returns a rich diff describing which columns differ, and in what way.
1291    ///
1292    /// # Panics
1293    ///
1294    /// Panics if either `self` or `other` have columns that were added at a
1295    /// [`RelationVersion`] other than [`RelationVersion::root`] or if any
1296    /// columns were dropped.
1297    ///
1298    /// This simplifies things by allowing us to assume that `ColumnIndex`es are
1299    /// dense and that they match the indexes of `typ.columns()`. Without this
1300    /// we would, e.g., struggle comparing keys as those are in terms of
1301    /// `typ.columns()` indexes.
1302    pub fn diff(&self, other: &RelationDesc) -> RelationDescDiff {
1303        assert_eq!(self.metadata.len(), self.typ.columns().len());
1304        assert_eq!(other.metadata.len(), other.typ.columns().len());
1305        for (idx, meta) in self.metadata.iter().chain(other.metadata.iter()) {
1306            assert_eq!(meta.typ_idx, idx.0);
1307            assert_eq!(meta.added, RelationVersion::root());
1308            assert_none!(meta.dropped);
1309        }
1310
1311        let mut column_diffs = BTreeMap::new();
1312        let mut key_diff = None;
1313
1314        let left_arity = self.arity();
1315        let right_arity = other.arity();
1316        let common_arity = std::cmp::min(left_arity, right_arity);
1317
1318        for idx in 0..common_arity {
1319            let left_name = self.get_name(idx);
1320            let right_name = other.get_name(idx);
1321            let left_type = &self.typ.column_types[idx];
1322            let right_type = &other.typ.column_types[idx];
1323
1324            if left_name != right_name {
1325                let diff = ColumnDiff::NameMismatch {
1326                    left: left_name.clone(),
1327                    right: right_name.clone(),
1328                };
1329                column_diffs.insert(idx, diff);
1330            } else if left_type.scalar_type != right_type.scalar_type {
1331                let diff = ColumnDiff::TypeMismatch {
1332                    name: left_name.clone(),
1333                    left: left_type.scalar_type.clone(),
1334                    right: right_type.scalar_type.clone(),
1335                };
1336                column_diffs.insert(idx, diff);
1337            } else if left_type.nullable != right_type.nullable {
1338                let diff = ColumnDiff::NullabilityMismatch {
1339                    name: left_name.clone(),
1340                    left: left_type.nullable,
1341                    right: right_type.nullable,
1342                };
1343                column_diffs.insert(idx, diff);
1344            }
1345        }
1346
1347        for idx in common_arity..left_arity {
1348            let diff = ColumnDiff::Missing {
1349                name: self.get_name(idx).clone(),
1350            };
1351            column_diffs.insert(idx, diff);
1352        }
1353
1354        for idx in common_arity..right_arity {
1355            let diff = ColumnDiff::Extra {
1356                name: other.get_name(idx).clone(),
1357            };
1358            column_diffs.insert(idx, diff);
1359        }
1360
1361        let left_keys: BTreeSet<_> = self.typ.keys.iter().collect();
1362        let right_keys: BTreeSet<_> = other.typ.keys.iter().collect();
1363        if left_keys != right_keys {
1364            let column_names = |desc: &RelationDesc, keys: BTreeSet<&Vec<usize>>| {
1365                keys.iter()
1366                    .map(|key| key.iter().map(|&idx| desc.get_name(idx).clone()).collect())
1367                    .collect()
1368            };
1369            key_diff = Some(KeyDiff {
1370                left: column_names(self, left_keys),
1371                right: column_names(other, right_keys),
1372            });
1373        }
1374
1375        RelationDescDiff {
1376            column_diffs,
1377            key_diff,
1378        }
1379    }
1380
1381    /// Creates a new [`RelationDesc`] retaining only the columns specified in `demands`.
1382    pub fn apply_demand(&self, demands: &BTreeSet<usize>) -> RelationDesc {
1383        // This filters `metadata` by raw ColumnIndex but `typ` by position,
1384        // which only agree when the desc is dense. Every desc constructible
1385        // today is (schema history is add-only), but a dropped column would
1386        // desync the two and silently attach types, statistics, and filter
1387        // specs to the wrong columns downstream.
1388        debug_assert!(
1389            self.metadata
1390                .iter()
1391                .enumerate()
1392                .all(|(pos, (idx, meta))| idx.0 == pos && meta.typ_idx == pos),
1393            "apply_demand requires a dense RelationDesc (ColumnIndex == typ_idx): {:?}",
1394            self.metadata,
1395        );
1396        let mut new_desc = self.clone();
1397
1398        // Update ColumnMetadata.
1399        let mut removed = 0;
1400        new_desc.metadata.retain(|idx, metadata| {
1401            let retain = demands.contains(&idx.0);
1402            if !retain {
1403                removed += 1;
1404            } else {
1405                metadata.typ_idx -= removed;
1406            }
1407            retain
1408        });
1409
1410        // Update SqlColumnType.
1411        let mut idx = 0;
1412        new_desc.typ.column_types.retain(|_| {
1413            let keep = demands.contains(&idx);
1414            idx += 1;
1415            keep
1416        });
1417
1418        new_desc
1419    }
1420}
1421
1422#[cfg(any(test, feature = "proptest"))]
1423impl Arbitrary for RelationDesc {
1424    type Parameters = ();
1425    type Strategy = BoxedStrategy<RelationDesc>;
1426
1427    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1428        let mut weights = vec![(100, Just(0..4)), (50, Just(4..8)), (25, Just(8..16))];
1429        if std::env::var("PROPTEST_LARGE_DATA").is_ok() {
1430            weights.extend([
1431                (12, Just(16..32)),
1432                (6, Just(32..64)),
1433                (3, Just(64..128)),
1434                (1, Just(128..256)),
1435            ]);
1436        }
1437        let num_columns = Union::new_weighted(weights);
1438
1439        num_columns.prop_flat_map(arb_relation_desc).boxed()
1440    }
1441}
1442
1443/// Returns a [`Strategy`] that generates an arbitrary [`RelationDesc`] with a number columns
1444/// within the range provided.
1445#[cfg(any(test, feature = "proptest"))]
1446pub fn arb_relation_desc(num_cols: std::ops::Range<usize>) -> impl Strategy<Value = RelationDesc> {
1447    proptest::collection::btree_map(any::<ColumnName>(), any::<SqlColumnType>(), num_cols)
1448        .prop_map(RelationDesc::from_names_and_types)
1449}
1450
1451/// Returns a [`Strategy`] that generates a projection of the provided [`RelationDesc`].
1452#[cfg(any(test, feature = "proptest"))]
1453pub fn arb_relation_desc_projection(desc: RelationDesc) -> impl Strategy<Value = RelationDesc> {
1454    let mask: Vec<_> = (0..desc.len()).map(|_| any::<bool>()).collect();
1455    mask.prop_map(move |mask| {
1456        let demands: BTreeSet<_> = mask
1457            .into_iter()
1458            .enumerate()
1459            .filter_map(|(idx, keep)| keep.then_some(idx))
1460            .collect();
1461        desc.apply_demand(&demands)
1462    })
1463}
1464
1465impl IntoIterator for RelationDesc {
1466    type Item = (ColumnName, SqlColumnType);
1467    type IntoIter = Box<dyn Iterator<Item = (ColumnName, SqlColumnType)>>;
1468
1469    fn into_iter(self) -> Self::IntoIter {
1470        let iter = self
1471            .metadata
1472            .into_values()
1473            .zip_eq(self.typ.column_types)
1474            .map(|(meta, typ)| (meta.name, typ));
1475        Box::new(iter)
1476    }
1477}
1478
1479/// Returns a [`Strategy`] that yields arbitrary [`Row`]s for the provided [`RelationDesc`].
1480#[cfg(any(test, feature = "proptest"))]
1481pub fn arb_row_for_relation(desc: &RelationDesc) -> impl Strategy<Value = Row> + use<> {
1482    let datums: Vec<_> = desc
1483        .typ()
1484        .columns()
1485        .iter()
1486        .cloned()
1487        .map(arb_datum_for_column)
1488        .collect();
1489    datums.prop_map(|x| Row::pack(x.iter().map(Datum::from)))
1490}
1491
1492/// Expression violated not-null constraint on named column
1493#[derive(Debug, PartialEq, Eq)]
1494pub struct NotNullViolation(pub ColumnName);
1495
1496impl fmt::Display for NotNullViolation {
1497    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1498        write!(
1499            f,
1500            "null value in column {} violates not-null constraint",
1501            self.0.quoted()
1502        )
1503    }
1504}
1505
1506/// The result of comparing two [`RelationDesc`]s.
1507#[derive(Debug, Clone, PartialEq, Eq)]
1508pub struct RelationDescDiff {
1509    /// Column differences, keyed by column index.
1510    pub column_diffs: BTreeMap<usize, ColumnDiff>,
1511    /// Key differences, if any.
1512    pub key_diff: Option<KeyDiff>,
1513}
1514
1515impl RelationDescDiff {
1516    /// Returns whether the diff contains any differences.
1517    pub fn is_empty(&self) -> bool {
1518        self.column_diffs.is_empty() && self.key_diff.is_none()
1519    }
1520}
1521
1522/// A difference in a column between two [`RelationDesc`]s.
1523#[derive(Debug, Clone, PartialEq, Eq)]
1524pub enum ColumnDiff {
1525    /// Column exists only in the left relation.
1526    Missing { name: ColumnName },
1527    /// Column exists only in the right relation.
1528    Extra { name: ColumnName },
1529    /// Columns have different types.
1530    TypeMismatch {
1531        name: ColumnName,
1532        left: SqlScalarType,
1533        right: SqlScalarType,
1534    },
1535    /// Columns have different nullability.
1536    NullabilityMismatch {
1537        name: ColumnName,
1538        left: bool,
1539        right: bool,
1540    },
1541    /// Columns have different names.
1542    NameMismatch { left: ColumnName, right: ColumnName },
1543}
1544
1545/// A difference in the keys of two [`RelationDesc`]s.
1546#[derive(Debug, Clone, PartialEq, Eq)]
1547pub struct KeyDiff {
1548    /// Keys of the left relation.
1549    pub left: BTreeSet<Vec<ColumnName>>,
1550    /// Keys of the right relation.
1551    pub right: BTreeSet<Vec<ColumnName>>,
1552}
1553
1554/// A builder for a [`RelationDesc`].
1555#[derive(Clone, Default, Debug, PartialEq, Eq)]
1556pub struct RelationDescBuilder {
1557    /// Columns of the relation.
1558    columns: Vec<(ColumnName, SqlColumnType)>,
1559    /// Sets of indices that are "keys" for the collection.
1560    keys: Vec<Vec<usize>>,
1561}
1562
1563impl RelationDescBuilder {
1564    /// Appends a column with the specified name and type.
1565    pub fn with_column<N: Into<ColumnName>>(
1566        mut self,
1567        name: N,
1568        ty: SqlColumnType,
1569    ) -> RelationDescBuilder {
1570        let name = name.into();
1571        self.columns.push((name, ty));
1572        self
1573    }
1574
1575    /// Appends the provided columns to the builder.
1576    pub fn with_columns<I, T, N>(mut self, iter: I) -> Self
1577    where
1578        I: IntoIterator<Item = (N, T)>,
1579        T: Into<SqlColumnType>,
1580        N: Into<ColumnName>,
1581    {
1582        self.columns
1583            .extend(iter.into_iter().map(|(name, ty)| (name.into(), ty.into())));
1584        self
1585    }
1586
1587    /// Adds a new key for the relation.
1588    pub fn with_key(mut self, mut indices: Vec<usize>) -> RelationDescBuilder {
1589        indices.sort_unstable();
1590        if !self.keys.contains(&indices) {
1591            self.keys.push(indices);
1592        }
1593        self
1594    }
1595
1596    /// Removes all previously inserted keys.
1597    pub fn without_keys(mut self) -> RelationDescBuilder {
1598        self.keys.clear();
1599        assert_eq!(self.keys.len(), 0);
1600        self
1601    }
1602
1603    /// Concatenates a [`RelationDescBuilder`] onto the end of this [`RelationDescBuilder`].
1604    pub fn concat(mut self, other: Self) -> Self {
1605        let self_len = self.columns.len();
1606
1607        self.columns.extend(other.columns);
1608        for k in other.keys {
1609            let k = k.into_iter().map(|idx| idx + self_len).collect();
1610            self = self.with_key(k);
1611        }
1612
1613        self
1614    }
1615
1616    /// Finish the builder, returning a [`RelationDesc`].
1617    pub fn finish(self) -> RelationDesc {
1618        let mut desc = RelationDesc::from_names_and_types(self.columns);
1619        desc.typ = desc.typ.with_keys(self.keys);
1620        desc
1621    }
1622}
1623
1624/// Describes a [`RelationDesc`] at a specific version of a [`VersionedRelationDesc`].
1625#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)]
1626pub enum RelationVersionSelector {
1627    Specific(RelationVersion),
1628    Latest,
1629}
1630
1631impl RelationVersionSelector {
1632    pub fn specific(version: u64) -> Self {
1633        RelationVersionSelector::Specific(RelationVersion(version))
1634    }
1635}
1636
1637/// A wrapper around [`RelationDesc`] that provides an interface for adding
1638/// columns and generating new versions.
1639///
1640/// TODO(parkmycar): Using an immutable data structure for RelationDesc would
1641/// be great.
1642#[derive(Debug, Clone, Serialize)]
1643pub struct VersionedRelationDesc {
1644    inner: RelationDesc,
1645}
1646
1647impl VersionedRelationDesc {
1648    pub fn new(inner: RelationDesc) -> Self {
1649        VersionedRelationDesc { inner }
1650    }
1651
1652    /// Adds a new column to this [`RelationDesc`], creating a new version of the [`RelationDesc`].
1653    ///
1654    /// # Panics
1655    ///
1656    /// * Panics if a column with `name` already exists that hasn't been dropped.
1657    ///
1658    /// Note: For building a [`RelationDesc`] see [`RelationDescBuilder::with_column`].
1659    #[must_use]
1660    pub fn add_column<N, T>(&mut self, name: N, typ: T) -> RelationVersion
1661    where
1662        N: Into<ColumnName>,
1663        T: Into<SqlColumnType>,
1664    {
1665        let latest_version = self.latest_version();
1666        let new_version = latest_version.bump();
1667
1668        let name = name.into();
1669        let existing = self
1670            .inner
1671            .metadata
1672            .iter()
1673            .find(|(_, meta)| meta.name == name && meta.dropped.is_none());
1674        if let Some(existing) = existing {
1675            panic!("column named '{name}' already exists! {existing:?}");
1676        }
1677
1678        let next_idx = self.inner.metadata.len();
1679        let col_meta = ColumnMetadata {
1680            name,
1681            typ_idx: next_idx,
1682            added: new_version,
1683            dropped: None,
1684        };
1685
1686        self.inner.typ.column_types.push(typ.into());
1687        let prev = self.inner.metadata.insert(ColumnIndex(next_idx), col_meta);
1688
1689        assert_none!(prev, "column index overlap!");
1690        self.validate();
1691
1692        new_version
1693    }
1694
1695    /// Drops the column `name` from this [`RelationDesc`]. If there are multiple columns with
1696    /// `name` drops the left-most one that hasn't already been dropped.
1697    ///
1698    /// TODO(parkmycar): Add handling for dropping a column that is currently used as a key.
1699    ///
1700    /// # Panics
1701    ///
1702    /// Panics if a column with `name` does not exist or the dropped column was used as a key.
1703    #[must_use]
1704    pub fn drop_column<N>(&mut self, name: N) -> RelationVersion
1705    where
1706        N: Into<ColumnName>,
1707    {
1708        let name = name.into();
1709        let latest_version = self.latest_version();
1710        let new_version = latest_version.bump();
1711
1712        let col = self
1713            .inner
1714            .metadata
1715            .values_mut()
1716            .find(|meta| meta.name == name && meta.dropped.is_none())
1717            .expect("column to exist");
1718
1719        // Make sure the column hadn't been previously dropped.
1720        assert_none!(col.dropped, "column was already dropped");
1721        col.dropped = Some(new_version);
1722
1723        // Make sure the column isn't being used as a key.
1724        let dropped_key = self
1725            .inner
1726            .typ
1727            .keys
1728            .iter()
1729            .any(|keys| keys.contains(&col.typ_idx));
1730        assert!(!dropped_key, "column being dropped was used as a key");
1731
1732        self.validate();
1733        new_version
1734    }
1735
1736    /// Returns the [`RelationDesc`] at the latest version.
1737    pub fn latest(&self) -> RelationDesc {
1738        self.inner.clone()
1739    }
1740
1741    /// Returns this [`RelationDesc`] at the specified version.
1742    pub fn at_version(&self, version: RelationVersionSelector) -> RelationDesc {
1743        // Get all of the changes from the start, up to whatever version was requested.
1744        let up_to_version = match version {
1745            RelationVersionSelector::Latest => RelationVersion(u64::MAX),
1746            RelationVersionSelector::Specific(v) => v,
1747        };
1748
1749        let valid_columns = self.inner.metadata.iter().filter(|(_col_idx, meta)| {
1750            let added = meta.added <= up_to_version;
1751            let dropped = meta
1752                .dropped
1753                .map(|dropped_at| up_to_version >= dropped_at)
1754                .unwrap_or(false);
1755
1756            added && !dropped
1757        });
1758
1759        let mut column_types = Vec::new();
1760        let mut column_metas = BTreeMap::new();
1761
1762        // N.B. At this point we need to be careful because col_idx might not
1763        // equal typ_idx.
1764        //
1765        // For example, consider columns "a", "b", and "c" with indexes 0, 1,
1766        // and 2. If we drop column "b" then we'll have "a" and "c" with column
1767        // indexes 0 and 2, but their indices in SqlRelationType will be 0 and 1.
1768        for (col_idx, meta) in valid_columns {
1769            let new_meta = ColumnMetadata {
1770                name: meta.name.clone(),
1771                typ_idx: column_types.len(),
1772                added: meta.added.clone(),
1773                dropped: meta.dropped.clone(),
1774            };
1775            column_types.push(self.inner.typ.columns()[meta.typ_idx].clone());
1776            column_metas.insert(*col_idx, new_meta);
1777        }
1778
1779        // Remap keys in case a column with an index less than that of a key was
1780        // dropped.
1781        //
1782        // For example, consider columns "a", "b", and "c" where "a" and "c" are
1783        // keys and "b" was dropped.
1784        let keys = self
1785            .inner
1786            .typ
1787            .keys
1788            .iter()
1789            .map(|keys| {
1790                keys.iter()
1791                    .map(|key_idx| {
1792                        let metadata = column_metas
1793                            .get(&ColumnIndex(*key_idx))
1794                            .expect("found key for column that doesn't exist");
1795                        metadata.typ_idx
1796                    })
1797                    .collect()
1798            })
1799            .collect();
1800
1801        let relation_type = SqlRelationType { column_types, keys };
1802
1803        RelationDesc {
1804            typ: relation_type,
1805            metadata: column_metas,
1806        }
1807    }
1808
1809    pub fn latest_version(&self) -> RelationVersion {
1810        self.inner
1811            .metadata
1812            .values()
1813            // N.B. Dropped is always greater than added.
1814            .map(|meta| meta.dropped.unwrap_or(meta.added))
1815            .max()
1816            // If there aren't any columns we're implicitly the root version.
1817            .unwrap_or_else(RelationVersion::root)
1818    }
1819
1820    /// Validates internal contraints of the [`RelationDesc`] are correct.
1821    ///
1822    /// # Panics
1823    ///
1824    /// Panics if a constraint is not satisfied.
1825    fn validate(&self) {
1826        fn validate_inner(desc: &RelationDesc) -> Result<(), anyhow::Error> {
1827            if desc.typ.column_types.len() != desc.metadata.len() {
1828                anyhow::bail!("mismatch between number of types and metadatas");
1829            }
1830
1831            for (col_idx, meta) in &desc.metadata {
1832                if col_idx.0 > desc.metadata.len() {
1833                    anyhow::bail!("column index out of bounds");
1834                }
1835                if meta.added >= meta.dropped.unwrap_or(RelationVersion(u64::MAX)) {
1836                    anyhow::bail!("column was added after it was dropped?");
1837                }
1838                if desc.typ().columns().get(meta.typ_idx).is_none() {
1839                    anyhow::bail!("typ_idx incorrect");
1840                }
1841            }
1842
1843            for keys in &desc.typ.keys {
1844                for key in keys {
1845                    if *key >= desc.typ.column_types.len() {
1846                        anyhow::bail!("key index was out of bounds!");
1847                    }
1848                }
1849            }
1850
1851            // Every version past the root is stamped exactly once, by the add or the
1852            // drop that created it, so a column that was added and later dropped
1853            // accounts for two of them and both have to be counted. Collapsing to
1854            // `dropped.unwrap_or(added)` would lose the add's version, and with it any
1855            // desc where a non-root column was later dropped.
1856            let versions = desc
1857                .metadata
1858                .values()
1859                .flat_map(|meta| [Some(meta.added), meta.dropped])
1860                .flatten()
1861                // The root version is the one version many columns can share.
1862                .filter(|version| *version != RelationVersion::root());
1863            let mut max = 0;
1864            let mut sum = 0;
1865            for version in versions {
1866                max = std::cmp::max(max, version.0);
1867                sum += version.0;
1868            }
1869
1870            // Other than RelationVersion(0), we should never have duplicate
1871            // versions and they should always increase by 1. In other words, the
1872            // sum of all RelationVersions should be the sum of [0, max].
1873            //
1874            // N.B. n * (n + 1) / 2 = sum of [0, n]
1875            //
1876            // While I normally don't like tricks like this, it allows us to
1877            // validate that our column versions are correct in O(n) time and
1878            // without allocations.
1879            if sum != (max * (max + 1) / 2) {
1880                anyhow::bail!("there is a duplicate or missing relation version");
1881            }
1882
1883            Ok(())
1884        }
1885
1886        assert_ok!(validate_inner(&self.inner), "validate failed! {self:?}");
1887    }
1888}
1889
1890/// Diffs that can be generated proptest and applied to a [`RelationDesc`] to
1891/// exercise schema migrations.
1892#[derive(Debug)]
1893#[cfg(any(test, feature = "proptest"))]
1894pub enum PropRelationDescDiff {
1895    AddColumn {
1896        name: ColumnName,
1897        typ: SqlColumnType,
1898    },
1899    DropColumn {
1900        name: ColumnName,
1901    },
1902    ToggleNullability {
1903        name: ColumnName,
1904    },
1905    ChangeType {
1906        name: ColumnName,
1907        typ: SqlColumnType,
1908    },
1909}
1910
1911#[cfg(any(test, feature = "proptest"))]
1912impl PropRelationDescDiff {
1913    pub fn apply(self, desc: &mut RelationDesc) {
1914        match self {
1915            PropRelationDescDiff::AddColumn { name, typ } => {
1916                let new_idx = desc.metadata.len();
1917                let meta = ColumnMetadata {
1918                    name,
1919                    typ_idx: new_idx,
1920                    added: RelationVersion(0),
1921                    dropped: None,
1922                };
1923                let prev = desc.metadata.insert(ColumnIndex(new_idx), meta);
1924                desc.typ.column_types.push(typ);
1925
1926                assert_none!(prev);
1927                assert_eq!(desc.metadata.len(), desc.typ.column_types.len());
1928            }
1929            PropRelationDescDiff::DropColumn { name } => {
1930                let next_version = desc
1931                    .metadata
1932                    .values()
1933                    .map(|meta| meta.dropped.unwrap_or(meta.added))
1934                    .max()
1935                    .unwrap_or_else(RelationVersion::root)
1936                    .bump();
1937                let Some(metadata) = desc.metadata.values_mut().find(|meta| meta.name == name)
1938                else {
1939                    return;
1940                };
1941                if metadata.dropped.is_none() {
1942                    metadata.dropped = Some(next_version);
1943                }
1944            }
1945            PropRelationDescDiff::ToggleNullability { name } => {
1946                let Some((pos, _)) = desc.get_by_name(&name) else {
1947                    return;
1948                };
1949                let col_type = desc
1950                    .typ
1951                    .column_types
1952                    .get_mut(pos)
1953                    .expect("ColumnNames and SqlColumnTypes out of sync!");
1954                col_type.nullable = !col_type.nullable;
1955            }
1956            PropRelationDescDiff::ChangeType { name, typ } => {
1957                let Some((pos, _)) = desc.get_by_name(&name) else {
1958                    return;
1959                };
1960                let col_type = desc
1961                    .typ
1962                    .column_types
1963                    .get_mut(pos)
1964                    .expect("ColumnNames and SqlColumnTypes out of sync!");
1965                *col_type = typ;
1966            }
1967        }
1968    }
1969}
1970
1971/// Generates a set of [`PropRelationDescDiff`]s based on some source [`RelationDesc`].
1972#[cfg(any(test, feature = "proptest"))]
1973pub fn arb_relation_desc_diff(
1974    source: &RelationDesc,
1975) -> impl Strategy<Value = Vec<PropRelationDescDiff>> + use<> {
1976    let source = Rc::new(source.clone());
1977    let num_source_columns = source.typ.columns().len();
1978
1979    let num_add_columns = Union::new_weighted(vec![(100, Just(0..8)), (1, Just(8..64))]);
1980    let add_columns_strat = num_add_columns
1981        .prop_flat_map(|num_columns| {
1982            proptest::collection::vec((any::<ColumnName>(), any::<SqlColumnType>()), num_columns)
1983        })
1984        .prop_map(|cols| {
1985            cols.into_iter()
1986                .map(|(name, typ)| PropRelationDescDiff::AddColumn { name, typ })
1987                .collect::<Vec<_>>()
1988        });
1989
1990    // If the source RelationDesc is empty there is nothing else to do.
1991    if num_source_columns == 0 {
1992        return add_columns_strat.boxed();
1993    }
1994
1995    let source_ = Rc::clone(&source);
1996    let drop_columns_strat = (0..num_source_columns).prop_perturb(move |num_columns, mut rng| {
1997        let mut set = BTreeSet::default();
1998        for _ in 0..num_columns {
1999            let col_idx = rng.random_range(0..num_source_columns);
2000            set.insert(source_.get_name(col_idx).clone());
2001        }
2002        set.into_iter()
2003            .map(|name| PropRelationDescDiff::DropColumn { name })
2004            .collect::<Vec<_>>()
2005    });
2006
2007    let source_ = Rc::clone(&source);
2008    let toggle_nullability_strat =
2009        (0..num_source_columns).prop_perturb(move |num_columns, mut rng| {
2010            let mut set = BTreeSet::default();
2011            for _ in 0..num_columns {
2012                let col_idx = rng.random_range(0..num_source_columns);
2013                set.insert(source_.get_name(col_idx).clone());
2014            }
2015            set.into_iter()
2016                .map(|name| PropRelationDescDiff::ToggleNullability { name })
2017                .collect::<Vec<_>>()
2018        });
2019
2020    let source_ = Rc::clone(&source);
2021    let change_type_strat = (0..num_source_columns)
2022        .prop_perturb(move |num_columns, mut rng| {
2023            let mut set = BTreeSet::default();
2024            for _ in 0..num_columns {
2025                let col_idx = rng.random_range(0..num_source_columns);
2026                set.insert(source_.get_name(col_idx).clone());
2027            }
2028            set
2029        })
2030        .prop_flat_map(|cols| {
2031            proptest::collection::vec(any::<SqlColumnType>(), cols.len())
2032                .prop_map(move |types| (cols.clone(), types))
2033        })
2034        .prop_map(|(cols, types)| {
2035            cols.into_iter()
2036                .zip_eq(types)
2037                .map(|(name, typ)| PropRelationDescDiff::ChangeType { name, typ })
2038                .collect::<Vec<_>>()
2039        });
2040
2041    (
2042        add_columns_strat,
2043        drop_columns_strat,
2044        toggle_nullability_strat,
2045        change_type_strat,
2046    )
2047        .prop_map(|(adds, drops, toggles, changes)| {
2048            adds.into_iter()
2049                .chain(drops)
2050                .chain(toggles)
2051                .chain(changes)
2052                .collect::<Vec<_>>()
2053        })
2054        .prop_shuffle()
2055        .boxed()
2056}
2057
2058#[cfg(test)]
2059mod tests {
2060    use super::*;
2061    use prost::Message;
2062
2063    /// `apply_demand`, and the stats and filter-spec plumbing downstream of
2064    /// it, require dense descs. A desc with a dropped column must trip the
2065    /// assertion rather than silently misattach columns.
2066    #[mz_ore::test]
2067    #[should_panic(expected = "dense RelationDesc")]
2068    fn apply_demand_rejects_non_dense_desc() {
2069        let desc = RelationDesc::builder()
2070            .with_column("a", SqlScalarType::Int32.nullable(false))
2071            .with_column("b", SqlScalarType::Int32.nullable(false))
2072            .with_column("c", SqlScalarType::Int32.nullable(false))
2073            .finish();
2074        let mut versioned = VersionedRelationDesc::new(desc);
2075        let version = versioned.drop_column("b");
2076        let desc = versioned.at_version(RelationVersionSelector::Specific(version));
2077        let _ = desc.apply_demand(&BTreeSet::from([0]));
2078    }
2079
2080    #[mz_ore::test]
2081    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2082    fn smoktest_at_version() {
2083        let desc = RelationDesc::builder()
2084            .with_column("a", SqlScalarType::Bool.nullable(true))
2085            .with_column("z", SqlScalarType::String.nullable(false))
2086            .finish();
2087
2088        let mut versioned_desc = VersionedRelationDesc {
2089            inner: desc.clone(),
2090        };
2091        versioned_desc.validate();
2092
2093        let latest = versioned_desc.at_version(RelationVersionSelector::Latest);
2094        assert_eq!(desc, latest);
2095
2096        let v0 = versioned_desc.at_version(RelationVersionSelector::specific(0));
2097        assert_eq!(desc, v0);
2098
2099        let v3 = versioned_desc.at_version(RelationVersionSelector::specific(3));
2100        assert_eq!(desc, v3);
2101
2102        let v1 = versioned_desc.add_column("b", SqlScalarType::Bytes.nullable(false));
2103        assert_eq!(v1, RelationVersion(1));
2104
2105        let v1 = versioned_desc.at_version(RelationVersionSelector::Specific(v1));
2106        insta::assert_json_snapshot!(v1.metadata, @r###"
2107        {
2108          "0": {
2109            "name": "a",
2110            "typ_idx": 0,
2111            "added": 0,
2112            "dropped": null
2113          },
2114          "1": {
2115            "name": "z",
2116            "typ_idx": 1,
2117            "added": 0,
2118            "dropped": null
2119          },
2120          "2": {
2121            "name": "b",
2122            "typ_idx": 2,
2123            "added": 1,
2124            "dropped": null
2125          }
2126        }
2127        "###);
2128
2129        // Check that V0 doesn't show the new column.
2130        let v0_b = versioned_desc.at_version(RelationVersionSelector::specific(0));
2131        assert!(v0.iter().eq(v0_b.iter()));
2132
2133        let v2 = versioned_desc.drop_column("z");
2134        assert_eq!(v2, RelationVersion(2));
2135
2136        let v2 = versioned_desc.at_version(RelationVersionSelector::Specific(v2));
2137        insta::assert_json_snapshot!(v2.metadata, @r###"
2138        {
2139          "0": {
2140            "name": "a",
2141            "typ_idx": 0,
2142            "added": 0,
2143            "dropped": null
2144          },
2145          "2": {
2146            "name": "b",
2147            "typ_idx": 1,
2148            "added": 1,
2149            "dropped": null
2150          }
2151        }
2152        "###);
2153
2154        // Check that V0 and V1 are still correct.
2155        let v0_c = versioned_desc.at_version(RelationVersionSelector::specific(0));
2156        assert!(v0.iter().eq(v0_c.iter()));
2157
2158        let v1_b = versioned_desc.at_version(RelationVersionSelector::specific(1));
2159        assert!(v1.iter().eq(v1_b.iter()));
2160
2161        insta::assert_json_snapshot!(versioned_desc.inner.metadata, @r###"
2162        {
2163          "0": {
2164            "name": "a",
2165            "typ_idx": 0,
2166            "added": 0,
2167            "dropped": null
2168          },
2169          "1": {
2170            "name": "z",
2171            "typ_idx": 1,
2172            "added": 0,
2173            "dropped": 2
2174          },
2175          "2": {
2176            "name": "b",
2177            "typ_idx": 2,
2178            "added": 1,
2179            "dropped": null
2180          }
2181        }
2182        "###);
2183    }
2184
2185    #[mz_ore::test]
2186    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2187    fn test_dropping_columns_with_keys() {
2188        let desc = RelationDesc::builder()
2189            .with_column("a", SqlScalarType::Bool.nullable(true))
2190            .with_column("z", SqlScalarType::String.nullable(false))
2191            .with_key(vec![1])
2192            .finish();
2193
2194        let mut versioned_desc = VersionedRelationDesc {
2195            inner: desc.clone(),
2196        };
2197        versioned_desc.validate();
2198
2199        let v1 = versioned_desc.drop_column("a");
2200        assert_eq!(v1, RelationVersion(1));
2201
2202        // Make sure the key index for 'z' got remapped since 'a' was dropped.
2203        let v1 = versioned_desc.at_version(RelationVersionSelector::Specific(v1));
2204        insta::assert_json_snapshot!(v1, @r###"
2205        {
2206          "typ": {
2207            "column_types": [
2208              {
2209                "scalar_type": "String",
2210                "nullable": false
2211              }
2212            ],
2213            "keys": [
2214              [
2215                0
2216              ]
2217            ]
2218          },
2219          "metadata": {
2220            "1": {
2221              "name": "z",
2222              "typ_idx": 0,
2223              "added": 0,
2224              "dropped": null
2225            }
2226          }
2227        }
2228        "###);
2229
2230        // Make sure the key index of 'z' is correct when all columns are present.
2231        let v0 = versioned_desc.at_version(RelationVersionSelector::specific(0));
2232        insta::assert_json_snapshot!(v0, @r###"
2233        {
2234          "typ": {
2235            "column_types": [
2236              {
2237                "scalar_type": "Bool",
2238                "nullable": true
2239              },
2240              {
2241                "scalar_type": "String",
2242                "nullable": false
2243              }
2244            ],
2245            "keys": [
2246              [
2247                1
2248              ]
2249            ]
2250          },
2251          "metadata": {
2252            "0": {
2253              "name": "a",
2254              "typ_idx": 0,
2255              "added": 0,
2256              "dropped": 1
2257            },
2258            "1": {
2259              "name": "z",
2260              "typ_idx": 1,
2261              "added": 0,
2262              "dropped": null
2263            }
2264          }
2265        }
2266        "###);
2267    }
2268
2269    #[mz_ore::test]
2270    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2271    fn roundtrip_relation_desc_without_metadata() {
2272        let typ = ProtoRelationType {
2273            column_types: vec![
2274                SqlScalarType::String.nullable(false).into_proto(),
2275                SqlScalarType::Bool.nullable(true).into_proto(),
2276            ],
2277            keys: vec![],
2278        };
2279        let proto = ProtoRelationDesc {
2280            typ: Some(typ),
2281            names: vec![
2282                ColumnName("a".into()).into_proto(),
2283                ColumnName("b".into()).into_proto(),
2284            ],
2285            metadata: vec![],
2286        };
2287        let desc: RelationDesc = proto.into_rust().unwrap();
2288
2289        insta::assert_json_snapshot!(desc, @r###"
2290        {
2291          "typ": {
2292            "column_types": [
2293              {
2294                "scalar_type": "String",
2295                "nullable": false
2296              },
2297              {
2298                "scalar_type": "Bool",
2299                "nullable": true
2300              }
2301            ],
2302            "keys": []
2303          },
2304          "metadata": {
2305            "0": {
2306              "name": "a",
2307              "typ_idx": 0,
2308              "added": 0,
2309              "dropped": null
2310            },
2311            "1": {
2312              "name": "b",
2313              "typ_idx": 1,
2314              "added": 0,
2315              "dropped": null
2316            }
2317          }
2318        }
2319        "###);
2320    }
2321
2322    #[mz_ore::test]
2323    #[should_panic(expected = "column named 'a' already exists!")]
2324    fn test_add_column_with_same_name_panics() {
2325        let desc = RelationDesc::builder()
2326            .with_column("a", SqlScalarType::Bool.nullable(true))
2327            .finish();
2328        let mut versioned = VersionedRelationDesc::new(desc);
2329
2330        let _ = versioned.add_column("a", SqlScalarType::String.nullable(false));
2331    }
2332
2333    #[mz_ore::test]
2334    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2335    fn test_add_column_with_same_name_prev_dropped() {
2336        let desc = RelationDesc::builder()
2337            .with_column("a", SqlScalarType::Bool.nullable(true))
2338            .finish();
2339        let mut versioned = VersionedRelationDesc::new(desc);
2340
2341        let v1 = versioned.drop_column("a");
2342        let v1 = versioned.at_version(RelationVersionSelector::Specific(v1));
2343        insta::assert_json_snapshot!(v1, @r###"
2344        {
2345          "typ": {
2346            "column_types": [],
2347            "keys": []
2348          },
2349          "metadata": {}
2350        }
2351        "###);
2352
2353        let v2 = versioned.add_column("a", SqlScalarType::String.nullable(false));
2354        let v2 = versioned.at_version(RelationVersionSelector::Specific(v2));
2355        insta::assert_json_snapshot!(v2, @r###"
2356        {
2357          "typ": {
2358            "column_types": [
2359              {
2360                "scalar_type": "String",
2361                "nullable": false
2362              }
2363            ],
2364            "keys": []
2365          },
2366          "metadata": {
2367            "1": {
2368              "name": "a",
2369              "typ_idx": 0,
2370              "added": 2,
2371              "dropped": null
2372            }
2373          }
2374        }
2375        "###);
2376    }
2377
2378    #[mz_ore::test]
2379    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2380    fn test_drop_column_added_after_root() {
2381        let desc = RelationDesc::builder()
2382            .with_column("a", SqlScalarType::Bool.nullable(true))
2383            .finish();
2384        let mut versioned = VersionedRelationDesc::new(desc);
2385
2386        let v1 = versioned.add_column("b", SqlScalarType::String.nullable(false));
2387        let v2 = versioned.drop_column("b");
2388        assert_eq!(v1, RelationVersion(1));
2389        assert_eq!(v2, RelationVersion(2));
2390
2391        assert_eq!(
2392            versioned
2393                .at_version(RelationVersionSelector::Specific(v1))
2394                .arity(),
2395            2
2396        );
2397        assert_eq!(
2398            versioned
2399                .at_version(RelationVersionSelector::Specific(v2))
2400                .arity(),
2401            1
2402        );
2403    }
2404
2405    #[mz_ore::test]
2406    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2407    fn relation_desc_proto_rejects_corrupt_shapes() {
2408        fn proto(num_types: usize, num_names: usize, keys: Vec<Vec<usize>>) -> ProtoRelationDesc {
2409            let mut typ = SqlRelationType::new(vec![SqlScalarType::Bool.nullable(true); num_types]);
2410            typ.keys = keys;
2411            ProtoRelationDesc {
2412                typ: Some(typ.into_proto()),
2413                names: (0..num_names)
2414                    .map(|i| ColumnName::from(format!("c{i}")).into_proto())
2415                    .collect(),
2416                metadata: vec![],
2417            }
2418        }
2419
2420        // A desc with more names than types panics in `iter()`, one with more types than names
2421        // panics in `into_iter()`, and an out of bounds key is what `validate` calls corruption.
2422        // All three re-encode identically, so a proto round-trip oracle cannot see them.
2423        for (num_types, num_names) in [(0, 1), (2, 0), (3, 1)] {
2424            let err = RelationDesc::from_proto(proto(num_types, num_names, vec![]))
2425                .expect_err("length mismatch must be rejected");
2426            assert!(err.to_string().contains("length mismatch"), "{err}");
2427        }
2428        let err = RelationDesc::from_proto(proto(1, 1, vec![vec![7]]))
2429            .expect_err("out of bounds key must be rejected");
2430        assert!(err.to_string().contains("out of bounds"), "{err}");
2431
2432        // The well formed shape still decodes, and stays usable.
2433        let desc = RelationDesc::from_proto(proto(2, 2, vec![vec![1]])).expect("valid");
2434        assert_eq!(desc.iter().count(), 2);
2435    }
2436
2437    #[mz_ore::test]
2438    #[cfg_attr(miri, ignore)]
2439    fn apply_demand() {
2440        let desc = RelationDesc::builder()
2441            .with_column("a", SqlScalarType::String.nullable(true))
2442            .with_column("b", SqlScalarType::Int64.nullable(false))
2443            .with_column("c", SqlScalarType::Time.nullable(false))
2444            .finish();
2445        let desc = desc.apply_demand(&BTreeSet::from([0, 2]));
2446        assert_eq!(desc.arity(), 2);
2447        // TODO(parkmycar): Move validate onto RelationDesc.
2448        VersionedRelationDesc::new(desc).validate();
2449    }
2450
2451    #[mz_ore::test]
2452    #[cfg_attr(miri, ignore)]
2453    fn smoketest_column_index_stable_ident() {
2454        let idx_a = ColumnIndex(42);
2455        // Note(parkmycar): This should never change.
2456        assert_eq!(idx_a.to_stable_name(), "42");
2457    }
2458
2459    #[mz_ore::test]
2460    #[cfg_attr(miri, ignore)] // too slow
2461    fn proptest_relation_desc_roundtrips() {
2462        fn testcase(og: RelationDesc) {
2463            let bytes = og.into_proto().encode_to_vec();
2464            let proto = ProtoRelationDesc::decode(&bytes[..]).unwrap();
2465            let rnd = RelationDesc::from_proto(proto).unwrap();
2466
2467            assert_eq!(og, rnd);
2468        }
2469
2470        proptest!(|(desc in any::<RelationDesc>())| {
2471            testcase(desc);
2472        });
2473
2474        let strat = any::<RelationDesc>().prop_flat_map(|desc| {
2475            arb_relation_desc_diff(&desc).prop_map(move |diffs| (desc.clone(), diffs))
2476        });
2477
2478        proptest!(|((mut desc, diffs) in strat)| {
2479            for diff in diffs {
2480                diff.apply(&mut desc);
2481            };
2482            testcase(desc);
2483        });
2484    }
2485}