Skip to content

Commit

Permalink
feat!: Implement conversions for IntermediateOrderBy, Identifier, Res…
Browse files Browse the repository at this point in the history
…ourceId and Slice to SQLPARSER AST
  • Loading branch information
varshith257 committed Nov 6, 2024
1 parent d66e769 commit c5276b3
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 51 deletions.
2 changes: 1 addition & 1 deletion crates/proof-of-sql-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
21 changes: 0 additions & 21 deletions crates/proof-of-sql-parser/src/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -72,16 +71,6 @@ impl fmt::Display for Identifier {
}
}

// TryFrom<Ident> for Identifier
impl TryFrom<Ident> for Identifier {
type Error = ParseError;

fn try_from(ident: Ident) -> ParseResult<Self> {
// Convert Ident's value to Identifier
Identifier::try_new(ident.value)
}
}

impl PartialEq<str> for Identifier {
fn eq(&self, other: &str) -> bool {
other.eq_ignore_ascii_case(&self.name)
Expand Down Expand Up @@ -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());
}
}
2 changes: 2 additions & 0 deletions crates/proof-of-sql-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
29 changes: 0 additions & 29 deletions crates/proof-of-sql-parser/src/resource_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -112,22 +110,6 @@ impl FromStr for ResourceId {
}
impl_serde_from_str!(ResourceId);

impl TryFrom<Vec<Ident>> for ResourceId {
type Error = ParseError;

fn try_from(identifiers: Vec<Ident>) -> ParseResult<Self> {
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::*;
Expand Down Expand Up @@ -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());
}
}
210 changes: 210 additions & 0 deletions crates/proof-of-sql-parser/src/sqlparser.rs
Original file line number Diff line number Diff line change
@@ -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<Identifier> 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<ResourceId> 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<IntermediateOrderBy> 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<Slice> 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");
}
}

0 comments on commit c5276b3

Please sign in to comment.