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.clone())
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, column: ColumnRef) -> C {
89        let table_commitment = self.get(&column.table_ref()).unwrap();
90
91        table_commitment
92            .column_commitments()
93            .get_commitment(&column.column_id())
94            .unwrap()
95    }
96}
97
98impl<C: Commitment> SchemaAccessor for QueryCommitments<C> {
99    fn lookup_column(
100        &self,
101        table_ref: crate::base::database::TableRef,
102        column_id: Ident,
103    ) -> Option<ColumnType> {
104        let table_commitment = self.get(&table_ref)?;
105
106        table_commitment
107            .column_commitments()
108            .get_metadata(&column_id)
109            .map(|column_metadata| *column_metadata.column_type())
110    }
111
112    /// # Panics
113    ///
114    /// Panics if the column metadata cannot be found.
115    fn lookup_schema(
116        &self,
117        table_ref: crate::base::database::TableRef,
118    ) -> Vec<(Ident, ColumnType)> {
119        let table_commitment = self.get(&table_ref).unwrap();
120
121        table_commitment
122            .column_commitments()
123            .column_metadata()
124            .iter()
125            .map(|(identifier, column_metadata)| {
126                (identifier.clone(), *column_metadata.column_type())
127            })
128            .collect()
129    }
130}
131
132#[cfg(all(test, feature = "blitzar"))]
133mod tests {
134    use super::*;
135    use crate::{
136        base::{
137            commitment::{naive_commitment::NaiveCommitment, Bounds, ColumnBounds},
138            database::{
139                owned_table_utility::*, OwnedColumn, OwnedTable, OwnedTableTestAccessor,
140                TestAccessor,
141            },
142            scalar::test_scalar::TestScalar,
143        },
144        proof_primitive::dory::{
145            test_rng, DoryCommitment, DoryEvaluationProof, DoryProverPublicSetup, ProverSetup,
146            PublicParameters,
147        },
148    };
149
150    #[test]
151    fn we_can_get_length_and_offset_of_tables() {
152        let table_a: OwnedTable<TestScalar> = owned_table([
153            bigint("column_a", [1, 2, 3, 4]),
154            varchar("column_b", ["Lorem", "ipsum", "dolor", "sit"]),
155        ]);
156
157        let table_b: OwnedTable<TestScalar> = owned_table([scalar("column_c", [1, 2])]);
158
159        let offset_commitment =
160            TableCommitment::<NaiveCommitment>::from_owned_table_with_offset(&table_a, 2, &());
161        let offset_table_id = TableRef::new("off", "table");
162
163        let no_offset_commitment = TableCommitment::from_owned_table_with_offset(&table_b, 0, &());
164        let no_offset_id = TableRef::new("no", "off");
165
166        let no_columns_commitment = TableCommitment::try_from_columns_with_offset(
167            Vec::<(&Ident, &OwnedColumn<TestScalar>)>::new(),
168            0,
169            &(),
170        )
171        .unwrap();
172        let no_columns_id = TableRef::new("no", "columns");
173
174        let no_rows_commitment = TableCommitment::try_from_columns_with_offset(
175            [(
176                &"column_c".into(),
177                &OwnedColumn::<TestScalar>::BigInt(vec![]),
178            )],
179            3,
180            &(),
181        )
182        .unwrap();
183        let no_rows_id = TableRef::new("no", "rows");
184
185        let query_commitments = QueryCommitments::from_iter([
186            (offset_table_id.clone(), offset_commitment),
187            (no_offset_id.clone(), no_offset_commitment),
188            (no_columns_id.clone(), no_columns_commitment),
189            (no_rows_id.clone(), no_rows_commitment),
190        ]);
191
192        assert_eq!(query_commitments.get_offset(&offset_table_id), 2);
193        assert_eq!(query_commitments.get_length(&offset_table_id), 4);
194
195        assert_eq!(query_commitments.get_offset(&no_offset_id), 0);
196        assert_eq!(query_commitments.get_length(&no_offset_id), 2);
197
198        assert_eq!(query_commitments.get_offset(&no_columns_id), 0);
199        assert_eq!(query_commitments.get_length(&no_columns_id), 0);
200
201        assert_eq!(query_commitments.get_offset(&no_rows_id), 3);
202        assert_eq!(query_commitments.get_length(&no_rows_id), 0);
203    }
204
205    #[expect(clippy::similar_names)]
206    #[test]
207    fn we_can_get_commitment_of_a_column() {
208        let column_a_id: Ident = "column_a".into();
209        let column_b_id: Ident = "column_b".into();
210
211        let table_a: OwnedTable<TestScalar> = owned_table([
212            bigint(column_a_id.value.as_str(), [1, 2, 3, 4]),
213            varchar(
214                column_b_id.value.as_str(),
215                ["Lorem", "ipsum", "dolor", "sit"],
216            ),
217        ]);
218        let table_b: OwnedTable<TestScalar> =
219            owned_table([scalar(column_a_id.value.as_str(), [1, 2])]);
220
221        let table_a_commitment =
222            TableCommitment::<NaiveCommitment>::from_owned_table_with_offset(&table_a, 2, &());
223        let table_a_id = TableRef::new("table", "a");
224
225        let table_b_commitment = TableCommitment::from_owned_table_with_offset(&table_b, 0, &());
226        let table_b_id = TableRef::new("table", "b");
227
228        let query_commitments = QueryCommitments::from_iter([
229            (table_a_id.clone(), table_a_commitment.clone()),
230            (table_b_id.clone(), table_b_commitment.clone()),
231        ]);
232
233        assert_eq!(
234            query_commitments.get_commitment(ColumnRef::new(
235                table_a_id.clone(),
236                column_a_id.clone(),
237                ColumnType::BigInt
238            )),
239            table_a_commitment.column_commitments().commitments()[0]
240        );
241        assert_eq!(
242            query_commitments.get_commitment(ColumnRef::new(
243                table_a_id,
244                column_b_id,
245                ColumnType::VarChar
246            )),
247            table_a_commitment.column_commitments().commitments()[1]
248        );
249        assert_eq!(
250            query_commitments.get_commitment(ColumnRef::new(
251                table_b_id,
252                column_a_id,
253                ColumnType::Scalar
254            )),
255            table_b_commitment.column_commitments().commitments()[0]
256        );
257    }
258
259    #[expect(clippy::similar_names)]
260    #[test]
261    fn we_can_get_schema_of_tables() {
262        let column_a_id: Ident = "column_a".into();
263        let column_b_id: Ident = "column_b".into();
264
265        let table_a: OwnedTable<TestScalar> = owned_table([
266            bigint(column_a_id.value.as_str(), [1, 2, 3, 4]),
267            varchar(
268                column_b_id.value.as_str(),
269                ["Lorem", "ipsum", "dolor", "sit"],
270            ),
271        ]);
272        let table_b: OwnedTable<TestScalar> =
273            owned_table([scalar(column_a_id.value.as_str(), [1, 2])]);
274
275        let table_a_commitment =
276            TableCommitment::<NaiveCommitment>::from_owned_table_with_offset(&table_a, 2, &());
277        let table_a_id = TableRef::new("table", "a");
278
279        let table_b_commitment = TableCommitment::from_owned_table_with_offset(&table_b, 0, &());
280        let table_b_id = TableRef::new("table", "b");
281
282        let no_columns_commitment = TableCommitment::try_from_columns_with_offset(
283            Vec::<(&Ident, &OwnedColumn<TestScalar>)>::new(),
284            0,
285            &(),
286        )
287        .unwrap();
288        let no_columns_id = TableRef::new("no", "columns");
289
290        let query_commitments = QueryCommitments::from_iter([
291            (table_a_id.clone(), table_a_commitment),
292            (table_b_id.clone(), table_b_commitment),
293            (no_columns_id.clone(), no_columns_commitment),
294        ]);
295
296        assert_eq!(
297            query_commitments
298                .lookup_column(table_a_id.clone(), column_a_id.clone())
299                .unwrap(),
300            ColumnType::BigInt
301        );
302        assert_eq!(
303            query_commitments
304                .lookup_column(table_a_id.clone(), column_b_id.clone())
305                .unwrap(),
306            ColumnType::VarChar
307        );
308        assert_eq!(
309            query_commitments.lookup_schema(table_a_id),
310            vec![
311                (column_a_id.clone(), ColumnType::BigInt),
312                (column_b_id.clone(), ColumnType::VarChar)
313            ]
314        );
315
316        assert_eq!(
317            query_commitments
318                .lookup_column(table_b_id.clone(), column_a_id.clone())
319                .unwrap(),
320            ColumnType::Scalar
321        );
322        assert_eq!(
323            query_commitments.lookup_column(table_b_id.clone(), column_b_id),
324            None
325        );
326        assert_eq!(
327            query_commitments.lookup_schema(table_b_id),
328            vec![(column_a_id.clone(), ColumnType::Scalar),]
329        );
330
331        assert_eq!(
332            query_commitments.lookup_column(no_columns_id.clone(), column_a_id),
333            None
334        );
335        assert_eq!(query_commitments.lookup_schema(no_columns_id), vec![]);
336    }
337
338    #[expect(clippy::similar_names)]
339    #[test]
340    fn we_can_get_query_commitments_from_accessor() {
341        let public_parameters = PublicParameters::test_rand(4, &mut test_rng());
342        let prover_setup = ProverSetup::from(&public_parameters);
343        let setup = DoryProverPublicSetup::new(&prover_setup, 3);
344
345        let column_a_id: Ident = "column_a".into();
346        let column_b_id: Ident = "column_b".into();
347
348        let table_a = owned_table([
349            bigint(column_a_id.value.as_str(), [1, 2, 3, 4]),
350            varchar(
351                column_b_id.value.as_str(),
352                ["Lorem", "ipsum", "dolor", "sit"],
353            ),
354        ]);
355        let table_b = owned_table([
356            scalar(column_a_id.value.as_str(), [1, 2]),
357            int128(column_b_id.value.as_str(), [1, 2]),
358        ]);
359
360        let mut table_a_commitment =
361            TableCommitment::from_owned_table_with_offset(&table_a, 0, &setup);
362        let table_a_id = TableRef::new("table", "a");
363        *table_a_commitment
364            .column_commitments_mut()
365            .column_metadata_mut()
366            .get_mut(&column_a_id)
367            .unwrap()
368            .bounds_mut() = ColumnBounds::BigInt(Bounds::bounded(i64::MIN, i64::MAX).unwrap());
369
370        let mut table_b_commitment =
371            TableCommitment::from_owned_table_with_offset(&table_b, 0, &setup);
372        let table_b_id = TableRef::new("table", "b");
373        *table_b_commitment
374            .column_commitments_mut()
375            .column_metadata_mut()
376            .get_mut(&column_b_id)
377            .unwrap()
378            .bounds_mut() = ColumnBounds::Int128(Bounds::bounded(i128::MIN, i128::MAX).unwrap());
379
380        let expected_query_commitments = QueryCommitments::from_iter([
381            (table_a_id.clone(), table_a_commitment.clone()),
382            (table_b_id.clone(), table_b_commitment.clone()),
383        ]);
384
385        let mut accessor =
386            OwnedTableTestAccessor::<DoryEvaluationProof>::new_empty_with_setup(setup);
387        accessor.add_table(table_a_id.clone(), table_a, 0);
388        accessor.add_table(table_b_id.clone(), table_b, 0);
389
390        let query_commitments = QueryCommitments::<DoryCommitment>::from_accessor_with_max_bounds(
391            [
392                ColumnRef::new(table_a_id.clone(), column_a_id.clone(), ColumnType::BigInt),
393                ColumnRef::new(table_b_id.clone(), column_a_id, ColumnType::Scalar),
394                ColumnRef::new(table_a_id, column_b_id.clone(), ColumnType::VarChar),
395                ColumnRef::new(table_b_id, column_b_id, ColumnType::Int128),
396            ],
397            &accessor,
398        );
399        assert_eq!(query_commitments, expected_query_commitments);
400    }
401}