Repository navigation
fix(registry): preset/model-endpoint mutation integrity (rename, default, delete) #588
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
eec4067
21cb82f
52699a9
a9c2ca4
ffb89b3
61258c3
532b330
da9269c
1d0ed20
7e693c1
1477003
b0ffc08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| from typing import TYPE_CHECKING | ||
|
|
||
| from core.ports.preset_repo import PresetRepository | ||
| from core.utils.exceptions import NotFoundError | ||
|
|
||
| if TYPE_CHECKING: | ||
| import asyncpg | ||
|
|
@@ -79,18 +80,39 @@ async def upsert(self, name: str, preset_type: str, config: dict) -> dict: | |
| return self._row_to_dict(rec) | ||
|
|
||
| async def rename(self, old_name: str, new_name: str, preset_type: str, config: dict) -> dict: | ||
| """Rename a preset to ``new_name`` (also updating its config) and repoint | ||
| every referencing partition, atomically. | ||
|
|
||
| Raises :class:`NotFoundError` if ``old_name`` no longer exists; a collision | ||
| with an existing ``new_name`` is rejected by the ``(name, preset_type)`` | ||
| unique constraint. Returns the updated preset row as a dict. | ||
| """ | ||
| # Rename in place with a plain UPDATE (not an upsert): the | ||
| # (name, preset_type) unique constraint then rejects a collision with an | ||
| # existing target name instead of an upsert silently overwriting it, and | ||
| # the row keeps its identity/created_at. Partitions reference a preset by | ||
| # its name string (partitions.{col}) with no FK, so referencing partitions | ||
| # are repointed in the same transaction or they would orphan. | ||
| col = _preset_column(preset_type) | ||
| async with self.pool.acquire() as conn: | ||
| async with conn.transaction(): | ||
| await conn.execute( | ||
| "DELETE FROM pipeline_presets WHERE name = $1 AND preset_type = $2", | ||
| rec = await conn.fetchrow( | ||
| "UPDATE pipeline_presets SET name = $2, config = $3::jsonb, updated_at = now() " | ||
| "WHERE name = $1 AND preset_type = $4 RETURNING *", | ||
| old_name, | ||
| new_name, | ||
| config, | ||
| preset_type, | ||
| ) | ||
|
Comment on lines
97
to
106
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rename looks much safer now, but I think there's still one small consistency edge case.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Valid edge — a partition write committing old_name concurrently with the rename can still leave a stale reference. The robust fix (a shared advisory lock spanning preset rename AND the partition write path) is cross-cutting and touches the partition-write hot path, so we're deferring it to a focused follow-up after the pre-release rather than expanding this PR. Leaving this thread open to track it. |
||
| rec = await conn.fetchrow( | ||
| _UPSERT_PRESET_SQL, | ||
| if rec is None: | ||
| # old_name vanished between the service's existence check and | ||
| # this UPDATE (concurrent delete) — fail cleanly instead of | ||
| # blowing up in _row_to_dict(None). | ||
| raise NotFoundError(f"Preset '{old_name}' of type '{preset_type}' not found.") | ||
| await conn.execute( | ||
| f"UPDATE partitions SET {col} = $1 WHERE {col} = $2", | ||
| new_name, | ||
| preset_type, | ||
| config, | ||
| old_name, | ||
| ) | ||
| return self._row_to_dict(rec) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.