Skip to content

Commit e0ceacd

Browse files
committed
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.
1 parent 07790fe commit e0ceacd

File tree

5 files changed

+54
-23
lines changed

5 files changed

+54
-23
lines changed

src/sqlast/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub use self::value::Value;
2727

2828
pub use self::sql_operator::SQLOperator;
2929

30-
// This could be enhanced to remember the way the identifier was quoted
30+
/// Identifier name, in the originally quoted form (e.g. `"id"`)
3131
pub type SQLIdent = String;
3232

3333
/// SQL Abstract Syntax Tree (AST)
@@ -64,7 +64,8 @@ pub enum ASTNode {
6464
/// SQLValue
6565
SQLValue(Value),
6666
/// Scalar function call e.g. `LEFT(foo, 5)`
67-
SQLFunction { id: String, args: Vec<ASTNode> },
67+
/// TODO: this can be a compound SQLObjectName as well (for UDFs)
68+
SQLFunction { id: SQLIdent, args: Vec<ASTNode> },
6869
/// CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END
6970
SQLCase {
7071
// TODO: support optional operand for "simple case"
@@ -317,7 +318,7 @@ impl ToString for SQLObjectName {
317318
/// SQL assignment `foo = expr` as used in SQLUpdate
318319
#[derive(Debug, Clone, PartialEq)]
319320
pub struct SQLAssignment {
320-
id: String,
321+
id: SQLIdent,
321322
value: Box<ASTNode>,
322323
}
323324

src/sqlast/query.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pub enum JoinOperator {
134134
#[derive(Debug, Clone, PartialEq)]
135135
pub enum JoinConstraint {
136136
On(ASTNode),
137-
Using(Vec<String>),
137+
Using(Vec<SQLIdent>),
138138
Natural,
139139
}
140140

src/sqlast/table_key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use super::{SQLIdent, SQLObjectName};
33
#[derive(Debug, PartialEq, Clone)]
44
pub enum AlterOperation {
55
AddConstraint(TableKey),
6-
RemoveConstraint { name: String },
6+
RemoveConstraint { name: SQLIdent },
77
}
88

99
impl ToString for AlterOperation {

src/sqlparser.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,12 @@ impl Parser {
172172
})
173173
}
174174
_ => match self.peek_token() {
175-
Some(Token::LParen) => self.parse_function(&w.value),
175+
Some(Token::LParen) => self.parse_function(w.as_sql_ident()),
176176
Some(Token::Period) => {
177-
let mut id_parts: Vec<String> = vec![w.value];
177+
let mut id_parts: Vec<SQLIdent> = vec![w.as_sql_ident()];
178178
while self.consume_token(&Token::Period) {
179179
match self.next_token() {
180-
Some(Token::SQLWord(w)) => id_parts.push(w.value),
180+
Some(Token::SQLWord(w)) => id_parts.push(w.as_sql_ident()),
181181
_ => {
182182
return parser_err!(format!(
183183
"Error parsing compound identifier"
@@ -187,7 +187,7 @@ impl Parser {
187187
}
188188
Ok(ASTNode::SQLCompoundIdentifier(id_parts))
189189
}
190-
_ => Ok(ASTNode::SQLIdentifier(w.value)),
190+
_ => Ok(ASTNode::SQLIdentifier(w.as_sql_ident())),
191191
},
192192
},
193193
Token::Mult => Ok(ASTNode::SQLWildcard),
@@ -213,20 +213,17 @@ impl Parser {
213213
}
214214
}
215215

216-
pub fn parse_function(&mut self, id: &str) -> Result<ASTNode, ParserError> {
216+
pub fn parse_function(&mut self, id: SQLIdent) -> Result<ASTNode, ParserError> {
217217
self.expect_token(&Token::LParen)?;
218218
if self.consume_token(&Token::RParen) {
219219
Ok(ASTNode::SQLFunction {
220-
id: id.to_string(),
220+
id: id,
221221
args: vec![],
222222
})
223223
} else {
224224
let args = self.parse_expr_list()?;
225225
self.expect_token(&Token::RParen)?;
226-
Ok(ASTNode::SQLFunction {
227-
id: id.to_string(),
228-
args,
229-
})
226+
Ok(ASTNode::SQLFunction { id, args })
230227
}
231228
}
232229

@@ -573,7 +570,7 @@ impl Parser {
573570
Some(Token::Comma) => {
574571
self.next_token();
575572
columns.push(SQLColumnDef {
576-
name: column_name.value,
573+
name: column_name.as_sql_ident(),
577574
data_type: data_type,
578575
allow_null,
579576
is_primary,
@@ -584,7 +581,7 @@ impl Parser {
584581
Some(Token::RParen) => {
585582
self.next_token();
586583
columns.push(SQLColumnDef {
587-
name: column_name.value,
584+
name: column_name.as_sql_ident(),
588585
data_type: data_type,
589586
allow_null,
590587
is_primary,
@@ -622,15 +619,15 @@ impl Parser {
622619
}
623620
}
624621

625-
pub fn parse_table_key(&mut self, constraint_name: &str) -> Result<TableKey, ParserError> {
622+
pub fn parse_table_key(&mut self, constraint_name: SQLIdent) -> Result<TableKey, ParserError> {
626623
let is_primary_key = self.parse_keywords(vec!["PRIMARY", "KEY"]);
627624
let is_unique_key = self.parse_keywords(vec!["UNIQUE", "KEY"]);
628625
let is_foreign_key = self.parse_keywords(vec!["FOREIGN", "KEY"]);
629626
self.expect_token(&Token::LParen)?;
630627
let column_names = self.parse_column_names()?;
631628
self.expect_token(&Token::RParen)?;
632629
let key = Key {
633-
name: constraint_name.to_string(),
630+
name: constraint_name,
634631
columns: column_names,
635632
};
636633
if is_primary_key {
@@ -664,7 +661,7 @@ impl Parser {
664661
if self.parse_keywords(vec!["ADD", "CONSTRAINT"]) {
665662
match self.next_token() {
666663
Some(Token::SQLWord(ref id)) => {
667-
let table_key = self.parse_table_key(&id.value)?;
664+
let table_key = self.parse_table_key(id.as_sql_ident())?;
668665
Ok(AlterOperation::AddConstraint(table_key))
669666
}
670667
_ => {
@@ -1012,8 +1009,7 @@ impl Parser {
10121009
Some(Token::SQLWord(ref w))
10131010
if after_as || !reserved_kwds.contains(&w.keyword.as_str()) =>
10141011
{
1015-
// have to clone here until #![feature(bind_by_move_pattern_guards)] is enabled by default
1016-
Ok(Some(w.value.clone()))
1012+
Ok(Some(w.as_sql_ident()))
10171013
}
10181014
ref not_an_ident if after_as => parser_err!(format!(
10191015
"Expected an identifier after AS, got {:?}",
@@ -1036,7 +1032,7 @@ impl Parser {
10361032
match token {
10371033
Some(Token::SQLWord(s)) if expect_identifier => {
10381034
expect_identifier = false;
1039-
idents.push(s.to_string());
1035+
idents.push(s.as_sql_ident());
10401036
}
10411037
Some(token) if token == separator && !expect_identifier => {
10421038
expect_identifier = true;
@@ -1386,3 +1382,9 @@ impl Parser {
13861382
}
13871383
}
13881384
}
1385+
1386+
impl SQLWord {
1387+
pub fn as_sql_ident(&self) -> SQLIdent {
1388+
self.to_string()
1389+
}
1390+
}

tests/sqlparser_generic.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,34 @@ fn parse_select_version() {
427427
);
428428
}
429429

430+
#[test]
431+
fn parse_delimited_identifiers() {
432+
// check that quoted identifiers in any position remain quoted after serialization
433+
let sql = r#"SELECT "alias"."bar baz", "myfun"(), "simple id" FROM "a table" AS "alias""#;
434+
let select = verified_only_select(sql);
435+
// check SELECT
436+
assert_eq!(3, select.projection.len());
437+
assert_eq!(
438+
&ASTNode::SQLCompoundIdentifier(vec![r#""alias""#.to_string(), r#""bar baz""#.to_string()]),
439+
expr_from_projection(&select.projection[0]),
440+
);
441+
assert_eq!(
442+
&ASTNode::SQLFunction {
443+
id: r#""myfun""#.to_string(),
444+
args: vec![]
445+
},
446+
expr_from_projection(&select.projection[1]),
447+
);
448+
assert_eq!(
449+
&ASTNode::SQLIdentifier(r#""simple id""#.to_string()),
450+
expr_from_projection(&select.projection[2]),
451+
);
452+
453+
verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#);
454+
verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#);
455+
//TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#);
456+
}
457+
430458
#[test]
431459
fn parse_parens() {
432460
use self::ASTNode::*;

0 commit comments

Comments
 (0)