proof_of_sql/base/commitment/
query_commitments.rs

1use super::{Commitment, TableCommitment};
2use crate::base::{
3    database::{
4        ColumnField, ColumnRef, ColumnType, CommitmentAccessor, MetadataAccessor, SchemaAccessor,
5        TableRef,
6    },
7    map::IndexMap,
8};
9use alloc::vec::Vec;
10use sqlparser::ast::Ident;
11
12/// The commitments for all of the tables in a query.
13///
14/// Simply maps table refs to table commitments, and implements the following traits...
15/// - [`MetadataAccessor`]
16/// - [`CommitmentAccessor`]
17/// - [`SchemaAccessor`]
18pub type QueryCommitments<C> = IndexMap<TableRef, TableCommitment<C>>;
19
20/// A trait for extending the functionality of the [`QueryCommitments`] alias.
21pub trait QueryCommitmentsExt<C>
22where
23    C: Commitment,
24{
25    /// Create a new `QueryCommitments` from a collection of columns and an accessor.
26    fn from_accessor_with_max_bounds(
27        columns: impl IntoIterator<Item = ColumnRef>,
28        accessor: &(impl CommitmentAccessor<C> + SchemaAccessor),
29    ) -> Self;
30}
31
32impl<C: Commitment> QueryCommitmentsExt<C> for QueryCommitments<C> {
33    fn from_accessor_with_max_bounds(
34        columns: impl IntoIterator<Item = ColumnRef>,
35        accessor: &(impl CommitmentAccessor<C> + SchemaAccessor),
36    ) -> Self {
37        columns
38            .into_iter()
39            .fold(
40                IndexMap::<_, Vec<_>>::default(),
41                |mut table_columns, column| {
42                    table_columns
43                        .entry(column.table_ref())
44                        .or_default()
45                        .push(ColumnField::new(column.column_id(), *column.column_type()));
46                    table_columns
47                },
48            )
49            .into_iter()
50            .map(|(table_ref, column_fields)| {
51                let selected_column_fields = accessor
52                    .lookup_schema(&table_ref)
53                    .into_iter()
54                    .filter_map(|(ident, _)| {
55                        column_fields
56                            .iter()
57                            .find(|column_field| column_field.name() == ident)
58                            .cloned()
59                    })
60                    .collect::<Vec<_>>();
61                let table_commitment = TableCommitment::from_accessor_with_max_bounds(
62                    &table_ref,
63                    &selected_column_fields,
64                    accessor,
65                );
66                (table_ref, table_commitment)
67            })
68            .collect()
69    }
70}
71
72impl<C: Commitment> MetadataAccessor for QueryCommitments<C> {
73    fn get_length(&self, table_ref: &TableRef) -> usize {
74        let table_commitment = self.get(&table_ref).unwrap();
75        table_commitment.num_rows()
76    }
77
78    fn get_offset(&self, table_ref: &TableRef) -> usize {
79        let table_commitment = self.get(&table_ref).unwrap();
80        table_commitment.range().start
81    }
82}
83
84/// # Panics
85///
86/// Panics if the commitment for the table or column cannot be found.
87impl<C: Commitment> CommitmentAccessor<C> for QueryCommitments<C> {
88    fn get_commitment(&self, table_ref: &TableRef, column_id: &Ident) -> C {
89        let table_commitment = self.get(table_ref).unwrap();
90
91        table_commitment
92            .column_commitments()
93            .get_commitment(column_id)
94            .unwrap()
95    }
96}
97
98impl<C: Commitment> SchemaAccessor for QueryCommitments<C> {
99    fn lookup_column(&self, table_ref: &TableRef, column_id: &Ident) -> Option<ColumnType> {
100        let table_commitment = self.get(table_ref)?;
101
102        table_commitment
103            .column_commitments()
104            .get_metadata(column_id)
105            .map(|column_metadata| *column_metadata.column_type())
106    }
107
108    /// # Panics
109    ///
110    /// Panics if the column metadata cannot be found.
111    fn lookup_schema(&self, table_ref: &TableRef) -> Vec<(Ident, ColumnType)> {
112        let table_commitment = self.get(table_ref).unwrap();
113
114        table_commitment
115            .column_commitments()
116            .column_metadata()
117            .iter()
118            .map(|(identifier, column_metadata)| {
119                (identifier.clone(), *column_metadata.column_type())
120            })
121            .collect()
122    }
123}
124
125#[cfg(all(test, feature = "blitzar"))]
126mod tests {
127    use super::*;
128    use crate::{
129        base::{
130            commitment::{naive_commitment::NaiveCommitment, Bounds, ColumnBounds},
131            database::{
132                owned_table_utility::*, OwnedColumn, OwnedTable, OwnedTableTestAccessor,
133                TestAccessor,
134            },
135            scalar::test_scalar::TestScalar,
136        },
137        proof_primitive::dory::{
138            test_rng, DoryCommitment, DoryEvaluationProof, DoryProverPublicSetup, ProverSetup,
139            PublicParameters,
140        },
141    };
142
143    #[test]
144    fn we_can_get_length_and_offset_of_tables() {
145        let table_a: OwnedTable<TestScalar> = owned_table([
146            bigint("column_a", [1, 2, 3, 4]),
147            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
148        ]);
149
150        let table_b: OwnedTable<TestScalar> = owned_table([scalar("column_c", [1, 2])]);
151
152        let offset_commitment =
153            TableCommitment::<NaiveCommitment>::from_owned_table_with_offset(&table_a, 2, &());
154        let offset_table_id = TableRef::new("off", "table");
155
156        let no_offset_commitment = TableCommitment::from_owned_table_with_offset(&table_b, 0, &());
157        let no_offset_id = TableRef::new("no", "off");
158
159        let no_columns_commitment = TableCommitment::try_from_columns_with_offset(
160            Vec::<(&Ident, &OwnedColumn<TestScalar>)>::new(),
161            0,
162            &(),
163        )
164        .unwrap();
165        let no_columns_id = TableRef::new("no", "columns");
166
167        let no_rows_commitment = TableCommitment::try_from_columns_with_offset(
168            [(
169                &"column_c".into(),
170                &OwnedColumn::<TestScalar>::BigInt(vec![]),
171            )],
172            3,
173            &(),
174        )
175        .unwrap();
176        let no_rows_id = TableRef::new("no", "rows");
177
178        let query_commitments = QueryCommitments::from_iter([
179            (offset_table_id.clone(), offset_commitment),
180            (no_offset_id.clone(), no_offset_commitment),
181            (no_columns_id.clone(), no_columns_commitment),
182            (no_rows_id.clone(), no_rows_commitment),
183        ]);
184
185        assert_eq!(query_commitments.get_offset(&offset_table_id), 2);
186        assert_eq!(query_commitments.get_length(&offset_table_id), 4);
187
188        assert_eq!(query_commitments.get_offset(&no_offset_id), 0);
189        assert_eq!(query_commitments.get_length(&no_offset_id), 2);
190
191        assert_eq!(query_commitments.get_offset(&no_columns_id), 0);
192        assert_eq!(query_commitments.get_length(&no_columns_id), 0);
193
194        assert_eq!(query_commitments.get_offset(&no_rows_id), 3);
195        assert_eq!(query_commitments.get_length(&no_rows_id), 0);
196    }
197
198    #[expect(clippy::similar_names)]
199    #[test]
200    fn we_can_get_commitment_of_a_column() {
201        let column_a_id: Ident = "column_a".into();
202        let column_b_id: Ident = "column_b".into();
203
204        let table_a: OwnedTable<TestScalar> = owned_table([
205            bigint(column_a_id.value.as_str(), [1, 2, 3, 4]),
206            varchar(
207                column_b_id.value.as_str(),
208                ["Lorem", "ipsum", "dolor", "sit"],
209            ),
210        ]);
211        let table_b: OwnedTable<TestScalar> =
212            owned_table([scalar(column_a_id.value.as_str(), [1, 2])]);
213
214        let table_a_commitment =
215            TableCommitment::<NaiveCommitment>::from_owned_table_with_offset(&table_a, 2, &());
216        let table_a_id = TableRef::new("table", "a");
217
218        let table_b_commitment = TableCommitment::from_owned_table_with_offset(&table_b, 0, &());
219        let table_b_id = TableRef::new("table", "b");
220
221        let query_commitments = QueryCommitments::from_iter([
222            (table_a_id.clone(), table_a_commitment.clone()),
223            (table_b_id.clone(), table_b_commitment.clone()),
224        ]);
225
226        assert_eq!(
227            query_commitments.get_commitment(&table_a_id, &column_a_id,),
228            table_a_commitment.column_commitments().commitments()[0]
229        );
230        assert_eq!(
231            query_commitments.get_commitment(&table_a_id, &column_b_id,),
232            table_a_commitment.column_commitments().commitments()[1]
233        );
234        assert_eq!(
235            query_commitments.get_commitment(&table_b_id, &column_a_id,),
236            table_b_commitment.column_commitments().commitments()[0]
237        );
238    }
239
240    #[expect(clippy::similar_names)]
241    #[test]
242    fn we_can_get_schema_of_tables() {
243        let column_a_id: Ident = "column_a".into();
244        let column_b_id: Ident = "column_b".into();
245
246        let table_a: OwnedTable<TestScalar> = owned_table([
247            bigint(column_a_id.value.as_str(), [1, 2, 3, 4]),
248            varchar(
249                column_b_id.value.as_str(),
250                ["Lorem", "ipsum", "dolor", "sit"],
251            ),
252        ]);
253        let table_b: OwnedTable<TestScalar> =
254            owned_table([scalar(column_a_id.value.as_str(), [1, 2])]);
255
256        let table_a_commitment =
257            TableCommitment::<NaiveCommitment>::from_owned_table_with_offset(&table_a, 2, &());
258        let table_a_id = TableRef::new("table", "a");
259
260        let table_b_commitment = TableCommitment::from_owned_table_with_offset(&table_b, 0, &());
261        let table_b_id = TableRef::new("table", "b");
262
263        let no_columns_commitment = TableCommitment::try_from_columns_with_offset(
264            Vec::<(&Ident, &OwnedColumn<TestScalar>)>::new(),
265            0,
266            &(),
267        )
268        .unwrap();
269        let no_columns_id = TableRef::new("no", "columns");
270
271        let query_commitments = QueryCommitments::from_iter([
272            (table_a_id.clone(), table_a_commitment),
273            (table_b_id.clone(), table_b_commitment),
274            (no_columns_id.clone(), no_columns_commitment),
275        ]);
276
277        assert_eq!(
278            query_commitments
279                .lookup_column(&table_a_id, &column_a_id)
280                .unwrap(),
281            ColumnType::BigInt
282        );
283        assert_eq!(
284            query_commitments
285                .lookup_column(&table_a_id, &column_b_id)
286                .unwrap(),
287            ColumnType::VarChar
288        );
289        assert_eq!(
290            query_commitments.lookup_schema(&table_a_id),
291            vec![
292                (column_a_id.clone(), ColumnType::BigInt),
293                (column_b_id.clone(), ColumnType::VarChar)
294            ]
295        );
296
297        assert_eq!(
298            query_commitments
299                .lookup_column(&table_b_id, &column_a_id)
300                .unwrap(),
301            ColumnType::Scalar
302        );
303        assert_eq!(
304            query_commitments.lookup_column(&table_b_id, &column_b_id),
305            None
306        );
307        assert_eq!(
308            query_commitments.lookup_schema(&table_b_id),
309            vec![(column_a_id.clone(), ColumnType::Scalar),]
310        );
311
312        assert_eq!(
313            query_commitments.lookup_column(&no_columns_id, &column_a_id),
314            None
315        );
316        assert_eq!(query_commitments.lookup_schema(&no_columns_id), vec![]);
317    }
318
319    #[expect(clippy::similar_names)]
320    #[test]
321    fn we_can_get_query_commitments_from_accessor() {
322        let public_parameters = PublicParameters::test_rand(4, &mut test_rng());
323        let prover_setup = ProverSetup::from(&public_parameters);
324        let setup = DoryProverPublicSetup::new(&prover_setup, 3);
325
326        let column_a_id: Ident = "column_a".into();
327        let column_b_id: Ident = "column_b".into();
328
329        let table_a = owned_table([
330            bigint(column_a_id.value.as_str(), [1, 2, 3, 4]),
331            varchar(
332                column_b_id.value.as_str(),
333                ["Lorem", "ipsum", "dolor", "sit"],
334            ),
335        ]);
336        let table_b = owned_table([
337            scalar(column_a_id.value.as_str(), [1, 2]),
338            int128(column_b_id.value.as_str(), [1, 2]),
339        ]);
340
341        let mut table_a_commitment =
342            TableCommitment::from_owned_table_with_offset(&table_a, 0, &setup);
343        let table_a_id = TableRef::new("table", "a");
344        *table_a_commitment
345            .column_commitments_mut()
346            .column_metadata_mut()
347            .get_mut(&column_a_id)
348            .unwrap()
349            .bounds_mut() = ColumnBounds::BigInt(Bounds::bounded(i64::MIN, i64::MAX).unwrap());
350
351        let mut table_b_commitment =
352            TableCommitment::from_owned_table_with_offset(&table_b, 0, &setup);
353        let table_b_id = TableRef::new("table", "b");
354        *table_b_commitment
355            .column_commitments_mut()
356            .column_metadata_mut()
357            .get_mut(&column_b_id)
358            .unwrap()
359            .bounds_mut() = ColumnBounds::Int128(Bounds::bounded(i128::MIN, i128::MAX).unwrap());
360
361        let expected_query_commitments = QueryCommitments::from_iter([
362            (table_a_id.clone(), table_a_commitment.clone()),
363            (table_b_id.clone(), table_b_commitment.clone()),
364        ]);
365
366        let mut accessor =
367            OwnedTableTestAccessor::<DoryEvaluationProof>::new_empty_with_setup(setup);
368        accessor.add_table(table_a_id.clone(), table_a, 0);
369        accessor.add_table(table_b_id.clone(), table_b, 0);
370
371        let query_commitments = QueryCommitments::<DoryCommitment>::from_accessor_with_max_bounds(
372            [
373                ColumnRef::new(table_a_id.clone(), column_a_id.clone(), ColumnType::BigInt),
374                ColumnRef::new(table_b_id.clone(), column_a_id, ColumnType::Scalar),
375                ColumnRef::new(table_a_id, column_b_id.clone(), ColumnType::VarChar),
376                ColumnRef::new(table_b_id, column_b_id, ColumnType::Int128),
377            ],
378            &accessor,
379        );
380        assert_eq!(query_commitments, expected_query_commitments);
381    }
382}