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 ddaf8ab896d branch-3.1: [feature](maxcompute)support maxcompte catalog
read project-schema-table. #57012 (#57286)
ddaf8ab896d is described below
commit ddaf8ab896db2818481f89a4ec7a249f2c51b7a5
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Oct 30 14:40:41 2025 +0800
branch-3.1: [feature](maxcompute)support maxcompte catalog read
project-schema-table. #57012 (#57286)
Cherry-picked from #57012
Co-authored-by: daidai <[email protected]>
---
.../maxcompute/MaxComputeExternalCatalog.java | 104 ++++-----
.../maxcompute/MaxComputeExternalTable.java | 19 +-
.../maxcompute/MaxComputeSchemaCacheValue.java | 8 +-
.../datasource/maxcompute/McStructureHelper.java | 221 ++++++++++++++++++
.../maxcompute/source/MaxComputeScanNode.java | 4 +-
.../property/constants/MCProperties.java | 20 ++
.../maxcompute/test_max_compute_schema.out | 258 +++++++++++++++++++++
.../maxcompute/test_max_compute_schema.groovy | 239 +++++++++++++++++++
8 files changed, 802 insertions(+), 71 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalCatalog.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalCatalog.java
index ff46f3ad65a..65af7f967fe 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalCatalog.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalCatalog.java
@@ -27,21 +27,16 @@ import org.apache.doris.datasource.SessionContext;
import org.apache.doris.datasource.property.constants.MCProperties;
import com.aliyun.odps.Odps;
-import com.aliyun.odps.OdpsException;
import com.aliyun.odps.Partition;
-import com.aliyun.odps.Project;
import com.aliyun.odps.account.Account;
import com.aliyun.odps.account.AccountFormat;
import com.aliyun.odps.account.AliyunAccount;
-import com.aliyun.odps.security.SecurityManager;
+import com.aliyun.odps.table.TableIdentifier;
import com.aliyun.odps.table.configuration.RestOptions;
import com.aliyun.odps.table.configuration.SplitOptions;
import com.aliyun.odps.table.enviroment.Credentials;
import com.aliyun.odps.table.enviroment.EnvironmentSettings;
-import com.aliyun.odps.utils.StringUtils;
import com.google.common.collect.ImmutableList;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
import org.apache.log4j.Logger;
import java.time.ZoneId;
@@ -66,7 +61,6 @@ public class MaxComputeExternalCatalog extends
ExternalCatalog {
private String defaultProject;
private String quota;
private EnvironmentSettings settings;
- private String catalogOwner;
private String splitStrategy;
private SplitOptions splitOptions;
@@ -81,6 +75,8 @@ public class MaxComputeExternalCatalog extends
ExternalCatalog {
AccountFormat accountFormat = AccountFormat.DISPLAYNAME;
+ private McStructureHelper mcStructureHelper = null;
+
private static final Map<String, ZoneId> REGION_ZONE_MAP;
private static final List<String> REQUIRED_PROPERTIES = ImmutableList.of(
MCProperties.PROJECT,
@@ -231,6 +227,10 @@ public class MaxComputeExternalCatalog extends
ExternalCatalog {
.withQuotaName(quota)
.withRestOptions(restOptions)
.build();
+
+ boolean enableNamespaceSchema = Boolean.parseBoolean(
+ props.getOrDefault(MCProperties.ENABLE_NAMESPACE_SCHEMA,
MCProperties.DEFAULT_ENABLE_NAMESPACE_SCHEMA));
+ mcStructureHelper = McStructureHelper.getHelper(enableNamespaceSchema,
defaultProject);
}
public Odps getClient() {
@@ -239,39 +239,15 @@ public class MaxComputeExternalCatalog extends
ExternalCatalog {
}
protected List<String> listDatabaseNames() {
- List<String> result = new ArrayList<>();
- result.add(defaultProject);
-
- try {
- result.add(defaultProject);
- if (StringUtils.isNullOrEmpty(catalogOwner)) {
- SecurityManager sm =
odps.projects().get().getSecurityManager();
- String whoami = sm.runQuery("whoami", false);
-
- JsonObject js =
JsonParser.parseString(whoami).getAsJsonObject();
- catalogOwner = js.get("DisplayName").getAsString();
- }
- Iterator<Project> iterator =
odps.projects().iterator(catalogOwner);
- while (iterator.hasNext()) {
- Project project = iterator.next();
- if (!project.getName().equals(defaultProject)) {
- result.add(project.getName());
- }
- }
- } catch (OdpsException e) {
- throw new RuntimeException(e);
- }
- return result;
+ makeSureInitialized();
+ return mcStructureHelper.listDatabaseNames(getClient(),
getDefaultProject());
}
@Override
public boolean tableExist(SessionContext ctx, String dbName, String
tblName) {
makeSureInitialized();
- try {
- return getClient().tables().exists(dbName, tblName);
- } catch (OdpsException e) {
- throw new RuntimeException(e);
- }
+ return mcStructureHelper.tableExist(getClient(), dbName, tblName);
+
}
public List<String> listPartitionNames(String dbName, String tbl) {
@@ -279,43 +255,37 @@ public class MaxComputeExternalCatalog extends
ExternalCatalog {
}
public List<String> listPartitionNames(String dbName, String tbl, long
skip, long limit) {
- try {
- if (getClient().projects().exists(dbName)) {
- List<Partition> parts;
- if (limit < 0) {
- parts = getClient().tables().get(dbName,
tbl).getPartitions();
- } else {
- skip = skip < 0 ? 0 : skip;
- parts = new ArrayList<>();
- Iterator<Partition> it = getClient().tables().get(dbName,
tbl).getPartitionIterator();
- int count = 0;
- while (it.hasNext()) {
- if (count < skip) {
- count++;
- it.next();
- } else if (parts.size() >= limit) {
- break;
- } else {
- parts.add(it.next());
- }
+ if (mcStructureHelper.databaseExist(getClient(), dbName)) {
+ List<Partition> parts;
+ if (limit < 0) {
+ parts = mcStructureHelper.getPartitions(getClient(), dbName,
tbl);
+ } else {
+ skip = skip < 0 ? 0 : skip;
+ parts = new ArrayList<>();
+ Iterator<Partition> it =
mcStructureHelper.getPartitionIterator(getClient(), dbName, tbl);
+ int count = 0;
+ while (it.hasNext()) {
+ if (count < skip) {
+ count++;
+ it.next();
+ } else if (parts.size() >= limit) {
+ break;
+ } else {
+ parts.add(it.next());
}
}
- return parts.stream().map(p ->
p.getPartitionSpec().toString(false, true))
- .collect(Collectors.toList());
- } else {
- throw new OdpsException("Max compute project: " + dbName + "
not exists.");
}
- } catch (OdpsException e) {
- throw new RuntimeException(e);
+ return parts.stream().map(p ->
p.getPartitionSpec().toString(false, true))
+ .collect(Collectors.toList());
+ } else {
+ throw new RuntimeException("MaxCompute schema/project: " + dbName
+ " not exists.");
}
}
@Override
public List<String> listTableNames(SessionContext ctx, String dbName) {
makeSureInitialized();
- List<String> result = new ArrayList<>();
- getClient().tables().iterable(dbName).forEach(e ->
result.add(e.getName()));
- return result;
+ return mcStructureHelper.listTableNames(getClient(), dbName);
}
public String getAccessKey() {
@@ -402,6 +372,14 @@ public class MaxComputeExternalCatalog extends
ExternalCatalog {
return splitByteSize;
}
+ public com.aliyun.odps.Table getOdpsTable(String dbName, String tableName)
{
+ return mcStructureHelper.getOdpsTable(getClient(), dbName, tableName);
+ }
+
+ public TableIdentifier getOdpsTableIdentifier(String dbName, String
tableName) {
+ return mcStructureHelper.getTableIdentifier(dbName, tableName);
+ }
+
@Override
public void checkProperties() throws DdlException {
super.checkProperties();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalTable.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalTable.java
index a598c68703f..7135ef96fdc 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalTable.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeExternalTable.java
@@ -36,6 +36,7 @@ import org.apache.doris.thrift.TTableType;
import com.aliyun.odps.OdpsType;
import com.aliyun.odps.Table;
+import com.aliyun.odps.table.TableIdentifier;
import com.aliyun.odps.type.ArrayTypeInfo;
import com.aliyun.odps.type.CharTypeInfo;
import com.aliyun.odps.type.DecimalTypeInfo;
@@ -172,9 +173,12 @@ public class MaxComputeExternalTable extends ExternalTable
{
public Optional<SchemaCacheValue> initSchema() {
// this method will be called at semantic parsing.
makeSureInitialized();
- Table odpsTable = ((MaxComputeExternalCatalog)
catalog).getClient().tables().get(dbName, name);
- List<com.aliyun.odps.Column> columns =
odpsTable.getSchema().getColumns();
+ MaxComputeExternalCatalog mcCatalog = (MaxComputeExternalCatalog)
catalog;
+
+ Table odpsTable = mcCatalog.getOdpsTable(dbName, name);
+ TableIdentifier tableIdentifier =
mcCatalog.getOdpsTableIdentifier(dbName, name);
+ List<com.aliyun.odps.Column> columns =
odpsTable.getSchema().getColumns();
for (com.aliyun.odps.Column column : columns) {
columnNameToOdpsColumn.put(column.getName(), column);
@@ -213,8 +217,8 @@ public class MaxComputeExternalTable extends ExternalTable {
partitionSpecs = ImmutableList.of();
}
- return Optional.of(new MaxComputeSchemaCacheValue(schema, odpsTable,
partitionColumnNames,
- partitionSpecs, partitionDorisColumns, partitionTypes));
+ return Optional.of(new MaxComputeSchemaCacheValue(schema, odpsTable,
tableIdentifier,
+ partitionColumnNames, partitionSpecs, partitionDorisColumns,
partitionTypes));
}
private Type mcTypeToDorisType(TypeInfo typeInfo) {
@@ -303,6 +307,13 @@ public class MaxComputeExternalTable extends ExternalTable
{
}
}
+ public TableIdentifier getTableIdentifier() {
+ makeSureInitialized();
+ Optional<SchemaCacheValue> schemaCacheValue = getSchemaCacheValue();
+ return schemaCacheValue.map(value -> ((MaxComputeSchemaCacheValue)
value).getTableIdentifier())
+ .orElse(null);
+ }
+
@Override
public TTableDescriptor toThrift() {
// ak sk endpoint project quota
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeSchemaCacheValue.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeSchemaCacheValue.java
index 0d0fb69a3e4..cf87725c680 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeSchemaCacheValue.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/MaxComputeSchemaCacheValue.java
@@ -22,6 +22,7 @@ import org.apache.doris.catalog.Type;
import org.apache.doris.datasource.SchemaCacheValue;
import com.aliyun.odps.Table;
+import com.aliyun.odps.table.TableIdentifier;
import lombok.Getter;
import lombok.Setter;
@@ -31,15 +32,18 @@ import java.util.List;
@Setter
public class MaxComputeSchemaCacheValue extends SchemaCacheValue {
private Table odpsTable;
+ private TableIdentifier tableIdentifier;
private List<String> partitionColumnNames;
private List<String> partitionSpecs;
private List<Column> partitionColumns;
private List<Type> partitionTypes;
- public MaxComputeSchemaCacheValue(List<Column> schema, Table odpsTable,
List<String> partitionColumnNames,
- List<String> partitionSpecs, List<Column> partitionColumns,
List<Type> partitionTypes) {
+ public MaxComputeSchemaCacheValue(List<Column> schema, Table odpsTable,
TableIdentifier tableIdentifier,
+ List<String> partitionColumnNames, List<String> partitionSpecs,
List<Column> partitionColumns,
+ List<Type> partitionTypes) {
super(schema);
this.odpsTable = odpsTable;
+ this.tableIdentifier = tableIdentifier;
this.partitionSpecs = partitionSpecs;
this.partitionColumnNames = partitionColumnNames;
this.partitionColumns = partitionColumns;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/McStructureHelper.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/McStructureHelper.java
new file mode 100644
index 00000000000..7232b716426
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/McStructureHelper.java
@@ -0,0 +1,221 @@
+// 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.doris.datasource.maxcompute;
+
+
+import com.aliyun.odps.Odps;
+import com.aliyun.odps.OdpsException;
+import com.aliyun.odps.Partition;
+import com.aliyun.odps.Project;
+import com.aliyun.odps.Schema;
+import com.aliyun.odps.Table;
+import com.aliyun.odps.security.SecurityManager;
+import com.aliyun.odps.table.TableIdentifier;
+import com.aliyun.odps.utils.StringUtils;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+
+/**
+ * Due to the introduction of the `mc.enable.namespace.schema` property, most
interfaces using the
+ * ODPS client have changed, and the mapping structure between Doris and
MaxCompute has also changed.
+ * Different property values correspond to different implementation class.
+ * It's important to note that when external functions are called through the
interface, the structure
+ * mapped by Doris (database/table) is used, and the MaxCompute concept does
not need to be considered.
+ */
+public interface McStructureHelper {
+ List<String> listTableNames(Odps mcClient, String dbName);
+
+ List<String> listDatabaseNames(Odps mcClient, String defaultProject);
+
+ boolean tableExist(Odps mcClient, String dbName, String tableName) throws
RuntimeException;
+
+ boolean databaseExist(Odps mcClient, String dbName);
+
+ TableIdentifier getTableIdentifier(String dbName, String tableName);
+
+ List<Partition> getPartitions(Odps mcClient, String dbName, String
tableName);
+
+ Iterator<Partition> getPartitionIterator(Odps mcClient, String dbName,
String tableName);
+
+ Table getOdpsTable(Odps mcClient, String dbName, String tableName);
+
+ /**
+ * `mc.enable.namespace.schema` = true.
+ * mapping structure between Doris and MaxCompute:
+ * Doris : catalog, dbName, tableName
+ * MaxCompute: project, schema, table
+ */
+ class ProjectSchemaTableHelper implements McStructureHelper {
+ private String defaultProjectName = null;
+
+ public ProjectSchemaTableHelper(String defaultProjectName) {
+ this.defaultProjectName = defaultProjectName;
+ }
+
+ @Override
+ public List<String> listTableNames(Odps mcClient, String dbName) {
+ List<String> result = new ArrayList<>();
+ mcClient.tables().iterable(defaultProjectName, dbName, null, false)
+ .forEach(e -> result.add(e.getName()));
+ return result;
+ }
+
+ @Override
+ public List<String> listDatabaseNames(Odps mcClient, String
defaultProject) {
+ List<String> result = new ArrayList<>();
+ Iterator<Schema> iterator =
mcClient.schemas().iterator(defaultProjectName);
+ while (iterator.hasNext()) {
+ Schema schema = iterator.next();
+ result.add(schema.getName());
+ }
+ return result;
+ }
+
+ @Override
+ public List<Partition> getPartitions(Odps mcClient, String dbName,
String tableName) {
+ return mcClient.tables().get(defaultProjectName, dbName,
tableName).getPartitions();
+ }
+
+ @Override
+ public Iterator<Partition> getPartitionIterator(Odps mcClient, String
dbName, String tableName) {
+ return mcClient.tables().get(defaultProjectName, dbName,
tableName).getPartitions().iterator();
+ }
+
+ @Override
+ public boolean tableExist(Odps mcClient, String dbName, String
tableName) throws RuntimeException {
+ try {
+ return mcClient.tables().exists(defaultProjectName, dbName,
tableName);
+ } catch (OdpsException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean databaseExist(Odps mcClient, String dbName) throws
RuntimeException {
+ try {
+ return mcClient.schemas().exists(dbName);
+ } catch (OdpsException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public TableIdentifier getTableIdentifier(String dbName, String
tableName) {
+ return TableIdentifier.of(defaultProjectName, dbName, tableName);
+ }
+
+ @Override
+ public Table getOdpsTable(Odps mcClient, String dbName, String
tableName) {
+ return mcClient.tables().get(defaultProjectName, dbName,
tableName);
+ }
+ }
+
+ /**
+ * `mc.enable.namespace.schema` = false.
+ * mapping structure between Doris and MaxCompute:
+ * Doris : dbName, tableName
+ * MaxCompute: project, table
+ */
+ class ProjectTableHelper implements McStructureHelper {
+ private String catalogOwner = null;
+
+ @Override
+ public boolean tableExist(Odps mcClient, String dbName, String
tableName) throws RuntimeException {
+ try {
+ return mcClient.tables().exists(dbName, tableName);
+ } catch (OdpsException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ @Override
+ public List<String> listTableNames(Odps mcClient, String dbName) {
+ List<String> result = new ArrayList<>();
+ mcClient.tables().iterable(dbName).forEach(e ->
result.add(e.getName()));
+ return result;
+ }
+
+ @Override
+ public List<String> listDatabaseNames(Odps mcClient, String
defaultProject) {
+ List<String> result = new ArrayList<>();
+ result.add(defaultProject);
+ try {
+ result.add(defaultProject);
+ if (StringUtils.isNullOrEmpty(catalogOwner)) {
+ SecurityManager sm =
mcClient.projects().get().getSecurityManager();
+ String whoami = sm.runQuery("whoami", false);
+
+ JsonObject js =
JsonParser.parseString(whoami).getAsJsonObject();
+ catalogOwner = js.get("DisplayName").getAsString();
+ }
+ Iterator<Project> iterator =
mcClient.projects().iterator(catalogOwner);
+ while (iterator.hasNext()) {
+ Project project = iterator.next();
+ if (!project.getName().equals(defaultProject)) {
+ result.add(project.getName());
+ }
+ }
+ } catch (OdpsException e) {
+ throw new RuntimeException(e);
+ }
+ return result;
+ }
+
+ @Override
+ public List<Partition> getPartitions(Odps mcClient, String dbName,
String tableName) {
+ return mcClient.tables().get(dbName, tableName).getPartitions();
+ }
+
+ @Override
+ public Iterator<Partition> getPartitionIterator(Odps mcClient, String
dbName, String tableName) {
+ return mcClient.tables().get(dbName,
tableName).getPartitions().iterator();
+ }
+
+ @Override
+ public boolean databaseExist(Odps mcClient, String dbName) throws
RuntimeException {
+ try {
+ return mcClient.projects().exists(dbName);
+ } catch (OdpsException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public TableIdentifier getTableIdentifier(String dbName, String
tableName) {
+ return TableIdentifier.of(dbName, tableName);
+ }
+
+
+ @Override
+ public Table getOdpsTable(Odps mcClient, String dbName, String
tableName) {
+ return mcClient.tables().get(dbName, tableName);
+ }
+ }
+
+ static McStructureHelper getHelper(boolean isEnableNamespaceSchema, String
defaultProjectName) {
+ return isEnableNamespaceSchema
+ ? new ProjectSchemaTableHelper(defaultProjectName)
+ : new ProjectTableHelper();
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/source/MaxComputeScanNode.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/source/MaxComputeScanNode.java
index c8f24db0da9..1ed292a8774 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/source/MaxComputeScanNode.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/maxcompute/source/MaxComputeScanNode.java
@@ -53,7 +53,6 @@ import org.apache.doris.thrift.TTableFormatFileDesc;
import com.aliyun.odps.OdpsType;
import com.aliyun.odps.PartitionSpec;
-import com.aliyun.odps.table.TableIdentifier;
import com.aliyun.odps.table.configuration.ArrowOptions;
import com.aliyun.odps.table.configuration.ArrowOptions.TimestampUnit;
import com.aliyun.odps.table.optimizer.predicate.Predicate;
@@ -190,7 +189,8 @@ public class MaxComputeScanNode extends FileQueryScanNode {
retryTimes = mcCatalog.getRetryTimes();
TableReadSessionBuilder scanBuilder = new TableReadSessionBuilder();
- return scanBuilder.identifier(TableIdentifier.of(table.getDbName(),
table.getName()))
+
+ return scanBuilder.identifier(table.getTableIdentifier())
.withSettings(mcCatalog.getSettings())
.withSplitOptions(mcCatalog.getSplitOption())
.requiredPartitionColumns(requiredPartitionColumns)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/constants/MCProperties.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/constants/MCProperties.java
index 744a36aed37..bdfd9793c53 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/property/constants/MCProperties.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/property/constants/MCProperties.java
@@ -81,6 +81,26 @@ public class MCProperties extends BaseProperties {
public static final String ACCOUNT_FORMAT_ID = "id";
public static final String DEFAULT_ACCOUNT_FORMAT = ACCOUNT_FORMAT_NAME;
+ // In the previous MaxCompute architecture, and its mapping in Doris,
+ // the hierarchy was: project / database -> table / table.
+ // When creating a catalog, users needed to specify the property
`mc.default.project`,
+ // which indicated the default project to access.
+ // In this structure, executing `SHOW DATABASES` would list other projects.
+ //
+ // After MaxCompute introduced the concept of schemas, the hierarchy
changed to:
+ // project / catalog -> schema / database -> table / table.
+ // Here, the project is at a higher level, and `SHOW DATABASES` should now
list
+ // all schemas under the current project.
+ // As a result, users need to create a separate catalog for each project,
+ // specifying a different `mc.default.project` property.
+ //
+ // To maintain compatibility with the old version,
+ // a variable is introduced:
+ // - When the property is true, the new architecture is used.
+ // - When the property is false, the old architecture is used.
+ public static final String ENABLE_NAMESPACE_SCHEMA =
"mc.enable.namespace.schema";
+ public static final String DEFAULT_ENABLE_NAMESPACE_SCHEMA = "false";
+
public static CloudCredential getCredential(Map<String, String> props) {
return getCloudCredential(props, ACCESS_KEY, SECRET_KEY,
SESSION_TOKEN);
}
diff --git
a/regression-test/data/external_table_p2/maxcompute/test_max_compute_schema.out
b/regression-test/data/external_table_p2/maxcompute/test_max_compute_schema.out
new file mode 100644
index 00000000000..eaaa45dd88b
--- /dev/null
+++
b/regression-test/data/external_table_p2/maxcompute/test_max_compute_schema.out
@@ -0,0 +1,258 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !show_db_1 --
+analytics
+default
+information_schema
+iot
+mysql
+
+-- !show_tb_1 --
+order_detail
+user_info
+
+-- !show_tb_2 --
+product_sales
+web_log
+
+-- !show_tb_3 --
+employee_salary
+
+-- !show_par --
+ds1=202510
+ds1=202511
+ds1=202512
+
+-- !show_par2 --
+ds2=20251015/hour=10
+ds2=20251016/hour=09
+ds2=20251017/hour=08
+
+-- !desc --
+id int Yes true \N
+emp_name text Yes true \N
+department text Yes true \N
+salary decimal(10,2) Yes true \N
+hire_date date Yes true \N
+
+-- !show_db_2 --
+information_schema
+mc_datalake_schema
+mysql
+
+-- !show_tb_4 --
+order_detail
+user_info
+
+-- !mc_old_q1 --
+1 Alice 23 Beijing
+2 Bob 30 Shanghai
+3 Carol 27 XiAn
+4 David 22 Guangzhou
+
+-- !mc_old_q2 --
+1 ORD001 500 Alice 202510
+2 ORD002 899.9 Bob 202510
+3 ORD003 120.5 Carol 202511
+4 ORD004 350 David 202511
+5 ORD005 780 Alice 202511
+6 ORD006 640 Bob 202512
+7 ORD007 220 Carol 202512
+
+-- !show_db_1 --
+analytics
+default
+information_schema
+iot
+mysql
+
+-- !show_tb_1 --
+order_detail
+user_info
+
+-- !show_tb_2 --
+product_sales
+web_log
+
+-- !show_tb_3 --
+employee_salary
+
+-- !show_par --
+ds1=202510
+ds1=202511
+ds1=202512
+
+-- !show_par2 --
+ds2=20251015/hour=10
+ds2=20251016/hour=09
+ds2=20251017/hour=08
+
+-- !desc --
+id int Yes true \N
+emp_name text Yes true \N
+department text Yes true \N
+salary decimal(10,2) Yes true \N
+hire_date date Yes true \N
+
+-- !mc_q1 --
+1 Alice 23 Beijing
+2 Bob 30 Shanghai
+3 Carol 27 XiAn
+4 David 22 Guangzhou
+
+-- !mc_q2 --
+2 Bob 30
+3 Carol 27
+
+-- !mc_q3 --
+Alice Beijing
+Bob Shanghai
+
+-- !mc_q4 --
+1 Alice 23 Beijing
+
+-- !mc_q5 --
+1 ORD001 500 Alice 202510
+2 ORD002 899.9 Bob 202510
+5 ORD005 780 Alice 202511
+6 ORD006 640 Bob 202512
+
+-- !mc_q6 --
+ORD002 Bob 899.9
+ORD006 Bob 640
+
+-- !mc_q7 --
+3
+
+-- !mc_q8 --
+1 Keyboard 199.9 3 2025-10-15T10:00
+2 Mouse 99.5 5 2025-10-15T11:00
+3 Monitor 899 2 2025-10-15T12:00
+4 Laptop 5699 1 2025-10-15T13:00
+5 USB Cable 25 10 2025-10-15T14:00
+
+-- !mc_q9 --
+1 Keyboard 199.9
+3 Monitor 899
+
+-- !mc_q10 --
+Keyboard 3
+Mouse 5
+USB Cable 10
+
+-- !mc_q11 --
+2 Mouse 99.5 5 2025-10-15T11:00
+3 Monitor 899 2 2025-10-15T12:00
+4 Laptop 5699 1 2025-10-15T13:00
+5 USB Cable 25 10 2025-10-15T14:00
+
+-- !mc_q12 --
+1 1001 click https://site.com/page1 2025-10-15T10:00
20251015 10
+6 1006 click https://site.com/page6 2025-10-16T09:45
20251016 09
+
+-- !mc_q13 --
+4 1004 https://site.com/page4
+5 1005 https://site.com/page5
+6 1006 https://site.com/page6
+
+-- !mc_q14 --
+4 1004 logout https://site.com/page4 2025-10-16T09:15
20251016 09
+5 1005 login https://site.com/page5 2025-10-16T09:30
20251016 09
+6 1006 click https://site.com/page6 2025-10-16T09:45
20251016 09
+
+-- !mc_q15 --
+2
+
+-- !mc_q16 --
+1 Tom Finance 12000.00 2023-03-01
+2 Jerry IT 15000.50 2022-09-15
+3 Mike HR 9800.00 2024-06-20
+4 Lucy IT 13500.00 2022-11-05
+
+-- !mc_q17 --
+Tom Finance
+Jerry IT
+Lucy IT
+
+-- !mc_q18 --
+1 Tom Finance 12000.00 2023-03-01
+3 Mike HR 9800.00 2024-06-20
+
+-- !mc_q19 --
+Jerry 15000.50
+Lucy 13500.00
+
+-- !mc_q20 --
+1 Tom Finance 12000.00 2023-03-01
+2 Jerry IT 15000.50 2022-09-15
+3 Mike HR 9800.00 2024-06-20
+4 Lucy IT 13500.00 2022-11-05
+
+-- !mc_join_q21 --
+1 Alice ORD001 500
+2 Bob ORD002 899.9
+
+-- !mc_join_q22 --
+1 Alice Keyboard 199.9
+2 Bob Mouse 99.5
+3 Carol Monitor 899
+4 David Laptop 5699
+
+-- !mc_join_q23 --
+Alice click https://site.com/page1
+Bob view https://site.com/page2
+Carol buy https://site.com/page3
+
+-- !mc_join_q24 --
+Tom Keyboard 2025-10-15T10:00
+Jerry Mouse 2025-10-15T11:00
+Mike Monitor 2025-10-15T12:00
+Lucy Laptop 2025-10-15T13:00
+
+-- !mc_join_q25 --
+Alice Finance 12000.00
+Bob IT 15000.50
+Carol HR 9800.00
+David IT 13500.00
+
+-- !mc_join_q26 --
+ORD003 120.5 Monitor 899
+ORD004 350 Laptop 5699
+ORD005 780 USB Cable 25
+
+-- !mc_join_q27 --
+ORD006 https://site.com/page6 click
+
+-- !mc_join_q28 --
+ORD003 Mike HR
+ORD004 Lucy IT
+
+-- !mc_join_q29 --
+
+-- !mc_join_q30 --
+Alice Keyboard 12000.00
+Bob Mouse 15000.50
+Carol Monitor 9800.00
+David Laptop 13500.00
+
+-- !mc_join_q31 --
+Carol ORD003 https://site.com/page3
+
+-- !mc_join_q32 --
+Lucy Laptop logout
+
+-- !mc_join_q33 --
+Alice Tom Finance
+Bob Jerry IT
+Carol Mike HR
+David Lucy IT
+
+-- !mc_join_q34 --
+Keyboard Finance 12000.00
+Mouse IT 15000.50
+Monitor HR 9800.00
+Laptop IT 13500.00
+
+-- !mc_join_q35 --
+ORD001 Keyboard Tom
+ORD002 Mouse Jerry
+
diff --git
a/regression-test/suites/external_table_p2/maxcompute/test_max_compute_schema.groovy
b/regression-test/suites/external_table_p2/maxcompute/test_max_compute_schema.groovy
new file mode 100644
index 00000000000..b89c92fabdd
--- /dev/null
+++
b/regression-test/suites/external_table_p2/maxcompute/test_max_compute_schema.groovy
@@ -0,0 +1,239 @@
+// 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.
+/*
+// Test DDL and Data:
+
+
+use mc_datalake_schema;
+set odps.namespace.schema = true;
+
+USE schema `default`;
+CREATE TABLE user_info (id INT, name STRING, age INT, city STRING);
+INSERT INTO user_info VALUES
+(1, 'Alice', 23, 'Beijing'),
+(2, 'Bob', 30, 'Shanghai'),
+(3, 'Carol', 27, 'XiAn'),
+(4, 'David', 22, 'Guangzhou');
+
+CREATE TABLE order_detail (id INT, order_id STRING, amount DOUBLE, buyer
STRING) PARTITIONED BY (ds1 STRING);
+INSERT INTO order_detail PARTITION (ds1='202510') VALUES
+(1, 'ORD001', 500.0, 'Alice'),
+(2, 'ORD002', 899.9, 'Bob');
+INSERT INTO order_detail PARTITION (ds1='202511') VALUES
+(3, 'ORD003', 120.5, 'Carol'),
+(4, 'ORD004', 350.0, 'David'),
+(5, 'ORD005', 780.0, 'Alice');
+INSERT INTO order_detail PARTITION (ds1='202512') VALUES
+(6, 'ORD006', 640.0, 'Bob'),
+(7, 'ORD007', 220.0, 'Carol');
+
+
+
+create schema analytics;
+USE schema analytics;
+CREATE TABLE product_sales (id INT, product_name STRING, price DOUBLE,
quantity INT, sale_time DATETIME);
+INSERT INTO analytics.product_sales VALUES
+(1, 'Keyboard', 199.9, 3, CAST('2025-10-15 10:00:00' AS DATETIME)),
+(2, 'Mouse', 99.5, 5, CAST('2025-10-15 11:00:00' AS DATETIME)),
+(3, 'Monitor', 899.0, 2, CAST('2025-10-15 12:00:00' AS DATETIME)),
+(4, 'Laptop', 5699.0, 1, CAST('2025-10-15 13:00:00' AS DATETIME)),
+(5, 'USB Cable', 25.0, 10, CAST('2025-10-15 14:00:00' AS DATETIME));
+CREATE TABLE web_log (
+ id INT,
+ user_id INT,
+ action STRING,
+ url STRING,
+ log_time DATETIME
+) PARTITIONED BY (ds2 STRING, hour STRING);
+INSERT INTO web_log PARTITION (ds2='20251015', hour='10') VALUES
+(1, 1001, 'click', 'https://site.com/page1', CAST('2025-10-15 10:00:00' AS
DATETIME)),
+(2, 1002, 'view', 'https://site.com/page2', CAST('2025-10-15 10:05:00' AS
DATETIME)),
+(3, 1003, 'buy', 'https://site.com/page3', CAST('2025-10-15 10:10:00' AS
DATETIME));
+INSERT INTO web_log PARTITION (ds2='20251016', hour='09') VALUES
+(4, 1004, 'logout', 'https://site.com/page4', CAST('2025-10-16 09:15:00' AS
DATETIME)),
+(5, 1005, 'login', 'https://site.com/page5', CAST('2025-10-16 09:30:00' AS
DATETIME)),
+(6, 1006, 'click', 'https://site.com/page6', CAST('2025-10-16 09:45:00' AS
DATETIME));
+INSERT INTO web_log PARTITION (ds2='20251017', hour='08') VALUES
+(7, 1007, 'view', 'https://site.com/page7', CAST('2025-10-17 08:00:00' AS
DATETIME)),
+(8, 1008, 'buy', 'https://site.com/page8', CAST('2025-10-17 08:10:00' AS
DATETIME));
+
+
+create schema iot;
+USE schema iot;
+CREATE TABLE employee_salary (
+ id INT,
+ emp_name STRING,
+ department STRING,
+ salary DECIMAL(10,2),
+ hire_date DATE
+);
+INSERT INTO employee_salary VALUES
+(1, 'Tom', 'Finance', 12000.00, CAST('2023-03-01' AS DATE)),
+(2, 'Jerry', 'IT', 15000.50, CAST('2022-09-15' AS DATE)),
+(3, 'Mike', 'HR', 9800.00, CAST('2024-06-20' AS DATE)),
+(4, 'Lucy', 'IT', 13500.00, CAST('2022-11-05' AS DATE));
+ */
+suite("test_max_compute_schema",
"p2,external,maxcompute,external_remote,external_remote_maxcompute") {
+ String enabled = context.config.otherConfigs.get("enableMaxComputeTest")
+ if (enabled != null && enabled.equalsIgnoreCase("true")) {
+ String ak = context.config.otherConfigs.get("ak")
+ String sk = context.config.otherConfigs.get("sk");
+ String mc_project = "mc_datalake_schema"
+ String mc_catalog_name = "test_max_compute_schema"
+
+
+
+
+ sql """drop catalog if exists ${mc_catalog_name};"""
+ sql """
+ create catalog if not exists ${mc_catalog_name} properties (
+ "type" = "max_compute",
+ "mc.default.project" = "${mc_project}",
+ "mc.access_key" = "${ak}",
+ "mc.secret_key" = "${sk}",
+ "mc.endpoint" =
"http://service.cn-beijing-vpc.maxcompute.aliyun-inc.com/api",
+ "mc.enable.namespace.schema" = "true"
+ );
+ """
+
+ sql """ switch ${mc_catalog_name};"""
+ order_qt_show_db_1 """ show databases; """
+
+ sql """ use `default`; """
+ order_qt_show_tb_1 """ show tables; """
+
+
+ sql """ use `analytics`; """
+ order_qt_show_tb_2 """ show tables; """
+
+
+ sql """ use `iot`; """
+ order_qt_show_tb_3 """ show tables; """
+
+ order_qt_show_par """ show partitions from `default`.order_detail; """
+ order_qt_show_par2 """ show partitions from analytics.web_log; """
+ qt_desc """ desc iot.employee_salary; """
+
+ sql """ alter catalog ${mc_catalog_name} set
PROPERTIES("mc.enable.namespace.schema" = "false"); """
+ qt_show_db_2 """ show databases;"""
+ sql """ use ${mc_project}; """
+ order_qt_show_tb_4 """ show tables; """
+
+ qt_mc_old_q1 """ SELECT * FROM user_info ORDER BY id;"""
+ qt_mc_old_q2 """ SELECT * FROM order_detail ORDER BY id;"""
+
+ sql """ alter catalog ${mc_catalog_name} set
PROPERTIES("mc.enable.namespace.schema" = "true"); """
+
+
+
+ order_qt_show_db_1 """ show databases; """
+
+ sql """ use `default`; """
+ order_qt_show_tb_1 """ show tables; """
+
+
+ sql """ use `analytics`; """
+ order_qt_show_tb_2 """ show tables; """
+
+
+ sql """ use `iot`; """
+ order_qt_show_tb_3 """ show tables; """
+
+ order_qt_show_par """ show partitions from `default`.order_detail; """
+ order_qt_show_par2 """ show partitions from analytics.web_log; """
+ qt_desc """ desc iot.employee_salary; """
+
+
+ explain {
+ sql("""select * from `default`.order_detail """)
+ contains "partition=3/3"
+ }
+
+ explain {
+ sql("""select * from `default`.order_detail where ds1 = "11" """)
+ contains "partition=0/3"
+ }
+
+
+ explain {
+ sql("""select * from `default`.order_detail where ds1 = "202511"
or ds1 = "202512" """)
+ contains "partition=2/3"
+ }
+
+
+ explain {
+ sql("""select * from analytics.web_log; """)
+ contains "partition=3/3"
+ }
+
+
+ explain {
+ sql("""select * from analytics.web_log where hour="09" ; """)
+ contains "partition=1/3"
+ }
+
+ explain {
+ sql("""select * from analytics.web_log where ds2="20251016" and
hour="09" """)
+ contains "partition=1/3"
+ }
+
+
+ explain {
+ sql("""select * from analytics.web_log where ds2="20251016" and
hour="99" """)
+ contains "partition=0/3"
+ }
+
+
+
+ qt_mc_q1 """SELECT * FROM `default`.user_info ORDER BY id;"""
+ qt_mc_q2 """SELECT id, name, age FROM `default`.user_info WHERE age >
25 ORDER BY id;"""
+ qt_mc_q3 """SELECT name, city FROM `default`.user_info WHERE city IN
('Beijing','Shanghai') ORDER BY id;"""
+ qt_mc_q4 """SELECT * FROM `default`.user_info WHERE name LIKE 'A%'
ORDER BY id;"""
+ qt_mc_q5 """SELECT * FROM `default`.order_detail WHERE amount > 400
ORDER BY id;"""
+ qt_mc_q6 """SELECT order_id, buyer, amount FROM
`default`.order_detail WHERE buyer='Bob' ORDER BY id;"""
+ qt_mc_q7 """SELECT COUNT(*) AS cnt FROM `default`.order_detail WHERE
ds1='202511';"""
+ qt_mc_q8 """SELECT * FROM analytics.product_sales ORDER BY id;"""
+ qt_mc_q9 """SELECT id, product_name, price FROM
analytics.product_sales WHERE price BETWEEN 100 AND 900 ORDER BY id;"""
+ qt_mc_q10 """SELECT product_name, quantity FROM
analytics.product_sales WHERE quantity >= 3 ORDER BY id;"""
+ qt_mc_q11 """SELECT * FROM analytics.product_sales WHERE sale_time >=
'2025-10-15 11:00:00' ORDER BY id;"""
+ qt_mc_q12 """SELECT * FROM analytics.web_log WHERE action='click'
ORDER BY id;"""
+ qt_mc_q13 """SELECT id, user_id, url FROM analytics.web_log WHERE
ds2='20251016' ORDER BY id;"""
+ qt_mc_q14 """SELECT * FROM analytics.web_log WHERE hour='09' ORDER BY
id;"""
+ qt_mc_q15 """SELECT COUNT(*) AS clicks FROM analytics.web_log WHERE
action='buy';"""
+ qt_mc_q16 """SELECT * FROM iot.employee_salary ORDER BY id;"""
+ qt_mc_q17 """SELECT emp_name, department FROM iot.employee_salary
WHERE salary > 10000 ORDER BY id;"""
+ qt_mc_q18 """SELECT * FROM iot.employee_salary WHERE hire_date >
'2023-01-01' ORDER BY id;"""
+ qt_mc_q19 """SELECT emp_name, salary FROM iot.employee_salary WHERE
department='IT' ORDER BY id;"""
+ qt_mc_q20 """SELECT * FROM iot.employee_salary WHERE salary BETWEEN
9000 AND 16000 ORDER BY id;"""
+
+ qt_mc_join_q21 """SELECT u.id, u.name, o.order_id, o.amount FROM
`default`.user_info u JOIN `default`.order_detail o ON u.name=o.buyer WHERE
o.ds1='202510' ORDER BY u.id;"""
+ qt_mc_join_q22 """SELECT u.id, u.name, p.product_name, p.price FROM
`default`.user_info u JOIN analytics.product_sales p ON u.id=p.id ORDER BY
u.id;"""
+ qt_mc_join_q23 """SELECT u.name, w.action, w.url FROM
`default`.user_info u JOIN analytics.web_log w ON u.id=w.user_id-1000 WHERE
w.ds2='20251015' AND w.hour='10' ORDER BY u.id;"""
+ qt_mc_join_q24 """SELECT e.emp_name, p.product_name, p.sale_time FROM
iot.employee_salary e JOIN analytics.product_sales p ON e.id=p.id ORDER BY
e.id;"""
+ qt_mc_join_q25 """SELECT u.name, e.department, e.salary FROM
`default`.user_info u JOIN iot.employee_salary e ON u.id=e.id ORDER BY u.id;"""
+ qt_mc_join_q26 """SELECT o.order_id, o.amount, p.product_name,
p.price FROM `default`.order_detail o JOIN analytics.product_sales p ON
o.id=p.id WHERE o.ds1='202511' ORDER BY o.id;"""
+ qt_mc_join_q27 """SELECT o.order_id, w.url, w.action FROM
`default`.order_detail o JOIN analytics.web_log w ON o.id=w.id WHERE
o.ds1='202512' AND w.ds2='20251016' AND w.hour='09' ORDER BY o.id;"""
+ qt_mc_join_q28 """SELECT o.order_id, e.emp_name, e.department FROM
`default`.order_detail o JOIN iot.employee_salary e ON o.id=e.id WHERE
o.ds1='202511' ORDER BY o.id;"""
+ qt_mc_join_q29 """SELECT p.product_name, w.url, w.action FROM
analytics.product_sales p JOIN analytics.web_log w ON p.id=w.id WHERE
w.ds2='20251017' AND w.hour='08' ORDER BY p.id;"""
+ qt_mc_join_q30 """SELECT u.name, p.product_name, e.salary FROM
`default`.user_info u JOIN analytics.product_sales p ON u.id=p.id JOIN
iot.employee_salary e ON u.id=e.id ORDER BY u.id;"""
+ qt_mc_join_q31 """SELECT u.name, o.order_id, w.url FROM
`default`.user_info u JOIN `default`.order_detail o ON u.id=o.id JOIN
analytics.web_log w ON u.id=w.id WHERE o.ds1='202511' AND w.ds2='20251015' AND
w.hour='10' ORDER BY u.id;"""
+ qt_mc_join_q32 """SELECT e.emp_name, p.product_name, w.action FROM
iot.employee_salary e JOIN analytics.product_sales p ON e.id=p.id JOIN
analytics.web_log w ON e.id=w.id WHERE w.ds2='20251016' AND w.hour='09' ORDER
BY e.id;"""
+ qt_mc_join_q33 """SELECT u.name, e.emp_name, e.department FROM
`default`.user_info u JOIN iot.employee_salary e ON u.id=e.id ORDER BY u.id;"""
+ qt_mc_join_q34 """SELECT p.product_name, e.department, e.salary FROM
analytics.product_sales p JOIN iot.employee_salary e ON p.id=e.id ORDER BY
p.id;"""
+ qt_mc_join_q35 """SELECT o.order_id, p.product_name, e.emp_name FROM
`default`.order_detail o JOIN analytics.product_sales p ON o.id=p.id JOIN
iot.employee_salary e ON o.id=e.id WHERE o.ds1='202510' ORDER BY o.id;"""
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]