fix: properly handle documents with very old session/peer setups from previous versions of internal_metadata (#233)

This commit is contained in:
doria 2025-10-10 14:19:05 -04:00 committed by GitHub
parent 3af2cfb568
commit f218ac81ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 55 additions and 28 deletions

View File

@ -10,6 +10,7 @@ from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from nanoid import generate as generate_nanoid
from sqlalchemy import text
from migrations.utils import column_exists, constraint_exists, fk_exists, index_exists
@ -25,25 +26,47 @@ depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
"""Replace collections.name and documents.collection_name with observer and observed fields."""
schema = settings.DB.SCHEMA
inspector = sa.inspect(op.get_bind())
connection = op.get_bind()
# SESSION_NAME MIGRATION
# Replace NULL session_name values with empty strings and make column non-nullable
# Replace NULL session_name values with __global_observations__ and make column non-nullable
# This only applies to documents table
# Update documents table
connection.execute(
inspector = sa.inspect(connection)
# query documents table to get ALL workspace_names that have documents without a session_name
workspace_names = connection.execute(
text(
"""
UPDATE documents
SET session_name = ''
WHERE session_name IS NULL
f"""
SELECT DISTINCT workspace_name FROM {schema}.documents WHERE session_name IS NULL
"""
)
)
if column_exists("documents", "session_name", inspector):
op.alter_column("documents", "session_name", nullable=False, schema=schema)
).fetchall()
if workspace_names and column_exists("sessions", "name", inspector):
# Create __global_observations__ session for EACH workspace that needs it
for (workspace_name,) in workspace_names:
session_id = generate_nanoid()
connection.execute(
text(
f"""
INSERT INTO {schema}.sessions (id, name, workspace_name) VALUES (:session_id, '__global_observations__', :workspace_name) ON CONFLICT DO NOTHING
"""
),
{"session_id": session_id, "workspace_name": workspace_name},
)
# Update all documents with NULL session_name
connection.execute(
text(
f"""
UPDATE {schema}.documents
SET session_name = '__global_observations__'
WHERE session_name IS NULL
"""
),
)
op.alter_column("documents", "session_name", nullable=False, schema=schema)
# COLLECTIONS TABLE
# Step 1: Add new observer and observed columns to collections
@ -64,17 +87,21 @@ def upgrade() -> None:
# Step 2: Populate collections observer and observed from existing name field
# The logic is:
# - observer = peer_name (the exact peer ID)
# - If name is "global_representation", observed = peer_name
# - Otherwise, observed = name with the "observer_" prefix stripped
# - If name is "global_representation", observed = peer_name (self-observation)
# - If name starts with peer_name + "_", extract the observed part (pattern: observer_observed)
# - If name ends with "_" + peer_name, extract the first part (pattern: observed_observer)
# - Otherwise (legacy edge cases), observed = name itself
connection.execute(
text(
"""
UPDATE collections
f"""
UPDATE {schema}.collections
SET
observer = peer_name,
observed = CASE
WHEN name = 'global_representation' THEN peer_name
ELSE substring(name from length(peer_name) + 2)
WHEN name LIKE peer_name || '_%' THEN substring(name from length(peer_name) + 2)
WHEN name LIKE '%_' || peer_name THEN substring(name from 1 for length(name) - length(peer_name) - 1)
ELSE name
END
WHERE observer IS NULL OR observed IS NULL
"""
@ -108,18 +135,18 @@ def upgrade() -> None:
while True:
result = connection.execute(
text(
"""
f"""
WITH batch AS (
SELECT d.ctid
FROM documents d
FROM {schema}.documents d
WHERE d.observer IS NULL OR d.observed IS NULL
LIMIT :batch_size
)
UPDATE documents d
UPDATE {schema}.documents d
SET
observer = c.observer,
observed = c.observed
FROM collections c, batch
FROM {schema}.collections c, batch
WHERE d.ctid = batch.ctid
AND d.collection_name = c.name
AND d.peer_name = c.peer_name
@ -329,8 +356,8 @@ def downgrade() -> None:
# Step 2: Populate collections name from observer and observed
connection.execute(
text(
"""
UPDATE collections
f"""
UPDATE {schema}.collections
SET name = CASE
WHEN observer = observed THEN 'global_representation'
ELSE observer || '_' || observed
@ -354,8 +381,8 @@ def downgrade() -> None:
# Populate peer_name with observer value
connection.execute(
text(
"""
UPDATE collections
f"""
UPDATE {schema}.collections
SET peer_name = observer
WHERE peer_name IS NULL
"""
@ -391,8 +418,8 @@ def downgrade() -> None:
# Step 5: Populate documents collection_name from observer and observed
connection.execute(
text(
"""
UPDATE documents
f"""
UPDATE {schema}.documents
SET collection_name = CASE
WHEN observer = observed THEN 'global_representation'
ELSE observer || '_' || observed
@ -416,8 +443,8 @@ def downgrade() -> None:
# Populate peer_name with observed value
connection.execute(
text(
"""
UPDATE documents
f"""
UPDATE {schema}.documents
SET peer_name = observed
WHERE peer_name IS NULL
"""