v0.1.28 - Version bump and editor/throughput updates
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -66,9 +66,15 @@ class MigrationReport:
|
||||
created_tables: tuple[str, ...] = ()
|
||||
added_columns: tuple[str, ...] = ()
|
||||
synced_tenant_rows: dict[str, int] = field(default_factory=dict)
|
||||
resynced_sequences: tuple[str, ...] = ()
|
||||
|
||||
def has_changes(self) -> bool:
|
||||
return bool(self.created_tables or self.added_columns or self.synced_tenant_rows)
|
||||
return bool(
|
||||
self.created_tables
|
||||
or self.added_columns
|
||||
or self.synced_tenant_rows
|
||||
or self.resynced_sequences
|
||||
)
|
||||
|
||||
def summary(self) -> str:
|
||||
parts: list[str] = []
|
||||
@@ -79,6 +85,8 @@ class MigrationReport:
|
||||
if self.synced_tenant_rows:
|
||||
counts = ", ".join(f"{table}={count}" for table, count in sorted(self.synced_tenant_rows.items()))
|
||||
parts.append(f"synced tenant rows: {counts}")
|
||||
if self.resynced_sequences:
|
||||
parts.append(f"resynced sequences: {', '.join(self.resynced_sequences)}")
|
||||
return "; ".join(parts) if parts else "schema already up to date"
|
||||
|
||||
|
||||
@@ -435,7 +443,58 @@ def sync_product_visibility(engine: Engine) -> int:
|
||||
return result.rowcount or 0
|
||||
|
||||
|
||||
def resync_identity_sequences(engine: Engine) -> tuple[str, ...]:
|
||||
"""Realign Postgres identity sequences with each table's current MAX(id).
|
||||
|
||||
After a bulk import that carries original primary keys across (the SQLite →
|
||||
Postgres migration inserts rows with their existing ids), every table's
|
||||
sequence still points at its starting value. The next INSERT then reuses an
|
||||
id that already exists and fails with ``duplicate key value violates unique
|
||||
constraint`` — which is why creating a new mix/ingredient/product saved fine
|
||||
on SQLite but not on production Postgres.
|
||||
|
||||
This advances each ``id`` sequence to MAX(id) so the next INSERT continues
|
||||
cleanly. It is a no-op on SQLite and idempotent on Postgres, so it is safe to
|
||||
run on every startup. A per-table failure is skipped rather than aborting the
|
||||
whole boot.
|
||||
"""
|
||||
if engine.dialect.name != "postgresql":
|
||||
return ()
|
||||
|
||||
resynced: list[str] = []
|
||||
inspector = inspect(engine)
|
||||
with engine.begin() as connection:
|
||||
for table_name in inspector.get_table_names():
|
||||
if not any(column["name"] == "id" for column in inspector.get_columns(table_name)):
|
||||
continue
|
||||
try:
|
||||
sequence = connection.execute(
|
||||
text("SELECT pg_get_serial_sequence(:table, 'id')"),
|
||||
{"table": table_name},
|
||||
).scalar()
|
||||
if not sequence:
|
||||
continue
|
||||
max_id = connection.execute(text(f'SELECT MAX(id) FROM "{table_name}"')).scalar()
|
||||
if max_id is None:
|
||||
continue
|
||||
connection.execute(
|
||||
text("SELECT setval(:sequence, :value, true)"),
|
||||
{"sequence": sequence, "value": int(max_id)},
|
||||
)
|
||||
resynced.append(table_name)
|
||||
except Exception:
|
||||
# A single problematic table must not block startup; the others
|
||||
# still get realigned.
|
||||
continue
|
||||
return tuple(resynced)
|
||||
|
||||
|
||||
def bootstrap_schema(engine: Engine, metadata: MetaData) -> MigrationReport:
|
||||
created_tables = ensure_metadata_tables(engine, metadata)
|
||||
added_columns = ensure_tenant_columns(engine) + ensure_legacy_columns(engine)
|
||||
return MigrationReport(created_tables=created_tables, added_columns=added_columns)
|
||||
resynced_sequences = resync_identity_sequences(engine)
|
||||
return MigrationReport(
|
||||
created_tables=created_tables,
|
||||
added_columns=added_columns,
|
||||
resynced_sequences=resynced_sequences,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user