From 4c8cfa43b51b18c42c0d98d5d5b0ef07619b22eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97SO?= <142557582+Linxiushen@users.noreply.github.com> Date: Wed, 5 Aug 2026 04:06:31 +0800 Subject: [PATCH] fix: reconcile model index declarations --- .../versions/013_reconcile_model_indexes.py | 54 +++++++++++++++++++ openhands/automation/models.py | 12 ++--- tests/test_db.py | 14 +++++ 3 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/013_reconcile_model_indexes.py diff --git a/migrations/versions/013_reconcile_model_indexes.py b/migrations/versions/013_reconcile_model_indexes.py new file mode 100644 index 00000000..36c01ca4 --- /dev/null +++ b/migrations/versions/013_reconcile_model_indexes.py @@ -0,0 +1,54 @@ +"""Reconcile ORM index declarations with the deployed schema. + +Revision ID: 013 +Revises: 012 +Create Date: 2026-08-04 +""" + +from collections.abc import Sequence + +from alembic import op + + +revision: str = "013" +down_revision: str = "012" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +AUTOMATION_INDEXES = ( + ("ix_automations_user_id", ["user_id"]), + ("ix_automations_org_id", ["org_id"]), + ("ix_automations_enabled", ["enabled"]), + ("ix_automations_deleted_at", ["deleted_at"]), + ("ix_automations_last_polled_at", ["last_polled_at"]), +) +RUN_TIMEOUT_INDEX = ("ix_automation_runs_timeout_at", ["timeout_at"]) + + +def upgrade() -> None: + for name, _columns in AUTOMATION_INDEXES: + op.drop_index(name, table_name="automations", if_exists=True) + + op.drop_index( + RUN_TIMEOUT_INDEX[0], + table_name="automation_runs", + if_exists=True, + ) + + +def downgrade() -> None: + for name, columns in AUTOMATION_INDEXES: + op.create_index( + name, + "automations", + columns, + if_not_exists=True, + ) + + op.create_index( + RUN_TIMEOUT_INDEX[0], + "automation_runs", + RUN_TIMEOUT_INDEX[1], + if_not_exists=True, + ) diff --git a/openhands/automation/models.py b/openhands/automation/models.py index 888ef569..e962b08f 100644 --- a/openhands/automation/models.py +++ b/openhands/automation/models.py @@ -50,8 +50,8 @@ class Automation(Base): __tablename__ = "automations" id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) - user_id: Mapped[uuid.UUID] = mapped_column(Uuid, nullable=False, index=True) - org_id: Mapped[uuid.UUID] = mapped_column(Uuid, nullable=False, index=True) + user_id: Mapped[uuid.UUID] = mapped_column(Uuid, nullable=False) + org_id: Mapped[uuid.UUID] = mapped_column(Uuid, nullable=False) name: Mapped[str] = mapped_column(String(500), nullable=False) telemetry_distinct_id: Mapped[str | None] = mapped_column( String(256), nullable=True @@ -86,11 +86,11 @@ class Automation(Base): keep_alive: Mapped[bool | None] = mapped_column(default=None, nullable=True) # Whether the automation is enabled (can be triggered) - enabled: Mapped[bool] = mapped_column(default=True, nullable=False, index=True) + enabled: Mapped[bool] = mapped_column(default=True, nullable=False) # Soft delete timestamp (NULL = not deleted) deleted_at: Mapped[datetime | None] = mapped_column( - DateTime(timezone=True), nullable=True, index=True + DateTime(timezone=True), nullable=True ) # Last time the scheduler fired this automation @@ -100,7 +100,7 @@ class Automation(Base): # Last time the scheduler polled/checked this automation last_polled_at: Mapped[datetime | None] = mapped_column( - DateTime(timezone=True), nullable=True, index=True + DateTime(timezone=True), nullable=True ) created_at: Mapped[datetime] = mapped_column( @@ -156,7 +156,7 @@ class AutomationRun(Base): # Pre-computed deadline: started_at + max_duration. Set when transitioning # to RUNNING, used by the staleness watchdog for efficient indexed queries. timeout_at: Mapped[datetime | None] = mapped_column( - DateTime(timezone=True), nullable=True, index=True + DateTime(timezone=True), nullable=True ) # The sandbox ID used for execution (for status verification) diff --git a/tests/test_db.py b/tests/test_db.py index b952a2c9..21c410d2 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -252,8 +252,22 @@ def test_migrations_run_on_sqlite(self, monkeypatch): run_indexes = { index["name"] for index in inspector.get_indexes("automation_runs") } + automation_indexes = { + index["name"] for index in inspector.get_indexes("automations") + } + assert "ix_automation_runs_automation_id" in run_indexes assert "ix_automation_runs_status_created_at" in run_indexes assert "ix_automation_runs_status_timeout_at" in run_indexes + assert "ix_automation_runs_timeout_at" not in run_indexes + assert automation_indexes.isdisjoint( + { + "ix_automations_user_id", + "ix_automations_org_id", + "ix_automations_enabled", + "ix_automations_deleted_at", + "ix_automations_last_polled_at", + } + ) engine.dispose() finally: