proof_of_sql/base/commitment/
column_commitments.rs

1use super::{
2    committable_column::CommittableColumn, ColumnCommitmentMetadata, ColumnCommitmentMetadataMap,
3    ColumnCommitmentMetadataMapExt, ColumnCommitmentsMismatch, Commitment, VecCommitmentExt,
4};
5use crate::base::{
6    database::{ColumnField, CommitmentAccessor, TableRef},
7    map::IndexSet,
8};
9use alloc::{
10    string::{String, ToString},
11    vec,
12    vec::Vec,
13};
14use core::{iter, slice};
15use serde::{Deserialize, Serialize};
16use snafu::Snafu;
17use sqlparser::ast::Ident;
18
19/// Cannot create commitments with duplicate ident.
20#[derive(Debug, Snafu)]
21#[snafu(display("cannot create commitments with duplicate ident: {id}"))]
22pub struct DuplicateIdents {
23    id: String,
24}
25
26/// Errors that can occur when attempting to append rows to [`ColumnCommitments`].
27#[derive(Debug, Snafu)]
28pub enum AppendColumnCommitmentsError {
29    /// Metadata between new and old columns are mismatched.
30    #[snafu(transparent)]
31    Mismatch {
32        /// The underlying source error
33        source: ColumnCommitmentsMismatch,
34    },
35    /// New columns have duplicate idents.
36    #[snafu(transparent)]
37    DuplicateIdents {
38        /// The underlying source error
39        source: DuplicateIdents,
40    },
41}
42
43/// Commitments for a collection of columns with some metadata.
44///
45/// These columns do not need to belong to the same table, and can have differing lengths.
46#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub struct ColumnCommitments<C> {
48    commitments: Vec<C>,
49    column_metadata: ColumnCommitmentMetadataMap,
50}
51
52impl<C: Commitment> ColumnCommitments<C> {
53    /// Create a new [`ColumnCommitments`] for a table from a commitment accessor.
54    pub fn from_accessor_with_max_bounds(
55        table: &TableRef,
56        columns: &[ColumnField],
57        accessor: &impl CommitmentAccessor<C>,
58    ) -> Self {
59        let column_metadata =
60            ColumnCommitmentMetadataMap::from_column_fields_with_max_bounds(columns);
61        let commitments = columns
62            .iter()
63            .map(|c| accessor.get_commitment(table, &c.name()))
64            .collect();
65        ColumnCommitments {
66            commitments,
67            column_metadata,
68        }
69    }
70
71    #[cfg(test)]
72    pub(super) fn column_metadata_mut(&mut self) -> &mut ColumnCommitmentMetadataMap {
73        &mut self.column_metadata
74    }
75
76    /// Returns a reference to the stored commitments.
77    #[must_use]
78    pub fn commitments(&self) -> &Vec<C> {
79        &self.commitments
80    }
81
82    /// Returns a reference to the stored column metadata.
83    #[must_use]
84    pub fn column_metadata(&self) -> &ColumnCommitmentMetadataMap {
85        &self.column_metadata
86    }
87
88    /// Returns the number of columns.
89    #[must_use]
90    pub fn len(&self) -> usize {
91        self.column_metadata.len()
92    }
93
94    /// Returns true if there are no columns.
95    #[must_use]
96    pub fn is_empty(&self) -> bool {
97        self.column_metadata.is_empty()
98    }
99
100    /// Returns the commitment with the given ident.
101    #[must_use]
102    pub fn get_commitment(&self, identifier: &Ident) -> Option<C> {
103        self.column_metadata
104            .get_index_of(identifier)
105            .map(|index| self.commitments[index].clone())
106    }
107
108    /// Returns the metadata for the commitment with the given ident.
109    #[must_use]
110    pub fn get_metadata(&self, identifier: &Ident) -> Option<&ColumnCommitmentMetadata> {
111        self.column_metadata.get(identifier)
112    }
113
114    /// Iterate over the metadata and commitments by reference.
115    pub fn iter(&self) -> Iter<C> {
116        self.into_iter()
117    }
118
119    /// Returns [`ColumnCommitments`] to the provided columns using the given generator offset
120    pub fn try_from_columns_with_offset<'a, COL>(
121        columns: impl IntoIterator<Item = (&'a Ident, COL)>,
122        offset: usize,
123        setup: &C::PublicSetup<'_>,
124    ) -> Result<ColumnCommitments<C>, DuplicateIdents>
125    where
126        COL: Into<CommittableColumn<'a>>,
127    {
128        // Check for duplicate idents
129        let mut unique_identifiers = IndexSet::default();
130        let unique_columns = columns
131            .into_iter()
132            .map(|(identifier, column)| {
133                if unique_identifiers.insert(identifier) {
134                    Ok((identifier, column))
135                } else {
136                    Err(DuplicateIdents {
137                        id: identifier.to_string(),
138                    })
139                }
140            })
141            .collect::<Result<Vec<_>, _>>()?;
142
143        let (identifiers, committable_columns): (Vec<&Ident>, Vec<CommittableColumn>) =
144            unique_columns
145                .into_iter()
146                .map(|(identifier, column)| {
147                    let committable_column: CommittableColumn = column.into();
148                    (identifier, committable_column)
149                })
150                .unzip();
151
152        let column_metadata = ColumnCommitmentMetadataMap::from_columns(
153            identifiers.into_iter().zip(committable_columns.iter()),
154        );
155
156        let commitments = Vec::<C>::from_columns_with_offset(committable_columns, offset, setup);
157
158        Ok(ColumnCommitments {
159            commitments,
160            column_metadata,
161        })
162    }
163
164    /// Append rows of data from the provided columns to the existing commitments.
165    ///
166    /// The given generator offset will be used for committing to the new rows.
167    /// You most likely want this to be equal to the 0-indexed row number of the first new row.
168    ///
169    /// Will error on a variety of mismatches.
170    /// See [`ColumnCommitmentsMismatch`] for an enumeration of these errors.
171    #[expect(clippy::missing_panics_doc)]
172    pub fn try_append_rows_with_offset<'a, COL>(
173        &mut self,
174        columns: impl IntoIterator<Item = (&'a Ident, COL)>,
175        offset: usize,
176        setup: &C::PublicSetup<'_>,
177    ) -> Result<(), AppendColumnCommitmentsError>
178    where
179        COL: Into<CommittableColumn<'a>>,
180    {
181        // Check for duplicate idents.
182        let mut unique_identifiers = IndexSet::default();
183        let unique_columns = columns
184            .into_iter()
185            .map(|(identifier, column)| {
186                if unique_identifiers.insert(identifier) {
187                    Ok((identifier, column))
188                } else {
189                    Err(DuplicateIdents {
190                        id: identifier.to_string(),
191                    })
192                }
193            })
194            .collect::<Result<Vec<_>, _>>()?;
195
196        let (identifiers, committable_columns): (Vec<&Ident>, Vec<CommittableColumn>) =
197            unique_columns
198                .into_iter()
199                .map(|(identifier, column)| {
200                    let committable_column: CommittableColumn = column.into();
201                    (identifier, committable_column)
202                })
203                .unzip();
204
205        let column_metadata = ColumnCommitmentMetadataMap::from_columns(
206            identifiers.into_iter().zip(committable_columns.iter()),
207        );
208
209        self.column_metadata = self.column_metadata.clone().try_union(column_metadata)?;
210
211        self.commitments
212            .try_append_rows_with_offset(committable_columns, offset, setup)
213            .expect("we've already checked that self and other have equal column counts");
214
215        Ok(())
216    }
217
218    /// Add new columns to this [`ColumnCommitments`] using the given generator offset.
219    pub fn try_extend_columns_with_offset<'a, COL>(
220        &mut self,
221        columns: impl IntoIterator<Item = (&'a Ident, COL)>,
222        offset: usize,
223        setup: &C::PublicSetup<'_>,
224    ) -> Result<(), DuplicateIdents>
225    where
226        COL: Into<CommittableColumn<'a>>,
227    {
228        // Check for duplicates *between* the existing and new columns.
229        //
230        // The existing columns should not have any duplicates within themselves due to
231        // ColumnCommitments construction preventing it.
232        //
233        // If the new columns contain duplicates between each other, we'll catch this in the next
234        // step.
235        let unique_columns = columns
236            .into_iter()
237            .map(|(identifier, column)| {
238                if self.column_metadata.contains_key(identifier) {
239                    Err(DuplicateIdents {
240                        id: identifier.to_string(),
241                    })
242                } else {
243                    Ok((identifier, column))
244                }
245            })
246            .collect::<Result<Vec<_>, _>>()?;
247
248        // this constructor will check for duplicates among the new columns
249        let new_column_commitments =
250            ColumnCommitments::<C>::try_from_columns_with_offset(unique_columns, offset, setup)?;
251
252        self.commitments.extend(new_column_commitments.commitments);
253        self.column_metadata
254            .extend(new_column_commitments.column_metadata);
255
256        Ok(())
257    }
258
259    /// Add two [`ColumnCommitments`] together.
260    ///
261    /// Will error on a variety of mismatches.
262    /// See [`ColumnCommitmentsMismatch`] for an enumeration of these errors.
263    #[expect(clippy::missing_panics_doc)]
264    pub fn try_add(self, other: Self) -> Result<Self, ColumnCommitmentsMismatch>
265    where
266        Self: Sized,
267    {
268        let column_metadata = self.column_metadata.try_union(other.column_metadata)?;
269        let commitments = self
270            .commitments
271            .try_add(other.commitments)
272            .expect("we've already checked that self and other have equal column counts");
273
274        Ok(ColumnCommitments {
275            commitments,
276            column_metadata,
277        })
278    }
279
280    /// Subtract two [`ColumnCommitments`].
281    ///
282    /// Will error on a variety of mismatches.
283    /// See [`ColumnCommitmentsMismatch`] for an enumeration of these errors.
284    #[expect(clippy::missing_panics_doc)]
285    pub fn try_sub(self, other: Self) -> Result<Self, ColumnCommitmentsMismatch>
286    where
287        Self: Sized,
288    {
289        let column_metadata = self.column_metadata.try_difference(other.column_metadata)?;
290        let commitments = self
291            .commitments
292            .try_sub(other.commitments)
293            .expect("we've already checked that self and other have equal column counts");
294
295        Ok(ColumnCommitments {
296            commitments,
297            column_metadata,
298        })
299    }
300}
301
302/// Owning iterator for [`ColumnCommitments`].
303pub type IntoIter<C> = iter::Map<
304    iter::Zip<<ColumnCommitmentMetadataMap as IntoIterator>::IntoIter, vec::IntoIter<C>>,
305    fn(((Ident, ColumnCommitmentMetadata), C)) -> (Ident, ColumnCommitmentMetadata, C),
306>;
307
308impl<C> IntoIterator for ColumnCommitments<C> {
309    type Item = (Ident, ColumnCommitmentMetadata, C);
310    type IntoIter = IntoIter<C>;
311    fn into_iter(self) -> Self::IntoIter {
312        self.column_metadata
313            .into_iter()
314            .zip(self.commitments)
315            .map(|((identifier, metadata), commitment)| (identifier, metadata, commitment))
316    }
317}
318
319/// Borrowing iterator for [`ColumnCommitments`].
320pub type Iter<'a, C> = iter::Map<
321    iter::Zip<<&'a ColumnCommitmentMetadataMap as IntoIterator>::IntoIter, slice::Iter<'a, C>>,
322    fn(
323        ((&'a Ident, &'a ColumnCommitmentMetadata), &'a C),
324    ) -> (&'a Ident, &'a ColumnCommitmentMetadata, &'a C),
325>;
326
327impl<'a, C> IntoIterator for &'a ColumnCommitments<C> {
328    type Item = (&'a Ident, &'a ColumnCommitmentMetadata, &'a C);
329    type IntoIter = Iter<'a, C>;
330    fn into_iter(self) -> Self::IntoIter {
331        self.column_metadata
332            .iter()
333            .zip(self.commitments.iter())
334            .map(|((identifier, metadata), commitment)| (identifier, metadata, commitment))
335    }
336}
337
338impl<C> FromIterator<(Ident, ColumnCommitmentMetadata, C)> for ColumnCommitments<C> {
339    fn from_iter<T: IntoIterator<Item = (Ident, ColumnCommitmentMetadata, C)>>(iter: T) -> Self {
340        let (column_metadata, commitments) = iter
341            .into_iter()
342            .map(|(identifier, metadata, commitment)| ((identifier, metadata), commitment))
343            .unzip();
344
345        ColumnCommitments {
346            commitments,
347            column_metadata,
348        }
349    }
350}
351
352#[cfg(all(test, feature = "blitzar"))]
353mod tests {
354    use super::*;
355    use crate::base::{
356        commitment::{column_bounds::Bounds, naive_commitment::NaiveCommitment, ColumnBounds},
357        database::{owned_table_utility::*, ColumnType, OwnedColumn, OwnedTable},
358        scalar::test_scalar::TestScalar,
359    };
360
361    #[test]
362    fn we_can_construct_column_commitments_from_columns_and_identifiers() {
363        // empty case
364        let column_commitments =
365            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset::<
366                &OwnedColumn<TestScalar>,
367            >([], 0, &())
368            .unwrap();
369        assert_eq!(column_commitments.len(), 0);
370        assert!(column_commitments.is_empty());
371        assert!(column_commitments.commitments().is_empty());
372        assert!(column_commitments.column_metadata().is_empty());
373
374        // nonempty case
375        let bigint_id: Ident = "bigint_column".into();
376        let varchar_id: Ident = "varchar_column".into();
377        let scalar_id: Ident = "scalar_column".into();
378        let owned_table = owned_table::<TestScalar>([
379            bigint(bigint_id.value.as_str(), [1, 5, -5, 0]),
380            // "int128_column" => [100i128, 200, 300, 400], TODO: enable this column once blitzar
381            // supports it
382            varchar(
383                varchar_id.value.as_str(),
384                ["Lorem", "ipsum", "dolor", "sit"],
385            ),
386            scalar(scalar_id.value.as_str(), [1000, 2000, -1000, 0]),
387        ]);
388
389        let column_commitments =
390            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
391                owned_table.inner_table(),
392                0,
393                &(),
394            )
395            .unwrap();
396
397        assert_eq!(column_commitments.len(), 3);
398
399        let expected_commitments = Vec::<NaiveCommitment>::from_columns_with_offset(
400            owned_table.inner_table().values(),
401            0,
402            &(),
403        );
404        assert_eq!(column_commitments.commitments(), &expected_commitments);
405
406        assert_eq!(
407            column_commitments
408                .column_metadata()
409                .keys()
410                .collect::<Vec<_>>(),
411            vec![&bigint_id, &varchar_id, &scalar_id],
412        );
413
414        assert_eq!(
415            column_commitments
416                .get_metadata(&bigint_id)
417                .unwrap()
418                .column_type(),
419            &ColumnType::BigInt
420        );
421        assert_eq!(
422            column_commitments.get_commitment(&bigint_id).unwrap(),
423            expected_commitments[0]
424        );
425
426        assert_eq!(
427            column_commitments
428                .get_metadata(&varchar_id)
429                .unwrap()
430                .column_type(),
431            &ColumnType::VarChar
432        );
433        assert_eq!(
434            column_commitments.get_commitment(&varchar_id).unwrap(),
435            expected_commitments[1]
436        );
437
438        assert_eq!(
439            column_commitments
440                .get_metadata(&scalar_id)
441                .unwrap()
442                .column_type(),
443            &ColumnType::Scalar
444        );
445        assert_eq!(
446            column_commitments.get_commitment(&scalar_id).unwrap(),
447            expected_commitments[2]
448        );
449    }
450
451    #[test]
452    fn we_can_construct_column_commitments_from_iter() {
453        let bigint_id: Ident = "bigint_column".into();
454        let varchar_id: Ident = "varchar_column".into();
455        let scalar_id: Ident = "scalar_column".into();
456        let owned_table = owned_table::<TestScalar>([
457            bigint(bigint_id.value.as_str(), [1, 5, -5, 0]),
458            // "int128_column" => [100i128, 200, 300, 400], TODO: enable this column once blitzar
459            // supports it
460            varchar(
461                varchar_id.value.as_str(),
462                ["Lorem", "ipsum", "dolor", "sit"],
463            ),
464            scalar(scalar_id.value.as_str(), [1000, 2000, -1000, 0]),
465        ]);
466
467        let column_commitments_from_columns =
468            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
469                owned_table.inner_table(),
470                0,
471                &(),
472            )
473            .unwrap();
474
475        let column_commitments_from_iter =
476            ColumnCommitments::from_iter(column_commitments_from_columns.clone());
477
478        assert_eq!(
479            column_commitments_from_iter,
480            column_commitments_from_columns
481        );
482    }
483    #[test]
484    fn we_cannot_construct_commitments_with_duplicate_identifiers() {
485        let duplicate_identifier_a = "duplicate_identifier_a".into();
486        let duplicate_identifier_b = "duplicate_identifier_b".into();
487        let unique_identifier = "unique_identifier".into();
488
489        let empty_column = OwnedColumn::<TestScalar>::BigInt(vec![]);
490
491        let from_columns_result =
492            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
493                [
494                    (&duplicate_identifier_b, &empty_column),
495                    (&duplicate_identifier_b, &empty_column),
496                    (&unique_identifier, &empty_column),
497                ],
498                0,
499                &(),
500            );
501        assert!(matches!(from_columns_result, Err(DuplicateIdents { .. })));
502
503        let mut existing_column_commitments =
504            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
505                [
506                    (&duplicate_identifier_a, &empty_column),
507                    (&unique_identifier, &empty_column),
508                ],
509                0,
510                &(),
511            )
512            .unwrap();
513
514        let extend_with_existing_column_result = existing_column_commitments
515            .try_extend_columns_with_offset([(&duplicate_identifier_a, &empty_column)], 0, &());
516        assert!(matches!(
517            extend_with_existing_column_result,
518            Err(DuplicateIdents { .. })
519        ));
520
521        let extend_with_duplicate_columns_result = existing_column_commitments
522            .try_extend_columns_with_offset(
523                [
524                    (&duplicate_identifier_b, &empty_column),
525                    (&duplicate_identifier_b, &empty_column),
526                ],
527                0,
528                &(),
529            );
530        assert!(matches!(
531            extend_with_duplicate_columns_result,
532            Err(DuplicateIdents { .. })
533        ));
534
535        let append_result = existing_column_commitments.try_append_rows_with_offset(
536            [
537                (&duplicate_identifier_a, &empty_column),
538                (&unique_identifier, &empty_column),
539                (&duplicate_identifier_a, &empty_column),
540            ],
541            0,
542            &(),
543        );
544        assert!(matches!(
545            append_result,
546            Err(AppendColumnCommitmentsError::DuplicateIdents { .. })
547        ));
548    }
549
550    #[test]
551    fn we_can_iterate_over_column_commitments() {
552        let bigint_id: Ident = "bigint_column".into();
553        let varchar_id: Ident = "varchar_column".into();
554        let scalar_id: Ident = "scalar_column".into();
555        let owned_table = owned_table::<TestScalar>([
556            bigint(bigint_id.value.as_str(), [1, 5, -5, 0]),
557            varchar(
558                varchar_id.value.as_str(),
559                ["Lorem", "ipsum", "dolor", "sit"],
560            ),
561            scalar(scalar_id.value.as_str(), [1000, 2000, -1000, 0]),
562        ]);
563        let column_commitments =
564            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
565                owned_table.inner_table(),
566                0,
567                &(),
568            )
569            .unwrap();
570
571        let expected_commitments = Vec::<NaiveCommitment>::from_columns_with_offset(
572            owned_table.inner_table().values(),
573            0,
574            &(),
575        );
576
577        let mut iterator = column_commitments.iter();
578
579        let (identifier, metadata, commitment) = iterator.next().unwrap();
580        assert_eq!(commitment, &expected_commitments[0]);
581        assert_eq!(identifier, &bigint_id);
582        assert_eq!(metadata.column_type(), &ColumnType::BigInt);
583
584        let (identifier, metadata, commitment) = iterator.next().unwrap();
585        assert_eq!(commitment, &expected_commitments[1]);
586        assert_eq!(identifier, &varchar_id);
587        assert_eq!(metadata.column_type(), &ColumnType::VarChar);
588
589        let (identifier, metadata, commitment) = iterator.next().unwrap();
590        assert_eq!(commitment, &expected_commitments[2]);
591        assert_eq!(identifier, &scalar_id);
592        assert_eq!(metadata.column_type(), &ColumnType::Scalar);
593    }
594
595    #[test]
596    fn we_can_append_rows_to_column_commitments() {
597        let bigint_id: Ident = "bigint_column".into();
598        let bigint_data = [1i64, 5, -5, 0, 10];
599
600        let varchar_id: Ident = "varchar_column".into();
601        let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
602
603        let scalar_id: Ident = "scalar_column".into();
604        let scalar_data = [1000, 2000, 3000, -1000, 0];
605
606        let initial_columns: OwnedTable<TestScalar> = owned_table([
607            bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
608            varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
609            scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
610        ]);
611
612        let mut column_commitments =
613            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
614                initial_columns.inner_table(),
615                0,
616                &(),
617            )
618            .unwrap();
619
620        let append_columns: OwnedTable<TestScalar> = owned_table([
621            bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
622            varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
623            scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
624        ]);
625
626        column_commitments
627            .try_append_rows_with_offset(append_columns.inner_table(), 2, &())
628            .unwrap();
629
630        let total_columns: OwnedTable<TestScalar> = owned_table([
631            bigint(bigint_id.value.as_str(), bigint_data),
632            varchar(varchar_id.value.as_str(), varchar_data),
633            scalar(scalar_id.value.as_str(), scalar_data),
634        ]);
635
636        let expected_column_commitments =
637            ColumnCommitments::try_from_columns_with_offset(total_columns.inner_table(), 0, &())
638                .unwrap();
639
640        assert_eq!(column_commitments, expected_column_commitments);
641    }
642
643    #[test]
644    fn we_cannot_append_rows_to_mismatched_column_commitments() {
645        let base_table: OwnedTable<TestScalar> = owned_table([
646            bigint("column_a", [1, 2, 3, 4]),
647            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
648        ]);
649        let mut base_commitments =
650            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
651                base_table.inner_table(),
652                0,
653                &(),
654            )
655            .unwrap();
656
657        let table_diff_type: OwnedTable<TestScalar> = owned_table([
658            varchar("column_a", ["5", "6", "7", "8"]),
659            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
660        ]);
661        assert!(matches!(
662            base_commitments.try_append_rows_with_offset(table_diff_type.inner_table(), 4, &()),
663            Err(AppendColumnCommitmentsError::Mismatch {
664                source: ColumnCommitmentsMismatch::ColumnCommitmentMetadata { .. }
665            })
666        ));
667
668        let table_diff_id: OwnedTable<TestScalar> = owned_table([
669            bigint("column_a", [5, 6, 7, 8]),
670            varchar("b", ["amet", "ipsum", "dolor", "sit"]),
671        ]);
672        println!(
673            "{:?}",
674            base_commitments.try_append_rows_with_offset(table_diff_id.inner_table(), 4, &())
675        );
676        assert!(matches!(
677            base_commitments.try_append_rows_with_offset(table_diff_id.inner_table(), 4, &()),
678            Err(AppendColumnCommitmentsError::Mismatch {
679                source: ColumnCommitmentsMismatch::Ident { .. }
680            })
681        ));
682
683        let table_diff_len: OwnedTable<TestScalar> =
684            owned_table([bigint("column_a", [5, 6, 7, 8])]);
685        assert!(matches!(
686            base_commitments.try_append_rows_with_offset(table_diff_len.inner_table(), 4, &()),
687            Err(AppendColumnCommitmentsError::Mismatch {
688                source: ColumnCommitmentsMismatch::NumColumns
689            })
690        ));
691    }
692
693    #[test]
694    fn we_can_extend_columns_to_column_commitments() {
695        let bigint_id: Ident = "bigint_column".into();
696        let bigint_data = [1i64, 5, -5, 0, 10];
697
698        let varchar_id: Ident = "varchar_column".into();
699        let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
700
701        let scalar_id: Ident = "scalar_column".into();
702        let scalar_data = [1000, 2000, 3000, -1000, 0];
703
704        let initial_columns: OwnedTable<TestScalar> = owned_table([
705            bigint(bigint_id.value.as_str(), bigint_data),
706            varchar(varchar_id.value.as_str(), varchar_data),
707        ]);
708        let mut column_commitments =
709            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
710                initial_columns.inner_table(),
711                0,
712                &(),
713            )
714            .unwrap();
715
716        let new_columns =
717            owned_table::<TestScalar>([scalar(scalar_id.value.as_str(), scalar_data)]);
718        column_commitments
719            .try_extend_columns_with_offset(new_columns.inner_table(), 0, &())
720            .unwrap();
721
722        let expected_columns = owned_table::<TestScalar>([
723            bigint(bigint_id.value.as_str(), bigint_data),
724            varchar(varchar_id.value.as_str(), varchar_data),
725            scalar(scalar_id.value.as_str(), scalar_data),
726        ]);
727        let expected_commitments =
728            ColumnCommitments::try_from_columns_with_offset(expected_columns.inner_table(), 0, &())
729                .unwrap();
730
731        assert_eq!(column_commitments, expected_commitments);
732    }
733
734    #[test]
735    fn we_can_add_column_commitments() {
736        let bigint_id: Ident = "bigint_column".into();
737        let bigint_data = [1i64, 5, -5, 0, 10];
738
739        let varchar_id: Ident = "varchar_column".into();
740        let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
741
742        let scalar_id: Ident = "scalar_column".into();
743        let scalar_data = [1000, 2000, 3000, -1000, 0];
744
745        let columns_a: OwnedTable<TestScalar> = owned_table([
746            bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
747            varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
748            scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
749        ]);
750
751        let column_commitments_a =
752            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
753                columns_a.inner_table(),
754                0,
755                &(),
756            )
757            .unwrap();
758
759        let columns_b: OwnedTable<TestScalar> = owned_table([
760            bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
761            varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
762            scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
763        ]);
764        let column_commitments_b =
765            ColumnCommitments::try_from_columns_with_offset(columns_b.inner_table(), 2, &())
766                .unwrap();
767
768        let columns_sum: OwnedTable<TestScalar> = owned_table([
769            bigint(bigint_id.value.as_str(), bigint_data),
770            varchar(varchar_id.value.as_str(), varchar_data),
771            scalar(scalar_id.value.as_str(), scalar_data),
772        ]);
773        let column_commitments_sum =
774            ColumnCommitments::try_from_columns_with_offset(columns_sum.inner_table(), 0, &())
775                .unwrap();
776
777        assert_eq!(
778            column_commitments_a.try_add(column_commitments_b).unwrap(),
779            column_commitments_sum
780        );
781    }
782
783    #[test]
784    fn we_cannot_add_mismatched_column_commitments() {
785        let base_table: OwnedTable<TestScalar> = owned_table([
786            bigint("column_a", [1, 2, 3, 4]),
787            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
788        ]);
789        let base_commitments = ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
790            base_table.inner_table(),
791            0,
792            &(),
793        )
794        .unwrap();
795
796        let table_diff_type: OwnedTable<TestScalar> = owned_table([
797            varchar("column_a", ["5", "6", "7", "8"]),
798            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
799        ]);
800        let commitments_diff_type =
801            ColumnCommitments::try_from_columns_with_offset(table_diff_type.inner_table(), 4, &())
802                .unwrap();
803        assert!(matches!(
804            base_commitments.clone().try_add(commitments_diff_type),
805            Err(ColumnCommitmentsMismatch::ColumnCommitmentMetadata { .. })
806        ));
807
808        let table_diff_id: OwnedTable<TestScalar> = owned_table([
809            bigint("column_a", [5, 6, 7, 8]),
810            varchar("b", ["amet", "ipsum", "dolor", "sit"]),
811        ]);
812        let commitments_diff_id =
813            ColumnCommitments::try_from_columns_with_offset(table_diff_id.inner_table(), 4, &())
814                .unwrap();
815        assert!(matches!(
816            base_commitments.clone().try_add(commitments_diff_id),
817            Err(ColumnCommitmentsMismatch::Ident { .. })
818        ));
819
820        let table_diff_len: OwnedTable<TestScalar> =
821            owned_table([bigint("column_a", [5, 6, 7, 8])]);
822        let commitments_diff_len =
823            ColumnCommitments::try_from_columns_with_offset(table_diff_len.inner_table(), 4, &())
824                .unwrap();
825        assert!(matches!(
826            base_commitments.clone().try_add(commitments_diff_len),
827            Err(ColumnCommitmentsMismatch::NumColumns)
828        ));
829    }
830
831    #[test]
832    fn we_can_sub_column_commitments() {
833        let bigint_id: Ident = "bigint_column".into();
834        let bigint_data = [1i64, 5, -5, 0, 10];
835
836        let varchar_id: Ident = "varchar_column".into();
837        let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
838
839        let scalar_id: Ident = "scalar_column".into();
840        let scalar_data = [1000, 2000, 3000, -1000, 0];
841
842        let columns_subtrahend: OwnedTable<TestScalar> = owned_table([
843            bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
844            varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
845            scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
846        ]);
847
848        let column_commitments_subtrahend =
849            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
850                columns_subtrahend.inner_table(),
851                0,
852                &(),
853            )
854            .unwrap();
855
856        let columns_minuend: OwnedTable<TestScalar> = owned_table([
857            bigint(bigint_id.value.as_str(), bigint_data),
858            varchar(varchar_id.value.as_str(), varchar_data),
859            scalar(scalar_id.value.as_str(), scalar_data),
860        ]);
861        let column_commitments_minuend =
862            ColumnCommitments::try_from_columns_with_offset(columns_minuend.inner_table(), 0, &())
863                .unwrap();
864
865        let actual_difference = column_commitments_minuend
866            .try_sub(column_commitments_subtrahend)
867            .unwrap();
868
869        let expected_difference_columns: OwnedTable<TestScalar> = owned_table([
870            bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
871            varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
872            scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
873        ]);
874        let expected_difference = ColumnCommitments::try_from_columns_with_offset(
875            expected_difference_columns.inner_table(),
876            2,
877            &(),
878        )
879        .unwrap();
880
881        assert_eq!(
882            actual_difference.commitments(),
883            expected_difference.commitments()
884        );
885
886        assert_eq!(
887            actual_difference
888                .column_metadata()
889                .keys()
890                .collect::<Vec<_>>(),
891            vec![&bigint_id, &varchar_id, &scalar_id],
892        );
893
894        let bigint_metadata = actual_difference.get_metadata(&bigint_id).unwrap();
895        assert_eq!(bigint_metadata.column_type(), &ColumnType::BigInt);
896        if let ColumnBounds::BigInt(Bounds::Bounded(bounds)) = bigint_metadata.bounds() {
897            assert_eq!(bounds.min(), &-5);
898            assert_eq!(bounds.max(), &10);
899        }
900
901        let varchar_metadata = actual_difference.get_metadata(&varchar_id).unwrap();
902        assert_eq!(varchar_metadata.column_type(), &ColumnType::VarChar);
903        assert_eq!(varchar_metadata.bounds(), &ColumnBounds::NoOrder);
904
905        let scalar_metadata = actual_difference.get_metadata(&scalar_id).unwrap();
906        assert_eq!(scalar_metadata.column_type(), &ColumnType::Scalar);
907        assert_eq!(scalar_metadata.bounds(), &ColumnBounds::NoOrder);
908    }
909
910    #[test]
911    fn we_cannot_sub_mismatched_column_commitments() {
912        let minuend_table: OwnedTable<TestScalar> = owned_table([
913            bigint("column_a", [1, 2, 3, 4]),
914            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
915        ]);
916        let minuend_commitments =
917            ColumnCommitments::<NaiveCommitment>::try_from_columns_with_offset(
918                minuend_table.inner_table(),
919                0,
920                &(),
921            )
922            .unwrap();
923
924        let table_diff_type: OwnedTable<TestScalar> = owned_table([
925            varchar("column_a", ["1", "2"]),
926            varchar("column_b", ["Lorem", "ipsum"]),
927        ]);
928        let commitments_diff_type =
929            ColumnCommitments::try_from_columns_with_offset(table_diff_type.inner_table(), 4, &())
930                .unwrap();
931        assert!(matches!(
932            minuend_commitments.clone().try_sub(commitments_diff_type),
933            Err(ColumnCommitmentsMismatch::ColumnCommitmentMetadata { .. })
934        ));
935
936        let table_diff_id: OwnedTable<TestScalar> =
937            owned_table([bigint("column_a", [1, 2]), varchar("b", ["Lorem", "ipsum"])]);
938        let commitments_diff_id =
939            ColumnCommitments::try_from_columns_with_offset(table_diff_id.inner_table(), 4, &())
940                .unwrap();
941        assert!(matches!(
942            minuend_commitments.clone().try_sub(commitments_diff_id),
943            Err(ColumnCommitmentsMismatch::Ident { .. })
944        ));
945
946        let table_diff_len: OwnedTable<TestScalar> = owned_table([bigint("column_a", [1, 2])]);
947        let commitments_diff_len =
948            ColumnCommitments::try_from_columns_with_offset(table_diff_len.inner_table(), 4, &())
949                .unwrap();
950        assert!(matches!(
951            minuend_commitments.clone().try_sub(commitments_diff_len),
952            Err(ColumnCommitmentsMismatch::NumColumns)
953        ));
954    }
955}