Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -440,11 +440,13 @@ def manipulate(self) -> list[Testdrive]:
"""
> INSERT INTO mv_declared_table VALUES (3, 'z')
> CREATE MATERIALIZED VIEW mv_declared_schema2 (a int4 NOT NULL, b text) AS SELECT a, b FROM mv_declared_table
> CREATE REPLACEMENT MATERIALIZED VIEW mv_declared_replacement1 FOR mv_declared_schema1 AS SELECT a::int8, count(*) * 10 FROM mv_declared_table WHERE a IS NOT NULL GROUP BY a::int8
""",
"""
> INSERT INTO mv_declared_table VALUES (4, 'w')
> CREATE REPLACEMENT MATERIALIZED VIEW mv_declared_replacement (k int8 NOT NULL, n int8) FOR mv_declared_schema1 AS SELECT a, count(*) * 10 FROM mv_declared_table WHERE a IS NOT NULL GROUP BY a
> ALTER MATERIALIZED VIEW mv_declared_schema1 APPLY REPLACEMENT mv_declared_replacement
> ALTER MATERIALIZED VIEW mv_declared_schema1 APPLY REPLACEMENT mv_declared_replacement1
> CREATE REPLACEMENT MATERIALIZED VIEW mv_declared_replacement2 (a int4 NOT NULL, b text) FOR mv_declared_schema2 AS SELECT a, upper(b) FROM mv_declared_table
> ALTER MATERIALIZED VIEW mv_declared_schema2 APPLY REPLACEMENT mv_declared_replacement2
""",
]
]
Expand All @@ -458,10 +460,10 @@ def validate(self) -> Testdrive:
4 10

> SELECT * FROM mv_declared_schema2
1 x
1 X
2 <null>
3 z
4 w
3 Z
4 W

