Namespace Metadata Operations and Access Permissions#
LanceDB's namespace layer is a logical table-management abstraction (LanceNamespaceDatabase in Rust, LanceNamespaceDBConnection in Python) that sits above physical storage. It delegates table URI resolution, location registration, and credential vending to a LanceNamespace client, then uses standard Lance dataset machinery for actual data reads and writes.
Key operational fact: every open_table call triggers exactly one describe_table RPC to the namespace client. This is how the layer resolves a logical table name to a physical storage URI and obtains storage credentials.
open_table → describe_table call chain#
open_table(name)
└─ LanceNamespaceDatabase::open_table [namespace.rs:520-535]
└─ NativeTable::open_from_namespace [table.rs:2094]
└─ DatasetBuilder::from_namespace [table.rs:2123]
└─ describe_table RPC ← fetches location + storage options
LanceNamespaceDatabase::open_table delegates directly to NativeTable::open_from_namespace, which calls DatasetBuilder::from_namespace with the assembled table_id (namespace_path + [name]). The from_namespace builder internally calls describe_table on the namespace client to fetch the physical table location and storage options — eliminating any need for callers to pre-fetch and merge those values.
Integration tests confirm the exact call count: test_namespace_open_table_with_provider asserts exactly one describe_table call per open_table invocation , and that subsequent reads on the same table object make no additional calls . For two independently opened tables, the count is exactly two .
Credential vending#
The namespace client can optionally return storage credentials inside every describe_table response — the vend_input_storage_options feature on DirectoryNamespace . This enables automatic, namespace-managed credential rotation.
Read path (open_from_namespace): DatasetBuilder::from_namespace picks up credentials directly from the namespace response. No separate provider setup is needed.
Write path (create_from_namespace): NativeTable::create_from_namespace explicitly creates a LanceNamespaceStorageOptionsProvider and wraps it in a StorageOptionsAccessor for automatic refresh. If initial static options exist, they are merged with the dynamic provider via StorageOptionsAccessor::with_initial_and_provider; otherwise with_provider is used alone .
Credential expiry and refresh: After the first open_table, credentials are cached on the table object. Once the expiry window elapses, the next data access triggers a fresh describe_table call. test_namespace_credential_refresh_on_read verifies that exactly one additional describe_table call occurs after expiry . The same pattern holds on the write path .
The vend_input_storage_options_refresh_interval_millis property on DirectoryNamespace controls how long vended credentials are considered fresh before a re-call is triggered .
describe_table in create_table paths#
describe_table is not always called; the pattern depends on mode:
| Mode | describe_table called? | Purpose |
|---|---|---|
Create (default) | No | Uses declare_table to reserve location |
Overwrite | Yes, once | Retrieves existing location so data can be overwritten in-place |
ExistOk | Yes, once | Checks if table already exists; if found, opens it via open_from_namespace |
Create (declare conflict) | Yes, once | Disambiguates "declared but never written" vs. "fully realized" table before raising TableAlreadyExists |
Tests validate these counts explicitly: create mode emits zero describe_table calls , and overwrite mode emits exactly one .
Write permissions and read-only access#
Issue #3633 (filed 2026-07-08, v0.33.0): lancedb.connect(...).open_table(...) against hf:// (Hugging Face) buckets with a read-only token would trigger a POST .../xet-write-token request and fail with HTTP 403 — even though lance.dataset(...) succeeded with the same token.
Root cause: listing-mode connections eagerly initialized a manifest-enabled DirectoryNamespace for the storage root. The manifest initialization requested a write credential (the Xet write token) during namespace construction, regardless of whether any write operation would subsequently be performed .
Contrast with direct Lance reads: lance.dataset() bypasses the namespace layer entirely, so no write token was requested.
Fixed in Lance v9.0.0-beta.19 (LanceDB PR #3635): DirectoryNamespace read paths no longer trigger manifest writes during initialization. Root-level operations (connect, open_table, table_names) now proceed without creating __manifest artifacts or requesting write credentials, enabling side-effect-free directory namespace opens with read-only tokens.
Key source files#
| File | Role |
|---|---|
rust/lancedb/src/database/namespace.rs | LanceNamespaceDatabase — open_table, create_table, and all namespace DB logic |
rust/lancedb/src/table.rs | NativeTable::open_from_namespace (line 2094), create_from_namespace (line 2304) |
python/python/lancedb/namespace.py | LanceNamespaceDBConnection, connect_namespace, connect_namespace_async |
python/python/tests/test_namespace_integration.py | Integration tests tracking describe_table / declare_table call counts and credential refresh |