Skip to content

Commit 1e0aef9

Browse files
committed
Fix Create or replace Materialized view error
The previous files for materialized views were not cleaned properly: - during view replace, the existing view was deleted after creating the new one, not before (this case caused the error from the #25610 issue), - during view drop, there was an attempt to delete the files twice (this case caused an error when dropping the materialized view). - some leftover metadata and data files remained after recreate Now, when recreating the materialized view, we are always deleting the existing storage using the dropMaterializedViewStorage method at the beginning of the process. The dropMaterializedViewStorage had an extra metastore.dropTable call, which caused the DROP error, as the metastore.dropTable call needed files that were deleted by the call before. In dropMaterializedViewStorage we are making sure the table is cleared from the metastore. Enabled some unit tests that were ignored before due to the existing issues.
1 parent c45c360 commit 1e0aef9

File tree

3 files changed

+71
-31
lines changed

3 files changed

+71
-31
lines changed

plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@ public void createMaterializedView(
575575
}
576576
throw new TrinoException(ALREADY_EXISTS, "Materialized view already exists: " + viewName);
577577
}
578+
else {
579+
dropMaterializedViewStorage(session, existing.get());
580+
}
578581
}
579582

580583
if (hideMaterializedViewStorageTable) {
@@ -599,6 +602,8 @@ public void createMaterializedView(
599602
PrincipalPrivileges principalPrivileges = isUsingSystemSecurity ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser());
600603

601604
try {
605+
// Reload the existing table, in case it was deleted above
606+
existing = metastore.getTable(viewName.getSchemaName(), viewName.getTableName());
602607
if (existing.isPresent()) {
603608
metastore.replaceTable(viewName.getSchemaName(), viewName.getTableName(), table, principalPrivileges, ImmutableMap.of());
604609
}
@@ -618,20 +623,17 @@ public void createMaterializedView(
618623
}
619624
throw e;
620625
}
621-
622-
existing.ifPresent(existingView -> dropMaterializedViewStorage(session, existingView));
623626
}
624627
else {
625-
createMaterializedViewWithStorageTable(session, viewName, definition, materializedViewProperties, existing);
628+
createMaterializedViewWithStorageTable(session, viewName, definition, materializedViewProperties);
626629
}
627630
}
628631

629632
private void createMaterializedViewWithStorageTable(
630633
ConnectorSession session,
631634
SchemaTableName viewName,
632635
ConnectorMaterializedViewDefinition definition,
633-
Map<String, Object> materializedViewProperties,
634-
Optional<io.trino.metastore.Table> existing)
636+
Map<String, Object> materializedViewProperties)
635637
{
636638
SchemaTableName storageTable = createMaterializedViewStorageTable(session, viewName, definition, materializedViewProperties);
637639

@@ -654,19 +656,6 @@ private void createMaterializedViewWithStorageTable(
654656
.setViewExpandedText(Optional.of("/* " + ICEBERG_MATERIALIZED_VIEW_COMMENT + " */"));
655657
io.trino.metastore.Table table = tableBuilder.build();
656658
PrincipalPrivileges principalPrivileges = isUsingSystemSecurity ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser());
657-
if (existing.isPresent()) {
658-
// drop the current storage table
659-
String oldStorageTable = existing.get().getParameters().get(STORAGE_TABLE);
660-
if (oldStorageTable != null) {
661-
String storageSchema = Optional.ofNullable(existing.get().getParameters().get(STORAGE_SCHEMA))
662-
.orElse(viewName.getSchemaName());
663-
metastore.dropTable(storageSchema, oldStorageTable, true);
664-
}
665-
// Replace the existing view definition
666-
metastore.replaceTable(viewName.getSchemaName(), viewName.getTableName(), table, principalPrivileges, ImmutableMap.of());
667-
return;
668-
}
669-
// create the view definition
670659
metastore.createTable(table, principalPrivileges);
671660
}
672661

@@ -720,9 +709,7 @@ public void dropMaterializedView(ConnectorSession session, SchemaTableName viewN
720709
if (!isTrinoMaterializedView(view.getTableType(), view.getParameters())) {
721710
throw new TrinoException(UNSUPPORTED_TABLE_TYPE, "Not a Materialized View: " + viewName);
722711
}
723-
724712
dropMaterializedViewStorage(session, view);
725-
metastore.dropTable(viewName.getSchemaName(), viewName.getTableName(), true);
726713
}
727714

728715
private void dropMaterializedViewStorage(ConnectorSession session, io.trino.metastore.Table view)
@@ -749,6 +736,9 @@ private void dropMaterializedViewStorage(ConnectorSession session, io.trino.meta
749736
log.warn(e, "Failed to delete storage table metadata '%s' for materialized view '%s'", storageMetadataLocation, viewName);
750737
}
751738
}
739+
metastore.invalidateTable(viewName.getSchemaName(), viewName.getTableName());
740+
metastore.getTable(viewName.getSchemaName(), viewName.getTableName())
741+
.ifPresent(table -> metastore.dropTable(viewName.getSchemaName(), viewName.getTableName(), true));
752742
}
753743

754744
@Override

plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,65 @@ AS SELECT sum(value) AS s FROM iceberg2.tpch.common_base_table
134134
assertUpdate(defaultIceberg, "DROP TABLE common_base_table");
135135
assertUpdate("DROP MATERIALIZED VIEW mv_on_iceberg2");
136136
}
137+
138+
@Test
139+
public void testReplaceAndDropMaterializedView() {
140+
testReplaceAndDropMaterializedView(false, false);
141+
testReplaceAndDropMaterializedView(false, true);
142+
testReplaceAndDropMaterializedView(true, false);
143+
testReplaceAndDropMaterializedView(true, true);
144+
}
145+
146+
/**
147+
* Test CREATE OR REPLACE MATERIALIZED VIEW and DROP MATERIALIZED VIEW statements.
148+
* The test creates an Iceberg table and a materialized view on top of it,
149+
* then replaces the materialized view definition twice, and finally drops the materialized view.
150+
*/
151+
private void testReplaceAndDropMaterializedView(boolean uniqueTableLocation, boolean hideStorageTable) {
152+
QueryRunner queryRunner = getQueryRunner();
153+
String catalog = String.format("iceberg_%s_%s", uniqueTableLocation ? "unique" : "not_unique",
154+
hideStorageTable ? "storage" : "not_storage");
155+
queryRunner.createCatalog(catalog, "iceberg", Map.of(
156+
"iceberg.catalog.type", "TESTING_FILE_METASTORE",
157+
"iceberg.unique-table-location", uniqueTableLocation ? "true" : "false",
158+
"hive.metastore.catalog.dir", queryRunner.getCoordinator().getBaseDataDir().resolve(catalog + "-catalog").toString(),
159+
"iceberg.hive-catalog-name", "hive",
160+
"iceberg.materialized-views.hide-storage-table", hideStorageTable ? "true" : "false",
161+
"fs.hadoop.enabled", "true"));
162+
Session session = Session.builder(queryRunner.getDefaultSession())
163+
.setCatalog(catalog)
164+
.setSchema("default")
165+
.build();
166+
queryRunner.execute(session, "CREATE SCHEMA default");
167+
168+
assertUpdate(session, "CREATE TABLE replace_base_table AS SELECT 10 value", 1);
169+
170+
assertUpdate(session, "CREATE OR REPLACE MATERIALIZED VIEW replace_view" +
171+
" AS SELECT sum(value) AS s FROM replace_base_table");
172+
assertUpdate(session, "REFRESH MATERIALIZED VIEW replace_view", 1);
173+
assertQuery(session, "SELECT count(*) FROM replace_view", "VALUES 1");
174+
assertQuery(session, "SELECT * FROM replace_view", "VALUES 10");
175+
176+
assertUpdate(session, "CREATE OR REPLACE MATERIALIZED VIEW replace_view" +
177+
" AS SELECT 2 * sum(value) AS t FROM replace_base_table");
178+
assertUpdate(session, "REFRESH MATERIALIZED VIEW replace_view", 1);
179+
assertQuery(session, "SELECT count(*) FROM replace_view", "VALUES 1");
180+
assertQuery(session, "SELECT * FROM replace_view", "VALUES 20");
181+
182+
assertUpdate(session, "CREATE OR REPLACE MATERIALIZED VIEW replace_view" +
183+
" AS SELECT 3 * sum(value) AS v FROM replace_base_table");
184+
assertUpdate(session, "REFRESH MATERIALIZED VIEW replace_view", 1);
185+
assertQuery(session, "SELECT count(*) FROM replace_view", "VALUES 1");
186+
assertQuery(session, "SELECT * FROM replace_view", "VALUES 30");
187+
188+
assertUpdate(session, "DROP MATERIALIZED VIEW replace_view");
189+
190+
assertUpdate(session, "CREATE OR REPLACE MATERIALIZED VIEW replace_view" +
191+
" AS SELECT 4 * sum(value) AS v FROM replace_base_table");
192+
assertUpdate(session, "REFRESH MATERIALIZED VIEW replace_view", 1);
193+
assertQuery(session, "SELECT count(*) FROM replace_view", "VALUES 1");
194+
assertQuery(session, "SELECT * FROM replace_view", "VALUES 40");
195+
196+
assertUpdate(session, "DROP MATERIALIZED VIEW replace_view");
197+
}
137198
}

plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import io.trino.spi.type.TestingTypeManager;
3535
import org.junit.jupiter.api.AfterAll;
3636
import org.junit.jupiter.api.BeforeAll;
37-
import org.junit.jupiter.api.Disabled;
3837
import org.junit.jupiter.api.Test;
3938
import org.junit.jupiter.api.TestInstance;
4039
import org.junit.jupiter.api.parallel.Execution;
@@ -56,7 +55,6 @@
5655
import static io.trino.plugin.iceberg.IcebergTestUtils.FILE_IO_FACTORY;
5756
import static io.trino.spi.type.IntegerType.INTEGER;
5857
import static io.trino.testing.TestingNames.randomNameSuffix;
59-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
6058
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
6159
import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
6260

@@ -109,7 +107,6 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations)
109107
}
110108

111109
@Test
112-
@Disabled
113110
public void testDropMaterializedView()
114111
{
115112
testDropMaterializedView(false);
@@ -156,12 +153,4 @@ private void testDropMaterializedView(boolean useUniqueTableLocations)
156153
}
157154
}
158155
}
159-
160-
@Test
161-
@Override
162-
public void testListTables()
163-
{
164-
// the test actually works but when cleanup up the materialized view the error is thrown
165-
assertThatThrownBy(super::testListTables).hasMessageMatching("Table 'ns2.*.mv' not found");
166-
}
167156
}

0 commit comments

Comments
 (0)