Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions migrations/versions/013_reconcile_model_indexes.py
Original file line number Diff line number Diff line change
@@ -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,
)
12 changes: 6 additions & 6 deletions openhands/automation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading