diff --git a/Cargo.toml b/Cargo.toml index 6c23daf15..c86f50742 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ rayon = { version = "1.5" } serde = { version = "1", default-features = false } serde_json = { version = "1", default-features = false, features = ["alloc"] } snafu = { version = "0.8.4", default-features = false } +sqlparser = { version = "0.45.0", default-features = false } tiny-keccak = { version = "2.0.2", features = [ "keccak" ] } tracing = { version = "0.1.36", default-features = false } tracing-opentelemetry = { version = "0.22.0" } diff --git a/crates/proof-of-sql/Cargo.toml b/crates/proof-of-sql/Cargo.toml index 4cbdd894e..0f4e8279e 100644 --- a/crates/proof-of-sql/Cargo.toml +++ b/crates/proof-of-sql/Cargo.toml @@ -45,6 +45,7 @@ rayon = { workspace = true, optional = true } serde = { workspace = true, features = ["serde_derive"] } serde_json = { workspace = true } snafu = { workspace = true } +sqlparser = { workspace = true } tiny-keccak = { workspace = true } tracing = { workspace = true, features = ["attributes"] } zerocopy = { workspace = true } diff --git a/crates/proof-of-sql/src/lib.rs b/crates/proof-of-sql/src/lib.rs index d16905bdd..6595f74a2 100644 --- a/crates/proof-of-sql/src/lib.rs +++ b/crates/proof-of-sql/src/lib.rs @@ -8,6 +8,8 @@ extern crate alloc; pub mod base; pub mod proof_primitive; pub mod sql; +/// Utilities for working with the library +pub mod utils; #[cfg(test)] mod tests; diff --git a/crates/proof-of-sql/src/utils/mod.rs b/crates/proof-of-sql/src/utils/mod.rs new file mode 100644 index 000000000..888bd1de9 --- /dev/null +++ b/crates/proof-of-sql/src/utils/mod.rs @@ -0,0 +1,3 @@ +//! This module contains utilities for working with the library +/// Parse DDLs and find bigdecimal columns +pub mod parse; diff --git a/crates/proof-of-sql/src/utils/parse.rs b/crates/proof-of-sql/src/utils/parse.rs new file mode 100644 index 000000000..caeda9a6e --- /dev/null +++ b/crates/proof-of-sql/src/utils/parse.rs @@ -0,0 +1,94 @@ +use crate::base::map::IndexMap; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use sqlparser::{ + ast::{DataType, ExactNumberInfo, Statement}, + dialect::GenericDialect, + parser::Parser, +}; + +/// Parse a DDL file and return a map of table names to bigdecimal columns +/// +/// # Panics +/// Panics if there is an error parsing the SQL +#[must_use] +pub fn find_bigdecimals(queries: &str) -> IndexMap> { + let dialect = GenericDialect {}; + let ast = Parser::parse_sql(&dialect, queries).expect("Failed to parse SQL"); + // Find all `CREATE TABLE` statements + ast.iter() + .filter_map(|statement| match statement { + Statement::CreateTable { name, columns, .. } => { + // Find all `DECIMAL` columns where precision > 38 + // Find the table name + // Add the table name and column name to the map + let str_name = name.to_string(); + let big_decimal_specs: Vec<(String, u8, i8)> = columns + .iter() + .filter_map(|column_def| match column_def.data_type { + DataType::Decimal(ExactNumberInfo::PrecisionAndScale(precision, scale)) + if precision > 38 => + { + Some((column_def.name.to_string(), precision as u8, scale as i8)) + } + _ => None, + }) + .collect(); + Some((str_name, big_decimal_specs)) + } + _ => None, + }) + .collect::>>() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_bigdecimals() { + let sql = "CREATE TABLE IF NOT EXISTS ETHEREUM.BLOCKS( + BLOCK_NUMBER BIGINT NOT NULL, + TIME_STAMP TIMESTAMP, + BLOCK_HASH VARCHAR, + MINER VARCHAR, + REWARD DECIMAL(78, 0), + SIZE_ INT, + GAS_USED INT, + GAS_LIMIT INT, + BASE_FEE_PER_GAS DECIMAL(78, 0), + TRANSACTION_COUNT INT, + PARENT_HASH VARCHAR, + PRIMARY KEY(BLOCK_NUMBER) + ); + + CREATE TABLE IF NOT EXISTS ETHEREUM.BLOCK_DETAILS( + BLOCK_NUMBER BIGINT NOT NULL, + TIME_STAMP TIMESTAMP, + SHA3_UNCLES VARCHAR, + STATE_ROOT VARCHAR, + TRANSACTIONS_ROOT VARCHAR, + RECEIPTS_ROOT VARCHAR, + UNCLES_COUNT INT, + VERSION VARCHAR, + LOGS_BLOOM VARCHAR, + NONCE VARCHAR, + PRIMARY KEY(BLOCK_NUMBER) + );"; + let bigdecimals = find_bigdecimals(sql); + assert_eq!( + bigdecimals.get("ETHEREUM.BLOCKS").unwrap(), + &[ + ("REWARD".to_string(), 78, 0), + ("BASE_FEE_PER_GAS".to_string(), 78, 0) + ] + ); + let empty_vec: Vec<(String, u8, i8)> = vec![]; + assert_eq!( + bigdecimals.get("ETHEREUM.BLOCK_DETAILS").unwrap(), + &empty_vec + ); + } +}