fix(handle_directory_rename): iterate in Python — asyncpg rejected SUBSTRING(... FROM LENGTH(...)+1)
The raw-SQL prefix rewrite from f4a03b6 used
`SUBSTRING(filepath FROM LENGTH(:old_prefix) + 1)`. asyncpg's type
inference miscategorises the LENGTH() result and rejects the
parameter as "$2: int (expected str)" at execute time, so every
directory-rename webhook 500'd in production despite the surrounding
logic being correct.
Switch to the same per-row Python loop the existing PATCH
/api/v1/folders/{id} endpoint already uses. Folder renames are rare
and span ≤1k photos typically — the cost of N row UPDATEs is fine.
End-to-end verified:
RenameTestA -> RenameTestA-FromNC (WebDAV MOVE outside mule):
nc-webhook renamed (dir): {photos: 2, folders: 2, source_roots: 0}
DB rows now at -FromNC ✓
-FromNC -> -ViaMule (PATCH /folders/{id} inside mule):
mule rewrites synchronously
webhook fires back ~30s later
nc-webhook renamed (dir): {photos: 0, folders: 0, source_roots: 0}
idempotent no-op against an already-updated DB ✓
This commit is contained in:
@@ -618,43 +618,44 @@ async def handle_directory_rename(old_dirpath: str, new_dirpath: str) -> dict:
|
|||||||
)
|
)
|
||||||
return {"status": "cross_root", "discarded": n}
|
return {"status": "cross_root", "discarded": n}
|
||||||
|
|
||||||
# Same-root: prefix-rewrite. Use parameterised raw SQL so the
|
# Same-root: iterate the matching rows in Python and rewrite
|
||||||
# SUBSTRING + concat happens server-side in one shot; iterating
|
# the prefix attribute-side. We tried a single UPDATE … SET …
|
||||||
# in Python would mean N row updates.
|
# SUBSTRING(... FROM LENGTH(:old)+1) raw-SQL approach but
|
||||||
params = {
|
# asyncpg miscategorises the LENGTH() result and rejects it
|
||||||
"new_prefix": new_prefix,
|
# as "$2: int (expected str)". The PATCH /folders/{id}
|
||||||
"old_prefix": old_prefix,
|
# endpoint already loops in Python for the same reason — match
|
||||||
"off": len(old_prefix) + 1,
|
# its pattern. Folder renames are rare and typically span ≤1k
|
||||||
"old_pat": old_prefix + "/%",
|
# photos, so per-row UPDATEs are fine.
|
||||||
}
|
old_pat = old_prefix + "/%"
|
||||||
photos_res = await session.execute(
|
photos = (await session.execute(
|
||||||
text(
|
select(Photo).where(Photo.filepath.like(old_pat))
|
||||||
"UPDATE photos SET filepath = :new_prefix || SUBSTRING(filepath FROM :off) "
|
)).scalars().all()
|
||||||
"WHERE filepath LIKE :old_pat"
|
for p in photos:
|
||||||
),
|
p.filepath = new_prefix + p.filepath[len(old_prefix):]
|
||||||
params,
|
|
||||||
)
|
folders = (await session.execute(
|
||||||
folders_res = await session.execute(
|
select(Folder).where(
|
||||||
text(
|
or_(
|
||||||
"UPDATE folders SET path = CASE "
|
Folder.path == old_prefix,
|
||||||
"WHEN path = :old_prefix THEN :new_prefix "
|
Folder.path.like(old_pat),
|
||||||
"ELSE :new_prefix || SUBSTRING(path FROM :off) END "
|
)
|
||||||
"WHERE path = :old_prefix OR path LIKE :old_pat"
|
)
|
||||||
),
|
)).scalars().all()
|
||||||
params,
|
for f in folders:
|
||||||
)
|
f.path = new_prefix if f.path == old_prefix else \
|
||||||
source_roots_res = await session.execute(
|
new_prefix + f.path[len(old_prefix):]
|
||||||
text(
|
|
||||||
"UPDATE source_roots SET path = :new_prefix WHERE path = :old_prefix"
|
source_roots = (await session.execute(
|
||||||
),
|
select(SourceRoot).where(SourceRoot.path == old_prefix)
|
||||||
params,
|
)).scalars().all()
|
||||||
)
|
for sr in source_roots:
|
||||||
|
sr.path = new_prefix
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return {
|
return {
|
||||||
"status": "renamed",
|
"status": "renamed",
|
||||||
"photos": photos_res.rowcount or 0,
|
"photos": len(photos),
|
||||||
"folders": folders_res.rowcount or 0,
|
"folders": len(folders),
|
||||||
"source_roots": source_roots_res.rowcount or 0,
|
"source_roots": len(source_roots),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user