Add migrate:msgspec for changes in schema that don't require version bump. Rename the other migrations to migrate:sql and migrate:v{N}.

This commit is contained in:
Leo Vasanko
2026-01-28 19:24:15 +00:00
parent 93fd496254
commit 5ec50be922
3 changed files with 27 additions and 11 deletions
+26 -5
View File
@@ -105,7 +105,7 @@ def create_change_record(
# Actions that are allowed to create a new database file
_BOOTSTRAP_ACTIONS = frozenset({"bootstrap", "migrate"})
_BOOTSTRAP_ACTIONS = frozenset({"bootstrap", "migrate:sql"})
async def flush_changes(
@@ -187,15 +187,35 @@ class JsonlStore:
# Update previous state to migrated data FIRST (to avoid transaction hardening reset)
self._previous_builtins = data_dict
# Persist migration by manually computing and queueing the diff
# Persist version migration by manually computing and queueing the diff
if migrated:
new_version = data_dict.get("v", 1)
diff = compute_diff(original_dict, data_dict)
if diff:
action = f"migrate:v{new_version}"
self._pending_changes.append(
create_change_record("migrate", diff, user=None)
create_change_record(action, diff, user=None)
)
_logger.info("Queued migration changes for persistence")
diff_json = json.dumps(diff, default=str)
print(f"{action}: {diff_json}", file=sys.stderr)
await self.flush()
# Update original_dict to reflect persisted state
original_dict = copy.deepcopy(data_dict)
# Normalize via msgspec round-trip (handles omit_defaults etc.)
# This ensures _previous_builtins matches what msgspec would produce
normalized_dict = msgspec.to_builtins(self.db)
diff = compute_diff(original_dict, normalized_dict)
if diff:
action = "migrate:msgspec"
self._pending_changes.append(
create_change_record(action, diff, user=None)
)
diff_json = json.dumps(diff, default=str)
print(f"{action}: {diff_json}", file=sys.stderr)
await self.flush()
# Update _previous_builtins to normalized state
self._previous_builtins = normalized_dict
else:
# No data loaded - _previous_builtins stays as empty dict
pass
@@ -252,7 +272,8 @@ class JsonlStore:
current_state = msgspec.to_builtins(self.db)
if current_state != self._previous_builtins:
# Allow bootstrap/migrate to create a new database from empty state
if action in _BOOTSTRAP_ACTIONS and not self._previous_builtins:
is_bootstrap = action in _BOOTSTRAP_ACTIONS or action.startswith("migrate:")
if is_bootstrap and not self._previous_builtins:
pass # Expected: creating database from scratch
else:
diff = compute_diff(self._previous_builtins, current_state)
-5
View File
@@ -5,10 +5,6 @@ Migrations are applied during database load based on the version field.
Each migration should be idempotent and only run when needed.
"""
import logging
_logger = logging.getLogger(__name__)
def apply_migrations(data_dict: dict) -> bool:
"""Apply any pending schema migrations to the database dictionary.
@@ -29,6 +25,5 @@ def apply_migrations(data_dict: dict) -> bool:
org_data.pop("created_at", None)
data_dict["v"] = 1
migrated = True
_logger.info("Applied schema migration: v0 -> v1 (removed org.created_at)")
return migrated
+1 -1
View File
@@ -248,7 +248,7 @@ async def migrate_from_sql(
print(f" Migrated {len(token_models)} reset tokens")
# Queue and flush all changes using the transaction mechanism
with db.transaction("migrate"):
with db.transaction("migrate:sql"):
pass # All data already added to _data, transaction commits on exit
await store.flush()