mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-09 21:42:05 +00:00
Factor test helpers into a common module
Also run "generic" tests with all dialects (`parse_select_version` doesn't work with ANSI dialect, so I moved it to the postgres file temporarily)
This commit is contained in:
parent
de177f107c
commit
478dbe940d
8 changed files with 197 additions and 114 deletions
|
@ -1,5 +1,6 @@
|
|||
use crate::dialect::Dialect;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnsiSqlDialect {}
|
||||
|
||||
impl Dialect for AnsiSqlDialect {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::dialect::Dialect;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenericSqlDialect {}
|
||||
|
||||
impl Dialect for GenericSqlDialect {
|
||||
|
|
|
@ -3,11 +3,13 @@ mod generic_sql;
|
|||
pub mod keywords;
|
||||
mod postgresql;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub use self::ansi_sql::AnsiSqlDialect;
|
||||
pub use self::generic_sql::GenericSqlDialect;
|
||||
pub use self::postgresql::PostgreSqlDialect;
|
||||
|
||||
pub trait Dialect {
|
||||
pub trait Dialect: Debug {
|
||||
/// Determine if a character starts a quoted identifier. The default
|
||||
/// implementation, accepting "double quoted" ids is both ANSI-compliant
|
||||
/// and appropriate for most dialects (with the notable exception of
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::dialect::Dialect;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PostgreSqlDialect {}
|
||||
|
||||
impl Dialect for PostgreSqlDialect {
|
||||
|
|
|
@ -40,3 +40,8 @@ pub mod dialect;
|
|||
pub mod sqlast;
|
||||
pub mod sqlparser;
|
||||
pub mod sqltokenizer;
|
||||
|
||||
#[doc(hidden)]
|
||||
// This is required to make utilities accessible by both the crate-internal
|
||||
// unit-tests and by the integration tests <https://stackoverflow.com/a/44541071/1026>
|
||||
pub mod test_utils;
|
||||
|
|
121
src/test_utils.rs
Normal file
121
src/test_utils.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use super::dialect::*;
|
||||
use super::sqlast::*;
|
||||
use super::sqlparser::{Parser, ParserError};
|
||||
use super::sqltokenizer::Tokenizer;
|
||||
|
||||
/// Tests use the methods on this struct to invoke the parser on one or
|
||||
/// multiple dialects.
|
||||
pub struct TestedDialects {
|
||||
pub dialects: Vec<Box<dyn Dialect>>,
|
||||
}
|
||||
|
||||
impl TestedDialects {
|
||||
/// Run the given function for all of `self.dialects`, assert that they
|
||||
/// return the same result, and return that result.
|
||||
pub fn one_of_identical_results<F, T: Debug + PartialEq>(&self, f: F) -> T
|
||||
where
|
||||
F: Fn(&dyn Dialect) -> T,
|
||||
{
|
||||
let parse_results = self.dialects.iter().map(|dialect| (dialect, f(&**dialect)));
|
||||
parse_results
|
||||
.fold(None, |s, (dialect, parsed)| {
|
||||
if let Some((prev_dialect, prev_parsed)) = s {
|
||||
assert_eq!(
|
||||
prev_parsed, parsed,
|
||||
"Parse results with {:?} are different from {:?}",
|
||||
prev_dialect, dialect
|
||||
);
|
||||
}
|
||||
Some((dialect, parsed))
|
||||
})
|
||||
.unwrap()
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn run_parser_method<F, T: Debug + PartialEq>(&self, sql: &str, f: F) -> T
|
||||
where
|
||||
F: Fn(&mut Parser) -> T,
|
||||
{
|
||||
self.one_of_identical_results(|dialect| {
|
||||
let mut tokenizer = Tokenizer::new(dialect, sql);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
f(&mut Parser::new(tokens))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_sql_statements(&self, sql: &str) -> Result<Vec<SQLStatement>, ParserError> {
|
||||
self.one_of_identical_results(|dialect| Parser::parse_sql(dialect, sql.to_string()))
|
||||
// To fail the `ensure_multiple_dialects_are_tested` test:
|
||||
// Parser::parse_sql(&**self.dialects.first().unwrap(), sql.to_string())
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as a single statement, optionally checking
|
||||
/// that converting AST back to string equals to `canonical` (unless an
|
||||
/// empty canonical string is provided).
|
||||
pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> SQLStatement {
|
||||
let mut statements = self.parse_sql_statements(&sql).unwrap();
|
||||
assert_eq!(statements.len(), 1);
|
||||
|
||||
let only_statement = statements.pop().unwrap();
|
||||
if !canonical.is_empty() {
|
||||
assert_eq!(canonical, only_statement.to_string())
|
||||
}
|
||||
only_statement
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as a single SQLStatement, and is not modified
|
||||
/// after a serialization round-trip.
|
||||
pub fn verified_stmt(&self, query: &str) -> SQLStatement {
|
||||
self.one_statement_parses_to(query, query)
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as a single SQLQuery, and is not modified
|
||||
/// after a serialization round-trip.
|
||||
pub fn verified_query(&self, sql: &str) -> SQLQuery {
|
||||
match self.verified_stmt(sql) {
|
||||
SQLStatement::SQLQuery(query) => *query,
|
||||
_ => panic!("Expected SQLQuery"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as a single SQLSelect, and is not modified
|
||||
/// after a serialization round-trip.
|
||||
pub fn verified_only_select(&self, query: &str) -> SQLSelect {
|
||||
match self.verified_query(query).body {
|
||||
SQLSetExpr::Select(s) => *s,
|
||||
_ => panic!("Expected SQLSetExpr::Select"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as an expression, and is not modified
|
||||
/// after a serialization round-trip.
|
||||
pub fn verified_expr(&self, sql: &str) -> ASTNode {
|
||||
let ast = self.run_parser_method(sql, Parser::parse_expr).unwrap();
|
||||
assert_eq!(sql, &ast.to_string(), "round-tripping without changes");
|
||||
ast
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_dialects() -> TestedDialects {
|
||||
TestedDialects {
|
||||
dialects: vec![
|
||||
Box::new(GenericSqlDialect {}),
|
||||
Box::new(PostgreSqlDialect {}),
|
||||
Box::new(AnsiSqlDialect {}),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn only<T>(v: &[T]) -> &T {
|
||||
assert_eq!(1, v.len());
|
||||
v.first().unwrap()
|
||||
}
|
||||
|
||||
pub fn expr_from_projection(item: &SQLSelectItem) -> &ASTNode {
|
||||
match item {
|
||||
SQLSelectItem::UnnamedExpression(expr) => expr,
|
||||
_ => panic!("Expected UnnamedExpression"),
|
||||
}
|
||||
}
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
use matches::assert_matches;
|
||||
|
||||
use sqlparser::dialect::*;
|
||||
use sqlparser::sqlast::*;
|
||||
use sqlparser::sqlparser::*;
|
||||
use sqlparser::sqltokenizer::*;
|
||||
use sqlparser::sqlparser::ParserError;
|
||||
use sqlparser::test_utils::{all_dialects, expr_from_projection, only};
|
||||
|
||||
#[test]
|
||||
fn parse_insert_values() {
|
||||
|
@ -695,16 +694,6 @@ fn parse_simple_math_expr_minus() {
|
|||
verified_only_select(sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_select_version() {
|
||||
let sql = "SELECT @@version";
|
||||
let select = verified_only_select(sql);
|
||||
assert_eq!(
|
||||
&ASTNode::SQLIdentifier("@@version".to_string()),
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_delimited_identifiers() {
|
||||
// check that quoted identifiers in any position remain quoted after serialization
|
||||
|
@ -1169,73 +1158,35 @@ fn parse_invalid_subquery_without_parens() {
|
|||
);
|
||||
}
|
||||
|
||||
fn only<T>(v: &[T]) -> &T {
|
||||
assert_eq!(1, v.len());
|
||||
v.first().unwrap()
|
||||
}
|
||||
|
||||
fn verified_query(query: &str) -> SQLQuery {
|
||||
match verified_stmt(query) {
|
||||
SQLStatement::SQLQuery(query) => *query,
|
||||
_ => panic!("Expected SQLQuery"),
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_from_projection(item: &SQLSelectItem) -> &ASTNode {
|
||||
match item {
|
||||
SQLSelectItem::UnnamedExpression(expr) => expr,
|
||||
_ => panic!("Expected UnnamedExpression"),
|
||||
}
|
||||
}
|
||||
|
||||
fn verified_only_select(query: &str) -> SQLSelect {
|
||||
match verified_query(query).body {
|
||||
SQLSetExpr::Select(s) => *s,
|
||||
_ => panic!("Expected SQLSetExpr::Select"),
|
||||
}
|
||||
}
|
||||
|
||||
fn verified_stmt(query: &str) -> SQLStatement {
|
||||
one_statement_parses_to(query, query)
|
||||
}
|
||||
|
||||
fn verified_expr(query: &str) -> ASTNode {
|
||||
let ast = parse_sql_expr(query);
|
||||
assert_eq!(query, &ast.to_string());
|
||||
ast
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as a single statement, optionally checking that
|
||||
/// converting AST back to string equals to `canonical` (unless an empty string
|
||||
/// is provided).
|
||||
fn one_statement_parses_to(sql: &str, canonical: &str) -> SQLStatement {
|
||||
let mut statements = parse_sql_statements(&sql).unwrap();
|
||||
assert_eq!(statements.len(), 1);
|
||||
|
||||
let only_statement = statements.pop().unwrap();
|
||||
if !canonical.is_empty() {
|
||||
assert_eq!(canonical, only_statement.to_string())
|
||||
}
|
||||
only_statement
|
||||
#[test]
|
||||
#[should_panic(expected = "Parse results with PostgreSqlDialect are different from AnsiSqlDialect")]
|
||||
fn ensure_multiple_dialects_are_tested() {
|
||||
// The SQL here must be parsed differently by different dialects.
|
||||
// At the time of writing, `@foo` is accepted as a valid identifier
|
||||
// by the generic and the postgresql dialect, but not by the ANSI one.
|
||||
let _ = parse_sql_statements("SELECT @foo");
|
||||
}
|
||||
|
||||
fn parse_sql_statements(sql: &str) -> Result<Vec<SQLStatement>, ParserError> {
|
||||
let generic_ast = Parser::parse_sql(&GenericSqlDialect {}, sql.to_string());
|
||||
let pg_ast = Parser::parse_sql(&PostgreSqlDialect {}, sql.to_string());
|
||||
assert_eq!(generic_ast, pg_ast);
|
||||
generic_ast
|
||||
all_dialects().parse_sql_statements(sql)
|
||||
}
|
||||
|
||||
fn parse_sql_expr(sql: &str) -> ASTNode {
|
||||
let generic_ast = parse_sql_expr_with(&GenericSqlDialect {}, &sql.to_string());
|
||||
let pg_ast = parse_sql_expr_with(&PostgreSqlDialect {}, &sql.to_string());
|
||||
assert_eq!(generic_ast, pg_ast);
|
||||
generic_ast
|
||||
fn one_statement_parses_to(sql: &str, canonical: &str) -> SQLStatement {
|
||||
all_dialects().one_statement_parses_to(sql, canonical)
|
||||
}
|
||||
|
||||
fn parse_sql_expr_with(dialect: &dyn Dialect, sql: &str) -> ASTNode {
|
||||
let mut tokenizer = Tokenizer::new(dialect, &sql);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
let mut parser = Parser::new(tokens);
|
||||
parser.parse_expr().unwrap()
|
||||
fn verified_stmt(query: &str) -> SQLStatement {
|
||||
all_dialects().verified_stmt(query)
|
||||
}
|
||||
|
||||
fn verified_query(query: &str) -> SQLQuery {
|
||||
all_dialects().verified_query(query)
|
||||
}
|
||||
|
||||
fn verified_only_select(query: &str) -> SQLSelect {
|
||||
all_dialects().verified_only_select(query)
|
||||
}
|
||||
|
||||
fn verified_expr(query: &str) -> ASTNode {
|
||||
all_dialects().verified_expr(query)
|
||||
}
|
|
@ -1,39 +1,47 @@
|
|||
#![warn(clippy::all)]
|
||||
|
||||
use log::debug;
|
||||
|
||||
use sqlparser::dialect::PostgreSqlDialect;
|
||||
use sqlparser::dialect::{GenericSqlDialect, PostgreSqlDialect};
|
||||
use sqlparser::sqlast::*;
|
||||
use sqlparser::sqlparser::*;
|
||||
use sqlparser::sqltokenizer::*;
|
||||
use sqlparser::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_prev_index() {
|
||||
let sql = "SELECT version()";
|
||||
let mut parser = parser(sql);
|
||||
assert_eq!(parser.prev_token(), None);
|
||||
assert_eq!(parser.next_token(), Some(Token::make_keyword("SELECT")));
|
||||
assert_eq!(parser.next_token(), Some(Token::make_word("version", None)));
|
||||
assert_eq!(parser.prev_token(), Some(Token::make_word("version", None)));
|
||||
assert_eq!(parser.peek_token(), Some(Token::make_word("version", None)));
|
||||
assert_eq!(parser.prev_token(), Some(Token::make_keyword("SELECT")));
|
||||
assert_eq!(parser.prev_token(), None);
|
||||
all_dialects().run_parser_method(sql, |parser| {
|
||||
assert_eq!(parser.prev_token(), None);
|
||||
assert_eq!(parser.next_token(), Some(Token::make_keyword("SELECT")));
|
||||
assert_eq!(parser.next_token(), Some(Token::make_word("version", None)));
|
||||
assert_eq!(parser.prev_token(), Some(Token::make_word("version", None)));
|
||||
assert_eq!(parser.peek_token(), Some(Token::make_word("version", None)));
|
||||
assert_eq!(parser.prev_token(), Some(Token::make_keyword("SELECT")));
|
||||
assert_eq!(parser.prev_token(), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_invalid_table_name() {
|
||||
let mut parser = parser("db.public..customer");
|
||||
let ast = parser.parse_object_name();
|
||||
let ast = all_dialects().run_parser_method("db.public..customer", Parser::parse_object_name);
|
||||
assert!(ast.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_no_table_name() {
|
||||
let mut parser = parser("");
|
||||
let ast = parser.parse_object_name();
|
||||
let ast = all_dialects().run_parser_method("", Parser::parse_object_name);
|
||||
assert!(ast.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_select_version() {
|
||||
let sql = "SELECT @@version";
|
||||
let select = pg_and_generic().verified_only_select(sql);
|
||||
assert_eq!(
|
||||
&ASTNode::SQLIdentifier("@@version".to_string()),
|
||||
expr_from_projection(only(&select.projection)),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_with_defaults() {
|
||||
let sql = "CREATE TABLE public.customer (
|
||||
|
@ -209,32 +217,24 @@ PHP ₱ USD $
|
|||
//assert_eq!(sql, ast.to_string());
|
||||
}
|
||||
|
||||
fn verified_stmt(query: &str) -> SQLStatement {
|
||||
one_statement_parses_to(query, query)
|
||||
}
|
||||
|
||||
/// Ensures that `sql` parses as a single statement, optionally checking that
|
||||
/// converting AST back to string equals to `canonical` (unless an empty string
|
||||
/// is provided).
|
||||
fn one_statement_parses_to(sql: &str, canonical: &str) -> SQLStatement {
|
||||
let mut statements = parse_sql_statements(&sql).unwrap();
|
||||
assert_eq!(statements.len(), 1);
|
||||
pg().one_statement_parses_to(sql, canonical)
|
||||
}
|
||||
fn verified_stmt(query: &str) -> SQLStatement {
|
||||
pg().verified_stmt(query)
|
||||
}
|
||||
|
||||
let only_statement = statements.pop().unwrap();
|
||||
if !canonical.is_empty() {
|
||||
assert_eq!(canonical, only_statement.to_string())
|
||||
fn pg() -> TestedDialects {
|
||||
TestedDialects {
|
||||
dialects: vec![Box::new(PostgreSqlDialect {})],
|
||||
}
|
||||
only_statement
|
||||
}
|
||||
|
||||
fn parse_sql_statements(sql: &str) -> Result<Vec<SQLStatement>, ParserError> {
|
||||
Parser::parse_sql(&PostgreSqlDialect {}, sql.to_string())
|
||||
}
|
||||
|
||||
fn parser(sql: &str) -> Parser {
|
||||
let dialect = PostgreSqlDialect {};
|
||||
let mut tokenizer = Tokenizer::new(&dialect, &sql);
|
||||
let tokens = tokenizer.tokenize().unwrap();
|
||||
debug!("tokens: {:#?}", tokens);
|
||||
Parser::new(tokens)
|
||||
fn pg_and_generic() -> TestedDialects {
|
||||
TestedDialects {
|
||||
dialects: vec![
|
||||
Box::new(PostgreSqlDialect {}),
|
||||
Box::new(GenericSqlDialect {}),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue