Repository: ibis-project/ibis

Issue:
feat: SQL Server Hierarchical Column Support
### Is your feature request related to a problem?

I am trying SQL Server IBIS on Adventure Works SQL Server sample OLTP DB, as it has complex datatypes
https://learn.microsoft.com/en-us/sql/samples/adventureworks-install-configure?view=sql-server-ver16&tabs=ssms

I discovered an issue when I try to read the schema of [HumanResources].[Employee], and discovered it has columns, which is a [hierarchyid](https://learn.microsoft.com/en-us/sql/relational-databases/hierarchical-data-sql-server?view=sql-server-ver16) datatype.

Sample to Simulate the issue

DDL of table is
```
CREATE TABLE [dbo].[SimpleDemo]
(
    [Node] hierarchyid NOT NULL,  
    [Level]  AS ([Node].[GetLevel]()),
    [Location] nvarchar(30) NOT NULL,  
    [LocationType] nvarchar(9) NULL
);

INSERT INTO [dbo].[SimpleDemo] ( [Node],  [Location], [LocationType] ) 
VALUES
    ('/', 'Earth', 'Planet'),
	('/1/', 'Europe', 'Continent'),  
	('/2/', 'South America', 'Continent'),  
	('/1/1/', 'France', 'Country'),  
	('/1/1/1/', 'Paris', 'City'),  
	('/1/2/1/', 'Madrid', 'City'),  
	('/1/2/', 'Spain', 'Country'),  
	('/3/', 'Antarctica', 'Continent'),  
	('/2/1/', 'Brazil', 'Country'),  
	('/2/1/1/', 'Brasilia', 'City'),  
	('/2/1/2/', 'Bahia', 'State'),  
	('/2/1/2/1/', 'Salvador', 'City'),  
	('/3/1/', 'McMurdo Station', 'City'); 
```

Notice the `Node` & `Level` Columns, which is a [hierarchyid](https://learn.microsoft.com/en-us/sql/relational-databases/hierarchical-data-sql-server?view=sql-server-ver16) datatype related.

When I try to get the table info, I get error

```src_con.table(name='SimpleDemo', schema='dbo', database='AW_OLTP')```

```
ProgrammingError: (pyodbc.ProgrammingError) ('42000', "[42000] [Microsoft][ODBC Driver 18 for SQL Server][SQL Server]The request for procedure 'SimpleDemo' failed because 'SimpleDemo' is a table object. (2809) (SQLExecDirectW)")
[SQL: EXEC sp_describe_first_result_set @tsql = ?]
[parameters: ('dbo.[SimpleDemo]',)]
(Background on this error at: https://sqlalche.me/e/20/f405)
```

As per the error, when I check
```
###/usr/local/lib/python3.10/dist-packages/ibis/backends/base/sql/alchemy/__init__.py
            nulltype_cols = frozenset(
                col.name for col in table.c if isinstance(col.type, sa.types.NullType)
            )

            if not nulltype_cols:
                return table
            return self._handle_failed_column_type_inference(table, nulltype_cols)
```



### Describe the solution you'd like

I see that we are tying to handle it, If you can do a simple fix that would be great, as

```
select 
  CAST(Node AS nvarchar(100)) AS Node,
  Level,
  Location,
  LocationType
from [dbo].[SimpleDemo]
```

- Node is a VARCHAR that needs CAST
- Level is a INTEGER that also can be CAST

### What version of ibis are you running?

8.0.0

### What backend(s) are you using, if any?

SQL Server

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
Thanks for the issue and the reproducer, @sarsasid !  The initial issue here appears to be that `sqlglot` doesn't have support for the `hierarchyid` datatype.  

Resolving patch:
diff --git a/ibis/backends/base/sqlglot/datatypes.py b/ibis/backends/base/sqlglot/datatypes.py
--- a/ibis/backends/base/sqlglot/datatypes.py
+++ b/ibis/backends/base/sqlglot/datatypes.py
@@ -894,6 +894,8 @@ def _from_sqlglot_STRUCT(cls, *cols: sge.ColumnDef) -> NoReturn:
 class MSSQLType(SqlglotType):
     dialect = "mssql"
 
+    unknown_type_strings = FrozenDict({"hierarchyid": dt.string})
+
     @classmethod
     def _from_sqlglot_BIT(cls):
         return dt.Boolean(nullable=cls.default_nullable)


Test patch:
diff --git a/ibis/backends/mssql/tests/test_client.py b/ibis/backends/mssql/tests/test_client.py
--- a/ibis/backends/mssql/tests/test_client.py
+++ b/ibis/backends/mssql/tests/test_client.py
@@ -52,6 +52,7 @@
     ("DATETIMEOFFSET(5)", dt.timestamp(scale=5, timezone="UTC")),
     ("GEOMETRY", dt.geometry),
     ("GEOGRAPHY", dt.geography),
+    ("HIERARCHYID", dt.string),
 ]