Merge pull request #113 from abhi1693/perf/activity-events-eventtype-createdat
perf(db): index activity_events by (event_type, created_at)
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
"""merge heads for activity_events index
|
||||
|
||||
Revision ID: 836cf8009001
|
||||
Revises: b05c7b628636, fa6e83f8d9a1
|
||||
Create Date: 2026-02-13 10:57:21.395382
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '836cf8009001'
|
||||
down_revision = ('b05c7b628636', 'fa6e83f8d9a1')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -0,0 +1,32 @@
|
||||
"""add activity_events event_type created_at index
|
||||
|
||||
Revision ID: b05c7b628636
|
||||
Revises: bbd5bbb26d97
|
||||
Create Date: 2026-02-12 09:54:32.359256
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'b05c7b628636'
|
||||
down_revision = 'bbd5bbb26d97'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Speed activity feed/event filters that select by event_type and order by created_at.
|
||||
# Allows index scans (often backward) with LIMIT instead of bitmap+sort.
|
||||
op.create_index(
|
||||
"ix_activity_events_event_type_created_at",
|
||||
"activity_events",
|
||||
["event_type", "created_at"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_activity_events_event_type_created_at", table_name="activity_events")
|
||||
26
backend/migrations/versions/bbd5bbb26d97_merge_heads.py
Normal file
26
backend/migrations/versions/bbd5bbb26d97_merge_heads.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""merge heads
|
||||
|
||||
Revision ID: bbd5bbb26d97
|
||||
Revises: 99cd6df95f85, b4338be78eec
|
||||
Create Date: 2026-02-12 09:54:21.149702
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'bbd5bbb26d97'
|
||||
down_revision = ('99cd6df95f85', 'b4338be78eec')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -0,0 +1,26 @@
|
||||
"""merge heads after board lead rule
|
||||
|
||||
Revision ID: d3ca36cf31a1
|
||||
Revises: 1a7b2c3d4e5f, 836cf8009001
|
||||
Create Date: 2026-02-13 11:02:04.893298
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'd3ca36cf31a1'
|
||||
down_revision = ('1a7b2c3d4e5f', '836cf8009001')
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import BaseModel, Field
|
||||
from starlette.requests import Request
|
||||
@@ -17,6 +18,8 @@ from app.core.error_handling import (
|
||||
_json_safe,
|
||||
_request_validation_exception_handler,
|
||||
_response_validation_exception_handler,
|
||||
_request_validation_handler,
|
||||
_response_validation_handler,
|
||||
install_error_handling,
|
||||
)
|
||||
|
||||
@@ -243,3 +246,91 @@ async def test_http_exception_wrapper_rejects_wrong_exception() -> None:
|
||||
req = Request({"type": "http", "headers": [], "state": {}})
|
||||
with pytest.raises(TypeError, match="Expected StarletteHTTPException"):
|
||||
await _http_exception_exception_handler(req, Exception("x"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_request_validation_handler_includes_request_id() -> None:
|
||||
req = Request({"type": "http", "headers": [], "state": {"request_id": "req-1"}})
|
||||
exc = RequestValidationError(
|
||||
[
|
||||
{
|
||||
"loc": ("query", "limit"),
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
resp = await _request_validation_handler(req, exc)
|
||||
assert resp.status_code == 422
|
||||
assert resp.body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_request_validation_exception_wrapper_success_path() -> None:
|
||||
req = Request({"type": "http", "headers": [], "state": {"request_id": "req-wrap-1"}})
|
||||
exc = RequestValidationError(
|
||||
[
|
||||
{
|
||||
"loc": ("query", "limit"),
|
||||
"msg": "value is not a valid integer",
|
||||
"type": "type_error.integer",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
resp = await _request_validation_exception_handler(req, exc)
|
||||
assert resp.status_code == 422
|
||||
assert b"request_id" in resp.body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_response_validation_handler_includes_request_id() -> None:
|
||||
req = Request(
|
||||
{
|
||||
"type": "http",
|
||||
"method": "GET",
|
||||
"path": "/x",
|
||||
"headers": [],
|
||||
"state": {"request_id": "req-2"},
|
||||
}
|
||||
)
|
||||
exc = ResponseValidationError(
|
||||
[
|
||||
{
|
||||
"loc": ("response", "name"),
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
resp = await _response_validation_handler(req, exc)
|
||||
assert resp.status_code == 500
|
||||
assert resp.body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_response_validation_exception_wrapper_success_path() -> None:
|
||||
req = Request(
|
||||
{
|
||||
"type": "http",
|
||||
"method": "GET",
|
||||
"path": "/x",
|
||||
"headers": [],
|
||||
"state": {"request_id": "req-wrap-2"},
|
||||
}
|
||||
)
|
||||
exc = ResponseValidationError(
|
||||
[
|
||||
{
|
||||
"loc": ("response", "name"),
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
resp = await _response_validation_exception_handler(req, exc)
|
||||
assert resp.status_code == 500
|
||||
assert b"request_id" in resp.body
|
||||
|
||||
Reference in New Issue
Block a user