cocoindex-io/cocoindex · error · ValueError
Unsupported LanceDB column action for in-place evolution
Error message
Unsupported LanceDB column action for in-place evolution: {action!r} What it means
During LanceDB target schema reconciliation, _apply_column_actions only supports additive in-place schema evolution ('insert'/'upsert' actions, which add new nullable columns backfilled with null). Any other DiffAction (e.g. delete, update) for a column subkey means the desired change cannot be applied in place, so the library raises rather than corrupting the table. Dropping or altering columns requires recreating the table instead.
Solutions
- Drop the existing LanceDB table (or point the target at a new table name) so it is recreated from the current declared schema
- Restore the removed/changed column in the declared schema so the diff is only additive
- Do a two-phase migration: add the new column (supported additive op), backfill, then manually remove the old column via LanceDB tooling
- Inspect the diff actions by comparing schema.columns with the live table schema to identify the offending column
Example fix
// before
class Row(NamedTuple):
id: str
text: str
legacy_col: str # removed later -> 'delete' action -> error
// after
class Row(NamedTuple):
id: str
text: str
# and recreate the table (or use a fresh table name) since column drops are not in-place Defensive patterns
Strategy: validation
Validate before calling
live_cols = set((await table.schema()).names)
declared = set(schema.columns)
if not (declared >= live_cols and live_cols <= declared | set()):
# non-additive diff possible: drops or type changes
if live_cols - declared:
raise RuntimeError(f"columns removed; recreate table: {live_cols - declared}") Type guard
def is_additive(actions: dict[str, str]) -> bool:
return all(a in ("insert", "upsert") for a in actions.values()) Try / catch
try:
await app.update()
except ValueError as e:
if "Unsupported LanceDB column action" in str(e):
logger.warning("non-additive schema change; recreating table: %s", e)
await drop_lancedb_table(table_name)
await app.update()
else:
raise Prevention
- Treat declared column removals/renames as breaking changes: bump the table name or migrate explicitly
- Prefer adding new columns and deprecating old ones over in-place type changes
- Run schema diff checks in CI before deploying pipeline changes
- Keep a version suffix in table names for destructive schema migrations
When it happens
Trigger: A diff between the declared table schema and the existing LanceDB table produces a non-additive column action (delete/update/replace) in column_actions, e.g. a column was removed or its type changed in the declared TableSpec while the table already exists.
Common situations: Removing or renaming a field in a row dataclass after a table already has data; changing a column's type; changing a column to/from primary key in ways the additive path skips; running an updated pipeline version against an old LanceDB table.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Column ' ' not found in table schema
- Invalid vector dimension
- lancedb and pyarrow are required to use the LanceDB…
- Partial reads are not supported for Google Drive files.
- Primary key column ' ' not found in columns
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/0b5a022ca1199c8e.
Report an issue: GitHub.
Appendix: source
Thrown at python/cocoindex/connectors/lancedb/_target.py:1160
continue
if col_name in existing_cols:
continue
desired_col = schema.columns.get(col_name)
if desired_col is None:
continue
if action in ("insert", "upsert"):
fields_to_add.append(
# Existing rows are backfilled with null, so additive schema
# evolution must materialize the new column as nullable.
pa.field(col_name, desired_col.type, nullable=True)
)
if desired_col.nullable:
null_backfilled_columns.add(col_name)
continue
raise ValueError(
f"Unsupported LanceDB column action for in-place evolution: {action!r}"
)
if fields_to_add:
await table.add_columns(fields_to_add)
return frozenset(null_backfilled_columns)
return frozenset()
def reconcile(
self,
key: coco.StableKey,
desired_state: _TableSpec | coco.NonExistenceType,
prev_possible_records: Collection[_TableTrackingRecord],
prev_may_be_missing: bool,
/,
) -> (
coco.TargetReconcileOutput[_TableAction, _TableTrackingRecord, _RowHandler]
| NoneView on GitHub (pinned to e84aa99b32)