diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index c8f5ce667..674311a92 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -300,4 +300,8 @@ impl Dialect for GenericDialect { fn supports_select_item_multi_column_alias(&self) -> bool { true } + + fn supports_xml_expressions(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 1a2b9b19f..8dae15be8 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1702,6 +1702,17 @@ pub trait Dialect: Debug + Any { fn supports_select_item_multi_column_alias(&self) -> bool { false } + + /// Returns true if the dialect supports XML-related expressions + /// such as `xml ''` typed strings, XML functions like + /// `XMLCONCAT`, `XMLELEMENT`, etc. + /// + /// When this returns false, `xml` is treated as a regular identifier. + /// + /// [PostgreSQL](https://www.postgresql.org/docs/current/functions-xml.html) + fn supports_xml_expressions(&self) -> bool { + false + } } /// Operators for which precedence must be defined. diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index b99a8b5c3..982dc3649 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -310,4 +310,8 @@ impl Dialect for PostgreSqlDialect { fn supports_comma_separated_trim(&self) -> bool { true } + + fn supports_xml_expressions(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 60dd3e66d..40ea9a014 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1687,6 +1687,16 @@ impl<'a> Parser<'a> { } } + /// Returns true if the given [ObjectName] is a single unquoted + /// identifier matching `expected` (case-insensitive). + fn is_simple_unquoted_object_name(name: &ObjectName, expected: &str) -> bool { + if let [ObjectNamePart::Identifier(ident)] = name.0.as_slice() { + ident.quote_style.is_none() && ident.value.eq_ignore_ascii_case(expected) + } else { + false + } + } + /// Parse an expression prefix. pub fn parse_prefix(&mut self) -> Result { // allow the dialect to override prefix parsing @@ -1720,7 +1730,21 @@ impl<'a> Parser<'a> { // so given `NOT 'a' LIKE 'b'`, we'd accept `NOT` as a possible custom data type // name, resulting in `NOT 'a'` being recognized as a `TypedString` instead of // an unary negation `NOT ('a' LIKE 'b')`. To solve this, we don't accept the - // `type 'string'` syntax for the custom data types at all. + // `type 'string'` syntax for the custom data types at all ... + // + // ... with the exception of `xml '...'` on dialects that support XML + // expressions, which is a valid PostgreSQL typed string literal. + DataType::Custom(ref name, ref modifiers) + if modifiers.is_empty() + && Self::is_simple_unquoted_object_name(name, "xml") + && parser.dialect.supports_xml_expressions() => + { + Ok(Expr::TypedString(TypedString { + data_type: DataType::Custom(name.clone(), modifiers.clone()), + value: parser.parse_value()?, + uses_odbc_syntax: false, + })) + } DataType::Custom(..) => parser_err!("dummy", loc), // MySQL supports using the `BINARY` keyword as a cast to binary type. DataType::Binary(..) if self.dialect.supports_binary_kw_as_cast() => { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f5add3a63..4db5edeb3 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -18771,3 +18771,11 @@ fn parse_select_item_multi_column_alias() { .is_err() ); } + +#[test] +fn parse_non_pg_dialects_keep_xml_names_as_regular_identifiers() { + // On dialects that do NOT support XML expressions, bare `xml` should + // be treated as a regular column identifier, not a typed-string prefix. + let dialects = all_dialects_except(|d| d.supports_xml_expressions()); + dialects.verified_only_select("SELECT xml FROM t"); +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index af0f2be33..ff0476ca5 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -3750,6 +3750,25 @@ fn parse_on_commit() { pg_and_generic().verified_stmt("CREATE TEMPORARY TABLE table (COL INT) ON COMMIT DROP"); } +#[test] +fn parse_xml_typed_string() { + // xml '...' should parse as a TypedString on PostgreSQL and Generic + let sql = "SELECT xml ''"; + let select = pg_and_generic().verified_only_select(sql); + match expr_from_projection(&select.projection[0]) { + Expr::TypedString(TypedString { + data_type: DataType::Custom(name, modifiers), + value, + uses_odbc_syntax: false, + }) => { + assert_eq!(name.to_string(), "xml"); + assert!(modifiers.is_empty()); + assert_eq!(value.value, Value::SingleQuotedString("".to_string())); + } + other => panic!("Expected TypedString, got: {other:?}"), + } +} + fn pg() -> TestedDialects { TestedDialects::new(vec![Box::new(PostgreSqlDialect {})]) }