This is an automated email from the ASF dual-hosted git repository.
morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-3.1 by this push:
new d96c5fdcbc6 branch-3.1 [feature](iceberg catalog) support iceberg view
query #51376 (#52806)
d96c5fdcbc6 is described below
commit d96c5fdcbc60e0657d2a7b88ffb8e38c7edbd362
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Jul 7 13:50:16 2025 +0800
branch-3.1 [feature](iceberg catalog) support iceberg view query #51376
(#52806)
bp #51376
---------
Co-authored-by: heguanhui <[email protected]>
---
.../create_preinstalled_scripts/iceberg/run14.sql | 87 +++++++++
.../main/java/org/apache/doris/common/Config.java | 3 +
.../main/java/org/apache/doris/catalog/Env.java | 6 +-
.../apache/doris/datasource/ExternalCatalog.java | 11 ++
.../doris/datasource/hive/HMSExternalTable.java | 4 +-
.../datasource/iceberg/IcebergExternalCatalog.java | 12 +-
.../datasource/iceberg/IcebergExternalTable.java | 108 ++++++++++-
.../datasource/iceberg/IcebergMetadataCache.java | 41 +++++
.../datasource/iceberg/IcebergMetadataOps.java | 106 ++++++++++-
.../doris/datasource/iceberg/IcebergUtils.java | 81 +++++---
.../iceberg/source/IcebergApiSource.java | 11 ++
.../datasource/operations/ExternalMetadataOps.java | 33 ++++
.../doris/nereids/rules/analysis/BindRelation.java | 43 ++++-
.../org/apache/doris/planner/IcebergTableSink.java | 3 +
.../java/org/apache/doris/qe/ShowExecutor.java | 8 +
.../iceberg/test_iceberg_view_query_p0.out | Bin 0 -> 1033 bytes
.../iceberg/test_iceberg_view_query_p0.groovy | 203 +++++++++++++++++++++
17 files changed, 712 insertions(+), 48 deletions(-)
diff --git
a/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/iceberg/run14.sql
b/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/iceberg/run14.sql
new file mode 100644
index 00000000000..da4f0035c65
--- /dev/null
+++
b/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/iceberg/run14.sql
@@ -0,0 +1,87 @@
+use demo.test_db;
+drop table if exists test_db.t_partitioned_table;
+drop table if exists test_db.t_unpartitioned_table;
+drop table if exists test_db.t_product_info;
+drop table if exists test_db.t_sales_record;
+CREATE TABLE IF NOT EXISTS t_partitioned_table (
+ col1 int,
+ col2 string,
+ col3 int,
+ col4 string,
+ col5 string
+) USING iceberg
+PARTITIONED BY (col5);
+CREATE TABLE IF NOT EXISTS t_unpartitioned_table (
+ col1 INT,
+ col2 VARCHAR(100),
+ col3 DECIMAL(10, 2)
+) USING iceberg;
+CREATE TABLE IF NOT EXISTS t_product_info (
+ product_id INT,
+ product_name VARCHAR(100),
+ price DECIMAL(10, 2)
+) USING iceberg;
+CREATE TABLE IF NOT EXISTS t_sales_record (
+ sales_id INT,
+ order_id INT,
+ product_id INT,
+ quantity_sold INT,
+ sale_date DATE
+) USING iceberg;
+INSERT INTO t_partitioned_table (col1, col2, col3, col4, col5)
+VALUES
+(1, 'Alice', 25, 'Female', 'New York'),
+(2, 'Bob', 30, 'Male', 'Los Angeles'),
+(3, 'Charlie', 35, 'Male', 'Chicago'),
+(4, 'David', 22, 'Male', 'Houston'),
+(5, 'Eve', 28, 'Female', 'Phoenix');
+INSERT INTO t_unpartitioned_table (col1, col2, col3)
+VALUES
+(1001, 'Product A', 20.00),
+(1002, 'Product B', 30.00),
+(1003, 'Product C', 40.00),
+(1004, 'Product D', 50.00),
+(1005, 'Product E', 60.00);
+INSERT INTO t_product_info (product_id, product_name, price)
+VALUES
+(1001, 'Product A', 20.00),
+(1002, 'Product B', 30.00),
+(1003, 'Product C', 40.00),
+(1004, 'Product D', 50.00),
+(1005, 'Product E', 60.00);
+INSERT INTO t_sales_record (sales_id, order_id, product_id, quantity_sold,
sale_date)
+VALUES
+(7001, 101, 1001, 2, date '2024-01-01'),
+(7002, 102, 1002, 3, date '2024-01-02'),
+(7003, 103, 1003, 1, date '2024-01-03'),
+(7004, 104, 1001, 1, date '2024-01-04'),
+(7005, 105, 1004, 4, date '2024-01-05');
+create view v_with_unpartitioned_table
+as
+select
+ *
+from
+ t_unpartitioned_table;
+create view v_with_partitioned_table
+ as
+ select * from t_partitioned_table order by col1;
+create view v_with_partitioned_column
+as
+select
+ col5
+from
+ t_partitioned_table;
+create view v_with_joint_table
+as
+SELECT
+ pi.product_name,
+ sr.quantity_sold,
+ sr.sale_date
+FROM
+ t_product_info pi
+ JOIN
+ t_sales_record sr
+ ON pi.product_id = sr.product_id
+WHERE
+ sr.sale_date BETWEEN '2024-01-01' AND '2024-01-03'
+ AND sr.quantity_sold > 1;
diff --git a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
index cb41a970ace..5fcb5165d38 100644
--- a/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
+++ b/fe/fe-common/src/main/java/org/apache/doris/common/Config.java
@@ -2266,6 +2266,9 @@ public class Config extends ConfigBase {
@ConfField(mutable = true)
public static boolean enable_query_hive_views = true;
+ @ConfField(mutable = true)
+ public static boolean enable_query_iceberg_views = true;
+
/**
* If set to true, doris will automatically synchronize hms metadata to
the cache in fe.
*/
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index a6f150d1949..2e91f173007 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -4190,10 +4190,10 @@ public class Env {
sb.append("\n)");
} else if (table.getType() == TableType.ICEBERG_EXTERNAL_TABLE) {
addTableComment(table, sb);
- org.apache.iceberg.Table icebergTable = ((IcebergExternalTable)
table).getIcebergTable();
- sb.append("\nLOCATION
'").append(icebergTable.location()).append("'");
+ IcebergExternalTable icebergExternalTable = (IcebergExternalTable)
table;
+ sb.append("\nLOCATION
'").append(icebergExternalTable.location()).append("'");
sb.append("\nPROPERTIES (");
- Iterator<Entry<String, String>> iterator =
icebergTable.properties().entrySet().iterator();
+ Iterator<Entry<String, String>> iterator =
icebergExternalTable.properties().entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, String> prop = iterator.next();
sb.append("\n \"").append(prop.getKey()).append("\" =
\"").append(prop.getValue()).append("\"");
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
index 29287dc57b6..bed32c62b85 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java
@@ -1282,4 +1282,15 @@ public abstract class ExternalCatalog
public ThreadPoolExecutor getThreadPoolExecutor() {
return threadPoolWithPreAuth;
}
+
+ /**
+ * Check if an external view exists.
+ * @param dbName
+ * @param viewName
+ * @return
+ */
+ public boolean viewExists(String dbName, String viewName) {
+ throw new UnsupportedOperationException("View is not supported.");
+ }
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalTable.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalTable.java
index 4504e2a1f34..f944c704abe 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalTable.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HMSExternalTable.java
@@ -637,7 +637,8 @@ public class HMSExternalTable extends ExternalTable
implements MTMVRelatedTableI
}
private Optional<SchemaCacheValue> getIcebergSchema(SchemaCacheKey key) {
- return IcebergUtils.loadSchemaCacheValue(catalog, dbName, name,
((IcebergSchemaCacheKey) key).getSchemaId());
+ return IcebergUtils.loadSchemaCacheValue(
+ catalog, dbName, name, ((IcebergSchemaCacheKey)
key).getSchemaId(), isView());
}
private Optional<SchemaCacheValue> getHudiSchema(SchemaCacheKey key) {
@@ -1143,4 +1144,5 @@ public class HMSExternalTable extends ExternalTable
implements MTMVRelatedTableI
makeSureInitialized();
return dlaTable.isValidRelatedTable();
}
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalCatalog.java
index 41149370097..4aeb96f6e37 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalCatalog.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalCatalog.java
@@ -106,7 +106,12 @@ public abstract class IcebergExternalCatalog extends
ExternalCatalog {
@Override
public List<String> listTableNames(SessionContext ctx, String dbName) {
makeSureInitialized();
- return metadataOps.listTableNames(dbName);
+ // On the Doris side, the result of SHOW TABLES for Iceberg external
tables includes both tables and views,
+ // so the combined set of tables and views is used here.
+ List<String> tableNames = metadataOps.listTableNames(dbName);
+ List<String> viewNames = metadataOps.listViewNames(dbName);
+ tableNames.addAll(viewNames);
+ return tableNames;
}
@Override
@@ -121,4 +126,9 @@ public abstract class IcebergExternalCatalog extends
ExternalCatalog {
Map<String, String> properties = catalogProperty.getHadoopProperties();
conf.set(Constants.AWS_CREDENTIALS_PROVIDER,
PropertyConverter.getAWSCredentialsProviders(properties));
}
+
+ @Override
+ public boolean viewExists(String dbName, String viewName) {
+ return metadataOps.viewExists(dbName, viewName);
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalTable.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalTable.java
index 0813474b45f..bb947385f3b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalTable.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergExternalTable.java
@@ -28,6 +28,7 @@ import org.apache.doris.common.DdlException;
import org.apache.doris.datasource.ExternalSchemaCache.SchemaCacheKey;
import org.apache.doris.datasource.ExternalTable;
import org.apache.doris.datasource.SchemaCacheValue;
+import org.apache.doris.datasource.mvcc.EmptyMvccSnapshot;
import org.apache.doris.datasource.mvcc.MvccSnapshot;
import org.apache.doris.datasource.mvcc.MvccTable;
import org.apache.doris.datasource.systable.SupportedSysTables;
@@ -48,9 +49,13 @@ import org.apache.doris.thrift.TTableType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.apache.commons.lang3.StringUtils;
import org.apache.iceberg.PartitionField;
import org.apache.iceberg.PartitionSpec;
import org.apache.iceberg.Table;
+import org.apache.iceberg.view.SQLViewRepresentation;
+import org.apache.iceberg.view.View;
+import org.apache.iceberg.view.ViewVersion;
import java.util.HashMap;
import java.util.List;
@@ -64,6 +69,8 @@ public class IcebergExternalTable extends ExternalTable
implements MTMVRelatedTa
private Table table;
private boolean isValidRelatedTableCached = false;
private boolean isValidRelatedTable = false;
+ private boolean isView;
+ private static final String ENGINE_PROP_NAME = "engine-name";
public IcebergExternalTable(long id, String name, String remoteName,
IcebergExternalCatalog catalog,
IcebergExternalDatabase db) {
@@ -78,6 +85,7 @@ public class IcebergExternalTable extends ExternalTable
implements MTMVRelatedTa
super.makeSureInitialized();
if (!objectCreated) {
objectCreated = true;
+ isView = catalog.viewExists(dbName, getRemoteName());
}
}
@@ -88,7 +96,10 @@ public class IcebergExternalTable extends ExternalTable
implements MTMVRelatedTa
@Override
public Optional<SchemaCacheValue> initSchema(SchemaCacheKey key) {
- return IcebergUtils.loadSchemaCacheValue(catalog, dbName, name,
((IcebergSchemaCacheKey) key).getSchemaId());
+ boolean isView = isView();
+ String tableName = getRemoteName();
+ return IcebergUtils.loadSchemaCacheValue(
+ catalog, dbName, tableName, ((IcebergSchemaCacheKey)
key).getSchemaId(), isView);
}
@Override
@@ -230,8 +241,12 @@ public class IcebergExternalTable extends ExternalTable
implements MTMVRelatedTa
@Override
public MvccSnapshot loadSnapshot(Optional<TableSnapshot> tableSnapshot,
Optional<TableScanParams> scanParams) {
- return new
IcebergMvccSnapshot(IcebergUtils.getIcebergSnapshotCacheValue(
+ if (isView()) {
+ return new EmptyMvccSnapshot();
+ } else {
+ return new
IcebergMvccSnapshot(IcebergUtils.getIcebergSnapshotCacheValue(
tableSnapshot, getCatalog(), getDbName(), getName(),
scanParams));
+ }
}
@Override
@@ -263,4 +278,93 @@ public class IcebergExternalTable extends ExternalTable
implements MTMVRelatedTa
makeSureInitialized();
return SupportedSysTables.ICEBERG_SUPPORTED_SYS_TABLES;
}
+
+ @Override
+ public boolean isView() {
+ makeSureInitialized();
+ return isView;
+ }
+
+ public String getViewText() {
+ try {
+ return catalog.getPreExecutionAuthenticator().execute(() -> {
+ View icebergView = IcebergUtils.getIcebergView(getCatalog(),
dbName, getRemoteName());
+ ViewVersion viewVersion = icebergView.currentVersion();
+ if (viewVersion == null) {
+ throw new RuntimeException(String.format("Cannot get view
version for view '%s'", icebergView));
+ }
+ Map<String, String> summary = viewVersion.summary();
+ if (summary == null) {
+ throw new RuntimeException(String.format("Cannot get
summary for view '%s'", icebergView));
+ }
+ String engineName = summary.get(ENGINE_PROP_NAME);
+ if (StringUtils.isEmpty(engineName)) {
+ throw new RuntimeException(String.format("Cannot get
engine-name for view '%s'", icebergView));
+ }
+ SQLViewRepresentation sqlViewRepresentation =
icebergView.sqlFor(engineName.toLowerCase());
+ if (sqlViewRepresentation == null) {
+ throw new UnsupportedOperationException("Cannot get view
text from iceberg view");
+ }
+ return sqlViewRepresentation.sql();
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String getSqlDialect() {
+ try {
+ return catalog.getPreExecutionAuthenticator().execute(() -> {
+ View icebergView = IcebergUtils.getIcebergView(getCatalog(),
dbName, getRemoteName());
+ ViewVersion viewVersion = icebergView.currentVersion();
+ if (viewVersion == null) {
+ throw new RuntimeException(String.format("Cannot get view
version for view '%s'", icebergView));
+ }
+ Map<String, String> summary = viewVersion.summary();
+ if (summary == null) {
+ throw new RuntimeException(String.format("Cannot get
summary for view '%s'", icebergView));
+ }
+ String engineName = summary.get(ENGINE_PROP_NAME);
+ if (StringUtils.isEmpty(engineName)) {
+ throw new RuntimeException(String.format("Cannot get
engine-name for view '%s'", icebergView));
+ }
+ return engineName.toLowerCase();
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public View getIcebergView() {
+ return IcebergUtils.getIcebergView(getCatalog(), dbName,
getRemoteName());
+ }
+
+ /**
+ * get location of an iceberg table or view
+ * @return
+ */
+ public String location() {
+ if (isView()) {
+ View icebergView = getIcebergView();
+ return icebergView.location();
+ } else {
+ Table icebergTable = getIcebergTable();
+ return icebergTable.location();
+ }
+ }
+
+ /**
+ * get properties of an iceberg table or view
+ * @return
+ */
+ public Map<String, String> properties() {
+ if (isView()) {
+ View icebergView = getIcebergView();
+ return icebergView.properties();
+ } else {
+ Table icebergTable = getIcebergTable();
+ return icebergTable.properties();
+ }
+ }
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataCache.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataCache.java
index e41fa620b9c..67cf1d3855a 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataCache.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataCache.java
@@ -35,6 +35,7 @@ import org.apache.iceberg.ManifestFiles;
import org.apache.iceberg.SerializableTable;
import org.apache.iceberg.Snapshot;
import org.apache.iceberg.Table;
+import org.apache.iceberg.view.View;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -49,6 +50,7 @@ public class IcebergMetadataCache {
private final LoadingCache<IcebergMetadataCacheKey, List<Snapshot>>
snapshotListCache;
private final LoadingCache<IcebergMetadataCacheKey, Table> tableCache;
private final LoadingCache<IcebergMetadataCacheKey,
IcebergSnapshotCacheValue> snapshotCache;
+ private final LoadingCache<IcebergMetadataCacheKey, View> viewCache;
public IcebergMetadataCache(ExecutorService executor) {
CacheFactory snapshotListCacheFactory = new CacheFactory(
@@ -74,6 +76,7 @@ public class IcebergMetadataCache {
true,
null);
this.snapshotCache = snapshotCacheFactory.buildCache(key ->
loadSnapshot(key), null, executor);
+ this.viewCache = tableCacheFactory.buildCache(key -> loadView(key),
null, executor);
}
public Table getIcebergTable(CatalogIf catalog, String dbName, String
tbName) {
@@ -153,6 +156,10 @@ public class IcebergMetadataCache {
snapshotCache.asMap().keySet().stream()
.filter(key -> key.catalog.getId() == catalogId)
.forEach(snapshotCache::invalidate);
+
+ viewCache.asMap().entrySet().stream()
+ .filter(entry -> entry.getKey().catalog.getId() == catalogId)
+ .forEach(entry -> viewCache.invalidate(entry.getKey()));
}
public void invalidateTableCache(long catalogId, String dbName, String
tblName) {
@@ -176,6 +183,13 @@ public class IcebergMetadataCache {
.filter(key -> key.catalog.getId() == catalogId &&
key.dbName.equals(dbName) && key.tableName.equals(
tblName))
.forEach(snapshotCache::invalidate);
+ viewCache.asMap().entrySet().stream()
+ .filter(entry -> {
+ IcebergMetadataCacheKey key = entry.getKey();
+ return key.catalog.getId() == catalogId &&
key.dbName.equals(dbName) && key.tableName.equals(
+ tblName);
+ })
+ .forEach(entry -> viewCache.invalidate(entry.getKey()));
}
public void invalidateDbCache(long catalogId, String dbName) {
@@ -196,6 +210,12 @@ public class IcebergMetadataCache {
snapshotCache.asMap().keySet().stream()
.filter(key -> key.catalog.getId() == catalogId &&
key.dbName.equals(dbName))
.forEach(snapshotCache::invalidate);
+ viewCache.asMap().entrySet().stream()
+ .filter(entry -> {
+ IcebergMetadataCacheKey key = entry.getKey();
+ return key.catalog.getId() == catalogId &&
key.dbName.equals(dbName);
+ })
+ .forEach(entry -> viewCache.invalidate(entry.getKey()));
}
private static void initIcebergTableFileIO(Table table, Map<String,
String> props) {
@@ -257,4 +277,25 @@ public class IcebergMetadataCache {
snapshotCache.estimatedSize()));
return res;
}
+
+ private View loadView(IcebergMetadataCacheKey key) {
+ IcebergMetadataOps ops;
+ if (key.catalog instanceof IcebergExternalCatalog) {
+ ops = (IcebergMetadataOps) (((IcebergExternalCatalog)
key.catalog).getMetadataOps());
+ } else {
+ return null;
+ }
+ try {
+ return ((ExternalCatalog)
key.catalog).getPreExecutionAuthenticator().execute(() ->
+ ops.loadView(key.dbName, key.tableName));
+ } catch (Exception e) {
+ throw new RuntimeException(ExceptionUtils.getRootCauseMessage(e),
e);
+ }
+
+ }
+
+ public View getIcebergView(CatalogIf catalog, String dbName, String
tbName) {
+ IcebergMetadataCacheKey key = IcebergMetadataCacheKey.of(catalog,
dbName, tbName);
+ return viewCache.get(key);
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
index 9f304927b66..60e8d4343f5 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMetadataOps.java
@@ -42,10 +42,14 @@ import org.apache.iceberg.catalog.Catalog;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.SupportsNamespaces;
import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.catalog.ViewCatalog;
+import org.apache.iceberg.types.Type;
+import org.apache.iceberg.view.View;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -127,7 +131,24 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
try {
return preExecutionAuthenticator.execute(() -> {
List<TableIdentifier> tableIdentifiers =
catalog.listTables(getNamespace(dbName));
- return
tableIdentifiers.stream().map(TableIdentifier::name).collect(Collectors.toList());
+ List<String> views;
+ // Our original intention was simply to clearly define the
responsibilities of ViewCatalog and Catalog.
+ // IcebergMetadataOps handles listTableNames and listViewNames
separately.
+ // listTableNames should only focus on the table type,
+ // but in reality, Iceberg's return includes views. Therefore,
we added a filter to exclude views.
+ if (catalog instanceof ViewCatalog) {
+ views = ((ViewCatalog)
catalog).listViews(getNamespace(dbName))
+
.stream().map(TableIdentifier::name).collect(Collectors.toList());
+ } else {
+ views = Collections.emptyList();
+ }
+ if (views.isEmpty()) {
+ return
tableIdentifiers.stream().map(TableIdentifier::name).collect(Collectors.toList());
+ } else {
+ return tableIdentifiers.stream()
+ .map(TableIdentifier::name)
+ .filter(name ->
!views.contains(name)).collect(Collectors.toList());
+ }
});
} catch (Exception e) {
throw new RuntimeException("Failed to list table names, error
message is:" + e.getMessage(), e);
@@ -246,7 +267,7 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
.map(col -> new StructField(col.getName(), col.getType(),
col.getComment(), col.isAllowNull()))
.collect(Collectors.toList());
StructType structType = new StructType(new ArrayList<>(collect));
- org.apache.iceberg.types.Type visit =
+ Type visit =
DorisTypeVisitor.visit(structType, new
DorisTypeToIcebergType(structType));
Schema schema = new
Schema(visit.asNestedType().asStructType().fields());
Map<String, String> properties = stmt.getProperties();
@@ -268,7 +289,11 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
public void dropTableImpl(DropTableStmt stmt) throws DdlException {
try {
preExecutionAuthenticator.execute(() -> {
- performDropTable(stmt);
+ if
(getExternalCatalog().getMetadataOps().viewExists(stmt.getDbName(),
stmt.getTableName())) {
+ performDropView(stmt.getDbName(), stmt.getTableName(),
stmt.isSetIfExists());
+ } else {
+ performDropTable(stmt.getDbName(), stmt.getTableName(),
stmt.isSetIfExists());
+ }
return null;
});
} catch (Exception e) {
@@ -285,13 +310,6 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
}
}
- private void performDropTable(DropTableStmt stmt) throws DdlException {
- if (stmt == null) {
- throw new DdlException("DropTableStmt is null");
- }
- performDropTable(stmt.getDbName(), stmt.getTableName(),
stmt.isSetIfExists());
- }
-
private void performDropTable(String dbName, String tableName, boolean
ifExists) throws DdlException {
ExternalDatabase<?> db = dorisCatalog.getDbNullable(dbName);
if (db == null) {
@@ -332,6 +350,47 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
}
}
+ @Override
+ public boolean viewExists(String dbName, String viewName) {
+ if (!(catalog instanceof ViewCatalog)) {
+ return false;
+ }
+ try {
+ return preExecutionAuthenticator.execute(() ->
+ ((ViewCatalog)
catalog).viewExists(getTableIdentifier(dbName, viewName)));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to check view exist, error
message is:" + e.getMessage(), e);
+
+ }
+ }
+
+ @Override
+ public View loadView(String dbName, String tblName) {
+ if (!(catalog instanceof ViewCatalog)) {
+ return null;
+ }
+ try {
+ ViewCatalog viewCatalog = (ViewCatalog) catalog;
+ return preExecutionAuthenticator.execute(() ->
viewCatalog.loadView(TableIdentifier.of(dbName, tblName)));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to load view, error message
is:" + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public List<String> listViewNames(String db) {
+ if (!(catalog instanceof ViewCatalog)) {
+ return Collections.emptyList();
+ }
+ try {
+ return preExecutionAuthenticator.execute(() ->
+ ((ViewCatalog) catalog).listViews(Namespace.of(db))
+
.stream().map(TableIdentifier::name).collect(Collectors.toList()));
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to list view names, error
message is:" + e.getMessage(), e);
+ }
+ }
+
private TableIdentifier getTableIdentifier(String dbName, String tblName) {
return externalCatalogName
.map(s -> TableIdentifier.of(s, dbName, tblName))
@@ -351,4 +410,31 @@ public class IcebergMetadataOps implements
ExternalMetadataOps {
public ThreadPoolExecutor getThreadPoolWithPreAuth() {
return dorisCatalog.getThreadPoolExecutor();
}
+
+ private void performDropView(String dbName, String viewName, boolean
ifExists) throws DdlException {
+ if (!(catalog instanceof ViewCatalog)) {
+ throw new DdlException("Drop Iceberg view is not supported with
not view catalog.");
+ }
+ ExternalDatabase<?> db = dorisCatalog.getDbNullable(dbName);
+ if (db == null) {
+ if (ifExists) {
+ LOG.info("database [{}] does not exist when drop view[{}]",
dbName, viewName);
+ return;
+ } else {
+ ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR,
dbName);
+ }
+ }
+ ViewCatalog viewCatalog = (ViewCatalog) catalog;
+ if (!viewExists(dbName, viewName)) {
+ if (ifExists) {
+ LOG.info("drop view[{}] which does not exist", viewName);
+ return;
+ } else {
+ ErrorReport.reportDdlException(ErrorCode.ERR_UNKNOWN_TABLE,
viewName, dbName);
+ }
+ }
+ viewCatalog.dropView(getTableIdentifier(dbName, viewName));
+ db.setUnInitialized(true);
+ }
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
index e956e236903..73d4a2fa76a 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java
@@ -103,6 +103,7 @@ import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.LocationUtil;
import org.apache.iceberg.util.SnapshotUtil;
import org.apache.iceberg.util.StructProjection;
+import org.apache.iceberg.view.View;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -629,18 +630,29 @@ public class IcebergUtils {
/**
* Get iceberg schema from catalog and convert them to doris schema
*/
- public static List<Column> getSchema(ExternalCatalog catalog, String
dbName, String name, long schemaId) {
+ public static List<Column> getSchema(
+ ExternalCatalog catalog, String dbName, String name, long
schemaId, boolean isView) {
try {
return catalog.getPreExecutionAuthenticator().execute(() -> {
- org.apache.iceberg.Table icebergTable =
getIcebergTable(catalog, dbName, name);
Schema schema;
- if (schemaId == NEWEST_SCHEMA_ID ||
icebergTable.currentSnapshot() == null) {
- schema = icebergTable.schema();
+ if (isView) {
+ View icebergView = getIcebergView(catalog, dbName, name);
+ if (schemaId == NEWEST_SCHEMA_ID) {
+ schema = icebergView.schema();
+ } else {
+ schema = icebergView.schemas().get((int) schemaId);
+ }
} else {
- schema = icebergTable.schemas().get((int) schemaId);
+ Table icebergTable = getIcebergTable(catalog, dbName,
name);
+ if (schemaId == NEWEST_SCHEMA_ID ||
icebergTable.currentSnapshot() == null) {
+ schema = icebergTable.schema();
+ } else {
+ schema = icebergTable.schemas().get((int) schemaId);
+ }
}
+ String type = isView ? "view" : "table";
Preconditions.checkNotNull(schema,
- "Schema for table " + catalog.getName() + "." + dbName
+ "." + name + " is null");
+ "Schema for " + type + " " + catalog.getName() + "." +
dbName + "." + name + " is null");
return parseSchema(schema);
});
} catch (Exception e) {
@@ -1169,25 +1181,6 @@ public class IcebergUtils {
}
}
- // load table schema from iceberg API to external schema cache.
- public static Optional<SchemaCacheValue> loadSchemaCacheValue(
- ExternalCatalog catalog, String dbName, String tbName, long
schemaId) {
- Table table = IcebergUtils.getIcebergTable(catalog, dbName, tbName);
- List<Column> schema = IcebergUtils.getSchema(catalog, dbName, tbName,
schemaId);
- List<Column> tmpColumns = Lists.newArrayList();
- PartitionSpec spec = table.spec();
- for (PartitionField field : spec.fields()) {
- Types.NestedField col = table.schema().findField(field.sourceId());
- for (Column c : schema) {
- if (c.getName().equalsIgnoreCase(col.name())) {
- tmpColumns.add(c);
- break;
- }
- }
- }
- return Optional.of(new IcebergSchemaCacheValue(schema, tmpColumns));
- }
-
public static List<Column> getIcebergSchema(
TableIf tableIf,
ExternalCatalog catalog,
@@ -1213,4 +1206,42 @@ public class IcebergUtils {
Optional.empty(), catalog, dbName, tbName, Optional.empty());
}
}
+
+ public static org.apache.iceberg.view.View getIcebergView(ExternalCatalog
catalog, String dbName, String tblName) {
+ return getIcebergViewInternal(catalog, dbName, tblName);
+ }
+
+ private static org.apache.iceberg.view.View
getIcebergViewInternal(ExternalCatalog catalog, String dbName,
+ String tblName) {
+ IcebergMetadataCache metadataCache =
Env.getCurrentEnv().getExtMetaCacheMgr().getIcebergMetadataCache();
+ return metadataCache.getIcebergView(catalog, dbName, tblName);
+ }
+
+ public static Optional<SchemaCacheValue> loadSchemaCacheValue(
+ ExternalCatalog catalog, String dbName, String tbName, long
schemaId, boolean isView) {
+ List<Column> schema = IcebergUtils.getSchema(catalog, dbName, tbName,
schemaId, isView);
+ List<Column> tmpColumns = Lists.newArrayList();
+ if (!isView) {
+ // get table partition column info
+ Table table = IcebergUtils.getIcebergTable(catalog, dbName,
tbName);
+ PartitionSpec spec = table.spec();
+ for (PartitionField field : spec.fields()) {
+ Types.NestedField col =
table.schema().findField(field.sourceId());
+ for (Column c : schema) {
+ if (c.getName().equalsIgnoreCase(col.name())) {
+ tmpColumns.add(c);
+ break;
+ }
+ }
+ }
+ }
+ return Optional.of(new IcebergSchemaCacheValue(schema, tmpColumns));
+ }
+
+ public static String showCreateView(IcebergExternalTable
icebergExternalTable) {
+ return String.format("CREATE VIEW `%s` AS ",
icebergExternalTable.getName())
+ +
+ icebergExternalTable.getViewText();
+ }
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergApiSource.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergApiSource.java
index 4b4e76bea47..8ba1d71c1cb 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergApiSource.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergApiSource.java
@@ -42,6 +42,17 @@ public class IcebergApiSource implements IcebergSource {
public IcebergApiSource(IcebergExternalTable table, TupleDescriptor desc,
Map<String, ColumnRange> columnNameToRange) {
+ // Theoretically, the IcebergScanNode is responsible for scanning data
from physical tables.
+ // Views should not reach this point.
+ // By adding this validation, we aim to ensure that if a view query
does end up here, it indicates a bug.
+ // This helps us identify issues promptly.
+
+ // when use legacy planner, query an iceberg view will enter this
+ // we should set enable_fallback_to_original_planner=false
+ // so that it will throw exception by first planner
+ if (table.isView()) {
+ throw new UnsupportedOperationException("IcebergApiSource does not
support view");
+ }
this.icebergExtTable = table;
this.originTable =
Env.getCurrentEnv().getExtMetaCacheMgr().getIcebergMetadataCache().getIcebergTable(
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/operations/ExternalMetadataOps.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/operations/ExternalMetadataOps.java
index f565f2795e8..c8e9c63d7b9 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/operations/ExternalMetadataOps.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/operations/ExternalMetadataOps.java
@@ -24,6 +24,9 @@ import org.apache.doris.analysis.DropTableStmt;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.UserException;
+import org.apache.iceberg.view.View;
+
+import java.util.Collections;
import java.util.List;
/**
@@ -141,4 +144,34 @@ public interface ExternalMetadataOps {
* close the connection, eg, to hms
*/
void close();
+
+ /**
+ * load an iceberg view.
+ * @param dbName
+ * @param viewName
+ * @return
+ */
+ default View loadView(String dbName, String viewName) {
+ throw new UnsupportedOperationException("Load view is not supported.");
+ }
+
+ /**
+ * Check if an Iceberg view exists.
+ * @param dbName
+ * @param viewName
+ * @return
+ */
+ default boolean viewExists(String dbName, String viewName) {
+ throw new UnsupportedOperationException("View is not supported.");
+ }
+
+ /**
+ * List all views under a specific database.
+ * @param db
+ * @return
+ */
+ default List<String> listViewNames(String db) {
+ return Collections.emptyList();
+ }
+
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
index 3c071058cf9..584cf5e1ebc 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindRelation.java
@@ -17,6 +17,7 @@
package org.apache.doris.nereids.rules.analysis;
+import org.apache.doris.analysis.TableSnapshot;
import org.apache.doris.catalog.AggStateType;
import org.apache.doris.catalog.AggregateType;
import org.apache.doris.catalog.Column;
@@ -35,6 +36,7 @@ import org.apache.doris.common.util.Util;
import org.apache.doris.datasource.ExternalTable;
import org.apache.doris.datasource.hive.HMSExternalTable;
import org.apache.doris.datasource.hive.HMSExternalTable.DLAType;
+import org.apache.doris.datasource.iceberg.IcebergExternalTable;
import org.apache.doris.nereids.CTEContext;
import org.apache.doris.nereids.CascadesContext;
import org.apache.doris.nereids.SqlCacheContext;
@@ -405,7 +407,7 @@ public class BindRelation extends OneAnalysisRuleFactory {
String hiveCatalog = hmsTable.getCatalog().getName();
String hiveDb = hmsTable.getDatabase().getFullName();
String ddlSql = hmsTable.getViewText();
- Plan hiveViewPlan = parseAndAnalyzeHiveView(
+ Plan hiveViewPlan = parseAndAnalyzeExternalView(
hmsTable, hiveCatalog, hiveDb, ddlSql,
cascadesContext);
return new LogicalSubQueryAlias<>(qualifiedTableName,
hiveViewPlan);
}
@@ -424,6 +426,33 @@ public class BindRelation extends OneAnalysisRuleFactory {
Optional.ofNullable(unboundRelation.getScanParams()));
}
case ICEBERG_EXTERNAL_TABLE:
+ IcebergExternalTable icebergExternalTable =
(IcebergExternalTable) table;
+ if (Config.enable_query_iceberg_views &&
icebergExternalTable.isView()) {
+ Optional<TableSnapshot> tableSnapshot =
unboundRelation.getTableSnapshot();
+ if (tableSnapshot.isPresent()) {
+ // iceberg view not supported with snapshot
time/version travel
+ // note that enable_fallback_to_original_planner
should be set with false
+ // or else this exception will not be thrown
+ // because legacy planner will retry and thrown
other exception
+ throw new UnsupportedOperationException(
+ "iceberg view not supported with snapshot
time/version travel");
+ }
+ isView = true;
+ String icebergCatalog =
icebergExternalTable.getCatalog().getName();
+ String icebergDb =
icebergExternalTable.getDatabase().getFullName();
+ String ddlSql = icebergExternalTable.getViewText();
+ Plan icebergViewPlan =
parseAndAnalyzeExternalView(icebergExternalTable,
+ icebergCatalog, icebergDb, ddlSql,
cascadesContext);
+ return new LogicalSubQueryAlias<>(qualifiedTableName,
icebergViewPlan);
+ }
+ if (icebergExternalTable.isView()) {
+ throw new UnsupportedOperationException(
+ "please set enable_query_iceberg_views=true to
enable query iceberg views");
+ }
+ return new
LogicalFileScan(unboundRelation.getRelationId(), (ExternalTable) table,
+ qualifierWithoutTableName,
unboundRelation.getTableSample(),
+ unboundRelation.getTableSnapshot(),
+ Optional.ofNullable(unboundRelation.getScanParams()));
case PAIMON_EXTERNAL_TABLE:
case MAX_COMPUTE_EXTERNAL_TABLE:
case TRINO_CONNECTOR_EXTERNAL_TABLE:
@@ -462,15 +491,17 @@ public class BindRelation extends OneAnalysisRuleFactory {
}
}
- private Plan parseAndAnalyzeHiveView(
- HMSExternalTable table, String hiveCatalog, String hiveDb, String
ddlSql, CascadesContext cascadesContext) {
+ private Plan parseAndAnalyzeExternalView(
+ ExternalTable table, String externalCatalog, String externalDb,
+ String ddlSql, CascadesContext cascadesContext) {
ConnectContext ctx = cascadesContext.getConnectContext();
String previousCatalog = ctx.getCurrentCatalog().getName();
String previousDb = ctx.getDatabase();
String convertedSql = SqlDialectHelper.convertSqlByDialect(ddlSql,
ctx.getSessionVariable());
- // change catalog and db to hive catalog and db, so that we can parse
and analyze the view sql in hive context.
- ctx.changeDefaultCatalog(hiveCatalog);
- ctx.setDatabase(hiveDb);
+ // change catalog and db to external catalog and db,
+ // so that we can parse and analyze the view sql in external context.
+ ctx.changeDefaultCatalog(externalCatalog);
+ ctx.setDatabase(externalDb);
try {
return parseAndAnalyzeView(table, convertedSql, cascadesContext);
} finally {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/planner/IcebergTableSink.java
b/fe/fe-core/src/main/java/org/apache/doris/planner/IcebergTableSink.java
index 5bc0c803cb9..29746fc34e4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/planner/IcebergTableSink.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/planner/IcebergTableSink.java
@@ -59,6 +59,9 @@ public class IcebergTableSink extends
BaseExternalTableDataSink {
public IcebergTableSink(IcebergExternalTable targetTable) {
super();
+ if (targetTable.isView()) {
+ throw new UnsupportedOperationException("Write data to iceberg
view is not supported");
+ }
this.targetTable = targetTable;
}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
index 945feebd3b4..196ce304ee9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ShowExecutor.java
@@ -208,6 +208,7 @@ import
org.apache.doris.datasource.hive.HiveMetaStoreClientHelper;
import org.apache.doris.datasource.iceberg.IcebergExternalCatalog;
import org.apache.doris.datasource.iceberg.IcebergExternalDatabase;
import org.apache.doris.datasource.iceberg.IcebergExternalTable;
+import org.apache.doris.datasource.iceberg.IcebergUtils;
import org.apache.doris.datasource.maxcompute.MaxComputeExternalCatalog;
import org.apache.doris.job.manager.JobManager;
import org.apache.doris.load.DeleteHandler;
@@ -1218,6 +1219,13 @@ public class ShowExecutor {
resultSet = new ShowResultSet(showStmt.getMetaData(), rows);
return;
}
+ if ((table.getType() == TableType.ICEBERG_EXTERNAL_TABLE)
+ && ((IcebergExternalTable) table).isView()) {
+ rows.add(Arrays.asList(table.getName(),
+ IcebergUtils.showCreateView(((IcebergExternalTable)
table))));
+ resultSet = new ShowResultSet(showStmt.getMetaData(), rows);
+ return;
+ }
List<String> createTableStmt = Lists.newArrayList();
Env.getDdlStmt(null, null, table, createTableStmt, null, null,
false,
true /* hide password */, false, -1L,
showStmt.isNeedBriefDdl(), false);
diff --git
a/regression-test/data/external_table_p0/iceberg/test_iceberg_view_query_p0.out
b/regression-test/data/external_table_p0/iceberg/test_iceberg_view_query_p0.out
new file mode 100644
index 00000000000..480a2a74156
Binary files /dev/null and
b/regression-test/data/external_table_p0/iceberg/test_iceberg_view_query_p0.out
differ
diff --git
a/regression-test/suites/external_table_p0/iceberg/test_iceberg_view_query_p0.groovy
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_view_query_p0.groovy
new file mode 100644
index 00000000000..a855ed38af1
--- /dev/null
+++
b/regression-test/suites/external_table_p0/iceberg/test_iceberg_view_query_p0.groovy
@@ -0,0 +1,203 @@
+// 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.
+
+suite("test_iceberg_view_query_p0",
"p0,external,iceberg,external_docker,external_docker_iceberg") {
+
+ String enableIcebergTest =
context.config.otherConfigs.get("enableIcebergTest")
+ // if (enableIcebergTest == null ||
!enableIcebergTest.equalsIgnoreCase("true")) {
+ // This is suit can not be run currently because only hive catalog can
support view.
+ // but we can only create view in rest catalog
+ // will be fixed later
+ if (true) {
+ logger.info("disable iceberg test.")
+ return
+ }
+
+ try {
+ String hms_port = context.config.otherConfigs.get(hivePrefix +
"HmsPort")
+ String hdfs_port = context.config.otherConfigs.get(hivePrefix +
"HdfsPort")
+ String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
+
+ //create iceberg hms catalog
+ String iceberg_catalog_name = "test_iceberg_view_query_p0"
+ sql """drop catalog if exists ${iceberg_catalog_name}"""
+ sql """create catalog if not exists ${iceberg_catalog_name} properties
(
+ 'type'='iceberg',
+ 'iceberg.catalog.type'='hms',
+ 'hive.metastore.uris' = 'thrift://${externalEnvIp}:${hms_port}',
+ 'fs.defaultFS' = 'hdfs://${externalEnvIp}:${hdfs_port}',
+ 'use_meta_cache' = 'true'
+ );"""
+
+ //using database and close planner fallback
+ sql """use `${iceberg_catalog_name}`.`test_db`"""
+ sql """set enable_fallback_to_original_planner=false;"""
+
+ // run all suites
+ def q01 = {
+ order_qt_q01 """select * from v_with_partitioned_table order by
col1"""
+ order_qt_q02 """select * from v_with_unpartitioned_table order by
col1"""
+ order_qt_q03 """select * from v_with_partitioned_column order by
col5"""
+ }
+
+ def q02 = {
+ order_qt_q01 """select count(*) from v_with_partitioned_table"""
+ order_qt_q02 """select count(*) from v_with_unpartitioned_table"""
+ order_qt_q03 """select count(*) from v_with_partitioned_column"""
+ }
+
+ def q03 = {
+ order_qt_q01 """select col1,col2,col3,col4 from
v_with_partitioned_table order by col1"""
+ order_qt_q02 """select col5 from v_with_partitioned_table order by
col5"""
+ }
+
+ def q04 = {
+ order_qt_q01 """describe v_with_partitioned_table"""
+ order_qt_q02 """describe v_with_unpartitioned_table"""
+ order_qt_q03 """describe v_with_partitioned_column"""
+ }
+
+ q01()
+ q02()
+ q03()
+ q04()
+
+ def result1 = sql """explain verbose select * from
v_with_partitioned_table"""
+ assertTrue(result1.contains('t_partitioned_table'))
+ def result2 = sql """explain verbose select * from
v_with_unpartitioned_table"""
+ assertTrue(result2.contains('v_with_unpartitioned_table'))
+ def result3 = sql """explain verbose select * from
v_with_partitioned_column"""
+ assertTrue(result3.contains('t_partitioned_table'))
+
+ def result4 = sql """explain verbose select count(*) from
v_with_partitioned_table"""
+ assertTrue(result4.contains('t_partitioned_table'))
+ def result5 = sql """explain verbose select count(*) from
v_with_unpartitioned_table"""
+ assertTrue(result5.contains('t_unpartitioned_table'))
+ def result6 = sql """explain verbose select count(*) from
v_with_partitioned_column"""
+ assertTrue(result6.contains('t_partitioned_table'))
+
+ def result7 = sql """explain verbose select col1,col2,col3,col4 from
v_with_partitioned_table"""
+ assertTrue(result7.contains('t_partitioned_table'))
+ def result8 = sql """explain verbose select col5 from
v_with_partitioned_table"""
+ assertTrue(result8.contains('t_partitioned_table'))
+
+ def result9 = sql """show create table v_with_partitioned_table"""
+ assertTrue(result9.contains('v_with_partitioned_table'))
+ def result10 = sql """show create table v_with_unpartitioned_table"""
+ assertTrue(result10.contains('v_with_unpartitioned_table'))
+ def result11 = sql """show create table v_with_partitioned_column"""
+ assertTrue(result11.contains('v_with_partitioned_column'))
+
+ def result12 = sql """show create view v_with_partitioned_table"""
+ assertTrue(result12.contains('v_with_partitioned_table'))
+ def result13 = sql """show create view v_with_unpartitioned_table"""
+ assertTrue(result13.contains('v_with_unpartitioned_table'))
+ def result14 = sql """show create view v_with_partitioned_column"""
+ assertTrue(result14.contains('v_with_partitioned_column'))
+
+ try {
+ sql """select * from v_with_partitioned_table FOR TIME AS OF
'2025-06-11 20:17:01' order by col1 limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select * from v_with_partitioned_table FOR VERSION AS OF
5497706844625725452 order by col1 limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_partitioned_table FOR TIME AS
OF '2025-06-11 20:17:01'"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_partitioned_table FOR VERSION
AS OF 5497706844625725452"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+
+ try {
+ sql """select * from v_with_unpartitioned_table FOR TIME AS OF
'2025-06-11 20:17:01' order by col1 limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select * from v_with_unpartitioned_table FOR VERSION AS OF
5497706844625725452 order by col1 limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_unpartitioned_table FOR TIME AS
OF '2025-06-11 20:17:01'"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_unpartitioned_table FOR VERSION
AS OF 5497706844625725452"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+
+ try {
+ sql """select * from v_with_partitioned_column FOR TIME AS OF
'2025-06-11 20:17:01' order by col5 limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select * from v_with_partitioned_column FOR VERSION AS OF
5497706844625725452 order by col5 limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_partitioned_column FOR TIME AS
OF '2025-06-11 20:17:01'"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_partitioned_column FOR VERSION
AS OF 5497706844625725452"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+
+ try {
+ sql """select * from v_with_joint_table FOR TIME AS OF '2025-06-11
20:17:01' order by sale_date limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select * from v_with_joint_table FOR VERSION AS OF
5497706844625725452 order by sale_date limit 10"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_joint_table FOR TIME AS OF
'2025-06-11 20:17:01'"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+ try {
+ sql """select count(*) from v_with_joint_table FOR VERSION AS OF
5497706844625725452"""
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains("iceberg view not supported
with snapshot time/version travel"), e.getMessage())
+ }
+
+ sql """drop view v_with_partitioned_table"""
+ sql """drop view v_with_unpartitioned_table"""
+ sql """drop view v_with_partitioned_column"""
+
+ sql """drop catalog if exists ${iceberg_catalog_name}"""
+ } finally {
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]