Skip to content

Commit 899f91b

Browse files
gamifegamife
and
gamife
authored
feat: add arg placeholder (#420)
Co-authored-by: gamife <[email protected]>
1 parent 1da49c1 commit 899f91b

File tree

4 files changed

+60
-1
lines changed

4 files changed

+60
-1
lines changed

src/ast/value.rs

+3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ pub enum Value {
5959
},
6060
/// `NULL` value
6161
Null,
62+
/// `?` or `$` Prepared statement arg placeholder
63+
Placeholder(String),
6264
}
6365

6466
impl fmt::Display for Value {
@@ -111,6 +113,7 @@ impl fmt::Display for Value {
111113
Ok(())
112114
}
113115
Value::Null => write!(f, "NULL"),
116+
Value::Placeholder(v) => write!(f, "{}", v),
114117
}
115118
}
116119
}

src/parser.rs

+5
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,10 @@ impl<'a> Parser<'a> {
505505
self.expect_token(&Token::RParen)?;
506506
Ok(expr)
507507
}
508+
Token::Placeholder(_) => {
509+
self.prev_token();
510+
Ok(Expr::Value(self.parse_value()?))
511+
}
508512
unexpected => self.expected("an expression:", unexpected),
509513
}?;
510514

@@ -2261,6 +2265,7 @@ impl<'a> Parser<'a> {
22612265
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
22622266
Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())),
22632267
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
2268+
Token::Placeholder(ref s) => Ok(Value::Placeholder(s.to_string())),
22642269
unexpected => self.expected("a value", unexpected),
22652270
}
22662271
}

src/tokenizer.rs

+13
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ pub enum Token {
139139
PGSquareRoot,
140140
/// `||/` , a cube root math operator in PostgreSQL
141141
PGCubeRoot,
142+
/// `?` or `$` , a prepared statement arg placeholder
143+
Placeholder(String),
142144
}
143145

144146
impl fmt::Display for Token {
@@ -194,6 +196,7 @@ impl fmt::Display for Token {
194196
Token::ShiftRight => f.write_str(">>"),
195197
Token::PGSquareRoot => f.write_str("|/"),
196198
Token::PGCubeRoot => f.write_str("||/"),
199+
Token::Placeholder(ref s) => write!(f, "{}", s),
197200
}
198201
}
199202
}
@@ -337,6 +340,7 @@ impl<'a> Tokenizer<'a> {
337340
Token::Word(w) if w.quote_style != None => self.col += w.value.len() as u64 + 2,
338341
Token::Number(s, _) => self.col += s.len() as u64,
339342
Token::SingleQuotedString(s) => self.col += s.len() as u64,
343+
Token::Placeholder(s) => self.col += s.len() as u64,
340344
_ => self.col += 1,
341345
}
342346

@@ -598,6 +602,15 @@ impl<'a> Tokenizer<'a> {
598602
}
599603
'#' => self.consume_and_return(chars, Token::Sharp),
600604
'@' => self.consume_and_return(chars, Token::AtSign),
605+
'?' => self.consume_and_return(chars, Token::Placeholder(String::from("?"))),
606+
'$' => {
607+
chars.next();
608+
let s = peeking_take_while(
609+
chars,
610+
|ch| matches!(ch, '0'..='9' | 'A'..='Z' | 'a'..='z'),
611+
);
612+
Ok(Some(Token::Placeholder(String::from("$") + &s)))
613+
}
601614
other => self.consume_and_return(chars, Token::Char(other)),
602615
},
603616
None => Ok(None),

tests/sqlparser_common.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
mod test_utils;
2323
use matches::assert_matches;
2424
use sqlparser::ast::*;
25-
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect, SQLiteDialect};
25+
use sqlparser::dialect::{
26+
AnsiDialect, GenericDialect, MsSqlDialect, PostgreSqlDialect, SQLiteDialect, SnowflakeDialect,
27+
};
2628
use sqlparser::keywords::ALL_KEYWORDS;
2729
use sqlparser::parser::{Parser, ParserError};
2830
use test_utils::{
@@ -4160,6 +4162,42 @@ fn test_revoke() {
41604162
}
41614163
}
41624164

4165+
#[test]
4166+
fn test_placeholder() {
4167+
let sql = "SELECT * FROM student WHERE id = ?";
4168+
let ast = verified_only_select(sql);
4169+
assert_eq!(
4170+
ast.selection,
4171+
Some(Expr::BinaryOp {
4172+
left: Box::new(Expr::Identifier(Ident::new("id"))),
4173+
op: BinaryOperator::Eq,
4174+
right: Box::new(Expr::Value(Value::Placeholder("?".into())))
4175+
})
4176+
);
4177+
4178+
let dialects = TestedDialects {
4179+
dialects: vec![
4180+
Box::new(GenericDialect {}),
4181+
Box::new(PostgreSqlDialect {}),
4182+
Box::new(MsSqlDialect {}),
4183+
Box::new(AnsiDialect {}),
4184+
Box::new(SnowflakeDialect {}),
4185+
// Note: `$` is the starting word for the HiveDialect identifier
4186+
// Box::new(sqlparser::dialect::HiveDialect {}),
4187+
],
4188+
};
4189+
let sql = "SELECT * FROM student WHERE id = $Id1";
4190+
let ast = dialects.verified_only_select(sql);
4191+
assert_eq!(
4192+
ast.selection,
4193+
Some(Expr::BinaryOp {
4194+
left: Box::new(Expr::Identifier(Ident::new("id"))),
4195+
op: BinaryOperator::Eq,
4196+
right: Box::new(Expr::Value(Value::Placeholder("$Id1".into())))
4197+
})
4198+
);
4199+
}
4200+
41634201
#[test]
41644202
fn all_keywords_sorted() {
41654203
// assert!(ALL_KEYWORDS.is_sorted())

0 commit comments

Comments
 (0)