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
606impl From<ColumnName> for mz_sql_parser::ast::Ident {
607    fn from(value: ColumnName) -> Self {
608        // Note: ColumnNames are known to be less than the max length of an Ident (I think?).
609        mz_sql_parser::ast::Ident::new_unchecked(value.0)
610    }
611}
612
613#[cfg(any(test, feature = "proptest"))]
614impl proptest::arbitrary::Arbitrary for ColumnName {
615    type Parameters = ();
616    type Strategy = BoxedStrategy<ColumnName>;
617
618    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
619        // Long column names are generally uninteresting, and can greatly
620        // increase the runtime for a test case, so bound the max length.
621        let mut weights = vec![(50, Just(1..8)), (20, Just(8..16))];
622        if std::env::var("PROPTEST_LARGE_DATA").is_ok() {
623            weights.extend([
624                (5, Just(16..128)),
625                (1, Just(128..1024)),
626                (1, Just(1024..4096)),
627            ]);
628        }
629        let name_length = Union::new_weighted(weights);
630
631        // Non-ASCII characters are also generally uninteresting and can make
632        // debugging harder.
633        let char_strat = Rc::new(Union::new_weighted(vec![
634            (50, proptest::char::range('A', 'z').boxed()),
635            (1, any::<char>().boxed()),
636        ]));
637
638        name_length
639            .prop_flat_map(move |length| proptest::collection::vec(Rc::clone(&char_strat), length))
640            .prop_map(|chars| ColumnName(chars.into_iter().collect::<Box<str>>()))
641            .no_shrink()
642            .boxed()
643    }
644}
645
646/// Default name of a column (when no other information is known).
647pub const UNKNOWN_COLUMN_NAME: &str = "?column?";
648
649/// Stable index of a column in a [`RelationDesc`].
650#[derive(
651    Clone,
652    Copy,
653    Debug,
654    Eq,
655    PartialEq,
656    PartialOrd,
657    Ord,
658    Serialize,
659    Deserialize,
660    Hash
661)]
662pub struct ColumnIndex(usize);
663
664#[cfg(any(test, feature = "proptest"))]
665static_assertions::assert_not_impl_all!(ColumnIndex: Arbitrary);
666
667impl ColumnIndex {
668    /// Returns a stable identifier for this [`ColumnIndex`].
669    pub fn to_stable_name(&self) -> String {
670        self.0.to_string()
671    }
672
673    pub fn to_raw(&self) -> usize {
674        self.0
675    }
676
677    pub fn from_raw(val: usize) -> Self {
678        ColumnIndex(val)
679    }
680}
681
682/// The version a given column was added at.
683#[derive(
684    Clone,
685    Copy,
686    Debug,
687    Eq,
688    PartialEq,
689    PartialOrd,
690    Ord,
691    Serialize,
692    Deserialize,
693    Hash
694)]
695#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
696pub struct RelationVersion(u64);
697
698impl RelationVersion {
699    /// Returns the "root" or "initial" version of a [`RelationDesc`].
700    pub fn root() -> Self {
701        RelationVersion(0)
702    }
703
704    /// Returns an instance of [`RelationVersion`] which is "one" higher than `self`.
705    pub fn bump(&self) -> Self {
706        let next_version = self
707            .0
708            .checked_add(1)
709            .expect("added more than u64::MAX columns?");
710        RelationVersion(next_version)
711    }
712
713    /// Consume a [`RelationVersion`] returning the raw value.
714    ///
715    /// Should __only__ be used for serialization.
716    pub fn into_raw(self) -> u64 {
717        self.0
718    }
719
720    /// Create a [`RelationVersion`] from a raw value.
721    ///
722    /// Should __only__ be used for serialization.
723    pub fn from_raw(val: u64) -> RelationVersion {
724        RelationVersion(val)
725    }
726}
727
728impl From<RelationVersion> for SchemaId {
729    fn from(value: RelationVersion) -> Self {
730        SchemaId(usize::cast_from(value.0))
731    }
732}
733
734impl From<mz_sql_parser::ast::Version> for RelationVersion {
735    fn from(value: mz_sql_parser::ast::Version) -> Self {
736        RelationVersion(value.into_inner())
737    }
738}
739
740impl fmt::Display for RelationVersion {
741    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742        write!(f, "v{}", self.0)
743    }
744}
745
746impl From<RelationVersion> for mz_sql_parser::ast::Version {
747    fn from(value: RelationVersion) -> Self {
748        mz_sql_parser::ast::Version::new(value.0)
749    }
750}
751
752impl RustType<ProtoRelationVersion> for RelationVersion {
753    fn into_proto(&self) -> ProtoRelationVersion {
754        ProtoRelationVersion { value: self.0 }
755    }
756
757    fn from_proto(proto: ProtoRelationVersion) -> Result<Self, TryFromProtoError> {
758        Ok(RelationVersion(proto.value))
759    }
760}
761
762/// Semantic type annotation for a column in a builtin catalog relation.
763///
764/// These are compile-time metadata used by the catalog ontology layer to
765/// describe the meaning of a column (e.g., that it contains a catalog item ID
766/// or a role ID). Possible values correspond to the entries in
767/// `SEMANTIC_TYPE_DEFS` in the `mz-catalog` crate.
768#[derive(
769    Clone,
770    Copy,
771    Debug,
772    PartialEq,
773    Eq,
774    PartialOrd,
775    Ord,
776    Hash,
777    serde::Serialize
778)]
779pub enum SemanticType {
780    CatalogItemId,
781    GlobalId,
782    ClusterId,
783    ReplicaId,
784    SchemaId,
785    DatabaseId,
786    RoleId,
787    NetworkPolicyId,
788    ShardId,
789    OID,
790    ObjectType,
791    ConnectionType,
792    SourceType,
793    MzTimestamp,
794    WallclockTimestamp,
795    ByteCount,
796    RecordCount,
797    CreditRate,
798    SqlDefinition,
799    RedactedSqlDefinition,
800}
801
802impl fmt::Display for SemanticType {
803    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804        let s = match self {
805            SemanticType::CatalogItemId => "CatalogItemId",
806            SemanticType::GlobalId => "GlobalId",
807            SemanticType::ClusterId => "ClusterId",
808            SemanticType::ReplicaId => "ReplicaId",
809            SemanticType::SchemaId => "SchemaId",
810            SemanticType::DatabaseId => "DatabaseId",
811            SemanticType::RoleId => "RoleId",
812            SemanticType::NetworkPolicyId => "NetworkPolicyId",
813            SemanticType::ShardId => "ShardId",
814            SemanticType::OID => "OID",
815            SemanticType::ObjectType => "ObjectType",
816            SemanticType::ConnectionType => "ConnectionType",
817            SemanticType::SourceType => "SourceType",
818            SemanticType::MzTimestamp => "MzTimestamp",
819            SemanticType::WallclockTimestamp => "WallclockTimestamp",
820            SemanticType::ByteCount => "ByteCount",
821            SemanticType::RecordCount => "RecordCount",
822            SemanticType::CreditRate => "CreditRate",
823            SemanticType::SqlDefinition => "SqlDefinition",
824            SemanticType::RedactedSqlDefinition => "RedactedSqlDefinition",
825        };
826        f.write_str(s)
827    }
828}
829
830/// Metadata (other than type) for a column in a [`RelationDesc`].
831#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
832struct ColumnMetadata {
833    /// Name of the column.
834    name: ColumnName,
835    /// Index into a [`SqlRelationType`] for this column.
836    typ_idx: usize,
837    /// Version this column was added at.
838    added: RelationVersion,
839    /// Version this column was dropped at.
840    dropped: Option<RelationVersion>,
841}
842
843/// A description of the shape of a relation.
844///
845/// It bundles a [`SqlRelationType`] with `ColumnMetadata` for each column in
846/// the relation.
847///
848/// # Examples
849///
850/// A `RelationDesc`s is typically constructed via its builder API:
851///
852/// ```
853/// use mz_repr::{SqlColumnType, RelationDesc, SqlScalarType};
854///
855/// let desc = RelationDesc::builder()
856///     .with_column("id", SqlScalarType::Int64.nullable(false))
857///     .with_column("price", SqlScalarType::Float64.nullable(true))
858///     .finish();
859/// ```
860///
861/// In more complicated cases, like when constructing a `RelationDesc` in
862/// response to user input, it may be more convenient to construct a relation
863/// type first, and imbue it with column names to form a `RelationDesc` later:
864///
865/// ```
866/// use mz_repr::RelationDesc;
867///
868/// # fn plan_query(_: &str) -> mz_repr::SqlRelationType { mz_repr::SqlRelationType::new(vec![]) }
869/// let relation_type = plan_query("SELECT * FROM table");
870/// let names = (0..relation_type.arity()).map(|i| match i {
871///     0 => "first",
872///     1 => "second",
873///     _ => "unknown",
874/// });
875/// let desc = RelationDesc::new(relation_type, names);
876/// ```
877///
878/// Next to the [`SqlRelationType`] we maintain a map of `ColumnIndex` to
879/// `ColumnMetadata`, where [`ColumnIndex`] is a stable identifier for a
880/// column throughout the lifetime of the relation. This allows a
881/// [`RelationDesc`] to represent a projection over a version of itself.
882///
883/// ```
884/// use std::collections::BTreeSet;
885/// use mz_repr::{ColumnIndex, RelationDesc, SqlScalarType};
886///
887/// let desc = RelationDesc::builder()
888///     .with_column("name", SqlScalarType::String.nullable(false))
889///     .with_column("email", SqlScalarType::String.nullable(false))
890///     .finish();
891///
892/// // Project away the second column.
893/// let demands = BTreeSet::from([1]);
894/// let proj = desc.apply_demand(&demands);
895///
896/// // We projected away the first column.
897/// assert!(!proj.contains_index(&ColumnIndex::from_raw(0)));
898/// // But retained the second.
899/// assert!(proj.contains_index(&ColumnIndex::from_raw(1)));
900///
901/// // The underlying `SqlRelationType` also contains a single column.
902/// assert_eq!(proj.typ().arity(), 1);
903/// ```
904///
905/// To maintain this stable mapping and track the lifetime of a column (e.g.
906/// when adding or dropping a column) we use `ColumnMetadata`. It maintains
907/// the index in [`SqlRelationType`] that corresponds to a given column, and the
908/// version at which this column was added or dropped.
909///
910#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
911pub struct RelationDesc {
912    typ: SqlRelationType,
913    metadata: BTreeMap<ColumnIndex, ColumnMetadata>,
914}
915
916impl RustType<ProtoRelationDesc> for RelationDesc {
917    fn into_proto(&self) -> ProtoRelationDesc {
918        let (names, metadata): (Vec<_>, Vec<_>) = self
919            .metadata
920            .values()
921            .map(|meta| {
922                let metadata = ProtoColumnMetadata {
923                    added: Some(meta.added.into_proto()),
924                    dropped: meta.dropped.map(|v| v.into_proto()),
925                };
926                (meta.name.into_proto(), metadata)
927            })
928            .unzip();
929
930        // `metadata` Migration Logic: We wrote some `ProtoRelationDesc`s into Persist before the
931        // metadata field was added. To make sure our serialization roundtrips the same as before
932        // we added the field, we omit `metadata` if all of the values are equal to the default.
933        //
934        // Note: This logic needs to exist approximately forever.
935        let is_all_default_metadata = metadata.iter().all(|meta| {
936            meta.added == Some(RelationVersion::root().into_proto()) && meta.dropped == None
937        });
938        let metadata = if is_all_default_metadata {
939            Vec::new()
940        } else {
941            metadata
942        };
943
944        ProtoRelationDesc {
945            typ: Some(self.typ.into_proto()),
946            names,
947            metadata,
948        }
949    }
950
951    fn from_proto(proto: ProtoRelationDesc) -> Result<Self, TryFromProtoError> {
952        // `metadata` Migration Logic: We wrote some `ProtoRelationDesc`s into Persist before the
953        // metadata field was added. If the field doesn't exist we fill it in with default values,
954        // and when converting into_proto we omit these fields so the serialized bytes roundtrip.
955        //
956        // Note: This logic needs to exist approximately forever.
957        let proto_metadata: Box<dyn Iterator<Item = _>> = if proto.metadata.is_empty() {
958            let val = ProtoColumnMetadata {
959                added: Some(RelationVersion::root().into_proto()),
960                dropped: None,
961            };
962            Box::new(itertools::repeat_n(val, proto.names.len()))
963        } else {
964            // Reject mismatched lengths explicitly rather than panicking via
965            // `zip_eq` below, since this branch is reachable from untrusted
966            // proto bytes.
967            if proto.names.len() != proto.metadata.len() {
968                return Err(TryFromProtoError::InvalidFieldError(format!(
969                    "ProtoRelationDesc: names ({}) and metadata ({}) length mismatch",
970                    proto.names.len(),
971                    proto.metadata.len()
972                )));
973            }
974            Box::new(proto.metadata.into_iter())
975        };
976
977        let metadata = proto
978            .names
979            .into_iter()
980            .zip_eq(proto_metadata)
981            .enumerate()
982            .map(|(idx, (name, metadata))| {
983                let meta = ColumnMetadata {
984                    name: name.into_rust()?,
985                    typ_idx: idx,
986                    added: metadata.added.into_rust_if_some("ColumnMetadata::added")?,
987                    dropped: metadata.dropped.into_rust()?,
988                };
989                Ok::<_, TryFromProtoError>((ColumnIndex(idx), meta))
990            })
991            .collect::<Result<_, _>>()?;
992
993        Ok(RelationDesc {
994            typ: proto.typ.into_rust_if_some("ProtoRelationDesc::typ")?,
995            metadata,
996        })
997    }
998}
999
1000impl RelationDesc {
1001    /// Returns a [`RelationDescBuilder`] that can be used to construct a [`RelationDesc`].
1002    pub fn builder() -> RelationDescBuilder {
1003        RelationDescBuilder::default()
1004    }
1005
1006    /// Constructs a new `RelationDesc` that represents the empty relation
1007    /// with no columns and no keys.
1008    pub fn empty() -> Self {
1009        RelationDesc {
1010            typ: SqlRelationType::empty(),
1011            metadata: BTreeMap::default(),
1012        }
1013    }
1014
1015    /// Check if the `RelationDesc` is empty.
1016    pub fn is_empty(&self) -> bool {
1017        self == &Self::empty()
1018    }
1019
1020    /// Returns the number of columns in this [`RelationDesc`].
1021    pub fn len(&self) -> usize {
1022        self.typ().column_types.len()
1023    }
1024
1025    /// Constructs a new `RelationDesc` from a `SqlRelationType` and an iterator
1026    /// over column names.
1027    ///
1028    /// # Panics
1029    ///
1030    /// Panics if the arity of the `SqlRelationType` is not equal to the number of
1031    /// items in `names`.
1032    pub fn new<I, N>(typ: SqlRelationType, names: I) -> Self
1033    where
1034        I: IntoIterator<Item = N>,
1035        N: Into<ColumnName>,
1036    {
1037        let metadata: BTreeMap<_, _> = names
1038            .into_iter()
1039            .enumerate()
1040            .map(|(idx, name)| {
1041                let col_idx = ColumnIndex(idx);
1042                let metadata = ColumnMetadata {
1043                    name: name.into(),
1044                    typ_idx: idx,
1045                    added: RelationVersion::root(),
1046                    dropped: None,
1047                };
1048                (col_idx, metadata)
1049            })
1050            .collect();
1051
1052        // TODO(parkmycar): Add better validation here.
1053        assert_eq!(typ.column_types.len(), metadata.len());
1054
1055        RelationDesc { typ, metadata }
1056    }
1057
1058    pub fn from_names_and_types<I, T, N>(iter: I) -> Self
1059    where
1060        I: IntoIterator<Item = (N, T)>,
1061        T: Into<SqlColumnType>,
1062        N: Into<ColumnName>,
1063    {
1064        let (names, types): (Vec<_>, Vec<_>) = iter.into_iter().unzip();
1065        let types = types.into_iter().map(Into::into).collect();
1066        let typ = SqlRelationType::new(types);
1067        Self::new(typ, names)
1068    }
1069
1070    /// Concatenates a `RelationDesc` onto the end of this `RelationDesc`.
1071    ///
1072    /// # Panics
1073    ///
1074    /// Panics if either `self` or `other` have columns that were added at a
1075    /// [`RelationVersion`] other than [`RelationVersion::root`] or if any
1076    /// columns were dropped.
1077    ///
1078    /// TODO(parkmycar): Move this method to [`RelationDescBuilder`].
1079    pub fn concat(mut self, other: Self) -> Self {
1080        let self_len = self.typ.column_types.len();
1081
1082        for (typ, (_col_idx, meta)) in other.typ.column_types.into_iter().zip_eq(other.metadata) {
1083            assert_eq!(meta.added, RelationVersion::root());
1084            assert_none!(meta.dropped);
1085
1086            let new_idx = self.typ.columns().len();
1087            let new_meta = ColumnMetadata {
1088                name: meta.name,
1089                typ_idx: new_idx,
1090                added: RelationVersion::root(),
1091                dropped: None,
1092            };
1093
1094            self.typ.column_types.push(typ);
1095            let prev = self.metadata.insert(ColumnIndex(new_idx), new_meta);
1096
1097            assert_eq!(self.metadata.len(), self.typ.columns().len());
1098            assert_none!(prev);
1099        }
1100
1101        for k in other.typ.keys {
1102            let k = k.into_iter().map(|idx| idx + self_len).collect();
1103            self = self.with_key(k);
1104        }
1105        self
1106    }
1107
1108    /// Adds a new key for the relation.
1109    pub fn with_key(mut self, indices: Vec<usize>) -> Self {
1110        self.typ = self.typ.with_key(indices);
1111        self
1112    }
1113
1114    /// Drops all existing keys.
1115    pub fn without_keys(mut self) -> Self {
1116        self.typ.keys.clear();
1117        self
1118    }
1119
1120    /// Builds a new relation description with the column names replaced with
1121    /// new names.
1122    ///
1123    /// # Panics
1124    ///
1125    /// Panics if the arity of the relation type does not match the number of
1126    /// items in `names`.
1127    pub fn with_names<I, N>(self, names: I) -> Self
1128    where
1129        I: IntoIterator<Item = N>,
1130        N: Into<ColumnName>,
1131    {
1132        Self::new(self.typ, names)
1133    }
1134
1135    /// Computes the number of columns in the relation.
1136    pub fn arity(&self) -> usize {
1137        self.typ.arity()
1138    }
1139
1140    /// Returns the relation type underlying this relation description.
1141    pub fn typ(&self) -> &SqlRelationType {
1142        &self.typ
1143    }
1144
1145    /// Returns the owned relation type underlying this relation description.
1146    pub fn into_typ(self) -> SqlRelationType {
1147        self.typ
1148    }
1149
1150    /// Returns an iterator over the columns in this relation.
1151    pub fn iter(&self) -> impl Iterator<Item = (&ColumnName, &SqlColumnType)> {
1152        self.metadata.values().map(|meta| {
1153            let typ = &self.typ.columns()[meta.typ_idx];
1154            (&meta.name, typ)
1155        })
1156    }
1157
1158    /// Returns an iterator over the types of the columns in this relation.
1159    pub fn iter_types(&self) -> impl Iterator<Item = &SqlColumnType> {
1160        self.typ.column_types.iter()
1161    }
1162
1163    /// Returns an iterator over the names of the columns in this relation.
1164    pub fn iter_names(&self) -> impl Iterator<Item = &ColumnName> {
1165        self.metadata.values().map(|meta| &meta.name)
1166    }
1167
1168    /// Returns an iterator over the columns in this relation, with all their metadata.
1169    pub fn iter_all(&self) -> impl Iterator<Item = (&ColumnIndex, &ColumnName, &SqlColumnType)> {
1170        self.metadata.iter().map(|(col_idx, metadata)| {
1171            let col_typ = &self.typ.columns()[metadata.typ_idx];
1172            (col_idx, &metadata.name, col_typ)
1173        })
1174    }
1175
1176    /// Returns an iterator over the names of the columns in this relation that are "similar" to
1177    /// the provided `name`.
1178    pub fn iter_similar_names<'a>(
1179        &'a self,
1180        name: &'a ColumnName,
1181    ) -> impl Iterator<Item = &'a ColumnName> {
1182        self.iter_names().filter(|n| n.is_similar(name))
1183    }
1184
1185    /// Returns whether this [`RelationDesc`] contains a column at the specified index.
1186    pub fn contains_index(&self, idx: &ColumnIndex) -> bool {
1187        self.metadata.contains_key(idx)
1188    }
1189
1190    /// Finds a column by name.
1191    ///
1192    /// Returns the index and type of the column named `name`. If no column with
1193    /// the specified name exists, returns `None`. If multiple columns have the
1194    /// specified name, the leftmost column is returned.
1195    pub fn get_by_name(&self, name: &ColumnName) -> Option<(usize, &SqlColumnType)> {
1196        self.iter_names()
1197            .position(|n| n == name)
1198            .map(|i| (i, &self.typ.column_types[i]))
1199    }
1200
1201    /// Gets the name of the `i`th column.
1202    ///
1203    /// # Panics
1204    ///
1205    /// Panics if `i` is not a valid column index.
1206    ///
1207    /// TODO(parkmycar): Migrate all uses of this to [`RelationDesc::get_name_idx`].
1208    pub fn get_name(&self, i: usize) -> &ColumnName {
1209        // TODO(parkmycar): Refactor this to use `ColumnIndex`.
1210        self.get_name_idx(&ColumnIndex(i))
1211    }
1212
1213    /// Gets the name of the column at `idx`.
1214    ///
1215    /// # Panics
1216    ///
1217    /// Panics if no column exists at `idx`.
1218    pub fn get_name_idx(&self, idx: &ColumnIndex) -> &ColumnName {
1219        &self.metadata.get(idx).expect("should exist").name
1220    }
1221
1222    /// Mutably gets the name of the `i`th column.
1223    ///
1224    /// # Panics
1225    ///
1226    /// Panics if `i` is not a valid column index.
1227    pub fn get_name_mut(&mut self, i: usize) -> &mut ColumnName {
1228        // TODO(parkmycar): Refactor this to use `ColumnIndex`.
1229        &mut self
1230            .metadata
1231            .get_mut(&ColumnIndex(i))
1232            .expect("should exist")
1233            .name
1234    }
1235
1236    /// Gets the [`SqlColumnType`] of the column at `idx`.
1237    ///
1238    /// # Panics
1239    ///
1240    /// Panics if no column exists at `idx`.
1241    pub fn get_type(&self, idx: &ColumnIndex) -> &SqlColumnType {
1242        let typ_idx = self.metadata.get(idx).expect("should exist").typ_idx;
1243        &self.typ.column_types[typ_idx]
1244    }
1245
1246    /// Gets the name of the `i`th column if that column name is unambiguous.
1247    ///
1248    /// If at least one other column has the same name as the `i`th column,
1249    /// returns `None`. If the `i`th column has no name, returns `None`.
1250    ///
1251    /// # Panics
1252    ///
1253    /// Panics if `i` is not a valid column index.
1254    pub fn get_unambiguous_name(&self, i: usize) -> Option<&ColumnName> {
1255        let name = self.get_name(i);
1256        if self.iter_names().filter(|n| *n == name).count() == 1 {
1257            Some(name)
1258        } else {
1259            None
1260        }
1261    }
1262
1263    /// Verifies that `d` meets all of the constraints for the `i`th column of `self`.
1264    ///
1265    /// n.b. The only constraint MZ currently supports in NOT NULL, but this
1266    /// structure will be simple to extend.
1267    pub fn constraints_met(&self, i: usize, d: &Datum) -> Result<(), NotNullViolation> {
1268        let name = self.get_name(i);
1269        let typ = &self.typ.column_types[i];
1270        if d == &Datum::Null && !typ.nullable {
1271            Err(NotNullViolation(name.clone()))
1272        } else {
1273            Ok(())
1274        }
1275    }
1276
1277    /// Computes the differences between two [`RelationDesc`]s.
1278    ///
1279    /// Returns a rich diff describing which columns differ, and in what way.
1280    ///
1281    /// # Panics
1282    ///
1283    /// Panics if either `self` or `other` have columns that were added at a
1284    /// [`RelationVersion`] other than [`RelationVersion::root`] or if any
1285    /// columns were dropped.
1286    ///
1287    /// This simplifies things by allowing us to assume that `ColumnIndex`es are
1288    /// dense and that they match the indexes of `typ.columns()`. Without this
1289    /// we would, e.g., struggle comparing keys as those are in terms of
1290    /// `typ.columns()` indexes.
1291    pub fn diff(&self, other: &RelationDesc) -> RelationDescDiff {
1292        assert_eq!(self.metadata.len(), self.typ.columns().len());
1293        assert_eq!(other.metadata.len(), other.typ.columns().len());
1294        for (idx, meta) in self.metadata.iter().chain(other.metadata.iter()) {
1295            assert_eq!(meta.typ_idx, idx.0);
1296            assert_eq!(meta.added, RelationVersion::root());
1297            assert_none!(meta.dropped);
1298        }
1299
1300        let mut column_diffs = BTreeMap::new();
1301        let mut key_diff = None;
1302
1303        let left_arity = self.arity();
1304        let right_arity = other.arity();
1305        let common_arity = std::cmp::min(left_arity, right_arity);
1306
1307        for idx in 0..common_arity {
1308            let left_name = self.get_name(idx);
1309            let right_name = other.get_name(idx);
1310            let left_type = &self.typ.column_types[idx];
1311            let right_type = &other.typ.column_types[idx];
1312
1313            if left_name != right_name {
1314                let diff = ColumnDiff::NameMismatch {
1315                    left: left_name.clone(),
1316                    right: right_name.clone(),
1317                };
1318                column_diffs.insert(idx, diff);
1319            } else if left_type.scalar_type != right_type.scalar_type {
1320                let diff = ColumnDiff::TypeMismatch {
1321                    name: left_name.clone(),
1322                    left: left_type.scalar_type.clone(),
1323                    right: right_type.scalar_type.clone(),
1324                };
1325                column_diffs.insert(idx, diff);
1326            } else if left_type.nullable != right_type.nullable {
1327                let diff = ColumnDiff::NullabilityMismatch {
1328                    name: left_name.clone(),
1329                    left: left_type.nullable,
1330                    right: right_type.nullable,
1331                };
1332                column_diffs.insert(idx, diff);
1333            }
1334        }
1335
1336        for idx in common_arity..left_arity {
1337            let diff = ColumnDiff::Missing {
1338                name: self.get_name(idx).clone(),
1339            };
1340            column_diffs.insert(idx, diff);
1341        }
1342
1343        for idx in common_arity..right_arity {
1344            let diff = ColumnDiff::Extra {
1345                name: other.get_name(idx).clone(),
1346            };
1347            column_diffs.insert(idx, diff);
1348        }
1349
1350        let left_keys: BTreeSet<_> = self.typ.keys.iter().collect();
1351        let right_keys: BTreeSet<_> = other.typ.keys.iter().collect();
1352        if left_keys != right_keys {
1353            let column_names = |desc: &RelationDesc, keys: BTreeSet<&Vec<usize>>| {
1354                keys.iter()
1355                    .map(|key| key.iter().map(|&idx| desc.get_name(idx).clone()).collect())
1356                    .collect()
1357            };
1358            key_diff = Some(KeyDiff {
1359                left: column_names(self, left_keys),
1360                right: column_names(other, right_keys),
1361            });
1362        }
1363
1364        RelationDescDiff {
1365            column_diffs,
1366            key_diff,
1367        }
1368    }
1369
1370    /// Creates a new [`RelationDesc`] retaining only the columns specified in `demands`.
1371    pub fn apply_demand(&self, demands: &BTreeSet<usize>) -> RelationDesc {
1372        let mut new_desc = self.clone();
1373
1374        // Update ColumnMetadata.
1375        let mut removed = 0;
1376        new_desc.metadata.retain(|idx, metadata| {
1377            let retain = demands.contains(&idx.0);
1378            if !retain {
1379                removed += 1;
1380            } else {
1381                metadata.typ_idx -= removed;
1382            }
1383            retain
1384        });
1385
1386        // Update SqlColumnType.
1387        let mut idx = 0;
1388        new_desc.typ.column_types.retain(|_| {
1389            let keep = demands.contains(&idx);
1390            idx += 1;
1391            keep
1392        });
1393
1394        new_desc
1395    }
1396}
1397
1398#[cfg(any(test, feature = "proptest"))]
1399impl Arbitrary for RelationDesc {
1400    type Parameters = ();
1401    type Strategy = BoxedStrategy<RelationDesc>;
1402
1403    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
1404        let mut weights = vec![(100, Just(0..4)), (50, Just(4..8)), (25, Just(8..16))];
1405        if std::env::var("PROPTEST_LARGE_DATA").is_ok() {
1406            weights.extend([
1407                (12, Just(16..32)),
1408                (6, Just(32..64)),
1409                (3, Just(64..128)),
1410                (1, Just(128..256)),
1411            ]);
1412        }
1413        let num_columns = Union::new_weighted(weights);
1414
1415        num_columns.prop_flat_map(arb_relation_desc).boxed()
1416    }
1417}
1418
1419/// Returns a [`Strategy`] that generates an arbitrary [`RelationDesc`] with a number columns
1420/// within the range provided.
1421#[cfg(any(test, feature = "proptest"))]
1422pub fn arb_relation_desc(num_cols: std::ops::Range<usize>) -> impl Strategy<Value = RelationDesc> {
1423    proptest::collection::btree_map(any::<ColumnName>(), any::<SqlColumnType>(), num_cols)
1424        .prop_map(RelationDesc::from_names_and_types)
1425}
1426
1427/// Returns a [`Strategy`] that generates a projection of the provided [`RelationDesc`].
1428#[cfg(any(test, feature = "proptest"))]
1429pub fn arb_relation_desc_projection(desc: RelationDesc) -> impl Strategy<Value = RelationDesc> {
1430    let mask: Vec<_> = (0..desc.len()).map(|_| any::<bool>()).collect();
1431    mask.prop_map(move |mask| {
1432        let demands: BTreeSet<_> = mask
1433            .into_iter()
1434            .enumerate()
1435            .filter_map(|(idx, keep)| keep.then_some(idx))
1436            .collect();
1437        desc.apply_demand(&demands)
1438    })
1439}
1440
1441impl IntoIterator for RelationDesc {
1442    type Item = (ColumnName, SqlColumnType);
1443    type IntoIter = Box<dyn Iterator<Item = (ColumnName, SqlColumnType)>>;
1444
1445    fn into_iter(self) -> Self::IntoIter {
1446        let iter = self
1447            .metadata
1448            .into_values()
1449            .zip_eq(self.typ.column_types)
1450            .map(|(meta, typ)| (meta.name, typ));
1451        Box::new(iter)
1452    }
1453}
1454
1455/// Returns a [`Strategy`] that yields arbitrary [`Row`]s for the provided [`RelationDesc`].
1456#[cfg(any(test, feature = "proptest"))]
1457pub fn arb_row_for_relation(desc: &RelationDesc) -> impl Strategy<Value = Row> + use<> {
1458    let datums: Vec<_> = desc
1459        .typ()
1460        .columns()
1461        .iter()
1462        .cloned()
1463        .map(arb_datum_for_column)
1464        .collect();
1465    datums.prop_map(|x| Row::pack(x.iter().map(Datum::from)))
1466}
1467
1468/// Expression violated not-null constraint on named column
1469#[derive(Debug, PartialEq, Eq)]
1470pub struct NotNullViolation(pub ColumnName);
1471
1472impl fmt::Display for NotNullViolation {
1473    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1474        write!(
1475            f,
1476            "null value in column {} violates not-null constraint",
1477            self.0.quoted()
1478        )
1479    }
1480}
1481
1482/// The result of comparing two [`RelationDesc`]s.
1483#[derive(Debug, Clone, PartialEq, Eq)]
1484pub struct RelationDescDiff {
1485    /// Column differences, keyed by column index.
1486    pub column_diffs: BTreeMap<usize, ColumnDiff>,
1487    /// Key differences, if any.
1488    pub key_diff: Option<KeyDiff>,
1489}
1490
1491impl RelationDescDiff {
1492    /// Returns whether the diff contains any differences.
1493    pub fn is_empty(&self) -> bool {
1494        self.column_diffs.is_empty() && self.key_diff.is_none()
1495    }
1496}
1497
1498/// A difference in a column between two [`RelationDesc`]s.
1499#[derive(Debug, Clone, PartialEq, Eq)]
1500pub enum ColumnDiff {
1501    /// Column exists only in the left relation.
1502    Missing { name: ColumnName },
1503    /// Column exists only in the right relation.
1504    Extra { name: ColumnName },
1505    /// Columns have different types.
1506    TypeMismatch {
1507        name: ColumnName,
1508        left: SqlScalarType,
1509        right: SqlScalarType,
1510    },
1511    /// Columns have different nullability.
1512    NullabilityMismatch {
1513        name: ColumnName,
1514        left: bool,
1515        right: bool,
1516    },
1517    /// Columns have different names.
1518    NameMismatch { left: ColumnName, right: ColumnName },
1519}
1520
1521/// A difference in the keys of two [`RelationDesc`]s.
1522#[derive(Debug, Clone, PartialEq, Eq)]
1523pub struct KeyDiff {
1524    /// Keys of the left relation.
1525    pub left: BTreeSet<Vec<ColumnName>>,
1526    /// Keys of the right relation.
1527    pub right: BTreeSet<Vec<ColumnName>>,
1528}
1529
1530/// A builder for a [`RelationDesc`].
1531#[derive(Clone, Default, Debug, PartialEq, Eq)]
1532pub struct RelationDescBuilder {
1533    /// Columns of the relation.
1534    columns: Vec<(ColumnName, SqlColumnType)>,
1535    /// Sets of indices that are "keys" for the collection.
1536    keys: Vec<Vec<usize>>,
1537}
1538
1539impl RelationDescBuilder {
1540    /// Appends a column with the specified name and type.
1541    pub fn with_column<N: Into<ColumnName>>(
1542        mut self,
1543        name: N,
1544        ty: SqlColumnType,
1545    ) -> RelationDescBuilder {
1546        let name = name.into();
1547        self.columns.push((name, ty));
1548        self
1549    }
1550
1551    /// Appends the provided columns to the builder.
1552    pub fn with_columns<I, T, N>(mut self, iter: I) -> Self
1553    where
1554        I: IntoIterator<Item = (N, T)>,
1555        T: Into<SqlColumnType>,
1556        N: Into<ColumnName>,
1557    {
1558        self.columns
1559            .extend(iter.into_iter().map(|(name, ty)| (name.into(), ty.into())));
1560        self
1561    }
1562
1563    /// Adds a new key for the relation.
1564    pub fn with_key(mut self, mut indices: Vec<usize>) -> RelationDescBuilder {
1565        indices.sort_unstable();
1566        if !self.keys.contains(&indices) {
1567            self.keys.push(indices);
1568        }
1569        self
1570    }
1571
1572    /// Removes all previously inserted keys.
1573    pub fn without_keys(mut self) -> RelationDescBuilder {
1574        self.keys.clear();
1575        assert_eq!(self.keys.len(), 0);
1576        self
1577    }
1578
1579    /// Concatenates a [`RelationDescBuilder`] onto the end of this [`RelationDescBuilder`].
1580    pub fn concat(mut self, other: Self) -> Self {
1581        let self_len = self.columns.len();
1582
1583        self.columns.extend(other.columns);
1584        for k in other.keys {
1585            let k = k.into_iter().map(|idx| idx + self_len).collect();
1586            self = self.with_key(k);
1587        }
1588
1589        self
1590    }
1591
1592    /// Finish the builder, returning a [`RelationDesc`].
1593    pub fn finish(self) -> RelationDesc {
1594        let mut desc = RelationDesc::from_names_and_types(self.columns);
1595        desc.typ = desc.typ.with_keys(self.keys);
1596        desc
1597    }
1598}
1599
1600/// Describes a [`RelationDesc`] at a specific version of a [`VersionedRelationDesc`].
1601#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)]
1602pub enum RelationVersionSelector {
1603    Specific(RelationVersion),
1604    Latest,
1605}
1606
1607impl RelationVersionSelector {
1608    pub fn specific(version: u64) -> Self {
1609        RelationVersionSelector::Specific(RelationVersion(version))
1610    }
1611}
1612
1613/// A wrapper around [`RelationDesc`] that provides an interface for adding
1614/// columns and generating new versions.
1615///
1616/// TODO(parkmycar): Using an immutable data structure for RelationDesc would
1617/// be great.
1618#[derive(Debug, Clone, Serialize)]
1619pub struct VersionedRelationDesc {
1620    inner: RelationDesc,
1621}
1622
1623impl VersionedRelationDesc {
1624    pub fn new(inner: RelationDesc) -> Self {
1625        VersionedRelationDesc { inner }
1626    }
1627
1628    /// Adds a new column to this [`RelationDesc`], creating a new version of the [`RelationDesc`].
1629    ///
1630    /// # Panics
1631    ///
1632    /// * Panics if a column with `name` already exists that hasn't been dropped.
1633    ///
1634    /// Note: For building a [`RelationDesc`] see [`RelationDescBuilder::with_column`].
1635    #[must_use]
1636    pub fn add_column<N, T>(&mut self, name: N, typ: T) -> RelationVersion
1637    where
1638        N: Into<ColumnName>,
1639        T: Into<SqlColumnType>,
1640    {
1641        let latest_version = self.latest_version();
1642        let new_version = latest_version.bump();
1643
1644        let name = name.into();
1645        let existing = self
1646            .inner
1647            .metadata
1648            .iter()
1649            .find(|(_, meta)| meta.name == name && meta.dropped.is_none());
1650        if let Some(existing) = existing {
1651            panic!("column named '{name}' already exists! {existing:?}");
1652        }
1653
1654        let next_idx = self.inner.metadata.len();
1655        let col_meta = ColumnMetadata {
1656            name,
1657            typ_idx: next_idx,
1658            added: new_version,
1659            dropped: None,
1660        };
1661
1662        self.inner.typ.column_types.push(typ.into());
1663        let prev = self.inner.metadata.insert(ColumnIndex(next_idx), col_meta);
1664
1665        assert_none!(prev, "column index overlap!");
1666        self.validate();
1667
1668        new_version
1669    }
1670
1671    /// Drops the column `name` from this [`RelationDesc`]. If there are multiple columns with
1672    /// `name` drops the left-most one that hasn't already been dropped.
1673    ///
1674    /// TODO(parkmycar): Add handling for dropping a column that is currently used as a key.
1675    ///
1676    /// # Panics
1677    ///
1678    /// Panics if a column with `name` does not exist or the dropped column was used as a key.
1679    #[must_use]
1680    pub fn drop_column<N>(&mut self, name: N) -> RelationVersion
1681    where
1682        N: Into<ColumnName>,
1683    {
1684        let name = name.into();
1685        let latest_version = self.latest_version();
1686        let new_version = latest_version.bump();
1687
1688        let col = self
1689            .inner
1690            .metadata
1691            .values_mut()
1692            .find(|meta| meta.name == name && meta.dropped.is_none())
1693            .expect("column to exist");
1694
1695        // Make sure the column hadn't been previously dropped.
1696        assert_none!(col.dropped, "column was already dropped");
1697        col.dropped = Some(new_version);
1698
1699        // Make sure the column isn't being used as a key.
1700        let dropped_key = self
1701            .inner
1702            .typ
1703            .keys
1704            .iter()
1705            .any(|keys| keys.contains(&col.typ_idx));
1706        assert!(!dropped_key, "column being dropped was used as a key");
1707
1708        self.validate();
1709        new_version
1710    }
1711
1712    /// Returns the [`RelationDesc`] at the latest version.
1713    pub fn latest(&self) -> RelationDesc {
1714        self.inner.clone()
1715    }
1716
1717    /// Returns this [`RelationDesc`] at the specified version.
1718    pub fn at_version(&self, version: RelationVersionSelector) -> RelationDesc {
1719        // Get all of the changes from the start, up to whatever version was requested.
1720        let up_to_version = match version {
1721            RelationVersionSelector::Latest => RelationVersion(u64::MAX),
1722            RelationVersionSelector::Specific(v) => v,
1723        };
1724
1725        let valid_columns = self.inner.metadata.iter().filter(|(_col_idx, meta)| {
1726            let added = meta.added <= up_to_version;
1727            let dropped = meta
1728                .dropped
1729                .map(|dropped_at| up_to_version >= dropped_at)
1730                .unwrap_or(false);
1731
1732            added && !dropped
1733        });
1734
1735        let mut column_types = Vec::new();
1736        let mut column_metas = BTreeMap::new();
1737
1738        // N.B. At this point we need to be careful because col_idx might not
1739        // equal typ_idx.
1740        //
1741        // For example, consider columns "a", "b", and "c" with indexes 0, 1,
1742        // and 2. If we drop column "b" then we'll have "a" and "c" with column
1743        // indexes 0 and 2, but their indices in SqlRelationType will be 0 and 1.
1744        for (col_idx, meta) in valid_columns {
1745            let new_meta = ColumnMetadata {
1746                name: meta.name.clone(),
1747                typ_idx: column_types.len(),
1748                added: meta.added.clone(),
1749                dropped: meta.dropped.clone(),
1750            };
1751            column_types.push(self.inner.typ.columns()[meta.typ_idx].clone());
1752            column_metas.insert(*col_idx, new_meta);
1753        }
1754
1755        // Remap keys in case a column with an index less than that of a key was
1756        // dropped.
1757        //
1758        // For example, consider columns "a", "b", and "c" where "a" and "c" are
1759        // keys and "b" was dropped.
1760        let keys = self
1761            .inner
1762            .typ
1763            .keys
1764            .iter()
1765            .map(|keys| {
1766                keys.iter()
1767                    .map(|key_idx| {
1768                        let metadata = column_metas
1769                            .get(&ColumnIndex(*key_idx))
1770                            .expect("found key for column that doesn't exist");
1771                        metadata.typ_idx
1772                    })
1773                    .collect()
1774            })
1775            .collect();
1776
1777        let relation_type = SqlRelationType { column_types, keys };
1778
1779        RelationDesc {
1780            typ: relation_type,
1781            metadata: column_metas,
1782        }
1783    }
1784
1785    pub fn latest_version(&self) -> RelationVersion {
1786        self.inner
1787            .metadata
1788            .values()
1789            // N.B. Dropped is always greater than added.
1790            .map(|meta| meta.dropped.unwrap_or(meta.added))
1791            .max()
1792            // If there aren't any columns we're implicitly the root version.
1793            .unwrap_or_else(RelationVersion::root)
1794    }
1795
1796    /// Validates internal contraints of the [`RelationDesc`] are correct.
1797    ///
1798    /// # Panics
1799    ///
1800    /// Panics if a constraint is not satisfied.
1801    fn validate(&self) {
1802        fn validate_inner(desc: &RelationDesc) -> Result<(), anyhow::Error> {
1803            if desc.typ.column_types.len() != desc.metadata.len() {
1804                anyhow::bail!("mismatch between number of types and metadatas");
1805            }
1806
1807            for (col_idx, meta) in &desc.metadata {
1808                if col_idx.0 > desc.metadata.len() {
1809                    anyhow::bail!("column index out of bounds");
1810                }
1811                if meta.added >= meta.dropped.unwrap_or(RelationVersion(u64::MAX)) {
1812                    anyhow::bail!("column was added after it was dropped?");
1813                }
1814                if desc.typ().columns().get(meta.typ_idx).is_none() {
1815                    anyhow::bail!("typ_idx incorrect");
1816                }
1817            }
1818
1819            for keys in &desc.typ.keys {
1820                for key in keys {
1821                    if *key >= desc.typ.column_types.len() {
1822                        anyhow::bail!("key index was out of bounds!");
1823                    }
1824                }
1825            }
1826
1827            let versions = desc
1828                .metadata
1829                .values()
1830                .map(|meta| meta.dropped.unwrap_or(meta.added));
1831            let mut max = 0;
1832            let mut sum = 0;
1833            for version in versions {
1834                max = std::cmp::max(max, version.0);
1835                sum += version.0;
1836            }
1837
1838            // Other than RelationVersion(0), we should never have duplicate
1839            // versions and they should always increase by 1. In other words, the
1840            // sum of all RelationVersions should be the sum of [0, max].
1841            //
1842            // N.B. n * (n + 1) / 2 = sum of [0, n]
1843            //
1844            // While I normally don't like tricks like this, it allows us to
1845            // validate that our column versions are correct in O(n) time and
1846            // without allocations.
1847            if sum != (max * (max + 1) / 2) {
1848                anyhow::bail!("there is a duplicate or missing relation version");
1849            }
1850
1851            Ok(())
1852        }
1853
1854        assert_ok!(validate_inner(&self.inner), "validate failed! {self:?}");
1855    }
1856}
1857
1858/// Diffs that can be generated proptest and applied to a [`RelationDesc`] to
1859/// exercise schema migrations.
1860#[derive(Debug)]
1861#[cfg(any(test, feature = "proptest"))]
1862pub enum PropRelationDescDiff {
1863    AddColumn {
1864        name: ColumnName,
1865        typ: SqlColumnType,
1866    },
1867    DropColumn {
1868        name: ColumnName,
1869    },
1870    ToggleNullability {
1871        name: ColumnName,
1872    },
1873    ChangeType {
1874        name: ColumnName,
1875        typ: SqlColumnType,
1876    },
1877}
1878
1879#[cfg(any(test, feature = "proptest"))]
1880impl PropRelationDescDiff {
1881    pub fn apply(self, desc: &mut RelationDesc) {
1882        match self {
1883            PropRelationDescDiff::AddColumn { name, typ } => {
1884                let new_idx = desc.metadata.len();
1885                let meta = ColumnMetadata {
1886                    name,
1887                    typ_idx: new_idx,
1888                    added: RelationVersion(0),
1889                    dropped: None,
1890                };
1891                let prev = desc.metadata.insert(ColumnIndex(new_idx), meta);
1892                desc.typ.column_types.push(typ);
1893
1894                assert_none!(prev);
1895                assert_eq!(desc.metadata.len(), desc.typ.column_types.len());
1896            }
1897            PropRelationDescDiff::DropColumn { name } => {
1898                let next_version = desc
1899                    .metadata
1900                    .values()
1901                    .map(|meta| meta.dropped.unwrap_or(meta.added))
1902                    .max()
1903                    .unwrap_or_else(RelationVersion::root)
1904                    .bump();
1905                let Some(metadata) = desc.metadata.values_mut().find(|meta| meta.name == name)
1906                else {
1907                    return;
1908                };
1909                if metadata.dropped.is_none() {
1910                    metadata.dropped = Some(next_version);
1911                }
1912            }
1913            PropRelationDescDiff::ToggleNullability { name } => {
1914                let Some((pos, _)) = desc.get_by_name(&name) else {
1915                    return;
1916                };
1917                let col_type = desc
1918                    .typ
1919                    .column_types
1920                    .get_mut(pos)
1921                    .expect("ColumnNames and SqlColumnTypes out of sync!");
1922                col_type.nullable = !col_type.nullable;
1923            }
1924            PropRelationDescDiff::ChangeType { name, typ } => {
1925                let Some((pos, _)) = desc.get_by_name(&name) else {
1926                    return;
1927                };
1928                let col_type = desc
1929                    .typ
1930                    .column_types
1931                    .get_mut(pos)
1932                    .expect("ColumnNames and SqlColumnTypes out of sync!");
1933                *col_type = typ;
1934            }
1935        }
1936    }
1937}
1938
1939/// Generates a set of [`PropRelationDescDiff`]s based on some source [`RelationDesc`].
1940#[cfg(any(test, feature = "proptest"))]
1941pub fn arb_relation_desc_diff(
1942    source: &RelationDesc,
1943) -> impl Strategy<Value = Vec<PropRelationDescDiff>> + use<> {
1944    let source = Rc::new(source.clone());
1945    let num_source_columns = source.typ.columns().len();
1946
1947    let num_add_columns = Union::new_weighted(vec![(100, Just(0..8)), (1, Just(8..64))]);
1948    let add_columns_strat = num_add_columns
1949        .prop_flat_map(|num_columns| {
1950            proptest::collection::vec((any::<ColumnName>(), any::<SqlColumnType>()), num_columns)
1951        })
1952        .prop_map(|cols| {
1953            cols.into_iter()
1954                .map(|(name, typ)| PropRelationDescDiff::AddColumn { name, typ })
1955                .collect::<Vec<_>>()
1956        });
1957
1958    // If the source RelationDesc is empty there is nothing else to do.
1959    if num_source_columns == 0 {
1960        return add_columns_strat.boxed();
1961    }
1962
1963    let source_ = Rc::clone(&source);
1964    let drop_columns_strat = (0..num_source_columns).prop_perturb(move |num_columns, mut rng| {
1965        let mut set = BTreeSet::default();
1966        for _ in 0..num_columns {
1967            let col_idx = rng.random_range(0..num_source_columns);
1968            set.insert(source_.get_name(col_idx).clone());
1969        }
1970        set.into_iter()
1971            .map(|name| PropRelationDescDiff::DropColumn { name })
1972            .collect::<Vec<_>>()
1973    });
1974
1975    let source_ = Rc::clone(&source);
1976    let toggle_nullability_strat =
1977        (0..num_source_columns).prop_perturb(move |num_columns, mut rng| {
1978            let mut set = BTreeSet::default();
1979            for _ in 0..num_columns {
1980                let col_idx = rng.random_range(0..num_source_columns);
1981                set.insert(source_.get_name(col_idx).clone());
1982            }
1983            set.into_iter()
1984                .map(|name| PropRelationDescDiff::ToggleNullability { name })
1985                .collect::<Vec<_>>()
1986        });
1987
1988    let source_ = Rc::clone(&source);
1989    let change_type_strat = (0..num_source_columns)
1990        .prop_perturb(move |num_columns, mut rng| {
1991            let mut set = BTreeSet::default();
1992            for _ in 0..num_columns {
1993                let col_idx = rng.random_range(0..num_source_columns);
1994                set.insert(source_.get_name(col_idx).clone());
1995            }
1996            set
1997        })
1998        .prop_flat_map(|cols| {
1999            proptest::collection::vec(any::<SqlColumnType>(), cols.len())
2000                .prop_map(move |types| (cols.clone(), types))
2001        })
2002        .prop_map(|(cols, types)| {
2003            cols.into_iter()
2004                .zip_eq(types)
2005                .map(|(name, typ)| PropRelationDescDiff::ChangeType { name, typ })
2006                .collect::<Vec<_>>()
2007        });
2008
2009    (
2010        add_columns_strat,
2011        drop_columns_strat,
2012        toggle_nullability_strat,
2013        change_type_strat,
2014    )
2015        .prop_map(|(adds, drops, toggles, changes)| {
2016            adds.into_iter()
2017                .chain(drops)
2018                .chain(toggles)
2019                .chain(changes)
2020                .collect::<Vec<_>>()
2021        })
2022        .prop_shuffle()
2023        .boxed()
2024}
2025
2026#[cfg(test)]
2027mod tests {
2028    use super::*;
2029    use prost::Message;
2030
2031    #[mz_ore::test]
2032    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2033    fn smoktest_at_version() {
2034        let desc = RelationDesc::builder()
2035            .with_column("a", SqlScalarType::Bool.nullable(true))
2036            .with_column("z", SqlScalarType::String.nullable(false))
2037            .finish();
2038
2039        let mut versioned_desc = VersionedRelationDesc {
2040            inner: desc.clone(),
2041        };
2042        versioned_desc.validate();
2043
2044        let latest = versioned_desc.at_version(RelationVersionSelector::Latest);
2045        assert_eq!(desc, latest);
2046
2047        let v0 = versioned_desc.at_version(RelationVersionSelector::specific(0));
2048        assert_eq!(desc, v0);
2049
2050        let v3 = versioned_desc.at_version(RelationVersionSelector::specific(3));
2051        assert_eq!(desc, v3);
2052
2053        let v1 = versioned_desc.add_column("b", SqlScalarType::Bytes.nullable(false));
2054        assert_eq!(v1, RelationVersion(1));
2055
2056        let v1 = versioned_desc.at_version(RelationVersionSelector::Specific(v1));
2057        insta::assert_json_snapshot!(v1.metadata, @r###"
2058        {
2059          "0": {
2060            "name": "a",
2061            "typ_idx": 0,
2062            "added": 0,
2063            "dropped": null
2064          },
2065          "1": {
2066            "name": "z",
2067            "typ_idx": 1,
2068            "added": 0,
2069            "dropped": null
2070          },
2071          "2": {
2072            "name": "b",
2073            "typ_idx": 2,
2074            "added": 1,
2075            "dropped": null
2076          }
2077        }
2078        "###);
2079
2080        // Check that V0 doesn't show the new column.
2081        let v0_b = versioned_desc.at_version(RelationVersionSelector::specific(0));
2082        assert!(v0.iter().eq(v0_b.iter()));
2083
2084        let v2 = versioned_desc.drop_column("z");
2085        assert_eq!(v2, RelationVersion(2));
2086
2087        let v2 = versioned_desc.at_version(RelationVersionSelector::Specific(v2));
2088        insta::assert_json_snapshot!(v2.metadata, @r###"
2089        {
2090          "0": {
2091            "name": "a",
2092            "typ_idx": 0,
2093            "added": 0,
2094            "dropped": null
2095          },
2096          "2": {
2097            "name": "b",
2098            "typ_idx": 1,
2099            "added": 1,
2100            "dropped": null
2101          }
2102        }
2103        "###);
2104
2105        // Check that V0 and V1 are still correct.
2106        let v0_c = versioned_desc.at_version(RelationVersionSelector::specific(0));
2107        assert!(v0.iter().eq(v0_c.iter()));
2108
2109        let v1_b = versioned_desc.at_version(RelationVersionSelector::specific(1));
2110        assert!(v1.iter().eq(v1_b.iter()));
2111
2112        insta::assert_json_snapshot!(versioned_desc.inner.metadata, @r###"
2113        {
2114          "0": {
2115            "name": "a",
2116            "typ_idx": 0,
2117            "added": 0,
2118            "dropped": null
2119          },
2120          "1": {
2121            "name": "z",
2122            "typ_idx": 1,
2123            "added": 0,
2124            "dropped": 2
2125          },
2126          "2": {
2127            "name": "b",
2128            "typ_idx": 2,
2129            "added": 1,
2130            "dropped": null
2131          }
2132        }
2133        "###);
2134    }
2135
2136    #[mz_ore::test]
2137    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2138    fn test_dropping_columns_with_keys() {
2139        let desc = RelationDesc::builder()
2140            .with_column("a", SqlScalarType::Bool.nullable(true))
2141            .with_column("z", SqlScalarType::String.nullable(false))
2142            .with_key(vec![1])
2143            .finish();
2144
2145        let mut versioned_desc = VersionedRelationDesc {
2146            inner: desc.clone(),
2147        };
2148        versioned_desc.validate();
2149
2150        let v1 = versioned_desc.drop_column("a");
2151        assert_eq!(v1, RelationVersion(1));
2152
2153        // Make sure the key index for 'z' got remapped since 'a' was dropped.
2154        let v1 = versioned_desc.at_version(RelationVersionSelector::Specific(v1));
2155        insta::assert_json_snapshot!(v1, @r###"
2156        {
2157          "typ": {
2158            "column_types": [
2159              {
2160                "scalar_type": "String",
2161                "nullable": false
2162              }
2163            ],
2164            "keys": [
2165              [
2166                0
2167              ]
2168            ]
2169          },
2170          "metadata": {
2171            "1": {
2172              "name": "z",
2173              "typ_idx": 0,
2174              "added": 0,
2175              "dropped": null
2176            }
2177          }
2178        }
2179        "###);
2180
2181        // Make sure the key index of 'z' is correct when all columns are present.
2182        let v0 = versioned_desc.at_version(RelationVersionSelector::specific(0));
2183        insta::assert_json_snapshot!(v0, @r###"
2184        {
2185          "typ": {
2186            "column_types": [
2187              {
2188                "scalar_type": "Bool",
2189                "nullable": true
2190              },
2191              {
2192                "scalar_type": "String",
2193                "nullable": false
2194              }
2195            ],
2196            "keys": [
2197              [
2198                1
2199              ]
2200            ]
2201          },
2202          "metadata": {
2203            "0": {
2204              "name": "a",
2205              "typ_idx": 0,
2206              "added": 0,
2207              "dropped": 1
2208            },
2209            "1": {
2210              "name": "z",
2211              "typ_idx": 1,
2212              "added": 0,
2213              "dropped": null
2214            }
2215          }
2216        }
2217        "###);
2218    }
2219
2220    #[mz_ore::test]
2221    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2222    fn roundtrip_relation_desc_without_metadata() {
2223        let typ = ProtoRelationType {
2224            column_types: vec![
2225                SqlScalarType::String.nullable(false).into_proto(),
2226                SqlScalarType::Bool.nullable(true).into_proto(),
2227            ],
2228            keys: vec![],
2229        };
2230        let proto = ProtoRelationDesc {
2231            typ: Some(typ),
2232            names: vec![
2233                ColumnName("a".into()).into_proto(),
2234                ColumnName("b".into()).into_proto(),
2235            ],
2236            metadata: vec![],
2237        };
2238        let desc: RelationDesc = proto.into_rust().unwrap();
2239
2240        insta::assert_json_snapshot!(desc, @r###"
2241        {
2242          "typ": {
2243            "column_types": [
2244              {
2245                "scalar_type": "String",
2246                "nullable": false
2247              },
2248              {
2249                "scalar_type": "Bool",
2250                "nullable": true
2251              }
2252            ],
2253            "keys": []
2254          },
2255          "metadata": {
2256            "0": {
2257              "name": "a",
2258              "typ_idx": 0,
2259              "added": 0,
2260              "dropped": null
2261            },
2262            "1": {
2263              "name": "b",
2264              "typ_idx": 1,
2265              "added": 0,
2266              "dropped": null
2267            }
2268          }
2269        }
2270        "###);
2271    }
2272
2273    #[mz_ore::test]
2274    #[should_panic(expected = "column named 'a' already exists!")]
2275    fn test_add_column_with_same_name_panics() {
2276        let desc = RelationDesc::builder()
2277            .with_column("a", SqlScalarType::Bool.nullable(true))
2278            .finish();
2279        let mut versioned = VersionedRelationDesc::new(desc);
2280
2281        let _ = versioned.add_column("a", SqlScalarType::String.nullable(false));
2282    }
2283
2284    #[mz_ore::test]
2285    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `pipe2` on OS `linux`
2286    fn test_add_column_with_same_name_prev_dropped() {
2287        let desc = RelationDesc::builder()
2288            .with_column("a", SqlScalarType::Bool.nullable(true))
2289            .finish();
2290        let mut versioned = VersionedRelationDesc::new(desc);
2291
2292        let v1 = versioned.drop_column("a");
2293        let v1 = versioned.at_version(RelationVersionSelector::Specific(v1));
2294        insta::assert_json_snapshot!(v1, @r###"
2295        {
2296          "typ": {
2297            "column_types": [],
2298            "keys": []
2299          },
2300          "metadata": {}
2301        }
2302        "###);
2303
2304        let v2 = versioned.add_column("a", SqlScalarType::String.nullable(false));
2305        let v2 = versioned.at_version(RelationVersionSelector::Specific(v2));
2306        insta::assert_json_snapshot!(v2, @r###"
2307        {
2308          "typ": {
2309            "column_types": [
2310              {
2311                "scalar_type": "String",
2312                "nullable": false
2313              }
2314            ],
2315            "keys": []
2316          },
2317          "metadata": {
2318            "1": {
2319              "name": "a",
2320              "typ_idx": 0,
2321              "added": 2,
2322              "dropped": null
2323            }
2324          }
2325        }
2326        "###);
2327    }
2328
2329    #[mz_ore::test]
2330    #[cfg_attr(miri, ignore)]
2331    fn apply_demand() {
2332        let desc = RelationDesc::builder()
2333            .with_column("a", SqlScalarType::String.nullable(true))
2334            .with_column("b", SqlScalarType::Int64.nullable(false))
2335            .with_column("c", SqlScalarType::Time.nullable(false))
2336            .finish();
2337        let desc = desc.apply_demand(&BTreeSet::from([0, 2]));
2338        assert_eq!(desc.arity(), 2);
2339        // TODO(parkmycar): Move validate onto RelationDesc.
2340        VersionedRelationDesc::new(desc).validate();
2341    }
2342
2343    #[mz_ore::test]
2344    #[cfg_attr(miri, ignore)]
2345    fn smoketest_column_index_stable_ident() {
2346        let idx_a = ColumnIndex(42);
2347        // Note(parkmycar): This should never change.
2348        assert_eq!(idx_a.to_stable_name(), "42");
2349    }
2350
2351    #[mz_ore::test]
2352    #[cfg_attr(miri, ignore)] // too slow
2353    fn proptest_relation_desc_roundtrips() {
2354        fn testcase(og: RelationDesc) {
2355            let bytes = og.into_proto().encode_to_vec();
2356            let proto = ProtoRelationDesc::decode(&bytes[..]).unwrap();
2357            let rnd = RelationDesc::from_proto(proto).unwrap();
2358
2359            assert_eq!(og, rnd);
2360        }
2361
2362        proptest!(|(desc in any::<RelationDesc>())| {
2363            testcase(desc);
2364        });
2365
2366        let strat = any::<RelationDesc>().prop_flat_map(|desc| {
2367            arb_relation_desc_diff(&desc).prop_map(move |diffs| (desc.clone(), diffs))
2368        });
2369
2370        proptest!(|((mut desc, diffs) in strat)| {
2371            for diff in diffs {
2372                diff.apply(&mut desc);
2373            };
2374            testcase(desc);
2375        });
2376    }
2377}