proof_of_sql/utils/
parse.rs

1use crate::base::map::IndexMap;
2use alloc::{
3    string::{String, ToString},
4    vec::Vec,
5};
6use sqlparser::{
7    ast::{DataType, ExactNumberInfo, Statement},
8    dialect::GenericDialect,
9    parser::Parser,
10};
11
12#[expect(clippy::cast_possible_truncation)]
13/// Parse a DDL file and return a map of table names to bigdecimal columns
14///
15/// # Panics
16/// Panics if there is an error parsing the SQL
17#[must_use]
18pub fn find_bigdecimals(queries: &str) -> IndexMap<String, Vec<(String, u8, i8)>> {
19    let dialect = GenericDialect {};
20    let ast = Parser::parse_sql(&dialect, queries).expect("Failed to parse SQL");
21    // Find all `CREATE TABLE` statements
22    ast.iter()
23        .filter_map(|statement| match statement {
24            Statement::CreateTable { name, columns, .. } => {
25                // Find all `DECIMAL` columns where precision > 38
26                // Find the table name
27                // Add the table name and column name to the map
28                let str_name = name.to_string();
29                let big_decimal_specs: Vec<(String, u8, i8)> = columns
30                    .iter()
31                    .filter_map(|column_def| match column_def.data_type {
32                        DataType::Decimal(ExactNumberInfo::PrecisionAndScale(precision, scale))
33                            if precision > 38 =>
34                        {
35                            Some((column_def.name.to_string(), precision as u8, scale as i8))
36                        }
37                        _ => None,
38                    })
39                    .collect();
40                Some((str_name, big_decimal_specs))
41            }
42            _ => None,
43        })
44        .collect::<IndexMap<String, Vec<_>>>()
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn test_find_bigdecimals() {
53        let sql = "CREATE TABLE IF NOT EXISTS ETHEREUM.BLOCKS(
54            BLOCK_NUMBER BIGINT NOT NULL,
55            TIME_STAMP TIMESTAMP,
56            BLOCK_HASH VARCHAR,
57            MINER VARCHAR,
58            REWARD DECIMAL(78, 0),
59            SIZE_ INT,
60            GAS_USED INT,
61            GAS_LIMIT INT,
62            BASE_FEE_PER_GAS DECIMAL(78, 0),
63            TRANSACTION_COUNT INT,
64            PARENT_HASH VARCHAR,
65            PRIMARY KEY(BLOCK_NUMBER)
66          );
67          
68        CREATE TABLE IF NOT EXISTS ETHEREUM.BLOCK_DETAILS(
69            BLOCK_NUMBER BIGINT NOT NULL,
70            TIME_STAMP TIMESTAMP,
71            SHA3_UNCLES VARCHAR,
72            STATE_ROOT VARCHAR,
73            TRANSACTIONS_ROOT VARCHAR,
74            RECEIPTS_ROOT VARCHAR,
75            UNCLES_COUNT INT,
76            VERSION VARCHAR,
77            LOGS_BLOOM VARCHAR,
78            NONCE VARCHAR,
79            PRIMARY KEY(BLOCK_NUMBER)
80        );";
81        let bigdecimals = find_bigdecimals(sql);
82        assert_eq!(
83            bigdecimals.get("ETHEREUM.BLOCKS").unwrap(),
84            &[
85                ("REWARD".to_string(), 78, 0),
86                ("BASE_FEE_PER_GAS".to_string(), 78, 0)
87            ]
88        );
89        let empty_vec: Vec<(String, u8, i8)> = vec![];
90        assert_eq!(
91            bigdecimals.get("ETHEREUM.BLOCK_DETAILS").unwrap(),
92            &empty_vec
93        );
94    }
95}