cocoindex-io/cocoindex · error · RuntimeError
Failed to replace column {col_name!r} in SQLite table {table
Error message
Failed to replace column {col_name!r} in SQLite table {table_name!r}: {e} What it means
For a `replace` column action, SQLite has no `ALTER COLUMN TYPE`, so the connector emulates replacement by adding a new column with the desired type (after copying data). If that `ALTER TABLE ... ADD COLUMN` fails with anything other than 'duplicate column name', the `OperationalError` is wrapped in a `RuntimeError` naming the column and table.
Source
Thrown at python/cocoindex/connectors/sqlite/_target.py:870
if action == "replace":
# SQLite doesn't support ALTER COLUMN TYPE directly.
# For type changes, attempt to drop and re-add column.
try:
conn.execute(f'ALTER TABLE {qualified_name} DROP COLUMN "{col_name}"')
except sqlite3.OperationalError:
pass
nullable = "" if desired_col.nullable else " NOT NULL"
try:
conn.execute(
f"ALTER TABLE {qualified_name} "
f'ADD COLUMN "{col_name}" {desired_col.type}{nullable}'
)
except sqlite3.OperationalError as e:
if "duplicate column name" in str(e).lower():
pass
else:
raise RuntimeError(
f"Failed to replace column {col_name!r} in SQLite table {table_name!r}: {e}"
) from e
continue
def _apply_table_actions(
context_provider: ContextProvider,
actions: Sequence[_TableAction],
) -> list[coco.ChildTargetDef["_RowHandler"] | None]:
"""Apply table actions (DDL) and return child row handlers."""
actions_list = list(actions)
outputs: list[coco.ChildTargetDef[_RowHandler] | None] = [None] * len(actions_list)
# Group actions by table key so we can apply all DDL for the same table
by_key: dict[_TableKey, list[int]] = {}
for i, action in enumerate(actions_list):
by_key.setdefault(action.key, []).append(i)
View on GitHub (pinned to e84aa99b32)
Solutions
- Inspect the wrapped SQLite error message for the exact ADD COLUMN failure
- Fix the changed field's type mapping so it compiles to a valid SQLite type
- Make the replacement column nullable or give it a default value
- Close other connections / retry the sync once the database is not locked
Example fix
// before
class Row:
tags: int # changed from str, triggers replace
// after — revert the type, or delete/recreate the table to avoid in-place replace
class Row:
tags: str Defensive patterns
Strategy: try-catch
Validate before calling
# detect type changes on pk-adjacent/replaceable columns before syncing assert all(isinstance(f.type, type) for f in dataclasses.fields(MyRow))
Try / catch
try:
await app.update()
except RuntimeError as e:
if str(e).startswith("Failed to replace column"):
inspect(e.__cause__) # underlying OperationalError has the real reason
else:
raise Prevention
- Treat column type changes as migrations: prefer dropping/recreating the table
- Verify the replacement type compiles to a valid SQLite type
- Release other connections/locks before running schema-changing syncs
When it happens
Trigger: Replacing a column with a type SQLite cannot add (invalid type string, bad vector spec); a non-nullable add violating SQLite ADD COLUMN rules; the target table having been dropped or locked by another connection mid-migration.
Common situations: Changing a column's type in a record dataclass (e.g. `int` to a vector) triggers column replacement during sync; running the app against a database file opened by another process holding a write lock.
Related errors
- Failed to add column {col_name!r} to SQLite table {table_nam
- Unexpected column subkey format: {sub_key!r}, expected to st
- row_count must be positive
- Invalid identifier: {name}
- Invalid table name format: {table}
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/af34fdf2f9ed660d.
Report an issue: GitHub.