diff --git a/docs/web-tests/py/test_connection.py b/docs/web-tests/py/test_connection.py
index 92fb963e6..b9b3ca5a6 100644
--- a/docs/web-tests/py/test_connection.py
+++ b/docs/web-tests/py/test_connection.py
@@ -116,14 +116,26 @@ def namespace_table_ops_example():
mode="create", # use "overwrite" only if you want to replace existing table
)
- # Verify
- print(db.list_namespaces()) # ['prod']
- print(db.list_namespaces(namespace_path=["prod"])) # ['recommendations', 'search']
- print(db.list_tables(namespace_path=["prod", "search"])) # ['user']
- print(db.list_tables(namespace_path=["prod", "recommendations"])) # ['user']
+ # Verify: listings return a response with the names and a page token
+ print(db.list_namespaces().namespaces) # ['prod']
+ print(db.list_namespaces(namespace_path=["prod"]).namespaces)
+ # ['recommendations', 'search']
+ print(db.list_tables(namespace_path=["prod", "search"]).tables) # ['user']
+ print(db.list_tables(namespace_path=["prod", "recommendations"]).tables) # ['user']
# --8<-- [end:namespace_table_ops]
+def test_namespace_table_ops(tmp_path, monkeypatch, capsys):
+ monkeypatch.chdir(tmp_path)
+ namespace_table_ops_example()
+ assert capsys.readouterr().out.splitlines() == [
+ "['prod']",
+ "['recommendations', 'search']",
+ "['user']",
+ "['user']",
+ ]
+
+
def namespace_admin_ops_example():
# --8<-- [start:namespace_admin_ops]
import lancedb
@@ -135,8 +147,8 @@ def namespace_admin_ops_example():
db.create_namespace(["prod", "search"])
child_namespaces = db.list_namespaces(namespace_path=["prod"]).namespaces
- print(f"Child namespaces under {namespace}: {child_namespaces}")
- # Child namespaces under ['prod', 'search']: ['search']
+ print(f"Child namespaces under ['prod']: {child_namespaces}")
+ # Child namespaces under ['prod']: ['search']
metadata = db.describe_namespace(["prod", "search"])
print(f"Metadata for namespace {namespace}: {metadata}")
@@ -145,7 +157,17 @@ def namespace_admin_ops_example():
db.drop_namespace(["prod", "search"], mode="skip")
db.drop_namespace(["prod"], mode="skip")
# --8<-- [end:namespace_admin_ops]
- return child_namespaces, metadata
+ return db, child_namespaces, metadata
+
+
+def test_namespace_admin_ops(tmp_path, monkeypatch, capsys):
+ monkeypatch.chdir(tmp_path)
+ db, child_namespaces, metadata = namespace_admin_ops_example()
+ assert child_namespaces == ["search"]
+ assert capsys.readouterr().out.splitlines()[0] == (
+ "Child namespaces under ['prod']: ['search']"
+ )
+ assert db.list_namespaces().namespaces == []
async def connect_object_storage_config_async():
# --8<-- [start:connect_object_storage_async]
diff --git a/docs/web/deployment/storage.mdx b/docs/web/deployment/storage.mdx
index 3455cf614..93b65ada6 100644
--- a/docs/web/deployment/storage.mdx
+++ b/docs/web/deployment/storage.mdx
@@ -189,9 +189,9 @@ Table-level `storage_options` inherit every key from the connection and override
-**Inspect the effective options**
+**Inspect a table's own options**
-On `AsyncTable`, `await table.initial_storage_options()` returns the options the table was opened with, and `await table.latest_storage_options()` returns the current options after any provider-driven refresh. The deprecated `table.storage_options()` method will be removed in a future release.
+On `AsyncTable`, `await table.initial_storage_options()` returns the options passed when the table was opened, and `await table.latest_storage_options()` returns them after any provider-driven refresh. Neither includes what the table inherits from its connection's `storage_options`, which still apply. The deprecated `table.storage_options()` method will be removed in a future release.
#### General object store options {#general-object-store-options}
diff --git a/docs/web/ingestion/create-table.mdx b/docs/web/ingestion/create-table.mdx
index c7b6d0960..b3eb3824e 100644
--- a/docs/web/ingestion/create-table.mdx
+++ b/docs/web/ingestion/create-table.mdx
@@ -352,5 +352,8 @@ Use the `drop_table()` method on the database to remove a table.
This permanently removes the table and is not recoverable, unlike deleting rows.
-By default, if the table does not exist an exception is raised. To suppress this
-in Python on a local database, you can pass in `ignore_missing=True`.
+On a local database, dropping a table that does not exist raises an error; in
+Python, pass `ignore_missing=True` to suppress it. A deployment may report
+success instead, depending on how it stores its catalog, so do not rely on the
+error to tell you a table was missing. Python's deployment connection does not
+take `ignore_missing`.
diff --git a/docs/web/management/namespace.mdx b/docs/web/management/namespace.mdx
index b9b014e9f..560499f4a 100644
--- a/docs/web/management/namespace.mdx
+++ b/docs/web/management/namespace.mdx
@@ -53,14 +53,15 @@ If you want to go deeper, see the Lance format [namespace documentation](https:/
A namespace path is a list of components. For example, `["prod", "search"]` means the `search`
namespace inside the `prod` namespace. The empty path, `[]`, means the root namespace.
-Each component is a name, not a filesystem path segment. Namespace names can't be empty, and each
-component can contain only letters, numbers, underscores, hyphens, and periods. That keeps the same
-identifier usable across local directory namespaces and REST namespace identifiers.
+Each component is a name, not a filesystem path segment, and can't be empty. Keep each one to
+letters, numbers, underscores, hyphens, and periods, so the same identifier is usable everywhere: a
+local directory namespace accepts other characters too, but other namespace implementations and
+deployments can refuse them.
## Directory namespaces {#directory-namespaces}
The simplest namespace model in LanceDB is a single root namespace, often represented by one
-directory:
+directory. Namespaces can nest inside it, as in this hierarchy:
```bash
./local_lancedb (root)
@@ -70,7 +71,12 @@ directory:
└── data (table)
```
-As a user of LanceDB OSS, you might never notice namespaces at first, because LanceDB exposes the single-level hierarchy shown above, with the data stored in the `data/` directory, where the root namespace is implicit. Connecting to this namespace is as simple as connecting to the catalog root:
+The hierarchy is logical, not a tree of folders: a directory namespace records it in a
+`__manifest` table in the root directory, and stores each table in a directory of its own there.
+
+As a user of LanceDB OSS, you might never notice namespaces at first, because a plain connection
+uses only the root namespace, which is implicit. Connecting to this namespace is as simple as
+connecting to the catalog root:
```python Python icon="python"
@@ -95,7 +101,7 @@ let db = connect("./local_lancedb").execute().await?;
```
-This creates the default namespace directory (`data/`) under the specified root path.
+A table created through this connection is stored directly under that path, as `.lance`.
You can also explicitly connect to a namespace using `lancedb.connect_namespace(...)` with the directory namespace implementation:
@@ -286,8 +292,8 @@ The SDK methods expose the namespace path in the idiom of each language:
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.
+An empty namespace (`[]`), which is the default, means the root namespace, whose tables are stored
+directly under the specified root path.
## Namespace management APIs {#namespace-management-apis}
@@ -322,12 +328,13 @@ doesn't exist, or contains data:
- 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.
+Namespace path components can't be empty, and are best kept to letters, numbers, underscores,
+hyphens, and periods, as [above](#namespace-paths-and-names).
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.
+want to list from the root namespace. In Python, a listing returns a response: the names are in its
+`namespaces` or `tables`, and `page_token` is set when there is another page.
## Namespaces in LanceDB Enterprise {#namespaces-in-lancedb-enterprise}
diff --git a/docs/web/management/table.mdx b/docs/web/management/table.mdx
index 3a4af3c5f..fda1c1cff 100644
--- a/docs/web/management/table.mdx
+++ b/docs/web/management/table.mdx
@@ -185,10 +185,16 @@ await db.tableNames();
Each accepts `namespace_path` to act inside a [namespace](/management/namespace)
-rather than at the database root. Python's `table_exists` works only on a
-namespace connection, where it takes the table's full path as a list; local
-directory and deployment connections raise `NotImplementedError`. TypeScript
-has no `tableExists`.
+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`.
## Renaming and cloning {#rename-clone}
@@ -204,8 +210,8 @@ db.clone_table(
```
Renaming is not supported on a local directory connection. On a deployment,
-whether a table can be renamed depends on how the deployment stores its
-catalog.
+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
@@ -218,7 +224,8 @@ 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
-also makes shallow clones only.
+that supports cloning also makes shallow clones only, and one whose catalog
+does not support it refuses every clone.
@@ -896,8 +903,13 @@ Remove several columns at once for efficiency:
-
-Dropping columns cannot be undone. Make sure you have backups or are certain before removing columns.
+
+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.
## Versioning {#versioning}
diff --git a/docs/web/snippets/connection.mdx b/docs/web/snippets/connection.mdx
index d359a02f2..c895f5323 100644
--- a/docs/web/snippets/connection.mdx
+++ b/docs/web/snippets/connection.mdx
@@ -10,9 +10,9 @@ export const PyConnectObjectStorage = "import lancedb\n\nuri = \"s3://your-bucke
export const PyConnectObjectStorageAsync = "import lancedb\n\nuri = \"s3://your-bucket/path\"\n# You can also use \"gs://your-bucket/path\" or \"az://your-container/path\".\nasync_db = await lancedb.connect_async(uri)\n";
-export const PyNamespaceAdminOps = "import lancedb\n\ndb = lancedb.connect_namespace(\"dir\", {\"root\": \"./local_lancedb\"})\nnamespace = [\"prod\", \"search\"]\n\ndb.create_namespace([\"prod\"])\ndb.create_namespace([\"prod\", \"search\"])\n\nchild_namespaces = db.list_namespaces(namespace_path=[\"prod\"]).namespaces\nprint(f\"Child namespaces under {namespace}: {child_namespaces}\")\n# Child namespaces under ['prod', 'search']: ['search']\n\nmetadata = db.describe_namespace([\"prod\", \"search\"])\nprint(f\"Metadata for namespace {namespace}: {metadata}\")\n# Metadata for namespace ['prod', 'search']: properties=None\n\ndb.drop_namespace([\"prod\", \"search\"], mode=\"skip\")\ndb.drop_namespace([\"prod\"], mode=\"skip\")\n";
+export const PyNamespaceAdminOps = "import lancedb\n\ndb = lancedb.connect_namespace(\"dir\", {\"root\": \"./local_lancedb\"})\nnamespace = [\"prod\", \"search\"]\n\ndb.create_namespace([\"prod\"])\ndb.create_namespace([\"prod\", \"search\"])\n\nchild_namespaces = db.list_namespaces(namespace_path=[\"prod\"]).namespaces\nprint(f\"Child namespaces under ['prod']: {child_namespaces}\")\n# Child namespaces under ['prod']: ['search']\n\nmetadata = db.describe_namespace([\"prod\", \"search\"])\nprint(f\"Metadata for namespace {namespace}: {metadata}\")\n# Metadata for namespace ['prod', 'search']: properties=None\n\ndb.drop_namespace([\"prod\", \"search\"], mode=\"skip\")\ndb.drop_namespace([\"prod\"], mode=\"skip\")\n";
-export const PyNamespaceTableOps = "import lancedb\n\ndb = lancedb.connect_namespace(\"dir\", {\"root\": \"./local_lancedb\"})\n\n# Create namespace tree: prod/search\ndb.create_namespace([\"prod\"], mode=\"exist_ok\")\ndb.create_namespace([\"prod\", \"search\"], mode=\"exist_ok\")\ndb.create_namespace([\"prod\", \"recommendations\"], mode=\"exist_ok\")\n\ndb.create_table(\n \"user\",\n data=[{\"id\": 1, \"vector\": [0.1, 0.2], \"name\": \"alice\"}],\n namespace_path=[\"prod\", \"search\"],\n mode=\"create\", # use \"overwrite\" only if you want to replace existing table\n)\n\ndb.create_table(\n \"user\",\n data=[{\"id\": 2, \"vector\": [0.3, 0.4], \"name\": \"bob\"}],\n namespace_path=[\"prod\", \"recommendations\"],\n mode=\"create\", # use \"overwrite\" only if you want to replace existing table\n)\n\n# Verify\nprint(db.list_namespaces()) # ['prod']\nprint(db.list_namespaces(namespace_path=[\"prod\"])) # ['recommendations', 'search']\nprint(db.list_tables(namespace_path=[\"prod\", \"search\"])) # ['user']\nprint(db.list_tables(namespace_path=[\"prod\", \"recommendations\"])) # ['user']\n";
+export const PyNamespaceTableOps = "import lancedb\n\ndb = lancedb.connect_namespace(\"dir\", {\"root\": \"./local_lancedb\"})\n\n# Create namespace tree: prod/search\ndb.create_namespace([\"prod\"], mode=\"exist_ok\")\ndb.create_namespace([\"prod\", \"search\"], mode=\"exist_ok\")\ndb.create_namespace([\"prod\", \"recommendations\"], mode=\"exist_ok\")\n\ndb.create_table(\n \"user\",\n data=[{\"id\": 1, \"vector\": [0.1, 0.2], \"name\": \"alice\"}],\n namespace_path=[\"prod\", \"search\"],\n mode=\"create\", # use \"overwrite\" only if you want to replace existing table\n)\n\ndb.create_table(\n \"user\",\n data=[{\"id\": 2, \"vector\": [0.3, 0.4], \"name\": \"bob\"}],\n namespace_path=[\"prod\", \"recommendations\"],\n mode=\"create\", # use \"overwrite\" only if you want to replace existing table\n)\n\n# Verify: listings return a response with the names and a page token\nprint(db.list_namespaces().namespaces) # ['prod']\nprint(db.list_namespaces(namespace_path=[\"prod\"]).namespaces)\n# ['recommendations', 'search']\nprint(db.list_tables(namespace_path=[\"prod\", \"search\"]).tables) # ['user']\nprint(db.list_tables(namespace_path=[\"prod\", \"recommendations\"]).tables) # ['user']\n";
export const TsConnect = "import * as lancedb from \"@lancedb/lancedb\";\n\nasync function connectExample(uri: string) {\n const db = await lancedb.connect(uri);\n return db;\n}\n";