mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
feat: record the namespace a view's unqualified names resolve against
A view outlives the session that declared it, so the namespace path its query resolved against travels with it, beside the database. Without it a reader would resolve an unqualified name against its own default and read a different table. `ViewDescription` carries `default_namespace_path`; it crosses the wire as `default_namespace`, a path like `namespace`, absent for the root.
This commit is contained in:
Generated
+3
-3
@@ -5756,7 +5756,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lancedb"
|
||||
version = "0.40.0-beta.4"
|
||||
version = "0.40.0-beta.5"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -5853,7 +5853,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lancedb-nodejs"
|
||||
version = "0.40.0-beta.4"
|
||||
version = "0.40.0-beta.5"
|
||||
dependencies = [
|
||||
"arrow-array",
|
||||
"arrow-buffer",
|
||||
@@ -5878,7 +5878,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lancedb-python"
|
||||
version = "0.40.0-beta.4"
|
||||
version = "0.40.0-beta.5"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"arrow",
|
||||
|
||||
@@ -24,6 +24,21 @@ The database that unqualified table names in [query](ViewDescription.md#query) r
|
||||
|
||||
***
|
||||
|
||||
### defaultNamespacePath
|
||||
|
||||
```ts
|
||||
defaultNamespacePath: string[];
|
||||
```
|
||||
|
||||
The namespace path those unqualified names resolve against; empty is the
|
||||
root namespace.
|
||||
|
||||
Recorded with the view because it outlives the session that declared it:
|
||||
a reader resolving the query against its own default namespace could read
|
||||
a different table than the view was defined over.
|
||||
|
||||
***
|
||||
|
||||
### name
|
||||
|
||||
```ts
|
||||
|
||||
@@ -112,6 +112,8 @@ describe("remote connection", () => {
|
||||
query: "SELECT name FROM people",
|
||||
// biome-ignore lint/style/useNamingConvention: the wire field is snake_case
|
||||
default_database: "db",
|
||||
// biome-ignore lint/style/useNamingConvention: the wire field is snake_case
|
||||
default_namespace: ["analytics"],
|
||||
schema: {
|
||||
fields: [
|
||||
{ name: "name", nullable: true, type: { type: "utf8" } },
|
||||
@@ -128,6 +130,7 @@ describe("remote connection", () => {
|
||||
expect(view.namespacePath).toEqual(["analytics"]);
|
||||
expect(view.query).toBe("SELECT name FROM people");
|
||||
expect(view.defaultDatabase).toBe("db");
|
||||
expect(view.defaultNamespacePath).toEqual(["analytics"]);
|
||||
expect(view.schema.fields.map((f) => f.name)).toEqual(["name"]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,6 +20,15 @@ export interface ViewDescription {
|
||||
query: string;
|
||||
/** The database that unqualified table names in {@link query} resolve against. */
|
||||
defaultDatabase: string;
|
||||
/**
|
||||
* The namespace path those unqualified names resolve against; empty is the
|
||||
* root namespace.
|
||||
*
|
||||
* Recorded with the view because it outlives the session that declared it:
|
||||
* a reader resolving the query against its own default namespace could read
|
||||
* a different table than the view was defined over.
|
||||
*/
|
||||
defaultNamespacePath: string[];
|
||||
/** The schema the defining query resolved to when the view was created. */
|
||||
schema: Schema;
|
||||
}
|
||||
@@ -33,6 +42,7 @@ export function viewDescriptionFromNative(
|
||||
namespacePath: view.namespacePath,
|
||||
query: view.query,
|
||||
defaultDatabase: view.defaultDatabase,
|
||||
defaultNamespacePath: view.defaultNamespacePath,
|
||||
schema: tableFromIPC(view.schema).schema,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ pub struct ViewDescription {
|
||||
pub namespace_path: Vec<String>,
|
||||
pub query: String,
|
||||
pub default_database: String,
|
||||
pub default_namespace_path: Vec<String>,
|
||||
/// The view's schema as an empty Arrow IPC file, the way a table reports
|
||||
/// its own.
|
||||
pub schema: Buffer,
|
||||
@@ -74,6 +75,7 @@ impl ViewDescription {
|
||||
namespace_path: view.namespace_path,
|
||||
query: view.query,
|
||||
default_database: view.default_database,
|
||||
default_namespace_path: view.default_namespace_path,
|
||||
schema: crate::util::schema_to_buffer(&view.schema)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -170,10 +170,10 @@ class Connection(object):
|
||||
) -> Tuple[str, int, int]: ...
|
||||
async def create_view(
|
||||
self, name: str, query: str, namespace_path: Optional[List[str]] = None
|
||||
) -> Tuple[str, List[str], str, str, pa.Schema]: ...
|
||||
) -> Tuple[str, List[str], str, str, List[str], pa.Schema]: ...
|
||||
async def describe_view(
|
||||
self, name: str, namespace_path: Optional[List[str]] = None
|
||||
) -> Tuple[str, List[str], str, str, pa.Schema]: ...
|
||||
) -> Tuple[str, List[str], str, str, List[str], pa.Schema]: ...
|
||||
async def drop_view(
|
||||
self, name: str, namespace_path: Optional[List[str]] = None
|
||||
) -> None: ...
|
||||
|
||||
@@ -98,16 +98,24 @@ from .namespace_utils import (
|
||||
|
||||
|
||||
def _view_description(
|
||||
described: Tuple[str, List[str], str, str, "pa.Schema"],
|
||||
described: Tuple[str, List[str], str, str, List[str], "pa.Schema"],
|
||||
) -> ViewDescription:
|
||||
"""Name the fields the binding returns positionally."""
|
||||
name, namespace_path, query, default_database, schema = described
|
||||
(
|
||||
name,
|
||||
namespace_path,
|
||||
query,
|
||||
default_database,
|
||||
default_namespace_path,
|
||||
schema,
|
||||
) = described
|
||||
return ViewDescription(
|
||||
name=name,
|
||||
query=query,
|
||||
default_database=default_database,
|
||||
schema=schema,
|
||||
namespace_path=namespace_path,
|
||||
default_namespace_path=default_namespace_path,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -31,3 +31,10 @@ class ViewDescription:
|
||||
"""The schema the defining query resolved to when the view was created."""
|
||||
namespace_path: List[str] = field(default_factory=list)
|
||||
"""The namespace holding the view; empty is the root namespace."""
|
||||
default_namespace_path: List[str] = field(default_factory=list)
|
||||
"""The namespace path those unqualified names resolve against.
|
||||
|
||||
Recorded with the view because it outlives the session that declared it: a
|
||||
reader resolving the query against its own default namespace could read a
|
||||
different table than the view was defined over.
|
||||
"""
|
||||
|
||||
@@ -2766,6 +2766,7 @@ def test_view_crud_addresses_its_own_routes():
|
||||
"namespace": ["analytics"],
|
||||
"query": "SELECT name FROM people",
|
||||
"default_database": "dev",
|
||||
"default_namespace": ["analytics"],
|
||||
"schema": {
|
||||
"fields": [
|
||||
{"name": "name", "nullable": True, "type": {"type": "utf8"}}
|
||||
@@ -2785,6 +2786,7 @@ def test_view_crud_addresses_its_own_routes():
|
||||
assert view.namespace_path == ["analytics"]
|
||||
assert view.query == "SELECT name FROM people"
|
||||
assert view.default_database == "dev"
|
||||
assert view.default_namespace_path == ["analytics"]
|
||||
assert view.schema == pa.schema([pa.field("name", pa.utf8(), nullable=True)])
|
||||
|
||||
described = db.describe_view("adults", namespace_path=["analytics"])
|
||||
|
||||
@@ -106,7 +106,7 @@ 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>);
|
||||
type PyViewDescription = (String, Vec<String>, String, String, Vec<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
|
||||
@@ -120,6 +120,7 @@ fn view_description_to_py(view: lancedb::view::ViewDescription) -> PyResult<PyVi
|
||||
view.namespace_path,
|
||||
view.query,
|
||||
view.default_database,
|
||||
view.default_namespace_path,
|
||||
schema,
|
||||
))
|
||||
})
|
||||
|
||||
@@ -801,6 +801,10 @@ struct RemoteViewDescription {
|
||||
namespace: Vec<String>,
|
||||
query: String,
|
||||
default_database: String,
|
||||
/// A path, like `namespace`: the root is the absent field rather than a
|
||||
/// spelling of its own.
|
||||
#[serde(default)]
|
||||
default_namespace: Vec<String>,
|
||||
schema: JsonArrowSchema,
|
||||
}
|
||||
|
||||
@@ -820,6 +824,7 @@ impl RemoteViewDescription {
|
||||
namespace_path: self.namespace,
|
||||
query: self.query,
|
||||
default_database: self.default_database,
|
||||
default_namespace_path: self.default_namespace,
|
||||
schema: Arc::new(schema),
|
||||
})
|
||||
}
|
||||
@@ -3853,6 +3858,7 @@ mod tests {
|
||||
"namespace": namespace,
|
||||
"query": query,
|
||||
"default_database": "db",
|
||||
"default_namespace": namespace,
|
||||
"schema": lance_namespace::schema::arrow_schema_to_json(&schema).unwrap(),
|
||||
})
|
||||
.to_string()
|
||||
@@ -3885,6 +3891,7 @@ mod tests {
|
||||
assert_eq!(view.namespace_path, vec!["analytics".to_string()]);
|
||||
assert_eq!(view.query, "SELECT id FROM people");
|
||||
assert_eq!(view.default_database, "db");
|
||||
assert_eq!(view.default_namespace_path, vec!["analytics".to_string()]);
|
||||
assert_eq!(view.schema.field(0).name(), "id");
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,13 @@ pub struct ViewDescription {
|
||||
/// other than its own, so this is part of what the query means rather
|
||||
/// than a restatement of where the view lives.
|
||||
pub default_database: String,
|
||||
/// The namespace path those unqualified names resolve against; empty is
|
||||
/// the root namespace.
|
||||
///
|
||||
/// Recorded with the view because it outlives the session that declared
|
||||
/// it: a reader that resolved the query against its own default namespace
|
||||
/// could read a different table than the view was defined over.
|
||||
pub default_namespace_path: Vec<String>,
|
||||
/// The schema the defining query resolved to when the view was created.
|
||||
pub schema: SchemaRef,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user