refactor: store MV resolution defaults in definition

This commit is contained in:
Jack Ye
2026-10-02 11:47:41 -05:00
parent 2e997be6ff
commit 2b701d3ade
3 changed files with 65 additions and 18 deletions
-4
View File
@@ -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
-4
View File
@@ -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,
+65 -10
View File
@@ -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<String, String>) -> Result<Option<String>> {
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<String>,
/// The namespace used to resolve unqualified references when the view was created.
#[serde(default)]
pub default_namespace_path: Option<Vec<String>>,
}
/// Read the SQL definition and its optional resolution defaults from
/// [`DEFINITION_META_KEY`].
pub fn read_definition_sql_with_defaults(
metadata: &HashMap<String, String>,
) -> Result<Option<MaterializedViewSqlDefinition>> {
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();