iceberg · API

iceberg.update_schema

Schema evolution: add columns, rename them, make them optional, widen them.

var u = UpdateSchema() u.add_column("note", "string") u.add_nested_column("addr", "zip", "int") u.rename_column("amount", "total") u.make_column_optional("region") u.update_column_type("qty", "long") _ = table.update_schema(u) # a filesystem table _ = rest.update_schema("db", "orders", u) # or through a catalog

The shape is Java's and PyIceberg's UpdateSchema: a list of changes that is applied to the table's current schema when the commit is prepared, so a commit that loses a race re-applies the same changes to whatever schema won. What it produces is what the spec asks for:

  • every new field — the column itself, then the fields, elements, keys and values inside its type — gets a fresh id above the table's last-column-id, which moves up to the highest id handed out;
  • the result is a new schema with the next free schema-id, appended to schemas, and current-schema-id points at it. A result equal to a schema the table already has reuses that schema's id instead, as Java does; a result equal to the current schema commits nothing at all;
  • through a REST catalog the request carries assert-table-uuid, assert-current-schema-id and assert-last-assigned-field-id, and the updates are add-schema then set-current-schema with -1 ("the schema just added").

Nothing here rewrites a data file. A file written before a column existed has no column with that id, and the reader's projection by field id already produces null for it; a widened column is read at the file's width and cast.

Only compatible changes. An added column is optional — rows already in the table have no value for it, and there is no initial-default support on the write side. Type changes are the spec's promotions only: int→long, float→double, and decimal(P, S)→decimal(P', S) with P' > P. Columns are not deleted or moved. union_by_name refuses any type difference rather than promoting, so a caller handing it a schema always learns about a type change instead of having it applied.

Structs

UpdateSchema

struct UpdateSchema

Schema changes, recorded now and applied at commit time.

Implements Copyable, Defaultable, Movable

Fields
ops: List[_SchemaOp]

__init__

def __init__(out self)

is_empty

def is_empty(self) -> Bool

add_column

def add_column(mut self, name: String, type: String, doc: String = "")

Add an optional top-level column.

type is the Iceberg JSON type: a primitive's spelling (long, decimal(10, 2), quoted or not) or a struct/list/map object. Field ids inside a nested type are placeholders; they are replaced by fresh ones.

add_nested_column

def add_nested_column(mut self, parent: String, name: String, type: String, doc: String = "")

Add an optional field to the struct at parent (addr, a.b).

A parent that is a list adds to its element struct, and a map to its value struct, the way Java resolves a parent.

rename_column

def rename_column(mut self, path: String, new_name: String)

Rename a field. Its id, and so every data file, is unaffected.

make_column_optional

def make_column_optional(mut self, path: String)

Let a required field (or a list element, or a map value) be null.

update_column_type

def update_column_type(mut self, path: String, new_type: String)

Widen a primitive the way the spec allows: int→long, float→double, decimal(P, S)→decimal(P', S) with P' > P.

union_by_name

def union_by_name(mut self, schema: Schema)

Make the table's schema hold every field of schema, matched by name: a missing field is added (it must be optional), a field schema marks optional becomes optional, a changed doc is taken, and fields schema does not mention are kept. Structs are merged at any depth, including a list's element and a map's value. A field whose type differs raises: no promotion, no replacement.

schema's own field ids are ignored — only names count.

apply

def apply(self, base: Schema, last_column_id: Int) -> Tuple[Schema, Int]

The changes applied to base: the new schema (still carrying base's id) and the new last-column-id.

SchemaCommit

struct SchemaCommit

What committing an UpdateSchema would do to one table metadata.

Implements Copyable, Movable

Fields
metadata: TableMetadata: The table metadata after the change. Not persisted.
schema: Schema: The new current schema.
added: Bool: A new schema was appended to schemas (else an existing one is current again).
changed: Bool: False when the changes leave the current schema as it was; there is then nothing to commit.

Functions

prepare_schema_update

def prepare_schema_update(metadata: TableMetadata, update: UpdateSchema) -> SchemaCommit

Apply update to metadata's current schema and build the metadata a commit would write. Nothing is persisted: the caller commits it.

schema_update_body

def schema_update_body(namespace: String, table: String, current: TableMetadata, commit: SchemaCommit) -> String

The REST CommitTableRequest for a schema change.

The requirements are Java's: the table is the one we read, its current schema is the one the changes were applied to, and no column id has been handed out since — so a concurrent schema change is a 409, not two columns with the same id.