proof_of_sql/base/commitment/
column_commitment_metadata.rs

1use super::{column_bounds::BoundsInner, committable_column::CommittableColumn, ColumnBounds};
2use crate::base::database::ColumnType;
3use core::fmt::Debug;
4use serde::{Deserialize, Serialize};
5use snafu::Snafu;
6
7/// Errors that can occur when constructing invalid [`ColumnCommitmentMetadata`].
8#[derive(Debug, Snafu)]
9pub enum InvalidColumnCommitmentMetadata {
10    /// Column of this type cannot have these bounds.
11    #[snafu(display("column of type {column_type} cannot have bounds like {column_bounds:?}"))]
12    TypeBoundsMismatch {
13        column_type: ColumnType,
14        column_bounds: ColumnBounds,
15    },
16}
17
18/// During column operation, metadata indicates that the operand columns cannot be the same.
19#[derive(Debug, Snafu)]
20#[snafu(display(
21    "column with type {datatype_a} cannot operate with column with type {datatype_b}"
22))]
23pub struct ColumnCommitmentMetadataMismatch {
24    datatype_a: ColumnType,
25    datatype_b: ColumnType,
26}
27
28const EXPECT_BOUNDS_MATCH_MESSAGE: &str = "we've already checked the column types match, which is a stronger requirement (mapping of type variants to bounds variants is surjective)";
29
30/// Anonymous metadata associated with a column commitment.
31#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32pub struct ColumnCommitmentMetadata {
33    column_type: ColumnType,
34    bounds: ColumnBounds,
35}
36
37impl ColumnCommitmentMetadata {
38    /// Construct a new [`ColumnCommitmentMetadata`].
39    ///
40    /// Will error if the supplied metadata are invalid.
41    /// i.e., if The Bounds variant and column type do not match.
42    pub fn try_new(
43        column_type: ColumnType,
44        bounds: ColumnBounds,
45    ) -> Result<ColumnCommitmentMetadata, InvalidColumnCommitmentMetadata> {
46        match (column_type, bounds) {
47            (ColumnType::Uint8, ColumnBounds::Uint8(_))
48            | (ColumnType::TinyInt, ColumnBounds::TinyInt(_))
49            | (ColumnType::SmallInt, ColumnBounds::SmallInt(_))
50            | (ColumnType::Int, ColumnBounds::Int(_))
51            | (ColumnType::BigInt, ColumnBounds::BigInt(_))
52            | (ColumnType::Int128, ColumnBounds::Int128(_))
53            | (ColumnType::TimestampTZ(_, _), ColumnBounds::TimestampTZ(_))
54            | (
55                ColumnType::Boolean
56                | ColumnType::VarChar
57                | ColumnType::Scalar
58                | ColumnType::Decimal75(..),
59                ColumnBounds::NoOrder,
60            ) => Ok(ColumnCommitmentMetadata {
61                column_type,
62                bounds,
63            }),
64            _ => Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch {
65                column_type,
66                column_bounds: bounds,
67            }),
68        }
69    }
70
71    #[allow(clippy::missing_panics_doc)]
72    /// Construct a [`ColumnCommitmentMetadata`] with widest possible bounds for the column type.
73    #[must_use]
74    pub fn from_column_type_with_max_bounds(column_type: ColumnType) -> Self {
75        let bounds = match column_type {
76            ColumnType::SmallInt => ColumnBounds::SmallInt(super::Bounds::Bounded(
77                BoundsInner::try_new(i16::MIN, i16::MAX)
78                    .expect("i16::MIN and i16::MAX are valid bounds for SmallInt"),
79            )),
80            ColumnType::Int => ColumnBounds::Int(super::Bounds::Bounded(
81                BoundsInner::try_new(i32::MIN, i32::MAX)
82                    .expect("i32::MIN and i32::MAX are valid bounds for Int"),
83            )),
84            ColumnType::BigInt => ColumnBounds::BigInt(super::Bounds::Bounded(
85                BoundsInner::try_new(i64::MIN, i64::MAX)
86                    .expect("i64::MIN and i64::MAX are valid bounds for BigInt"),
87            )),
88            ColumnType::TimestampTZ(_, _) => ColumnBounds::TimestampTZ(super::Bounds::Bounded(
89                BoundsInner::try_new(i64::MIN, i64::MAX)
90                    .expect("i64::MIN and i64::MAX are valid bounds for TimeStamp"),
91            )),
92            ColumnType::Int128 => ColumnBounds::Int128(super::Bounds::Bounded(
93                BoundsInner::try_new(i128::MIN, i128::MAX)
94                    .expect("i128::MIN and i128::MAX are valid bounds for Int128"),
95            )),
96            _ => ColumnBounds::NoOrder,
97        };
98        Self::try_new(column_type, bounds).expect("default bounds for column type are valid")
99    }
100
101    #[cfg(test)]
102    pub(super) fn bounds_mut(&mut self) -> &mut ColumnBounds {
103        &mut self.bounds
104    }
105
106    /// Immutable reference to this column's type.
107    #[must_use]
108    pub fn column_type(&self) -> &ColumnType {
109        &self.column_type
110    }
111
112    /// Immutable reference to this column's bounds.
113    #[must_use]
114    pub fn bounds(&self) -> &ColumnBounds {
115        &self.bounds
116    }
117
118    /// Construct a [`ColumnCommitmentMetadata`] by analyzing a column.
119    #[must_use]
120    pub fn from_column(column: &CommittableColumn) -> ColumnCommitmentMetadata {
121        ColumnCommitmentMetadata {
122            column_type: column.column_type(),
123            bounds: ColumnBounds::from_column(column),
124        }
125    }
126
127    /// Combine two [`ColumnCommitmentMetadata`] as if their source collections are being unioned.
128    ///
129    /// Can error if the two metadatas are mismatched.
130    #[allow(clippy::missing_panics_doc)]
131    pub fn try_union(
132        self,
133        other: ColumnCommitmentMetadata,
134    ) -> Result<ColumnCommitmentMetadata, ColumnCommitmentMetadataMismatch> {
135        if self.column_type != other.column_type {
136            return Err(ColumnCommitmentMetadataMismatch {
137                datatype_a: self.column_type,
138                datatype_b: other.column_type,
139            });
140        }
141
142        let bounds = self
143            .bounds
144            .try_union(other.bounds)
145            .expect(EXPECT_BOUNDS_MATCH_MESSAGE);
146
147        Ok(ColumnCommitmentMetadata {
148            bounds,
149            column_type: self.column_type,
150        })
151    }
152
153    /// Combine two [`ColumnBounds`] as if their source collections are being differenced.
154    ///
155    /// This should be interpreted as the set difference of the two collections.
156    /// The result would be the rows in self that are not also rows in other.
157    #[allow(clippy::missing_panics_doc)]
158    pub fn try_difference(
159        self,
160        other: ColumnCommitmentMetadata,
161    ) -> Result<ColumnCommitmentMetadata, ColumnCommitmentMetadataMismatch> {
162        if self.column_type != other.column_type {
163            return Err(ColumnCommitmentMetadataMismatch {
164                datatype_a: self.column_type,
165                datatype_b: other.column_type,
166            });
167        }
168
169        let bounds = self
170            .bounds
171            .try_difference(other.bounds)
172            .expect(EXPECT_BOUNDS_MATCH_MESSAGE);
173
174        Ok(ColumnCommitmentMetadata {
175            bounds,
176            column_type: self.column_type,
177        })
178    }
179}
180
181#[cfg(test)]
182mod tests {
183
184    use super::*;
185    use crate::base::{
186        commitment::column_bounds::Bounds, database::OwnedColumn, math::decimal::Precision,
187        scalar::test_scalar::TestScalar,
188    };
189    use alloc::string::String;
190    use proof_of_sql_parser::posql_time::{PoSQLTimeUnit, PoSQLTimeZone};
191
192    #[test]
193    fn we_can_construct_metadata() {
194        assert_eq!(
195            ColumnCommitmentMetadata::try_new(
196                ColumnType::TinyInt,
197                ColumnBounds::TinyInt(Bounds::Empty)
198            )
199            .unwrap(),
200            ColumnCommitmentMetadata {
201                column_type: ColumnType::TinyInt,
202                bounds: ColumnBounds::TinyInt(Bounds::Empty)
203            }
204        );
205
206        assert_eq!(
207            ColumnCommitmentMetadata::try_new(
208                ColumnType::SmallInt,
209                ColumnBounds::SmallInt(Bounds::Empty)
210            )
211            .unwrap(),
212            ColumnCommitmentMetadata {
213                column_type: ColumnType::SmallInt,
214                bounds: ColumnBounds::SmallInt(Bounds::Empty)
215            }
216        );
217
218        assert_eq!(
219            ColumnCommitmentMetadata::try_new(ColumnType::Int, ColumnBounds::Int(Bounds::Empty))
220                .unwrap(),
221            ColumnCommitmentMetadata {
222                column_type: ColumnType::Int,
223                bounds: ColumnBounds::Int(Bounds::Empty)
224            }
225        );
226
227        assert_eq!(
228            ColumnCommitmentMetadata::try_new(
229                ColumnType::BigInt,
230                ColumnBounds::BigInt(Bounds::Empty)
231            )
232            .unwrap(),
233            ColumnCommitmentMetadata {
234                column_type: ColumnType::BigInt,
235                bounds: ColumnBounds::BigInt(Bounds::Empty)
236            }
237        );
238
239        assert_eq!(
240            ColumnCommitmentMetadata::try_new(ColumnType::Boolean, ColumnBounds::NoOrder,).unwrap(),
241            ColumnCommitmentMetadata {
242                column_type: ColumnType::Boolean,
243                bounds: ColumnBounds::NoOrder,
244            }
245        );
246
247        assert_eq!(
248            ColumnCommitmentMetadata::try_new(
249                ColumnType::Decimal75(Precision::new(10).unwrap(), 0),
250                ColumnBounds::NoOrder,
251            )
252            .unwrap(),
253            ColumnCommitmentMetadata {
254                column_type: ColumnType::Decimal75(Precision::new(10).unwrap(), 0),
255                bounds: ColumnBounds::NoOrder,
256            }
257        );
258
259        assert_eq!(
260            ColumnCommitmentMetadata::try_new(
261                ColumnType::TimestampTZ(PoSQLTimeUnit::Second, PoSQLTimeZone::utc()),
262                ColumnBounds::TimestampTZ(Bounds::Empty),
263            )
264            .unwrap(),
265            ColumnCommitmentMetadata {
266                column_type: ColumnType::TimestampTZ(PoSQLTimeUnit::Second, PoSQLTimeZone::utc()),
267                bounds: ColumnBounds::TimestampTZ(Bounds::Empty),
268            }
269        );
270
271        assert_eq!(
272            ColumnCommitmentMetadata::try_new(
273                ColumnType::Int128,
274                ColumnBounds::Int128(Bounds::sharp(-5, 10).unwrap())
275            )
276            .unwrap(),
277            ColumnCommitmentMetadata {
278                column_type: ColumnType::Int128,
279                bounds: ColumnBounds::Int128(Bounds::sharp(-5, 10).unwrap())
280            }
281        );
282
283        assert_eq!(
284            ColumnCommitmentMetadata::try_new(ColumnType::VarChar, ColumnBounds::NoOrder).unwrap(),
285            ColumnCommitmentMetadata {
286                column_type: ColumnType::VarChar,
287                bounds: ColumnBounds::NoOrder
288            }
289        );
290    }
291
292    #[test]
293    fn we_cannot_construct_metadata_with_type_bounds_mismatch() {
294        assert!(matches!(
295            ColumnCommitmentMetadata::try_new(
296                ColumnType::Boolean,
297                ColumnBounds::BigInt(Bounds::Empty)
298            ),
299            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
300        ));
301        assert!(matches!(
302            ColumnCommitmentMetadata::try_new(
303                ColumnType::Boolean,
304                ColumnBounds::Int128(Bounds::Empty)
305            ),
306            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
307        ));
308
309        assert!(matches!(
310            ColumnCommitmentMetadata::try_new(
311                ColumnType::Decimal75(Precision::new(10).unwrap(), 10),
312                ColumnBounds::Int128(Bounds::Empty)
313            ),
314            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
315        ));
316        assert!(matches!(
317            ColumnCommitmentMetadata::try_new(
318                ColumnType::Decimal75(Precision::new(10).unwrap(), 10),
319                ColumnBounds::BigInt(Bounds::Empty)
320            ),
321            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
322        ));
323
324        assert!(matches!(
325            ColumnCommitmentMetadata::try_new(
326                ColumnType::Scalar,
327                ColumnBounds::BigInt(Bounds::Empty)
328            ),
329            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
330        ));
331        assert!(matches!(
332            ColumnCommitmentMetadata::try_new(
333                ColumnType::Scalar,
334                ColumnBounds::Int128(Bounds::Empty)
335            ),
336            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
337        ));
338
339        assert!(matches!(
340            ColumnCommitmentMetadata::try_new(
341                ColumnType::BigInt,
342                ColumnBounds::Int128(Bounds::Empty)
343            ),
344            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
345        ));
346        assert!(matches!(
347            ColumnCommitmentMetadata::try_new(ColumnType::BigInt, ColumnBounds::NoOrder),
348            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
349        ));
350
351        assert!(matches!(
352            ColumnCommitmentMetadata::try_new(
353                ColumnType::Int128,
354                ColumnBounds::BigInt(Bounds::Empty)
355            ),
356            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
357        ));
358        assert!(matches!(
359            ColumnCommitmentMetadata::try_new(ColumnType::Int128, ColumnBounds::NoOrder),
360            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
361        ));
362
363        assert!(matches!(
364            ColumnCommitmentMetadata::try_new(
365                ColumnType::VarChar,
366                ColumnBounds::BigInt(Bounds::Empty)
367            ),
368            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
369        ));
370        assert!(matches!(
371            ColumnCommitmentMetadata::try_new(
372                ColumnType::VarChar,
373                ColumnBounds::Int128(Bounds::Empty)
374            ),
375            Err(InvalidColumnCommitmentMetadata::TypeBoundsMismatch { .. })
376        ));
377    }
378
379    #[test]
380    fn we_can_construct_metadata_from_column() {
381        let boolean_column =
382            OwnedColumn::<TestScalar>::Boolean([true, false, true, false, true].to_vec());
383        let committable_boolean_column = CommittableColumn::from(&boolean_column);
384        let boolean_metadata = ColumnCommitmentMetadata::from_column(&committable_boolean_column);
385        assert_eq!(boolean_metadata.column_type(), &ColumnType::Boolean);
386        assert_eq!(boolean_metadata.bounds(), &ColumnBounds::NoOrder);
387
388        let decimal_column = OwnedColumn::<TestScalar>::Decimal75(
389            Precision::new(10).unwrap(),
390            0,
391            [1, 2, 3, 4, 5].map(TestScalar::from).to_vec(),
392        );
393        let committable_decimal_column = CommittableColumn::from(&decimal_column);
394        let decimal_metadata = ColumnCommitmentMetadata::from_column(&committable_decimal_column);
395        assert_eq!(
396            decimal_metadata.column_type(),
397            &ColumnType::Decimal75(Precision::new(10).unwrap(), 0)
398        );
399        assert_eq!(decimal_metadata.bounds(), &ColumnBounds::NoOrder);
400
401        let timestamp_column: OwnedColumn<TestScalar> = OwnedColumn::<TestScalar>::TimestampTZ(
402            PoSQLTimeUnit::Second,
403            PoSQLTimeZone::utc(),
404            [1i64, 2, 3, 4, 5].to_vec(),
405        );
406        let committable_timestamp_column = CommittableColumn::from(&timestamp_column);
407        let timestamp_metadata =
408            ColumnCommitmentMetadata::from_column(&committable_timestamp_column);
409        assert_eq!(
410            timestamp_metadata.column_type(),
411            &ColumnType::TimestampTZ(PoSQLTimeUnit::Second, PoSQLTimeZone::utc())
412        );
413        if let ColumnBounds::TimestampTZ(Bounds::Sharp(bounds)) = timestamp_metadata.bounds() {
414            assert_eq!(bounds.min(), &1);
415            assert_eq!(bounds.max(), &5);
416        } else {
417            panic!("Bounds constructed from nonempty TimestampTZ column should be ColumnBounds::BigInt(Bounds::Sharp(_))");
418        }
419
420        let varchar_column = OwnedColumn::<TestScalar>::VarChar(
421            ["Lorem", "ipsum", "dolor", "sit", "amet"]
422                .map(String::from)
423                .to_vec(),
424        );
425        let committable_varchar_column = CommittableColumn::from(&varchar_column);
426        let varchar_metadata = ColumnCommitmentMetadata::from_column(&committable_varchar_column);
427        assert_eq!(varchar_metadata.column_type(), &ColumnType::VarChar);
428        assert_eq!(varchar_metadata.bounds(), &ColumnBounds::NoOrder);
429
430        let bigint_column = OwnedColumn::<TestScalar>::BigInt([1, 2, 3, 1, 0].to_vec());
431        let committable_bigint_column = CommittableColumn::from(&bigint_column);
432        let bigint_metadata = ColumnCommitmentMetadata::from_column(&committable_bigint_column);
433        assert_eq!(bigint_metadata.column_type(), &ColumnType::BigInt);
434        if let ColumnBounds::BigInt(Bounds::Sharp(bounds)) = bigint_metadata.bounds() {
435            assert_eq!(bounds.min(), &0);
436            assert_eq!(bounds.max(), &3);
437        } else {
438            panic!("Bounds constructed from nonempty BigInt column should be ColumnBounds::BigInt(Bounds::Sharp(_))");
439        }
440
441        let int_column = OwnedColumn::<TestScalar>::Int([1, 2, 3, 1, 0].to_vec());
442        let committable_int_column = CommittableColumn::from(&int_column);
443        let int_metadata = ColumnCommitmentMetadata::from_column(&committable_int_column);
444        assert_eq!(int_metadata.column_type(), &ColumnType::Int);
445        if let ColumnBounds::Int(Bounds::Sharp(bounds)) = int_metadata.bounds() {
446            assert_eq!(bounds.min(), &0);
447            assert_eq!(bounds.max(), &3);
448        } else {
449            panic!("Bounds constructed from nonempty BigInt column should be ColumnBounds::Int(Bounds::Sharp(_))");
450        }
451
452        let tinyint_column = OwnedColumn::<TestScalar>::TinyInt([1, 2, 3, 1, 0].to_vec());
453        let committable_tinyint_column = CommittableColumn::from(&tinyint_column);
454        let tinyint_metadata = ColumnCommitmentMetadata::from_column(&committable_tinyint_column);
455        assert_eq!(tinyint_metadata.column_type(), &ColumnType::TinyInt);
456        if let ColumnBounds::TinyInt(Bounds::Sharp(bounds)) = tinyint_metadata.bounds() {
457            assert_eq!(bounds.min(), &0);
458            assert_eq!(bounds.max(), &3);
459        } else {
460            panic!("Bounds constructed from nonempty BigInt column should be ColumnBounds::TinyInt(Bounds::Sharp(_))");
461        }
462
463        let smallint_column = OwnedColumn::<TestScalar>::SmallInt([1, 2, 3, 1, 0].to_vec());
464        let committable_smallint_column = CommittableColumn::from(&smallint_column);
465        let smallint_metadata = ColumnCommitmentMetadata::from_column(&committable_smallint_column);
466        assert_eq!(smallint_metadata.column_type(), &ColumnType::SmallInt);
467        if let ColumnBounds::SmallInt(Bounds::Sharp(bounds)) = smallint_metadata.bounds() {
468            assert_eq!(bounds.min(), &0);
469            assert_eq!(bounds.max(), &3);
470        } else {
471            panic!("Bounds constructed from nonempty BigInt column should be ColumnBounds::SmallInt(Bounds::Sharp(_))");
472        }
473
474        let int128_column = OwnedColumn::<TestScalar>::Int128([].to_vec());
475        let committable_int128_column = CommittableColumn::from(&int128_column);
476        let int128_metadata = ColumnCommitmentMetadata::from_column(&committable_int128_column);
477        assert_eq!(int128_metadata.column_type(), &ColumnType::Int128);
478        assert_eq!(
479            int128_metadata.bounds(),
480            &ColumnBounds::Int128(Bounds::Empty)
481        );
482
483        let scalar_column = OwnedColumn::Scalar([1, 2, 3, 4, 5].map(TestScalar::from).to_vec());
484        let committable_scalar_column = CommittableColumn::from(&scalar_column);
485        let scalar_metadata = ColumnCommitmentMetadata::from_column(&committable_scalar_column);
486        assert_eq!(scalar_metadata.column_type(), &ColumnType::Scalar);
487        assert_eq!(scalar_metadata.bounds(), &ColumnBounds::NoOrder);
488    }
489
490    #[test]
491    fn we_can_union_matching_metadata() {
492        // NoOrder cases
493        let boolean_metadata = ColumnCommitmentMetadata {
494            column_type: ColumnType::Boolean,
495            bounds: ColumnBounds::NoOrder,
496        };
497        assert_eq!(
498            boolean_metadata.try_union(boolean_metadata).unwrap(),
499            boolean_metadata
500        );
501
502        let decimal_metadata = ColumnCommitmentMetadata {
503            column_type: ColumnType::Decimal75(Precision::new(12).unwrap(), 0),
504            bounds: ColumnBounds::NoOrder,
505        };
506        assert_eq!(
507            decimal_metadata.try_union(decimal_metadata).unwrap(),
508            decimal_metadata
509        );
510
511        let varchar_metadata = ColumnCommitmentMetadata {
512            column_type: ColumnType::VarChar,
513            bounds: ColumnBounds::NoOrder,
514        };
515        assert_eq!(
516            varchar_metadata.try_union(varchar_metadata).unwrap(),
517            varchar_metadata
518        );
519
520        let scalar_metadata = ColumnCommitmentMetadata {
521            column_type: ColumnType::Scalar,
522            bounds: ColumnBounds::NoOrder,
523        };
524        assert_eq!(
525            scalar_metadata.try_union(scalar_metadata).unwrap(),
526            scalar_metadata
527        );
528
529        // Ordered case
530        let ints = [1, 2, 3, 1, 0];
531        let tinyint_column_a = CommittableColumn::TinyInt(&ints[..2]);
532        let tinyint_metadata_a = ColumnCommitmentMetadata::from_column(&tinyint_column_a);
533        let tinyint_column_b = CommittableColumn::TinyInt(&ints[2..]);
534        let tinyint_metadata_b = ColumnCommitmentMetadata::from_column(&tinyint_column_b);
535        let tinyint_column_c = CommittableColumn::TinyInt(&ints);
536        let tinyint_metadata_c = ColumnCommitmentMetadata::from_column(&tinyint_column_c);
537        assert_eq!(
538            tinyint_metadata_a.try_union(tinyint_metadata_b).unwrap(),
539            tinyint_metadata_c
540        );
541
542        let ints = [1, 2, 3, 1, 0];
543        let smallint_column_a = CommittableColumn::SmallInt(&ints[..2]);
544        let smallint_metadata_a = ColumnCommitmentMetadata::from_column(&smallint_column_a);
545        let smallint_column_b = CommittableColumn::SmallInt(&ints[2..]);
546        let smallint_metadata_b = ColumnCommitmentMetadata::from_column(&smallint_column_b);
547        let smallint_column_c = CommittableColumn::SmallInt(&ints);
548        let smallint_metadata_c = ColumnCommitmentMetadata::from_column(&smallint_column_c);
549        assert_eq!(
550            smallint_metadata_a.try_union(smallint_metadata_b).unwrap(),
551            smallint_metadata_c
552        );
553
554        let ints = [1, 2, 3, 1, 0];
555        let int_column_a = CommittableColumn::Int(&ints[..2]);
556        let int_metadata_a = ColumnCommitmentMetadata::from_column(&int_column_a);
557        let int_column_b = CommittableColumn::Int(&ints[2..]);
558        let int_metadata_b = ColumnCommitmentMetadata::from_column(&int_column_b);
559        let int_column_c = CommittableColumn::Int(&ints);
560        let int_metadata_c = ColumnCommitmentMetadata::from_column(&int_column_c);
561        assert_eq!(
562            int_metadata_a.try_union(int_metadata_b).unwrap(),
563            int_metadata_c
564        );
565
566        let ints = [1, 2, 3, 1, 0];
567        let bigint_column_a = CommittableColumn::BigInt(&ints[..2]);
568        let bigint_metadata_a = ColumnCommitmentMetadata::from_column(&bigint_column_a);
569        let bigint_column_b = CommittableColumn::BigInt(&ints[2..]);
570        let bigint_metadata_b = ColumnCommitmentMetadata::from_column(&bigint_column_b);
571        let bigint_column_c = CommittableColumn::BigInt(&ints);
572        let bigint_metadata_c = ColumnCommitmentMetadata::from_column(&bigint_column_c);
573        assert_eq!(
574            bigint_metadata_a.try_union(bigint_metadata_b).unwrap(),
575            bigint_metadata_c
576        );
577
578        // Ordered case for TimestampTZ
579        // Example Unix epoch times
580        let times = [
581            1_625_072_400,
582            1_625_076_000,
583            1_625_079_600,
584            1_625_072_400,
585            1_625_065_000,
586        ];
587        let timezone = PoSQLTimeZone::utc();
588        let timeunit = PoSQLTimeUnit::Second;
589        let timestamp_column_a = CommittableColumn::TimestampTZ(timeunit, timezone, &times[..2]);
590        let timestamp_metadata_a = ColumnCommitmentMetadata::from_column(&timestamp_column_a);
591        let timestamp_column_b = CommittableColumn::TimestampTZ(timeunit, timezone, &times[2..]);
592        let timestamp_metadata_b = ColumnCommitmentMetadata::from_column(&timestamp_column_b);
593        let timestamp_column_c = CommittableColumn::TimestampTZ(timeunit, timezone, &times);
594        let timestamp_metadata_c = ColumnCommitmentMetadata::from_column(&timestamp_column_c);
595        assert_eq!(
596            timestamp_metadata_a
597                .try_union(timestamp_metadata_b)
598                .unwrap(),
599            timestamp_metadata_c
600        );
601    }
602
603    #[test]
604    fn we_can_difference_timestamp_tz_matching_metadata() {
605        // Ordered case
606        let times = [
607            1_625_072_400,
608            1_625_076_000,
609            1_625_079_600,
610            1_625_072_400,
611            1_625_065_000,
612        ];
613        let timezone = PoSQLTimeZone::utc();
614        let timeunit = PoSQLTimeUnit::Second;
615
616        let timestamp_column_a = CommittableColumn::TimestampTZ(timeunit, timezone, &times[..2]);
617        let timestamp_metadata_a = ColumnCommitmentMetadata::from_column(&timestamp_column_a);
618        let timestamp_column_b = CommittableColumn::TimestampTZ(timeunit, timezone, &times);
619        let timestamp_metadata_b = ColumnCommitmentMetadata::from_column(&timestamp_column_b);
620
621        let b_difference_a = timestamp_metadata_b
622            .try_difference(timestamp_metadata_a)
623            .unwrap();
624        assert_eq!(
625            b_difference_a.column_type,
626            ColumnType::TimestampTZ(timeunit, timezone)
627        );
628        if let ColumnBounds::TimestampTZ(Bounds::Bounded(bounds)) = b_difference_a.bounds {
629            assert_eq!(bounds.min(), &1_625_065_000);
630            assert_eq!(bounds.max(), &1_625_079_600);
631        } else {
632            panic!("difference of overlapping bounds should be Bounded");
633        }
634
635        let timestamp_column_empty = CommittableColumn::TimestampTZ(timeunit, timezone, &[]);
636        let timestamp_metadata_empty =
637            ColumnCommitmentMetadata::from_column(&timestamp_column_empty);
638
639        assert_eq!(
640            timestamp_metadata_b
641                .try_difference(timestamp_metadata_empty)
642                .unwrap(),
643            timestamp_metadata_b
644        );
645        assert_eq!(
646            timestamp_metadata_empty
647                .try_difference(timestamp_metadata_b)
648                .unwrap(),
649            timestamp_metadata_empty
650        );
651    }
652
653    #[test]
654    fn we_can_difference_bigint_matching_metadata() {
655        // Ordered case
656        let ints = [1, 2, 3, 1, 0];
657        let bigint_column_a = CommittableColumn::BigInt(&ints[..2]);
658        let bigint_metadata_a = ColumnCommitmentMetadata::from_column(&bigint_column_a);
659        let bigint_column_b = CommittableColumn::BigInt(&ints);
660        let bigint_metadata_b = ColumnCommitmentMetadata::from_column(&bigint_column_b);
661
662        let b_difference_a = bigint_metadata_b.try_difference(bigint_metadata_a).unwrap();
663        assert_eq!(b_difference_a.column_type, ColumnType::BigInt);
664        if let ColumnBounds::BigInt(Bounds::Bounded(bounds)) = b_difference_a.bounds() {
665            assert_eq!(bounds.min(), &0);
666            assert_eq!(bounds.max(), &3);
667        } else {
668            panic!("difference of overlapping bounds should be Bounded");
669        }
670
671        let bigint_column_empty = CommittableColumn::BigInt(&[]);
672        let bigint_metadata_empty = ColumnCommitmentMetadata::from_column(&bigint_column_empty);
673
674        assert_eq!(
675            bigint_metadata_b
676                .try_difference(bigint_metadata_empty)
677                .unwrap(),
678            bigint_metadata_b
679        );
680        assert_eq!(
681            bigint_metadata_empty
682                .try_difference(bigint_metadata_b)
683                .unwrap(),
684            bigint_metadata_empty
685        );
686    }
687
688    #[test]
689    fn we_can_difference_tinyint_matching_metadata() {
690        // Ordered case
691        let tinyints = [1, 2, 3, 1, 0];
692        let tinyint_column_a = CommittableColumn::TinyInt(&tinyints[..2]);
693        let tinyint_metadata_a = ColumnCommitmentMetadata::from_column(&tinyint_column_a);
694        let tinyint_column_b = CommittableColumn::TinyInt(&tinyints);
695        let tinyint_metadata_b = ColumnCommitmentMetadata::from_column(&tinyint_column_b);
696
697        let b_difference_a = tinyint_metadata_b
698            .try_difference(tinyint_metadata_a)
699            .unwrap();
700        assert_eq!(b_difference_a.column_type, ColumnType::TinyInt);
701        if let ColumnBounds::TinyInt(Bounds::Bounded(bounds)) = b_difference_a.bounds() {
702            assert_eq!(bounds.min(), &0);
703            assert_eq!(bounds.max(), &3);
704        } else {
705            panic!("difference of overlapping bounds should be Bounded");
706        }
707
708        let tinyint_column_empty = CommittableColumn::TinyInt(&[]);
709        let tinyint_metadata_empty = ColumnCommitmentMetadata::from_column(&tinyint_column_empty);
710
711        assert_eq!(
712            tinyint_metadata_b
713                .try_difference(tinyint_metadata_empty)
714                .unwrap(),
715            tinyint_metadata_b
716        );
717        assert_eq!(
718            tinyint_metadata_empty
719                .try_difference(tinyint_metadata_b)
720                .unwrap(),
721            tinyint_metadata_empty
722        );
723    }
724
725    #[test]
726    fn we_can_difference_smallint_matching_metadata() {
727        // Ordered case
728        let smallints = [1, 2, 3, 1, 0];
729        let smallint_column_a = CommittableColumn::SmallInt(&smallints[..2]);
730        let smallint_metadata_a = ColumnCommitmentMetadata::from_column(&smallint_column_a);
731        let smallint_column_b = CommittableColumn::SmallInt(&smallints);
732        let smallint_metadata_b = ColumnCommitmentMetadata::from_column(&smallint_column_b);
733
734        let b_difference_a = smallint_metadata_b
735            .try_difference(smallint_metadata_a)
736            .unwrap();
737        assert_eq!(b_difference_a.column_type, ColumnType::SmallInt);
738        if let ColumnBounds::SmallInt(Bounds::Bounded(bounds)) = b_difference_a.bounds() {
739            assert_eq!(bounds.min(), &0);
740            assert_eq!(bounds.max(), &3);
741        } else {
742            panic!("difference of overlapping bounds should be Bounded");
743        }
744
745        let smallint_column_empty = CommittableColumn::SmallInt(&[]);
746        let smallint_metadata_empty = ColumnCommitmentMetadata::from_column(&smallint_column_empty);
747
748        assert_eq!(
749            smallint_metadata_b
750                .try_difference(smallint_metadata_empty)
751                .unwrap(),
752            smallint_metadata_b
753        );
754        assert_eq!(
755            smallint_metadata_empty
756                .try_difference(smallint_metadata_b)
757                .unwrap(),
758            smallint_metadata_empty
759        );
760    }
761
762    #[test]
763    fn we_can_difference_int_matching_metadata() {
764        // Ordered case
765        let ints = [1, 2, 3, 1, 0];
766        let int_column_a = CommittableColumn::Int(&ints[..2]);
767        let int_metadata_a = ColumnCommitmentMetadata::from_column(&int_column_a);
768        let int_column_b = CommittableColumn::Int(&ints);
769        let int_metadata_b = ColumnCommitmentMetadata::from_column(&int_column_b);
770
771        let b_difference_a = int_metadata_b.try_difference(int_metadata_a).unwrap();
772        assert_eq!(b_difference_a.column_type, ColumnType::Int);
773        if let ColumnBounds::Int(Bounds::Bounded(bounds)) = b_difference_a.bounds() {
774            assert_eq!(bounds.min(), &0);
775            assert_eq!(bounds.max(), &3);
776        } else {
777            panic!("difference of overlapping bounds should be Bounded");
778        }
779
780        let int_column_empty = CommittableColumn::Int(&[]);
781        let int_metadata_empty = ColumnCommitmentMetadata::from_column(&int_column_empty);
782
783        assert_eq!(
784            int_metadata_b.try_difference(int_metadata_empty).unwrap(),
785            int_metadata_b
786        );
787        assert_eq!(
788            int_metadata_empty.try_difference(int_metadata_b).unwrap(),
789            int_metadata_empty
790        );
791    }
792
793    #[allow(clippy::too_many_lines)]
794    #[test]
795    fn we_cannot_perform_arithmetic_on_mismatched_metadata() {
796        let boolean_metadata = ColumnCommitmentMetadata {
797            column_type: ColumnType::Boolean,
798            bounds: ColumnBounds::NoOrder,
799        };
800        let varchar_metadata = ColumnCommitmentMetadata {
801            column_type: ColumnType::VarChar,
802            bounds: ColumnBounds::NoOrder,
803        };
804        let scalar_metadata = ColumnCommitmentMetadata {
805            column_type: ColumnType::Scalar,
806            bounds: ColumnBounds::NoOrder,
807        };
808        let tinyint_metadata = ColumnCommitmentMetadata {
809            column_type: ColumnType::TinyInt,
810            bounds: ColumnBounds::TinyInt(Bounds::Empty),
811        };
812        let smallint_metadata = ColumnCommitmentMetadata {
813            column_type: ColumnType::SmallInt,
814            bounds: ColumnBounds::SmallInt(Bounds::Empty),
815        };
816        let int_metadata = ColumnCommitmentMetadata {
817            column_type: ColumnType::Int,
818            bounds: ColumnBounds::Int(Bounds::Empty),
819        };
820        let bigint_metadata = ColumnCommitmentMetadata {
821            column_type: ColumnType::BigInt,
822            bounds: ColumnBounds::BigInt(Bounds::Empty),
823        };
824        let int128_metadata = ColumnCommitmentMetadata {
825            column_type: ColumnType::Int128,
826            bounds: ColumnBounds::Int128(Bounds::Empty),
827        };
828        let decimal75_metadata = ColumnCommitmentMetadata {
829            column_type: ColumnType::Decimal75(Precision::new(4).unwrap(), 8),
830            bounds: ColumnBounds::Int128(Bounds::Empty),
831        };
832
833        assert!(tinyint_metadata.try_union(scalar_metadata).is_err());
834        assert!(scalar_metadata.try_union(tinyint_metadata).is_err());
835
836        assert!(tinyint_metadata.try_union(decimal75_metadata).is_err());
837        assert!(decimal75_metadata.try_union(tinyint_metadata).is_err());
838
839        assert!(tinyint_metadata.try_union(varchar_metadata).is_err());
840        assert!(varchar_metadata.try_union(tinyint_metadata).is_err());
841
842        assert!(tinyint_metadata.try_union(boolean_metadata).is_err());
843        assert!(boolean_metadata.try_union(tinyint_metadata).is_err());
844
845        assert!(smallint_metadata.try_union(scalar_metadata).is_err());
846        assert!(scalar_metadata.try_union(smallint_metadata).is_err());
847
848        assert!(smallint_metadata.try_union(decimal75_metadata).is_err());
849        assert!(decimal75_metadata.try_union(smallint_metadata).is_err());
850
851        assert!(smallint_metadata.try_union(varchar_metadata).is_err());
852        assert!(varchar_metadata.try_union(smallint_metadata).is_err());
853
854        assert!(smallint_metadata.try_union(boolean_metadata).is_err());
855        assert!(boolean_metadata.try_union(smallint_metadata).is_err());
856
857        assert!(int_metadata.try_union(scalar_metadata).is_err());
858        assert!(scalar_metadata.try_union(int_metadata).is_err());
859
860        assert!(int_metadata.try_union(decimal75_metadata).is_err());
861        assert!(decimal75_metadata.try_union(int_metadata).is_err());
862
863        assert!(int_metadata.try_union(varchar_metadata).is_err());
864        assert!(varchar_metadata.try_union(int_metadata).is_err());
865
866        assert!(int_metadata.try_union(boolean_metadata).is_err());
867        assert!(boolean_metadata.try_union(int_metadata).is_err());
868
869        assert!(varchar_metadata.try_union(scalar_metadata).is_err());
870        assert!(scalar_metadata.try_union(varchar_metadata).is_err());
871
872        assert!(varchar_metadata.try_union(bigint_metadata).is_err());
873        assert!(bigint_metadata.try_union(varchar_metadata).is_err());
874
875        assert!(varchar_metadata.try_union(int128_metadata).is_err());
876        assert!(int128_metadata.try_union(varchar_metadata).is_err());
877
878        assert!(decimal75_metadata.try_union(scalar_metadata).is_err());
879        assert!(scalar_metadata.try_union(decimal75_metadata).is_err());
880
881        assert!(decimal75_metadata.try_union(bigint_metadata).is_err());
882        assert!(bigint_metadata.try_union(decimal75_metadata).is_err());
883
884        assert!(decimal75_metadata.try_union(varchar_metadata).is_err());
885        assert!(varchar_metadata.try_union(decimal75_metadata).is_err());
886
887        assert!(decimal75_metadata.try_union(int128_metadata).is_err());
888        assert!(int128_metadata.try_union(decimal75_metadata).is_err());
889
890        assert!(scalar_metadata.try_union(bigint_metadata).is_err());
891        assert!(bigint_metadata.try_union(scalar_metadata).is_err());
892
893        assert!(scalar_metadata.try_union(int128_metadata).is_err());
894        assert!(int128_metadata.try_union(scalar_metadata).is_err());
895
896        assert!(bigint_metadata.try_union(int128_metadata).is_err());
897        assert!(int128_metadata.try_union(bigint_metadata).is_err());
898
899        assert!(varchar_metadata.try_difference(scalar_metadata).is_err());
900        assert!(scalar_metadata.try_difference(varchar_metadata).is_err());
901
902        assert!(varchar_metadata.try_difference(bigint_metadata).is_err());
903        assert!(bigint_metadata.try_difference(varchar_metadata).is_err());
904
905        assert!(varchar_metadata.try_difference(int128_metadata).is_err());
906        assert!(int128_metadata.try_difference(varchar_metadata).is_err());
907
908        assert!(scalar_metadata.try_difference(bigint_metadata).is_err());
909        assert!(bigint_metadata.try_difference(scalar_metadata).is_err());
910
911        assert!(scalar_metadata.try_difference(int128_metadata).is_err());
912        assert!(int128_metadata.try_difference(scalar_metadata).is_err());
913
914        assert!(bigint_metadata.try_difference(int128_metadata).is_err());
915        assert!(int128_metadata.try_difference(bigint_metadata).is_err());
916
917        assert!(decimal75_metadata.try_difference(scalar_metadata).is_err());
918        assert!(scalar_metadata.try_difference(decimal75_metadata).is_err());
919
920        assert!(decimal75_metadata.try_difference(bigint_metadata).is_err());
921        assert!(bigint_metadata.try_difference(decimal75_metadata).is_err());
922
923        assert!(decimal75_metadata.try_difference(int128_metadata).is_err());
924        assert!(int128_metadata.try_difference(decimal75_metadata).is_err());
925
926        assert!(decimal75_metadata.try_difference(varchar_metadata).is_err());
927        assert!(varchar_metadata.try_difference(decimal75_metadata).is_err());
928
929        assert!(decimal75_metadata.try_difference(boolean_metadata).is_err());
930        assert!(boolean_metadata.try_difference(decimal75_metadata).is_err());
931
932        assert!(boolean_metadata.try_difference(bigint_metadata).is_err());
933        assert!(bigint_metadata.try_difference(boolean_metadata).is_err());
934
935        assert!(boolean_metadata.try_difference(int128_metadata).is_err());
936        assert!(int128_metadata.try_difference(boolean_metadata).is_err());
937
938        assert!(boolean_metadata.try_difference(varchar_metadata).is_err());
939        assert!(varchar_metadata.try_difference(boolean_metadata).is_err());
940
941        assert!(boolean_metadata.try_difference(scalar_metadata).is_err());
942        assert!(scalar_metadata.try_difference(boolean_metadata).is_err());
943
944        let different_decimal75_metadata = ColumnCommitmentMetadata {
945            column_type: ColumnType::Decimal75(Precision::new(75).unwrap(), 0),
946            bounds: ColumnBounds::Int128(Bounds::Empty),
947        };
948
949        assert!(decimal75_metadata
950            .try_difference(different_decimal75_metadata)
951            .is_err());
952        assert!(different_decimal75_metadata
953            .try_difference(decimal75_metadata)
954            .is_err());
955
956        assert!(decimal75_metadata
957            .try_union(different_decimal75_metadata)
958            .is_err());
959        assert!(different_decimal75_metadata
960            .try_union(decimal75_metadata)
961            .is_err());
962
963        let timestamp_tz_metadata_a = ColumnCommitmentMetadata {
964            column_type: ColumnType::TimestampTZ(PoSQLTimeUnit::Second, PoSQLTimeZone::utc()),
965            bounds: ColumnBounds::TimestampTZ(Bounds::Empty),
966        };
967
968        let timestamp_tz_metadata_b = ColumnCommitmentMetadata {
969            column_type: ColumnType::TimestampTZ(PoSQLTimeUnit::Millisecond, PoSQLTimeZone::utc()),
970            bounds: ColumnBounds::TimestampTZ(Bounds::Empty),
971        };
972
973        // Tests for union operations
974        assert!(timestamp_tz_metadata_a.try_union(varchar_metadata).is_err());
975        assert!(varchar_metadata.try_union(timestamp_tz_metadata_a).is_err());
976
977        // Tests for difference operations
978        assert!(timestamp_tz_metadata_a
979            .try_difference(scalar_metadata)
980            .is_err());
981        assert!(scalar_metadata
982            .try_difference(timestamp_tz_metadata_a)
983            .is_err());
984
985        // Tests for different time units within the same type
986        assert!(timestamp_tz_metadata_a
987            .try_union(timestamp_tz_metadata_b)
988            .is_err());
989        assert!(timestamp_tz_metadata_b
990            .try_union(timestamp_tz_metadata_a)
991            .is_err());
992
993        // Difference with different time units
994        assert!(timestamp_tz_metadata_a
995            .try_difference(timestamp_tz_metadata_b)
996            .is_err());
997        assert!(timestamp_tz_metadata_b
998            .try_difference(timestamp_tz_metadata_a)
999            .is_err());
1000    }
1001}