From 8a353d85752ee4b01a7cc95ed4a5bf8c1ffda228 Mon Sep 17 00:00:00 2001 From: ila Date: Tue, 6 Oct 2026 01:55:36 +0200 Subject: [PATCH 1/2] Missing DISTINCT/SEMI_ANTI aux metadata silently falls through to GROUP_RECOMPUTE --- CLAUDE.md | 2 +- src/upsert/refresh_sql.cpp | 10 +-- test/sql/missing_aux_meta_refresh_error.test | 72 ++++++++++++++++++++ 3 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 test/sql/missing_aux_meta_refresh_error.test diff --git a/CLAUDE.md b/CLAUDE.md index 8fb38d33..c246d5b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,7 +105,7 @@ Three compilation paths based on view type (`RefreshType`): | `SIMPLE_PROJECTION` | `CompileProjectionsFilters()` | Counting-based consolidation (GROUP BY + generate_series/rowid for bag semantics) | | `WINDOW_PARTITION` | `CompileWindowRecompute()` or DuckLake snapshot diff | Delete/reinsert only affected partitions | | `GROUP_RECOMPUTE` | `CompileGroupRecompute()` | Delete/reinsert only affected groups; used for inner DISTINCT under aggregate and other non-linear group cases | -| `DISTINCT_INCREMENTAL` | `CompileDistinctIncremental()` | Aux-state path for single-source inner DISTINCT under one SUM when `openivm_distinct_aux_state=true`; falls back to group-recompute if metadata is missing | +| `DISTINCT_INCREMENTAL` | `CompileDistinctIncremental()` | Aux-state path for single-source inner DISTINCT under one SUM when `openivm_distinct_aux_state=true`; refresh throws if aux metadata is missing (no silent fallback) | | `FULL_REFRESH` | `BuildRecomputeQuery()` | DELETE all rows from `openivm_data_`, INSERT full query, then cleanup deltas | MIN/MAX and ARG_MIN/ARG_MAX usually force group-recompute unless insert-only fast paths are enabled and safe. AVG is decomposed to SUM+COUNT; STDDEV/VARIANCE are decomposed to SUM, COUNT, and sum-of-squares helper columns. diff --git a/src/upsert/refresh_sql.cpp b/src/upsert/refresh_sql.cpp index 90b5336b..8d7aeed5 100644 --- a/src/upsert/refresh_sql.cpp +++ b/src/upsert/refresh_sql.cpp @@ -1207,10 +1207,7 @@ string GenerateRefreshSQL(ClientContext &context, const string &view_catalog_nam aux_meta.sum_out.c_str()); break; } - OPENIVM_DEBUG_PRINT("[UPSERT] DISTINCT_INCREMENTAL view has no aux meta — " - "falling through to " - "GROUP_RECOMPUTE\n"); - [[fallthrough]]; + throw InternalException("DISTINCT_INCREMENTAL view '%s' has no aux metadata", view_name); } case RefreshType::SEMI_ANTI_RECOMPUTE: { RefreshMetadata::SemiAntiAuxMeta aux_meta; @@ -1241,10 +1238,7 @@ string GenerateRefreshSQL(ClientContext &context, const string &view_catalog_nam aux_meta.join_type.c_str(), aux_meta.left_cols.size()); break; } - OPENIVM_DEBUG_PRINT("[UPSERT] SEMI_ANTI_RECOMPUTE view has no aux meta — " - "falling through to " - "GROUP_RECOMPUTE\n"); - [[fallthrough]]; + throw InternalException("SEMI_ANTI_RECOMPUTE view '%s' has no aux metadata", view_name); } case RefreshType::GROUP_RECOMPUTE: { auto group_columns = metadata.GetGroupColumns(view_name); diff --git a/test/sql/missing_aux_meta_refresh_error.test b/test/sql/missing_aux_meta_refresh_error.test new file mode 100644 index 00000000..ab4f8a1f --- /dev/null +++ b/test/sql/missing_aux_meta_refresh_error.test @@ -0,0 +1,72 @@ +# name: test/sql/missing_aux_meta_refresh_error.test +# description: Refresh of aux-state views with lost aux metadata fails instead of silently falling back to GROUP_RECOMPUTE +# group: [sql] + +require openivm + +statement ok +CREATE TABLE mam_l (id INT, x INT); + +statement ok +CREATE TABLE mam_r (rid INT, y INT); + +statement ok +INSERT INTO mam_l VALUES (1, 10), (2, 20), (3, 30); + +statement ok +INSERT INTO mam_r VALUES (100, 9); + +statement ok +CREATE MATERIALIZED VIEW mam_semi AS + SELECT l.id, l.x FROM mam_l l SEMI JOIN mam_r r ON abs(l.x - r.y) <= 2; + +query I +SELECT type FROM openivm_views WHERE type = 9; +---- +9 + +statement ok +UPDATE openivm_views SET semi_anti_aux_meta_json = NULL WHERE type = 9; + +statement ok +INSERT INTO mam_r VALUES (101, 21); + +statement error +PRAGMA refresh('mam_semi'); +---- +has no aux metadata + +# DISTINCT_INCREMENTAL: same contract. +statement ok +SET openivm_distinct_aux_state = true; + +statement ok +CREATE TABLE mam_src (grp INT, machine VARCHAR, cores INT); + +statement ok +INSERT INTO mam_src VALUES (1, 'm1', 8), (1, 'm1', 8), (1, 'm2', 16), (2, 'm3', 4); + +statement ok +CREATE MATERIALIZED VIEW mam_distinct AS + SELECT grp, SUM(cores) AS total_cores + FROM (SELECT DISTINCT grp, machine, cores FROM mam_src) d + GROUP BY grp; + +query I +SELECT type FROM openivm_views WHERE type = 8; +---- +8 + +statement ok +UPDATE openivm_views SET distinct_aux_meta_json = NULL WHERE type = 8; + +statement ok +INSERT INTO mam_src VALUES (2, 'm4', 5); + +statement error +PRAGMA refresh('mam_distinct'); +---- +has no aux metadata + +statement ok +SET openivm_distinct_aux_state = false; From 263a5ce1f625211a0671206a91abf94a6ef2d6a6 Mon Sep 17 00:00:00 2001 From: ila Date: Tue, 6 Oct 2026 14:31:29 +0200 Subject: [PATCH 2/2] Missing DISTINCT/SEMI_ANTI aux metadata silently falls through to GROUP_RECOMPUTE --- src/upsert/refresh_sql.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/upsert/refresh_sql.cpp b/src/upsert/refresh_sql.cpp index 8d7aeed5..275fbc99 100644 --- a/src/upsert/refresh_sql.cpp +++ b/src/upsert/refresh_sql.cpp @@ -1155,7 +1155,7 @@ string GenerateRefreshSQL(ClientContext &context, const string &view_catalog_nam case RefreshType::COUNT_DISTINCT_INCREMENTAL: { RefreshMetadata::CountDistinctAuxMeta aux_meta; if (!metadata.GetCountDistinctAuxMeta(view_name, aux_meta)) { - throw InternalException("COUNT_DISTINCT_INCREMENTAL view '%s' has no aux metadata", view_name); + throw InvalidInputException("COUNT_DISTINCT_INCREMENTAL view '%s' has no aux metadata", view_name); } else { if (!active_facts.compile_only) { EnsureCountDistinctAuxState(metadata, con, view_name, aux_meta, delta_table_names, @@ -1207,7 +1207,7 @@ string GenerateRefreshSQL(ClientContext &context, const string &view_catalog_nam aux_meta.sum_out.c_str()); break; } - throw InternalException("DISTINCT_INCREMENTAL view '%s' has no aux metadata", view_name); + throw InvalidInputException("DISTINCT_INCREMENTAL view '%s' has no aux metadata", view_name); } case RefreshType::SEMI_ANTI_RECOMPUTE: { RefreshMetadata::SemiAntiAuxMeta aux_meta; @@ -1238,7 +1238,7 @@ string GenerateRefreshSQL(ClientContext &context, const string &view_catalog_nam aux_meta.join_type.c_str(), aux_meta.left_cols.size()); break; } - throw InternalException("SEMI_ANTI_RECOMPUTE view '%s' has no aux metadata", view_name); + throw InvalidInputException("SEMI_ANTI_RECOMPUTE view '%s' has no aux metadata", view_name); } case RefreshType::GROUP_RECOMPUTE: { auto group_columns = metadata.GetGroupColumns(view_name);