Repository: ibis-project/ibis

Issue:
bug(postgres): cannot connect to postgres table with `tsvector` column
### What happened?

Reposting from https://stackoverflow.com/questions/74520302/why-cant-i-connect-ibis-to-a-postgres-table-with-a-tsvector-column

Implementing whatever postgres functionality exists around tsvector may not be something we want to do, but I think we should at least allow connecting to the table.

### What version of ibis are you using?

3.2.0

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

postgres

### Relevant log output

```sh
KeyError                                  Traceback (most recent call last)
File ~/anaconda3/envs/ec_extract/lib/python3.9/site-packages/multipledispatch/dispatcher.py:269, in Dispatcher.__call__(self, *args, **kwargs)
    268 try:
--> 269     func = self._cache[types]
    270 except KeyError:

KeyError: (<class 'sqlalchemy.dialects.postgresql.psycopg2.PGDialect_psycopg2'>, <class 'sqlalchemy.dialects.postgresql.base.TSVECTOR'>)

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
*** 
----> 29 main_table = con.table(table_name)[columns['column_list']]

File ~/anaconda3/envs/ec_extract/lib/python3.9/site-packages/ibis/backends/base/sql/alchemy/__init__.py:438, in BaseAlchemyBackend.table(self, name, database, schema)
    428     return self.database(database=database).table(
    429         name=name,
    430         database=database,
    431         schema=schema,
    432     )
    433 sqla_table = self._get_sqla_table(
    434     name,
...
    275             (self.name, str_signature(types)))
    276     self._cache[types] = func
    277 try:

NotImplementedError: Could not find signature for dtype: <PGDialect_psycopg2, TSVECTOR>
```


### Code of Conduct

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


Discussion:
What should we infer this type as? String?
I think that's what makes the most sense given what we currently support.  We won't have any of the specialized string vector methods, but at least users can interact with the rest of the table.

Resolving patch:
diff --git a/ibis/backends/postgres/datatypes.py b/ibis/backends/postgres/datatypes.py
--- a/ibis/backends/postgres/datatypes.py
+++ b/ibis/backends/postgres/datatypes.py
@@ -170,3 +170,8 @@ def sa_pg_array(dialect, satype, nullable=True):
 
     value_dtype = dt.dtype(dialect, satype.item_type)
     return dt.Array(value_dtype, nullable=nullable)
+
+
+@dt.dtype.register(PGDialect, postgresql.TSVECTOR)
+def sa_postgres_tsvector(_, satype, nullable=True):
+    return dt.String(nullable=nullable)


Test patch:
diff --git a/ibis/backends/postgres/tests/test_string.py b/ibis/backends/postgres/tests/test_string.py
--- a/ibis/backends/postgres/tests/test_string.py
+++ b/ibis/backends/postgres/tests/test_string.py
@@ -4,6 +4,7 @@
 from pytest import param
 
 import ibis
+import ibis.expr.datatypes as dt
 
 
 @pytest.mark.parametrize(
@@ -16,3 +17,9 @@ def test_special_strings(alltypes, data, data_type):
     expr = alltypes[[alltypes.id, lit]].head(1)
     df = expr.execute()
     assert df['tmp'].iloc[0] == uuid.UUID(data)
+
+
+def test_load_tsvector_table(con):
+    awards_players = con.table("awards_players")
+    assert "search" in awards_players.columns
+    assert awards_players.schema()["search"] == dt.String(nullable=True)