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