Skip to content

Commit

Permalink
Merge pull request #3200 from manthan-jsharma/issue-2126
Browse files Browse the repository at this point in the history
added: exclusion violation custom error
  • Loading branch information
Anish9901 committed Dec 4, 2023
2 parents e86f93a + 12aa7e8 commit 1644ec5
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 3 deletions.
23 changes: 22 additions & 1 deletion mathesar/api/exceptions/database_exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,32 @@ class ExclusionViolationAPIException(MathesarAPIException):
def __init__(
self,
exception,
message=None,
message="The requested update violates an exclusion constraint",
field=None,
details=None,
table=None,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
):
if details is None and table is not None:
details = {}
try:
constraint_oid = get_constraint_oid_by_name_and_table_oid(
exception.orig.diag.constraint_name,
table.oid,
table._sa_engine
)
constraint = Constraint.objects.get(table=table, oid=constraint_oid)
details = {
"constraint": constraint.id,
"constraint_columns": [c.id for c in constraint.columns],
}
except Exception:
warnings.warn("Could not enrich Exception")
details.update(
{
"original_details": exception.orig.diag.message_detail,
}
)
super().__init__(exception, self.error_code, message, field, details, status_code)


Expand Down
14 changes: 13 additions & 1 deletion mathesar/api/serializers/records.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from psycopg2.errors import NotNullViolation, UniqueViolation, CheckViolation
from psycopg2.errors import NotNullViolation, UniqueViolation, CheckViolation, ExclusionViolation
from rest_framework import serializers
from rest_framework import status
from sqlalchemy.exc import IntegrityError
Expand Down Expand Up @@ -53,6 +53,12 @@ def update(self, instance, validated_data):
e,
status_code=status.HTTP_400_BAD_REQUEST
)
elif type(e.orig) is ExclusionViolation:
raise database_api_exceptions.ExclusionViolationAPIException(
e,
table=table,
status_code=status.HTTP_400_BAD_REQUEST,
)
else:
raise database_api_exceptions.MathesarAPIException(e, status_code=status.HTTP_400_BAD_REQUEST)
return record
Expand Down Expand Up @@ -80,6 +86,12 @@ def create(self, validated_data):
e,
status_code=status.HTTP_400_BAD_REQUEST
)
elif type(e.orig) is ExclusionViolation:
raise database_api_exceptions.ExclusionViolationAPIException(
e,
table=table,
status_code=status.HTTP_400_BAD_REQUEST,
)
else:
raise database_api_exceptions.MathesarAPIException(e, status_code=status.HTTP_400_BAD_REQUEST)
return record
Expand Down
57 changes: 57 additions & 0 deletions mathesar/tests/api/test_record_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import pytest
from sqlalchemy import text
from copy import deepcopy
from unittest.mock import patch

Expand All @@ -9,6 +10,7 @@
from db.records.operations.group import GroupBy
from db.records.operations.sort import BadSortFormat, SortFieldNotFound

from mathesar.state import reset_reflection
from mathesar.api.exceptions.error_codes import ErrorCodes
from mathesar.api.utils import follows_json_number_spec
from mathesar.functions.operations.convert import rewrite_db_function_spec_column_ids_to_names
Expand Down Expand Up @@ -1464,3 +1466,58 @@ def test_record_patch_unique_violation(create_patents_table, client):
f'/api/db/v0/tables/{table.id}/constraints/{constraint_id}/'
).json()
assert actual_constraint_details['name'] == 'NASA unique record PATCH_pkey'


