Files
lancedb/docs/web/management/table.mdx
T

1529 lines
55 KiB
Plaintext

---
title: "Tables"
sidebarTitle: "Table"
description: "The object most operations act on: creating, opening, renaming, cloning and dropping one."
icon: table
keywords: ["table", "create_table", "open_table", "clone", "rename", "drop"]
availability:
oss: available
enterprise: available
summary: >-
Tables are created, evolved, versioned and dropped in the embedded library
and in LanceDB Enterprise. Renaming, cloning, dropping every table and pruning old
versions differ between the two.
---
import {
PySchemaAddSetup,
TsSchemaAddSetup as TsSchemaAddSetup,
RsSchemaAddSetup as RsSchemaAddSetup,
PyAddColumnsDefaultValues as AddColumnsDefaultValues,
TsAddColumnsDefaultValues as TsAddColumnsDefaultValues,
RsAddColumnsDefaultValues as RsAddColumnsDefaultValues,
PyAddColumnsNullable as AddColumnsNullable,
TsAddColumnsNullable as TsAddColumnsNullable,
RsAddColumnsNullable as RsAddColumnsNullable,
PySchemaAlterSetup as SchemaAlterSetup,
TsSchemaAlterSetup as TsSchemaAlterSetup,
RsSchemaAlterSetup as RsSchemaAlterSetup,
PyAlterColumnsRename as AlterColumnsRename,
TsAlterColumnsRename as TsAlterColumnsRename,
RsAlterColumnsRename as RsAlterColumnsRename,
PyAlterColumnsDataType as AlterColumnsDataType,
TsAlterColumnsDataType as TsAlterColumnsDataType,
RsAlterColumnsDataType as RsAlterColumnsDataType,
PyAlterColumnsNullable as AlterColumnsNullable,
TsAlterColumnsNullable as TsAlterColumnsNullable,
RsAlterColumnsNullable as RsAlterColumnsNullable,
PyAlterColumnsMultiple as AlterColumnsMultiple,
TsAlterColumnsMultiple as TsAlterColumnsMultiple,
PyAlterColumnsWithExpression as AlterColumnsWithExpression,
TsAlterColumnsWithExpression as TsAlterColumnsWithExpression,
RsAlterColumnsMultiple as RsAlterColumnsMultiple,
RsAlterColumnsWithExpression as RsAlterColumnsWithExpression,
PySchemaDropSetup as SchemaDropSetup,
TsSchemaDropSetup as TsSchemaDropSetup,
RsSchemaDropSetup as RsSchemaDropSetup,
PyDropColumnsSingle as DropColumnsSingle,
TsDropColumnsSingle as TsDropColumnsSingle,
RsDropColumnsSingle as RsDropColumnsSingle,
PyDropColumnsMultiple as DropColumnsMultiple,
TsDropColumnsMultiple as TsDropColumnsMultiple,
RsDropColumnsMultiple as RsDropColumnsMultiple,
PyAlterVectorColumn as AlterVectorColumn,
TsAlterVectorColumn as TsAlterVectorColumn,
RsAlterVectorColumn as RsAlterVectorColumn,
PySchemaFieldMetadataMerge as SchemaFieldMetadataMerge,
TsSchemaFieldMetadataMerge as TsSchemaFieldMetadataMerge,
RsSchemaFieldMetadataMerge as RsSchemaFieldMetadataMerge,
PySchemaFieldMetadataReplace as SchemaFieldMetadataReplace,
TsSchemaFieldMetadataReplace as TsSchemaFieldMetadataReplace,
RsSchemaFieldMetadataReplace as RsSchemaFieldMetadataReplace,
PyVersioningBasicSetup as VersioningBasicSetup,
TsVersioningBasicSetup as TsVersioningBasicSetup,
RsVersioningBasicSetup as RsVersioningBasicSetup,
PyVersioningCheckInitialVersion as VersioningCheckInitialVersion,
TsVersioningCheckInitialVersion as TsVersioningCheckInitialVersion,
RsVersioningCheckInitialVersion as RsVersioningCheckInitialVersion,
PyVersioningUpdateData as VersioningUpdateData,
TsVersioningUpdateData as TsVersioningUpdateData,
RsVersioningUpdateData as RsVersioningUpdateData,
PyVersioningAddData as VersioningAddData,
TsVersioningAddData as TsVersioningAddData,
RsVersioningAddData as RsVersioningAddData,
PyVersioningCheckVersionsAfterMod as VersioningCheckVersionsAfterMod,
TsVersioningCheckVersionsAfterMod as TsVersioningCheckVersionsAfterMod,
RsVersioningCheckVersionsAfterMod as RsVersioningCheckVersionsAfterMod,
PyVersioningListAllVersions as VersioningListAllVersions,
TsVersioningListAllVersions as TsVersioningListAllVersions,
RsVersioningListAllVersions as RsVersioningListAllVersions,
PyVersioningRollback as VersioningRollback,
TsVersioningRollback as TsVersioningRollback,
RsVersioningRollback as RsVersioningRollback,
PyVersioningCheckoutLatest as VersioningCheckoutLatest,
TsVersioningCheckoutLatest as TsVersioningCheckoutLatest,
RsVersioningCheckoutLatest as RsVersioningCheckoutLatest,
PyVersioningDeleteData as VersioningDeleteData,
TsVersioningDeleteData as TsVersioningDeleteData,
RsVersioningDeleteData as RsVersioningDeleteData,
PyVersioningTags as VersioningTags,
TsVersioningTags as TsVersioningTags,
RsVersioningTags as RsVersioningTags,
RsVersioningMakeQuotesReader as RsVersioningMakeQuotesReader,
} from '/snippets/tables.mdx';
import {
PyMultimodalImports as MultimodalImports,
TsMultimodalImports as TsMultimodalImports,
RsMultimodalImports as RsMultimodalImports,
PyCreateDummyData as CreateDummyData,
TsCreateDummyData as TsCreateDummyData,
RsCreateDummyData as RsCreateDummyData,
PyDefineSchema as DefineSchema,
TsDefineSchema as TsDefineSchema,
RsDefineSchema as RsDefineSchema,
PyIngestData as IngestData,
TsIngestData as TsIngestData,
RsIngestData as RsIngestData,
PySearchData as SearchData,
TsSearchData as TsSearchData,
RsSearchData as RsSearchData,
PyProcessResults as ProcessResults,
TsProcessResults as TsProcessResults,
RsProcessResults as RsProcessResults,
PyBlobApiSetup as BlobApiSetup,
TsBlobApiSetup as TsBlobApiSetup,
RsBlobApiImports as RsBlobApiImports,
RsBlobApiSetup as RsBlobApiSetup,
PyBlobApiSchema as BlobApiSchema,
TsBlobApiSchema as TsBlobApiSchema,
RsBlobApiSchema as RsBlobApiSchema,
PyBlobApiIngest as BlobApiIngest,
PyBlobApiBytes as BlobApiBytes,
TsBlobApiIngest as TsBlobApiIngest,
RsBlobApiIngest as RsBlobApiIngest,
PyBlobApiFiles as BlobApiFiles,
TsBlobApiFiles as TsBlobApiFiles,
RsBlobApiFiles as RsBlobApiFiles,
PyBlobApiFetch as BlobApiFetch,
TsBlobApiFetch as TsBlobApiFetch,
RsBlobApiFetch as RsBlobApiFetch,
PyBlobApiRanges as BlobApiRanges,
TsBlobApiRanges as TsBlobApiRanges,
RsBlobApiRanges as RsBlobApiRanges,
TsBlobApiBytesMode as TsBlobApiBytesMode,
PyLegacyBlobApiSchema as LegacyBlobApiSchema,
TsLegacyBlobApiSchema as TsLegacyBlobApiSchema,
RsLegacyBlobApiSchema as RsLegacyBlobApiSchema,
PyLegacyBlobApiIngest as LegacyBlobApiIngest,
TsLegacyBlobApiIngest as TsLegacyBlobApiIngest,
RsLegacyBlobApiIngest as RsLegacyBlobApiIngest,
PyLegacyBlobApiToPandas as LegacyBlobApiToPandas,
PyLegacyQueryToPandasKwargs as LegacyQueryToPandasKwargs,
} from '/snippets/multimodal.mdx';
import {
PyBlobApiToPandas as BlobApiToPandas,
PyQueryToPandasKwargs as QueryToPandasKwargs,
} from '/snippets/blob_pylance.mdx';
A table stores data in rows and columns and keeps a history of committed
versions. Queries read table data, writes update it, and indexes accelerate
searches over its columns. Branches let you make changes separately from the
main table history.
This page covers table management, data types, schema changes and version
history. For loading records, see [Ingestion](/ingestion). For changes to
columns, see [Schema evolution](/management/table#schema).
<Note>
In LanceDB Enterprise, tables are also created and dropped in SQL with [`CREATE TABLE` and `DROP TABLE`](/sql/ddl/table).
</Note>
## Table structure and metadata {#carries}
| | |
| --- | --- |
| **Columns** | including vectors, blobs, and [computed](/feature-engineering/add-computed-column) columns |
| **Versions** | every write commits one, so reads can [travel back](/management/table#versioning) |
| **Indexes** | vector, full-text and scalar, built over its columns |
| **Branches** | isolated lines of change you can [merge or discard](/tables/branching) |
## Create a table {#create}
<CodeGroup>
```python Python icon="python"
tbl = db.create_table("documents", data)
tbl = db.create_table("documents", schema=schema) # empty, typed
```
```typescript TypeScript icon="square-js"
const tbl = await db.createTable("documents", data);
const tbl = await db.createEmptyTable("documents", schema);
```
```sql SQL
CREATE TABLE documents (id BIGINT, title VARCHAR)
```
</CodeGroup>
`data` can be a list of objects, an Arrow table or reader, a pandas or polars
frame, or Pydantic models. [Ingesting data](/ingestion) covers each, plus
`exist_ok` and `mode="overwrite"` for re-runnable scripts.
<Warning>
An explicit schema is where nullability is decided. In the embedded library,
creation is also the only point at which a table can be given **stable row
ids**; in LanceDB Enterprise, whether a new table gets them is the deployment's
setting. See [Limits](/basics/limits#schema).
</Warning>
## Open and list tables {#open}
<CodeGroup>
```python Python icon="python"
tbl = db.open_table("documents")
db.table_names()
```
```typescript TypeScript icon="square-js"
const tbl = await db.openTable("documents");
await db.tableNames();
```
</CodeGroup>
Each accepts `namespace_path` to act inside a [namespace](/management/namespace)
rather than at the database root.
To find out whether a table exists, open it and handle the error, or look for
its name in a listing. A missing table raises `ValueError` on a local directory
or a deployment, and `lance_namespace.TableNotFoundError` on a connection from
`connect_namespace`. A listing returns one page of names at a time, so check
every page. Python's `table_exists` is no substitute: a local directory or a
deployment raises `NotImplementedError` for it, and on a connection from
`connect_namespace("dir", ...)` or `connect_namespace("rest", ...)` it fails with
an `AttributeError`. TypeScript has no `tableExists`.
## Rename and clone tables {#rename-clone}
```python
db.rename_table("documents", "docs")
db.clone_table(
"docs_snapshot",
source_uri="s3://bucket/db/documents.lance",
source_version=42, # or source_tag="release-1"
is_shallow=True,
)
```
Renaming is not supported on a local directory connection. In LanceDB Enterprise,
whether a table can be renamed or cloned at all depends on how the deployment
stores its catalog.
A **shallow** clone (the default) references the source's data files rather
than copying them, so it is fast and cheap regardless of table size. It is the
practical way to branch an experiment off a known version, or to pin a training
set to the exact version a model saw.
<Warning>
A shallow clone depends on the source's files continuing to exist. If the source
is [compacted](/maintenance/compaction) past the retention window, the version
the clone points at can be pruned. Local OSS connections support shallow clones
only; `is_shallow=False` is not implemented. If the copy must outlive the
source's history, copy its data into an independent table instead. A deployment
that supports cloning also makes shallow clones only, and one whose catalog
does not support it refuses every clone.
</Warning>
<Note>
**Enterprise job type:** on an Enterprise cluster that drops tables in the background,
the teardown runs as a job: `drop_table`, which fences the table's directory
with a marker and deletes its content, or `drop_object`, depending on how the
deployment stores its catalog. See
[Background jobs](/maintenance/background-jobs#teardown).
</Note>
## Drop a table {#drop}
<CodeGroup>
```python Python icon="python"
db.drop_table("documents")
job = db.drop_table_async("documents")
db.drop_all_tables()
```
```typescript TypeScript icon="square-js"
await db.dropTable("documents");
const job = await db.dropTableAsync("documents");
await db.dropAllTables();
```
```sql SQL
DROP TABLE documents
```
</CodeGroup>
<Warning>
Dropping removes the table and **every version of it**.
[Versioning](/management/table#versioning) does not survive a drop, and neither
`drop_all_tables` nor `drop_database` asks for confirmation. Both work only on
a local database; on an Enterprise connection the SDK raises instead.
</Warning>
On a large table the drop can take a while, so `drop_table_async` returns a
[job](/basics/jobs) rather than blocking when the deployment drops tables in the
background. In the embedded library, and on an Enterprise cluster that drops inline, the
returned job has already finished.
## Table management APIs {#surface}
| | Python | TypeScript | SQL |
| --- | --- | --- | --- |
| create | `create_table` | `createTable` | `CREATE TABLE` |
| create empty | `create_table(schema=)` | `createEmptyTable` | `CREATE TABLE` |
| open | `open_table` | `openTable` | -- |
| list | `table_names`, `list_tables` | `tableNames`, `listTables` | -- |
| exists | `table_exists` | -- | -- |
| rename | `rename_table` | `renameTable` | -- |
| clone | `clone_table` | `cloneTable` | -- |
| drop | `drop_table`, `drop_table_async` | `dropTable`, `dropTableAsync` | `DROP TABLE` |
| drop all | `drop_all_tables` | `dropAllTables` | -- |
## Data types {#data-types}
A table's schema is an **Arrow schema**: there is no separate LanceDB type
system mapped onto storage, so a column is the same object to the query engine
as it is to the format.
| Category | Types |
| --- | --- |
| Numeric | `int8`-`int64` and unsigned, `float16`, `float32`, `float64` |
| String and binary | `utf8`, `large_utf8`, `binary`, `large_binary`, `fixed_size_binary(n)` |
| Date and time | `date32`, `date64`, `time32`, `time64`, `timestamp`, `duration` |
| Nested | `struct`, `list`, `large_list`, `fixed_size_list`, `map` |
| Lance extensions | vectors, [JSON](/sql/data-types#json), [blobs](#multimodal), bfloat16 |
A vector column is a `fixed_size_list<float32, dim>`, an ordinary Arrow type,
which is why it is selectable and joinable but why arithmetic over it is list
arithmetic rather than scalar.
See [SQL data types](/sql/data-types) for the full reference, including the
extension types and pseudo-columns.
## Multimodal data {#multimodal}
Store images, audio, video, and documents alongside structured columns and
embeddings in the same versioned table.
Use `blob` columns for these payloads. Small values stay inline in the data
file, up to 64 KiB by default, and larger values are stored outside it.
### Blob columns {#multimodal-blob-v2}
<Warning>
Blob V2 is in early access.
</Warning>
A `blob` column keeps metadata separate from the payload bytes, so queries can
filter rows without loading the blobs.
Most queries return descriptors, which contain metadata about the blob instead
of its bytes. You can
[read the bytes in the query results](#multimodal-blob-query-results) or
[fetch them by row ID](#multimodal-read-blobs).
In Python, plain `to_pandas()` scans can also return `BlobFile` handles when
`pylance` is installed.
When you write raw bytes, Lance chooses a storage layout based on the payload
size. A descriptor's `kind` tells you which one was used:
| Storage mode | `kind` | Default placement | Storage behavior |
| --- | --- | --- | --- |
| `Inline` | `0` | Up to 64 KiB | Stored in the main data file. |
| `Packed` | `1` | Above 64 KiB, up to 4 MiB | Packed with other blobs into a shared sidecar file, up to 1 GiB per file by default. |
| `Dedicated` | `2` | Above 4 MiB | Stored in its own raw sidecar file. |
| `Managed` | `4` or `5` | Out-of-line values when managed storage is enabled | Stored in a Lance-owned object independently of the data file. |
Managed storage must be enabled separately. See
[Managed blobs](#managed-blobs) for details. `kind=5` identifies a Managed
object using an explicitly registered base.
Lance uses the default thresholds unless you customize them. Explicitly
configured thresholds are stored in the table schema and preserved on append.
#### Setup {#multimodal-blob-v2-setup}
Import the SDK and open a local database. The examples in this section use `db`.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsMultimodalImports}{"\n"}{TsBlobApiSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiImports}{"\n"}{RsBlobApiSetup}
</CodeBlock>
</CodeGroup>
#### Define a blob schema {#multimodal-blob-v2-schema}
Declare a blob column with `lancedb.blob()` in Python and TypeScript, or
`blob()` in Rust:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiSchema}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiSchema}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiSchema}
</CodeBlock>
</CodeGroup>
#### Ingest blobs {#multimodal-blob-v2-ingest}
Pass raw bytes for each blob value. In Python, a blob column accepts `bytes`.
In TypeScript, it accepts a `Buffer`. The Python example creates two JPEGs
with EXIF metadata, which we'll read using a blob handle below.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiIngest}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiIngest}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiIngest}
</CodeBlock>
</CodeGroup>
#### Read bytes in query results {#multimodal-blob-query-results}
Python and TypeScript can return blob bytes in query results without
collecting `_rowid`. Rust uses [`fetch_blobs`](#multimodal-read-blobs) with
row IDs instead.
##### Python {#multimodal-blob-python}
`to_list(blob_mode="bytes")` returns the `image` bytes from the `photos` table
above:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiBytes}
</CodeBlock>
</CodeGroup>
In the Python SDK, `Table.to_pandas()`, `Table.to_arrow()`, and the query builder
methods `to_pandas()`, `to_arrow()`, and `to_list()` accept a `blob_mode`
argument:
| `blob_mode` | `to_pandas()` | `to_arrow()` and `to_list()` |
| --- | --- | --- |
| `"lazy"` | Default. Plain scans return seekable `BlobFile` handles. Vector, full-text, hybrid, and ordered queries return descriptor dicts. | Default. Returns descriptors carrying `_lance_row_id` so the result table can be passed to `fetch_blobs` or `fetch_blob_files`. |
| `"descriptions"` | Returns descriptor dicts with `kind`, `position`, `size`, `blob_id`, and `blob_uri`. | Returns the 5-field descriptor without `_lance_row_id`. |
| `"bytes"` | Materializes each blob as `bytes` in the DataFrame. | Materializes each blob as `large_binary` in Arrow or `bytes` in Python dicts. |
`to_pandas(blob_mode="lazy")` and `to_pandas(blob_mode="bytes")` require
pylance. Without it they raise `ImportError`. `to_arrow()`,
`to_list(blob_mode="bytes")`, `to_pandas(blob_mode="descriptions")`, and the
fetch methods do not.
`blob_mode="bytes"` requires a local table snapshot, so remote and in-memory
tables raise `NotImplementedError`. On a remote table, `Table.to_pandas()` and
`Table.to_arrow()` aren't supported, and a query's `to_pandas()` also raises in
the default `"lazy"` mode because it would open `BlobFile` handles. Query with
`blob_mode="descriptions"`, then read bytes with `fetch_blobs` or
`fetch_blob_files`.
`blob_mode="bytes"` materializes top-level and struct-nested blob columns. It
raises `ValueError` when the selected columns include blobs
inside a list. Select those columns with `blob_mode="descriptions"`, or leave
them out of the projection. Extra keyword arguments on
`to_pandas()`, such as `split_blocks` and `self_destruct`, are forwarded to
PyArrow.
###### Optional pandas examples {#multimodal-blob-pandas}
These examples require pylance. The query example expects `tbl_async`,
`query_vector`, and a `video` column, which the `photos` table does not define.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiToPandas}
</CodeBlock>
</CodeGroup>
Sync and async query builders accept the same `blob_mode` values:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{QueryToPandasKwargs}
</CodeBlock>
</CodeGroup>
##### TypeScript {#multimodal-blob-typescript}
In TypeScript, `toArray()` and `toArrow()` accept a `blobMode` option:
| `blobMode` | Result |
| --- | --- |
| `"descriptions"` | Default. Returns descriptors. |
| `"bytes"` | Returns each top-level blob as bytes, or `null` for a null blob. Blobs nested in a struct or list keep their descriptors. |
With `"bytes"`, the rows and the bytes come from the same table version. If the
table changes while the query runs, the query runs again, up to three times:
<CodeGroup>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiBytesMode}
</CodeBlock>
</CodeGroup>
#### Read blobs on demand {#multimodal-read-blobs}
Query the rows you want and pass their `_rowid` values to a fetch method.
`fetch_blob_files` opens a seekable handle and loads bytes only when you read.
The Python example opens the JPEG from the ingest step and reads its EXIF
Make tag with Pillow. TypeScript and Rust read the first bytes from the handle.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiFiles}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiFiles}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiFiles}
</CodeBlock>
</CodeGroup>
The other fetch methods load bytes directly:
| Need | Python | TypeScript | Rust |
| --- | --- | --- | --- |
| Whole payloads for a set of rows | `fetch_blobs` | `fetchBlobs` | `fetch_blobs` |
| Byte slices across many rows in one call | `fetch_blob_ranges` | Not available. Use `fetchBlobFiles` and `readRanges`. | `fetch_blob_ranges` |
- `fetch_blobs` returns the full bytes for each row ID in order. Null blobs
produce null entries, and empty blobs produce empty byte strings.
- `fetch_blob_files` loads bytes when you call `read`, `read_range`, or
`read_ranges`. Range reads don't move the file cursor. Null blobs produce
null handles.
- `fetch_blob_ranges` takes `(row_id, offset, length)` requests and returns one
slice per request, in order. A request list can repeat a row to read more
than one slice of that blob.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiFetch}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiFetch}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiFetch}
</CodeBlock>
</CodeGroup>
Python and Rust read slices across rows with `fetch_blob_ranges`. On a handle,
`read_ranges` reads more than one slice of one blob. Python takes
`(offset, length)` pairs, TypeScript takes `{ start, end }` ranges with an
exclusive `end`, and Rust takes `start..end` ranges:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{BlobApiRanges}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBlobApiRanges}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBlobApiRanges}
</CodeBlock>
</CodeGroup>
#### Notes and limits {#multimodal-blob-notes}
Blob fetches fail if any requested row ID is missing or deleted. With stable
row IDs enabled, IDs remain valid across compaction.
A blob field can sit at the top level of the schema or inside a struct or list.
Struct-nested blob columns use dotted paths such as `clip.video` in the fetch
APIs. Blobs inside lists can be written and queried as descriptors. A table
row ID does not address one list element, so `blob_columns()`, `fetch_blobs`,
`fetch_blob_files`, and `blob_mode="bytes"` can't return those bytes.
See the Lance [blob guide](https://lance.org/guide/blob/) for the storage format.
### Managed blobs (Blob v2) {#managed-blobs}
Set the table schema metadata `lance.blob.managed` to `true` on a table with
[`lancedb.blob()`](#multimodal-blob-v2) columns to store small values inline and
larger values in managed objects under the table's `_blobs/` directory. Medium
values can share a packed object, and large values use a dedicated object.
The table owns these objects. You do not upload them separately, keep an object
catalog, or delete their files yourself. Compaction preserves each descriptor
and its payload object, so merging table fragments does not rewrite the blob
payloads.
<Warning>
Use an SDK and Enterprise deployment that support Blob v2 Managed storage.
Upgrade every reader, writer, and maintenance worker before activating Managed
blobs on a table, and reopen existing table handles after the upgrade. Older
clients cannot safely maintain a table containing Managed blobs.
</Warning>
#### Write and read blobs {#managed-blobs-read-write}
This example uses a local connection. The same schema, `add`, query, and
`fetch_blobs` calls work with an Enterprise database connection:
```python
import lancedb
import pyarrow as pa
db = lancedb.connect("./media")
table = db.create_table(
"images",
schema=pa.schema([
pa.field("id", pa.int64(), nullable=False),
lancedb.blob("image"),
], metadata={"lance.blob.managed": "true"}),
)
table.add([
{"id": 1, "image": b""},
{"id": 2, "image": bytes(range(256)) * 400},
{"id": 3, "image": None},
])
# Query descriptors first, then fetch only the selected rows' payloads.
hits = (
table.search()
.where("id <= 2")
.select(["id", "image"])
.with_row_id(True)
.to_arrow()
)
images = table.fetch_blobs("image", hits).to_pylist()
by_id = dict(zip(hits["id"].to_pylist(), images, strict=True))
assert by_id[1] == b""
assert len(by_id[2]) == 102400
```
`fetch_blobs` materializes the selected payloads in memory, so fetch bounded
batches when blobs are large. Row IDs belong to their table and snapshot. Keep
descriptors opaque in application code, because their representation can differ
between inline values and Managed objects.
#### Derived blobs and maintenance {#managed-blobs-lifecycle}
Enterprise Functions can consume blob columns and
return new blobs. Their output payloads are managed by the destination table.
Local tables can also derive blob columns with
[SQL computed columns](/feature-engineering/add-computed-column).
Deleting a row or replacing its blob does not immediately remove the old
payload: retained table versions may still reference it. [Cleanup](/maintenance/cleanup#managed-blobs)
reclaims objects after those references expire. A packed object remains while
any of its contained blobs is still referenced.
### Legacy large-binary blobs {#multimodal-large-blobs-blob-api}
Use [`lancedb.blob()`](#multimodal-blob-v2) for new tables. The sections below
cover existing tables that use the V1 large-binary Blob API
(`pa.large_binary()` in Python, `LargeBinary` in TypeScript, and
`DataType::LargeBinary` in Rust with `{"lance-encoding:blob": "true"}` field
metadata). Changing a field's metadata alone does not migrate an existing V1
column to Blob v2.
#### 1\. Define a blob schema {#multimodal-1-define-a-blob-schema}
V1 blob columns use a `large_binary` field marked with `{"lance-encoding:blob": "true"}` metadata:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{LegacyBlobApiSchema}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsLegacyBlobApiSchema}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsLegacyBlobApiSchema}
</CodeBlock>
</CodeGroup>
#### 2\. Ingest large blobs {#multimodal-2-ingest-large-blobs}
Write raw bytes to a V1 blob column:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{LegacyBlobApiIngest}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsLegacyBlobApiIngest}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsLegacyBlobApiIngest}
</CodeBlock>
</CodeGroup>
#### 3\. Convert blob tables to pandas {#multimodal-3-convert-blob-tables-to-pandas}
On local tables with V1 blob columns, `to_pandas()` in Python accepts `blob_mode`
(`"lazy"`, `"bytes"`, or `"descriptions"`). Remote tables raise
`NotImplementedError`, and `"bytes"` is not supported on in-memory tables.
Extra keyword arguments such as `split_blocks` or `self_destruct` are forwarded
to the PyArrow / Lance pandas conversion:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{LegacyBlobApiToPandas}
</CodeBlock>
</CodeGroup>
On query builders with V1 blob columns, plain scans support `"lazy"`, `"bytes"`,
and `"descriptions"`. Vector, FTS, hybrid, and ordered queries cannot
materialize V1 blob columns through `to_pandas()`, so omit those columns from
the projection:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{LegacyQueryToPandasKwargs}
</CodeBlock>
</CodeGroup>
### Store binary data {#multimodal-store-binary-data}
Use a `binary` column when the query should return the raw bytes. Declare it as `pa.binary()` in Python, `Binary` in TypeScript, and `DataType::Binary` in Rust.
#### 1\. Setup and imports {#multimodal-1-setup-and-imports}
Import LanceDB and Arrow in your SDK.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{MultimodalImports}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsMultimodalImports}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsMultimodalImports}
</CodeBlock>
</CodeGroup>
#### 2\. Prepare data {#multimodal-2-prepare-data}
Encode each image or file into raw bytes before writing.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{CreateDummyData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsCreateDummyData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsCreateDummyData}
</CodeBlock>
</CodeGroup>
#### 3\. Define the schema {#multimodal-3-define-the-schema}
Declare the schema explicitly so Arrow and LanceDB store the field as `binary` instead of inferring a string or integer list.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{DefineSchema}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDefineSchema}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDefineSchema}
</CodeBlock>
</CodeGroup>
#### 4\. Ingest data {#multimodal-4-ingest-data}
Create the table from the rows and schema.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{IngestData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsIngestData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsIngestData}
</CodeBlock>
</CodeGroup>
### Retrieve binary data {#multimodal-retrieve-and-use-blobs}
When you search the table, select the `binary` column alongside your metadata columns.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SearchData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSearchData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSearchData}
</CodeBlock>
</CodeGroup>
#### Convert bytes back to objects {#multimodal-convert-bytes-back-to-objects}
Decode the returned bytes into an image or media buffer in your application.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{ProcessResults}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsProcessResults}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsProcessResults}
</CodeBlock>
</CodeGroup>
### Other modalities {#multimodal-other-modalities}
For PDFs, audio recordings, and other files, keep searchable fields in separate
columns so you can filter rows before fetching blob bytes.
### bfloat16 {#multimodal-bfloat16}
Sixteen-bit floats in the bfloat16 layout, stored as `fixed_size_binary(2)`.
Useful as a vector element type when halving the memory matters more than the
precision does.
## Schema evolution {#schema}
Schema evolution enables non-breaking modifications to a database table's structure (such as adding columns, altering data types, or dropping fields) to adapt to evolving data requirements without service interruptions.
LanceDB supports ACID-compliant schema evolution through granular operations (add/alter/drop columns), allowing you to:
* Iterate Safely: Modify schemas in production with versioned datasets and backward compatibility
* Scale Seamlessly: Handle ML model iterations, regulatory changes, or feature additions
* Optimize Continuously: Remove unused fields or enforce new constraints without downtime
<Note>
In SQL, schema changes are [`ALTER TABLE`](/sql/ddl/table#alter).
</Note>
### Schema evolution operations {#schema-schema-evolution-operations}
LanceDB supports four primary schema evolution operations:
1. **Adding new columns**: Extend your table with additional attributes
2. **Altering existing columns**: Change column names, data types, or nullability
3. **Updating field metadata**: Attach or change per-column Arrow metadata
4. **Dropping columns**: Remove unnecessary columns from your schema
<Tip title="Schema Evolution Performance">
Schema evolution operations are applied immediately but do not typically require rewriting all data. However, data type changes may involve more substantial operations.
</Tip>
Each schema evolution operation commits a new table version and returns status metadata such as
the committed `version`. Run these operations from a mutable table handle; if you checked out an
older version for reads, call `checkout_latest` / `checkoutLatest` before modifying the schema.
### Add new columns {#schema-add-new-columns}
You can add new columns to a table with the [`add_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.add_columns)
method in Python, [`addColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#addcolumns) in TypeScript/JavaScript, or `add_columns` in Rust.
New columns are populated based on SQL expressions you provide.
#### Set up the example table {#schema-set-up-the-example-table}
First, let's create a sample table with product data to demonstrate schema evolution:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{PySchemaAddSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaAddSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaAddSetup}
</CodeBlock>
</CodeGroup>
<Note>
Columns derived from existing data (SQL expressions over other columns) are
covered in [Data evolution](/feature-engineering/data-evolution), because
deriving values is a feature-engineering job rather than a schema one.
</Note>
#### Add columns with default values {#schema-add-columns-with-default-values}
Add boolean columns with default values for status tracking:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AddColumnsDefaultValues}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAddColumnsDefaultValues}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAddColumnsDefaultValues}
</CodeBlock>
</CodeGroup>
#### Add nullable columns {#schema-add-nullable-columns}
Add timestamp columns that can contain NULL values:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AddColumnsNullable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAddColumnsNullable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAddColumnsNullable}
</CodeBlock>
</CodeGroup>
<Warning title="NULL Values in New Columns">
When adding columns that should contain NULL values, be sure to cast the NULL to the appropriate type, e.g., `cast(NULL as timestamp)`.
</Warning>
### Alter existing columns {#schema-alter-existing-columns}
You can alter columns using the [`alter_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.alter_columns)
method in Python, [`alterColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#altercolumns) in TypeScript/JavaScript, or `alter_columns` in Rust. This allows you to:
- Rename a column
- Change a column's data type
- Modify nullability (whether a column can contain NULL values)
#### Set up the example table {#schema-set-up-the-example-table-2}
Create a table with a custom schema to demonstrate column alterations:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaAlterSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaAlterSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaAlterSetup}
</CodeBlock>
</CodeGroup>
#### Rename columns {#schema-rename-columns}
Change column names to better reflect their purpose:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsRename}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsRename}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsRename}
</CodeBlock>
</CodeGroup>
#### Change data types {#schema-change-data-types}
Convert column data types for better performance or compatibility:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsDataType}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsDataType}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsDataType}
</CodeBlock>
</CodeGroup>
#### Make columns nullable {#schema-make-columns-nullable}
You can alter columns to contain NULL values:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsNullable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsNullable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsNullable}
</CodeBlock>
</CodeGroup>
Changing a column to nullable affects future writes and merges too: missing values are accepted
only when the target column is nullable.
#### Multiple changes at once {#schema-multiple-changes-at-once}
Apply several alterations in a single operation:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsMultiple}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsMultiple}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsMultiple}
</CodeBlock>
</CodeGroup>
#### Expression-based type changes {#schema-expression-based-type-changes}
For transformations that are not simple casts (for example, converting `"$100"` to an integer), use a SQL-expression column add, then drop and rename:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterColumnsWithExpression}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterColumnsWithExpression}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterColumnsWithExpression}
</CodeBlock>
</CodeGroup>
#### Alter embedding types and dimensions {#schema-alter-embedding-types-and-dimensions}
It's quite common to need to change an embedding column's schema, in case a new model becomes available with a different embedding dimension.
- In Python, the example shows an in-place type update when the cast is compatible.
- In TypeScript and Rust, the example shows a dimension change (`384 -> 1024`), which cannot be cast in-place.
For dimension changes, use this 3-step pattern: add a new column with the target type, drop the old column, then rename the new column to the original name.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{AlterVectorColumn}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsAlterVectorColumn}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsAlterVectorColumn}
</CodeBlock>
</CodeGroup>
<Warning>
**`FixedSizeList` Dimension Changes in TypeScript and Rust**
`alterColumns` / `alter_columns` can cast between compatible types, but changing `FixedSizeList` dimensions (for example `384 -> 1024`) is not a compatible cast.
For such cases, use `addColumns` / `add_columns` (with `arrow_cast`), then `dropColumns` / `drop_columns`, then rename the replacement column.
</Warning>
<Warning title="Data Type Changes">
Changing data types requires rewriting the column data and may be resource-intensive for large tables. Renaming columns or changing nullability is more efficient as it only updates metadata.
</Warning>
### Update field metadata {#schema-update-field-metadata}
Each column in a LanceDB table can carry a small key/value map of Arrow field metadata, which is useful
for annotating columns with units, provenance, PII flags, embedding model versions, or any other
schema-level context your application needs.
Use [`update_field_metadata`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.update_field_metadata)
in Python, [`updateFieldMetadata`](https://lancedb.github.io/lancedb/js/classes/Table/#updatefieldmetadata)
in TypeScript/JavaScript, or `update_field_metadata` in Rust to add, change, or remove these
key/value pairs without rewriting the column data. Each call commits a new table version and returns
the new `version`.
Each update targets one field by **dot-path**: top-level columns are addressed by name (for
example `"embedding"`), and nested fields by their full path (for example `"address.zip"`). By
default, the keys you pass are **merged** into the field's existing metadata: keys you do not
mention are preserved, and passing `None` (Python) or `null` (TypeScript) deletes a key. Set
`replace: true` to swap the field's entire metadata map instead of merging.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaFieldMetadataMerge}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaFieldMetadataMerge}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaFieldMetadataMerge}
</CodeBlock>
</CodeGroup>
To overwrite a field's metadata entirely instead of merging, set `replace` to `true`:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaFieldMetadataReplace}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaFieldMetadataReplace}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaFieldMetadataReplace}
</CodeBlock>
</CodeGroup>
<Tip>
You can pass multiple updates in a single call to change metadata on several fields at once.
Each call commits a single new table version.
</Tip>
#### Canonical metadata keys {#schema-canonical-metadata-keys}
You can use any string as a metadata key. By convention, LanceDB treats keys prefixed with
`lancedb:` as canonical. LanceDB Enterprise displays these keys in the UI, and agents can rely
on finding this information in a consistent place. Use them when they fit your use case:
| Key | Purpose | Example |
| --- | --- | --- |
| `lancedb:description` | Human-readable description of the field. | `"CLIP embedding of the product image"` |
| `lancedb:tag:<name>` | User-defined key/value tag, where the suffix names the tag category. | `"lancedb:tag:model": "clip"` |
| `lancedb:logical-column` | Groups related columns into one logical column. For example, `feature_v1` and `feature_v2` might belong to the same logical column. | `"lancedb:logical-column": "feature"` |
| `lancedb:status` | Life cycle state of the column: `production`, `candidate`, `deprecated`, or `archived`. | `"lancedb:status": "production"` |
<Note>
These keys are conventions, not enforced constraints. LanceDB does not validate their values,
but following the conventions keeps your tables consistent with Enterprise tooling.
</Note>
### Drop columns {#schema-drop-columns}
You can remove columns using the [`drop_columns`](https://lancedb.github.io/lancedb/python/python/#lancedb.table.Table.drop_columns)
method in Python, [`dropColumns`](https://lancedb.github.io/lancedb/js/classes/Table/#dropcolumns) in TypeScript/JavaScript, or `drop_columns` in Rust.
#### Set Up the example table {#schema-set-up-the-example-table-3}
Create a table with temporary columns that we'll remove:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{SchemaDropSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsSchemaDropSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsSchemaDropSetup}
</CodeBlock>
</CodeGroup>
#### Drop single columns {#schema-drop-single-columns}
Remove individual columns that are no longer needed:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{DropColumnsSingle}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDropColumnsSingle}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDropColumnsSingle}
</CodeBlock>
</CodeGroup>
#### Drop multiple columns {#schema-drop-multiple-columns}
Remove several columns at once for efficiency:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{DropColumnsMultiple}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDropColumnsMultiple}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDropColumnsMultiple}
</CodeBlock>
</CodeGroup>
<Warning title="Dropping a column is undone only by restoring a version">
Dropping a column changes the latest version only. Until
[cleanup](/maintenance/cleanup) prunes the versions from before the drop,
[restoring](#versioning) one of them brings the column and its values back,
along with the rest of the table as it was then. Once those versions are
pruned, the drop cannot be undone, so check before dropping a column you may
need.
</Warning>
## Versioning {#versioning}
This page shows the core table-versioning APIs used in the code snippets for Python, TypeScript, and Rust.
Each operation below maps directly to methods shown in the examples.
### Basic Versioning Example {#versioning-basic-versioning-example}
Let's create a table with sample data to demonstrate LanceDB's versioning capabilities:
#### Set Up the Table {#versioning-set-up-the-table}
First, let's create a table with some sample data:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningBasicSetup}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningBasicSetup}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningBasicSetup}
</CodeBlock>
</CodeGroup>
<Expandable title="Rust helper used in this page">
<CodeGroup>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningMakeQuotesReader}
</CodeBlock>
</CodeGroup>
</Expandable>
#### Check Initial Version {#versioning-check-initial-version}
After creating the table, let's check the initial version information:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningCheckInitialVersion}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningCheckInitialVersion}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningCheckInitialVersion}
</CodeBlock>
</CodeGroup>
### Modify Data {#versioning-modify-data}
When you modify data through operations like update or delete, LanceDB automatically creates new versions.
#### Update Existing Data {#versioning-update-existing-data}
Let's update some existing records to see versioning in action:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningUpdateData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningUpdateData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningUpdateData}
</CodeBlock>
</CodeGroup>
#### Add New Data {#versioning-add-new-data}
Now let's add more records to the table:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningAddData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningAddData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningAddData}
</CodeBlock>
</CodeGroup>
#### Check Version Changes {#versioning-check-version-changes}
Let's see how the versions have changed after our modifications:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningCheckVersionsAfterMod}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningCheckVersionsAfterMod}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningCheckVersionsAfterMod}
</CodeBlock>
</CodeGroup>
### Rollback to Previous Versions {#versioning-rollback-to-previous-versions}
LanceDB supports fast rollbacks to any previous version without data duplication.
#### View All Versions {#versioning-view-all-versions}
First, let's see all the versions we've created:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningListAllVersions}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningListAllVersions}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningListAllVersions}
</CodeBlock>
</CodeGroup>
#### Restore a Version Snapshot {#versioning-restore-a-version-snapshot}
Now let's restore a captured version snapshot:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningRollback}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningRollback}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningRollback}
</CodeBlock>
</CodeGroup>
### Tag-Based Versioning {#versioning-tag-based-versioning}
Numeric table versions like `v3` or `v17` are precise but hard to remember. Tags
let you attach human-readable labels (e.g., `"prod"`, `"baseline"`,
`"q3-evaluation"`) to specific versions and check those out by name. They are
conceptually similar to git tags, and unlike numeric versions, **tagged versions
are never pruned**. A tagged version that is older than the retention window and
is not the latest stops cleanup instead (see the cleanup note at the bottom of this
page).
The tags API supports the standard CRUD operations (create, list, update, delete),
plus checking out by tag name.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningTags}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningTags}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningTags}
</CodeBlock>
</CodeGroup>
<Note>
Deleting a tag only removes the label, not the version it points to. After
deletion, the underlying table version becomes eligible for cleanup again.
</Note>
### Branches {#versioning-branches}
Beyond linear history, LanceDB also supports **branches**: isolated, writable
lines of history forked from `main` (or a specific version). Whereas tags and
`checkout` give you read-only views of existing versions, a branch has its own
writable history, making it ideal for experiments, backfills, and migrations
that you want to keep separate from production reads on `main`.
Branches are covered in their own guide: see [Branches](/tables/branching).
### Delete Data From the Table {#versioning-delete-data-from-the-table}
Let's demonstrate how deletions also create new versions:
#### Go Back to Latest Version {#versioning-go-back-to-latest-version}
First, let's return to the latest version:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningCheckoutLatest}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningCheckoutLatest}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningCheckoutLatest}
</CodeBlock>
</CodeGroup>
#### Delete Data {#versioning-delete-data}
Now let's delete some data to see how it affects versioning:
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{VersioningDeleteData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsVersioningDeleteData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsVersioningDeleteData}
</CodeBlock>
</CodeGroup>
#### Version History and Operations {#versioning-version-history-and-operations}
On a fresh table, the snippets in this guide produce this version sequence:
1. `v1`: create table (`create_table` / `createTable` / `create_table`)
2. `v2`: update rows (`update`)
3. `v3`: add rows (`add`)
4. `v4`: restore snapshot (`restore`) from `version_after_mod`/`versionAfterMod`
5. `v5`: delete rows (`delete`)
Read-only and checkout operations shown here (`list_versions`/`listVersions`, `version`, `checkout`, `checkout_latest`/`checkoutLatest`) do not create new versions.
The version metadata fields can differ by backend. Direct table-backed version listing exposes a
timestamp, while namespace-backed listing may expose fields such as `manifest_path`,
`manifest_size`, `e_tag`, and `timestamp_millis`. In deployments that use managed versioning,
prefer the table version APIs exposed by LanceDB Enterprise or the namespace service instead of
mixing in lower-level Lance file operations.
<Note>
**System Operations**
System operations like `optimize()`, index updates, and table compaction also increment table version numbers.
In LanceDB OSS, `optimize()` can prune older versions based on its retention setting (`cleanup_older_than`, 7 days by default),
which is when old-version files are removed and disk space is reclaimed. A deployment prunes them in the background
when its configuration enables [cleanup](/maintenance/cleanup); `optimize()` is not available there.
**Tagged versions are never pruned.** A version with a tag pointing at it is
retained regardless of age. When a tagged version is older than the retention window and
is not the latest, cleanup refuses to run instead of pruning around it: `optimize()` raises
an error naming the tag and prunes nothing. A tag on the latest version does not cause
this, but `optimize()` compacts before it prunes, and compacting commits a new version
whenever it merges files, so a tag on what was the latest version can become an older
one by the time cleanup runs; the error then comes after compacting, and the index
update does not run. To let cleanup proceed, [move the tag](#versioning-tag-based-versioning)
to a version inside the retention window, delete it, or widen the window.
</Note>