From 37050a4bcdb2ebe662182a5c2a78a4526e2c5921 Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 24 Apr 2026 22:15:23 -0400 Subject: [PATCH] =?UTF-8?q?fix(db):=20claim=5Fnext=5Fmutation=20works=20on?= =?UTF-8?q?=20MySQL=20=E2=80=94=20derived-table=20workaround?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MySQL ERROR 1093 forbids referencing the UPDATE target inside a subquery; the existing UPDATE ... WHERE id = (SELECT id FROM topology_mutations ...) form blew up on every mutation claim under the MySQL backend, so no mutation ever progressed past pending. Wrap the inner SELECT in a derived table (SELECT id FROM (...) AS _next). MySQL materialises the derived rowset before applying the UPDATE, sidestepping 1093. SQLite accepts both forms, so the single-statement atomic claim semantics are preserved on both backends — racing watchers still serialise correctly. --- decnet/web/db/sqlmodel_repo.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/decnet/web/db/sqlmodel_repo.py b/decnet/web/db/sqlmodel_repo.py index 40dcdcde..358d5722 100644 --- a/decnet/web/db/sqlmodel_repo.py +++ b/decnet/web/db/sqlmodel_repo.py @@ -1711,15 +1711,21 @@ class SQLModelRepository(BaseRepository): # oldest pending row; the outer UPDATE re-checks state so a # second racer that also saw that id finds state='applying' # and matches zero rows. + # MySQL forbids referencing the UPDATE target inside a + # subquery (ERROR 1093). Wrapping the inner SELECT in a + # derived table forces materialisation and sidesteps the + # rule. SQLite accepts both forms, so this stays portable. sql = text( """ UPDATE topology_mutations SET state = 'applying' WHERE id = ( - SELECT id FROM topology_mutations - WHERE topology_id = :t AND state = 'pending' - ORDER BY requested_at ASC - LIMIT 1 + SELECT id FROM ( + SELECT id FROM topology_mutations + WHERE topology_id = :t AND state = 'pending' + ORDER BY requested_at ASC + LIMIT 1 + ) AS _next ) AND state = 'pending' """