|
@@ -502,6 +502,103 @@ async def _migrate_update_auto_link_constraint(conn) -> None:
|
|
|
raise
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+async def _migrate_widen_spoolman_slot_ams_id_range(conn) -> None:
|
|
|
|
|
+ """Widen ck_ams_id_range on spoolman_slot_assignments to admit AMS-HT (#1274).
|
|
|
|
|
+
|
|
|
|
|
+ Old formula: (ams_id >= 0 AND ams_id <= 7) OR ams_id = 255
|
|
|
|
|
+ New formula: (ams_id >= 0 AND ams_id <= 7) OR (ams_id >= 128 AND ams_id <= 191) OR ams_id = 255
|
|
|
|
|
+
|
|
|
|
|
+ The H2C/H2D AMS-HT reports ams_id 128+. The old constraint rejected every
|
|
|
|
|
+ AMS-HT slot link with `IntegrityError: CHECK constraint failed: ck_ams_id_range`.
|
|
|
|
|
+
|
|
|
|
|
+ PostgreSQL: DROP CONSTRAINT IF EXISTS + ADD new formula via _safe_execute.
|
|
|
|
|
+ SQLite: table recreation when the old (narrower) formula is detected in
|
|
|
|
|
+ sqlite_master. Fresh installs already have the widened constraint from
|
|
|
|
|
+ the CREATE TABLE migration above.
|
|
|
|
|
+ """
|
|
|
|
|
+ from sqlalchemy import text
|
|
|
|
|
+
|
|
|
|
|
+ _NEW_FORMULA = "(ams_id >= 0 AND ams_id <= 7) OR (ams_id >= 128 AND ams_id <= 191) OR ams_id = 255"
|
|
|
|
|
+ _CONSTRAINT_NAME = "ck_ams_id_range"
|
|
|
|
|
+
|
|
|
|
|
+ if not is_sqlite():
|
|
|
|
|
+ await _safe_execute(
|
|
|
|
|
+ conn,
|
|
|
|
|
+ f"ALTER TABLE spoolman_slot_assignments DROP CONSTRAINT IF EXISTS {_CONSTRAINT_NAME}",
|
|
|
|
|
+ )
|
|
|
|
|
+ await _safe_execute(
|
|
|
|
|
+ conn,
|
|
|
|
|
+ f"ALTER TABLE spoolman_slot_assignments ADD CONSTRAINT {_CONSTRAINT_NAME} CHECK ({_NEW_FORMULA})",
|
|
|
|
|
+ )
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ row = (
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ text("SELECT sql FROM sqlite_master WHERE type='table' AND name='spoolman_slot_assignments'")
|
|
|
|
|
+ )
|
|
|
|
|
+ ).fetchone()
|
|
|
|
|
+ if not row:
|
|
|
|
|
+ return
|
|
|
|
|
+ sql = row[0] or ""
|
|
|
|
|
+ # Already widened by an earlier run or by the fresh-install CREATE TABLE above.
|
|
|
|
|
+ if "ams_id >= 128" in sql:
|
|
|
|
|
+ return
|
|
|
|
|
+ # Pre-migration table without any CHECK constraint at all → leave alone;
|
|
|
|
|
+ # the app-level validation handles correctness and we don't risk a
|
|
|
|
|
+ # destructive table rebuild for a constraint that isn't blocking anyone.
|
|
|
|
|
+ if "ck_ams_id_range" not in sql and "ams_id <= 7" not in sql:
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ async with conn.begin_nested():
|
|
|
|
|
+ await conn.execute(text("DROP TABLE IF EXISTS spoolman_slot_assignments_v2"))
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ "CREATE TABLE spoolman_slot_assignments_v2 ("
|
|
|
|
|
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
|
|
|
|
+ "printer_id INTEGER NOT NULL REFERENCES printers(id) ON DELETE CASCADE, "
|
|
|
|
|
+ f"ams_id INTEGER NOT NULL CHECK ({_NEW_FORMULA}), "
|
|
|
|
|
+ "tray_id INTEGER NOT NULL CHECK (tray_id >= 0 AND tray_id <= 3), "
|
|
|
|
|
+ "spoolman_spool_id INTEGER NOT NULL, "
|
|
|
|
|
+ "assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
|
|
|
|
+ "CONSTRAINT uq_slot_assignment UNIQUE(printer_id, ams_id, tray_id)"
|
|
|
|
|
+ ")"
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ "INSERT INTO spoolman_slot_assignments_v2 "
|
|
|
|
|
+ "(id, printer_id, ams_id, tray_id, spoolman_spool_id, assigned_at) "
|
|
|
|
|
+ "SELECT id, printer_id, ams_id, tray_id, spoolman_spool_id, assigned_at "
|
|
|
|
|
+ "FROM spoolman_slot_assignments"
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ original = (await conn.execute(text("SELECT count(*) FROM spoolman_slot_assignments"))).scalar_one()
|
|
|
|
|
+ copied = (await conn.execute(text("SELECT count(*) FROM spoolman_slot_assignments_v2"))).scalar_one()
|
|
|
|
|
+ if copied != original:
|
|
|
|
|
+ raise RuntimeError(
|
|
|
|
|
+ f"spoolman_slot_assignments migration: row count mismatch after copy "
|
|
|
|
|
+ f"({original} in source, {copied} in copy)"
|
|
|
|
|
+ )
|
|
|
|
|
+ await conn.execute(text("DROP TABLE spoolman_slot_assignments"))
|
|
|
|
|
+ await conn.execute(text("ALTER TABLE spoolman_slot_assignments_v2 RENAME TO spoolman_slot_assignments"))
|
|
|
|
|
+ # The index sits on the renamed table; recreate it idempotently
|
|
|
|
|
+ # to handle older sqlite versions that don't auto-rename indexes.
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ text(
|
|
|
|
|
+ "CREATE INDEX IF NOT EXISTS ix_slot_assignment_spool "
|
|
|
|
|
+ "ON spoolman_slot_assignments (spoolman_spool_id)"
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ logger.error(
|
|
|
|
|
+ "spoolman_slot_assignments ck_ams_id_range widening (SQLite table recreation) FAILED: %s",
|
|
|
|
|
+ exc,
|
|
|
|
|
+ exc_info=True,
|
|
|
|
|
+ )
|
|
|
|
|
+ raise
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
async def run_migrations(conn):
|
|
async def run_migrations(conn):
|
|
|
"""Run all schema migrations and data backfills on startup.
|
|
"""Run all schema migrations and data backfills on startup.
|
|
|
|
|
|
|
@@ -2039,13 +2136,14 @@ async def run_migrations(conn):
|
|
|
# Migration: Create spoolman_slot_assignments table for local AMS-slot→Spoolman-spool mapping.
|
|
# Migration: Create spoolman_slot_assignments table for local AMS-slot→Spoolman-spool mapping.
|
|
|
# Replaces the pattern of writing spool.location in Spoolman (which polluted the
|
|
# Replaces the pattern of writing spool.location in Spoolman (which polluted the
|
|
|
# user-editable storage_location field in the UI).
|
|
# user-editable storage_location field in the UI).
|
|
|
|
|
+ # ck_ams_id_range formula was widened in #1274 to admit AMS-HT (ams_id 128-191).
|
|
|
await _safe_execute(
|
|
await _safe_execute(
|
|
|
conn,
|
|
conn,
|
|
|
"""
|
|
"""
|
|
|
CREATE TABLE IF NOT EXISTS spoolman_slot_assignments (
|
|
CREATE TABLE IF NOT EXISTS spoolman_slot_assignments (
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
printer_id INTEGER NOT NULL REFERENCES printers(id) ON DELETE CASCADE,
|
|
printer_id INTEGER NOT NULL REFERENCES printers(id) ON DELETE CASCADE,
|
|
|
- ams_id INTEGER NOT NULL CHECK ((ams_id >= 0 AND ams_id <= 7) OR ams_id = 255),
|
|
|
|
|
|
|
+ ams_id INTEGER NOT NULL CHECK ((ams_id >= 0 AND ams_id <= 7) OR (ams_id >= 128 AND ams_id <= 191) OR ams_id = 255),
|
|
|
tray_id INTEGER NOT NULL CHECK (tray_id >= 0 AND tray_id <= 3),
|
|
tray_id INTEGER NOT NULL CHECK (tray_id >= 0 AND tray_id <= 3),
|
|
|
spoolman_spool_id INTEGER NOT NULL,
|
|
spoolman_spool_id INTEGER NOT NULL,
|
|
|
assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
@@ -2057,7 +2155,7 @@ async def run_migrations(conn):
|
|
|
CREATE TABLE IF NOT EXISTS spoolman_slot_assignments (
|
|
CREATE TABLE IF NOT EXISTS spoolman_slot_assignments (
|
|
|
id SERIAL PRIMARY KEY,
|
|
id SERIAL PRIMARY KEY,
|
|
|
printer_id INTEGER NOT NULL REFERENCES printers(id) ON DELETE CASCADE,
|
|
printer_id INTEGER NOT NULL REFERENCES printers(id) ON DELETE CASCADE,
|
|
|
- ams_id INTEGER NOT NULL CHECK ((ams_id >= 0 AND ams_id <= 7) OR ams_id = 255),
|
|
|
|
|
|
|
+ ams_id INTEGER NOT NULL CHECK ((ams_id >= 0 AND ams_id <= 7) OR (ams_id >= 128 AND ams_id <= 191) OR ams_id = 255),
|
|
|
tray_id INTEGER NOT NULL CHECK (tray_id >= 0 AND tray_id <= 3),
|
|
tray_id INTEGER NOT NULL CHECK (tray_id >= 0 AND tray_id <= 3),
|
|
|
spoolman_spool_id INTEGER NOT NULL,
|
|
spoolman_spool_id INTEGER NOT NULL,
|
|
|
assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
assigned_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
@@ -2070,6 +2168,11 @@ async def run_migrations(conn):
|
|
|
"CREATE INDEX IF NOT EXISTS ix_slot_assignment_spool ON spoolman_slot_assignments (spoolman_spool_id)",
|
|
"CREATE INDEX IF NOT EXISTS ix_slot_assignment_spool ON spoolman_slot_assignments (spoolman_spool_id)",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ # Migration: widen ck_ams_id_range on spoolman_slot_assignments to allow
|
|
|
|
|
+ # AMS-HT ids (128-191). Existing installs created before #1274 carry the
|
|
|
|
|
+ # stale formula which rejects every AMS-HT slot link with a CHECK violation.
|
|
|
|
|
+ await _migrate_widen_spoolman_slot_ams_id_range(conn)
|
|
|
|
|
+
|
|
|
# Migration: Create spoolman_k_profile table for K-value calibration profiles linked to Spoolman spools.
|
|
# Migration: Create spoolman_k_profile table for K-value calibration profiles linked to Spoolman spools.
|
|
|
await _safe_execute(
|
|
await _safe_execute(
|
|
|
conn,
|
|
conn,
|