Repository navigation
INSERT fails after a column is added to the table, until the provider is rebuilt #56
Description
Describe the bug
IcebergCatalogTableProvider caches the table's Arrow schema when it is built (catalog_table_provider.rs#L81-L84). IcebergCatalogProvider builds one provider per table when it is constructed and keeps it (schema.rs#L64-L94). insert_into plans its input against the cached schema, but IcebergWriteExec builds the Parquet writer from the table's current schema (write/exec.rs#L244-L250).
Once a column has been added to the table, by another engine or another session, the input batches lack that column and the Parquet writer fails on it:
Execution error: DataInvalid => Field id 3 not found in struct array
Every INSERT through that provider fails this way, with or without a column list, including after other sessions have written with the new schema. Scans through the same provider keep working and return only the cached columns. Writing works again only once the provider is rebuilt, which for IcebergCatalogProvider means registering the catalog again.
To Reproduce
Tested with an unpartitioned table in a MemoryCatalog:
- Create
ns.t(id INT NOT NULL, name STRING NOT NULL), register anIcebergCatalogProviderascin a session, and runINSERT INTO c.ns.t VALUES (1, 'a'). - Outside that session, add an optional column, for example with
Transaction::update_schema().add_column(AddColumn::optional("email", Type::Primitive(PrimitiveType::String)))orALTER TABLE t ADD COLUMN email STRINGfrom Spark. - In the same session, run
INSERT INTO c.ns.t VALUES (2, 'b')orINSERT INTO c.ns.t (id, name) VALUES (2, 'b'). Both fail with the error above.
In a session that registers the catalog after step 2, INSERT INTO c.ns.t (id, name) VALUES (2, 'b') succeeds and email reads back as NULL.
Expected behavior
The INSERT succeeds and writes NULL for email, as it does through a provider built after the change. At the least, it should fail with an error saying that the table's schema changed after the provider was built.
Additional context
- This is a concrete case of CatalogProvider Limitations: Query Scoped Resolution #24 §2: the provider plans with its cached schema but writes with the table's current metadata. It persists because of CatalogProvider Limitations: Query Scoped Resolution #24 §1:
IcebergCatalogProvidernever rebuilds its providers. - feat: add IcebergCatalogTableProvider::new_with_schema #48 adds a constructor so that Ballista can rebuild a provider on the scheduler with the schema the client planned against. Ballista runs DML through that rebuilt provider, so it fails the same way.
- Possible fixes:
- have
IcebergWriteExecfill the columns the input lacks with NULL, or with their write default - have
insert_intocompare the cached schema with the table's current schema and return a clear error
- have
Activity