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#[derive(Debug, Snafu)]
21#[snafu(display("cannot create commitments with duplicate ident: {id}"))]
22pub struct DuplicateIdents {
23 id: String,
24}
25
26#[derive(Debug, Snafu)]
28pub enum AppendColumnCommitmentsError {
29 #[snafu(transparent)]
31 Mismatch {
32 source: ColumnCommitmentsMismatch,
34 },
35 #[snafu(transparent)]
37 DuplicateIdents {
38 source: DuplicateIdents,
40 },
41}
42
43#[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 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 #[must_use]
78 pub fn commitments(&self) -> &Vec<C> {
79 &self.commitments
80 }
81
82 #[must_use]
84 pub fn column_metadata(&self) -> &ColumnCommitmentMetadataMap {
85 &self.column_metadata
86 }
87
88 #[must_use]
90 pub fn len(&self) -> usize {
91 self.column_metadata.len()
92 }
93
94 #[must_use]
96 pub fn is_empty(&self) -> bool {
97 self.column_metadata.is_empty()
98 }
99
100 #[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 #[must_use]
110 pub fn get_metadata(&self, identifier: &Ident) -> Option<&ColumnCommitmentMetadata> {
111 self.column_metadata.get(identifier)
112 }
113
114 pub fn iter(&self) -> Iter<C> {
116 self.into_iter()
117 }
118
119 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 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 #[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 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 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 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 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 #[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 #[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
302pub 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
319pub 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 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 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 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 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}