diff --git a/Cargo.lock b/Cargo.lock index bb5f388cd..9905e8b86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/docs/src/js/interfaces/ViewDescription.md b/docs/src/js/interfaces/ViewDescription.md index 346e29ecc..d53c884b5 100644 --- a/docs/src/js/interfaces/ViewDescription.md +++ b/docs/src/js/interfaces/ViewDescription.md @@ -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 diff --git a/nodejs/__test__/remote.test.ts b/nodejs/__test__/remote.test.ts index cf28e29b4..4c1d21604 100644 --- a/nodejs/__test__/remote.test.ts +++ b/nodejs/__test__/remote.test.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"]); }, ); diff --git a/nodejs/lancedb/view.ts b/nodejs/lancedb/view.ts index e58576955..5ed848648 100644 --- a/nodejs/lancedb/view.ts +++ b/nodejs/lancedb/view.ts @@ -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, }; } diff --git a/nodejs/src/connection.rs b/nodejs/src/connection.rs index 736560f44..ee2a9470b 100644 --- a/nodejs/src/connection.rs +++ b/nodejs/src/connection.rs @@ -62,6 +62,7 @@ pub struct ViewDescription { pub namespace_path: Vec, pub query: String, pub default_database: String, + pub default_namespace_path: Vec, /// 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)?, }) } diff --git a/python/python/lancedb/_lancedb.pyi b/python/python/lancedb/_lancedb.pyi index 0fbc33dab..b3aeb924f 100644 --- a/python/python/lancedb/_lancedb.pyi +++ b/python/python/lancedb/_lancedb.pyi @@ -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: ... diff --git a/python/python/lancedb/db.py b/python/python/lancedb/db.py index cd5eaa324..ecccbc174 100644 --- a/python/python/lancedb/db.py +++ b/python/python/lancedb/db.py @@ -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, ) diff --git a/python/python/lancedb/view.py b/python/python/lancedb/view.py index e04110fc4..715252fc6 100644 --- a/python/python/lancedb/view.py +++ b/python/python/lancedb/view.py @@ -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. + """ diff --git a/python/python/tests/test_remote_db.py b/python/python/tests/test_remote_db.py index 92cbc756a..c03862a49 100644 --- a/python/python/tests/test_remote_db.py +++ b/python/python/tests/test_remote_db.py @@ -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"]) diff --git a/python/src/connection.rs b/python/src/connection.rs index 147d81dae..217cddb2e 100644 --- a/python/src/connection.rs +++ b/python/src/connection.rs @@ -106,7 +106,7 @@ fn parse_default_namespace_path(path: Option>) -> PyResult, String, String, Py); +type PyViewDescription = (String, Vec, String, String, Vec, Py); /// 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, 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, 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"); } diff --git a/rust/lancedb/src/view.rs b/rust/lancedb/src/view.rs index 795b70f79..ca9703209 100644 --- a/rust/lancedb/src/view.rs +++ b/rust/lancedb/src/view.rs @@ -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, /// The schema the defining query resolved to when the view was created. pub schema: SchemaRef, }