docs: one page per object in Manage

Namespace and Table were the only objects behind a nested group. Both are now a
single page like Database, Materialized view, View and Function, so Manage is a
flat list of the things a catalog holds.

Namespace merges its two pages, which were a concept page and a usage page for
the same object.

Table is a new page. The old tables/index was a 672-line basic-usage tutorial
whose every section had acquired a better home during the restructure --
connecting, ingesting, searching, filtering, adding and dropping columns -- to
the point that its own Next steps cards pointed at the pages that superseded it.
It is replaced by a table object page: what a table carries, create, open, list,
rename, clone, drop, and links out.

Schema evolution, versioning and branching stay as siblings rather than folding
in. Merging them would have made one 1,800-line page, which is not what the
other object pages look like.
This commit is contained in:
Jack Ye
2026-09-14 00:07:34 -07:00
parent 5b5ed930ac
commit cd0ebe3d12
14 changed files with 326 additions and 854 deletions
+2 -2
View File
@@ -19,8 +19,8 @@ feature store. The catalog part is what this page is about.
| Object | What it is |
| --- | --- |
| [Database](/management/database) | The root. A connection opens exactly one. |
| [Namespace](/namespaces) | A named grouping inside a database. Can nest. |
| [Table](/tables) | Rows and columns, versioned. What most operations act on. |
| [Namespace](/management/namespace) | A named grouping inside a database. Can nest. |
| [Table](/management/table) | Rows and columns, versioned. What most operations act on. |
| [Materialized view](/feature-engineering/materialized-views) | A table whose contents come from a query, computed and stored. |
| [View](/management/view) | A saved query. Computed when read, not stored. |
| [Function](/management/function) | Registered code, applied to produce column values. |
+17 -16
View File
@@ -90,6 +90,18 @@
{
"source": "/tutorials/feature-engineering",
"destination": "/tutorials/feature-engineering/feature-engineering-101"
},
{
"source": "/tables",
"destination": "/management/table"
},
{
"source": "/namespaces",
"destination": "/management/namespace"
},
{
"source": "/namespaces/usage",
"destination": "/management/namespace"
}
],
"navigation": {
@@ -124,22 +136,11 @@
"pages": [
"management/index",
"management/database",
{
"group": "Namespace",
"pages": [
"namespaces/index",
"namespaces/usage"
]
},
{
"group": "Table",
"pages": [
"tables/index",
"tables/schema",
"tables/versioning",
"tables/branching"
]
},
"management/namespace",
"management/table",
"tables/schema",
"tables/versioning",
"tables/branching",
"management/materialized-view",
"management/function"
]
+1 -1
View File
@@ -94,7 +94,7 @@ for agents. Start here:
<Card
title="Basic Table Operations"
icon="table"
href="/tables/"
href="/management/table"
>
Create tables, evolve schemas, version data, and modify rows in LanceDB.
</Card>
+1 -1
View File
@@ -110,7 +110,7 @@ SELECT animal, noise, vector, _hybrid_score, _distance, _score
A directory namespace maps a LanceDB catalog root to namespace-qualified table identifiers in DuckDB. This keeps table discovery and table naming stable as your project grows.
To learn more about the catalog and namespace model, see [Namespaces and the Catalog Model](/namespaces).
To learn more about the catalog and namespace model, see [Namespaces and the Catalog Model](/management/namespace).
## Advanced usage {#advanced-usage}
+1 -1
View File
@@ -169,7 +169,7 @@ DROP TABLE documents;
```
</CodeGroup>
Each takes a `namespace_path` to act inside a [namespace](/namespaces) rather
Each takes a `namespace_path` to act inside a [namespace](/management/namespace) rather
than at the root.
<Warning>
+2 -2
View File
@@ -30,7 +30,7 @@ See [Python, TypeScript, and SQL](/basics/apis).
| exists | `namespace_exists` | -- | -- |
| drop | `drop_namespace` | `dropNamespace` | -- |
See [Namespaces](/namespaces).
See [Namespaces](/management/namespace).
### Tables {#tables}
@@ -46,7 +46,7 @@ See [Namespaces](/namespaces).
| drop | `drop_table`, `drop_table_async` | `dropTable`, `dropTableAsync` | `DROP TABLE` |
| drop all | `drop_all_tables` | `dropAllTables` | -- |
See [Tables](/tables) for creating one and shaping its schema.
See [Tables](/management/table) for creating one and shaping its schema.
### Materialized views {#materialized-views}
@@ -1,11 +1,20 @@
---
title: "Namespaces and the Catalog Model"
sidebarTitle: "Overview"
description: "Understand LanceDB as a catalog-level abstraction over Lance's table format, and learn how namespaces help organize Lance tables."
icon: "sitemap"
keywords: ["namespace", "catalog", "lance format", "table format", "lancedb"]
title: "Namespaces"
sidebarTitle: "Namespace"
description: "Grouping tables into a hierarchy, and addressing them through it."
icon: folder-tree
keywords: ["namespace", "catalog", "hierarchy", "directory namespace", "rest namespace"]
---
import {
PyNamespaceTableOps,
PyNamespaceAdminOps,
TsNamespaceTableOps,
TsNamespaceAdminOps,
RsNamespaceAdminOps,
RsNamespaceTableOps,
} from '/snippets/connection.mdx';
Despite its name, LanceDB is not a "database" in the traditional sense -- it is a **Multimodal Lakehouse** that builds on the table abstraction,
similar to many other lakehouse projects. LanceDB exposes a catalog-level abstraction over the Lance table format, via a *namespace spec*.
If you're coming from traditional databases or lakehouses, you can think of a namespace as the catalog path that says where a table name lives.
@@ -215,7 +224,7 @@ let db = lancedb::connect_namespace("rest", properties)
</CodeGroup>
[LanceDB Enterprise](/enterprise) operates a REST namespace server on top of the Lance format, so any REST client that can speak the REST namespace API
contract can be used to interact with it. For authentication examples in LanceDB Enterprise, visit
the [Namespaces in SDKs](/namespaces/usage#namespaces-in-lancedb-enterprise) page.
the [Namespaces in SDKs](#namespaces-in-lancedb-enterprise) page.
## Best practices {#best-practices}
@@ -224,3 +233,127 @@ Below, we list some best practices for working with namespaces:
- For remote storage locations, introduce explicit namespaces when multiple teams, environments, or domains share the same catalog.
- Treat namespace paths as stable identifiers (for example `"prod/search"`, `"staging/recs"`).
- For maintainability reasons, avoid hard-coding object-store table paths in application code -- instead, prefer catalog identifiers + namespaces.
## Table operations with namespace paths {#table-operations-with-namespace-paths}
Let's imagine a scenario where your table management needs have evolved, and you now have the following multi-level structure to organize your tables outside the root namespace.
```
./local_lancedb (root)
└── prod
└── search
└── user (table)
└── data (table)
└── recommendations
└── user (table)
└── data (data)
```
Below, we show how you would express table operations within that namespace. Each item in the namespace
list (`["prod", "search"]`) represents a level in the namespace hierarchy, and the table name is
specified when you create, open, list, or drop it.
The SDK methods expose the namespace path in the idiom of each language:
- Python: pass `namespace_path=["prod", "search"]` to table operations.
- Rust: call builder methods such as `.namespace(vec!["prod".to_string(), "search".to_string()])`.
- TypeScript: pass a `namespacePath` array, for example `await db.openTable("user", ["prod", "search"])`.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{PyNamespaceTableOps}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsNamespaceTableOps}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsNamespaceTableOps}
</CodeBlock>
</CodeGroup>
<Note>
Using namespaces is **optional** in LanceDB, and most basic use cases do not require to work with them.
An empty namespace (`[]`), which is the default, means "root namespace", and the data will be stored in
the `data/` directory under the specified root path.
</Note>
## Namespace management APIs {#namespace-management-apis}
You can open/create/drop tables inside a namespace path (like `["prod", "search"]`).
All three SDKs expose namespace lifecycle operations directly.
In Python, use `lancedb.connect_namespace(...)` when calling namespace lifecycle methods such as
`create_namespace`, `list_namespaces`, `describe_namespace`, and `drop_namespace`.
In TypeScript, use `lancedb.connectNamespace(...)` and call `createNamespace`, `listNamespaces`,
`describeNamespace`, and `dropNamespace` on the returned `Connection`.
In Rust, use `lancedb::connect_namespace(...)` and call `create_namespace`, `list_namespaces`,
and `drop_namespace`.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{PyNamespaceAdminOps}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsNamespaceAdminOps}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsNamespaceAdminOps}
</CodeBlock>
</CodeGroup>
Namespace creation and deletion have modes that control what happens when the target already exists,
doesn't exist, or contains data:
- Create mode: `create` fails if the namespace already exists, `exist_ok` keeps the existing namespace, and `overwrite` replaces it.
- Drop mode: `fail` reports an error when the namespace doesn't exist, and `skip` treats a missing namespace as a successful no-op.
- Drop behavior: `restrict` keeps non-empty namespaces from being dropped, and `cascade` drops child namespaces and tables first.
Namespace path components can't be empty. Each component can contain only letters, numbers,
underscores, hyphens, and periods.
Listing APIs return the immediate children of the requested namespace path. Use `limit` with the
returned `page_token` to page through large catalogs; pass an empty namespace path (`[]`) when you
want to list from the root namespace.
## Namespaces in LanceDB Enterprise {#namespaces-in-lancedb-enterprise}
In LanceDB Enterprise deployments, configure namespace-backed federated databases in a TOML file under your deployment's `config` directory.
LanceDB Enterprise supports both directory-based (`ns_impl = "dir"`) and REST-based (`ns_impl = "rest"`) namespace implementations.
The example below shows how to configure a directory-based namespace implementation in LanceDB Enterprise.
```toml
# Federated database configuration for DirectoryNamespace
# This example uses minio storage
[federated_dbs.federated_dir_test]
ns_impl = "dir"
root = "s3://<your-deployment>/<your-dir>"
"storage.region" = "us-east-1"
"storage.endpoint" = "http://localhost:9000"
"storage.access_key_id" = "minioadmin"
"storage.secret_access_key" = "minioadmin"
"storage.allow_http" = "true"
# Far future expiration (year 2100)
"storage.expires_at_millis" = "4102444800000"
```
The example above uses MinIO, but the same approach applies to other cloud object storage platforms based on your deployment.
For REST-based namespace servers, you can specify the namespace implementation as `"rest"` with forwarding prefixed headers
for authentication and context propagation.
```toml
[federated_dbs.federated_rest_test]
ns_impl = "rest"
uri = "http://<your_org>.internal.catalog.com"
forward_header_prefixes = ["X-forward"]
```
With `forward_header_prefixes = ["X-forward"]`, any incoming header starting with `X-forward` is forwarded to
`http://<your_org>.internal.catalog.com`. This is useful for auth propagation, for example sending
`X-forward-authorization: Bearer xxxx`.
For the LanceDB REST API itself, requests use `x-api-key` for API-key authentication. If your endpoint
serves more than one database, LanceDB can also use headers such as `x-lancedb-database` or
`x-lancedb-database-prefix` to route the request to the right database context.
+159
View File
@@ -0,0 +1,159 @@
---
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"]
---
A table is rows and columns, versioned. It is what searches read, what writes
commit to, and what indexes and branches belong to.
This page covers the table as a catalog object. Getting data into one is
[Ingest](/ingestion); changing its shape is
[Schema evolution](/tables/schema).
## What a table carries {#carries}
| | |
| --- | --- |
| **Columns** | including vectors, blobs, and [computed](/feature-engineering/add-computed-column) columns |
| **Versions** | every write commits one, so reads can [travel back](/tables/versioning) |
| **Indexes** | vector, full-text and scalar, built over its columns |
| **Branches** | isolated lines of change you can [merge or discard](/tables/branching) |
## Creating {#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, and where you opt into
**stable row ids** -- which cannot be enabled later and are required for a
[materialized view](/management/materialized-view) source. See
[Limits](/basics/limits#schema).
</Warning>
## Opening and listing {#open}
<CodeGroup>
```python Python icon="python"
tbl = db.open_table("documents")
db.table_names()
db.table_exists("documents")
```
```typescript TypeScript icon="square-js"
const tbl = await db.openTable("documents");
await db.tableNames();
```
```sql SQL
SHOW TABLES
```
</CodeGroup>
Each accepts `namespace_path` to act inside a [namespace](/management/namespace)
rather than at the database root. TypeScript has no `tableExists`.
## Renaming and cloning {#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,
)
```
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. Use a deep clone (`is_shallow=False`) when
the copy has to outlive the source's history.
</Warning>
## Dropping {#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](/tables/versioning) does not survive a drop, and neither
`drop_all_tables` nor `drop_database` asks for confirmation.
</Warning>
On a large table the drop can take a while, so `drop_table_async` returns a
[job](/basics/jobs) rather than blocking.
## The full surface {#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` | `SHOW TABLES` |
| 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` | -- |
## Where to go next {#next}
<CardGroup cols={2}>
<Card title="Ingesting data" icon="inbox" href="/ingestion">
Creating a table from data, and writing to one that exists.
</Card>
<Card title="Schema evolution" icon="boxes-stacked" href="/tables/schema">
Adding, altering and dropping columns.
</Card>
<Card title="Versioning" icon="clock-rotate-left" href="/tables/versioning">
Every write commits a version; reads can go back.
</Card>
<Card title="Branching" icon="code-branch" href="/tables/branching">
Isolated lines of change, merged or discarded.
</Card>
</CardGroup>
-149
View File
@@ -1,149 +0,0 @@
---
title: "Using Namespaces"
sidebarTitle: "Using namespaces"
description: "Use LanceDB's namespace-aware table and catalog APIs in Python, TypeScript, and Rust."
icon: "folder-tree"
keywords: ["namespace", "create_table", "open_table", "list_tables", "catalog"]
---
import {
PyNamespaceTableOps,
PyNamespaceAdminOps,
TsNamespaceTableOps,
TsNamespaceAdminOps,
RsNamespaceAdminOps,
RsNamespaceTableOps,
} from '/snippets/connection.mdx';
As your table organization needs grow over time and your projects become more complex, you can use namespaces to organize your tables in a way that reflects your business domains, teams, or environments.
As described in the [Namespaces and Catalog Model](/namespaces) section, namespaces are LanceDB's way of generalizing catalog specs, providing developers a clean way to manage hierarchical organization of tables in the catalog. The SDKs treat a namespace as a path and can use it for table resolution when you use LanceDB outside the root namespace.
## Table operations with namespace paths {#table-operations-with-namespace-paths}
Let's imagine a scenario where your table management needs have evolved, and you now have the following multi-level structure to organize your tables outside the root namespace.
```
./local_lancedb (root)
└── prod
└── search
└── user (table)
└── data (table)
└── recommendations
└── user (table)
└── data (data)
```
Below, we show how you would express table operations within that namespace. Each item in the namespace
list (`["prod", "search"]`) represents a level in the namespace hierarchy, and the table name is
specified when you create, open, list, or drop it.
The SDK methods expose the namespace path in the idiom of each language:
- Python: pass `namespace_path=["prod", "search"]` to table operations.
- Rust: call builder methods such as `.namespace(vec!["prod".to_string(), "search".to_string()])`.
- TypeScript: pass a `namespacePath` array, for example `await db.openTable("user", ["prod", "search"])`.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{PyNamespaceTableOps}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsNamespaceTableOps}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsNamespaceTableOps}
</CodeBlock>
</CodeGroup>
<Note>
Using namespaces is **optional** in LanceDB, and most basic use cases do not require to work with them.
An empty namespace (`[]`), which is the default, means "root namespace", and the data will be stored in
the `data/` directory under the specified root path.
</Note>
## Namespace management APIs {#namespace-management-apis}
You can open/create/drop tables inside a namespace path (like `["prod", "search"]`).
All three SDKs expose namespace lifecycle operations directly.
In Python, use `lancedb.connect_namespace(...)` when calling namespace lifecycle methods such as
`create_namespace`, `list_namespaces`, `describe_namespace`, and `drop_namespace`.
In TypeScript, use `lancedb.connectNamespace(...)` and call `createNamespace`, `listNamespaces`,
`describeNamespace`, and `dropNamespace` on the returned `Connection`.
In Rust, use `lancedb::connect_namespace(...)` and call `create_namespace`, `list_namespaces`,
and `drop_namespace`.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="python">
{PyNamespaceAdminOps}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsNamespaceAdminOps}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsNamespaceAdminOps}
</CodeBlock>
</CodeGroup>
Namespace creation and deletion have modes that control what happens when the target already exists,
doesn't exist, or contains data:
- Create mode: `create` fails if the namespace already exists, `exist_ok` keeps the existing namespace, and `overwrite` replaces it.
- Drop mode: `fail` reports an error when the namespace doesn't exist, and `skip` treats a missing namespace as a successful no-op.
- Drop behavior: `restrict` keeps non-empty namespaces from being dropped, and `cascade` drops child namespaces and tables first.
Namespace path components can't be empty. Each component can contain only letters, numbers,
underscores, hyphens, and periods.
Listing APIs return the immediate children of the requested namespace path. Use `limit` with the
returned `page_token` to page through large catalogs; pass an empty namespace path (`[]`) when you
want to list from the root namespace.
## Namespaces in LanceDB Enterprise {#namespaces-in-lancedb-enterprise}
In LanceDB Enterprise deployments, configure namespace-backed federated databases in a TOML file under your deployment's `config` directory.
LanceDB Enterprise supports both directory-based (`ns_impl = "dir"`) and REST-based (`ns_impl = "rest"`) namespace implementations.
The example below shows how to configure a directory-based namespace implementation in LanceDB Enterprise.
```toml
# Federated database configuration for DirectoryNamespace
# This example uses minio storage
[federated_dbs.federated_dir_test]
ns_impl = "dir"
root = "s3://<your-deployment>/<your-dir>"
"storage.region" = "us-east-1"
"storage.endpoint" = "http://localhost:9000"
"storage.access_key_id" = "minioadmin"
"storage.secret_access_key" = "minioadmin"
"storage.allow_http" = "true"
# Far future expiration (year 2100)
"storage.expires_at_millis" = "4102444800000"
```
The example above uses MinIO, but the same approach applies to other cloud object storage platforms based on your deployment.
For REST-based namespace servers, you can specify the namespace implementation as `"rest"` with forwarding prefixed headers
for authentication and context propagation.
```toml
[federated_dbs.federated_rest_test]
ns_impl = "rest"
uri = "http://<your_org>.internal.catalog.com"
forward_header_prefixes = ["X-forward"]
```
With `forward_header_prefixes = ["X-forward"]`, any incoming header starting with `X-forward` is forwarded to
`http://<your_org>.internal.catalog.com`. This is useful for auth propagation, for example sending
`X-forward-authorization: Bearer xxxx`.
For the LanceDB REST API itself, requests use `x-api-key` for API-key authentication. If your endpoint
serves more than one database, LanceDB can also use headers such as `x-lancedb-database` or
`x-lancedb-database-prefix` to route the request to the right database context.
## Related references {#related-references}
- [Client SDK API references](/basics/apis#reference)
- [REST API Reference](/api-reference/rest)
- [Namespaces and the Catalog Model](/namespaces)
+1 -1
View File
@@ -514,7 +514,7 @@ full-text and hybrid search, reranking, and server-side feature engineering.
<Card
title="Basic table operations"
icon="table"
href="/tables"
href="/management/table"
>
Build on this quickstart with table creation, updates, and schema tips.
</Card>
+1 -1
View File
@@ -20,7 +20,7 @@ DROP TABLE documents
```
Column types are Arrow types under SQL names -- see
[Data types](/sql/data-types). A table in a [namespace](/namespaces) is written
[Data types](/sql/data-types). A table in a [namespace](/management/namespace) is written
as a dotted path: `prod.events.clicks`.
## Views {#views}
+1 -1
View File
@@ -32,7 +32,7 @@ avoids repeating it in the select list.
## Across namespaces {#namespaces}
Tables in different [namespaces](/namespaces) join by their dotted paths -- the
Tables in different [namespaces](/management/namespace) join by their dotted paths -- the
catalog is one catalog, so there is nothing to federate:
```sql
+1 -1
View File
@@ -117,7 +117,7 @@ WHERE category_id IN (SELECT id FROM categories WHERE active)
## Namespaced tables {#namespaces}
A table inside a [namespace](/namespaces) is written as a dotted path:
A table inside a [namespace](/management/namespace) is written as a dotted path:
```sql
SELECT * FROM prod.events.clicks
-672
View File
@@ -1,672 +0,0 @@
---
title: "Basic Table Operations"
sidebarTitle: "Basic usage"
description: "Create tables, search vectors, and append data in LanceDB."
icon: "table"
keywords: ["create table", "polars", "pandas", "pyarrow", "dataframe", "nested data"]
---
import { PyConnect, PyConnectEnterpriseQuickstart, TsConnect, TsConnectEnterpriseQuickstart, RsConnect, RsConnectEnterpriseQuickstart } from '/snippets/connection.mdx';
import {
PyBasicImports,
PyDataLoad,
PyBasicCreateTable,
PyBasicOpenTable,
PyBasicCreateEmptyTable,
PyBasicCreateTablePandas,
PyBasicCreateTablePolars,
PyBasicAsyncApi,
PyBasicVectorSearch,
PyBasicVectorSearchQ1,
PyBasicVectorSearchQ2,
PyBasicVectorSearchQ3,
PyBasicVectorSearchQ4,
PyBasicSortPolars,
PyBasicDeleteRows,
PyBasicAddData,
PyBasicAddColumns,
PyBasicDropColumns,
PyBasicDropTable,
TsBasicImports,
TsDataLoad,
TsBasicCreateTable,
TsBasicOpenTable,
TsBasicCreateEmptyTable,
TsBasicVectorSearchQ1,
TsBasicVectorSearchQ2,
TsBasicVectorSearchQ3,
TsBasicVectorSearchQ4,
TsBasicDeleteRows,
TsBasicAddData,
TsBasicAddColumns,
TsBasicDropColumns,
TsBasicDropTable,
RsBasicImports,
RsDataLoad,
RsBasicCreateTable,
RsBasicOpenTable,
RsBasicCreateEmptyTable,
RsBasicAddData,
RsBasicVectorSearchQ1,
RsBasicVectorSearchQ2,
RsBasicVectorSearchQ3,
RsBasicVectorSearchQ4,
RsBasicDeleteRows,
RsBasicAddColumns,
RsBasicDropColumns,
RsBasicDropTable,
} from '/snippets/basic_usage.mdx';
Now that you've completed the [LanceDB quickstart](/quickstart), you're ready to
explore some more table operations you'll typically need when working with LanceDB.
- **Ingest data into tables** from JSON data (and in Python, Pandas or Polars DataFrames)
- **Create empty tables** by defining explicit Arrow schemas
- **Vector similarity search** with filtering and projections
- **Filtered queries** that can operate on nested structs
- **Query Lance tables in DuckDB** via the Lance extension for SQL analytics (including joins)
<Note title="Python async users">
This page uses **synchronous** Python snippets. If your Python app uses `asyncio`,
the same flow works with `connect_async(...)` and `await`-based table/query calls.
Use the example below as a template, and see [Quickstart](/quickstart#python-sync-and-async-apis)
for example snippets on both sync and async Python usage.
</Note>
## Dataset {#dataset}
We'll work with this small dataset based on characters from the legends of Camelot. Note that
the `vector` column holds 4-dimensional embeddings, and the `stats` column is a nested struct
with several integer fields, indicating each character's attributes.
```json camelot.json icon="brackets-curly" expandable=true
[
{
"id": 1,
"name": "King Arthur",
"role": "King of Camelot",
"description": "The legendary ruler of Camelot, wielder of Excalibur, and leader of the Knights of the Round Table.",
"vector": [0.72, -0.28, 0.60, 0.86],
"stats": { "strength": 2, "courage": 5, "magic": 1, "wisdom": 4 }
},
{
"id": 2,
"name": "Merlin",
"role": "Wizard and Advisor",
"description": "A powerful wizard and prophet who mentors Arthur and shapes the destiny of Camelot through magic and foresight.",
"vector": [0.05, 0.88, 0.62, 0.85],
"stats": { "strength": 2, "courage": 4, "magic": 5, "wisdom": 5 }
},
{
"id": 3,
"name": "Queen Guinevere",
"role": "Queen of Camelot",
"description": "Arthur's queen, admired for her grace and diplomacy, whose romances and loyalties influence Camelot's fate.",
"vector": [0.22, -0.22, 0.42, 0.82],
"stats": { "strength": 1, "courage": 3, "magic": 1, "wisdom": 4 }
},
{
"id": 4,
"name": "Sir Lancelot",
"role": "Knight of the Round Table",
"description": "Arthur's most skilled knight, famed for unmatched combat prowess and his tragic love for Queen Guinevere.",
"vector": [0.86, -0.35, 0.38, 0.55],
"stats": { "strength": 5, "courage": 5, "magic": 1, "wisdom": 3 }
},
{
"id": 5,
"name": "Sir Gawain",
"role": "Knight of the Round Table",
"description": "A noble and honorable knight known for his courtesy and his encounter with the Green Knight.",
"vector": [0.82, -0.32, 0.52, 0.60],
"stats": { "strength": 4, "courage": 5, "magic": 1, "wisdom": 4 }
},
{
"id": 6,
"name": "Sir Galahad",
"role": "Knight of the Round Table",
"description": "The purest and most virtuous knight, chosen to achieve the Holy Grail due to his unwavering spiritual purity.",
"vector": [0.80, -0.20, 0.70, 0.78],
"stats": { "strength": 4, "courage": 5, "magic": 2, "wisdom": 5 }
},
{
"id": 7,
"name": "Sir Percival",
"role": "Knight of the Round Table",
"description": "A loyal and innocent knight whose bravery and sincerity make him one of the key seekers of the Holy Grail.",
"vector": [0.78, -0.36, 0.48, 0.52],
"stats": { "strength": 4, "courage": 4, "magic": 1, "wisdom": 3 }
},
{
"id": 8,
"name": "Mordred",
"role": "Traitor Knight",
"description": "Arthur's treacherous son or nephew who ultimately rebels against him, leading to Camelot's downfall.",
"vector": [0.68, -0.30, -0.65, 0.20],
"stats": { "strength": 4, "courage": 2, "magic": 1, "wisdom": 2 }
}
]
```
<Note>
The `vector` arrays here are synthetic and for demonstration purposes only. In your real-world
applications, you'd generate these vectors from the raw text fields using a suitable embedding model.
</Note>
## Connect to a database {#connect-to-a-database}
### Option 1: Direct table access {#option-1-direct-table-access}
We start by connecting to a LanceDB database path. The example below uses a local path in LanceDB OSS.
<CodeGroup>
<CodeBlock filename="Python" language="Python" icon="Python">
{PyConnect}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsConnect}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsConnect}
</CodeBlock>
</CodeGroup>
You can also connect LanceDB OSS directly to object storage. For credentials, endpoints, and provider-specific options, see
[Configuring storage](/storage/configuration).
### Option 2: Remote tables {#option-2-remote-tables}
If you're using LanceDB [Enterprise](/enterprise), you can connect using a `db://` URI,
along with any necessary credentials. Simply replace the local path with a remote `uri`
that points to where your data is stored, and you're ready to go.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="python">
{ "import lancedb\n\n" }
{PyConnectEnterpriseQuickstart}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{ "import * as lancedb from \"@lancedb/lancedb\";\n\n" }
{TsConnectEnterpriseQuickstart}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{ "use lancedb::connect;\n\n" }
{RsConnectEnterpriseQuickstart}
</CodeBlock>
</CodeGroup >
<Note>
- When you connect to a remote URI (Enterprise), `open_table(...)` returns a *remote* table.
Remote tables support core operations (ingest, search, update, delete), but some convenience
methods for bulk data export are not available.
- In the Python SDK, `table.to_arrow()` and `table.to_pandas()` are not implemented for remote tables.
To retrieve data, use search queries instead: `table.search(query).limit(n).to_arrow()`.
</Note>
## Create a table and ingest data {#create-a-table-and-ingest-data}
### From JSON {#from-json}
LanceDB stores records in Lance tables. Each row is a record and each column
holds a field or related metadata. The simplest way to start is to obtain the source
data as a list of JSON records that includes a vector column and any metadata
fields you care about.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicImports}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicImports}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicImports}
</CodeBlock>
</CodeGroup >
Load the data from the JSON file:
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyDataLoad}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsDataLoad}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsDataLoad}
</CodeBlock>
</CodeGroup >
You can now create a LanceDB table from the loaded data. By default, creating a table with a name
that already exists raises an error. Use `mode="overwrite"` only when you intentionally want to
replace the existing table and its data, or use `exist_ok` / `existOk` when repeatable setup should
reuse the existing table instead of writing the supplied rows again.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicCreateTable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicCreateTable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicCreateTable}
</CodeBlock>
</CodeGroup >
<Info>
If you want to avoid overwriting an existing table, omit the overwrite mode. For append-only
ingestion into a table that already exists, open the table and call `add(...)` instead of
`create_table(...)`. For repeatable setup with `exist_ok` / `existOk`, see
[Handle existing tables](/ingestion#handle-existing-tables).
</Info>
For more ingestion patterns, including PyArrow tables, Python `pyarrow.dataset.Dataset` inputs,
empty tables, and Python `LanceModel` schemas with nested fields, see
[Ingesting data](/ingestion).
### From Pandas DataFrames {#from-pandas-dataframes}
<Badge color="green">Python Only</Badge>
You can create LanceDB tables directly from [Pandas](https://pandas.pydata.org/) DataFrames. Simply
obtain the source data as a Pandas DataFrame, then create the table
and directly ingest to it.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicCreateTablePandas}
</CodeBlock>
</CodeGroup>
### From Polars DataFrames {#from-polars-dataframes}
<Badge color="green">Python Only</Badge>
You can also create LanceDB tables directly from [Polars](https://www.pola.rs/) DataFrames. Simply
obtain the source data as a Polars DataFrame, then create the table
and directly ingest to it.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicCreateTablePolars}
</CodeBlock>
</CodeGroup>
### From an Arrow schema {#from-an-arrow-schema}
If you want to create an _empty_ table without any data -- say you want to
define the schema first and then incrementally add data later -- you can
do so by defining an Arrow schema explicitly.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicCreateEmptyTable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicCreateEmptyTable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicCreateEmptyTable}
</CodeBlock>
</CodeGroup>
Once the empty table is defined, LanceDB is ready to accept new data via
the `add` method, as shown in the next section.
<Card title="Display table schema" icon="book">
LanceDB tables are type-aware, leveraging Apache Arrow under the hood.
You can display a given table's schema using the `schema` property or
method. For example, in Python, running `print(table.schema)` would show
something like the following:
```txt expandable=true
id: int64
name: string
role: string
description: string
vector: fixed_size_list<item: float>[4]
child 0, item: float
stats: struct<courage: int64, magic: int64, strength: int64, wisdom: int64>
child 0, courage: int64
child 1, magic: int64
child 2, strength: int64
child 3, wisdom: int64
```
</Card>
## Append data to a table {#append-data-to-a-table}
LanceDB tables are mutable, and you can append new records to existing tables.
If you're starting with a fresh session, connect to the database and open the
existing table named `camelot`.
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicOpenTable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicOpenTable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicOpenTable}
</CodeBlock>
</CodeGroup>
Prepare the new records to add. Here, we add two new magical characters
via the `add` method. For the Rust snippet, you can find the helper functions in the
[code](https://github.com/lancedb/docs/blob/main/tests/rs/basic_usage.rs).
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicAddData}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicAddData}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicAddData}
</CodeBlock>
</CodeGroup>
We now have two new records in the table. Let's begin to query our data!
## Vector search {#vector-search}
It's straightforward to run vector similarity search in LanceDB. Let's answer
some questions about the data using vector search with projections (returning only
the desired columns).
> Q1: _Who are the characters similar to "wizard"?_
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicVectorSearchQ1}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicVectorSearchQ1}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicVectorSearchQ1}
</CodeBlock>
</CodeGroup>
| name | role | description |
| --- | --- | --- |
| Merlin | Wizard and Advisor | A powerful wizard and prophet |
| The Lady of the Lake | Mystical Guardian | A mysterious supernatural figu… |
| Morgan le Fay | Sorceress | A powerful enchantress, Arthur… |
| Queen Guinevere | Queen of Camelot | Arthur's queen, admired for he… |
| Sir Galahad | Knight of the Round Table | The purest and most virtuous k… |
We have Merlin, The Lady of the Lake, and Morgan le Fay in the top results, who
all have magical abilities.
Next, let's try to answer a different question that involves vector search while
filtering on a nested struct field. Filtering is done using the `where` method,
into which you can pass SQL-like expressions.
> Q2: _Who are the characters similar to "wizard" with high magic stats?_
<CodeGroup >
<CodeBlock filename="Python" language="Python" icon="Python">
{PyBasicVectorSearchQ2}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicVectorSearchQ2}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicVectorSearchQ2}
</CodeBlock>
</CodeGroup>
| name | role | description |
| --- | --- | --- |
| Merlin | Wizard and Advisor | A powerful wizard and prophet |
| The Lady of the Lake | Mystical Guardian | A mysterious supernatural figu… |
| Morgan le Fay | Sorceress | A powerful enchantress, Arthur… |
Only three characters have magical abilities greater than 3. Merlin is
clearly the most magical of them all!
## Filtered search {#filtered-search}
You can also run traditional analytics-style search queries that do not
involve vectors. For example, let's find the strongest characters in
the dataset. In the query below, we leave the `search` method empty to indicate
that we don't want to use any vector for similarity search (in TypeScript/Rust,
use `query()` instead), and use the `where` method to filter on the `strength` field.
> Q3: _Who are the strongest characters?_
<CodeGroup >
<CodeBlock filename="Python" language="Python">
{PyBasicVectorSearchQ3}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicVectorSearchQ3}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicVectorSearchQ3}
</CodeBlock>
</CodeGroup>
| name | role | description |
| --- | --- | --- |
| Sir Galahad | Knight of the Round Table | The purest and most virtuous k… |
| Sir Gawain | Knight of the Round Table | A noble and honorable knight k… |
| Sir Percival | Knight of the Round Table | A loyal and innocent knight wh… |
| Sir Lancelot | Knight of the Round Table | Arthur's most skilled knight, … |
| Mordred | Traitor Knight | Arthur's treacherous son or ne… |
Clearly, the strongest characters are all Knights of the Round Table!
<Note>
Need SQL analytics like filters, aggregations, or joins on Lance tables? Use the DuckDB
Lance extension to query Lance tables directly with SQL. See the
[DuckDB integration guide](/integrations/data/duckdb).
</Note>
## Add column {#add-column}
We can also add new columns to an existing LanceDB table using the `add_columns` method.
For this example, let's add a new float column named `power` that shows the average
of each character's strength, courage, magic, and wisdom stats.
<CodeGroup >
<CodeBlock filename="Python" language="Python">
{PyBasicAddColumns}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicAddColumns}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicAddColumns}
</CodeBlock>
</CodeGroup>
The example above sums up the individual stats and divides by 4 to compute the average.
The resulting average total stats is cast to an Arrow float type under the hood for the
Lance table.
We can display the results of this column in descending order of power.
> Q4: _Who are the most powerful characters?_
<CodeGroup >
<CodeBlock filename="Python" language="Python">
{PyBasicVectorSearchQ4}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicVectorSearchQ4}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicVectorSearchQ4}
</CodeBlock>
</CodeGroup>
Note that LanceDB's `where` only filters rows, but doesn't sort them by applying an `ORDER BY`
clause that you may be used to when working with SQL databases.
You can also sort the results after converting them to a Polars DataFrame.
In TypeScript/Rust, you can sort the
returned array in application code.
<CodeBlock filename="Python" language="Python">
{PyBasicSortPolars}
</CodeBlock>
| name | role | description | power |
| --- | --- | --- | --- |
| Merlin | Wizard and Advisor | A powerful wizard and prophet … | 4.0 |
| Sir Galahad | Knight of the Round Table | The purest and most virtuous k… | 4.0 |
| The Lady of the Lake | Mystical Guardian | A mysterious supernatural figu… | 3.75 |
| Sir Lancelot | Knight of the Round Table | Arthur's most skilled knight, … | 3.5 |
| Sir Gawain | Knight of the Round Table | A noble and honorable knight k… | 3.5 |
Merlin and Sir Galahad are the most powerful characters when considering the average of
all their abilities! Sir Lancelot and the Lady of the Lake follow closely behind.
For column renames, type changes, nullability changes, and grouping multiple schema changes into
one operation, see [Schema and data evolution](/tables/schema/).
## Delete data {#delete-data}
You can delete rows from a LanceDB table using the `delete` method with
a filtering expression.
Say we want to remove Mordred, the traitor knight, from our table.
<CodeGroup >
<CodeBlock filename="Python" language="Python">
{PyBasicDeleteRows}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicDeleteRows}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicDeleteRows}
</CodeBlock>
</CodeGroup>
This will delete the row(s) where the `role` value matches "Traitor Knight".
You can verify that the row has been deleted by running a search query again,
and confirming that Mordred no longer appears in the results.
## Drop column {#drop-column}
If you want to remove or delete a column from an existing LanceDB table, you can use
the `drop_columns` method.
<CodeGroup >
<CodeBlock filename="Python" language="Python">
{PyBasicDropColumns}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicDropColumns}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicDropColumns}
</CodeBlock>
</CodeGroup>
This will remove the `power` column we added earlier from the table schema.
## Drop table {#drop-table}
If you want to delete an entire table from the database, you can use the
`drop_table` method.
<CodeGroup >
<CodeBlock filename="Python" language="Python">
{PyBasicDropTable}
</CodeBlock>
<CodeBlock filename="TypeScript" language="TypeScript" icon="square-js">
{TsBasicDropTable}
</CodeBlock>
<CodeBlock filename="Rust" language="Rust" icon="rust">
{RsBasicDropTable}
</CodeBlock>
</CodeGroup>
This will delete the `camelot` table from the connected LanceDB database.
<Info>
See the full code for these examples (including helper functions) in the
`basic_usage` file for the appropriate client language in the
[docs repo](https://github.com/lancedb/docs/tree/main/tests).
</Info>
## What about vector indexes? {#what-about-vector-indexes}
LanceDB supports vector indexes to speed up similarity search on large datasets.
For datasets up to a few hundred thousand vectors, LanceDB's highly efficient kNN
(brute-force) retrieval of nearest neighbors is often sufficient. As your dataset
grows larger, you can create vector indexes on your vector columns to accelerate
search. See the [indexing](/indexing/) documentation for details on how to create and use
vector indexes in LanceDB.
## Next steps {#next-steps}
Now that you've learned the basics of creating tables, adding data, running
vector search, and modifying table schemas, you're ready to explore more
advanced features of LanceDB. Below are some suggested next pages.
<Columns cols={2}>
<Card
title="Ingesting data"
icon="cookie"
href="/ingestion"
>
Learn the different approaches to creating and ingesting data into LanceDB tables from various sources.
</Card>
<Card
title="Ingest and modify data"
icon="clone"
href="/ingestion"
>
Insert, merge, update and delete rows in an existing table.
</Card>
<Card
title="Schema & data evolution"
icon="boxes-stacked"
href="/tables/schema/"
>
Understand how to evolve your table schemas and data over time with LanceDB.
</Card>
<Card
title="Versioning and time travel"
icon="clock"
href="/tables/versioning/"
>
Explore LanceDB's built-in table versioning and time travel capabilities.
</Card>
</Columns>