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