Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/sql-parser/src/ast/defs/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
// limitations under the License.

use mz_ore::str::StrExt;
use mz_sql_lexer::keywords::{ALL, ANY, AS, DISTINCT, INTO, Keyword, LIST, PREPARE, SOME, WHEN};
use mz_sql_lexer::keywords::{
ALL, ANY, AS, DISTINCT, INTO, Keyword, LIST, MAP, PREPARE, SOME, WHEN,
};
use mz_sql_lexer::lexer::{IdentString, MAX_IDENTIFIER_LENGTH};
use serde::{Deserialize, Serialize};
use std::fmt;
Expand Down Expand Up @@ -332,11 +334,17 @@ impl Ident {
// `LIST` followed by `[` re-lexes as a `LIST[...]` literal
// (`list[1]` is a valid one-element list), so a bare `list`
// identifier that gets subscripted — `"list"[1]` — would
// reparse as a list literal instead of a subscript. (`ARRAY`
// is reserved-in-scalar-expression and so already quoted;
// `MAP[...]` requires `=>`, so `map[1]` is unambiguously a
// subscript.)
// reparse as a list literal instead of a subscript.
// (`ARRAY` is reserved-in-scalar-expression and so already
// quoted.)
|| kw == LIST
// An option value may be a `MAP[k => v]` literal, and that
// grammar commits to the map form on the `MAP` keyword
// alone, then demands `[`. So a bare `map` option value —
// `CREATE SINK … (TOPIC = map)` — fails to reparse rather
// than staying an identifier. Unlike expression position,
// there is no next-token lookahead to fall back on.
|| kw == MAP
// `DEALLOCATE [PREPARE] <name>` accepts an optional
// `PREPARE` keyword before the name, so a bare `prepare`
// name is consumed as that keyword on reparse, leaving no
Expand Down
22 changes: 22 additions & 0 deletions src/sql-parser/tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,28 @@ fn test_list_keyword_bare_identifier_subscript_display_roundtrip() {
}
}

#[mz_ore::test]
#[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
fn test_map_keyword_bare_identifier_option_value_display_roundtrip() {
// An option value may be a `MAP[k => v]` literal, and that grammar commits to
// the map form on the `MAP` keyword alone and then demands `[` — with no
// next-token lookahead to fall back on, unlike expression position. So a bare
// `map` option value fails to reparse ("Expected left square bracket") and
// `can_be_printed_bare` must quote it. Regression for the sql_roundtrip fuzz
// finding `CREATE SINK … (TOPIC = "map")`.
for sql in [
r#"CREATE SINK s FROM t INTO KAFKA CONNECTION c (TOPIC = "map") FORMAT BYTES ENVELOPE DEBEZIUM"#,
r#"CREATE SOURCE s FROM KAFKA CONNECTION c (TOPIC = "map") FORMAT BYTES"#,
r#"CREATE MATERIALIZED VIEW v WITH (PARTITION BY = "map") AS SELECT 1"#,
r#"SELECT "map""#,
r#"SELECT "map"[1]"#,
// The map literal itself still prints as the special form.
r#"SELECT map['a' => 1]"#,
] {
assert_display_roundtrips(sql);
}
}

#[mz_ore::test]
#[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `rust_psm_stack_pointer` on OS `linux`
fn test_table_function_special_name_display_roundtrip() {
Expand Down
2 changes: 1 addition & 1 deletion src/sql-parser/tests/testdata/select
Original file line number Diff line number Diff line change
Expand Up @@ -2102,6 +2102,6 @@ parse-statement
SELECT "true", "false", "null", "array", "list", "map", "case", "cast", "coalesce", "greatest", "least", "nullif", "exists", "extract", "not", "row", "trim", "position", "substring"
FROM iffy_colnames
----
SELECT "true", "false", "null", "array", "list", map, "case", "cast", coalesce, greatest, least, nullif, exists, extract, "not", row, trim, position, substring FROM iffy_colnames
SELECT "true", "false", "null", "array", "list", "map", "case", "cast", coalesce, greatest, least, nullif, exists, extract, "not", row, trim, position, substring FROM iffy_colnames
=>
Select(SelectStatement { query: Query { ctes: Simple([]), body: Select(Select { distinct: None, projection: [Expr { expr: Identifier([Ident("true")]), alias: None }, Expr { expr: Identifier([Ident("false")]), alias: None }, Expr { expr: Identifier([Ident("null")]), alias: None }, Expr { expr: Identifier([Ident("array")]), alias: None }, Expr { expr: Identifier([Ident("list")]), alias: None }, Expr { expr: Identifier([Ident("map")]), alias: None }, Expr { expr: Identifier([Ident("case")]), alias: None }, Expr { expr: Identifier([Ident("cast")]), alias: None }, Expr { expr: Identifier([Ident("coalesce")]), alias: None }, Expr { expr: Identifier([Ident("greatest")]), alias: None }, Expr { expr: Identifier([Ident("least")]), alias: None }, Expr { expr: Identifier([Ident("nullif")]), alias: None }, Expr { expr: Identifier([Ident("exists")]), alias: None }, Expr { expr: Identifier([Ident("extract")]), alias: None }, Expr { expr: Identifier([Ident("not")]), alias: None }, Expr { expr: Identifier([Ident("row")]), alias: None }, Expr { expr: Identifier([Ident("trim")]), alias: None }, Expr { expr: Identifier([Ident("position")]), alias: None }, Expr { expr: Identifier([Ident("substring")]), alias: None }], from: [TableWithJoins { relation: Table { name: Name(UnresolvedItemName([Ident("iffy_colnames")])), alias: None }, joins: [] }], selection: None, group_by: [], having: None, qualify: None, options: [] }), order_by: [], limit: None, offset: None }, as_of: None })
2 changes: 1 addition & 1 deletion test/sqllogictest/pretty.slt
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ SELECT
"null",
"array",
"list",
map,
"map",
"case",
"cast",
coalesce,
Expand Down
2 changes: 1 addition & 1 deletion test/sqllogictest/quote_ident.slt
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ SELECT quote_ident('true'), quote_ident('false'), quote_ident('null'), quote_ide
"null"
"array"
"list"
map
"map"
"case"
"cast"
coalesce
Expand Down
Loading