Repository: mathesar-foundation/mathesar

Issue:
Unable to load Mathesar when a column contains an unrecognized type
## Reproduce

1. Execute this SQL:

    ```sql
    DROP TABLE IF EXISTS "type_test";
    CREATE TABLE "type_test" (
        "id" serial NOT NULL,
        "array_of_ints" integer[]
    );
    INSERT INTO "type_test" ("array_of_ints") VALUES
    ('{ 1, 2, 3 }');
    ```

1. Restart Docker.

1. Try to load the base page.

1. Observe the following error:

    ```
    TypeError at /mathesar_tables/1/
    
    __init__() missing 1 required positional argument: 'item_type'

    ...
    ```

    ![image](https://user-images.githubusercontent.com/42411/165419626-e6679a91-d543-4d03-9ed4-87425161b3ba.png)


## Notes

- I've tried this with a few different types, `integer[]`, `circle`, `jsonb`. I get the same behavior every time.
- After dropping the offending table, I'm able to load Mathesar again.



Discussion:
Assigning myself because this is a 15 second fix.
Actually, it's a bit more of a problem the Array problem is different than the non-array column.

Resolving patch:
diff --git a/db/columns/base.py b/db/columns/base.py
--- a/db/columns/base.py
+++ b/db/columns/base.py
@@ -1,4 +1,5 @@
 from sqlalchemy import Column, ForeignKey, inspect
+from sqlalchemy.exc import CompileError
 
 from db.columns.defaults import TYPE, PRIMARY_KEY, NULLABLE, DEFAULT_COLUMNS
 from db.columns.operations.select import (
@@ -111,7 +112,7 @@ def valid_target_types(self):
         Returns a set of valid types to which the type of the column can be
         altered.
         """
-        if self.engine is not None and not self.is_default:
+        if self.engine is not None and self.plain_type is not None and not self.is_default:
             db_type = self.plain_type
             valid_target_types = sorted(
                 list(
@@ -161,7 +162,12 @@ def plain_type(self):
         """
         Get the type name without arguments
         """
-        return self.type.__class__().compile(self.engine.dialect)
+        try:
+            _plain_type = self.type.__class__().compile(self.engine.dialect)
+        except (TypeError, CompileError):
+            _plain_type = None
+
+        return _plain_type
 
     @property
     def type_options(self):


Test patch:
diff --git a/db/tests/columns/test_base.py b/db/tests/columns/test_base.py
--- a/db/tests/columns/test_base.py
+++ b/db/tests/columns/test_base.py
@@ -1,7 +1,10 @@
 import re
 
 import pytest
-from sqlalchemy import (CHAR, ForeignKey, Integer, Numeric, String, VARCHAR)
+from sqlalchemy import (
+    CHAR, ForeignKey, Integer, Numeric, String, VARCHAR, ARRAY, JSON
+)
+from sqlalchemy.sql.sqltypes import NullType
 
 from db.columns.base import MathesarColumn
 from db.columns.defaults import DEFAULT_COLUMNS
@@ -182,6 +185,24 @@ def test_MC_plain_type_numeric_opts(engine):
     assert mc.plain_type == "NUMERIC"
 
 
+def test_MC_plain_type_unknown_type(engine):
+    mc = MathesarColumn('testable_col', NullType())
+    mc.add_engine(engine)
+    assert mc.plain_type is None
+
+
+def test_MC_plain_type_array_type(engine):
+    mc = MathesarColumn('testable_col', ARRAY(Integer))
+    mc.add_engine(engine)
+    assert mc.plain_type is None
+
+
+def test_MC_plain_type_json_type(engine):
+    mc = MathesarColumn('testable_col', JSON())
+    mc.add_engine(engine)
+    assert mc.plain_type == "JSON"
+
+
 def test_MC_type_options_no_opts(engine):
     mc = MathesarColumn('testable_col', Numeric)
     mc.add_engine(engine)