Repository: mathesar-foundation/mathesar

Issue:
Handle `BIGINT` data type in the backend.
This issue is to ensure that Mathesar can handle the Postgres `BIGINT` data type.

As part of this issue, we need to ensure that:
- Users can use the API to change a column to `BIGINT` if it's possible to do so.
- Automatic type inference during file import suggests `BIGINT` when it makes sense to do so. 

**Additional Context** 
- See PR #368 for code that calculates which types are allowed for a column.


Discussion:

Resolving patch:
diff --git a/db/types/alteration.py b/db/types/alteration.py
--- a/db/types/alteration.py
+++ b/db/types/alteration.py
@@ -3,15 +3,18 @@
 from sqlalchemy.sql.functions import Function
 from db.types import base, email
 
+BIGINT = "bigint"
 BOOLEAN = "boolean"
 EMAIL = "email"
 DECIMAL = "decimal"
 DOUBLE_PRECISION = "double precision"
 FLOAT = "float"
+INTEGER = "integer"
 INTERVAL = "interval"
 NAME = "name"
 NUMERIC = "numeric"
 REAL = "real"
+SMALLINT = "smallint"
 STRING = "string"
 VARCHAR = "varchar"
 FULL_VARCHAR = "character varying"
@@ -33,13 +36,16 @@ def get_supported_alter_column_types(engine, friendly_names=True):
     dialect_types = engine.dialect.ischema_names
     friendly_type_map = {
         # Default Postgres types
+        BIGINT: dialect_types.get(BIGINT),
         BOOLEAN: dialect_types.get(BOOLEAN),
         DECIMAL: dialect_types.get(DECIMAL),
         DOUBLE_PRECISION: dialect_types.get(DOUBLE_PRECISION),
         FLOAT: dialect_types.get(FLOAT),
+        INTEGER: dialect_types.get(INTEGER),
         INTERVAL: dialect_types.get(INTERVAL),
         NUMERIC: dialect_types.get(NUMERIC),
         REAL: dialect_types.get(REAL),
+        SMALLINT: dialect_types.get(SMALLINT),
         STRING: dialect_types.get(NAME),
         VARCHAR: dialect_types.get(FULL_VARCHAR),
         # Custom Mathesar types
@@ -143,6 +149,7 @@ def get_column_cast_expression(column, target_type_str, engine, type_options={})
 def install_all_casts(engine):
     create_boolean_casts(engine)
     create_email_casts(engine)
+    create_integer_casts(engine)
     create_decimal_number_casts(engine)
     create_interval_casts(engine)
     create_varchar_casts(engine)
@@ -158,6 +165,13 @@ def create_email_casts(engine):
     create_cast_functions(email.QUALIFIED_EMAIL, type_body_map, engine)
 
 
+def create_integer_casts(engine):
+    integer_types = [BIGINT, INTEGER, SMALLINT]
+    for type_str in integer_types:
+        type_body_map = _get_integer_type_body_map(target_type_str=type_str)
+        create_cast_functions(type_str, type_body_map, engine)
+
+
 def create_interval_casts(engine):
     type_body_map = _get_interval_type_body_map()
     create_cast_functions(INTERVAL, type_body_map, engine)
@@ -193,14 +207,17 @@ def get_full_cast_map(engine):
 
 def get_defined_source_target_cast_tuples(engine):
     type_body_map_map = {
+        BIGINT: _get_integer_type_body_map(target_type_str=BIGINT),
         BOOLEAN: _get_boolean_type_body_map(),
         EMAIL: _get_email_type_body_map(),
         DECIMAL: _get_decimal_number_type_body_map(target_type_str=DECIMAL),
         DOUBLE_PRECISION: _get_decimal_number_type_body_map(target_type_str=DOUBLE_PRECISION),
         FLOAT: _get_decimal_number_type_body_map(target_type_str=FLOAT),
+        INTEGER: _get_integer_type_body_map(target_type_str=INTEGER),
         INTERVAL: _get_interval_type_body_map(),
         NUMERIC: _get_decimal_number_type_body_map(target_type_str=NUMERIC),
         REAL: _get_decimal_number_type_body_map(target_type_str=REAL),
+        SMALLINT: _get_integer_type_body_map(target_type_str=SMALLINT),
         VARCHAR: _get_varchar_type_body_map(engine),
     }
     return {
@@ -265,7 +282,10 @@ def _get_boolean_type_body_map():
                              PostgreSQL).  Others raise a custom
                              exception.
     """
-    source_number_types = [DECIMAL, DOUBLE_PRECISION, FLOAT, NUMERIC, REAL]
+    source_number_types = [
+        BIGINT, DECIMAL, DOUBLE_PRECISION, FLOAT, INTEGER, NUMERIC, REAL,
+        SMALLINT,
+    ]
     default_behavior_source_types = [BOOLEAN]
 
     not_bool_exception_str = f"RAISE EXCEPTION '% is not a {BOOLEAN}', $1;"
@@ -352,6 +372,44 @@ def _get_interval_type_body_map():
     }
 
 
+def _get_integer_type_body_map(target_type_str=INTEGER):
+    """
+    We use default behavior for identity and casts from TEXT types.
+    We specifically disallow rounding or truncating when casting from numerics,
+    etc.
+    """
+    default_behavior_source_types = [BIGINT, INTEGER, SMALLINT, VARCHAR]
+    no_rounding_source_types = [
+        DECIMAL, DOUBLE_PRECISION, FLOAT, NUMERIC, REAL
+    ]
+    cast_loss_exception_str = (
+        f"RAISE EXCEPTION '% cannot be cast to {target_type_str} without loss', $1;"
+    )
+
+    def _get_no_rounding_cast_to_integer():
+        return f"""
+        DECLARE integer_res {target_type_str};
+        BEGIN
+          SELECT $1::{target_type_str} INTO integer_res;
+          IF integer_res = $1 THEN
+            RETURN integer_res;
+          END IF;
+          {cast_loss_exception_str}
+        END;
+        """
+    type_body_map = _get_default_type_body_map(
+        default_behavior_source_types, target_type_str,
+    )
+    type_body_map.update(
+        {
+            type_name: _get_no_rounding_cast_to_integer()
+            for type_name in no_rounding_source_types
+        }
+    )
+    type_body_map.update({BOOLEAN: _get_boolean_to_number_cast(target_type_str)})
+    return type_body_map
+
+
 def _get_decimal_number_type_body_map(target_type_str=NUMERIC):
     """
     Get SQL strings that create various functions for casting different
@@ -363,7 +421,8 @@ def _get_decimal_number_type_body_map(target_type_str=NUMERIC):
     """
 
     default_behavior_source_types = [
-        DECIMAL, DOUBLE_PRECISION, FLOAT, NUMERIC, REAL, VARCHAR
+        BIGINT, DECIMAL, DOUBLE_PRECISION, FLOAT, INTEGER, NUMERIC, REAL,
+        SMALLINT, VARCHAR,
     ]
     type_body_map = _get_default_type_body_map(
         default_behavior_source_types, target_type_str,


Test patch:
diff --git a/db/tests/test_columns.py b/db/tests/test_columns.py
--- a/db/tests/test_columns.py
+++ b/db/tests/test_columns.py
@@ -414,13 +414,16 @@ def test_retype_column_adds_options(engine_with_schema, target_type):
 
 
 type_set = {
+    'BIGINT',
     'BOOLEAN',
     'DECIMAL',
     'DOUBLE PRECISION',
     'FLOAT',
+    'INTEGER',
     'INTERVAL',
     'NUMERIC',
     'REAL',
+    'SMALLINT',
     'VARCHAR',
     'mathesar_types.email',
 }
diff --git a/db/tests/types/test_alteration.py b/db/tests/types/test_alteration.py
--- a/db/tests/types/test_alteration.py
+++ b/db/tests/types/test_alteration.py
@@ -18,83 +18,314 @@
 temporary_testing_schema = fixtures.temporary_testing_schema
 
 
+BIGINT = "BIGINT"
 BOOLEAN = "BOOLEAN"
 DECIMAL = "DECIMAL"
 DOUBLE = "DOUBLE PRECISION"
 EMAIL = "mathesar_types.email"
 FLOAT = "FLOAT"
+INTEGER = "INTEGER"
 INTERVAL = "INTERVAL"
 NUMERIC = "NUMERIC"
 REAL = "REAL"
+SMALLINT = "SMALLINT"
 VARCHAR = "VARCHAR"
 
 
 ISCHEMA_NAME = "ischema_name"
-TARGET_LIST = "target_list"
+TARGET_DICT = "target_dict"
 REFLECTED_NAME = "reflected_name"
 SUPPORTED_MAP_NAME = "supported_map_name"
+VALID = "valid"
+INVALID = "invalid"
 
 MASTER_DB_TYPE_MAP_SPEC = {
     # This dict specifies the full map of what types can be cast to what
-    # target types in Mathesar.  Format of each key, val pair is:
+    # target types in Mathesar.  Format of each top-level key, val pair is:
     # <db_set_type_name>: {
     #     ISCHEMA_NAME: <name for looking up in engine.dialect.ischema_names>,
     #     REFLECTED_NAME: <name for reflection of db type>,
     #     SUPPORTED_MAP_NAME: <optional; key in supported type map dictionaries>
-    #     TARGET_LIST: <list of target db types for alteration>,
+    #     TARGET_DICT: {
+    #         <target_type_1>: {
+    #             VALID: [(in_val, out_val), (in_val, out_val)],
+    #             INVALID: [in_val, in_val]
+    #         },
+    #         <target_type_2>: {
+    #             INVALID: [(in_val, out_val), (in_val, out_val)]
+    #             INVALID: [in_val, in_val]
+    #         },
+    #     }
     # }
-    # The tuples in TARGET_LIST should be (set, reflect), where 'set' is
-    # a valid DB type for setting a column type, and 'reflect' is the
-    # type actually reflected afterwards
+    #
+    # The TARGET_DICT is a dict with keys giving a valid target type for
+    # alteration of a column of the given type, and values giving a dict
+    # of valid and invalid casting values.  VALID value list is a list of
+    # tuples representing the input and expected output, whereas INVALID
+    # value list only needs input (since it should break, giving no output)
+    BIGINT: {
+        ISCHEMA_NAME: "bigint",
+        REFLECTED_NAME: BIGINT,
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500), (500000000000, 500000000000)]},
+            BOOLEAN: {VALID: [(1, True), (0, False)], INVALID: [3]},
+            DECIMAL: {VALID: [(1, Decimal('1.0'))]},
+            DOUBLE: {VALID: [(3.0, 3)]},
+            FLOAT: {VALID: [(4.0, 4)]},
+            INTEGER: {VALID: [(500, 500)]},
+            NUMERIC: {VALID: [(1, Decimal('1.0'))]},
+            REAL: {VALID: [(5, 5.0)]},
+            SMALLINT: {VALID: [(500, 500)]},
+            VARCHAR: {VALID: [(3, "3")]},
+        }
+    },
     BOOLEAN: {
         ISCHEMA_NAME: "boolean",
         REFLECTED_NAME: BOOLEAN,
-        TARGET_LIST: [BOOLEAN, DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL, VARCHAR]
+        TARGET_DICT: {
+            BIGINT: {VALID: [(True, 1), (False, 0)]},
+            BOOLEAN: {VALID: [(True, True), (False, False)]},
+            DECIMAL: {VALID: [(True, Decimal('1.0')), (False, Decimal('0'))]},
+            DOUBLE: {VALID: [(True, 1.0), (False, 0.0)]},
+            FLOAT: {VALID: [(True, 1.0), (False, 0.0)]},
+            INTEGER: {VALID: [(True, 1), (False, 0)]},
+            NUMERIC: {VALID: [(True, Decimal('1.0')), (False, Decimal('0'))]},
+            REAL: {VALID: [(True, 1.0), (False, 0.0)]},
+            SMALLINT: {VALID: [(True, 1), (False, 0)]},
+            VARCHAR: {VALID: [(True, 'true'), (False, 'false')]},
+        }
     },
     DECIMAL: {
         ISCHEMA_NAME: "decimal",
         REFLECTED_NAME: NUMERIC,
-        TARGET_LIST: [BOOLEAN, DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL, VARCHAR]
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500), (1234123412341234, 1234123412341234)]},
+            BOOLEAN: {
+                VALID: [(1, True), (0, False), (1.0, True), (0.0, False)],
+                INVALID: [Decimal('1.3')]
+            },
+            DECIMAL: {VALID: [(1, 1.0)]},
+            DOUBLE: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            FLOAT: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            INTEGER: {
+                VALID: [(500, 500)],
+                INVALID: [1234123412341234]
+            },
+            NUMERIC: {VALID: [(1, 1.0)]},
+            REAL: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            SMALLINT: {
+                VALID: [(500, 500)],
+                INVALID: [12341234]
+            },
+            VARCHAR: {VALID: [(3, "3")]},
+        }
     },
     DOUBLE: {
         ISCHEMA_NAME: "double precision",
         REFLECTED_NAME: DOUBLE,
-        TARGET_LIST: [BOOLEAN, DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL, VARCHAR]
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500)]},
+            BOOLEAN: {VALID: [(1.0, True), (0.0, False)]},
+            DECIMAL: {VALID: [(1, 1.0)]},
+            DOUBLE: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            FLOAT: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            INTEGER: {VALID: [(500, 500)]},
+            NUMERIC: {VALID: [(1, 1.0)]},
+            REAL: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            SMALLINT: {VALID: [(500, 500)]},
+            VARCHAR: {VALID: [(3, "3")]},
+        }
+    },
+    EMAIL: {
+        ISCHEMA_NAME: "mathesar_types.email",
+        SUPPORTED_MAP_NAME: "email",
+        REFLECTED_NAME: EMAIL,
+        TARGET_DICT: {
+            EMAIL: {VALID: [("alice@example.com", "alice@example.com")]},
+            VARCHAR: {VALID: [("bob@example.com", "bob@example.com")]},
+        }
     },
     FLOAT: {
         ISCHEMA_NAME: "float",
         REFLECTED_NAME: DOUBLE,
-        TARGET_LIST: [BOOLEAN, DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL, VARCHAR]
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500)]},
+            BOOLEAN: {VALID: [(1.0, True), (0.0, False)], INVALID: [1.234]},
+            DECIMAL: {VALID: [(1, 1.0)]},
+            DOUBLE: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            FLOAT: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            INTEGER: {VALID: [(500, 500), (-5, -5)], INVALID: [-3.234, 234.34]},
+            NUMERIC: {VALID: [(1, 1.0)]},
+            REAL: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            SMALLINT: {VALID: [(500, 500), (-5, -5)], INVALID: [-3.234, 234.34]},
+            VARCHAR: {VALID: [(3, "3")]},
+        }
+    },
+    INTEGER: {
+        ISCHEMA_NAME: "integer",
+        REFLECTED_NAME: INTEGER,
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500)]},
+            BOOLEAN: {VALID: [(1, True), (0, False)], INVALID: [3]},
+            DECIMAL: {VALID: [(1, Decimal('1.0'))]},
+            DOUBLE: {VALID: [(3.0, 3)]},
+            FLOAT: {VALID: [(4.0, 4)]},
+            INTEGER: {VALID: [(500, 500)]},
+            NUMERIC: {VALID: [(1, Decimal('1.0'))]},
+            REAL: {VALID: [(5, 5.0)]},
+            SMALLINT: {VALID: [(500, 500)]},
+            VARCHAR: {VALID: [(3, "3")]},
+        }
     },
     INTERVAL: {
         ISCHEMA_NAME: "interval",
         REFLECTED_NAME: INTERVAL,
-        TARGET_LIST: [INTERVAL, VARCHAR]
-    },
-    EMAIL: {
-        ISCHEMA_NAME: "mathesar_types.email",
-        SUPPORTED_MAP_NAME: "email",
-        REFLECTED_NAME: EMAIL,
-        TARGET_LIST: [EMAIL, VARCHAR]
+        TARGET_DICT: {
+            INTERVAL: {
+                VALID: [
+                    (
+                        timedelta(days=3, hours=3, minutes=5, seconds=30),
+                        timedelta(days=3, hours=3, minutes=5, seconds=30),
+                    )
+                ]
+            },
+            VARCHAR: {
+                VALID: [
+                    (
+                        timedelta(days=3, hours=3, minutes=5, seconds=30),
+                        '3 days 03:05:30'
+                    )
+                ]
+            },
+        }
     },
     NUMERIC: {
         ISCHEMA_NAME: "numeric",
         REFLECTED_NAME: NUMERIC,
-        TARGET_LIST: [BOOLEAN, DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL, VARCHAR]
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500)]},
+            BOOLEAN: {
+                VALID: [(1, True), (0, False), (1.0, True), (0.0, False)],
+                INVALID: [42, -1]
+            },
+            DECIMAL: {VALID: [(1, 1.0)]},
+            DOUBLE: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            FLOAT: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            INTEGER: {
+                VALID: [(500, 500)],
+                INVALID: [1.234, 1234123412341234]
+            },
+            NUMERIC: {VALID: [(1, 1.0)]},
+            REAL: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            SMALLINT: {
+                VALID: [(500, 500)],
+                INVALID: [1.234, 12341234]
+            },
+            VARCHAR: {VALID: [(3, "3")]},
+        }
     },
     REAL: {
         ISCHEMA_NAME: "real",
         REFLECTED_NAME: REAL,
-        TARGET_LIST: [BOOLEAN, DECIMAL, DOUBLE, FLOAT, NUMERIC, REAL, VARCHAR]
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500)]},
+            BOOLEAN: {
+                VALID: [(1.0, True), (0.0, False)],
+                INVALID: [42, -1]
+            },
+            DECIMAL: {VALID: [(1, 1.0)]},
+            DOUBLE: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            FLOAT: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            INTEGER: {
+                VALID: [(500, 500)],
+                INVALID: [3.345]
+            },
+            NUMERIC: {VALID: [(1, 1.0)]},
+            REAL: {VALID: [(1, 1.0), (1.5, 1.5)]},
+            SMALLINT: {
+                VALID: [(500, 500)],
+                INVALID: [3.345]
+            },
+            VARCHAR: {VALID: [(3, "3")]},
+        }
+    },
+    SMALLINT: {
+        ISCHEMA_NAME: "smallint",
+        REFLECTED_NAME: SMALLINT,
+        TARGET_DICT: {
+            BIGINT: {VALID: [(500, 500)]},
+            BOOLEAN: {VALID: [(1, True), (0, False)], INVALID: [3]},
+            DECIMAL: {VALID: [(1, Decimal('1.0'))]},
+            DOUBLE: {VALID: [(3.0, 3)]},
+            FLOAT: {VALID: [(4.0, 4)]},
+            INTEGER: {VALID: [(500, 500)]},
+            NUMERIC: {VALID: [(1, Decimal('1.0'))]},
+            REAL: {VALID: [(5, 5.0)]},
+            SMALLINT: {VALID: [(500, 500)]},
+            VARCHAR: {VALID: [(3, "3")]},
+        }
     },
     VARCHAR: {
         ISCHEMA_NAME: "character varying",
         SUPPORTED_MAP_NAME: "varchar",
         REFLECTED_NAME: VARCHAR,
-        TARGET_LIST: [
-            BOOLEAN, DECIMAL, DOUBLE, EMAIL, FLOAT, INTERVAL, NUMERIC, REAL,
-            VARCHAR,
-        ]
+        TARGET_DICT: {
+            BIGINT: {
+                VALID: [("432", 432), ("1234123412341234", 1234123412341234)],
+                INVALID: ["1.2234"]
+            },
+            BOOLEAN: {
+                VALID: [
+                    ("true", True), ("false", False), ("t", True), ("f", False)
+                ],
+                INVALID: ["cat"],
+            },
+            DECIMAL: {
+                VALID: [("1.2", Decimal("1.2")), ("1", Decimal("1"))],
+                INVALID: ["abc"],
+            },
+            DOUBLE: {
+                VALID: [("1.234", 1.234)],
+                INVALID: ["bat"],
+            },
+            EMAIL: {
+                VALID: [("alice@example.com", "alice@example.com")],
+                INVALID: ["alice-example.com"]
+            },
+            FLOAT: {
+                VALID: [("1.234", 1.234)],
+                INVALID: ["bat"],
+            },
+            INTEGER: {
+                VALID: [("432", 432)],
+                INVALID: ["1.2234"]
+            },
+            INTERVAL: {
+                VALID: [
+                    ("1 day", timedelta(days=1)),
+                    ("1 week", timedelta(days=7)),
+                    ("3:30", timedelta(hours=3, minutes=30)),
+                    ("00:03:30", timedelta(minutes=3, seconds=30)),
+                ],
+                INVALID: ["1 potato", "3"],
+            },
+            NUMERIC: {
+                VALID: [
+                    ("1.2", Decimal("1.2")),
+                    ("1", Decimal("1")),
+                ],
+                INVALID: ["not a number"],
+            },
+            REAL: {
+                VALID: [("1.234", 1.234)],
+                INVALID: ["real"]
+            },
+            SMALLINT: {
+                VALID: [("432", 432)],
+                INVALID: ["1.2234"]
+            },
+            VARCHAR: {VALID: [("a string", "a string")]},
+        }
     }
 }
 
@@ -131,19 +362,19 @@ def test_get_alter_column_types_with_unfriendly_names(engine_with_types):
         MASTER_DB_TYPE_MAP_SPEC[target][REFLECTED_NAME]
     )
     for val in MASTER_DB_TYPE_MAP_SPEC.values()
-    for target in val[TARGET_LIST]
+    for target in val[TARGET_DICT]
 ] + [
     (val[ISCHEMA_NAME], "numeric", {"precision": 5}, "NUMERIC(5, 0)")
-    for val in MASTER_DB_TYPE_MAP_SPEC.values() if NUMERIC in val[TARGET_LIST]
+    for val in MASTER_DB_TYPE_MAP_SPEC.values() if NUMERIC in val[TARGET_DICT]
 ] + [
     (val[ISCHEMA_NAME], "numeric", {"precision": 5, "scale": 3}, "NUMERIC(5, 3)")
-    for val in MASTER_DB_TYPE_MAP_SPEC.values() if NUMERIC in val[TARGET_LIST]
+    for val in MASTER_DB_TYPE_MAP_SPEC.values() if NUMERIC in val[TARGET_DICT]
 ] + [
     (val[ISCHEMA_NAME], "decimal", {"precision": 5}, "NUMERIC(5, 0)")
-    for val in MASTER_DB_TYPE_MAP_SPEC.values() if DECIMAL in val[TARGET_LIST]
+    for val in MASTER_DB_TYPE_MAP_SPEC.values() if DECIMAL in val[TARGET_DICT]
 ] + [
     (val[ISCHEMA_NAME], "decimal", {"precision": 5, "scale": 3}, "NUMERIC(5, 3)")
-    for val in MASTER_DB_TYPE_MAP_SPEC.values() if DECIMAL in val[TARGET_LIST]
+    for val in MASTER_DB_TYPE_MAP_SPEC.values() if DECIMAL in val[TARGET_DICT]
 ]
 
 
@@ -184,24 +415,7 @@ def test_alter_column_type_alters_column_type(
     assert actual_type == expect_type
 
 
-type_test_data_list = [
-    (String, "boolean", {}, "false", False),
-    (String, "boolean", {}, "true", True),
-    (String, "boolean", {}, "f", False),
-    (String, "boolean", {}, "t", True),
-    (String, "interval", {}, "1 day", timedelta(days=1)),
-    (String, "interval", {}, "1 week", timedelta(days=7)),
-    (String, "interval", {}, "3:30", timedelta(hours=3, minutes=30)),
-    (String, "interval", {}, "00:03:30", timedelta(minutes=3, seconds=30)),
-    (String, "numeric", {}, "1", 1.0),
-    (String, "numeric", {}, "1.2", Decimal('1.2')),
-    (Numeric, "numeric", {}, 1, 1.0),
-    (String, "numeric", {}, "5", 5),
-    (String, "numeric", {}, "500000", 500000),
-    (String, "numeric", {}, "500000.134", Decimal("500000.134")),
-    (Numeric, "string", {}, 3, "3"),
-    (String, "string", {}, "abc", "abc"),
-    (String, "email", {}, "alice@example.com", "alice@example.com"),
+type_test_data_args_list = [
     (Numeric(precision=5), "numeric", {}, 1, 1.0),
     (Numeric(precision=5, scale=2), "numeric", {}, 1, 1.0),
     (Numeric, "numeric", {"precision": 5, "scale": 2}, 1.234, Decimal("1.23")),
@@ -212,9 +426,9 @@ def test_alter_column_type_alters_column_type(
 
 
 @pytest.mark.parametrize(
-    "type_,target_type,options,value,expect_value", type_test_data_list
+    "type_,target_type,options,value,expect_value", type_test_data_args_list
 )
-def test_alter_column_type_casts_column_data(
+def test_alter_column_type_casts_column_data_args(
         engine_email_type, type_, target_type, options, value, expect_value,
 ):
     engine, schema = engine_email_type
@@ -249,17 +463,68 @@ def test_alter_column_type_casts_column_data(
     assert actual_value == expect_value
 
 
-type_test_bad_data_list = [
-    (String, "boolean", "cat"),
-    (String, "interval", "1 potato"),
-    (String, "interval", "3"),
-    (String, "numeric", "abc"),
-    (String, "email", "alice-example.com"),
+type_test_data_gen_list = [
+    (
+        val[ISCHEMA_NAME],
+        MASTER_DB_TYPE_MAP_SPEC[target].get(
+            SUPPORTED_MAP_NAME, MASTER_DB_TYPE_MAP_SPEC[target][ISCHEMA_NAME]
+        ),
+        in_val,
+        out_val,
+    )
+    for val in MASTER_DB_TYPE_MAP_SPEC.values()
+    for target in val[TARGET_DICT]
+    for in_val, out_val in val[TARGET_DICT][target].get(VALID, [])
 ]
 
 
 @pytest.mark.parametrize(
-    "type_,target_type,value", type_test_bad_data_list
+    "source_type,target_type,in_val,out_val", type_test_data_gen_list
+)
+def test_alter_column_casts_data_gen(
+        engine_email_type, source_type, target_type, in_val, out_val
+):
+    engine, schema = engine_email_type
+    TABLE = "testtable"
+    COLUMN = "testcol"
+    metadata = MetaData(bind=engine)
+    input_table = Table(
+        TABLE,
+        metadata,
+        Column(COLUMN, engine.dialect.ischema_names[source_type]),
+        schema=schema
+    )
+    input_table.create()
+    ins = input_table.insert(values=(in_val,))
+    with engine.begin() as conn:
+        conn.execute(ins)
+    alteration.alter_column_type(schema, TABLE, COLUMN, target_type, engine)
+    metadata = MetaData(bind=engine)
+    metadata.reflect()
+    actual_table = Table(TABLE, metadata, schema=schema, autoload_with=engine)
+    sel = actual_table.select()
+    with engine.connect() as conn:
+        res = conn.execute(sel).fetchall()
+    actual_value = res[0][0]
+    assert actual_value == out_val
+
+
+type_test_bad_data_gen_list = [
+    (
+        val[ISCHEMA_NAME],
+        MASTER_DB_TYPE_MAP_SPEC[target].get(
+            SUPPORTED_MAP_NAME, MASTER_DB_TYPE_MAP_SPEC[target][ISCHEMA_NAME]
+        ),
+        data,
+    )
+    for val in MASTER_DB_TYPE_MAP_SPEC.values()
+    for target in val[TARGET_DICT]
+    for data in val[TARGET_DICT][target].get(INVALID, [])
+]
+
+
+@pytest.mark.parametrize(
+    "type_,target_type,value", type_test_bad_data_gen_list
 )
 def test_alter_column_type_raises_on_bad_column_data(
         engine_email_type, type_, target_type, value,
@@ -271,7 +536,7 @@ def test_alter_column_type_raises_on_bad_column_data(
     input_table = Table(
         TABLE_NAME,
         metadata,
-        Column(COLUMN_NAME, type_),
+        Column(COLUMN_NAME, engine.dialect.ischema_names[type_]),
         schema=schema
     )
     input_table.create()
@@ -373,7 +638,7 @@ def test_get_column_cast_expression_numeric_options(
 
 
 expect_cast_tuples = [
-    (key, [target for target in val[TARGET_LIST]])
+    (key, [target for target in val[TARGET_DICT]])
     for key, val in MASTER_DB_TYPE_MAP_SPEC.items()
 ]
 
diff --git a/mathesar/tests/views/api/test_column_api.py b/mathesar/tests/views/api/test_column_api.py
--- a/mathesar/tests/views/api/test_column_api.py
+++ b/mathesar/tests/views/api/test_column_api.py
@@ -42,7 +42,10 @@ def test_column_list(column_test_table, client):
             'index': 0,
             'nullable': False,
             'primary_key': True,
-            'valid_target_types': None,
+            'valid_target_types': [
+                'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                'INTEGER', 'NUMERIC', 'REAL', 'SMALLINT', 'VARCHAR',
+            ],
         },
         {
             'name': 'mycolumn1',
@@ -51,7 +54,10 @@ def test_column_list(column_test_table, client):
             'index': 1,
             'nullable': False,
             'primary_key': False,
-            'valid_target_types': None,
+            'valid_target_types': [
+                'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                'INTEGER', 'NUMERIC', 'REAL', 'SMALLINT', 'VARCHAR',
+            ],
         },
         {
             'name': 'mycolumn2',
@@ -60,7 +66,10 @@ def test_column_list(column_test_table, client):
             'index': 2,
             'nullable': True,
             'primary_key': False,
-            'valid_target_types': None,
+            'valid_target_types': [
+                'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                'INTEGER', 'NUMERIC', 'REAL', 'SMALLINT', 'VARCHAR',
+            ],
         },
         {
             'name': 'mycolumn3',
@@ -70,14 +79,8 @@ def test_column_list(column_test_table, client):
             'nullable': True,
             'primary_key': False,
             'valid_target_types': [
-                'BOOLEAN',
-                'DECIMAL',
-                'DOUBLE PRECISION',
-                'FLOAT',
-                'INTERVAL',
-                'NUMERIC',
-                'REAL',
-                'VARCHAR',
+                'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                'INTEGER', 'INTERVAL', 'NUMERIC', 'REAL', 'SMALLINT', 'VARCHAR',
                 'mathesar_types.email',
             ],
         }
@@ -97,7 +100,10 @@ def test_column_list(column_test_table, client):
                 'index': 0,
                 'nullable': False,
                 'primary_key': True,
-                'valid_target_types': None
+                'valid_target_types': [
+                    'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                    'INTEGER', 'NUMERIC', 'REAL', 'SMALLINT', 'VARCHAR',
+                ],
             },
         ),
         (
@@ -109,7 +115,10 @@ def test_column_list(column_test_table, client):
                 'index': 2,
                 'nullable': True,
                 'primary_key': False,
-                'valid_target_types': None
+                'valid_target_types': [
+                    'BIGINT', 'BOOLEAN', 'DECIMAL', 'DOUBLE PRECISION', 'FLOAT',
+                    'INTEGER', 'NUMERIC', 'REAL', 'SMALLINT', 'VARCHAR',
+                ],
             },
         ),
     ]
diff --git a/mathesar/tests/views/api/test_table_api.py b/mathesar/tests/views/api/test_table_api.py
--- a/mathesar/tests/views/api/test_table_api.py
+++ b/mathesar/tests/views/api/test_table_api.py
@@ -489,7 +489,7 @@ def test_table_previews_unsupported_type(client, schema, engine_email_type):
     post_body = {
         'columns': [
             {"name": "mathesar_id", "type": "INTEGER"},
-            {"name": "col_1", "type": "INTEGER"},
+            {"name": "col_1", "type": "notatype"},
             {"name": "col_2", "type": "BOOLEAN"},
             {"name": "col_3", "type": "BOOLEAN"},
             {"name": "col_4", "type": "NUMERIC"},