1use super::{
2 committable_column::CommittableColumn, AppendColumnCommitmentsError, ColumnCommitments,
3 ColumnCommitmentsMismatch, Commitment, DuplicateIdents,
4};
5use crate::base::{
6 database::{ColumnField, CommitmentAccessor, OwnedTable, TableRef},
7 scalar::Scalar,
8};
9use alloc::vec::Vec;
10use core::ops::Range;
11use serde::{Deserialize, Serialize};
12use snafu::Snafu;
13use sqlparser::ast::Ident;
14
15#[derive(Debug, Snafu)]
17#[snafu(display("cannot create a TableCommitment with a negative range"))]
18pub struct NegativeRange;
19
20#[derive(Debug, Snafu)]
22#[snafu(display("cannot create a TableCommitment from columns of mixed length"))]
23pub struct MixedLengthColumns;
24
25#[derive(Debug, Snafu)]
27pub enum TableCommitmentFromColumnsError {
28 #[snafu(transparent)]
30 MixedLengthColumns {
31 source: MixedLengthColumns,
33 },
34 #[snafu(transparent)]
36 DuplicateIdents {
37 source: DuplicateIdents,
39 },
40}
41
42#[derive(Debug, Snafu)]
44pub enum AppendTableCommitmentError {
45 #[snafu(transparent)]
47 MixedLengthColumns {
48 source: MixedLengthColumns,
50 },
51 #[snafu(transparent)]
53 AppendColumnCommitments {
54 source: AppendColumnCommitmentsError,
56 },
57}
58
59#[derive(Debug, Snafu)]
61pub enum TableCommitmentArithmeticError {
62 #[snafu(transparent)]
64 ColumnMismatch {
65 source: ColumnCommitmentsMismatch,
67 },
68 #[snafu(transparent)]
70 NegativeRange {
71 source: NegativeRange,
73 },
74 #[snafu(display(
76 "cannot perform table commitment arithmetic for noncontiguous table commitments"
77 ))]
78 NonContiguous,
79}
80
81#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
85pub struct TableCommitment<C>
86where
87 C: Commitment,
88{
89 column_commitments: ColumnCommitments<C>,
90 range: Range<usize>,
91}
92
93impl<C: Commitment> TableCommitment<C> {
94 #[allow(
96 clippy::missing_panics_doc,
97 reason = "The assertion ensures that from_accessor should not create columns with a negative range"
98 )]
99 pub fn from_accessor_with_max_bounds(
100 table_ref: &TableRef,
101 columns: &[ColumnField],
102 accessor: &impl CommitmentAccessor<C>,
103 ) -> Self {
104 let length = accessor.get_length(table_ref);
105 let offset = accessor.get_offset(table_ref);
106 Self::try_new(
107 ColumnCommitments::from_accessor_with_max_bounds(table_ref, columns, accessor),
108 offset..offset + length,
109 )
110 .expect("from_accessor should not create columns with a negative range")
111 }
112
113 #[cfg(test)]
114 pub(super) fn column_commitments_mut(&mut self) -> &mut ColumnCommitments<C> {
115 &mut self.column_commitments
116 }
117
118 pub fn try_new(
122 column_commitments: ColumnCommitments<C>,
123 range: Range<usize>,
124 ) -> Result<Self, NegativeRange> {
125 if range.start <= range.end {
126 Ok(TableCommitment {
127 column_commitments,
128 range,
129 })
130 } else {
131 Err(NegativeRange)
132 }
133 }
134
135 #[must_use]
137 pub fn column_commitments(&self) -> &ColumnCommitments<C> {
138 &self.column_commitments
139 }
140
141 #[must_use]
143 pub fn range(&self) -> &Range<usize> {
144 &self.range
145 }
146
147 #[must_use]
149 pub fn num_columns(&self) -> usize {
150 self.column_commitments.len()
151 }
152
153 #[must_use]
155 pub fn num_rows(&self) -> usize {
156 self.range.len()
157 }
158
159 pub fn try_from_columns_with_offset<'a, COL>(
163 columns: impl IntoIterator<Item = (&'a Ident, COL)>,
164 offset: usize,
165 setup: &C::PublicSetup<'_>,
166 ) -> Result<TableCommitment<C>, TableCommitmentFromColumnsError>
167 where
168 COL: Into<CommittableColumn<'a>>,
169 {
170 let (identifiers, committable_columns): (Vec<&Ident>, Vec<CommittableColumn>) = columns
171 .into_iter()
172 .map(|(identifier, column)| (identifier, column.into()))
173 .unzip();
174
175 let num_rows = num_rows_of_columns(&committable_columns)?;
176
177 let column_commitments = ColumnCommitments::try_from_columns_with_offset(
178 identifiers.into_iter().zip(committable_columns.into_iter()),
179 offset,
180 setup,
181 )?;
182
183 Ok(TableCommitment {
184 column_commitments,
185 range: offset..offset + num_rows,
186 })
187 }
188
189 #[allow(
191 clippy::missing_panics_doc,
192 reason = "since OwnedTables cannot have columns of mixed length or duplicate idents"
193 )]
194 pub fn from_owned_table_with_offset<S>(
195 owned_table: &OwnedTable<S>,
196 offset: usize,
197 setup: &C::PublicSetup<'_>,
198 ) -> TableCommitment<C>
199 where
200 S: Scalar,
201 {
202 Self::try_from_columns_with_offset(owned_table.inner_table(), offset, setup)
203 .expect("OwnedTables cannot have columns of mixed length or duplicate idents")
204 }
205
206 pub fn try_append_rows<'a, COL>(
212 &mut self,
213 columns: impl IntoIterator<Item = (&'a Ident, COL)>,
214 setup: &C::PublicSetup<'_>,
215 ) -> Result<(), AppendTableCommitmentError>
216 where
217 COL: Into<CommittableColumn<'a>>,
218 {
219 let (identifiers, committable_columns): (Vec<&Ident>, Vec<CommittableColumn>) = columns
220 .into_iter()
221 .map(|(identifier, column)| (identifier, column.into()))
222 .unzip();
223
224 let num_rows = num_rows_of_columns(&committable_columns)?;
225
226 self.column_commitments.try_append_rows_with_offset(
227 identifiers.into_iter().zip(committable_columns.into_iter()),
228 self.range.end,
229 setup,
230 )?;
231 self.range.end += num_rows;
232
233 Ok(())
234 }
235
236 pub fn append_owned_table<S>(
244 &mut self,
245 owned_table: &OwnedTable<S>,
246 setup: &C::PublicSetup<'_>,
247 ) -> Result<(), ColumnCommitmentsMismatch>
248 where
249 S: Scalar,
250 {
251 self.try_append_rows(owned_table.inner_table(), setup)
252 .map_err(|e| match e {
253 AppendTableCommitmentError::AppendColumnCommitments { source: e } => match e {
254 AppendColumnCommitmentsError::Mismatch { source: e } => e,
255 AppendColumnCommitmentsError::DuplicateIdents { .. } => {
256 panic!("OwnedTables cannot have duplicate idents");
257 }
258 },
259 AppendTableCommitmentError::MixedLengthColumns { .. } => {
260 panic!("OwnedTables cannot have columns of mixed length");
261 }
262 })
263 }
264
265 pub fn try_extend_columns<'a, COL>(
269 &mut self,
270 columns: impl IntoIterator<Item = (&'a Ident, COL)>,
271 setup: &C::PublicSetup<'_>,
272 ) -> Result<(), TableCommitmentFromColumnsError>
273 where
274 COL: Into<CommittableColumn<'a>>,
275 {
276 let num_rows = self.range.len();
277
278 let (identifiers, committable_columns): (Vec<&Ident>, Vec<CommittableColumn>) = columns
279 .into_iter()
280 .map(|(identifier, column)| (identifier, column.into()))
281 .unzip();
282
283 let num_rows_of_new_columns = num_rows_of_columns(&committable_columns)?;
284 if num_rows_of_new_columns != num_rows {
285 Err(MixedLengthColumns)?;
286 }
287
288 self.column_commitments.try_extend_columns_with_offset(
289 identifiers.into_iter().zip(committable_columns.into_iter()),
290 self.range.start,
291 setup,
292 )?;
293
294 Ok(())
295 }
296
297 pub fn try_add(self, other: Self) -> Result<Self, TableCommitmentArithmeticError>
305 where
306 Self: Sized,
307 {
308 let range = if self.range.end == other.range.start {
309 self.range.start..other.range.end
310 } else if other.range.end == self.range.start {
311 other.range.start..self.range.end
312 } else {
313 return Err(TableCommitmentArithmeticError::NonContiguous);
314 };
315
316 let column_commitments = self.column_commitments.try_add(other.column_commitments)?;
317
318 Ok(TableCommitment {
319 column_commitments,
320 range,
321 })
322 }
323
324 pub fn try_sub(self, other: Self) -> Result<Self, TableCommitmentArithmeticError>
335 where
336 Self: Sized,
337 {
338 if self.range.len() < other.range.len() {
339 Err(NegativeRange)?;
340 }
341
342 let range = if self.range.start == other.range.start {
343 other.range.end..self.range.end
344 } else if self.range.end == other.range.end {
345 self.range.start..other.range.start
346 } else {
347 return Err(TableCommitmentArithmeticError::NonContiguous);
348 };
349
350 let column_commitments = self.column_commitments.try_sub(other.column_commitments)?;
351
352 Ok(TableCommitment {
353 column_commitments,
354 range,
355 })
356 }
357}
358
359fn num_rows_of_columns<'a>(
361 committable_columns: impl IntoIterator<Item = &'a CommittableColumn<'a>>,
362) -> Result<usize, MixedLengthColumns> {
363 let mut committable_columns_iter = committable_columns.into_iter().peekable();
364 let num_rows = committable_columns_iter
365 .peek()
366 .map_or(0, |committable_column| committable_column.len());
367
368 for committable_column in committable_columns_iter {
369 if committable_column.len() != num_rows {
370 return Err(MixedLengthColumns);
371 }
372 }
373
374 Ok(num_rows)
375}
376
377#[cfg(all(test, feature = "arrow", feature = "blitzar"))]
378mod tests {
379 use super::*;
380 use crate::{
381 base::{
382 commitment::naive_commitment::NaiveCommitment,
383 database::{owned_table_utility::*, Column, OwnedColumn},
384 map::IndexMap,
385 scalar::test_scalar::TestScalar,
386 },
387 record_batch,
388 };
389
390 #[test]
391 #[allow(clippy::reversed_empty_ranges)]
392 fn we_cannot_construct_table_commitment_with_negative_range() {
393 let try_new_result =
394 TableCommitment::<NaiveCommitment>::try_new(ColumnCommitments::default(), 1..0);
395
396 assert!(matches!(try_new_result, Err(NegativeRange)));
397 }
398
399 #[test]
400 fn we_can_construct_table_commitment_from_columns_and_identifiers() {
401 let mut empty_columns_iter: IndexMap<Ident, OwnedColumn<TestScalar>> = IndexMap::default();
403 let empty_table_commitment =
404 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
405 &empty_columns_iter,
406 0,
407 &(),
408 )
409 .unwrap();
410 assert_eq!(
411 empty_table_commitment.column_commitments(),
412 &ColumnCommitments::try_from_columns_with_offset(&empty_columns_iter, 0, &()).unwrap()
413 );
414 assert_eq!(empty_table_commitment.range(), &(0..0));
415 assert_eq!(empty_table_commitment.num_columns(), 0);
416 assert_eq!(empty_table_commitment.num_rows(), 0);
417
418 empty_columns_iter.insert("column_a".into(), OwnedColumn::BigInt(vec![]));
420 let empty_table_commitment =
421 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
422 &empty_columns_iter,
423 1,
424 &(),
425 )
426 .unwrap();
427 assert_eq!(
428 empty_table_commitment.column_commitments(),
429 &ColumnCommitments::try_from_columns_with_offset(&empty_columns_iter, 1, &()).unwrap()
430 );
431 assert_eq!(empty_table_commitment.range(), &(1..1));
432 assert_eq!(empty_table_commitment.num_columns(), 1);
433 assert_eq!(empty_table_commitment.num_rows(), 0);
434
435 let owned_table = owned_table::<TestScalar>([
437 bigint("bigint_id", [1, 5, -5, 0]),
438 varchar("varchar_id", ["Lorem", "ipsum", "dolor", "sit"]),
441 scalar("scalar_id", [1000, 2000, -1000, 0]),
442 ]);
443 let table_commitment = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
444 owned_table.inner_table(),
445 2,
446 &(),
447 )
448 .unwrap();
449 assert_eq!(
450 table_commitment.column_commitments(),
451 &ColumnCommitments::try_from_columns_with_offset(owned_table.inner_table(), 2, &())
452 .unwrap()
453 );
454 assert_eq!(table_commitment.range(), &(2..6));
455 assert_eq!(table_commitment.num_columns(), 3);
456 assert_eq!(table_commitment.num_rows(), 4);
457
458 let table_commitment_from_owned_table =
460 TableCommitment::from_owned_table_with_offset(&owned_table, 2, &());
461 assert_eq!(table_commitment_from_owned_table, table_commitment);
462 }
463
464 #[test]
465 fn we_cannot_construct_table_commitment_from_duplicate_identifiers() {
466 let duplicate_identifier_a = "duplicate_identifier_a".into();
467 let duplicate_identifier_b = "duplicate_identifier_b".into();
468 let unique_identifier = "unique_identifier".into();
469
470 let empty_column = OwnedColumn::<TestScalar>::BigInt(vec![]);
471
472 let from_columns_result = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
473 [
474 (&duplicate_identifier_a, &empty_column),
475 (&unique_identifier, &empty_column),
476 (&duplicate_identifier_a, &empty_column),
477 ],
478 0,
479 &(),
480 );
481 assert!(matches!(
482 from_columns_result,
483 Err(TableCommitmentFromColumnsError::DuplicateIdents { .. })
484 ));
485
486 let mut table_commitment =
487 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
488 [
489 (&duplicate_identifier_a, &empty_column),
490 (&unique_identifier, &empty_column),
491 ],
492 0,
493 &(),
494 )
495 .unwrap();
496 let column_commitments = table_commitment.column_commitments().clone();
497
498 let extend_columns_result =
499 table_commitment.try_extend_columns([(&duplicate_identifier_a, &empty_column)], &());
500 assert!(matches!(
501 extend_columns_result,
502 Err(TableCommitmentFromColumnsError::DuplicateIdents { .. })
503 ));
504
505 let extend_columns_result = table_commitment.try_extend_columns(
506 [
507 (&duplicate_identifier_b, &empty_column),
508 (&duplicate_identifier_b, &empty_column),
509 ],
510 &(),
511 );
512 assert!(matches!(
513 extend_columns_result,
514 Err(TableCommitmentFromColumnsError::DuplicateIdents { .. })
515 ));
516
517 assert_eq!(table_commitment.num_columns(), 2);
519 assert_eq!(table_commitment.column_commitments(), &column_commitments);
520 }
521
522 #[test]
523 fn we_cannot_construct_table_commitment_from_columns_of_mixed_length() {
524 let column_id_a = "column_a".into();
525 let column_id_b = "column_b".into();
526 let column_id_c = "column_c".into();
527
528 let one_row_column = OwnedColumn::<TestScalar>::BigInt(vec![1]);
529 let two_row_column = OwnedColumn::<TestScalar>::BigInt(vec![1, 2]);
530
531 let from_columns_result = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
532 [
533 (&column_id_a, &one_row_column),
534 (&column_id_b, &two_row_column),
535 ],
536 0,
537 &(),
538 );
539 assert!(matches!(
540 from_columns_result,
541 Err(TableCommitmentFromColumnsError::MixedLengthColumns { .. })
542 ));
543
544 let mut table_commitment =
545 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
546 [(&column_id_a, &one_row_column)],
547 0,
548 &(),
549 )
550 .unwrap();
551 let column_commitments = table_commitment.column_commitments().clone();
552
553 let extend_columns_result =
554 table_commitment.try_extend_columns([(&column_id_b, &two_row_column)], &());
555 assert!(matches!(
556 extend_columns_result,
557 Err(TableCommitmentFromColumnsError::MixedLengthColumns { .. })
558 ));
559
560 let extend_columns_result = table_commitment.try_extend_columns(
561 [
562 (&column_id_b, &one_row_column),
563 (&column_id_c, &two_row_column),
564 ],
565 &(),
566 );
567 assert!(matches!(
568 extend_columns_result,
569 Err(TableCommitmentFromColumnsError::MixedLengthColumns { .. })
570 ));
571
572 assert_eq!(table_commitment.num_columns(), 1);
574 assert_eq!(table_commitment.column_commitments(), &column_commitments);
575 }
576
577 #[test]
578 fn we_can_append_rows_to_table_commitment() {
579 let bigint_id: Ident = "bigint_column".into();
580 let bigint_data = [1i64, 5, -5, 0, 10];
581
582 let varchar_id: Ident = "varchar_column".into();
583 let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
584
585 let scalar_id: Ident = "scalar_column".into();
586 let scalar_data = [1000, 2000, 3000, -1000, 0];
587
588 let initial_columns: OwnedTable<TestScalar> = owned_table([
589 bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
590 varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
591 scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
592 ]);
593
594 let mut table_commitment =
595 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
596 initial_columns.inner_table(),
597 0,
598 &(),
599 )
600 .unwrap();
601 let mut table_commitment_clone = table_commitment.clone();
602
603 let append_columns: OwnedTable<TestScalar> = owned_table([
604 bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
605 varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
606 scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
607 ]);
608
609 table_commitment
610 .try_append_rows(append_columns.inner_table(), &())
611 .unwrap();
612
613 let total_columns: OwnedTable<TestScalar> = owned_table([
614 bigint(bigint_id.value.as_str(), bigint_data),
615 varchar(varchar_id.value.as_str(), varchar_data),
616 scalar(scalar_id.value.as_str(), scalar_data),
617 ]);
618
619 let expected_table_commitment =
620 TableCommitment::try_from_columns_with_offset(total_columns.inner_table(), 0, &())
621 .unwrap();
622
623 assert_eq!(table_commitment, expected_table_commitment);
624
625 table_commitment_clone
627 .append_owned_table(&append_columns, &())
628 .unwrap();
629 assert_eq!(table_commitment, table_commitment_clone);
630 }
631
632 #[test]
633 fn we_cannot_append_mismatched_columns_to_table_commitment() {
634 let base_table: OwnedTable<TestScalar> = owned_table([
635 bigint("column_a", [1, 2, 3, 4]),
636 varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
637 ]);
638 let mut table_commitment =
639 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
640 base_table.inner_table(),
641 0,
642 &(),
643 )
644 .unwrap();
645 let column_commitments = table_commitment.column_commitments().clone();
646
647 let table_diff_type: OwnedTable<TestScalar> = owned_table([
648 varchar("column_a", ["5", "6", "7", "8"]),
649 varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
650 ]);
651 assert!(matches!(
652 table_commitment.try_append_rows(table_diff_type.inner_table(), &()),
653 Err(AppendTableCommitmentError::AppendColumnCommitments {
654 source: AppendColumnCommitmentsError::Mismatch {
655 source: ColumnCommitmentsMismatch::ColumnCommitmentMetadata { .. }
656 }
657 })
658 ));
659
660 assert_eq!(table_commitment.num_rows(), 4);
662 assert_eq!(table_commitment.column_commitments(), &column_commitments);
663 }
664
665 #[test]
666 fn we_cannot_append_columns_with_duplicate_identifiers_to_table_commitment() {
667 let column_id_a = "column_a".into();
668 let column_id_b = "column_b".into();
669
670 let column_data = OwnedColumn::<TestScalar>::BigInt(vec![1, 2, 3]);
671
672 let mut table_commitment =
673 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
674 [(&column_id_a, &column_data), (&column_id_b, &column_data)],
675 0,
676 &(),
677 )
678 .unwrap();
679 let column_commitments = table_commitment.column_commitments().clone();
680
681 let append_column_result = table_commitment.try_append_rows(
682 [
683 (&column_id_a, &column_data),
684 (&column_id_b, &column_data),
685 (&column_id_a, &column_data),
686 ],
687 &(),
688 );
689 assert!(matches!(
690 append_column_result,
691 Err(AppendTableCommitmentError::AppendColumnCommitments {
692 source: AppendColumnCommitmentsError::DuplicateIdents { .. }
693 })
694 ));
695
696 assert_eq!(table_commitment.num_rows(), 3);
698 assert_eq!(table_commitment.column_commitments(), &column_commitments);
699 }
700
701 #[allow(clippy::similar_names)]
702 #[test]
703 fn we_cannot_append_columns_of_mixed_length_to_table_commitment() {
704 let column_id_a: Ident = "column_a".into();
705 let column_id_b: Ident = "column_b".into();
706 let base_table: OwnedTable<TestScalar> = owned_table([
707 bigint(column_id_a.value.as_str(), [1, 2, 3, 4]),
708 varchar(
709 column_id_b.value.as_str(),
710 ["Lorem", "ipsum", "dolor", "sit"],
711 ),
712 ]);
713
714 let mut table_commitment =
715 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
716 base_table.inner_table(),
717 0,
718 &(),
719 )
720 .unwrap();
721 let column_commitments = table_commitment.column_commitments().clone();
722
723 let column_a_append_data = OwnedColumn::<TestScalar>::BigInt(vec![5, 6, 7]);
724 let column_b_append_data =
725 OwnedColumn::VarChar(["amet", "consectetur"].map(String::from).to_vec());
726
727 let append_result = table_commitment.try_append_rows(
728 [
729 (&column_id_a, &column_a_append_data),
730 (&column_id_b, &column_b_append_data),
731 ],
732 &(),
733 );
734 assert!(matches!(
735 append_result,
736 Err(AppendTableCommitmentError::MixedLengthColumns { .. })
737 ));
738
739 assert_eq!(table_commitment.num_rows(), 4);
741 assert_eq!(table_commitment.column_commitments(), &column_commitments);
742 }
743
744 #[test]
745 fn we_can_extend_columns_to_table_commitment() {
746 let bigint_id: Ident = "bigint_column".into();
747 let bigint_data = [1i64, 5, -5, 0, 10];
748
749 let varchar_id: Ident = "varchar_column".into();
750 let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
751
752 let scalar_id: Ident = "scalar_column".into();
753 let scalar_data = [1000, 2000, 3000, -1000, 0];
754
755 let initial_columns: OwnedTable<TestScalar> = owned_table([
756 bigint(bigint_id.value.as_str(), bigint_data),
757 varchar(varchar_id.value.as_str(), varchar_data),
758 ]);
759 let mut table_commitment =
760 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
761 initial_columns.inner_table(),
762 2,
763 &(),
764 )
765 .unwrap();
766
767 let new_columns =
768 owned_table::<TestScalar>([scalar(scalar_id.value.as_str(), scalar_data)]);
769 table_commitment
770 .try_extend_columns(new_columns.inner_table(), &())
771 .unwrap();
772
773 let expected_columns = owned_table::<TestScalar>([
774 bigint(bigint_id.value.as_str(), bigint_data),
775 varchar(varchar_id.value.as_str(), varchar_data),
776 scalar(scalar_id.value.as_str(), scalar_data),
777 ]);
778 let expected_table_commitment =
779 TableCommitment::try_from_columns_with_offset(expected_columns.inner_table(), 2, &())
780 .unwrap();
781
782 assert_eq!(table_commitment, expected_table_commitment);
783 }
784
785 #[test]
786 fn we_can_add_table_commitments() {
787 let bigint_id: Ident = "bigint_column".into();
788 let bigint_data = [1i64, 5, -5, 0, 10];
789
790 let varchar_id: Ident = "varchar_column".into();
791 let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
792
793 let scalar_id: Ident = "scalar_column".into();
794 let scalar_data = [1000, 2000, 3000, -1000, 0];
795
796 let columns_a: OwnedTable<TestScalar> = owned_table([
797 bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
798 varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
799 scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
800 ]);
801
802 let table_commitment_a = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
803 columns_a.inner_table(),
804 0,
805 &(),
806 )
807 .unwrap();
808
809 let columns_b: OwnedTable<TestScalar> = owned_table([
810 bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
811 varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
812 scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
813 ]);
814 let table_commitment_b =
815 TableCommitment::try_from_columns_with_offset(columns_b.inner_table(), 2, &()).unwrap();
816
817 let columns_sum: OwnedTable<TestScalar> = owned_table([
818 bigint(bigint_id.value.as_str(), bigint_data),
819 varchar(varchar_id.value.as_str(), varchar_data),
820 scalar(scalar_id.value.as_str(), scalar_data),
821 ]);
822 let table_commitment_sum =
823 TableCommitment::try_from_columns_with_offset(columns_sum.inner_table(), 0, &())
824 .unwrap();
825
826 assert_eq!(
827 table_commitment_a
828 .clone()
829 .try_add(table_commitment_b.clone())
830 .unwrap(),
831 table_commitment_sum
832 );
833 assert_eq!(
835 table_commitment_b.try_add(table_commitment_a).unwrap(),
836 table_commitment_sum
837 );
838 }
839
840 #[test]
841 fn we_cannot_add_mismatched_table_commitments() {
842 let base_table: OwnedTable<TestScalar> = owned_table([
843 bigint("column_a", [1, 2, 3, 4]),
844 varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
845 ]);
846 let table_commitment = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
847 base_table.inner_table(),
848 0,
849 &(),
850 )
851 .unwrap();
852
853 let table_diff_type: OwnedTable<TestScalar> = owned_table([
854 varchar("column_a", ["5", "6", "7", "8"]),
855 varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
856 ]);
857 let table_commitment_diff_type =
858 TableCommitment::try_from_columns_with_offset(table_diff_type.inner_table(), 4, &())
859 .unwrap();
860 assert!(matches!(
861 table_commitment.try_add(table_commitment_diff_type),
862 Err(TableCommitmentArithmeticError::ColumnMismatch { .. })
863 ));
864 }
865
866 #[test]
867 fn we_cannot_add_noncontiguous_table_commitments() {
868 let base_table: OwnedTable<TestScalar> = owned_table([
869 bigint("column_a", [1, 2, 3, 4]),
870 varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
871 ]);
872 let table_commitment = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
873 base_table.inner_table(),
874 5,
875 &(),
876 )
877 .unwrap();
878
879 let high_disjoint_table_commitment =
880 TableCommitment::try_from_columns_with_offset(base_table.inner_table(), 10, &())
881 .unwrap();
882 assert!(matches!(
883 table_commitment
884 .clone()
885 .try_add(high_disjoint_table_commitment),
886 Err(TableCommitmentArithmeticError::NonContiguous)
887 ));
888
889 let high_overlapping_table_commitment =
890 TableCommitment::try_from_columns_with_offset(base_table.inner_table(), 7, &())
891 .unwrap();
892 assert!(matches!(
893 table_commitment
894 .clone()
895 .try_add(high_overlapping_table_commitment),
896 Err(TableCommitmentArithmeticError::NonContiguous)
897 ));
898
899 let equal_range_table_commitment =
900 TableCommitment::try_from_columns_with_offset(base_table.inner_table(), 5, &())
901 .unwrap();
902 assert!(matches!(
903 table_commitment
904 .clone()
905 .try_add(equal_range_table_commitment),
906 Err(TableCommitmentArithmeticError::NonContiguous)
907 ));
908
909 let low_overlapping_table_commitment =
910 TableCommitment::try_from_columns_with_offset(base_table.inner_table(), 3, &())
911 .unwrap();
912 assert!(matches!(
913 table_commitment
914 .clone()
915 .try_add(low_overlapping_table_commitment),
916 Err(TableCommitmentArithmeticError::NonContiguous)
917 ));
918
919 let low_disjoint_table_commitment =
920 TableCommitment::try_from_columns_with_offset(base_table.inner_table(), 0, &())
921 .unwrap();
922 assert!(matches!(
923 table_commitment
924 .clone()
925 .try_add(low_disjoint_table_commitment),
926 Err(TableCommitmentArithmeticError::NonContiguous)
927 ));
928 }
929
930 #[test]
931 fn we_can_sub_table_commitments() {
932 let bigint_id: Ident = "bigint_column".into();
933 let bigint_data = [1i64, 5, -5, 0, 10];
934
935 let varchar_id: Ident = "varchar_column".into();
936 let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
937
938 let scalar_id: Ident = "scalar_column".into();
939 let scalar_data = [1000, 2000, 3000, -1000, 0];
940
941 let columns_low: OwnedTable<TestScalar> = owned_table([
942 bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
943 varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
944 scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
945 ]);
946 let table_commitment_low =
947 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
948 columns_low.inner_table(),
949 0,
950 &(),
951 )
952 .unwrap();
953
954 let columns_high: OwnedTable<TestScalar> = owned_table([
955 bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
956 varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
957 scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
958 ]);
959 let table_commitment_high =
960 TableCommitment::try_from_columns_with_offset(columns_high.inner_table(), 2, &())
961 .unwrap();
962
963 let columns_all: OwnedTable<TestScalar> = owned_table([
964 bigint(bigint_id.value.as_str(), bigint_data),
965 varchar(varchar_id.value.as_str(), varchar_data),
966 scalar(scalar_id.value.as_str(), scalar_data),
967 ]);
968 let table_commitment_all =
969 TableCommitment::try_from_columns_with_offset(columns_all.inner_table(), 0, &())
970 .unwrap();
971
972 let high_difference = table_commitment_all
974 .clone()
975 .try_sub(table_commitment_low.clone())
976 .unwrap();
977 assert_eq!(
978 high_difference.column_commitments().commitments(),
979 table_commitment_high.column_commitments().commitments()
980 );
981 assert_eq!(high_difference.range(), table_commitment_high.range());
982
983 let low_difference = table_commitment_all.try_sub(table_commitment_high).unwrap();
985 assert_eq!(
986 low_difference.column_commitments().commitments(),
987 table_commitment_low.column_commitments().commitments()
988 );
989 assert_eq!(low_difference.range(), table_commitment_low.range());
990
991 }
993
994 #[test]
995 fn we_cannot_sub_mismatched_table_commitments() {
996 let base_table: OwnedTable<TestScalar> = owned_table([
997 bigint("column_a", [1, 2, 3, 4]),
998 varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
999 ]);
1000 let table_commitment = TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
1001 base_table.inner_table(),
1002 0,
1003 &(),
1004 )
1005 .unwrap();
1006
1007 let table_diff_type: OwnedTable<TestScalar> = owned_table([
1008 varchar("column_a", ["1", "2"]),
1009 varchar("column_b", ["Lorem", "ipsum"]),
1010 ]);
1011 let table_commitment_diff_type =
1012 TableCommitment::try_from_columns_with_offset(table_diff_type.inner_table(), 0, &())
1013 .unwrap();
1014 assert!(matches!(
1015 table_commitment.try_sub(table_commitment_diff_type),
1016 Err(TableCommitmentArithmeticError::ColumnMismatch { .. })
1017 ));
1018 }
1019
1020 #[test]
1021 fn we_cannot_sub_noncontiguous_table_commitments() {
1022 let bigint_id: Ident = "bigint_column".into();
1023 let bigint_data = [1i64, 5, -5, 0, 10];
1024
1025 let varchar_id: Ident = "varchar_column".into();
1026 let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
1027
1028 let scalar_id: Ident = "scalar_column".into();
1029 let scalar_data = [1000, 2000, 3000, -1000, 0];
1030
1031 let columns_minuend: OwnedTable<TestScalar> = owned_table([
1032 bigint(bigint_id.value.as_str(), bigint_data[..].to_vec()),
1033 varchar(varchar_id.value.as_str(), varchar_data[..].to_vec()),
1034 scalar(scalar_id.value.as_str(), scalar_data[..].to_vec()),
1035 ]);
1036
1037 let columns_subtrahend: OwnedTable<TestScalar> = owned_table([
1038 bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
1039 varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
1040 scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
1041 ]);
1042
1043 let minuend_table_commitment =
1044 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
1045 columns_minuend.inner_table(),
1046 4,
1047 &(),
1048 )
1049 .unwrap();
1050
1051 let high_contiguous_table_commitment =
1052 TableCommitment::try_from_columns_with_offset(columns_subtrahend.inner_table(), 9, &())
1053 .unwrap();
1054 assert!(matches!(
1055 minuend_table_commitment
1056 .clone()
1057 .try_sub(high_contiguous_table_commitment),
1058 Err(TableCommitmentArithmeticError::NonContiguous)
1059 ));
1060
1061 let high_overlapping_table_commitment =
1062 TableCommitment::try_from_columns_with_offset(columns_subtrahend.inner_table(), 6, &())
1063 .unwrap();
1064 assert!(matches!(
1065 minuend_table_commitment
1066 .clone()
1067 .try_sub(high_overlapping_table_commitment),
1068 Err(TableCommitmentArithmeticError::NonContiguous)
1069 ));
1070
1071 let low_overlapping_table_commitment =
1072 TableCommitment::try_from_columns_with_offset(columns_subtrahend.inner_table(), 3, &())
1073 .unwrap();
1074 assert!(matches!(
1075 minuend_table_commitment
1076 .clone()
1077 .try_sub(low_overlapping_table_commitment),
1078 Err(TableCommitmentArithmeticError::NonContiguous)
1079 ));
1080
1081 let low_contiguous_table_commitment =
1082 TableCommitment::try_from_columns_with_offset(columns_subtrahend.inner_table(), 2, &())
1083 .unwrap();
1084 assert!(matches!(
1085 minuend_table_commitment
1086 .clone()
1087 .try_sub(low_contiguous_table_commitment),
1088 Err(TableCommitmentArithmeticError::NonContiguous)
1089 ));
1090 }
1091
1092 #[test]
1093 fn we_cannot_sub_commitments_with_negative_difference() {
1094 let bigint_id: Ident = "bigint_column".into();
1095 let bigint_data = [1i64, 5, -5, 0, 10];
1096
1097 let varchar_id: Ident = "varchar_column".into();
1098 let varchar_data = ["Lorem", "ipsum", "dolor", "sit", "amet"];
1099
1100 let scalar_id: Ident = "scalar_column".into();
1101 let scalar_data = [1000, 2000, 3000, -1000, 0];
1102
1103 let columns_low: OwnedTable<TestScalar> = owned_table([
1104 bigint(bigint_id.value.as_str(), bigint_data[..2].to_vec()),
1105 varchar(varchar_id.value.as_str(), varchar_data[..2].to_vec()),
1106 scalar(scalar_id.value.as_str(), scalar_data[..2].to_vec()),
1107 ]);
1108 let table_commitment_low =
1109 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(
1110 columns_low.inner_table(),
1111 0,
1112 &(),
1113 )
1114 .unwrap();
1115
1116 let columns_high: OwnedTable<TestScalar> = owned_table([
1117 bigint(bigint_id.value.as_str(), bigint_data[2..].to_vec()),
1118 varchar(varchar_id.value.as_str(), varchar_data[2..].to_vec()),
1119 scalar(scalar_id.value.as_str(), scalar_data[2..].to_vec()),
1120 ]);
1121 let table_commitment_high =
1122 TableCommitment::try_from_columns_with_offset(columns_high.inner_table(), 2, &())
1123 .unwrap();
1124
1125 let columns_all: OwnedTable<TestScalar> = owned_table([
1126 bigint(bigint_id.value.as_str(), bigint_data),
1127 varchar(varchar_id.value.as_str(), varchar_data),
1128 scalar(scalar_id.value.as_str(), scalar_data),
1129 ]);
1130 let table_commitment_all =
1131 TableCommitment::try_from_columns_with_offset(columns_all.inner_table(), 0, &())
1132 .unwrap();
1133
1134 let try_negative_high_difference_result =
1136 table_commitment_low.try_sub(table_commitment_all.clone());
1137 assert!(matches!(
1138 try_negative_high_difference_result,
1139 Err(TableCommitmentArithmeticError::NegativeRange { .. })
1140 ));
1141
1142 let try_negative_low_difference_result =
1144 table_commitment_high.try_sub(table_commitment_all);
1145 assert!(matches!(
1146 try_negative_low_difference_result,
1147 Err(TableCommitmentArithmeticError::NegativeRange { .. })
1148 ));
1149 }
1150
1151 #[test]
1152 fn we_can_create_and_append_table_commitments_with_record_batches() {
1153 let batch = record_batch!(
1154 "a" => [1i64, 2, 3],
1155 "b" => ["1", "2", "3"],
1156 );
1157
1158 let b_scals = ["1".into(), "2".into(), "3".into()];
1159
1160 let columns = [
1161 (&"a".into(), &Column::<TestScalar>::BigInt(&[1, 2, 3])),
1162 (
1163 &"b".into(),
1164 &Column::<TestScalar>::VarChar((&["1", "2", "3"], &b_scals)),
1165 ),
1166 ];
1167
1168 let mut expected_commitment =
1169 TableCommitment::<NaiveCommitment>::try_from_columns_with_offset(columns, 0, &())
1170 .unwrap();
1171
1172 let mut commitment =
1173 TableCommitment::<NaiveCommitment>::try_from_record_batch(&batch, &()).unwrap();
1174
1175 assert_eq!(commitment, expected_commitment);
1176
1177 let batch2 = record_batch!(
1178 "a" => [4i64, 5, 6],
1179 "b" => ["4", "5", "6"],
1180 );
1181
1182 let b_scals2 = ["4".into(), "5".into(), "6".into()];
1183
1184 let columns2 = [
1185 (&"a".into(), &Column::<TestScalar>::BigInt(&[4, 5, 6])),
1186 (
1187 &"b".into(),
1188 &Column::<TestScalar>::VarChar((&["4", "5", "6"], &b_scals2)),
1189 ),
1190 ];
1191
1192 expected_commitment.try_append_rows(columns2, &()).unwrap();
1193 commitment.try_append_record_batch(&batch2, &()).unwrap();
1194
1195 assert_eq!(commitment, expected_commitment);
1196 }
1197}