From 2b701d3ade1146d2aa3f16ee1725da00135ccc46 Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Fri, 2 Oct 2026 10:14:32 -0500 Subject: [PATCH] refactor: store MV resolution defaults in definition --- docs/src/js/classes/Connection.md | 4 -- nodejs/lancedb/connection.ts | 4 -- rust/lancedb/src/materialized_view.rs | 75 +++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/docs/src/js/classes/Connection.md b/docs/src/js/classes/Connection.md index f1aace87b..4bb769a0e 100644 --- a/docs/src/js/classes/Connection.md +++ b/docs/src/js/classes/Connection.md @@ -183,10 +183,6 @@ Define a materialized view named `name` over the table `source`. The view is populated before creation returns. Set `withNoData` to create only its definition and empty backing table. The view is a normal table: it can be queried, indexed and searched, and it appears in `tableNames`. -The source table must have stable row ids (create it with -the `newTableEnableStableRowIds` storage option); they keep the view's -provenance valid across source compactions and cannot be enabled after -a table exists. #### Parameters diff --git a/nodejs/lancedb/connection.ts b/nodejs/lancedb/connection.ts index 5e9641202..32feaf02c 100644 --- a/nodejs/lancedb/connection.ts +++ b/nodejs/lancedb/connection.ts @@ -324,10 +324,6 @@ export abstract class Connection { * The view is populated before creation returns. Set `withNoData` to create * only its definition and empty backing table. The view is a normal table: * it can be queried, indexed and searched, and it appears in `tableNames`. - * The source table must have stable row ids (create it with - * the `newTableEnableStableRowIds` storage option); they keep the view's - * provenance valid across source compactions and cannot be enabled after - * a table exists. */ abstract createMaterializedView( name: string, diff --git a/rust/lancedb/src/materialized_view.rs b/rust/lancedb/src/materialized_view.rs index bd77b3340..c96b67ca7 100644 --- a/rust/lancedb/src/materialized_view.rs +++ b/rust/lancedb/src/materialized_view.rs @@ -326,6 +326,23 @@ pub fn definition_metadata_from_sql(sql: &str) -> String { definition_metadata_from_sql_with_format(sql, 1) } +/// Serialize an arbitrary SQL definition together with the resolution +/// defaults needed to plan unqualified identifiers on a later refresh. +pub fn definition_metadata_from_sql_with_defaults( + sql: &str, + default_database: &str, + default_namespace_path: &[String], +) -> String { + serde_json::json!({ + "kind": "query", + "format": 1, + "query": sql, + "default_database": default_database, + "default_namespace_path": default_namespace_path, + }) + .to_string() +} + fn definition_metadata_from_sql_with_format(sql: &str, format: u64) -> String { serde_json::json!({ "kind": "query", @@ -338,20 +355,36 @@ fn definition_metadata_from_sql_with_format(sql: &str, format: u64) -> String { /// Return the defining SQL stored in [`DEFINITION_META_KEY`], without /// requiring the local refresh parser to understand it. pub fn read_definition_sql(metadata: &HashMap) -> Result> { + Ok(read_definition_sql_with_defaults(metadata)?.map(|definition| definition.query)) +} + +/// A stored SQL definition and the optional name-resolution defaults recorded +/// by a remote SQL engine. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct MaterializedViewSqlDefinition { + /// The SQL query that defines the materialized view. + pub query: String, + /// The database used to resolve unqualified references when the view was created. + #[serde(default)] + pub default_database: Option, + /// The namespace used to resolve unqualified references when the view was created. + #[serde(default)] + pub default_namespace_path: Option>, +} + +/// Read the SQL definition and its optional resolution defaults from +/// [`DEFINITION_META_KEY`]. +pub fn read_definition_sql_with_defaults( + metadata: &HashMap, +) -> Result> { let Some(raw) = metadata.get(DEFINITION_META_KEY) else { return Ok(None); }; - let value: serde_json::Value = serde_json::from_str(raw).map_err(|e| Error::Runtime { - message: format!("unreadable materialized view definition: {e}"), - })?; - value - .get("query") - .and_then(|query| query.as_str()) - .map(ToOwned::to_owned) - .ok_or_else(|| Error::Runtime { - message: "unreadable materialized view definition: missing query".into(), - }) + serde_json::from_str(raw) .map(Some) + .map_err(|e| Error::Runtime { + message: format!("unreadable materialized view definition: {e}"), + }) } /// Read a view definition off a schema metadata map, if it carries one. @@ -1837,6 +1870,28 @@ mod tests { ); } + #[test] + fn sql_definition_defaults_share_one_metadata_value() { + let metadata = HashMap::from([( + DEFINITION_META_KEY.to_string(), + definition_metadata_from_sql_with_defaults( + "SELECT * FROM source", + "db", + &["analytics".to_string()], + ), + )]); + + let definition = read_definition_sql_with_defaults(&metadata) + .unwrap() + .unwrap(); + assert_eq!(definition.query, "SELECT * FROM source"); + assert_eq!(definition.default_database.as_deref(), Some("db")); + assert_eq!( + definition.default_namespace_path, + Some(vec!["analytics".to_string()]) + ); + } + #[tokio::test] async fn refresh_always_rebuilds_without_source_row_ids() { let conn = connect("memory://").execute().await.unwrap();