Repository: mathesar-foundation/mathesar

Issue:
Table creation returns 500 if there's a table with the same name
## Description
<!-- A clear and concise description of what the bug is. -->
Making a request to create a new table from a data file with the name of a table that already exists results in:
```
InvalidRequestError at /api/v0/tables/

Table 'public.Untitled' is already defined for this MetaData instance.  Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
```

## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
We should be validating that there isn't a table with the same name at the API layer and raising DRF's `ValidationError` (which results in a 400, not a 500).


Discussion:

Resolving patch:
diff --git a/db/tables.py b/db/tables.py
--- a/db/tables.py
+++ b/db/tables.py
@@ -45,9 +45,6 @@ def create_mathesar_table(name, schema, columns_, engine, metadata=None):
     # SQLAlchemy context (e.g., for creating a ForeignKey relationship)
     if metadata is None:
         metadata = MetaData(bind=engine, schema=schema)
-    # This reflection step lets us notice any "table already exists"
-    # errors before sending error-generating requests to the DB.
-    metadata.reflect()
     table = Table(
         name,
         metadata,
diff --git a/mathesar/views/api.py b/mathesar/views/api.py
--- a/mathesar/views/api.py
+++ b/mathesar/views/api.py
@@ -5,7 +5,7 @@
 from rest_framework.response import Response
 from rest_framework.decorators import action
 from django_filters import rest_framework as filters
-from psycopg2.errors import DuplicateColumn, UndefinedFunction
+from psycopg2.errors import DuplicateColumn, DuplicateTable, UndefinedFunction
 from sqlalchemy.exc import ProgrammingError
 from sqlalchemy_filters.exceptions import (
     BadFilterFormat, BadSortFormat, FilterFieldNotFound, SortFieldNotFound,
@@ -85,18 +85,25 @@ def create(self, request):
         serializer = TableSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
 
-        if serializer.validated_data['data_files']:
-            table = create_table_from_datafile(
-                serializer.validated_data['data_files'],
-                serializer.validated_data['name'],
-                serializer.validated_data['schema'],
-            )
-        else:
-            table = create_empty_table(
-                serializer.validated_data['name'],
-                serializer.validated_data['schema'],
-            )
-
+        try:
+            if serializer.validated_data['data_files']:
+                table = create_table_from_datafile(
+                    serializer.validated_data['data_files'],
+                    serializer.validated_data['name'],
+                    serializer.validated_data['schema'],
+                )
+            else:
+                table = create_empty_table(
+                    serializer.validated_data['name'],
+                    serializer.validated_data['schema'],
+                )
+        except ProgrammingError as e:
+            if type(e.orig) == DuplicateTable:
+                raise ValidationError(
+                    f"Relation {request.data['name']} already exists in schema {request.data['schema']}"
+                )
+            else:
+                raise APIException(e)
         serializer = TableSerializer(table, context={'request': request})
         return Response(serializer.data, status=status.HTTP_201_CREATED)
 


Test patch:
diff --git a/mathesar/tests/imports/test_csv.py b/mathesar/tests/imports/test_csv.py
--- a/mathesar/tests/imports/test_csv.py
+++ b/mathesar/tests/imports/test_csv.py
@@ -1,7 +1,7 @@
 import pytest
 
 from django.core.files import File
-from sqlalchemy.exc import InvalidRequestError
+from sqlalchemy.exc import ProgrammingError
 from sqlalchemy import text
 
 from mathesar.models import DataFile, Schema
@@ -76,9 +76,7 @@ def test_headerless_csv_upload(headerless_data_file, schema):
 
 def test_csv_upload_with_duplicate_table_name(data_file, schema):
     table_name = 'NASA 2'
-    already_defined_str = (
-        f"Table '{schema.name}.{table_name}' is already defined"
-    )
+    already_defined_str = ('relation "NASA 2" already exists')
 
     table = create_table_from_csv(data_file, table_name, schema)
     assert table is not None
@@ -86,9 +84,9 @@ def test_csv_upload_with_duplicate_table_name(data_file, schema):
     assert table.schema == schema
     assert table.sa_num_records() == 1393
 
-    with pytest.raises(InvalidRequestError) as excinfo:
+    with pytest.raises(ProgrammingError) as excinfo:
         create_table_from_csv(data_file, table_name, schema)
-        assert already_defined_str in str(excinfo)
+    assert already_defined_str in str(excinfo.value)
 
 
 def test_csv_upload_table_imported_to(data_file, schema):
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
@@ -330,6 +330,19 @@ def test_table_create_with_empty_datafile(client, schema):
     check_table_response(response_table, table, table_name)
 
 
+def test_table_create_with_same_name(client, schema):
+    table_name = 'test_table_duplicate'
+    body = {
+        'name': table_name,
+        'schema': schema.id,
+    }
+    client.post('/api/v0/tables/', body)
+    response = client.post('/api/v0/tables/', body)
+    response_error = response.json()
+    assert response.status_code == 400
+    assert response_error[0] == f"Relation {table_name} already exists in schema {schema.id}"
+
+
 def test_table_partial_update(create_table, client):
     table_name = 'NASA Table Partial Update'
     new_table_name = 'NASA Table Partial Update New'