This is an automated email from the ASF dual-hosted git repository.

difin pushed a commit to branch iceberg_native_views
in repository https://gitbox.apache.org/repos/asf/hive.git

commit 984a425a083c5c6b96fb39a48ab3411f5382f150
Author: Dmitriy Fingerman <[email protected]>
AuthorDate: Thu Apr 23 15:49:20 2026 -0400

    HIVE-29578: Iceberg: support for Iceberg native views
---
 .../java/org/apache/hadoop/hive/ql/ErrorMsg.java   |   2 +
 .../apache/iceberg/hive/HiveViewOperations.java    |   2 +-
 .../org/apache/iceberg/hive/MetastoreUtil.java     |  64 ++++++
 .../iceberg/hive/NativeIcebergViewSupport.java     | 124 ++++++++++++
 .../iceberg/hive/client/HiveRESTCatalogClient.java |  49 ++++-
 .../iceberg/hive/TestNativeIcebergViewSupport.java | 136 +++++++++++++
 .../test/queries/positive/iceberg_native_view.q    |  38 ++++
 .../positive/iceberg_rest_catalog_gravitino.q      |  20 +-
 .../queries/positive/iceberg_rest_catalog_hms.q    |  20 +-
 .../positive/llap/iceberg_native_view.q.out        | 223 +++++++++++++++++++++
 .../llap/iceberg_rest_catalog_gravitino.q.out      |  69 +++++++
 .../positive/llap/iceberg_rest_catalog_hms.q.out   |  69 +++++++
 .../hive/TestHiveRESTCatalogClientITBase.java      |  52 ++++-
 .../test/resources/testconfiguration.properties    |   2 +
 .../org/apache/hadoop/hive/ql/parse/HiveParser.g   |   2 +
 .../hadoop/hive/ql/parse/TestParseDefault.java     |   9 +
 .../ql/ddl/view/create/CreateViewAnalyzer.java     |  47 ++++-
 .../hive/ql/ddl/view/create/CreateViewDesc.java    |  17 ++
 .../ql/ddl/view/create/CreateViewOperation.java    | 126 ++++++++++++
 19 files changed, 1053 insertions(+), 18 deletions(-)

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 57f7cadac18..de96936df4c 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 c0c959974a6..10f6736c353 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 95e1e5b3662..6ba49b7337d 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 00000000000..ace4ea703b8
--- /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<FieldSchema> fieldSchemas, String viewSql, Map<String, String> 
tblProperties, String comment,
+      boolean replace, boolean ifNotExists) throws Exception {
+
+    TableIdentifier identifier = TableIdentifier.of(databaseName, viewName);
+    String catalogName = IcebergCatalogProperties.getCatalogName(conf);
+    Map<String, String> 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<FieldSchema> 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<String, String> tblProperties, String comment) {
+    ViewBuilder viewBuilder = builder;
+    if (comment != null && !comment.isEmpty()) {
+      viewBuilder = viewBuilder.withProperty("comment", comment);
+    }
+    if (tblProperties != null) {
+      for (Map.Entry<String, String> 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 4390d5a0bca..2194b9950e3 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<String> 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<String> 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<String> 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 00000000000..daf187b5c09
--- /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<FieldSchema> 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<String, String> 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<FieldSchema> 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<FieldSchema> 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 00000000000..4c954f786f0
--- /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 81982ca44d9..6b73ff2aa13 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 27f23122240..eefd5bd5ce9 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 00000000000..ae1a55ef129
--- /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 8bba659e8fd..0c27b278031 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 409eb484480..3a046d00a7f 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 fd30223c993..6ba5ef472e2 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<FieldSchema> 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<String> 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<String, String> 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 64a761acfdd..fd38e9c1f2a 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,\
@@ -484,6 +485,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 0033aa3251c..4e31d7bef60 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 45a3a9e7418..7a612a0ec41 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 60b123dbb7b..61a19adce57 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<String> 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<FieldSchema> 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<FieldSchema> getPartitionColumns(List<String> 
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 e71cbce773f..bbdf0df0da2 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<String, String> properties;
   private final List<String> partitionColumnNames;
   private final boolean ifNotExists;
   private final boolean replace;
   private final List<FieldSchema> 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<FieldSchema> schema, String 
comment, Map<String, String> properties,
       List<String> partitionColumnNames, boolean ifNotExists, boolean replace, 
String originalText,
       String expandedText, List<FieldSchema> partitionColumns) {
+    this(viewName, schema, comment, properties, partitionColumnNames, 
ifNotExists, replace, originalText,
+        expandedText, partitionColumns, false);
+  }
+
+  public CreateViewDesc(String viewName, List<FieldSchema> schema, String 
comment, Map<String, String> properties,
+      List<String> partitionColumnNames, boolean ifNotExists, boolean replace, 
String originalText,
+      String expandedText, List<FieldSchema> partitionColumns, boolean 
icebergNativeView) {
     super(viewName, schema, originalText, expandedText);
     this.comment = comment;
     this.properties = properties;
@@ -54,6 +65,7 @@ public CreateViewDesc(String viewName, List<FieldSchema> 
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 b6a41a8d4fb..f8eed31cf1e 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<String, String> 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());

Reply via email to