> SELECT name, nullable, type FROM (SHOW COLUMNS FROM mv_declared_schema1)
k false bigint
Expand Down
13 changes: 12 additions & 1 deletion src/adapter/src/coord/command_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ use mz_sql::plan::{
StatementClassification, TransactionType,
};
use mz_sql::pure::{
materialized_view_option_contains_temporal, purify_create_materialized_view_options,
materialized_view_option_contains_temporal, purify_create_materialized_view_columns,
purify_create_materialized_view_options,
};
use mz_sql::rbac;
use mz_sql::session::hint::ApplicationNameHint;
Expand Down Expand Up @@ -1732,6 +1733,11 @@ impl Coordinator {

let catalog = self.catalog().for_session(ctx.session());

if let Err(e) =
purify_create_materialized_view_columns(&catalog, &mut cmvs, &mut resolved_ids)
{
return ctx.retire(Err(e.into()));
}
purify_create_materialized_view_options(
catalog,
mz_now,
Expand Down Expand Up @@ -1779,6 +1785,11 @@ impl Coordinator {

let catalog = self.catalog().for_session(ctx.session());

if let Err(e) =
purify_create_materialized_view_columns(&catalog, &mut cmvs, &mut resolved_ids)
{
return ctx.retire(Err(e.into()));
}
purify_create_materialized_view_options(
catalog,
mz_now,
Expand Down
4 changes: 2 additions & 2 deletions src/adapter/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,8 +851,8 @@ impl AdapterError {
.to_string(),
),
AdapterError::ReplacementSchemaNotDeclared => Some(
"Declare the target's columns with their types. EXPLAIN ... WITH (schema) prints \
them for the target."
"Omit the column list to use the target's column definitions, or declare the \
columns with their types."
.to_string(),
),
AdapterError::SubscribeFellBehind { .. } => Some(
Expand Down
41 changes: 40 additions & 1 deletion src/sql/src/pure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use mz_sql_parser::ast::{
CsrConfigOptionName, CsrConnection, CsrSeedAvro, CsrSeedProtobuf, CsrSeedProtobufSchema,
DeferredItemName, DocOnIdentifier, DocOnSchema, Expr, Function, FunctionArgs, GlueAvroOption,
GlueAvroSeed, Ident, KafkaSourceConfigOption, KafkaSourceConfigOptionName, LoadGenerator,
LoadGeneratorOption, LoadGeneratorOptionName, MaterializedViewOption,
LoadGeneratorOption, LoadGeneratorOptionName, MaterializedViewColumns, MaterializedViewOption,
MaterializedViewOptionName, MySqlConfigOption, MySqlConfigOptionName, PgConfigOption,
PgConfigOptionName, RawItemName, ReaderSchemaSelectionStrategy, RefreshAtOptionValue,
RefreshEveryOptionValue, RefreshOptionValue, SourceEnvelope, SqlServerConfigOption,
Expand Down Expand Up @@ -2949,6 +2949,45 @@ async fn compile_proto(
})
}

/// Gives a replacement materialized view without a column list the column
/// definitions of its target, if the target declares its schema.
///
/// Writing the definitions into the statement keeps the replacement's
/// `create_sql` self-contained, so re-planning it does not depend on the
/// target, and records dependencies on the types the definitions use. Applying
/// the replacement copies its column list to the target, which keeps the
/// target's schema declared.
pub fn purify_create_materialized_view_columns(
catalog: &dyn SessionCatalog,
cmvs: &mut CreateMaterializedViewStatement<Aug>,
resolved_ids: &mut ResolvedIds,
) -> Result<(), PlanError> {
// Planning reports a missing or invalid replacement target.
let Some(ResolvedItemName::Item { id, .. }) = &cmvs.replacement_for else {
return Ok(());
};
if !cmvs.columns.is_empty() {
return Ok(());
}
let target = catalog.get_item(id);
let stmt = mz_sql_parser::parser::parse_statements(target.create_sql())?
.into_element()
.ast;
let Statement::CreateMaterializedView(target_stmt) = stmt else {
return Ok(());
};
if !matches!(
target_stmt.columns,
MaterializedViewColumns::Definitions { .. }
) {
return Ok(());
}
let (columns, ids) = crate::names::resolve(catalog, target_stmt.columns)?;
cmvs.columns = columns;
resolved_ids.extend_from(&ids);
Ok(())
}

const MZ_NOW_NAME: &str = "mz_now";
const MZ_NOW_SCHEMA: &str = "mz_catalog";

Expand Down
66 changes: 65 additions & 1 deletion test/sqllogictest/materialized_view_declared_schema.slt
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ q text

# Replacements are compared against the declared schema.
statement error replacement must declare its schema because its target does
CREATE REPLACEMENT MATERIALIZED VIEW rp FOR mv AS SELECT a::int8 AS x, b AS y, c::numeric(10, 1) AS z FROM t
CREATE REPLACEMENT MATERIALIZED VIEW rp (x, y, z) FOR mv AS SELECT a::int8, b, c::numeric(10, 1) FROM t

statement error replacement schema differs from target schema
CREATE REPLACEMENT MATERIALIZED VIEW rp (x int8, y text, z numeric(10, 1)) FOR mv AS SELECT a, b, c::numeric(10, 1) FROM t
Expand All @@ -301,6 +301,70 @@ SELECT create_sql LIKE '%("x" %int8% NOT NULL, "y" %text%, "z" %numeric%10%1%)%'
----
true

# A replacement without a column list gets the target's column definitions,
# including names, and is cast to them as if they were written out.
statement error materialized view materialize.public.rp declares 3 columns, but its query produces 2
CREATE REPLACEMENT MATERIALIZED VIEW rp FOR mv AS SELECT a::int8, b FROM t

# Inherited types are cast to with implicit casts only.
statement error column "z" is of type numeric but expression is of type numeric with different type modifiers
CREATE REPLACEMENT MATERIALIZED VIEW rp FOR mv AS SELECT a::int8, b, c FROM t

statement ok
CREATE REPLACEMENT MATERIALIZED VIEW rp FOR mv AS SELECT a * 10 AS ignored, b, c::numeric(10, 1) FROM t

query TTT rowsort
SELECT name, nullable, type FROM (SHOW COLUMNS FROM rp)
----
x false bigint
y true text
z true numeric

query T
SELECT create_sql LIKE '%("x" %int8% NOT NULL, "y" %text%, "z" %numeric%10%1%)%' FROM mz_materialized_views WHERE name = 'rp'
----
true

statement ok
ALTER MATERIALIZED VIEW mv APPLY REPLACEMENT rp

query ITR rowsort
SELECT * FROM mv
----
10 x 1.2
20 y 2.5

query T
SELECT create_sql LIKE '%("x" %int8% NOT NULL, "y" %text%, "z" %numeric%10%1%)%' FROM mz_materialized_views WHERE name = 'mv'
----
true

# Types used by inherited column definitions are dependencies, also after the
# replacement is applied.
statement ok
CREATE TYPE int4_list AS LIST (ELEMENT TYPE = int4)

statement ok
CREATE VIEW lists AS SELECT LIST[a]::int4_list AS l FROM t

statement ok
CREATE MATERIALIZED VIEW mv_typed (l int4_list) AS SELECT l FROM lists

statement ok
CREATE REPLACEMENT MATERIALIZED VIEW rp FOR mv_typed AS SELECT l FROM lists

statement ok
ALTER MATERIALIZED VIEW mv_typed APPLY REPLACEMENT rp

query I
SELECT count(*)
FROM mz_internal.mz_object_dependencies d
JOIN mz_materialized_views m ON d.object_id = m.id
JOIN mz_types ty ON d.referenced_object_id = ty.id
WHERE m.name = 'mv_typed' AND ty.name = 'int4_list'
----
1

# An implicit target can be given a declared schema that matches its current
# one.
statement ok
Expand Down
Loading