Repository: tobymao/sqlglot

Issue:
add support for table cast to parse the tables correctly when casting is done with user defined table types
Hello Team,
Warm greetings!

Parser is unable to parse query when TABLE(CAST(:VAR AS PAKAGE.UERDEFINEDTABLETYPE)) is used.
TABLE(CAST(...)) retuning wrong table name. But when we used Schema or pakage name o access the userdefined datatype, then sqlglot fails.

**Oracle Documentation -**
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6600331069731
https://docs.oracle.com/javadb/10.8.3.0/ref/rrefsqlj33562.html

**Code for your reference -**

```
import sqlglot.expressions as exp

# previously raised issue, but it returns 'TABLE(CAST(somelist ' as output
query1 ="""
SELECT col1, col2
FROM table1, TABLE(CAST(somelist as data_list))
WHERE col like '%contact'
"""

# user defined table datatype can be accessed with pakage.<type>
# below query generate error as it expects ')' and not the dot(.)
query2="""
SELECT col1, col2
FROM table1, TABLE(CAST(somelist as pakage.data_list))
WHERE col like '%contact'
"""
expressions = sqlglot.parse_one(query2, read='oracle')
x = list(expressions.find_all(exp.Table))

for i in x:
  listTables = str(i).split(' ')[0]
  print(listTables)
```

Note that above queries are valid oracle queries.

query1 returns incorrect data and query2 fails.
Request you to add support for the same.

Thanks ton in advance!

Thanks,
Sachin


Discussion:
I can't reproduce the issue you see with the first query:

```python
>>> import sqlglot
>>> query1 ="""
... SELECT col1, col2
... FROM table1, TABLE(CAST(somelist as data_list))
... WHERE col like '%contact'
... """
>>>
>>> sqlglot.transpile(query1, "oracle")
["SELECT col1, col2 FROM table1, TABLE(CAST(somelist AS data_list)) WHERE col LIKE '%contact'"]
>>>
>>> sqlglot.__version__
'18.5.1.dev4'
```

I'll see if it's simple to fix the second one; UDTs with additional nesting are not supported currently.
> I can't reproduce the issue you see with the first query:
> 
> ```python
> >>> query1 ="""
> ... SELECT col1, col2
> ... FROM table1, TABLE(CAST(somelist as data_list))
> ... WHERE col like '%contact'
> ... """
> >>>
> >>> import sqlglot
> >>>
> >>> sqlglot.transpile(query1, "oracle")
> ["SELECT col1, col2 FROM table1, TABLE(CAST(somelist AS data_list)) WHERE col LIKE '%contact'"]
> ```
> 
> I'll see if it's simple to fix the second one; UDTs with additional nesting are not supported currently.

When I test first query with latest sqlglot version 18.5.0, Im getting output as below -
table1
TABLE(CAST(somelist

Yes, second fails directly, if you could provide support for the same that would be great help.
We have lot of queries like these which needs to be parsed. 

Thanks in advance!


@sashindeitidata updated my previous message to also display the SQLGlot version. No issue with latest `main` branch.

The docs you linked don't have an example of a user-defined type which is scoped to a package. Can you point me to the documentation for this syntax so that I can verify it's valid Oracle?
> @sashindeitidata updated my previous message to also display the SQLGlot version. No issue with latest `main` branch.
> 
> The docs you linked don't have an example of a user-defined type which is scoped to a package. Can you point me to the documentation for this syntax so that I can verify it's valid Oracle?

Hi @GeorgeSittas 
 Check the below link - 
 https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6600331069731
 
![image](https://github.com/tobymao/sqlglot/assets/142426118/d9ba938e-0a10-476d-b846-9a187a68665a)

This is valid syntax, which our DBA team confirmed. 

Hi @GeorgeSittas 
Also check below link - 

https://docs.oracle.com/en/database/oracle/oracle-database/21/odpnt/featUDTs.html#GUID-21F1E450-EF2A-40EC-8486-1691C4842DA8
`For example, the parameter for a stored procedure is of type, SCOTT.PERSON and has a subtype, SCOTT.STUDENT. The application has a custom class instance that represents SCOTT.STUDENT. The UdtTypeName is set to SCOTT.PERSON, but the custom type mapping indicates that the custom class is mapped to SCOTT.STUDENT and overrides the UdtTypeName when it instantiates the Oracle UDT. Thus, ODP.NET instantiates and binds Oracle UDTs appropriately when the custom object represents an Oracle UDT that is a subtype of the parameter type.`


Also check section - 'Oracle UDT Metadata Retrieval from OracleDataReader' where Schema and Type are used.

Thanks, Sachin
Thanks - I'll take a look, it seems like Postgres also supports scoped UDTs.
> Thanks - I'll take a look, it seems like Postgres also supports scoped UDTs.

Thank you so much. 

Resolving patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -3279,7 +3279,12 @@ def _parse_types(
                 if tokens[0].token_type in self.TYPE_TOKENS:
                     self._prev = tokens[0]
                 elif self.SUPPORTS_USER_DEFINED_TYPES:
-                    return exp.DataType.build(identifier.name, udt=True)
+                    type_name = identifier.name
+
+                    while self._match(TokenType.DOT):
+                        type_name = f"{type_name}.{self._advance_any() and self._prev.text}"
+
+                    return exp.DataType.build(type_name, udt=True)
                 else:
                     return None
             else:


Test patch:
diff --git a/tests/dialects/test_oracle.py b/tests/dialects/test_oracle.py
--- a/tests/dialects/test_oracle.py
+++ b/tests/dialects/test_oracle.py
@@ -91,6 +91,16 @@ def test_oracle(self):
                 "": "CAST(x AS FLOAT)",
             },
         )
+        self.validate_all(
+            "CAST(x AS sch.udt)",
+            read={
+                "postgres": "CAST(x AS sch.udt)",
+            },
+            write={
+                "oracle": "CAST(x AS sch.udt)",
+                "postgres": "CAST(x AS sch.udt)",
+            },
+        )
 
     def test_join_marker(self):
         self.validate_identity("SELECT e1.x, e2.x FROM e e1, e e2 WHERE e1.y (+) = e2.y")
diff --git a/tests/test_expressions.py b/tests/test_expressions.py
--- a/tests/test_expressions.py
+++ b/tests/test_expressions.py
@@ -934,5 +934,8 @@ def test_is_type(self):
         assert dtype.is_type("foo")
         assert not dtype.is_type("bar")
 
+        dtype = exp.DataType.build("a.b.c", udt=True)
+        assert dtype.is_type("a.b.c")
+
         with self.assertRaises(ParseError):
             exp.DataType.build("foo")