fix: satisfy the lint and docs gates for the view APIs

The refusal test's closure never returns, so its response type needed
naming; the Python binding's tuple needed a type alias; ruff and biome
wanted their formatting; and the generated JS docs needed the four new
verbs and the ViewDescription page.
This commit is contained in:
Jack Ye
2026-09-22 10:06:01 -07:00
parent aeea54ff48
commit 24628a8bb9
9 changed files with 183 additions and 17 deletions
+95
View File
@@ -319,6 +319,38 @@ Creates a new Table and initialize it with new data.
***
### createView()
```ts
abstract createView(
name,
query,
namespacePath?): Promise<ViewDescription>
```
Create a view: a named query the database plans on every read.
The query is planned once, at creation, so one that cannot be planned is
rejected now rather than at the first read. A view holds no rows, and its
readers see its sources as they are at read time.
There is no replace: a name already taken is an error, and changing a
view is a drop followed by a create.
#### Parameters
* **name**: `string`
* **query**: `string`
* **namespacePath?**: `string`[]
#### Returns
`Promise`&lt;[`ViewDescription`](../interfaces/ViewDescription.md)&gt;
***
### describeNamespace()
```ts
@@ -342,6 +374,27 @@ The namespace's properties
***
### describeView()
```ts
abstract describeView(name, namespacePath?): Promise<ViewDescription>
```
What this database records about the view named `name`: its defining
query and the schema that query resolved to.
#### Parameters
* **name**: `string`
* **namespacePath?**: `string`[]
#### Returns
`Promise`&lt;[`ViewDescription`](../interfaces/ViewDescription.md)&gt;
***
### display()
```ts
@@ -498,6 +551,28 @@ on the returned job to know when cleanup has finished.
***
### dropView()
```ts
abstract dropView(name, namespacePath?): Promise<void>
```
Drop the view named `name`.
The tables it reads are untouched: a view holds no rows of its own.
#### Parameters
* **name**: `string`
* **namespacePath?**: `string`[]
#### Returns
`Promise`&lt;`void`&gt;
***
### isOpen()
```ts
@@ -636,6 +711,26 @@ A page of table names and an
***
### listViews()
```ts
abstract listViews(namespacePath?): Promise<string[]>
```
The names of the views in one namespace.
Names only; a definition comes from [describeView](Connection.md#describeview).
#### Parameters
* **namespacePath?**: `string`[]
#### Returns
`Promise`&lt;`string`[]&gt;
***
### openJob()
```ts
+1
View File
@@ -149,6 +149,7 @@
- [UpdateOptions](interfaces/UpdateOptions.md)
- [UpdateResult](interfaces/UpdateResult.md)
- [Version](interfaces/Version.md)
- [ViewDescription](interfaces/ViewDescription.md)
- [WriteExecutionOptions](interfaces/WriteExecutionOptions.md)
- [WriteProgress](interfaces/WriteProgress.md)
+63
View File
@@ -0,0 +1,63 @@
[**@lancedb/lancedb**](../README.md) • **Docs**
***
[@lancedb/lancedb](../globals.md) / ViewDescription
# Interface: ViewDescription
What a database records about one view.
A view holds no rows: it stores the statement that defines it and the
schema that statement resolved to, so a reader sees its sources as they
are at read time.
## Properties
### defaultDatabase
```ts
defaultDatabase: string;
```
The database that unqualified table names in [query](ViewDescription.md#query) resolve against.
***
### name
```ts
name: string;
```
The view's name within its namespace.
***
### namespacePath
```ts
namespacePath: string[];
```
The namespace holding the view; empty is the root namespace.
***
### query
```ts
query: string;
```
The defining query, as the database stores it.
***
### schema
```ts
schema: Schema<any>;
```
The schema the defining query resolved to when the view was created.
+7 -6
View File
@@ -110,19 +110,20 @@ describe("remote connection", () => {
name: "adults",
namespace: ["analytics"],
query: "SELECT name FROM people",
// biome-ignore lint/style/useNamingConvention: the wire field is snake_case
default_database: "db",
schema: {
fields: [{ name: "name", nullable: true, type: { type: "utf8" } }],
fields: [
{ name: "name", nullable: true, type: { type: "utf8" } },
],
},
}),
);
},
async (db) => {
const view = await db.createView(
"adults",
"SELECT name FROM people",
["analytics"],
);
const view = await db.createView("adults", "SELECT name FROM people", [
"analytics",
]);
expect(view.name).toBe("adults");
expect(view.namespacePath).toEqual(["analytics"]);
expect(view.query).toBe("SELECT name FROM people");
+1 -1
View File
@@ -23,7 +23,6 @@ import {
validateNonNegativeInteger,
} from "./materialized_view";
import { Connection as LanceDbConnection } from "./native";
import { ViewDescription, viewDescriptionFromNative } from "./view";
import type {
CreateNamespaceResponse,
DescribeNamespaceResponse,
@@ -32,6 +31,7 @@ import type {
ListNamespacesResponse,
ListTablesResponse,
} from "./native";
import { ViewDescription, viewDescriptionFromNative } from "./view";
export type {
CreateNamespaceResponse,
DescribeNamespaceResponse,
+7 -5
View File
@@ -958,7 +958,9 @@ class DBConnection(EnforceOverrides):
"View operations are not supported for this connection type"
)
def drop_view(self, name: str, *, namespace_path: Optional[List[str]] = None) -> None:
def drop_view(
self, name: str, *, namespace_path: Optional[List[str]] = None
) -> None:
"""Drop a view.
The tables it reads are untouched: a view holds no rows of its own.
@@ -1802,7 +1804,9 @@ class LanceDBConnection(DBConnection):
return LOOP.run(self._conn.describe_view(name, namespace_path=namespace_path))
@override
def drop_view(self, name: str, *, namespace_path: Optional[List[str]] = None) -> None:
def drop_view(
self, name: str, *, namespace_path: Optional[List[str]] = None
) -> None:
LOOP.run(self._conn.drop_view(name, namespace_path=namespace_path))
@override
@@ -2764,9 +2768,7 @@ class AsyncConnection(object):
[DBConnection.create_view][lancedb.DBConnection.create_view].
"""
return _view_description(
await self._inner.create_view(
name, query, list(namespace_path or [])
)
await self._inner.create_view(name, query, list(namespace_path or []))
)
async def describe_view(
+3 -1
View File
@@ -920,7 +920,9 @@ class RemoteDBConnection(DBConnection):
return LOOP.run(self._conn.describe_view(name, namespace_path=namespace_path))
@override
def drop_view(self, name: str, *, namespace_path: Optional[List[str]] = None) -> None:
def drop_view(
self, name: str, *, namespace_path: Optional[List[str]] = None
) -> None:
LOOP.run(self._conn.drop_view(name, namespace_path=namespace_path))
@override
+5 -3
View File
@@ -104,13 +104,15 @@ fn parse_default_namespace_path(path: Option<Bound<'_, PyAny>>) -> PyResult<Vec<
}
}
/// A view description on its way to Python: name, namespace, query, default
/// database, and the schema as pyarrow renders it.
type PyViewDescription = (String, Vec<String>, String, String, Py<PyAny>);
/// A view description as a plain tuple, with the schema converted to the
/// pyarrow schema the caller would get from any other lancedb API. The Python
/// layer names the fields; this keeps the binding free of a class that would
/// have to be kept in step with the Rust struct.
fn view_description_to_py(
view: lancedb::view::ViewDescription,
) -> PyResult<(String, Vec<String>, String, String, Py<PyAny>)> {
fn view_description_to_py(view: lancedb::view::ViewDescription) -> PyResult<PyViewDescription> {
Python::attach(|py| {
let schema = view.schema.to_pyarrow(py)?.unbind();
Ok((
+1 -1
View File
@@ -3980,7 +3980,7 @@ mod tests {
/// view, so it is refused before it reaches a route.
#[tokio::test]
async fn test_view_names_that_would_resplit_are_refused() {
let conn = Connection::new_with_handler(|_| {
let conn = Connection::new_with_handler(|_| -> http::Response<String> {
panic!("an invalid identifier must not reach the service")
});
for (name, namespace) in [