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#[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| {
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 #[must_use]
80 pub fn commitments(&self) -> &Vec<C> {
81 &self.commitments
82 }
83
84 #[must_use]
86 pub fn column_metadata(&self) -> &ColumnCommitmentMetadataMap {
87 &self.column_metadata
88 }
89
90 #[must_use]
92 pub fn len(&self) -> usize {
93 self.column_metadata.len()
94 }
95
96 #[must_use]
98 pub fn is_empty(&self) -> bool {
99 self.column_metadata.is_empty()
100 }
101
102 #[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 #[must_use]
112 pub fn get_metadata(&self, identifier: &Ident) -> Option<&ColumnCommitmentMetadata> {
113 self.column_metadata.get(identifier)
114 }
115
116 pub fn iter(&self) -> Iter<C> {
118 self.into_iter()
119 }
120
121 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 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 #[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 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 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 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 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 #[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 #[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
304pub 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
321pub 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 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 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 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 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}