# iceberg.commit

> A module of iceberg's API.

A snapshot that adds *and* removes files, with the manifest maintenance that implies.

`iceberg.append` only ever adds: its manifest list is the new manifest plus
the parent's, carried by reference. Everything else — delete, overwrite,
replace, compaction — has to *rewrite* the manifests that mention a file it is
removing, and that is what this module does.

    prepare_commit(io, metadata, operation, files, extra)

`files` says what the snapshot adds (data files, delete files) and what it
removes (by path). The parent's manifest list is then walked once:

* a manifest that mentions none of the removed paths is **carried by
  reference** — same path, same counts, same `sequence_number`,
  `min_sequence_number` and `first_row_id`. Nothing is rewritten that does not
  have to be, which is what keeps a delete from touching the whole table;
* a manifest that mentions one is **rewritten**. Every live entry it keeps
  becomes `EXISTING` with its `sequence_number`, `file_sequence_number`,
  `snapshot_id` and (v3) `first_row_id` written out **explicitly** — sequence
  inheritance is ADDED-only, so an EXISTING entry that left them null would be
  silently re-dated. Every live entry it drops becomes `DELETED`, with the
  same numbers, so an expiry can still find the file. Entries that were
  already `DELETED` in the parent belong to an older snapshot and are dropped;
* a rewritten manifest with nothing live left is **dropped from the list**
  entirely rather than written as an empty file.

The rewritten manifest's list entry is credited to the new snapshot
(`added_snapshot_id`, `sequence_number`) while its `min_sequence_number` stays
whatever its oldest surviving file says — which is what makes the delete scope
rules keep working across a rewrite.

**Totals.** The `total-*` summary keys are carried forward from the parent
snapshot and adjusted by what this commit added and removed, because the
manifest list does not record file *sizes* and a scan of every manifest to
recompute them would defeat the point of carrying most of them by reference.

**Row lineage (v3).** Added data files get the table's `next-row-id` through
their manifest, exactly as an append does. A rewritten manifest keeps the
`first_row_id` it was assigned, and its entries carry theirs explicitly, so no
range moves and none overlap. Rows in a data file this commit *rewrites*
(copy-on-write) do get new ids — see the README's gaps table.

## Structs

### `FileChanges`

```mojo
struct FileChanges
```

What one snapshot adds and what it takes away.

Implements: `Copyable`, `Defaultable`, `Movable`

Fields:

- `added_data: List[DataFile]`
- `added_deletes: List[DataFile]`
- `removed: List[String]`: Paths of files this snapshot removes — data or delete, either way the manifest entry that names them becomes `DELETED`.

#### `__init__`

```mojo
def __init__(out self)
```

#### `is_empty`

```mojo
def is_empty(self) -> Bool
```

#### `removes`

```mojo
def removes(self, path: String) -> Bool
```

## Functions

### `prepare_commit`

```mojo
def prepare_commit(io: FileIO, metadata: TableMetadata, operation: String, var files: FileChanges, var extra_summary: Dict[String, String]) -> AppendResult
```

Write the manifests and manifest list for a commit, and build the new table metadata. Nothing is persisted: the caller commits it.

## Aliases

### `OP_DELETE`

```mojo
comptime OP_DELETE = String("delete")
```

### `OP_OVERWRITE`

```mojo
comptime OP_OVERWRITE = String("overwrite")
```

### `OP_REPLACE`

```mojo
comptime OP_REPLACE = String("replace")
```
