From 114412aec26e970ce66e812ed159c56e6457919d Mon Sep 17 00:00:00 2001 From: Dmitriy Fingerman Date: Thu, 23 Apr 2026 15:49:20 -0400 Subject: [PATCH] HIVE-29578: Iceberg: support for Iceberg native views --- .../org/apache/hadoop/hive/ql/ErrorMsg.java | 2 + .../iceberg/hive/HiveViewOperations.java | 2 +- .../apache/iceberg/hive/MetastoreUtil.java | 64 +++++ .../hive/NativeIcebergViewSupport.java | 124 ++++++++++ .../hive/client/HiveRESTCatalogClient.java | 49 +++- .../hive/TestNativeIcebergViewSupport.java | 136 +++++++++++ .../queries/positive/iceberg_native_view.q | 38 +++ .../positive/iceberg_rest_catalog_gravitino.q | 20 +- .../positive/iceberg_rest_catalog_hms.q | 20 +- .../positive/llap/iceberg_native_view.q.out | 223 ++++++++++++++++++ .../llap/iceberg_rest_catalog_gravitino.q.out | 69 ++++++ .../llap/iceberg_rest_catalog_hms.q.out | 69 ++++++ .../hive/TestHiveRESTCatalogClientITBase.java | 52 +++- .../resources/testconfiguration.properties | 2 + .../apache/hadoop/hive/ql/parse/HiveParser.g | 2 + .../hive/ql/parse/TestParseDefault.java | 9 + .../ddl/view/create/CreateViewAnalyzer.java | 47 +++- .../ql/ddl/view/create/CreateViewDesc.java | 17 ++ .../ddl/view/create/CreateViewOperation.java | 126 ++++++++++ 19 files changed, 1053 insertions(+), 18 deletions(-) create mode 100644 iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/NativeIcebergViewSupport.java create mode 100644 iceberg/iceberg-catalog/src/test/java/org/apache/iceberg/hive/TestNativeIcebergViewSupport.java create mode 100644 iceberg/iceberg-handler/src/test/queries/positive/iceberg_native_view.q create mode 100644 iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_native_view.q.out diff --git a/common/src/java/org/apache/hadoop/hive/ql/ErrorMsg.java b/common/src/java/org/apache/hadoop/hive/ql/ErrorMsg.java index 57f7cadac181..de96936df4c2 100644 --- a/common/src/java/org/apache/hadoop/hive/ql/ErrorMsg.java +++ b/common/src/java/org/apache/hadoop/hive/ql/ErrorMsg.java @@ -445,6 +445,8 @@ public enum ErrorMsg { @Deprecated // kept for backwards reference REPLACE_VIEW_WITH_MATERIALIZED(10400, "Attempt to replace view {0} with materialized view", true), REPLACE_MATERIALIZED_WITH_VIEW(10401, "Attempt to replace materialized view {0} with view", true), + VIEW_STORAGE_HANDLER_UNSUPPORTED(10448, "CREATE VIEW only supports STORED BY ICEBERG for native " + + "Iceberg views; unsupported storage clause: {0}", true), UPDATE_DELETE_VIEW(10402, "You cannot update or delete records in a view"), MATERIALIZED_VIEW_DEF_EMPTY(10403, "Query for the materialized view rebuild could not be retrieved"), MERGE_PREDIACTE_REQUIRED(10404, "MERGE statement with both UPDATE and DELETE clauses " + diff --git a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/HiveViewOperations.java b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/HiveViewOperations.java index c0c959974a61..10f6736c3538 100644 --- a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/HiveViewOperations.java +++ b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/HiveViewOperations.java @@ -302,7 +302,7 @@ private Table newHMSView(ViewMetadata metadata) { tableType().name()); } - private String sqlFor(ViewMetadata metadata) { + public static String sqlFor(ViewMetadata metadata) { SQLViewRepresentation closest = null; for (ViewRepresentation representation : metadata.currentVersion().representations()) { if (representation instanceof SQLViewRepresentation) { diff --git a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java index 95e1e5b36623..6ba49b7337d3 100644 --- a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java +++ b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/MetastoreUtil.java @@ -46,6 +46,11 @@ import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; import org.apache.iceberg.relocated.com.google.common.collect.Lists; import org.apache.iceberg.relocated.com.google.common.collect.Maps; +import org.apache.iceberg.util.PropertyUtil; +import org.apache.iceberg.view.BaseView; +import org.apache.iceberg.view.SQLViewRepresentation; +import org.apache.iceberg.view.View; +import org.apache.iceberg.view.ViewMetadata; import org.apache.thrift.TException; public class MetastoreUtil { @@ -148,6 +153,65 @@ public static Table toHiveTable(org.apache.iceberg.Table table, Configuration co return result; } + /** + * Builds a Hive metastore {@link Table} representation for an Iceberg {@link View}, for clients + * (e.g. {@code HiveRESTCatalogClient}) that bridge Iceberg catalog metadata into the HMS API. + */ + public static Table toHiveView(View view, Configuration conf) { + Table result = new Table(); + TableName tableName = + TableName.fromString( + view.name(), MetaStoreUtils.getDefaultCatalog(conf), Warehouse.DEFAULT_DATABASE_NAME); + result.setCatName(tableName.getCat()); + result.setDbName(tableName.getDb()); + result.setTableName(tableName.getTable()); + result.setTableType(TableType.VIRTUAL_VIEW.toString()); + + ViewMetadata metadata = ((BaseView) view).operations().current(); + String sqlText = viewSqlText(view, metadata); + result.setViewOriginalText(sqlText); + result.setViewExpandedText(sqlText); + + long nowMillis = System.currentTimeMillis(); + int nowSec = (int) (nowMillis / 1000); + String owner = + PropertyUtil.propertyAsString( + metadata.properties(), HiveCatalog.HMS_TABLE_OWNER, System.getProperty("user.name")); + result.setOwner(owner); + result.setCreateTime(nowSec); + result.setLastAccessTime(nowSec); + result.setRetention(Integer.MAX_VALUE); + + boolean hiveEngineEnabled = false; + result.setSd(HiveOperationsBase.storageDescriptor(metadata.schema(), metadata.location(), hiveEngineEnabled)); + + long maxHiveTablePropertySize = + conf.getLong( + HiveOperationsBase.HIVE_TABLE_PROPERTY_MAX_SIZE, + HiveOperationsBase.HIVE_TABLE_PROPERTY_MAX_SIZE_DEFAULT); + HMSTablePropertyHelper.updateHmsTableForIcebergView( + metadata.metadataFileLocation(), + result, + metadata, + Collections.emptySet(), + maxHiveTablePropertySize, + null); + + String catalogType = IcebergCatalogProperties.getCatalogType(conf); + if (!StringUtils.isEmpty(catalogType) && !IcebergCatalogProperties.NO_CATALOG_TYPE.equals(catalogType)) { + result.getParameters().put(CatalogUtil.ICEBERG_CATALOG_TYPE, IcebergCatalogProperties.getCatalogType(conf)); + } + return result; + } + + private static String viewSqlText(View view, ViewMetadata metadata) { + SQLViewRepresentation hiveRepr = view.sqlFor("hive"); + if (hiveRepr != null) { + return hiveRepr.sql(); + } + return HiveViewOperations.sqlFor(metadata); + } + private static StorageDescriptor getHiveStorageDescriptor(org.apache.iceberg.Table table) { var result = new StorageDescriptor(); result.setCols(HiveSchemaUtil.convert(table.schema())); diff --git a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/NativeIcebergViewSupport.java b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/NativeIcebergViewSupport.java new file mode 100644 index 000000000000..ace4ea703b84 --- /dev/null +++ b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/NativeIcebergViewSupport.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iceberg.hive; + +import java.io.Closeable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.metastore.api.FieldSchema; +import org.apache.iceberg.CatalogUtil; +import org.apache.iceberg.catalog.Catalog; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.catalog.ViewCatalog; +import org.apache.iceberg.view.ViewBuilder; + +/** + * Commits a native Iceberg view through the configured default Iceberg catalog (HiveCatalog or REST + * catalog, etc.) when {@code Catalog} also implements {@link ViewCatalog}. + */ +public final class NativeIcebergViewSupport { + + /** HMS parameter aligned with Hive's {@code CreateViewDesc#ICEBERG_NATIVE_VIEW_PROPERTY}. */ + public static final String ICEBERG_NATIVE_VIEW_PROPERTY = "hive.iceberg.native.view"; + + private NativeIcebergViewSupport() { + } + + /** + * Creates or replaces a view in the Iceberg catalog. + * + * @return {@code false} if skipped because {@code ifNotExists} is true and the view already exists + */ + public static boolean createOrReplaceNativeView(Configuration conf, String databaseName, String viewName, + List fieldSchemas, String viewSql, Map tblProperties, String comment, + boolean replace, boolean ifNotExists) throws Exception { + + TableIdentifier identifier = TableIdentifier.of(databaseName, viewName); + String catalogName = IcebergCatalogProperties.getCatalogName(conf); + Map catalogProps = IcebergCatalogProperties.getCatalogProperties(conf, catalogName); + Catalog catalog = CatalogUtil.buildIcebergCatalog(catalogName, catalogProps, conf); + try { + ViewCatalog viewCatalog = asViewCatalog(catalog, catalogName); + if (!replace && ifNotExists && viewCatalog.viewExists(identifier)) { + return false; + } + + ViewBuilder builder = startViewBuilder(viewCatalog, identifier, fieldSchemas, viewSql); + builder = applyCommentAndTblProps(builder, tblProperties, comment); + commitView(builder, replace); + return true; + } finally { + if (catalog instanceof Closeable) { + ((Closeable) catalog).close(); + } + } + } + + private static ViewCatalog asViewCatalog(Catalog catalog, String catalogName) { + if (!(catalog instanceof ViewCatalog)) { + throw new UnsupportedOperationException( + String.format( + "Iceberg catalog '%s' does not implement ViewCatalog.", + catalogName) + + " Native views require a catalog that implements ViewCatalog (e.g. HiveCatalog or REST)."); + } + return (ViewCatalog) catalog; + } + + private static ViewBuilder startViewBuilder( + ViewCatalog viewCatalog, + TableIdentifier identifier, + List fieldSchemas, + String viewSql) { + return viewCatalog + .buildView(identifier) + .withSchema(HiveSchemaUtil.convert(fieldSchemas, Collections.emptyMap(), true)) + .withDefaultNamespace(Namespace.of(identifier.namespace().level(0))) + .withQuery("hive", viewSql) + .withProperty(ICEBERG_NATIVE_VIEW_PROPERTY, "true"); + } + + private static ViewBuilder applyCommentAndTblProps( + ViewBuilder builder, Map tblProperties, String comment) { + ViewBuilder viewBuilder = builder; + if (comment != null && !comment.isEmpty()) { + viewBuilder = viewBuilder.withProperty("comment", comment); + } + if (tblProperties != null) { + for (Map.Entry e : tblProperties.entrySet()) { + if (e.getKey() != null && e.getValue() != null) { + viewBuilder = viewBuilder.withProperty(e.getKey(), e.getValue()); + } + } + } + return viewBuilder; + } + + private static void commitView(ViewBuilder builder, boolean replace) { + if (replace) { + builder.createOrReplace(); + } else { + builder.create(); + } + } +} diff --git a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/client/HiveRESTCatalogClient.java b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/client/HiveRESTCatalogClient.java index 4390d5a0bca1..2194b9950e3e 100644 --- a/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/client/HiveRESTCatalogClient.java +++ b/iceberg/iceberg-catalog/src/main/java/org/apache/iceberg/hive/client/HiveRESTCatalogClient.java @@ -21,10 +21,12 @@ import java.io.IOException; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; +import java.util.Set; import java.util.regex.Pattern; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.metastore.api.CreateTableRequest; @@ -43,7 +45,9 @@ import org.apache.iceberg.SortOrder; import org.apache.iceberg.catalog.Namespace; import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.catalog.ViewCatalog; import org.apache.iceberg.exceptions.NoSuchTableException; +import org.apache.iceberg.exceptions.NoSuchViewException; import org.apache.iceberg.hive.HMSTablePropertyHelper; import org.apache.iceberg.hive.HiveSchemaUtil; import org.apache.iceberg.hive.IcebergCatalogProperties; @@ -54,6 +58,7 @@ import org.apache.iceberg.relocated.com.google.common.collect.Lists; import org.apache.iceberg.relocated.com.google.common.collect.Maps; import org.apache.iceberg.rest.RESTCatalog; +import org.apache.iceberg.view.View; import org.apache.thrift.TException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -123,10 +128,19 @@ public List getTables(String catName, String dbName, String tablePattern Pattern pattern = Pattern.compile(regex); // List tables from the specific database (namespace) and filter them. - return restCatalog.listTables(Namespace.of(dbName)).stream() + Set names = new LinkedHashSet<>(); + restCatalog.listTables(Namespace.of(dbName)).stream() .map(TableIdentifier::name) .filter(pattern.asPredicate()) - .toList(); + .forEach(names::add); + if (restCatalog instanceof ViewCatalog) { + ((ViewCatalog) restCatalog) + .listViews(Namespace.of(dbName)).stream() + .map(TableIdentifier::name) + .filter(pattern.asPredicate()) + .forEach(names::add); + } + return Lists.newArrayList(names); } @Override @@ -136,7 +150,12 @@ public List getAllTables(String catName, String dbName) { @Override public void dropTable(Table table, boolean deleteData, boolean ignoreUnknownTab, boolean ifPurge) throws TException { - restCatalog.dropTable(TableIdentifier.of(table.getDbName(), table.getTableName())); + TableIdentifier id = TableIdentifier.of(table.getDbName(), table.getTableName()); + if (restCatalog instanceof ViewCatalog && ((ViewCatalog) restCatalog).viewExists(id)) { + ((ViewCatalog) restCatalog).dropView(id); + } else { + restCatalog.dropTable(id); + } } private void validateCurrentCatalog(String catName) { @@ -149,7 +168,11 @@ private void validateCurrentCatalog(String catName) { @Override public boolean tableExists(String catName, String dbName, String tableName) { validateCurrentCatalog(catName); - return restCatalog.tableExists(TableIdentifier.of(dbName, tableName)); + TableIdentifier id = TableIdentifier.of(dbName, tableName); + if (restCatalog.tableExists(id)) { + return true; + } + return restCatalog instanceof ViewCatalog && ((ViewCatalog) restCatalog).viewExists(id); } @Override @@ -178,14 +201,22 @@ public Database getDatabase(String catName, String dbName) throws NoSuchObjectEx @Override public Table getTable(GetTableRequest tableRequest) throws TException { validateCurrentCatalog(tableRequest.getCatName()); - org.apache.iceberg.Table icebergTable; + TableIdentifier id = + TableIdentifier.of(tableRequest.getDbName(), tableRequest.getTblName()); try { - icebergTable = restCatalog.loadTable(TableIdentifier.of(tableRequest.getDbName(), - tableRequest.getTblName())); - } catch (NoSuchTableException exception) { + org.apache.iceberg.Table icebergTable = restCatalog.loadTable(id); + return MetastoreUtil.toHiveTable(icebergTable, conf); + } catch (NoSuchTableException tableMissing) { + if (restCatalog instanceof ViewCatalog) { + try { + View icebergView = ((ViewCatalog) restCatalog).loadView(id); + return MetastoreUtil.toHiveView(icebergView, conf); + } catch (NoSuchViewException viewMissing) { + throw new NoSuchObjectException(); + } + } throw new NoSuchObjectException(); } - return MetastoreUtil.toHiveTable(icebergTable, conf); } @Override diff --git a/iceberg/iceberg-catalog/src/test/java/org/apache/iceberg/hive/TestNativeIcebergViewSupport.java b/iceberg/iceberg-catalog/src/test/java/org/apache/iceberg/hive/TestNativeIcebergViewSupport.java new file mode 100644 index 000000000000..daf187b5c09f --- /dev/null +++ b/iceberg/iceberg-catalog/src/test/java/org/apache/iceberg/hive/TestNativeIcebergViewSupport.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iceberg.hive; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.metastore.api.FieldSchema; +import org.apache.hadoop.hive.metastore.conf.MetastoreConf; +import org.apache.iceberg.CatalogProperties; +import org.apache.iceberg.CatalogUtil; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; +import org.apache.iceberg.view.BaseView; +import org.apache.iceberg.view.View; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.apache.iceberg.CatalogUtil.ICEBERG_CATALOG_TYPE; +import static org.apache.iceberg.CatalogUtil.ICEBERG_CATALOG_TYPE_HIVE; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestNativeIcebergViewSupport { + + private static final String DB = "native_vw_db"; + private static final String VIEW = "native_vw"; + + @RegisterExtension + private static final HiveMetastoreExtension HIVE_METASTORE_EXTENSION = + HiveMetastoreExtension.builder().withDatabase(DB).build(); + + @AfterEach + public void dropView() { + HiveCatalog cat = verifyCatalog(); + TableIdentifier id = TableIdentifier.of(DB, VIEW); + if (cat.viewExists(id)) { + cat.dropView(id); + } + } + + private HiveConf nativeViewConf() { + HiveConf conf = new HiveConf(HIVE_METASTORE_EXTENSION.hiveConf()); + MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CATALOG_DEFAULT, "hive"); + conf.set( + IcebergCatalogProperties.catalogPropertyConfigKey("hive", ICEBERG_CATALOG_TYPE), + ICEBERG_CATALOG_TYPE_HIVE); + return conf; + } + + private HiveCatalog verifyCatalog() { + return (HiveCatalog) + CatalogUtil.loadCatalog( + HiveCatalog.class.getName(), + ICEBERG_CATALOG_TYPE_HIVE, + ImmutableMap.of( + CatalogProperties.CLIENT_POOL_CACHE_EVICTION_INTERVAL_MS, + String.valueOf(TimeUnit.SECONDS.toMillis(10))), + HIVE_METASTORE_EXTENSION.hiveConf()); + } + + @Test + public void testCreateCommitsNativeViewWithMarkerProperty() throws Exception { + HiveConf conf = nativeViewConf(); + List cols = + Arrays.asList(new FieldSchema("id", "int", null), new FieldSchema("name", "string", null)); + String sql = String.format("select id, name from %s.src_tbl", DB); + Map props = Collections.singletonMap("k1", "v1"); + + boolean created = + NativeIcebergViewSupport.createOrReplaceNativeView( + conf, DB, VIEW, cols, sql, props, "hello-view", false, false); + assertThat(created).isTrue(); + + HiveCatalog cat = verifyCatalog(); + TableIdentifier id = TableIdentifier.of(DB, VIEW); + assertThat(cat.viewExists(id)).isTrue(); + View view = cat.loadView(id); + assertThat(view.properties().get(NativeIcebergViewSupport.ICEBERG_NATIVE_VIEW_PROPERTY)) + .isEqualTo("true"); + assertThat(view.properties().get("comment")).isEqualTo("hello-view"); + assertThat(view.properties().get("k1")).isEqualTo("v1"); + HiveViewOperations ops = (HiveViewOperations) ((BaseView) view).operations(); + assertThat(ops.current().currentVersion().representations()).isNotEmpty(); + } + + @Test + public void testIfNotExistsReturnsFalseWhenViewExists() throws Exception { + HiveConf conf = nativeViewConf(); + List cols = Collections.singletonList(new FieldSchema("id", "int", null)); + + assertThat( + NativeIcebergViewSupport.createOrReplaceNativeView( + conf, DB, VIEW, cols, "select 1 as id", null, null, false, false)) + .isTrue(); + assertThat( + NativeIcebergViewSupport.createOrReplaceNativeView( + conf, DB, VIEW, cols, "select 2 as id", null, null, false, true)) + .isFalse(); + } + + @Test + public void testReplaceUpdatesView() throws Exception { + HiveConf conf = nativeViewConf(); + List cols = Collections.singletonList(new FieldSchema("id", "int", null)); + + NativeIcebergViewSupport.createOrReplaceNativeView( + conf, DB, VIEW, cols, "select 1 as id", null, null, false, false); + assertThat( + NativeIcebergViewSupport.createOrReplaceNativeView( + conf, DB, VIEW, cols, "select 2 as id", null, null, true, false)) + .isTrue(); + + assertThat(verifyCatalog().viewExists(TableIdentifier.of(DB, VIEW))).isTrue(); + } +} diff --git a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_native_view.q b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_native_view.q new file mode 100644 index 000000000000..4c954f786f0b --- /dev/null +++ b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_native_view.q @@ -0,0 +1,38 @@ +-- SORT_QUERY_RESULTS + +create database ice_native_view_db; +use ice_native_view_db; + +create table src_ice ( + first_name string, + last_name string + ) +partitioned by (dept_id bigint) +stored by iceberg stored as orc; + +insert into src_ice values ('fn1','ln1', 1); +insert into src_ice values ('fn2','ln2', 1); +insert into src_ice values ('fn3','ln3', 1); +insert into src_ice values ('fn4','ln4', 1); +insert into src_ice values ('fn5','ln5', 2); +insert into src_ice values ('fn6','ln6', 2); +insert into src_ice values ('fn7','ln7', 2); + +update src_ice set last_name = 'ln1a' where first_name='fn1'; +update src_ice set last_name = 'ln2a' where first_name='fn2'; +update src_ice set last_name = 'ln3a' where first_name='fn3'; +update src_ice set last_name = 'ln4a' where first_name='fn4'; +update src_ice set last_name = 'ln5a' where first_name='fn5'; +update src_ice set last_name = 'ln6a' where first_name='fn6'; +update src_ice set last_name = 'ln7a' where first_name='fn7'; + +delete from src_ice where last_name in ('ln1a', 'ln2a', 'ln7a'); + +create view v_ice as select * from src_ice stored by iceberg; + +select * from v_ice; + +drop view v_ice; +drop table src_ice; +use default; +drop database ice_native_view_db cascade; diff --git a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_gravitino.q b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_gravitino.q index 81982ca44d98..6b73ff2aa13e 100644 --- a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_gravitino.q +++ b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_gravitino.q @@ -15,8 +15,6 @@ --! qt:replace:/(\s+current-snapshot-timestamp-ms\s+)\S+(\s*)/$1#Masked#$2/ --! qt:replace:/(MAJOR\s+succeeded\s+)[a-zA-Z0-9\-\.\s+]+(\s+manual)/$1#Masked#$2/ --! qt:replace:/(MAJOR\s+refused\s+)[a-zA-Z0-9\-\.\s+]+(\s+manual)/$1#Masked#$2/ --- Mask compaction id as they will be allocated in parallel threads ---! qt:replace:/^[0-9]/#Masked#/ -- Mask removed file size --! qt:replace:/(\S\"removed-files-size\\\":\\\")(\d+)(\\\")/$1#Masked#$3/ -- Mask iceberg version @@ -47,7 +45,7 @@ partitioned by (company_id bigint) stored by iceberg stored as orc; ----------------------------------------------------------------------------- ---! Creating table with a valid catalog name in table properties +--! Creating a table with a valid catalog name in table properties ----------------------------------------------------------------------------- create table ice_orc2 ( @@ -74,6 +72,22 @@ VALUES ('fn1','ln1', 1, 10), ('fn2','ln2', 2, 20), ('fn3','ln3', 3, 30); describe formatted ice_orc2; select * from ice_orc2; +----------------------------------------------------------------------------- +--! Iceberg native views (STORED BY ICEBERG) on REST catalog +----------------------------------------------------------------------------- + +-- Enable once CBO supports Iceberg native views on partitioned tables +set hive.cbo.enable=false; + +create view ice_v1 as select * from ice_orc1 stored by iceberg; +create view ice_v2 as select * from ice_orc2 stored by iceberg; + +select * from ice_v1; +select * from ice_v2; + +drop view ice_v1; +drop view ice_v2; + ----------------------------------------------------------------------------- show tables; diff --git a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_hms.q b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_hms.q index 27f23122240b..eefd5bd5ce98 100644 --- a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_hms.q +++ b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_rest_catalog_hms.q @@ -15,8 +15,6 @@ --! qt:replace:/(\s+current-snapshot-timestamp-ms\s+)\S+(\s*)/$1#Masked#$2/ --! qt:replace:/(MAJOR\s+succeeded\s+)[a-zA-Z0-9\-\.\s+]+(\s+manual)/$1#Masked#$2/ --! qt:replace:/(MAJOR\s+refused\s+)[a-zA-Z0-9\-\.\s+]+(\s+manual)/$1#Masked#$2/ --- Mask compaction id as they will be allocated in parallel threads ---! qt:replace:/^[0-9]/#Masked#/ -- Mask removed file size --! qt:replace:/(\S\"removed-files-size\\\":\\\")(\d+)(\\\")/$1#Masked#$3/ -- Mask iceberg version @@ -47,7 +45,7 @@ partitioned by (company_id bigint) stored by iceberg stored as orc; ----------------------------------------------------------------------------- ---! Creating table with a valid catalog name in table properties +--! Creating a table with a valid catalog name in table properties ----------------------------------------------------------------------------- create table ice_orc2 ( @@ -69,6 +67,22 @@ VALUES ('fn1','ln1', 1, 10), ('fn2','ln2', 2, 20), ('fn3','ln3', 3, 30); describe formatted ice_orc2; select * from ice_orc2; +----------------------------------------------------------------------------- +--! Iceberg native views (STORED BY ICEBERG) on REST catalog +----------------------------------------------------------------------------- + +-- Enable once CBO supports Iceberg native views on partitioned tables +set hive.cbo.enable=false; + +create view ice_v1 as select * from ice_orc1 stored by iceberg; +create view ice_v2 as select * from ice_orc2 stored by iceberg; + +select * from ice_v1; +select * from ice_v2; + +drop view ice_v1; +drop view ice_v2; + ----------------------------------------------------------------------------- show tables; diff --git a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_native_view.q.out b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_native_view.q.out new file mode 100644 index 000000000000..ae1a55ef129a --- /dev/null +++ b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_native_view.q.out @@ -0,0 +1,223 @@ +PREHOOK: query: create database ice_native_view_db +PREHOOK: type: CREATEDATABASE +PREHOOK: Output: database:ice_native_view_db +POSTHOOK: query: create database ice_native_view_db +POSTHOOK: type: CREATEDATABASE +POSTHOOK: Output: database:ice_native_view_db +PREHOOK: query: use ice_native_view_db +PREHOOK: type: SWITCHDATABASE +PREHOOK: Input: database:ice_native_view_db +POSTHOOK: query: use ice_native_view_db +POSTHOOK: type: SWITCHDATABASE +POSTHOOK: Input: database:ice_native_view_db +PREHOOK: query: create table src_ice ( + first_name string, + last_name string + ) +partitioned by (dept_id bigint) +stored by iceberg stored as orc +PREHOOK: type: CREATETABLE +PREHOOK: Output: database:ice_native_view_db +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: create table src_ice ( + first_name string, + last_name string + ) +partitioned by (dept_id bigint) +stored by iceberg stored as orc +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: database:ice_native_view_db +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn1','ln1', 1) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn1','ln1', 1) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn2','ln2', 1) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn2','ln2', 1) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn3','ln3', 1) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn3','ln3', 1) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn4','ln4', 1) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn4','ln4', 1) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn5','ln5', 2) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn5','ln5', 2) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn6','ln6', 2) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn6','ln6', 2) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: insert into src_ice values ('fn7','ln7', 2) +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: insert into src_ice values ('fn7','ln7', 2) +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln1a' where first_name='fn1' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln1a' where first_name='fn1' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln2a' where first_name='fn2' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln2a' where first_name='fn2' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln3a' where first_name='fn3' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln3a' where first_name='fn3' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln4a' where first_name='fn4' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln4a' where first_name='fn4' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln5a' where first_name='fn5' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln5a' where first_name='fn5' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln6a' where first_name='fn6' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln6a' where first_name='fn6' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: update src_ice set last_name = 'ln7a' where first_name='fn7' +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: update src_ice set last_name = 'ln7a' where first_name='fn7' +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: delete from src_ice where last_name in ('ln1a', 'ln2a', 'ln7a') +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +#### A masked pattern was here #### +POSTHOOK: query: delete from src_ice where last_name in ('ln1a', 'ln2a', 'ln7a') +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +#### A masked pattern was here #### +PREHOOK: query: create view v_ice as select * from src_ice stored by iceberg +PREHOOK: type: CREATEVIEW +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: database:ice_native_view_db +PREHOOK: Output: ice_native_view_db@v_ice +POSTHOOK: query: create view v_ice as select * from src_ice stored by iceberg +POSTHOOK: type: CREATEVIEW +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: database:ice_native_view_db +POSTHOOK: Output: ice_native_view_db@v_ice +POSTHOOK: Lineage: v_ice.dept_id SIMPLE [(src_ice)src_ice.FieldSchema(name:dept_id, type:bigint, comment:null), ] +POSTHOOK: Lineage: v_ice.first_name SIMPLE [(src_ice)src_ice.FieldSchema(name:first_name, type:string, comment:null), ] +POSTHOOK: Lineage: v_ice.last_name SIMPLE [(src_ice)src_ice.FieldSchema(name:last_name, type:string, comment:null), ] +PREHOOK: query: select * from v_ice +PREHOOK: type: QUERY +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Input: ice_native_view_db@v_ice +#### A masked pattern was here #### +POSTHOOK: query: select * from v_ice +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Input: ice_native_view_db@v_ice +#### A masked pattern was here #### +fn3 ln3a 1 +fn4 ln4a 1 +fn5 ln5a 2 +fn6 ln6a 2 +PREHOOK: query: drop view v_ice +PREHOOK: type: DROPVIEW +PREHOOK: Input: ice_native_view_db@v_ice +PREHOOK: Output: ice_native_view_db@v_ice +POSTHOOK: query: drop view v_ice +POSTHOOK: type: DROPVIEW +POSTHOOK: Input: ice_native_view_db@v_ice +POSTHOOK: Output: ice_native_view_db@v_ice +PREHOOK: query: drop table src_ice +PREHOOK: type: DROPTABLE +PREHOOK: Input: ice_native_view_db@src_ice +PREHOOK: Output: database:ice_native_view_db +PREHOOK: Output: ice_native_view_db@src_ice +POSTHOOK: query: drop table src_ice +POSTHOOK: type: DROPTABLE +POSTHOOK: Input: ice_native_view_db@src_ice +POSTHOOK: Output: database:ice_native_view_db +POSTHOOK: Output: ice_native_view_db@src_ice +PREHOOK: query: use default +PREHOOK: type: SWITCHDATABASE +PREHOOK: Input: database:default +POSTHOOK: query: use default +POSTHOOK: type: SWITCHDATABASE +POSTHOOK: Input: database:default +PREHOOK: query: drop database ice_native_view_db cascade +PREHOOK: type: DROPDATABASE +PREHOOK: Input: database:ice_native_view_db +PREHOOK: Output: database:ice_native_view_db +POSTHOOK: query: drop database ice_native_view_db cascade +POSTHOOK: type: DROPDATABASE +POSTHOOK: Input: database:ice_native_view_db +POSTHOOK: Output: database:ice_native_view_db diff --git a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_gravitino.q.out b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_gravitino.q.out index 8bba659e8fd1..0c27b2780319 100644 --- a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_gravitino.q.out +++ b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_gravitino.q.out @@ -180,6 +180,75 @@ POSTHOOK: Input: ice_rest@ice_orc2 fn1 ln1 1 10 100 fn2 ln2 2 20 100 fn3 ln3 3 30 100 +PREHOOK: query: create view ice_v1 as select * from ice_orc1 stored by iceberg +PREHOOK: type: CREATEVIEW +PREHOOK: Input: ice_rest@ice_orc1 +PREHOOK: Output: database:ice_rest +PREHOOK: Output: ice_rest@ice_v1 +POSTHOOK: query: create view ice_v1 as select * from ice_orc1 stored by iceberg +POSTHOOK: type: CREATEVIEW +POSTHOOK: Input: ice_rest@ice_orc1 +POSTHOOK: Output: database:ice_rest +POSTHOOK: Output: ice_rest@ice_v1 +POSTHOOK: Lineage: ice_v1.company_id SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:company_id, type:bigint, comment:Transform: identity), ] +POSTHOOK: Lineage: ice_v1.dept_id SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:dept_id, type:bigint, comment:null), ] +POSTHOOK: Lineage: ice_v1.first_name SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:first_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v1.last_name SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:last_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v1.team_id SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:team_id, type:bigint, comment:null), ] +PREHOOK: query: create view ice_v2 as select * from ice_orc2 stored by iceberg +PREHOOK: type: CREATEVIEW +PREHOOK: Input: ice_rest@ice_orc2 +PREHOOK: Output: database:ice_rest +PREHOOK: Output: ice_rest@ice_v2 +POSTHOOK: query: create view ice_v2 as select * from ice_orc2 stored by iceberg +POSTHOOK: type: CREATEVIEW +POSTHOOK: Input: ice_rest@ice_orc2 +POSTHOOK: Output: database:ice_rest +POSTHOOK: Output: ice_rest@ice_v2 +POSTHOOK: Lineage: ice_v2.company_id SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:company_id, type:bigint, comment:Transform: identity), ] +POSTHOOK: Lineage: ice_v2.dept_id SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:dept_id, type:bigint, comment:null), ] +POSTHOOK: Lineage: ice_v2.first_name SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:first_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v2.last_name SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:last_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v2.team_id SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:team_id, type:bigint, comment:null), ] +PREHOOK: query: select * from ice_v1 +PREHOOK: type: QUERY +PREHOOK: Input: ice_rest@ice_orc1 +PREHOOK: Input: ice_rest@ice_v1 +#### A masked pattern was here #### +POSTHOOK: query: select * from ice_v1 +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_rest@ice_orc1 +POSTHOOK: Input: ice_rest@ice_v1 +#### A masked pattern was here #### +PREHOOK: query: select * from ice_v2 +PREHOOK: type: QUERY +PREHOOK: Input: ice_rest@ice_orc2 +PREHOOK: Input: ice_rest@ice_v2 +#### A masked pattern was here #### +POSTHOOK: query: select * from ice_v2 +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_rest@ice_orc2 +POSTHOOK: Input: ice_rest@ice_v2 +#### A masked pattern was here #### +fn1 ln1 1 10 100 +fn2 ln2 2 20 100 +fn3 ln3 3 30 100 +PREHOOK: query: drop view ice_v1 +PREHOOK: type: DROPVIEW +PREHOOK: Input: ice_rest@ice_v1 +PREHOOK: Output: ice_rest@ice_v1 +POSTHOOK: query: drop view ice_v1 +POSTHOOK: type: DROPVIEW +POSTHOOK: Input: ice_rest@ice_v1 +POSTHOOK: Output: ice_rest@ice_v1 +PREHOOK: query: drop view ice_v2 +PREHOOK: type: DROPVIEW +PREHOOK: Input: ice_rest@ice_v2 +PREHOOK: Output: ice_rest@ice_v2 +POSTHOOK: query: drop view ice_v2 +POSTHOOK: type: DROPVIEW +POSTHOOK: Input: ice_rest@ice_v2 +POSTHOOK: Output: ice_rest@ice_v2 PREHOOK: query: show tables PREHOOK: type: SHOWTABLES PREHOOK: Input: database:ice_rest diff --git a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_hms.q.out b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_hms.q.out index 409eb484480b..3a046d00a7f0 100644 --- a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_hms.q.out +++ b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_rest_catalog_hms.q.out @@ -180,6 +180,75 @@ POSTHOOK: Input: ice_rest@ice_orc2 fn1 ln1 1 10 100 fn2 ln2 2 20 100 fn3 ln3 3 30 100 +PREHOOK: query: create view ice_v1 as select * from ice_orc1 stored by iceberg +PREHOOK: type: CREATEVIEW +PREHOOK: Input: ice_rest@ice_orc1 +PREHOOK: Output: database:ice_rest +PREHOOK: Output: ice_rest@ice_v1 +POSTHOOK: query: create view ice_v1 as select * from ice_orc1 stored by iceberg +POSTHOOK: type: CREATEVIEW +POSTHOOK: Input: ice_rest@ice_orc1 +POSTHOOK: Output: database:ice_rest +POSTHOOK: Output: ice_rest@ice_v1 +POSTHOOK: Lineage: ice_v1.company_id SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:company_id, type:bigint, comment:Transform: identity), ] +POSTHOOK: Lineage: ice_v1.dept_id SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:dept_id, type:bigint, comment:null), ] +POSTHOOK: Lineage: ice_v1.first_name SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:first_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v1.last_name SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:last_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v1.team_id SIMPLE [(ice_orc1)ice_orc1.FieldSchema(name:team_id, type:bigint, comment:null), ] +PREHOOK: query: create view ice_v2 as select * from ice_orc2 stored by iceberg +PREHOOK: type: CREATEVIEW +PREHOOK: Input: ice_rest@ice_orc2 +PREHOOK: Output: database:ice_rest +PREHOOK: Output: ice_rest@ice_v2 +POSTHOOK: query: create view ice_v2 as select * from ice_orc2 stored by iceberg +POSTHOOK: type: CREATEVIEW +POSTHOOK: Input: ice_rest@ice_orc2 +POSTHOOK: Output: database:ice_rest +POSTHOOK: Output: ice_rest@ice_v2 +POSTHOOK: Lineage: ice_v2.company_id SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:company_id, type:bigint, comment:Transform: identity), ] +POSTHOOK: Lineage: ice_v2.dept_id SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:dept_id, type:bigint, comment:null), ] +POSTHOOK: Lineage: ice_v2.first_name SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:first_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v2.last_name SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:last_name, type:string, comment:null), ] +POSTHOOK: Lineage: ice_v2.team_id SIMPLE [(ice_orc2)ice_orc2.FieldSchema(name:team_id, type:bigint, comment:null), ] +PREHOOK: query: select * from ice_v1 +PREHOOK: type: QUERY +PREHOOK: Input: ice_rest@ice_orc1 +PREHOOK: Input: ice_rest@ice_v1 +#### A masked pattern was here #### +POSTHOOK: query: select * from ice_v1 +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_rest@ice_orc1 +POSTHOOK: Input: ice_rest@ice_v1 +#### A masked pattern was here #### +PREHOOK: query: select * from ice_v2 +PREHOOK: type: QUERY +PREHOOK: Input: ice_rest@ice_orc2 +PREHOOK: Input: ice_rest@ice_v2 +#### A masked pattern was here #### +POSTHOOK: query: select * from ice_v2 +POSTHOOK: type: QUERY +POSTHOOK: Input: ice_rest@ice_orc2 +POSTHOOK: Input: ice_rest@ice_v2 +#### A masked pattern was here #### +fn1 ln1 1 10 100 +fn2 ln2 2 20 100 +fn3 ln3 3 30 100 +PREHOOK: query: drop view ice_v1 +PREHOOK: type: DROPVIEW +PREHOOK: Input: ice_rest@ice_v1 +PREHOOK: Output: ice_rest@ice_v1 +POSTHOOK: query: drop view ice_v1 +POSTHOOK: type: DROPVIEW +POSTHOOK: Input: ice_rest@ice_v1 +POSTHOOK: Output: ice_rest@ice_v1 +PREHOOK: query: drop view ice_v2 +PREHOOK: type: DROPVIEW +PREHOOK: Input: ice_rest@ice_v2 +PREHOOK: Output: ice_rest@ice_v2 +POSTHOOK: query: drop view ice_v2 +POSTHOOK: type: DROPVIEW +POSTHOOK: Input: ice_rest@ice_v2 +POSTHOOK: Output: ice_rest@ice_v2 PREHOOK: query: show tables PREHOOK: type: SHOWTABLES PREHOOK: Input: database:ice_rest diff --git a/itests/hive-iceberg/src/test/java/org/apache/hive/TestHiveRESTCatalogClientITBase.java b/itests/hive-iceberg/src/test/java/org/apache/hive/TestHiveRESTCatalogClientITBase.java index fd30223c9934..6ba5ef472e2f 100644 --- a/itests/hive-iceberg/src/test/java/org/apache/hive/TestHiveRESTCatalogClientITBase.java +++ b/itests/hive-iceberg/src/test/java/org/apache/hive/TestHiveRESTCatalogClientITBase.java @@ -30,6 +30,7 @@ import org.apache.hadoop.hive.metastore.api.PrincipalType; import org.apache.hadoop.hive.metastore.api.SerDeInfo; import org.apache.hadoop.hive.metastore.api.StorageDescriptor; +import org.apache.hadoop.hive.metastore.TableType; import org.apache.hadoop.hive.metastore.api.Table; import org.apache.hadoop.hive.metastore.conf.MetastoreConf; import org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat; @@ -38,13 +39,15 @@ import org.apache.hadoop.hive.ql.metadata.HiveStorageHandler; import org.apache.hadoop.hive.ql.metadata.HiveUtils; import org.apache.hadoop.mapred.TextInputFormat; +import org.apache.iceberg.BaseMetastoreTableOperations; import org.apache.iceberg.CatalogUtil; import org.apache.iceberg.PartitionSpec; import org.apache.iceberg.PartitionSpecParser; import org.apache.iceberg.Schema; import org.apache.iceberg.TableProperties; -import org.apache.iceberg.hive.IcebergCatalogProperties; import org.apache.iceberg.hive.HiveSchemaUtil; +import org.apache.iceberg.hive.IcebergCatalogProperties; +import org.apache.iceberg.hive.NativeIcebergViewSupport; import org.apache.iceberg.rest.extension.HiveRESTCatalogServerExtension; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -67,11 +70,14 @@ public abstract class TestHiveRESTCatalogClientITBase { static final String DB_NAME = "ice_db"; + static final String VIEW_DB_NAME = "ice_db_view"; + static final String NATIVE_VIEW_NAME = "native_rest_v"; static final String TABLE_NAME = "ice_tbl"; static final String CATALOG_NAME = "ice01"; static final String HIVE_ICEBERG_STORAGE_HANDLER = "org.apache.iceberg.mr.hive.HiveIcebergStorageHandler"; static final String REST_CATALOG_PREFIX = String.format("%s%s.", IcebergCatalogProperties.CATALOG_CONFIG_PREFIX, CATALOG_NAME); + static final String ICEBERG_VIEW_TYPE_VALUE = "iceberg-view"; HiveConf hiveConf; Configuration conf; @@ -195,6 +201,50 @@ public void testIceberg() throws Exception { Assertions.assertFalse(msClient.getAllDatabases(CATALOG_NAME).contains(DB_NAME)); } + @Test + public void testNativeIcebergView() throws Exception { + Database db = new Database(); + db.setCatalogName(CATALOG_NAME); + db.setName(VIEW_DB_NAME); + db.setOwnerType(PrincipalType.USER); + db.setOwnerName(System.getProperty("user.name")); + String warehouseDir = MetastoreConf.get(conf, MetastoreConf.ConfVars.WAREHOUSE_EXTERNAL.getVarname()); + db.setLocationUri(warehouseDir + "/" + VIEW_DB_NAME + ".db"); + hive.createDatabase(db, true); + + List cols = Collections.singletonList(new FieldSchema("x", "int", "")); + NativeIcebergViewSupport.createOrReplaceNativeView( + hiveConf, + VIEW_DB_NAME, + NATIVE_VIEW_NAME, + cols, + "select 1 as x", + null, + "rest-native-view", + false, + false); + + GetTableRequest getTableRequest = new GetTableRequest(); + getTableRequest.setCatName(CATALOG_NAME); + getTableRequest.setDbName(VIEW_DB_NAME); + getTableRequest.setTblName(NATIVE_VIEW_NAME); + Table view = msClient.getTable(getTableRequest); + Assertions.assertEquals(TableType.VIRTUAL_VIEW.name(), view.getTableType()); + String tableTypeProp = view.getParameters().get(BaseMetastoreTableOperations.TABLE_TYPE_PROP); + Assertions.assertNotNull(tableTypeProp); + Assertions.assertEquals(ICEBERG_VIEW_TYPE_VALUE, tableTypeProp.toLowerCase()); + + Assertions.assertTrue(msClient.tableExists(CATALOG_NAME, VIEW_DB_NAME, NATIVE_VIEW_NAME)); + + List names = msClient.getTables(CATALOG_NAME, VIEW_DB_NAME, "*"); + Assertions.assertTrue(names.contains(NATIVE_VIEW_NAME)); + + msClient.dropTable(CATALOG_NAME, VIEW_DB_NAME, NATIVE_VIEW_NAME); + Assertions.assertFalse(msClient.tableExists(CATALOG_NAME, VIEW_DB_NAME, NATIVE_VIEW_NAME)); + + msClient.dropDatabase(VIEW_DB_NAME); + } + private static Table createPartitionedTable(IMetaStoreClient db, String catName, String dbName, String tableName, Map tableParameters) throws Exception { db.dropTable(catName, dbName, tableName); diff --git a/itests/src/test/resources/testconfiguration.properties b/itests/src/test/resources/testconfiguration.properties index 92e80e6a822d..bf1aaa986911 100644 --- a/itests/src/test/resources/testconfiguration.properties +++ b/itests/src/test/resources/testconfiguration.properties @@ -433,6 +433,7 @@ iceberg.llap.query.files=\ iceberg_create_locally_zordered_table.q,\ iceberg_merge_delete_files.q,\ iceberg_merge_files.q,\ + iceberg_native_view.q,\ llap_iceberg_read_orc.q,\ llap_iceberg_read_parquet.q,\ puffin_col_stats_with_time_travel.q,\ @@ -483,6 +484,7 @@ iceberg.llap.only.query.files=\ iceberg_create_locally_zordered_table.q,\ iceberg_merge_delete_files.q,\ iceberg_merge_files.q,\ + iceberg_native_view.q,\ llap_iceberg_read_orc.q,\ llap_iceberg_read_parquet.q,\ puffin_col_stats_with_time_travel.q diff --git a/parser/src/java/org/apache/hadoop/hive/ql/parse/HiveParser.g b/parser/src/java/org/apache/hadoop/hive/ql/parse/HiveParser.g index 0033aa3251c6..4e31d7bef60c 100644 --- a/parser/src/java/org/apache/hadoop/hive/ql/parse/HiveParser.g +++ b/parser/src/java/org/apache/hadoop/hive/ql/parse/HiveParser.g @@ -1653,6 +1653,7 @@ createViewStatement tablePropertiesPrefixed? KW_AS selectStatementWithCTE + (viewStoredFormat=tableFileFormat)? -> ^(TOK_CREATEVIEW $name orReplace? ifNotExists? columnNameCommentList? @@ -1660,6 +1661,7 @@ createViewStatement viewPartition? tablePropertiesPrefixed? selectStatementWithCTE + $viewStoredFormat? ) ; diff --git a/parser/src/test/org/apache/hadoop/hive/ql/parse/TestParseDefault.java b/parser/src/test/org/apache/hadoop/hive/ql/parse/TestParseDefault.java index 45a3a9e74180..7a612a0ec41a 100644 --- a/parser/src/test/org/apache/hadoop/hive/ql/parse/TestParseDefault.java +++ b/parser/src/test/org/apache/hadoop/hive/ql/parse/TestParseDefault.java @@ -90,6 +90,15 @@ public void testParseStructNamedDefault() throws Exception { assertFalse(tree.dump(), tree.toStringTree().contains("tok_default_value")); } + @Test + public void testParseCreateViewStoredByIceberg() throws Exception { + ASTNode tree = parseDriver.parse( + "create view v1 as select * from t stored by iceberg", null).getTree(); + assertTrue(tree.dump(), tree.toStringTree().contains("tok_createview")); + assertTrue(tree.dump(), tree.toStringTree().contains("tok_storagehandler")); + assertTrue(tree.dump(), tree.toStringTree().contains("iceberg")); + } + @Test public void testParseStructFieldNamedDefault() throws Exception { ASTNode tree = parseDriver.parse( diff --git a/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewAnalyzer.java b/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewAnalyzer.java index 60b123dbb7bd..61a19adce57f 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewAnalyzer.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewAnalyzer.java @@ -75,6 +75,17 @@ public void analyzeInternal(ASTNode root) throws SemanticException { List partitionColumnNames = children.containsKey(HiveParser.TOK_VIEWPARTCOLS) ? getColumnNames((ASTNode) children.remove(HiveParser.TOK_VIEWPARTCOLS).getChild(0)) : null; + ASTNode storageClause = null; + for (int storageTokenType : new int[] { HiveParser.TOK_STORAGEHANDLER, HiveParser.TOK_TABLEFILEFORMAT, + HiveParser.TOK_FILEFORMAT_GENERIC }) { + ASTNode storageClauseCandidate = children.remove(storageTokenType); + if (storageClauseCandidate != null) { + storageClause = storageClauseCandidate; + break; + } + } + boolean icebergNativeView = validateOptionalViewStorageClause(storageClause); + assert children.isEmpty(); if (ifNotExists && orReplace) { @@ -94,7 +105,7 @@ public void analyzeInternal(ASTNode root) throws SemanticException { List partitionColumns = getPartitionColumns(partitionColumnNames); setColumnAccessInfo(analyzer.getColumnAccessInfo()); CreateViewDesc desc = new CreateViewDesc(fqViewName, schema, comment, properties, partitionColumnNames, - ifNotExists, orReplace, originalText, expandedText, partitionColumns); + ifNotExists, orReplace, originalText, expandedText, partitionColumns, icebergNativeView); validateCreateView(desc, analyzer); rootTasks.add(TaskFactory.get(new DDLWork(getInputs(), getOutputs(), desc))); @@ -191,6 +202,40 @@ private List getPartitionColumns(List partitionColumnNames) return partitionColumnsCopy; } + /** + * Optional trailing {@code tableFileFormat} on CREATE VIEW: only {@code STORED BY ICEBERG} is allowed + * (no serde properties or {@code STORED AS} tail). + */ + private boolean validateOptionalViewStorageClause(ASTNode storageRoot) throws SemanticException { + if (storageRoot == null) { + return false; + } + if (storageRoot.getToken().getType() == HiveParser.TOK_TABLEFILEFORMAT + || storageRoot.getToken().getType() == HiveParser.TOK_FILEFORMAT_GENERIC) { + throw new SemanticException(ErrorMsg.VIEW_STORAGE_HANDLER_UNSUPPORTED.getMsg( + storageRoot.toStringTree())); + } + if (storageRoot.getToken().getType() != HiveParser.TOK_STORAGEHANDLER) { + throw new SemanticException(ErrorMsg.VIEW_STORAGE_HANDLER_UNSUPPORTED.getMsg( + storageRoot.toStringTree())); + } + if (storageRoot.getChildCount() != 1) { + throw new SemanticException(ErrorMsg.VIEW_STORAGE_HANDLER_UNSUPPORTED.getMsg( + "STORED BY ICEBERG views do not support WITH SERDEPROPERTIES or STORED AS")); + } + ASTNode handlerNode = (ASTNode) storageRoot.getChild(0); + if (handlerNode.getToken().getType() == HiveParser.StringLiteral) { + throw new SemanticException(ErrorMsg.VIEW_STORAGE_HANDLER_UNSUPPORTED.getMsg( + "STORED BY with string literal handler")); + } + String handler = handlerNode.getText(); + if (handler == null || !handler.equalsIgnoreCase("iceberg")) { + throw new SemanticException(ErrorMsg.VIEW_STORAGE_HANDLER_UNSUPPORTED.getMsg( + "STORED BY " + handler)); + } + return true; + } + private void validateCreateView(CreateViewDesc desc, SemanticAnalyzer analyzer) throws SemanticException { try { validateTablesUsed(analyzer); diff --git a/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewDesc.java b/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewDesc.java index e71cbce773f1..bbdf0df0da24 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewDesc.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewDesc.java @@ -34,12 +34,16 @@ public class CreateViewDesc extends AbstractCreateViewDesc { private static final long serialVersionUID = 1L; + /** HMS table property set when the view is declared with {@code STORED BY ICEBERG} (native Iceberg view). */ + public static final String ICEBERG_NATIVE_VIEW_PROPERTY = "hive.iceberg.native.view"; + private final String comment; private final Map properties; private final List partitionColumnNames; private final boolean ifNotExists; private final boolean replace; private final List partitionColumns; + private final boolean icebergNativeView; private ReplicationSpec replicationSpec = null; private String ownerName = null; @@ -47,6 +51,13 @@ public class CreateViewDesc extends AbstractCreateViewDesc { public CreateViewDesc(String viewName, List schema, String comment, Map properties, List partitionColumnNames, boolean ifNotExists, boolean replace, String originalText, String expandedText, List partitionColumns) { + this(viewName, schema, comment, properties, partitionColumnNames, ifNotExists, replace, originalText, + expandedText, partitionColumns, false); + } + + public CreateViewDesc(String viewName, List schema, String comment, Map properties, + List partitionColumnNames, boolean ifNotExists, boolean replace, String originalText, + String expandedText, List partitionColumns, boolean icebergNativeView) { super(viewName, schema, originalText, expandedText); this.comment = comment; this.properties = properties; @@ -54,6 +65,7 @@ public CreateViewDesc(String viewName, List schema, String comment, this.ifNotExists = ifNotExists; this.replace = replace; this.partitionColumns = partitionColumns; + this.icebergNativeView = icebergNativeView; } @Explain(displayName = "partition columns") @@ -89,6 +101,11 @@ public boolean isReplace() { return replace; } + @Explain(displayName = "iceberg native view", displayOnlyOnTrue = true) + public boolean isIcebergNativeView() { + return icebergNativeView; + } + /** * @param replicationSpec Sets the replication spec governing this create. * This parameter will have meaningful values only for creates happening as a result of a replication. diff --git a/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewOperation.java b/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewOperation.java index b6a41a8d4fb8..f8eed31cf1e1 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewOperation.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/ddl/view/create/CreateViewOperation.java @@ -34,6 +34,8 @@ import org.apache.hadoop.hive.ql.parse.HiveTableName; import org.apache.hadoop.hive.ql.parse.StorageFormat; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Map; /** @@ -46,6 +48,10 @@ public CreateViewOperation(DDLOperationContext context, CreateViewDesc desc) { @Override public int execute() throws HiveException { + if (desc.isIcebergNativeView()) { + return executeIcebergNativeView(); + } + Table oldview = context.getDb().getTable(desc.getViewName(), false); if (oldview != null) { boolean isReplace = desc.isReplace(); @@ -82,6 +88,11 @@ public int execute() throws HiveException { if (desc.getProperties() != null) { oldview.getTTable().getParameters().putAll(desc.getProperties()); } + if (desc.isIcebergNativeView()) { + oldview.setProperty(CreateViewDesc.ICEBERG_NATIVE_VIEW_PROPERTY, "true"); + } else { + oldview.getParameters().remove(CreateViewDesc.ICEBERG_NATIVE_VIEW_PROPERTY); + } oldview.setPartCols(desc.getPartitionColumns()); oldview.checkValidity(null); @@ -105,6 +116,118 @@ public int execute() throws HiveException { return 0; } + /** + * Creates a native Iceberg view via the configured default Iceberg catalog (HiveCatalog, REST + * catalog with {@link org.apache.iceberg.view.ViewCatalog}, etc.). + */ + private int executeIcebergNativeView() throws HiveException { + Table oldview = context.getDb().getTable(desc.getViewName(), false); + + if (oldview != null) { + boolean isReplace = desc.isReplace(); + + if (desc.getReplicationSpec().isInReplicationScope()) { + Map dbParams = context.getDb().getDatabase(oldview.getDbName()).getParameters(); + if (desc.getReplicationSpec().allowEventReplacementInto(dbParams)) { + isReplace = true; + } else { + LOG.debug("DDLTask: Create Iceberg native view is skipped as view {} is newer than update", + desc.getViewName()); + return 0; + } + } + + if (!isReplace) { + if (desc.getIfNotExists()) { + return 0; + } + throw new HiveException(ErrorMsg.TABLE_ALREADY_EXISTS.getMsg(desc.getViewName())); + } + } + + TableName name = HiveTableName.of(desc.getViewName()); + boolean replace = oldview != null || desc.isReplace(); + + try { + boolean created = invokeNativeIcebergViewSupport( + name.getDb(), + name.getTable(), + replace, + desc.getIfNotExists()); + if (!created) { + return 0; + } + } catch (HiveException e) { + throw e; + } catch (Exception e) { + throw new HiveException(e); + } + + Table viewTbl = context.getDb().getTable(desc.getViewName(), false); + if (viewTbl != null) { + DDLUtils.addIfAbsentByName(new WriteEntity(viewTbl, WriteEntity.WriteType.DDL_NO_LOCK), + context.getWork().getOutputs()); + DataContainer dc = new DataContainer(viewTbl.getTTable()); + context.getQueryState().getLineageState().setLineage(new Path(desc.getViewName()), dc, viewTbl.getCols()); + } + return 0; + } + + /** + * Delegates to {@code org.apache.iceberg.hive.NativeIcebergViewSupport} when the Iceberg catalog + * module is on the classpath (avoids a Maven reactor cycle between {@code hive-exec} and + * {@code hive-iceberg-catalog}). + */ + private boolean invokeNativeIcebergViewSupport( + String databaseName, String viewName, boolean replace, boolean ifNotExists) + throws Exception { + Class supportClass; + Method method; + try { + supportClass = Class.forName("org.apache.iceberg.hive.NativeIcebergViewSupport"); + method = + supportClass.getMethod( + "createOrReplaceNativeView", + org.apache.hadoop.conf.Configuration.class, + String.class, + String.class, + java.util.List.class, + String.class, + Map.class, + String.class, + boolean.class, + boolean.class); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new HiveException( + "Native Iceberg views require hive-iceberg-catalog on the classpath " + + "(org.apache.iceberg.hive.NativeIcebergViewSupport is missing).", + e); + } + try { + return (Boolean) + method.invoke( + null, + context.getConf(), + databaseName, + viewName, + desc.getSchema(), + desc.getExpandedText(), + desc.getProperties(), + desc.getComment(), + replace, + ifNotExists); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause() == null ? e : e.getCause(); + if (cause.getClass().getName().endsWith("AlreadyExistsException")) { + throw new HiveException(ErrorMsg.TABLE_ALREADY_EXISTS.getMsg(desc.getViewName()), cause); + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new HiveException(cause); + } + } + private Table createViewObject() throws HiveException { TableName name = HiveTableName.of(desc.getViewName()); Table view = new Table(name.getDb(), name.getTable()); @@ -121,6 +244,9 @@ private Table createViewObject() throws HiveException { if (desc.getProperties() != null) { view.getParameters().putAll(desc.getProperties()); } + if (desc.isIcebergNativeView()) { + view.setProperty(CreateViewDesc.ICEBERG_NATIVE_VIEW_PROPERTY, "true"); + } if (!CollectionUtils.isEmpty(desc.getPartitionColumns())) { view.setPartCols(desc.getPartitionColumns());