Skip to content

Commit ffa0df7

Browse files
feat(parser): Support Oracle/Postgres XMLNAMESPACES in XMLTABLE (#4643)
* feat(parser): Support Oracle/Postgres XMLNAMESPACES in XMLTABLE * Update sqlglot/parser.py * Update sqlglot/parser.py --------- Co-authored-by: Jo <[email protected]>
1 parent 761e835 commit ffa0df7

File tree

5 files changed

+52
-6
lines changed

5 files changed

+52
-6
lines changed

sqlglot/expressions.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6782,7 +6782,17 @@ class XMLElement(Func):
67826782

67836783

67846784
class XMLTable(Func):
6785-
arg_types = {"this": True, "passing": False, "columns": False, "by_ref": False}
6785+
arg_types = {
6786+
"this": True,
6787+
"namespaces": False,
6788+
"passing": False,
6789+
"columns": False,
6790+
"by_ref": False,
6791+
}
6792+
6793+
6794+
class XMLNamespace(Expression):
6795+
pass
67866796

67876797

67886798
class Year(Func):

sqlglot/generator.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4738,9 +4738,15 @@ def analyze_sql(self, expression: exp.Analyze) -> str:
47384738

47394739
def xmltable_sql(self, expression: exp.XMLTable) -> str:
47404740
this = self.sql(expression, "this")
4741+
namespaces = self.expressions(expression, key="namespaces")
4742+
namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else ""
47414743
passing = self.expressions(expression, key="passing")
47424744
passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else ""
47434745
columns = self.expressions(expression, key="columns")
47444746
columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else ""
47454747
by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else ""
4746-
return f"XMLTABLE({self.sep('')}{self.indent(this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4748+
return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4749+
4750+
def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:
4751+
this = self.sql(expression, "this")
4752+
return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}"

sqlglot/parser.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6212,11 +6212,16 @@ def _parse_convert(
62126212
return self.expression(exp.Cast if strict else exp.TryCast, this=this, to=to, safe=safe)
62136213

62146214
def _parse_xml_table(self) -> exp.XMLTable:
6215-
this = self._parse_string()
6216-
6215+
namespaces = None
62176216
passing = None
62186217
columns = None
62196218

6219+
if self._match_text_seq("XMLNAMESPACES", "("):
6220+
namespaces = self._parse_xml_namespace()
6221+
self._match_text_seq(")", ",")
6222+
6223+
this = self._parse_string()
6224+
62206225
if self._match_text_seq("PASSING"):
62216226
# The BY VALUE keywords are optional and are provided for semantic clarity
62226227
self._match_text_seq("BY", "VALUE")
@@ -6228,9 +6233,28 @@ def _parse_xml_table(self) -> exp.XMLTable:
62286233
columns = self._parse_csv(self._parse_field_def)
62296234

62306235
return self.expression(
6231-
exp.XMLTable, this=this, passing=passing, columns=columns, by_ref=by_ref
6236+
exp.XMLTable,
6237+
this=this,
6238+
namespaces=namespaces,
6239+
passing=passing,
6240+
columns=columns,
6241+
by_ref=by_ref,
62326242
)
62336243

6244+
def _parse_xml_namespace(self) -> t.List[exp.XMLNamespace]:
6245+
namespaces = []
6246+
6247+
while True:
6248+
if self._match_text_seq("DEFAULT"):
6249+
uri = self._parse_string()
6250+
else:
6251+
uri = self._parse_alias(self._parse_string())
6252+
namespaces.append(self.expression(exp.XMLNamespace, this=uri))
6253+
if not self._match(TokenType.COMMA):
6254+
break
6255+
6256+
return namespaces
6257+
62346258
def _parse_decode(self) -> t.Optional[exp.Decode | exp.Case]:
62356259
"""
62366260
There are generally two variants of the DECODE function:

tests/dialects/test_oracle.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ def test_xml_table(self):
394394
self.validate_identity(
395395
"XMLTABLE('x' RETURNING SEQUENCE BY REF COLUMNS a VARCHAR2, b FLOAT)"
396396
)
397+
self.validate_identity(
398+
"SELECT x.* FROM example t, XMLTABLE(XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS \"ns1\"), '/root/data' PASSING t.xml COLUMNS id NUMBER PATH '@id', value VARCHAR2(100) PATH 'ns1:value/text()') x"
399+
)
397400

398401
self.validate_all(
399402
"""SELECT warehouse_name warehouse,

tests/dialects/test_postgres.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ def test_postgres(self):
7676
self.validate_identity("SELECT CURRENT_USER")
7777
self.validate_identity("SELECT * FROM ONLY t1")
7878
self.validate_identity(
79-
"SELECT id, name FROM XMLTABLE('/root/user' PASSING xml_data COLUMNS id INT PATH '@id', name TEXT PATH 'name/text()') AS t"
79+
"SELECT id, name FROM xml_data AS t, XMLTABLE('/root/user' PASSING t.xml COLUMNS id INT PATH '@id', name TEXT PATH 'name/text()') AS x"
80+
)
81+
self.validate_identity(
82+
"SELECT id, value FROM xml_content AS t, XMLTABLE(XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2), '/root/data' PASSING t.xml COLUMNS id INT PATH '@ns1:id', value TEXT PATH 'ns2:value/text()') AS x"
8083
)
8184
self.validate_identity(
8285
"SELECT * FROM t WHERE some_column >= CURRENT_DATE + INTERVAL '1 day 1 hour' AND some_another_column IS TRUE"

0 commit comments

Comments
 (0)