feat: add arg placeholder (#420)

Co-authored-by: gamife <gamife9886@gmail.com>
This commit is contained in:
gamife 2022-02-17 20:55:21 +08:00 committed by GitHub
parent 1da49c15c7
commit 899f91b1f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 1 deletions

View file

@ -59,6 +59,8 @@ pub enum Value {
}, },
/// `NULL` value /// `NULL` value
Null, Null,
/// `?` or `$` Prepared statement arg placeholder
Placeholder(String),
} }
impl fmt::Display for Value { impl fmt::Display for Value {
@ -111,6 +113,7 @@ impl fmt::Display for Value {
Ok(()) Ok(())
} }
Value::Null => write!(f, "NULL"), Value::Null => write!(f, "NULL"),
Value::Placeholder(v) => write!(f, "{}", v),
} }
} }
} }

View file

@ -505,6 +505,10 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?; self.expect_token(&Token::RParen)?;
Ok(expr) Ok(expr)
} }
Token::Placeholder(_) => {
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
}
unexpected => self.expected("an expression:", unexpected), unexpected => self.expected("an expression:", unexpected),
}?; }?;
@ -2261,6 +2265,7 @@ impl<'a> Parser<'a> {
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())), Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())), Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())),
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(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), unexpected => self.expected("a value", unexpected),
} }
} }

View file

@ -139,6 +139,8 @@ pub enum Token {
PGSquareRoot, PGSquareRoot,
/// `||/` , a cube root math operator in PostgreSQL /// `||/` , a cube root math operator in PostgreSQL
PGCubeRoot, PGCubeRoot,
/// `?` or `$` , a prepared statement arg placeholder
Placeholder(String),
} }
impl fmt::Display for Token { impl fmt::Display for Token {
@ -194,6 +196,7 @@ impl fmt::Display for Token {
Token::ShiftRight => f.write_str(">>"), Token::ShiftRight => f.write_str(">>"),
Token::PGSquareRoot => f.write_str("|/"), Token::PGSquareRoot => f.write_str("|/"),
Token::PGCubeRoot => f.write_str("||/"), Token::PGCubeRoot => f.write_str("||/"),
Token::Placeholder(ref s) => write!(f, "{}", s),
} }
} }
} }
@ -337,6 +340,7 @@ impl<'a> Tokenizer<'a> {
Token::Word(w) if w.quote_style != None => self.col += w.value.len() as u64 + 2, 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::Number(s, _) => self.col += s.len() as u64,
Token::SingleQuotedString(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, _ => self.col += 1,
} }
@ -598,6 +602,15 @@ impl<'a> Tokenizer<'a> {
} }
'#' => self.consume_and_return(chars, Token::Sharp), '#' => self.consume_and_return(chars, Token::Sharp),
'@' => self.consume_and_return(chars, Token::AtSign), '@' => 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)), other => self.consume_and_return(chars, Token::Char(other)),
}, },
None => Ok(None), None => Ok(None),

View file

@ -22,7 +22,9 @@
mod test_utils; mod test_utils;
use matches::assert_matches; use matches::assert_matches;
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, PostgreSqlDialect, SQLiteDialect}; use sqlparser::dialect::{
AnsiDialect, GenericDialect, MsSqlDialect, PostgreSqlDialect, SQLiteDialect, SnowflakeDialect,
};
use sqlparser::keywords::ALL_KEYWORDS; use sqlparser::keywords::ALL_KEYWORDS;
use sqlparser::parser::{Parser, ParserError}; use sqlparser::parser::{Parser, ParserError};
use test_utils::{ use test_utils::{
@ -4160,6 +4162,42 @@ fn test_revoke() {
} }
} }
#[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())))
})
);
}
#[test] #[test]
fn all_keywords_sorted() { fn all_keywords_sorted() {
// assert!(ALL_KEYWORDS.is_sorted()) // assert!(ALL_KEYWORDS.is_sorted())