Skip to content

Conversation

@piotrrzysko
Copy link
Member

Description

This PR introduces a new optional clause for CREATE MATERIALIZED VIEW that allows users to control the behavior of materialized views when they are stale.

I will updated the documentation once the proposed syntax and behavior are approved.

New syntax

CREATE MATERIALIZED VIEW mv_name
WHEN STALE { FAIL | INLINE }
AS SELECT ...

Behavior

  • FAIL - If the MV is stale, queries referencing it will fail. Analysis of the underlying query is never performed while querying the MV.
  • INLINE (default) - Preserves the current behavior. Even if the MV is stale, it is expanded like a logical view, and the underlying query is analyzed.

Motivation

  • Avoid analyzing the underlying query every time, which can be costly when it references many tables.
  • Allow using fresh MVs even when some data sources are temporarily unavailable.

Additional context and related issues

Release notes

( ) This is not user-visible or is docs only, and no release notes are required.
( ) Release notes are required. Please propose a release note for me.
(x) Release notes are required, with the following suggested text:

## Section
* Add `WHEN STALE` option to `CREATE MATERIALIZED VIEW` ({issue}`issuenumber`)

TableHandle tableHandle = metadata.getTableHandle(session, storageTableName)
.orElseThrow(() -> semanticException(INVALID_VIEW, table, "Storage table '%s' does not exist", storageTableName));
return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.of(tableHandle));
return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.of(tableHandle), useLogicalViewSemantics);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned about this line: the new clause affects the path for fresh MVs (WHEN STALE FAIL skips analysis, while WHEN STALE INLINE runs the analysis), even though the syntax suggests it should only define behavior for stale MVs.

Is analysis of the underlying query even necessary for fresh MVs, regardless of the WHEN STALE clause? I understand that it can detect schema changes in the base tables or changes to the MV definer’s access permissions - but is that the intended behavior?

@piotrrzysko piotrrzysko marked this pull request as ready for review November 18, 2025 10:15
@Praveen2112
Copy link
Member

What is our plan on changing this behaviour for existing or new MVs ?

Comment on lines +163 to +165
if (!plannerContext.getMetadata().getConnectorCapabilities(session, catalogHandle).contains(MATERIALIZED_VIEW_WHEN_STALE_BEHAVIOR)) {
throw semanticException(NOT_SUPPORTED, statement, "Catalog '%s' does not support WHEN STALE", catalogName);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should catalogs need to handle anything in order to support it ? If it is some sort of MV property (like security mode in case of views) - It should be supported for all connectors supporting MVs right ? Or we could fail during the creation or alter phase ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we need a connector capability for this.
What's the default behavior of WHEN STALE going to be?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i get it -- storing the clause requires a capability.

}
// This is a stale materialized view and should be expanded like a logical view
return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.empty());
return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.empty(), useLogicalViewSemantics);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing a useLogicalViewSemantics can we create a scope for the storage table ?

@Praveen2112
Copy link
Member

Apart from the benefit on skipping the analysis what are the other benefits would I get if a run a query as WHEN STALE FAIL ? Now when I create a MV with WHEN STALE - I won't be able to use it unless I run a REFRESH MV. In case of pipeline which uses this MV might be affected if a MV went stale ? Is this expected ? Should we add this as an extension of WHEN FRESH SKIP ANALYSIS or something similar ?

@martint
Copy link
Member

martint commented Nov 18, 2025

For context, #15326

@martint
Copy link
Member

martint commented Nov 18, 2025

Also, #23387 (reply in thread) and #23747

@piotrrzysko
Copy link
Member Author

piotrrzysko commented Nov 19, 2025

@Praveen2112

What is our plan on changing this behaviour for existing or new MVs ?

This change is meant to be backward-compatible. The new clause is optional. If it is not specified, or if WHEN STALE INLINE is used, the MV will behave as it does currently.

Apart from the benefit on skipping the analysis what are the other benefits would I get if a run a query as WHEN STALE FAIL ? Now when I create a MV with WHEN STALE - I won't be able to use it unless I run a REFRESH MV. In case of pipeline which uses this MV might be affected if a MV went stale ? Is this expected ?

Skipping the analysis is not the goal in itself. The goal is to achieve better availability when the source tables are not available and to get closer to the semantics mentioned in the issues that Martin and I linked in this PR - specifically, that within the grace period, the MV should be treated as if it were a regular storage table. And today, as far as I know, the only blocker for this is the analysis performed every time the MV is queried.

Should we add this as an extension of WHEN FRESH SKIP ANALYSIS or something similar ?

This is more or less what I wanted to express in this comment: #27356 (comment). I’m happy to discuss what the syntax should look like to achieve the goals described above. Personally, WHEN FRESH SKIP ANALYSIS feels too low-level - as mentioned in my comment, I'd consider always skipping analysis when the MV is fresh.

@piotrrzysko
Copy link
Member Author

@martint just a gentle reminder about the PR when you have a moment

@findepi
Copy link
Member

findepi commented Nov 26, 2025

@piotrrzysko can you please rebase? there is a conflict.

query,
false,
false,
Optional.empty(), // TODO support GRACE PERIOD
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we miss doing this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it looks like someone (6d5215c) forgot about it 😀.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to create a separate PR to have this addressed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

Copy link
Member

@findepi findepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add WHEN STALE FAIL/INLINE syntax

lgtm

Copy link
Member

@findepi findepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add when stale behavior in ConnectorMaterializedViewDefinition

Comment on lines +163 to +165
if (!plannerContext.getMetadata().getConnectorCapabilities(session, catalogHandle).contains(MATERIALIZED_VIEW_WHEN_STALE_BEHAVIOR)) {
throw semanticException(NOT_SUPPORTED, statement, "Catalog '%s' does not support WHEN STALE", catalogName);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we need a connector capability for this.
What's the default behavior of WHEN STALE going to be?

This is a preparatory change that allows connectors to store information
about the WHEN STALE behavior. The option cannot yet be used in practice,
as execution will still fail with a NOT_SUPPORTED error.
@findepi findepi changed the title Add WHEN STALE option to CREATE MATERIALIZED VIEW Add WHEN STALE option to CREATE MATERIALIZED VIEW and implement in Iceberg Nov 28, 2025
Comment on lines 1686 to 1696
// Change underlying state
assertUpdate("INSERT INTO " + baseTable + " (regionkey, name) VALUES (42, 'foo new region')", 1);
String updatedResults = initialResults + " UNION ALL VALUES (42, 'foo new region')";

// The materialization is stale now
assertThat(query(defaultSession, "TABLE " + viewName))
.hasPlan(readFromStorageTable)
.matches(initialResults);
assertThat(query(futureSession, "TABLE " + viewName))
.hasPlan(readFromBaseTables)
.matches(updatedResults);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MV has GRACE PERIOD INTERVAL '1' HOUR.
The GRACE is defined earlier in the MV definition and should take precedence. I.e. we should keep querying the MV storage when it's known to be stale but grace period did not elapse.

@findepi findepi changed the title Add WHEN STALE option to CREATE MATERIALIZED VIEW and implement in Iceberg Add WHEN STALE option to CREATE MATERIALIZED VIEW Nov 28, 2025
@findepi findepi changed the title Add WHEN STALE option to CREATE MATERIALIZED VIEW Add WHEN STALE syntax for CREATE MATERIALIZED VIEW Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants