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