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 {})])
}