mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-08 07:00:33 +00:00
Store original, quoted form in SQLIdent
Also move more things to use SQLIdent instead of String in the hope of making it a newtype eventually. Add tests that quoted identifiers round-trip parsing/serialization correctly.
This commit is contained in:
parent
07790fe4c4
commit
e0ceacd1ad
5 changed files with 54 additions and 23 deletions
|
@ -27,7 +27,7 @@ pub use self::value::Value;
|
||||||
|
|
||||||
pub use self::sql_operator::SQLOperator;
|
pub use self::sql_operator::SQLOperator;
|
||||||
|
|
||||||
// This could be enhanced to remember the way the identifier was quoted
|
/// Identifier name, in the originally quoted form (e.g. `"id"`)
|
||||||
pub type SQLIdent = String;
|
pub type SQLIdent = String;
|
||||||
|
|
||||||
/// SQL Abstract Syntax Tree (AST)
|
/// SQL Abstract Syntax Tree (AST)
|
||||||
|
@ -64,7 +64,8 @@ pub enum ASTNode {
|
||||||
/// SQLValue
|
/// SQLValue
|
||||||
SQLValue(Value),
|
SQLValue(Value),
|
||||||
/// Scalar function call e.g. `LEFT(foo, 5)`
|
/// Scalar function call e.g. `LEFT(foo, 5)`
|
||||||
SQLFunction { id: String, args: Vec<ASTNode> },
|
/// TODO: this can be a compound SQLObjectName as well (for UDFs)
|
||||||
|
SQLFunction { id: SQLIdent, args: Vec<ASTNode> },
|
||||||
/// CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END
|
/// CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END
|
||||||
SQLCase {
|
SQLCase {
|
||||||
// TODO: support optional operand for "simple case"
|
// TODO: support optional operand for "simple case"
|
||||||
|
@ -317,7 +318,7 @@ impl ToString for SQLObjectName {
|
||||||
/// SQL assignment `foo = expr` as used in SQLUpdate
|
/// SQL assignment `foo = expr` as used in SQLUpdate
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct SQLAssignment {
|
pub struct SQLAssignment {
|
||||||
id: String,
|
id: SQLIdent,
|
||||||
value: Box<ASTNode>,
|
value: Box<ASTNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ pub enum JoinOperator {
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum JoinConstraint {
|
pub enum JoinConstraint {
|
||||||
On(ASTNode),
|
On(ASTNode),
|
||||||
Using(Vec<String>),
|
Using(Vec<SQLIdent>),
|
||||||
Natural,
|
Natural,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use super::{SQLIdent, SQLObjectName};
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum AlterOperation {
|
pub enum AlterOperation {
|
||||||
AddConstraint(TableKey),
|
AddConstraint(TableKey),
|
||||||
RemoveConstraint { name: String },
|
RemoveConstraint { name: SQLIdent },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for AlterOperation {
|
impl ToString for AlterOperation {
|
||||||
|
|
|
@ -172,12 +172,12 @@ impl Parser {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => match self.peek_token() {
|
_ => match self.peek_token() {
|
||||||
Some(Token::LParen) => self.parse_function(&w.value),
|
Some(Token::LParen) => self.parse_function(w.as_sql_ident()),
|
||||||
Some(Token::Period) => {
|
Some(Token::Period) => {
|
||||||
let mut id_parts: Vec<String> = vec![w.value];
|
let mut id_parts: Vec<SQLIdent> = vec![w.as_sql_ident()];
|
||||||
while self.consume_token(&Token::Period) {
|
while self.consume_token(&Token::Period) {
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Some(Token::SQLWord(w)) => id_parts.push(w.value),
|
Some(Token::SQLWord(w)) => id_parts.push(w.as_sql_ident()),
|
||||||
_ => {
|
_ => {
|
||||||
return parser_err!(format!(
|
return parser_err!(format!(
|
||||||
"Error parsing compound identifier"
|
"Error parsing compound identifier"
|
||||||
|
@ -187,7 +187,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
Ok(ASTNode::SQLCompoundIdentifier(id_parts))
|
Ok(ASTNode::SQLCompoundIdentifier(id_parts))
|
||||||
}
|
}
|
||||||
_ => Ok(ASTNode::SQLIdentifier(w.value)),
|
_ => Ok(ASTNode::SQLIdentifier(w.as_sql_ident())),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Token::Mult => Ok(ASTNode::SQLWildcard),
|
Token::Mult => Ok(ASTNode::SQLWildcard),
|
||||||
|
@ -213,20 +213,17 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_function(&mut self, id: &str) -> Result<ASTNode, ParserError> {
|
pub fn parse_function(&mut self, id: SQLIdent) -> Result<ASTNode, ParserError> {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
if self.consume_token(&Token::RParen) {
|
if self.consume_token(&Token::RParen) {
|
||||||
Ok(ASTNode::SQLFunction {
|
Ok(ASTNode::SQLFunction {
|
||||||
id: id.to_string(),
|
id: id,
|
||||||
args: vec![],
|
args: vec![],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let args = self.parse_expr_list()?;
|
let args = self.parse_expr_list()?;
|
||||||
self.expect_token(&Token::RParen)?;
|
self.expect_token(&Token::RParen)?;
|
||||||
Ok(ASTNode::SQLFunction {
|
Ok(ASTNode::SQLFunction { id, args })
|
||||||
id: id.to_string(),
|
|
||||||
args,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +570,7 @@ impl Parser {
|
||||||
Some(Token::Comma) => {
|
Some(Token::Comma) => {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
columns.push(SQLColumnDef {
|
columns.push(SQLColumnDef {
|
||||||
name: column_name.value,
|
name: column_name.as_sql_ident(),
|
||||||
data_type: data_type,
|
data_type: data_type,
|
||||||
allow_null,
|
allow_null,
|
||||||
is_primary,
|
is_primary,
|
||||||
|
@ -584,7 +581,7 @@ impl Parser {
|
||||||
Some(Token::RParen) => {
|
Some(Token::RParen) => {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
columns.push(SQLColumnDef {
|
columns.push(SQLColumnDef {
|
||||||
name: column_name.value,
|
name: column_name.as_sql_ident(),
|
||||||
data_type: data_type,
|
data_type: data_type,
|
||||||
allow_null,
|
allow_null,
|
||||||
is_primary,
|
is_primary,
|
||||||
|
@ -622,7 +619,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_table_key(&mut self, constraint_name: &str) -> Result<TableKey, ParserError> {
|
pub fn parse_table_key(&mut self, constraint_name: SQLIdent) -> Result<TableKey, ParserError> {
|
||||||
let is_primary_key = self.parse_keywords(vec!["PRIMARY", "KEY"]);
|
let is_primary_key = self.parse_keywords(vec!["PRIMARY", "KEY"]);
|
||||||
let is_unique_key = self.parse_keywords(vec!["UNIQUE", "KEY"]);
|
let is_unique_key = self.parse_keywords(vec!["UNIQUE", "KEY"]);
|
||||||
let is_foreign_key = self.parse_keywords(vec!["FOREIGN", "KEY"]);
|
let is_foreign_key = self.parse_keywords(vec!["FOREIGN", "KEY"]);
|
||||||
|
@ -630,7 +627,7 @@ impl Parser {
|
||||||
let column_names = self.parse_column_names()?;
|
let column_names = self.parse_column_names()?;
|
||||||
self.expect_token(&Token::RParen)?;
|
self.expect_token(&Token::RParen)?;
|
||||||
let key = Key {
|
let key = Key {
|
||||||
name: constraint_name.to_string(),
|
name: constraint_name,
|
||||||
columns: column_names,
|
columns: column_names,
|
||||||
};
|
};
|
||||||
if is_primary_key {
|
if is_primary_key {
|
||||||
|
@ -664,7 +661,7 @@ impl Parser {
|
||||||
if self.parse_keywords(vec!["ADD", "CONSTRAINT"]) {
|
if self.parse_keywords(vec!["ADD", "CONSTRAINT"]) {
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Some(Token::SQLWord(ref id)) => {
|
Some(Token::SQLWord(ref id)) => {
|
||||||
let table_key = self.parse_table_key(&id.value)?;
|
let table_key = self.parse_table_key(id.as_sql_ident())?;
|
||||||
Ok(AlterOperation::AddConstraint(table_key))
|
Ok(AlterOperation::AddConstraint(table_key))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -1012,8 +1009,7 @@ impl Parser {
|
||||||
Some(Token::SQLWord(ref w))
|
Some(Token::SQLWord(ref w))
|
||||||
if after_as || !reserved_kwds.contains(&w.keyword.as_str()) =>
|
if after_as || !reserved_kwds.contains(&w.keyword.as_str()) =>
|
||||||
{
|
{
|
||||||
// have to clone here until #![feature(bind_by_move_pattern_guards)] is enabled by default
|
Ok(Some(w.as_sql_ident()))
|
||||||
Ok(Some(w.value.clone()))
|
|
||||||
}
|
}
|
||||||
ref not_an_ident if after_as => parser_err!(format!(
|
ref not_an_ident if after_as => parser_err!(format!(
|
||||||
"Expected an identifier after AS, got {:?}",
|
"Expected an identifier after AS, got {:?}",
|
||||||
|
@ -1036,7 +1032,7 @@ impl Parser {
|
||||||
match token {
|
match token {
|
||||||
Some(Token::SQLWord(s)) if expect_identifier => {
|
Some(Token::SQLWord(s)) if expect_identifier => {
|
||||||
expect_identifier = false;
|
expect_identifier = false;
|
||||||
idents.push(s.to_string());
|
idents.push(s.as_sql_ident());
|
||||||
}
|
}
|
||||||
Some(token) if token == separator && !expect_identifier => {
|
Some(token) if token == separator && !expect_identifier => {
|
||||||
expect_identifier = true;
|
expect_identifier = true;
|
||||||
|
@ -1386,3 +1382,9 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SQLWord {
|
||||||
|
pub fn as_sql_ident(&self) -> SQLIdent {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -427,6 +427,34 @@ fn parse_select_version() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_delimited_identifiers() {
|
||||||
|
// check that quoted identifiers in any position remain quoted after serialization
|
||||||
|
let sql = r#"SELECT "alias"."bar baz", "myfun"(), "simple id" FROM "a table" AS "alias""#;
|
||||||
|
let select = verified_only_select(sql);
|
||||||
|
// check SELECT
|
||||||
|
assert_eq!(3, select.projection.len());
|
||||||
|
assert_eq!(
|
||||||
|
&ASTNode::SQLCompoundIdentifier(vec![r#""alias""#.to_string(), r#""bar baz""#.to_string()]),
|
||||||
|
expr_from_projection(&select.projection[0]),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&ASTNode::SQLFunction {
|
||||||
|
id: r#""myfun""#.to_string(),
|
||||||
|
args: vec![]
|
||||||
|
},
|
||||||
|
expr_from_projection(&select.projection[1]),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&ASTNode::SQLIdentifier(r#""simple id""#.to_string()),
|
||||||
|
expr_from_projection(&select.projection[2]),
|
||||||
|
);
|
||||||
|
|
||||||
|
verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#);
|
||||||
|
verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#);
|
||||||
|
//TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_parens() {
|
fn parse_parens() {
|
||||||
use self::ASTNode::*;
|
use self::ASTNode::*;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue