Skip to content

Commit 295fab0

Browse files
SQLite: Support multi-word type names
1 parent 1ba145b commit 295fab0

6 files changed

Lines changed: 208 additions & 4 deletions

File tree

‎src/ast/data_type.rs‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ use serde::{Deserialize, Serialize};
2525
#[cfg(feature = "visitor")]
2626
use sqlparser_derive::{Visit, VisitMut};
2727

28-
use crate::ast::{display_comma_separated, Expr, ObjectName, StructField, UnionField};
28+
use crate::ast::{
29+
display_comma_separated, display_separated, Expr, Ident, ObjectName, StructField, UnionField,
30+
};
2931

3032
use super::{value::escape_single_quote_string, ColumnDef};
3133

@@ -439,6 +441,8 @@ pub enum DataType {
439441
VarBit(Option<u64>),
440442
/// Custom types.
441443
Custom(ObjectName, Vec<String>),
444+
/// Multi-word type name whose parts are space-separated, as in SQLite's `typename` grammar.
445+
CustomMultiWord(Vec<Ident>, Vec<String>),
442446
/// Arrays.
443447
Array(ArrayElemTypeDef),
444448
/// Map, see [ClickHouse], [Hive].
@@ -742,6 +746,13 @@ impl fmt::Display for DataType {
742746
write!(f, "{}({})", ty, modifiers.join(", "))
743747
}
744748
}
749+
DataType::CustomMultiWord(parts, modifiers) => {
750+
write!(f, "{}", display_separated(parts, " "))?;
751+
if !modifiers.is_empty() {
752+
write!(f, "({})", modifiers.join(", "))?;
753+
}
754+
Ok(())
755+
}
745756
DataType::Enum(vals, bits) => {
746757
match bits {
747758
Some(bits) => write!(f, "ENUM{bits}"),

‎src/dialect/mod.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,12 @@ pub trait Dialect: Debug + Any {
516516
false
517517
}
518518

519+
/// Returns true if type names may consist of multiple space-separated identifiers
520+
/// or string literals, as in SQLite's `typename` grammar.
521+
fn supports_multiword_type_names(&self) -> bool {
522+
false
523+
}
524+
519525
/// Returns true if the dialects supports specifying null treatment
520526
/// as part of a window function's parameter list as opposed
521527
/// to after the parameter list.

‎src/dialect/sqlite.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ impl Dialect for SQLiteDialect {
130130
true
131131
}
132132

133+
fn supports_multiword_type_names(&self) -> bool {
134+
true
135+
}
136+
133137
fn supports_double_eq_assignment(&self) -> bool {
134138
true
135139
}

‎src/parser/mod.rs‎

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,7 +1830,7 @@ impl<'a> Parser<'a> {
18301830
uses_odbc_syntax: false,
18311831
}))
18321832
}
1833-
DataType::Custom(..) => parser_err!("dummy", loc),
1833+
DataType::Custom(..) | DataType::CustomMultiWord(..) => parser_err!("dummy", loc),
18341834
// MySQL supports using the `BINARY` keyword as a cast to binary type.
18351835
DataType::Binary(..) if self.dialect.supports_binary_kw_as_cast() => {
18361836
Ok(Expr::Cast {
@@ -9630,7 +9630,9 @@ impl<'a> Parser<'a> {
96309630
| Keyword::GENERATED
96319631
| Keyword::AS
96329632
),
9633-
_ => true, // e.g. comma immediately after column name
9633+
// A single-quoted string is a valid type name in SQLite's ids grammar.
9634+
Token::SingleQuotedString(_) => false,
9635+
_ => true,
96349636
}
96359637
} else {
96369638
false
@@ -13351,13 +13353,21 @@ impl<'a> Parser<'a> {
1335113353
}
1335213354
}
1335313355
Keyword::SIGNED => {
13356+
if self.dialect.supports_multiword_type_names() {
13357+
self.prev_token();
13358+
return self.parse_multiword_type_name();
13359+
}
1335413360
if self.parse_keyword(Keyword::INTEGER) {
1335513361
Ok(DataType::SignedInteger)
1335613362
} else {
1335713363
Ok(DataType::Signed)
1335813364
}
1335913365
}
1336013366
Keyword::UNSIGNED => {
13367+
if self.dialect.supports_multiword_type_names() {
13368+
self.prev_token();
13369+
return self.parse_multiword_type_name();
13370+
}
1336113371
if self.parse_keyword(Keyword::INTEGER) {
1336213372
Ok(DataType::UnsignedInteger)
1336313373
} else {
@@ -13372,6 +13382,9 @@ impl<'a> Parser<'a> {
1337213382
}
1337313383
_ => {
1337413384
self.prev_token();
13385+
if self.dialect.supports_multiword_type_names() {
13386+
return self.parse_multiword_type_name();
13387+
}
1337513388
let type_name = self.parse_object_name(false)?;
1337613389
if let Some(modifiers) = self.parse_optional_type_modifiers()? {
1337713390
Ok(DataType::Custom(type_name, modifiers))
@@ -13380,6 +13393,10 @@ impl<'a> Parser<'a> {
1338013393
}
1338113394
}
1338213395
},
13396+
Token::SingleQuotedString(_) if self.dialect.supports_multiword_type_names() => {
13397+
self.prev_token();
13398+
return self.parse_multiword_type_name();
13399+
}
1338313400
_ => self.expected_at("a data type name", next_token_index),
1338413401
}?;
1338513402

@@ -14511,6 +14528,125 @@ impl<'a> Parser<'a> {
1451114528
}
1451214529
}
1451314530

14531+
/// Parse a space-separated multi-word typename per SQLite's `typename` grammar.
14532+
fn parse_multiword_type_name(
14533+
&mut self,
14534+
) -> Result<(DataType, MatchedTrailingBracket), ParserError> {
14535+
let mut parts: Vec<Ident> = vec![];
14536+
let first = self.next_token();
14537+
match first.token {
14538+
Token::Word(w) => parts.push(w.into_ident(first.span)),
14539+
Token::SingleQuotedString(s) => parts.push(Ident::with_quote('\'', s)),
14540+
_ => return self.expected("a data type name", first),
14541+
}
14542+
loop {
14543+
let can_extend = {
14544+
let peeked = self.peek_token_ref();
14545+
match &peeked.token {
14546+
Token::Word(w) if w.keyword == Keyword::GENERATED => {
14547+
// `GENERATED` starts a constraint only when followed
14548+
// by `ALWAYS`, otherwise it is a type name word.
14549+
!matches!(
14550+
self.peek_nth_token(1).token,
14551+
Token::Word(w) if w.keyword == Keyword::ALWAYS
14552+
)
14553+
}
14554+
Token::Word(w) => {
14555+
// Fully-reserved keywords end the type name, see
14556+
// <https://www.sqlite.org/lang_keywords.html>
14557+
!matches!(
14558+
w.keyword,
14559+
Keyword::ADD
14560+
| Keyword::ALL
14561+
| Keyword::ALTER
14562+
| Keyword::AND
14563+
| Keyword::AS
14564+
| Keyword::AUTOINCREMENT
14565+
| Keyword::BETWEEN
14566+
| Keyword::CASE
14567+
| Keyword::CHECK
14568+
| Keyword::COLLATE
14569+
| Keyword::COMMIT
14570+
| Keyword::CONSTRAINT
14571+
| Keyword::CREATE
14572+
| Keyword::CROSS
14573+
| Keyword::DEFAULT
14574+
| Keyword::DELETE
14575+
| Keyword::DEFERRABLE
14576+
| Keyword::DISTINCT
14577+
| Keyword::DROP
14578+
| Keyword::ELSE
14579+
| Keyword::ESCAPE
14580+
| Keyword::EXCEPT
14581+
| Keyword::EXISTS
14582+
| Keyword::FOREIGN
14583+
| Keyword::FROM
14584+
| Keyword::FULL
14585+
| Keyword::GROUP
14586+
| Keyword::HAVING
14587+
| Keyword::IN
14588+
| Keyword::INDEX
14589+
| Keyword::INNER
14590+
| Keyword::INSERT
14591+
| Keyword::INTERSECT
14592+
| Keyword::INTO
14593+
| Keyword::IS
14594+
| Keyword::JOIN
14595+
| Keyword::LEFT
14596+
| Keyword::LIMIT
14597+
| Keyword::NATURAL
14598+
| Keyword::NOT
14599+
| Keyword::NULL
14600+
| Keyword::NOTHING
14601+
| Keyword::NOTNULL
14602+
| Keyword::ON
14603+
| Keyword::OR
14604+
| Keyword::ORDER
14605+
| Keyword::OUTER
14606+
| Keyword::PRIMARY
14607+
| Keyword::REFERENCES
14608+
| Keyword::RETURNING
14609+
| Keyword::RIGHT
14610+
| Keyword::SELECT
14611+
| Keyword::SET
14612+
| Keyword::TABLE
14613+
| Keyword::THEN
14614+
| Keyword::TO
14615+
| Keyword::TRANSACTION
14616+
| Keyword::UNIQUE
14617+
| Keyword::UNION
14618+
| Keyword::UPDATE
14619+
| Keyword::USING
14620+
| Keyword::VALUES
14621+
| Keyword::WHEN
14622+
| Keyword::WHERE
14623+
)
14624+
}
14625+
Token::SingleQuotedString(_) => true,
14626+
_ => false,
14627+
}
14628+
};
14629+
if !can_extend {
14630+
break;
14631+
}
14632+
let next = self.next_token();
14633+
match next.token {
14634+
Token::Word(w) => parts.push(w.into_ident(next.span)),
14635+
Token::SingleQuotedString(s) => parts.push(Ident::with_quote('\'', s)),
14636+
_ => break,
14637+
}
14638+
}
14639+
let modifiers = self.parse_optional_type_modifiers()?.unwrap_or_default();
14640+
if parts.len() == 1 {
14641+
Ok((
14642+
DataType::Custom(ObjectName::from(vec![parts.remove(0)]), modifiers),
14643+
false.into(),
14644+
))
14645+
} else {
14646+
Ok((DataType::CustomMultiWord(parts, modifiers), false.into()))
14647+
}
14648+
}
14649+
1451414650
fn parse_structured_object_type_def(&mut self) -> Result<Vec<ColumnDef>, ParserError> {
1451514651
self.expect_token(&Token::LParen)?;
1451614652
if self.consume_token(&Token::RParen) {

‎tests/sqlparser_common.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13743,7 +13743,8 @@ fn test_xmltable() {
1374313743
);
1374413744

1374513745
// Example from DB2 docs without explicit PASSING clause: https://www.ibm.com/docs/en/db2/12.1.0?topic=xquery-simple-column-name-passing-xmlexists-xmlquery-xmltable
13746-
all_dialects().verified_only_select(
13746+
// Multi-word type names would read `PATH` as part of the column type
13747+
all_dialects_where(|d| !d.supports_multiword_type_names()).verified_only_select(
1374713748
"SELECT X.* FROM T1, XMLTABLE('$CUSTLIST/customers/customerinfo' COLUMNS \"Cid\" BIGINT PATH '@Cid', \"Info\" XML PATH 'document{.}', \"History\" XML PATH 'NULL') AS X"
1374813749
);
1374913750

‎tests/sqlparser_sqlite.rs‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,52 @@ fn parse_update_set_double_eq() {
974974
);
975975
}
976976

977+
#[test]
978+
fn test_multiword_type_names() {
979+
// A SQLite type name is one or more names or strings
980+
sqlite().verified_stmt("CREATE TABLE t (a UNSIGNED BIG INT)");
981+
sqlite().verified_stmt("CREATE TABLE t (a foo bar baz)");
982+
sqlite().verified_stmt("CREATE TABLE t (a 'text')");
983+
sqlite().verified_stmt("SELECT CAST(a AS foo bar) FROM t");
984+
sqlite().verified_stmt("ALTER TABLE t ADD b foo bar");
985+
// Keywords without a dedicated parser arm also form multi-word type names
986+
sqlite().verified_stmt("CREATE TABLE t (a NATIVE CHARACTER(70))");
987+
sqlite().verified_stmt("CREATE TABLE t (a VARYING CHARACTER(10))");
988+
sqlite().verified_stmt("SELECT CAST(x AS VARYING CHARACTER) FROM t");
989+
// Column constraints still terminate the type name
990+
sqlite().verified_stmt("CREATE TABLE t (a foo bar NOT NULL)");
991+
sqlite().verified_stmt("CREATE TABLE t (a foo bar DEFAULT 0)");
992+
// Standard constraint keywords are not consumed as type words
993+
sqlite().verified_stmt("CREATE TABLE t (a INT PRIMARY KEY)");
994+
sqlite().verified_stmt("CREATE TABLE t (a TEXT COLLATE NOCASE)");
995+
sqlite().verified_stmt("CREATE TABLE t (a INT GENERATED ALWAYS AS (1))");
996+
sqlite().verified_stmt("CREATE TABLE t (a INT AS (1))");
997+
// GENERATED is a type word unless ALWAYS follows
998+
sqlite().verified_stmt("CREATE TABLE t (a foo GENERATED, b INT)");
999+
sqlite().verified_stmt("CREATE TABLE t (a foo GENERATED ALWAYS AS (1))");
1000+
// PATH is a valid type word, not a terminator
1001+
sqlite().verified_stmt("CREATE TABLE t (a foo bar PATH)");
1002+
sqlite().verified_stmt("CREATE TABLE t (a foo PATH bar)");
1003+
// fully-reserved keywords end the type name
1004+
assert!(sqlite()
1005+
.parse_sql_statements("CREATE TABLE t (a foo AND bar)")
1006+
.is_err());
1007+
assert!(sqlite()
1008+
.parse_sql_statements("CREATE TABLE t (a foo bar ON)")
1009+
.is_err());
1010+
assert!(sqlite()
1011+
.parse_sql_statements("CREATE TABLE t (a foo bar SELECT)")
1012+
.is_err());
1013+
}
1014+
1015+
#[test]
1016+
fn test_multiword_type_names_rejected_by_other_dialects() {
1017+
// GenericDialect does not support multi-word type names
1018+
assert!(TestedDialects::new(vec![Box::new(GenericDialect {})])
1019+
.parse_sql_statements("CREATE TABLE t (a foo bar)")
1020+
.is_err());
1021+
}
1022+
9771023
#[test]
9781024
fn parse_create_table_string_column_names() {
9791025
sqlite().verified_stmt("CREATE TABLE t ('a')");

0 commit comments

Comments
 (0)