def test_record_post_exclusion_violation(reservations_table, engine, client):
table_name = 'Exclusion test post'
table = reservations_table(table_name)
room_number_column_id = table.get_column_name_id_bidirectional_map()['room_number']
columns_name_id_map = table.get_column_name_id_bidirectional_map()
query = text(
f"""CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE "Reservations"."{table_name}" DROP CONSTRAINT IF EXISTS room_overlap;
ALTER TABLE "Reservations"."{table_name}"
ADD CONSTRAINT room_overlap
EXCLUDE USING gist
("room_number" WITH =, TSRANGE("check_in_date", "check_out_date", '[]') WITH &&);"""
)
with engine.begin() as conn:
conn.execute(query)
reset_reflection(db_name=table.schema.database.name)
response = client.post(f'/api/db/v0/tables/{table.id}/records/', data={
str(columns_name_id_map['id']): 3,
str(columns_name_id_map['room_number']): 1,
str(columns_name_id_map['check_in_date']): '11/12/2023',
str(columns_name_id_map['check_out_date']): '11/21/2023',
})
actual_exception = response.json()[0]
assert actual_exception['code'] == ErrorCodes.ExclusionViolation.value
assert actual_exception['message'] == 'The requested update violates an exclusion constraint'
assert actual_exception['detail']['constraint_columns'] == [room_number_column_id]


def test_record_patch_exclusion_violation(reservations_table, engine, client):
table_name = 'Exclusion test patch'
table = reservations_table(table_name)
room_number_column_id = table.get_column_name_id_bidirectional_map()['room_number']
columns_name_id_map = table.get_column_name_id_bidirectional_map()
query = text(
f"""CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE "Reservations"."{table_name}" DROP CONSTRAINT IF EXISTS room_overlap;
ALTER TABLE "Reservations"."{table_name}"
ADD CONSTRAINT room_overlap
EXCLUDE USING gist
("room_number" WITH =, TSRANGE("check_in_date", "check_out_date", '[]') WITH &&);"""
)
with engine.begin() as conn:
conn.execute(query)
reset_reflection(db_name=table.schema.database.name)
response = client.patch(f'/api/db/v0/tables/{table.id}/records/{2}/', data={
str(columns_name_id_map['room_number']): 1,
str(columns_name_id_map['check_in_date']): '11/12/2023',
str(columns_name_id_map['check_out_date']): '11/21/2023',
})
actual_exception = response.json()[0]
assert actual_exception['code'] == ErrorCodes.ExclusionViolation.value
assert actual_exception['message'] == 'The requested update violates an exclusion constraint'
assert actual_exception['detail']['constraint_columns'] == [room_number_column_id]
33 changes: 32 additions & 1 deletion mathesar/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.conf import settings
from rest_framework.test import APIClient

from sqlalchemy import Column, MetaData, Integer
from sqlalchemy import Column, MetaData, Integer, Date
from sqlalchemy import Table as SATable

from db.tables.operations.select import get_oid_from_table
Expand All @@ -30,6 +30,7 @@
from mathesar.state import reset_reflection
from mathesar.state.base import set_initial_reflection_happened
from db.metadata import get_empty_metadata
from db.tests.columns.utils import create_test_table


@pytest.fixture
Expand Down Expand Up @@ -329,6 +330,12 @@ def patent_schema(create_schema):
yield create_schema(PATENT_SCHEMA)


@pytest.fixture
def reservations_schema(create_schema):
RESERVATIONS_SCHEMA = 'Reservations'
yield create_schema(RESERVATIONS_SCHEMA)


# TODO rename to create_ma_schema
@pytest.fixture
def create_schema(test_db_model, create_db_schema):
Expand Down Expand Up @@ -362,6 +369,30 @@ def _create_mathesar_table(
yield _create_mathesar_table


@pytest.fixture
def reservations_table(engine, reservations_schema):
schema_name = reservations_schema.name

def _create_test_table(table_name, schema_name=schema_name):
table_name = table_name or 'Exclusion Check'
schema_name = reservations_schema.name
cols = [
Column('id', Integer, primary_key=True),
Column('room_number', Integer),
Column('check_in_date', Date),
Column('check_out_date', Date)
]
insert_data = [
(1, 1, '11/10/2023', '11/15/2023'),
(2, 1, '11/16/2023', '11/20/2023')
]
sa_table = create_test_table(table_name, cols, insert_data, schema_name, engine)
table_oid = get_oid_from_table(sa_table.name, schema_name, engine)
table = Table.current_objects.create(oid=table_oid, schema=reservations_schema)
return table
return _create_test_table


@pytest.fixture
def create_patents_table(patents_csv_filepath, patent_schema, create_table):
schema_name = patent_schema.name
Expand Down

0 comments on commit 1644ec5

Please sign in to comment.