fix(dn): reconcile staged ingestion indexes by name
This commit is contained in:
parent
ad23b6572e
commit
498b25858a
2 changed files with 56 additions and 10 deletions
|
|
@ -5,6 +5,7 @@ The migration is idempotent and is a dry run unless ``--apply`` is supplied.
|
|||
It reads existing PocketBase fields and indexes before appending anything, so
|
||||
running it against a live instance preserves unrelated schema changes.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pb_env import PB, http, superuser_token
|
||||
|
|
@ -51,6 +52,32 @@ def field(name, field_type="text", **options):
|
|||
return {"name": name, "type": field_type, "required": False, **options}
|
||||
|
||||
|
||||
def index_name(statement):
|
||||
"""Extract an SQLite/PocketBase index name from a CREATE INDEX statement."""
|
||||
match = re.search(r"\bINDEX\s+`?([^`\s]+)`?", statement, re.IGNORECASE)
|
||||
if not match:
|
||||
raise ValueError(f"not a CREATE INDEX statement: {statement!r}")
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def merge_indexes(existing, wanted):
|
||||
"""Union indexes by name, replacing stale same-name definitions in place."""
|
||||
merged = list(existing)
|
||||
positions = {index_name(statement): i for i, statement in enumerate(merged)}
|
||||
changes = []
|
||||
for statement in wanted:
|
||||
name = index_name(statement)
|
||||
position = positions.get(name)
|
||||
if position is None:
|
||||
positions[name] = len(merged)
|
||||
merged.append(statement)
|
||||
changes.append(("add", statement))
|
||||
elif merged[position] != statement:
|
||||
merged[position] = statement
|
||||
changes.append(("replace", statement))
|
||||
return merged, changes
|
||||
|
||||
|
||||
def add_fields(token, name, wanted):
|
||||
collection = require_collection(token, name)
|
||||
existing = list(collection.get("fields", []))
|
||||
|
|
@ -72,15 +99,15 @@ def add_fields(token, name, wanted):
|
|||
def add_indexes(token, name, wanted):
|
||||
collection = require_collection(token, name)
|
||||
existing = list(collection.get("indexes", []) or [])
|
||||
additions = [item for item in wanted if item not in existing]
|
||||
if not additions:
|
||||
merged, changes = merge_indexes(existing, wanted)
|
||||
if not changes:
|
||||
print(f" {name}: indexes already present")
|
||||
return
|
||||
print(f" {name}: add indexes {additions}")
|
||||
print(f" {name}: reconcile indexes {[item for _, item in changes]}")
|
||||
if not DRY_RUN:
|
||||
code, response = http(
|
||||
"PATCH", f"/api/collections/{collection['id']}",
|
||||
{"indexes": existing + additions}, token,
|
||||
{"indexes": merged}, token,
|
||||
)
|
||||
if code != 200:
|
||||
sys.exit(f"PATCH {name} indexes failed: {code} {response}")
|
||||
|
|
@ -113,7 +140,26 @@ def system_fields():
|
|||
]
|
||||
|
||||
|
||||
def self_test():
|
||||
old = [
|
||||
"CREATE INDEX `unrelated` ON `x` (`value`)",
|
||||
"CREATE UNIQUE INDEX `idx_delivery_notes_batch_note` ON `delivery_notes` (`batch_note`)",
|
||||
]
|
||||
replacement = (
|
||||
"CREATE UNIQUE INDEX `idx_delivery_notes_batch_note` ON `delivery_notes` (`batch_note`) "
|
||||
"WHERE `batch_note` IS NOT NULL AND `batch_note` != ''"
|
||||
)
|
||||
merged, changes = merge_indexes(old, [replacement])
|
||||
assert merged == [old[0], replacement]
|
||||
assert changes == [("replace", replacement)]
|
||||
assert merge_indexes(merged, [replacement])[1] == []
|
||||
print("self-test: index replacement and idempotence passed")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
if "--self-test" in sys.argv:
|
||||
return self_test()
|
||||
if APPLY and confirmation_host() != PB:
|
||||
sys.exit(
|
||||
"REFUSING --apply: pass --confirm-host with the exact configured "
|
||||
|
|
|
|||
|
|
@ -509,12 +509,12 @@ export interface SupplierDocumentAliasRecord extends RecordModel {
|
|||
export interface DnReviewEventRecord extends RecordModel {
|
||||
collectionName: "dn_review_events";
|
||||
batch: string;
|
||||
note: string;
|
||||
action: string;
|
||||
before: unknown;
|
||||
after: unknown;
|
||||
reason: string;
|
||||
actor: string;
|
||||
note?: string;
|
||||
action?: string;
|
||||
before?: unknown;
|
||||
after?: unknown;
|
||||
reason?: string;
|
||||
actor?: string;
|
||||
expand?: { batch?: DnBatchRecord; note?: DnBatchNoteRecord };
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue