diff --git a/crates/proof-of-sql-parser/Cargo.toml b/crates/proof-of-sql-parser/Cargo.toml index 29c23bc56..c0ff1a0de 100644 --- a/crates/proof-of-sql-parser/Cargo.toml +++ b/crates/proof-of-sql-parser/Cargo.toml @@ -21,7 +21,7 @@ chrono = { workspace = true, features = ["serde"] } lalrpop-util = { workspace = true, features = ["lexer", "unicode"] } serde = { workspace = true, features = ["serde_derive", "alloc"] } snafu = { workspace = true } -sqlparser = { workspace = true } +sqlparser = { workspace = true, default_features = false } [build-dependencies] lalrpop = { workspace = true } diff --git a/crates/proof-of-sql-parser/src/identifier.rs b/crates/proof-of-sql-parser/src/identifier.rs index 87ccc9de4..b11df6862 100644 --- a/crates/proof-of-sql-parser/src/identifier.rs +++ b/crates/proof-of-sql-parser/src/identifier.rs @@ -2,7 +2,6 @@ use crate::{sql::IdentifierParser, ParseError, ParseResult}; use alloc::{format, string::ToString}; use arrayvec::ArrayString; use core::{cmp::Ordering, fmt, ops::Deref, str::FromStr}; -use sqlparser::ast::Ident; /// Top-level unique identifier. #[derive(Debug, PartialEq, Eq, Clone, Hash, Ord, PartialOrd, Copy)] @@ -72,16 +71,6 @@ impl fmt::Display for Identifier { } } -// TryFrom for Identifier -impl TryFrom for Identifier { - type Error = ParseError; - - fn try_from(ident: Ident) -> ParseResult { - // Convert Ident's value to Identifier - Identifier::try_new(ident.value) - } -} - impl PartialEq for Identifier { fn eq(&self, other: &str) -> bool { other.eq_ignore_ascii_case(&self.name) @@ -289,14 +278,4 @@ mod tests { Identifier::new("t".repeat(64)); Identifier::new("茶".repeat(21)); } - - #[test] - fn try_from_ident() { - let ident = Ident::new("ValidIdentifier"); - let identifier = Identifier::try_from(ident).unwrap(); - assert_eq!(identifier.name(), "valididentifier"); - - let invalid_ident = Ident::new("INVALID$IDENTIFIER"); - assert!(Identifier::try_from(invalid_ident).is_err()); - } } diff --git a/crates/proof-of-sql-parser/src/lib.rs b/crates/proof-of-sql-parser/src/lib.rs index a600d6b97..d58de3721 100644 --- a/crates/proof-of-sql-parser/src/lib.rs +++ b/crates/proof-of-sql-parser/src/lib.rs @@ -16,6 +16,8 @@ mod intermediate_ast_tests; /// Shortcuts to construct intermediate AST nodes. pub mod utility; +pub mod sqlparser; + /// TODO: add docs pub(crate) mod select_statement; pub use select_statement::SelectStatement; diff --git a/crates/proof-of-sql-parser/src/resource_id.rs b/crates/proof-of-sql-parser/src/resource_id.rs index 10045d03b..2fe0d0848 100644 --- a/crates/proof-of-sql-parser/src/resource_id.rs +++ b/crates/proof-of-sql-parser/src/resource_id.rs @@ -3,13 +3,11 @@ use crate::{impl_serde_from_str, sql::ResourceIdParser, Identifier, ParseError, use alloc::{ format, string::{String, ToString}, - vec::Vec, }; use core::{ fmt::{self, Display}, str::FromStr, }; -use sqlparser::ast::Ident; /// Unique resource identifier, like `schema.object_name`. #[derive(Debug, PartialEq, Eq, Clone, Hash, Copy)] @@ -112,22 +110,6 @@ impl FromStr for ResourceId { } impl_serde_from_str!(ResourceId); -impl TryFrom> for ResourceId { - type Error = ParseError; - - fn try_from(identifiers: Vec) -> ParseResult { - if identifiers.len() != 2 { - return Err(ParseError::ResourceIdParseError { - error: "Expected exactly two identifiers for ResourceId".to_string(), - }); - } - - let schema = Identifier::try_from(identifiers[0].clone())?; - let object_name = Identifier::try_from(identifiers[1].clone())?; - Ok(ResourceId::new(schema, object_name)) - } -} - #[cfg(test)] mod tests { use super::*; @@ -251,15 +233,4 @@ mod tests { serde_json::from_str(r#""good_identifier.bad!identifier"#); assert!(deserialized.is_err()); } - - #[test] - fn test_try_from_vec_ident() { - let identifiers = alloc::vec![Ident::new("schema_name"), Ident::new("object_name")]; - let resource_id = ResourceId::try_from(identifiers).unwrap(); - assert_eq!(resource_id.schema().name(), "schema_name"); - assert_eq!(resource_id.object_name().name(), "object_name"); - - let invalid_identifiers = alloc::vec![Ident::new("only_one_ident")]; - assert!(ResourceId::try_from(invalid_identifiers).is_err()); - } } diff --git a/crates/proof-of-sql-parser/src/sqlparser.rs b/crates/proof-of-sql-parser/src/sqlparser.rs new file mode 100644 index 000000000..7bbf67b70 --- /dev/null +++ b/crates/proof-of-sql-parser/src/sqlparser.rs @@ -0,0 +1,210 @@ +use crate::{ + intermediate_ast::{OrderBy as IntermediateOrderBy, OrderByDirection, Slice}, + Identifier, ResourceId, +}; +use alloc::{string::ToString, vec}; +use sqlparser::ast::{Expr, Ident, ObjectName, Offset, OffsetRows, OrderByExpr, Value}; + +/// Converts a [`Identifier`] from the PoSQL AST to an `Ident` for the SQLParser AST. +impl From for Ident { + fn from(id: Identifier) -> Self { + Ident::new(id.as_str()) + } +} + +/// Converts a [`ResourceId`] from the PoSQL AST to an [`ObjectName`] for the SQLParser AST. +impl From for ObjectName { + fn from(resource_id: ResourceId) -> Self { + let schema_ident = Ident::new(resource_id.schema().as_str()); + let object_name_ident = Ident::new(resource_id.object_name().as_str()); + ObjectName(vec![schema_ident, object_name_ident]) + } +} + +/// Converts an [`IntermediateOrderBy`] from the intermediate AST to a [`OrderByExpr`] for the SQLParser AST. +impl From for OrderByExpr { + fn from(intermediate_order_by: IntermediateOrderBy) -> Self { + // Convert Identifier to Expr + let expr = Expr::Identifier(intermediate_order_by.expr.into()); + + let asc = match intermediate_order_by.direction { + OrderByDirection::Asc => Some(true), + OrderByDirection::Desc => Some(false), + }; + + // Create the OrderByExpr + OrderByExpr { + expr, + asc, + nulls_first: None, + } + } +} + +/// Converts a [`Slice`] representing pagination into an [`Offset`] for the SQL parser. +impl From for Offset { + fn from(slice: Slice) -> Self { + let value_expr = Expr::Value(Value::Number(slice.offset_value.to_string(), false)); + + let rows = match slice.number_rows { + u64::MAX => OffsetRows::None, // No specific row offset + 1 => OffsetRows::Row, // For a single row offset + _ => OffsetRows::Rows, // For multiple rows + }; + + Offset { + value: value_expr, + rows, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_identifier_conversion() { + let test_cases = vec![ + ("ValidIdentifier", "valididentifier"), + ("INVALID$IDENTIFIER", "invalid$identifier"), + ("MiXeDCaSeIdentifier", "mixedcaseidentifier"), + ("", ""), + ]; + + for (input, expected) in test_cases { + let identifier = Identifier::new(input); + let ident: Ident = identifier.into(); + assert_eq!(ident.value, expected); + } + } + + #[test] + fn test_conversion_order_by_asc() { + let intermediate_order_by = IntermediateOrderBy { + expr: Identifier::new("column_name"), + direction: OrderByDirection::Asc, + }; + + let order_by_expr: OrderByExpr = OrderByExpr::from(intermediate_order_by); + + assert_eq!(order_by_expr.asc, Some(true)); + assert_eq!( + order_by_expr.expr, + Expr::Identifier(Ident::new("column_name")) + ); + } + + #[test] + fn test_conversion_order_by_desc() { + let intermediate_order_by = IntermediateOrderBy { + expr: Identifier::new("column_name"), + direction: OrderByDirection::Desc, + }; + + let order_by_expr: OrderByExpr = OrderByExpr::from(intermediate_order_by); + + assert_eq!(order_by_expr.asc, Some(false)); + assert_eq!( + order_by_expr.expr, + Expr::Identifier(Ident::new("column_name")) + ); + } + + #[test] + fn test_conversion_order_by_nulls_first() { + let intermediate_order_by = IntermediateOrderBy { + expr: Identifier::new("column_name"), + direction: OrderByDirection::Asc, + }; + + let order_by_expr: OrderByExpr = OrderByExpr::from(intermediate_order_by); + + assert_eq!(order_by_expr.nulls_first, None); + } + + #[test] + fn test_edge_case_empty_order_by() { + let intermediate_order_by = IntermediateOrderBy { + expr: Identifier::new(""), + direction: OrderByDirection::Asc, + }; + + let order_by_expr: OrderByExpr = OrderByExpr::from(intermediate_order_by); + + assert_eq!(order_by_expr.asc, Some(true)); + assert_eq!(order_by_expr.expr, Expr::Identifier(Ident::new(""))); + } + + #[test] + fn test_slice_to_offset_conversion_all_rows() { + let slice = Slice { + number_rows: u64::MAX, + offset_value: 0, + }; + let offset: Offset = slice.into(); + + assert_eq!( + offset.value, + Expr::Value(Value::Number("0".to_string(), false)) + ); + assert_eq!(offset.rows, OffsetRows::None); + } + + #[test] + fn test_slice_to_offset_conversion_single_row() { + let slice = Slice { + number_rows: 1, + offset_value: 5, + }; + let offset: Offset = slice.into(); + + assert_eq!( + offset.value, + Expr::Value(Value::Number("5".to_string(), false)) + ); + assert_eq!(offset.rows, OffsetRows::Row); + } + + #[test] + fn test_slice_to_offset_conversion_multiple_rows() { + let slice = Slice { + number_rows: 10, + offset_value: -2, + }; + let offset: Offset = slice.into(); + + assert_eq!( + offset.value, + Expr::Value(Value::Number("-2".to_string(), false)) + ); + assert_eq!(offset.rows, OffsetRows::Rows); + } + + #[test] + fn test_slice_to_offset_conversion_zero_offset() { + let slice = Slice { + number_rows: 10, + offset_value: 0, + }; + let offset: Offset = slice.into(); + + assert_eq!( + offset.value, + Expr::Value(Value::Number("0".to_string(), false)) + ); + assert_eq!(offset.rows, OffsetRows::Rows); + } + + #[test] + fn test_resource_id_to_object_name_conversion() { + let resource_id = + ResourceId::new(Identifier::new("my_schema"), Identifier::new("my_table")); + + let object_name: ObjectName = resource_id.into(); + + assert_eq!(object_name.0.len(), 2); // Should have two parts + assert_eq!(object_name.0[0].value, "my_schema"); + assert_eq!(object_name.0[1].value, "my_table"); + } +}