Skip to content

Commit

Permalink
support param in parser
Browse files Browse the repository at this point in the history
  • Loading branch information
ZENOTME committed Feb 22, 2023
1 parent 229a3c7 commit 7c19b62
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 2 deletions.
9 changes: 9 additions & 0 deletions src/sqlparser/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ pub enum Expr {
Nested(Box<Expr>),
/// A literal value, such as string, number, date or NULL
Value(Value),
/// Parameter Symbol e.g. `$1`, `$1::int`
Parameter {
index: u64,
data_type: Option<DataType>,
},
/// A constant of form `<data_type> 'value'`.
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE
/// '2020-01-01'`), as well as constants of other types (a non-standard PostgreSQL extension).
Expand Down Expand Up @@ -439,6 +444,10 @@ impl fmt::Display for Expr {
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
Expr::Nested(ast) => write!(f, "({})", ast),
Expr::Value(v) => write!(f, "{}", v),
Expr::Parameter { index, data_type } => match data_type {
Some(data_type) => write!(f, "${}::{}", index, data_type),
None => write!(f, "${}", index),
},
Expr::TypedString { data_type, value } => {
write!(f, "{}", data_type)?;
write!(f, " '{}'", &value::escape_single_quote_string(value))
Expand Down
17 changes: 16 additions & 1 deletion src/sqlparser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ impl Parser {
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
}

Token::Parameter(number) => self.parse_param(number),
Token::LParen => {
let expr =
if self.parse_keyword(Keyword::SELECT) || self.parse_keyword(Keyword::WITH) {
Expand Down Expand Up @@ -583,6 +583,21 @@ impl Parser {
}
}

fn parse_param(&mut self, param: String) -> Result<Expr, ParserError> {
let data_type = if let Token::DoubleColon = self.peek_token() {
self.next_token();
Some(self.parse_data_type()?)
} else {
None
};
Ok(Expr::Parameter {
index: param.parse().map_err(|_| {
ParserError::ParserError(format!("Parameter symbol has a invalid index {}.", param))
})?,
data_type,
})
}

/// Parses a field selection expression. See also [`Expr::FieldIdentifier`].
pub fn parse_struct_selection(&mut self, expr: Expr) -> Result<Expr, ParserError> {
let mut nested_expr = expr;
Expand Down
22 changes: 21 additions & 1 deletion src/sqlparser/src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum Token {
NationalStringLiteral(String),
/// Hexadecimal string literal: i.e.: X'deadbeef'
HexStringLiteral(String),
/// Parameter symbols: i.e: $1, $2
Parameter(String),
/// Comma
Comma,
/// Whitespace (space, tab, etc)
Expand Down Expand Up @@ -153,6 +155,7 @@ impl fmt::Display for Token {
Token::NationalStringLiteral(ref s) => write!(f, "N'{}'", s),
Token::HexStringLiteral(ref s) => write!(f, "X'{}'", s),
Token::CstyleEscapesString(ref s) => write!(f, "E'{}'", s),
Token::Parameter(ref s) => write!(f, "${}", s),
Token::Comma => f.write_str(","),
Token::Whitespace(ws) => write!(f, "{}", ws),
Token::DoubleEq => f.write_str("=="),
Expand Down Expand Up @@ -347,7 +350,6 @@ impl<'a> Tokenizer<'a> {

/// Get the next token or return None
fn next_token(&self, chars: &mut Peekable<Chars<'_>>) -> Result<Option<Token>, TokenizerError> {
// println!("next_token: {:?}", chars.peek());
match chars.peek() {
Some(&ch) => match ch {
' ' => self.consume_and_return(chars, Token::Whitespace(Whitespace::Space)),
Expand Down Expand Up @@ -589,6 +591,13 @@ impl<'a> Tokenizer<'a> {
_ => Ok(Some(Token::Colon)),
}
}
'$' => {
if let Some(parameter) = self.tokenize_parameter(chars) {
Ok(Some(parameter))
} else {
Ok(Some(Token::Char('$')))
}
}
';' => self.consume_and_return(chars, Token::SemiColon),
'\\' => self.consume_and_return(chars, Token::Backslash),
'[' => self.consume_and_return(chars, Token::LBracket),
Expand Down Expand Up @@ -752,6 +761,17 @@ impl<'a> Tokenizer<'a> {
chars.next();
Ok(Some(t))
}

fn tokenize_parameter(&self, chars: &mut Peekable<Chars<'_>>) -> Option<Token> {
chars.next(); // consume '$'

let s = peeking_take_while(chars, |ch| ch.is_ascii_digit());
if s.is_empty() {
None
} else {
Some(Token::Parameter(s))
}
}
}

/// Read from `chars` until `predicate` returns `false` or EOF is hit.
Expand Down
65 changes: 65 additions & 0 deletions src/sqlparser/tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,3 +1049,68 @@ fn parse_array() {
)),
);
}

#[test]
fn parse_param_symbol() {
let select = verified_only_select("SELECT $1");
assert_eq!(
SelectItem::UnnamedExpr(Expr::Parameter {
index: 1,
data_type: None
}),
select.projection[0]
);

let select = verified_only_select("SELECT *, $2 FROM t WHERE a = $1");
assert_eq!(
Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
op: BinaryOperator::Eq,
right: Box::new(Expr::Parameter {
index: 1,
data_type: None
})
},
select.selection.unwrap()
);
assert_eq!(
SelectItem::UnnamedExpr(Expr::Parameter {
index: 2,
data_type: None
}),
select.projection[1]
);

let select = verified_only_select("SELECT $4096::INT");
assert_eq!(
SelectItem::UnnamedExpr(Expr::Parameter {
index: 4096,
data_type: Some(DataType::Int)
}),
select.projection[0]
);

let select = verified_only_select("SELECT * FROM t WHERE a = $1::BIGINT");
assert_eq!(
Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new("a"))),
op: BinaryOperator::Eq,
right: Box::new(Expr::Parameter {
index: 1,
data_type: Some(DataType::BigInt)
})
},
select.selection.unwrap()
);

let query = verified_query("VALUES ($1)");
if let SetExpr::Values(values) = query.body {
assert_eq!(
values.0[0][0],
Expr::Parameter {
index: 1,
data_type: None
}
);
}
}

0 comments on commit 7c19b62

Please sign in to comment.