Skip to main content

proof_of_sql/base/database/
accessor.rs

1use crate::base::{
2    commitment::Commitment,
3    database::{Column, ColumnType, Table, TableOptions, TableRef},
4    map::{IndexMap, IndexSet},
5    scalar::Scalar,
6};
7use alloc::vec::Vec;
8use sqlparser::ast::Ident;
9
10/// Access metadata of a table span in a database.
11///
12/// Both Prover and Verifier use this information when processing a query.
13///
14/// Note: we assume that the query has already been validated so that we
15/// will only be accessing information about tables that exist in the database.
16pub trait MetadataAccessor {
17    /// Return the data span's length in the table (not the full table length)
18    fn get_length(&self, table_ref: &TableRef) -> usize;
19
20    /// Return the data span's offset in the table
21    ///
22    /// If the data span has its first row starting at the ith table row,
23    /// this `get_offset` should then return `i`.
24    fn get_offset(&self, table_ref: &TableRef) -> usize;
25}
26
27/// Access commitments of database columns.
28///
29/// Verifier uses this information to process a query.
30///
31/// In pseudo-code, here is a sketch of how [`CommitmentAccessor`] fits in
32/// with the verification workflow:
33///
34/// ```ignore
35/// verify(proof, query, commitment_database) {
36///     if(!validate_query(query, commitment_database)) {
37///         // if the query references columns that don't exist
38///         // we should error here before going any further
39///         return invalid-query()
40///     }
41///     commitment_database.reader_lock()
42///     // we can't be updating commitments while verifying
43///     accessor <- make-commitment-accessor(commitment_database)
44///     verify_result <- verify-valid-query(proof, query, accessor)
45///     commitment_database.reader_unlock()
46///     return verify_result
47/// }
48/// ```
49///
50/// Note: we assume that the query has already been validated so that we
51/// will only be accessing information about columns that exist in the database.
52pub trait CommitmentAccessor<C: Commitment>: MetadataAccessor {
53    /// Return the full table column commitment
54    fn get_commitment(&self, table_ref: &TableRef, column_id: &Ident) -> C;
55}
56
57/// Access database columns of an in-memory table span.
58///
59/// Prover uses this information to process a query.
60///
61/// In pseudo-code, here is a sketch of how [`DataAccessor`] fits in
62/// with the prove workflow:
63///
64/// ```ignore
65/// prove(query, database) {
66///       if(!validate_query(query, database)) {
67///           // if the query references columns that don't exist
68///           // we should error here before going any further
69///           invalid-query()
70///       }
71///       update-cached-columns(database, query)
72///            // if the database represents an in-memory cache of an externally persisted
73///            // database we should update the cache so that any column referenced in the query
74///            // will be available
75///       database.reader_lock()
76///           // we can't be updating the database while proving
77///       accessor <- make-data-accessor(database)
78///       proof <- prove-valid-query(query, accessor)
79///       database.reader_unlock()
80///       return proof
81/// }
82/// ```
83///
84/// Note: we assume that the query has already been validated so that we
85/// will only be accessing information about columns that exist in the database.
86pub trait DataAccessor<S: Scalar>: MetadataAccessor {
87    /// Return the data span in the table (not the full-table data)
88    fn get_column(&self, table_ref: &TableRef, column_id: &Ident) -> Column<'_, S>;
89
90    /// Creates a new [`Table`] from a [`TableRef`] and [`Ident`]s.
91    ///
92    /// Columns are retrieved from the [`DataAccessor`] using the provided [`TableRef`] and [`Ident`]s.
93    /// The only reason why [`table_ref`] is needed is because [`column_ids`] can be empty.
94    /// # Panics
95    /// Column length mismatches can occur in theory. In practice, this should not happen.
96    fn get_table(&self, table_ref: &TableRef, column_ids: &IndexSet<Ident>) -> Table<'_, S> {
97        if column_ids.is_empty() {
98            let input_length = self.get_length(table_ref);
99            Table::<S>::try_new_with_options(
100                IndexMap::default(),
101                TableOptions::new(Some(input_length)),
102            )
103        } else {
104            Table::<S>::try_from_iter(column_ids.into_iter().map(|column_id| {
105                let column = self.get_column(table_ref, column_id);
106                (column_id.clone(), column)
107            }))
108        }
109        .expect("Failed to create table from table and column references")
110    }
111}
112
113/// Access tables and their schemas in a database.
114///
115/// This accessor should be implemented by both the prover and verifier
116/// and then used by the Proof of SQL code to convert an `IntermediateAst`
117/// into a [`ProofPlan`](crate::sql::proof::ProofPlan).
118pub trait SchemaAccessor {
119    /// Lookup the column's data type in the specified table
120    ///
121    /// Return:
122    ///   - Some(type) if the column exists, where `type` is the column's data type
123    ///   - None in case the column does not exist in the table
124    ///
125    /// Precondition 1: the table must exist and be tamperproof.
126    /// Precondition 2: `table_ref` and `column_id` must always be lowercase.
127    fn lookup_column(&self, table_ref: &TableRef, column_id: &Ident) -> Option<ColumnType>;
128
129    /// Lookup all the column names and their data types in the specified table
130    ///
131    /// Return:
132    ///   - The list of column names with their data types
133    ///
134    /// Precondition 1: the table must exist and be tamperproof.
135    /// Precondition 2: `table_name` must be lowercase.
136    fn lookup_schema(&self, table_ref: &TableRef) -> Vec<(Ident, ColumnType)>;
137}
138
139/// The simplest implementation of `SchemaAccessor`.
140/// This is effectively an in-memory mapping from table to the schema.
141#[derive(Clone)]
142pub struct SchemaAccessorImpl {
143    table_schema_lookup: IndexMap<TableRef, Vec<(Ident, ColumnType)>>,
144}
145
146impl SchemaAccessorImpl {
147    /// Constructs a new `SchemaAccessorImpl` implementation
148    #[must_use]
149    pub fn new(table_schema_lookup: IndexMap<TableRef, Vec<(Ident, ColumnType)>>) -> Self {
150        Self {
151            table_schema_lookup,
152        }
153    }
154}
155
156impl SchemaAccessor for SchemaAccessorImpl {
157    /// # Panics
158    ///
159    /// Panics if the table does not exist
160    fn lookup_column(&self, table_ref: &TableRef, column_id: &Ident) -> Option<ColumnType> {
161        self.table_schema_lookup
162            .get(table_ref)
163            .expect("Table does not exist in schema accessor.")
164            .iter()
165            .find_map(|(id, column_type)| (id == column_id).then_some(*column_type))
166    }
167
168    /// # Panics
169    ///
170    /// Panics if the table does not exist
171    fn lookup_schema(&self, table_ref: &TableRef) -> Vec<(Ident, ColumnType)> {
172        self.table_schema_lookup
173            .get(table_ref)
174            .expect("Table does not exist in schema accessor.")
175            .clone()
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::base::map::indexmap;
183
184    fn sample_schema_accessor() -> SchemaAccessorImpl {
185        let table1 = TableRef::new("schema", "table1");
186        let table2 = TableRef::new("schema", "table2");
187        SchemaAccessorImpl::new(indexmap! {
188            table1 => vec![("col1".into(), ColumnType::BigInt),
189                ("col2".into(), ColumnType::VarChar)],
190            table2 => vec![("col1".into(), ColumnType::BigInt)],
191        })
192    }
193
194    #[test]
195    fn test_lookup_column() {
196        let accessor = sample_schema_accessor();
197        let table1 = TableRef::new("schema", "table1");
198        let table2 = TableRef::new("schema", "table2");
199        assert_eq!(
200            accessor.lookup_column(&table1, &"col1".into()),
201            Some(ColumnType::BigInt)
202        );
203        assert_eq!(
204            accessor.lookup_column(&table1, &"col2".into()),
205            Some(ColumnType::VarChar)
206        );
207        assert_eq!(accessor.lookup_column(&table1, &"not_a_col".into()), None);
208        assert_eq!(
209            accessor.lookup_column(&table2, &"col1".into()),
210            Some(ColumnType::BigInt)
211        );
212        assert_eq!(accessor.lookup_column(&table2, &"col2".into()), None);
213    }
214
215    #[test]
216    fn test_lookup_non_existent_column_on_existing_table() {
217        let accessor = sample_schema_accessor();
218        let table1 = TableRef::new("schema", "table1");
219        assert_eq!(accessor.lookup_column(&table1, &"col3".into()), None);
220    }
221
222    #[test]
223    #[should_panic(expected = "Table does not exist in schema accessor.")]
224    fn test_lookup_column_on_non_existent_table() {
225        let accessor = sample_schema_accessor();
226        let not_a_table = TableRef::new("schema", "not_a_table");
227        accessor.lookup_column(&not_a_table, &"col1".into());
228    }
229
230    #[test]
231    fn test_lookup_schema() {
232        let accessor = sample_schema_accessor();
233        let table1 = TableRef::new("schema", "table1");
234        let table2 = TableRef::new("schema", "table2");
235        assert_eq!(
236            accessor.lookup_schema(&table1),
237            vec![
238                ("col1".into(), ColumnType::BigInt),
239                ("col2".into(), ColumnType::VarChar),
240            ]
241        );
242        assert_eq!(
243            accessor.lookup_schema(&table2),
244            vec![("col1".into(), ColumnType::BigInt),]
245        );
246    }
247
248    #[test]
249    #[should_panic(expected = "Table does not exist in schema accessor.")]
250    fn test_lookup_non_existent_schema() {
251        let accessor = sample_schema_accessor();
252        let not_a_table = TableRef::new("schema", "not_a_table");
253        accessor.lookup_schema(&not_a_table);
254    }
255}