Skip to content

Commit

Permalink
feat: add arg placeholder (apache#420)
Browse files Browse the repository at this point in the history
Co-authored-by: gamife <[email protected]>
  • Loading branch information
2 people authored and ovr committed Apr 14, 2023
1 parent a65390c commit 4c1e60e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 6 deletions.
3 changes: 3 additions & 0 deletions src/ast/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ pub enum Value {
},
/// `NULL` value
Null,
/// `?` or `$` Prepared statement arg placeholder
Placeholder(String),
}

impl fmt::Display for Value {
Expand Down Expand Up @@ -108,6 +110,7 @@ impl fmt::Display for Value {
Ok(())
}
Value::Null => write!(f, "NULL"),
Value::Placeholder(v) => write!(f, "{}", v),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?;
Ok(expr)
}
Token::Placeholder(_) => {
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
}
unexpected => self.expected("an expression:", unexpected),
}?;

Expand Down Expand Up @@ -1966,6 +1970,7 @@ impl<'a> Parser<'a> {
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())),
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
Token::Placeholder(ref s) => Ok(Value::Placeholder(s.to_string())),
unexpected => self.expected("a value", unexpected),
}
}
Expand Down
15 changes: 14 additions & 1 deletion src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ pub enum Token {
PGSquareRoot,
/// `||/` , a cube root math operator in PostgreSQL
PGCubeRoot,
/// `?` or `$` , a prepared statement arg placeholder
Placeholder(String),
}

impl fmt::Display for Token {
Expand Down Expand Up @@ -176,6 +178,7 @@ impl fmt::Display for Token {
Token::ShiftRight => f.write_str(">>"),
Token::PGSquareRoot => f.write_str("|/"),
Token::PGCubeRoot => f.write_str("||/"),
Token::Placeholder(ref s) => write!(f, "{}", s),
}
}
}
Expand Down Expand Up @@ -304,6 +307,7 @@ impl<'a> Tokenizer<'a> {
Token::Word(w) if w.quote_style != None => self.col += w.value.len() as u64 + 2,
Token::Number(s, _) => self.col += s.len() as u64,
Token::SingleQuotedString(s) => self.col += s.len() as u64,
Token::Placeholder(s) => self.col += s.len() as u64,
_ => self.col += 1,
}

Expand Down Expand Up @@ -550,6 +554,15 @@ impl<'a> Tokenizer<'a> {
'~' => self.consume_and_return(chars, Token::Tilde),
'#' => self.consume_and_return(chars, Token::Sharp),
'@' => self.consume_and_return(chars, Token::AtSign),
'?' => self.consume_and_return(chars, Token::Placeholder(String::from("?"))),
'$' => {
chars.next();
let s = peeking_take_while(
chars,
|ch| matches!(ch, '0'..='9' | 'A'..='Z' | 'a'..='z'),
);
Ok(Some(Token::Placeholder(String::from("$") + &s)))
}
other => self.consume_and_return(chars, Token::Char(other)),
},
None => Ok(None),
Expand Down Expand Up @@ -616,7 +629,7 @@ impl<'a> Tokenizer<'a> {
'r' => s.push('\r'),
't' => s.push('\t'),
'Z' => s.push('\x1a'),
x => s.push(x)
x => s.push(x),
}
}
}
Expand Down
50 changes: 45 additions & 5 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ use test_utils::{all_dialects, expr_from_projection, join, number, only, table,

use matches::assert_matches;
use sqlparser::ast::*;
use sqlparser::dialect::{keywords::ALL_KEYWORDS, SQLiteDialect};
use sqlparser::dialect::{
keywords::ALL_KEYWORDS, AnsiDialect, GenericDialect, MsSqlDialect, PostgreSqlDialect,
SQLiteDialect, SnowflakeDialect,
};
use sqlparser::parser::{Parser, ParserError};
use sqlparser::test_utils::TestedDialects;

#[test]
fn parse_insert_values() {
Expand Down Expand Up @@ -2715,10 +2719,10 @@ fn parse_scalar_subqueries() {
assert_matches!(
verified_expr(sql),
Expr::BinaryOp {
op: BinaryOperator::Plus, ..
//left: box Subquery { .. },
//right: box Subquery { .. },
}
op: BinaryOperator::Plus,
.. //left: box Subquery { .. },
//right: box Subquery { .. },
}
);
}

Expand Down Expand Up @@ -3557,6 +3561,42 @@ fn parse_rolling_window() {
);
}

#[test]
fn test_placeholder() {
let sql = "SELECT * FROM student WHERE id = ?";
let ast = verified_only_select(sql);
assert_eq!(
ast.selection,
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("id"))),
op: BinaryOperator::Eq,
right: Box::new(Expr::Value(Value::Placeholder("?".into())))
})
);

let dialects = TestedDialects {
dialects: vec![
Box::new(GenericDialect {}),
Box::new(PostgreSqlDialect {}),
Box::new(MsSqlDialect {}),
Box::new(AnsiDialect {}),
Box::new(SnowflakeDialect {}),
// Note: `$` is the starting word for the HiveDialect identifier
// Box::new(sqlparser::dialect::HiveDialect {}),
],
};
let sql = "SELECT * FROM student WHERE id = $Id1";
let ast = dialects.verified_only_select(sql);
assert_eq!(
ast.selection,
Some(Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("id"))),
op: BinaryOperator::Eq,
right: Box::new(Expr::Value(Value::Placeholder("$Id1".into())))
})
);
}

fn parse_sql_statements(sql: &str) -> Result<Vec<Statement>, ParserError> {
all_dialects().parse_sql_statements(sql)
}
Expand Down

0 comments on commit 4c1e60e

Please sign in to comment.