Skip to content

Commit 4c1e60e

Browse files
gamifegamife
authored andcommitted
feat: add arg placeholder (apache#420)
Co-authored-by: gamife <[email protected]>
1 parent a65390c commit 4c1e60e

File tree

4 files changed

+67
-6
lines changed

4 files changed

+67
-6
lines changed

src/ast/value.rs

+3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub enum Value {
5656
},
5757
/// `NULL` value
5858
Null,
59+
/// `?` or `$` Prepared statement arg placeholder
60+
Placeholder(String),
5961
}
6062

6163
impl fmt::Display for Value {
@@ -108,6 +110,7 @@ impl fmt::Display for Value {
108110
Ok(())
109111
}
110112
Value::Null => write!(f, "NULL"),
113+
Value::Placeholder(v) => write!(f, "{}", v),
111114
}
112115
}
113116
}

src/parser.rs

+5
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,10 @@ impl<'a> Parser<'a> {
445445
self.expect_token(&Token::RParen)?;
446446
Ok(expr)
447447
}
448+
Token::Placeholder(_) => {
449+
self.prev_token();
450+
Ok(Expr::Value(self.parse_value()?))
451+
}
448452
unexpected => self.expected("an expression:", unexpected),
449453
}?;
450454

@@ -1966,6 +1970,7 @@ impl<'a> Parser<'a> {
19661970
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
19671971
Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())),
19681972
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
1973+
Token::Placeholder(ref s) => Ok(Value::Placeholder(s.to_string())),
19691974
unexpected => self.expected("a value", unexpected),
19701975
}
19711976
}

src/tokenizer.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ pub enum Token {
124124
PGSquareRoot,
125125
/// `||/` , a cube root math operator in PostgreSQL
126126
PGCubeRoot,
127+
/// `?` or `$` , a prepared statement arg placeholder
128+
Placeholder(String),
127129
}
128130

129131
impl fmt::Display for Token {
@@ -176,6 +178,7 @@ impl fmt::Display for Token {
176178
Token::ShiftRight => f.write_str(">>"),
177179
Token::PGSquareRoot => f.write_str("|/"),
178180
Token::PGCubeRoot => f.write_str("||/"),
181+
Token::Placeholder(ref s) => write!(f, "{}", s),
179182
}
180183
}
181184
}
@@ -304,6 +307,7 @@ impl<'a> Tokenizer<'a> {
304307
Token::Word(w) if w.quote_style != None => self.col += w.value.len() as u64 + 2,
305308
Token::Number(s, _) => self.col += s.len() as u64,
306309
Token::SingleQuotedString(s) => self.col += s.len() as u64,
310+
Token::Placeholder(s) => self.col += s.len() as u64,
307311
_ => self.col += 1,
308312
}
309313

@@ -550,6 +554,15 @@ impl<'a> Tokenizer<'a> {
550554
'~' => self.consume_and_return(chars, Token::Tilde),
551555
'#' => self.consume_and_return(chars, Token::Sharp),
552556
'@' => self.consume_and_return(chars, Token::AtSign),
557+
'?' => self.consume_and_return(chars, Token::Placeholder(String::from("?"))),
558+
'$' => {
559+
chars.next();
560+
let s = peeking_take_while(
561+
chars,
562+
|ch| matches!(ch, '0'..='9' | 'A'..='Z' | 'a'..='z'),
563+
);
564+
Ok(Some(Token::Placeholder(String::from("$") + &s)))
565+
}
553566
other => self.consume_and_return(chars, Token::Char(other)),
554567
},
555568
None => Ok(None),
@@ -616,7 +629,7 @@ impl<'a> Tokenizer<'a> {
616629
'r' => s.push('\r'),
617630
't' => s.push('\t'),
618631
'Z' => s.push('\x1a'),
619-
x => s.push(x)
632+
x => s.push(x),
620633
}
621634
}
622635
}

tests/sqlparser_common.rs

+45-5
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ use test_utils::{all_dialects, expr_from_projection, join, number, only, table,
2424

2525
use matches::assert_matches;
2626
use sqlparser::ast::*;
27-
use sqlparser::dialect::{keywords::ALL_KEYWORDS, SQLiteDialect};
27+
use sqlparser::dialect::{
28+
keywords::ALL_KEYWORDS, AnsiDialect, GenericDialect, MsSqlDialect, PostgreSqlDialect,
29+
SQLiteDialect, SnowflakeDialect,
30+
};
2831
use sqlparser::parser::{Parser, ParserError};
32+
use sqlparser::test_utils::TestedDialects;
2933

3034
#[test]
3135
fn parse_insert_values() {
@@ -2715,10 +2719,10 @@ fn parse_scalar_subqueries() {
27152719
assert_matches!(
27162720
verified_expr(sql),
27172721
Expr::BinaryOp {
2718-
op: BinaryOperator::Plus, ..
2719-
//left: box Subquery { .. },
2720-
//right: box Subquery { .. },
2721-
}
2722+
op: BinaryOperator::Plus,
2723+
.. //left: box Subquery { .. },
2724+
//right: box Subquery { .. },
2725+
}
27222726
);
27232727
}
27242728

@@ -3557,6 +3561,42 @@ fn parse_rolling_window() {
35573561
);
35583562
}
35593563

3564+
#[test]
3565+
fn test_placeholder() {
3566+
let sql = "SELECT * FROM student WHERE id = ?";
3567+
let ast = verified_only_select(sql);
3568+
assert_eq!(
3569+
ast.selection,
3570+
Some(Expr::BinaryOp {
3571+
left: Box::new(Expr::Identifier(Ident::new("id"))),
3572+
op: BinaryOperator::Eq,
3573+
right: Box::new(Expr::Value(Value::Placeholder("?".into())))
3574+
})
3575+
);
3576+
3577+
let dialects = TestedDialects {
3578+
dialects: vec![
3579+
Box::new(GenericDialect {}),
3580+
Box::new(PostgreSqlDialect {}),
3581+
Box::new(MsSqlDialect {}),
3582+
Box::new(AnsiDialect {}),
3583+
Box::new(SnowflakeDialect {}),
3584+
// Note: `$` is the starting word for the HiveDialect identifier
3585+
// Box::new(sqlparser::dialect::HiveDialect {}),
3586+
],
3587+
};
3588+
let sql = "SELECT * FROM student WHERE id = $Id1";
3589+
let ast = dialects.verified_only_select(sql);
3590+
assert_eq!(
3591+
ast.selection,
3592+
Some(Expr::BinaryOp {
3593+
left: Box::new(Expr::Identifier(Ident::new("id"))),
3594+
op: BinaryOperator::Eq,
3595+
right: Box::new(Expr::Value(Value::Placeholder("$Id1".into())))
3596+
})
3597+
);
3598+
}
3599+
35603600
fn parse_sql_statements(sql: &str) -> Result<Vec<Statement>, ParserError> {
35613601
all_dialects().parse_sql_statements(sql)
35623602
}

0 commit comments

Comments
 (0)