Repository: mathesar-foundation/mathesar

Issue:
500s when patching column to a different type
## Description
When patching a column to a different type, having `display_options` or `type_options` in the request results in a 500 if the new type does not allow them.

### Scenario 1:
* Create a text column
* Patch the column to URI type with `null` values for display and type options:
   ```
  {
    "type": "MATHESAR_TYPES.URI",
    "display_options": null,
    "type_options": null
  }
   ```
* Notice the following response with status code 500:
  ```
  [
    {
        "code": 4999,
        "message": "'NoneType' object is not iterable",
        "field": null,
        "detail": null
    }
  ]
  ```

### Scenario 2:
* Create a text column
* Patch the column to URI type with an empty object: `{}` for display and type:
   ```
  {
    "type": "MATHESAR_TYPES.URI",
    "display_options": {},
    "type_options": {}
  }
   ```
* Notice the following response with status code 500:
  ```
  [
    {
        "code": 4999,
        "message": "Cannot find a matching serializer for the specified type uri",
        "field": null,
        "detail": null
    }
  ]
  ```

## Expected behavior
* The response should not result in 500 for both scenarios.
* For Scenario 1, the request should succeed.
* For Scenario 2, the it would be easier for the client if the request succeeds. If that's against backend convention, the response should result in a 400.


Discussion:
> For Scenario 2, the it would be easier for the client if the request succeeds. If that's against backend convention, the response should result in a 400.

If `display_options` is set to an empty object, we should remove all display options and the request should succeed.
Can I work on this?
@Anish9901 I have assigned the issue to you. This issue blocks a lot of frontend work, so a core contributor might take it if it takes too long to be completed(more than 2-3 days)
I'll try to get a PR in by a day or two @silentninja 
Thanks! @Anish9901 

Resolving patch:
diff --git a/db/columns/operations/alter.py b/db/columns/operations/alter.py
--- a/db/columns/operations/alter.py
+++ b/db/columns/operations/alter.py
@@ -61,6 +61,7 @@ def alter_column_type(
         table_oid, column_name, engine, connection, target_type_str,
         type_options={}, friendly_names=True,
 ):
+    type_options = type_options if type_options is not None else {}
     table = reflect_table_from_oid(table_oid, engine, connection)
     _preparer = engine.dialect.identifier_preparer
     supported_types = get_supported_alter_column_types(
diff --git a/mathesar/api/serializers/columns.py b/mathesar/api/serializers/columns.py
--- a/mathesar/api/serializers/columns.py
+++ b/mathesar/api/serializers/columns.py
@@ -33,7 +33,7 @@ class TypeOptionSerializer(MathesarErrorMessageMixin, serializers.Serializer):
 
     def run_validation(self, data=empty):
         # Ensure that there are no unknown type options passed in.
-        if data is not empty:
+        if data is not empty and data is not None:
             unknown = set(data) - set(self.fields)
             if unknown:
                 errors = ['Unknown field: {}'.format(field) for field in unknown]
diff --git a/mathesar/api/serializers/shared_serializers.py b/mathesar/api/serializers/shared_serializers.py
--- a/mathesar/api/serializers/shared_serializers.py
+++ b/mathesar/api/serializers/shared_serializers.py
@@ -38,7 +38,7 @@ def to_representation(self, instance):
         if serializer is not None:
             return serializer.to_representation(instance)
         else:
-            raise Exception(f"Cannot find a matching serializer for the specified type {self.get_mapping_field()}")
+            return instance
 
     def get_mapping_field(self):
         mapping_field = getattr(self, "mapping_field", None)
@@ -56,7 +56,8 @@ def to_internal_value(self, data):
         if serializer is not None:
             return serializer.to_internal_value(data=data)
         else:
-            raise Exception(f"Cannot find a matching serializer for the specified type {self.get_mapping_field()}")
+            data = {}
+            return data
 
 
 class MonkeyPatchPartial:


Test patch:
diff --git a/mathesar/tests/api/test_column_api.py b/mathesar/tests/api/test_column_api.py
--- a/mathesar/tests/api/test_column_api.py
+++ b/mathesar/tests/api/test_column_api.py
@@ -864,3 +864,47 @@ def test_column_duplicate_no_parameters(column_test_table, client):
     assert response.status_code == 400
     assert response_data[0]["message"] == "This field is required."
     assert response_data[0]["field"] == "type"
+
+
+def test_column_update_type_with_display_and_type_options_as_null(column_test_table, client):
+    cache.clear()
+    type_ = "MATHESAR_TYPES.URI"
+    display_options = None
+    type_options = None
+    data = {
+        "type": type_,
+        "display_options": display_options,
+        "type_options": type_options
+    }
+    column = _get_columns_by_name(column_test_table, ['mycolumn3'])[0]
+    response = client.patch(
+        f"/api/db/v0/tables/{column_test_table.id}/columns/{column.id}/",
+        data=data,
+    )
+    assert response.status_code == 200
+    response_json = response.json()
+    assert response_json["type"] == type_
+    assert response_json["display_options"] == display_options
+    assert response_json["type_options"] == type_options
+
+
+def test_column_update_type_with_display_and_type_options_as_empty_objects(column_test_table, client):
+    cache.clear()
+    type_ = "MATHESAR_TYPES.URI"
+    display_options = {}
+    type_options = {}
+    data = {
+        "type": type_,
+        "display_options": display_options,
+        "type_options": type_options
+    }
+    column = _get_columns_by_name(column_test_table, ['mycolumn3'])[0]
+    response = client.patch(
+        f"/api/db/v0/tables/{column_test_table.id}/columns/{column.id}/",
+        data=data,
+    )
+    assert response.status_code == 200
+    response_json = response.json()
+    assert response_json["type"] == type_
+    assert response_json["display_options"] == {}
+    assert response_json["type_options"] is None