From e26fbd3dcc116d155857921fff1bb92b819e1f68 Mon Sep 17 00:00:00 2001 From: Wyatt Alt Date: Thu, 13 Aug 2026 23:42:37 -0700 Subject: [PATCH] fix: reject build-dependent functions the volatility label misses DataFusion marks version() immutable, but it returns the build string: identical-across-refreshes has to hold across upgrades too, and an incremental view would mix values from different builds. The immutability guard now also excludes known build-dependent functions by name. Co-Authored-By: Claude Fable 5 --- rust/lancedb/src/materialized_view.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rust/lancedb/src/materialized_view.rs b/rust/lancedb/src/materialized_view.rs index 83c8454af..977c98ea4 100644 --- a/rust/lancedb/src/materialized_view.rs +++ b/rust/lancedb/src/materialized_view.rs @@ -285,11 +285,18 @@ fn ensure_immutable(expr: &datafusion_expr::Expr, error: impl Fn(String) -> Erro use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion}; use datafusion_expr::Volatility; + // Labeled immutable but actually build-dependent: version() returns the + // DataFusion build string, so its value changes across upgrades. + const BUILD_DEPENDENT: &[&str] = &["version"]; + let mut offending: Option = None; expr.apply(|node| { if let datafusion_expr::Expr::ScalarFunction(function) = node { - if function.func.signature().volatility != Volatility::Immutable { - offending = Some(function.func.name().to_string()); + let name = function.func.name(); + if function.func.signature().volatility != Volatility::Immutable + || BUILD_DEPENDENT.contains(&name) + { + offending = Some(name.to_string()); return Ok(TreeNodeRecursion::Stop); } } @@ -1014,7 +1021,7 @@ mod tests { #[tokio::test] async fn test_volatile_and_unstable_expressions_are_rejected() { let conn = people_db().await; - for expression in ["random()", "now()"] { + for expression in ["random()", "now()", "version()"] { let err = conn .create_materialized_view("bad", "people") .select([("x", expression)])