This is an automated email from the ASF dual-hosted git repository.
davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git
The following commit(s) were added to refs/heads/dev by this push:
new 78df51cf0f [Feature][Connector-V2][MaxCompute] Support Schema Name and
Passwordless Auth (#10831)
78df51cf0f is described below
commit 78df51cf0fdcb10dda3c04697380d25d5a7026d0
Author: zhiliang-wu <[email protected]>
AuthorDate: Tue May 12 16:29:58 2026 +0200
[Feature][Connector-V2][MaxCompute] Support Schema Name and Passwordless
Auth (#10831)
Co-authored-by: WU Zhiliang (External) <[email protected]>
---
docs/en/connectors/sink/Maxcompute.md | 21 ++-
docs/en/connectors/source/Maxcompute.md | 23 ++-
docs/zh/connectors/sink/Maxcompute.md | 21 ++-
docs/zh/connectors/source/Maxcompute.md | 27 +++-
.../connector-maxcompute/pom.xml | 22 ++-
.../maxcompute/catalog/MaxComputeCatalog.java | 34 +++--
.../catalog/MaxComputeCatalogFactory.java | 6 +-
.../maxcompute/config/MaxcomputeBaseOptions.java | 14 ++
.../maxcompute/sink/MaxcomputeSinkFactory.java | 9 +-
.../maxcompute/source/MaxcomputeSource.java | 16 +++
.../maxcompute/source/MaxcomputeSourceFactory.java | 7 +-
.../maxcompute/util/MaxcomputeOutputFormat.java | 23 ++-
.../seatunnel/maxcompute/util/MaxcomputeUtil.java | 43 +++++-
.../maxcompute/util/MaxcomputeUtilTest.java | 157 +++++++++++++++++++++
14 files changed, 371 insertions(+), 52 deletions(-)
diff --git a/docs/en/connectors/sink/Maxcompute.md
b/docs/en/connectors/sink/Maxcompute.md
index 347bba8777..4d6ee7fb1b 100644
--- a/docs/en/connectors/sink/Maxcompute.md
+++ b/docs/en/connectors/sink/Maxcompute.md
@@ -16,11 +16,13 @@ Used to read data from Maxcompute.
| name | type | required | default value |
|----------------|---------|----------|---------------|
-| accessId | string | yes | - |
-| accesskey | string | yes | - |
+| accessId | string | no | - |
+| accesskey | string | no | - |
+| sts_token | string | no | - |
| endpoint | string | yes | - |
| project | string | yes | - |
| table_name | string | yes | - |
+| schema_name | string | no | - |
| partition_spec | string | no | - |
| overwrite | boolean | no | false |
| insert_strategy| string | no | upload |
@@ -34,6 +36,13 @@ Used to read data from Maxcompute.
`accesskey` Your Maxcompute accessKey which cloud be access from Alibaba Cloud.
+### sts_token [string]
+
+`sts_token` Your MaxCompute STS Token for temporary authentication. **Note:**
If `sts_token` is provided, `accessId` and `accesskey` are strictly required.
+
+> **Passwordless Authentication (ECS RAM Role, Environment Variables, etc.)**
+> To use passwordless authentication seamlessly, simply leave `accessId`,
`accesskey`, and `sts_token` all blank. The connector will automatically fall
back to the Aliyun DefaultCredentialsProvider chain (Environment Variables,
System Properties, CLI Profiles, OIDC, ECS RAM Roles).
+
### endpoint [string]
`endpoint` Your Maxcompute endpoint start with http.
@@ -50,6 +59,14 @@ Used to read data from Maxcompute.
`partition_spec` This spec of Maxcompute partition table eg:ds='20220101'.
+### schema_name [string]
+
+`schema_name` The MaxCompute Schema name (the namespace between Project and
Table).
+Only required when the table resides in a **non-default schema** within your
MaxCompute project.
+See [Schema-related
operations](https://www.alibabacloud.com/help/en/maxcompute/user-guide/schema-related-operations).
+
+Default: not set (uses the project default schema).
+
### overwrite [boolean]
`overwrite` Whether to overwrite the table or partition, default: false.
diff --git a/docs/en/connectors/source/Maxcompute.md
b/docs/en/connectors/source/Maxcompute.md
index b1203f4fca..9395a857a7 100644
--- a/docs/en/connectors/source/Maxcompute.md
+++ b/docs/en/connectors/source/Maxcompute.md
@@ -20,11 +20,13 @@ Used to read data from Maxcompute.
| name | type | required | default value |
|----------------|--------|----------|---------------|
-| accessId | string | yes | - |
-| accesskey | string | yes | - |
+| accessId | string | no | - |
+| accesskey | string | no | - |
+| sts_token | string | no | - |
| endpoint | string | yes | - |
| project | string | yes | - |
| table_name | string | yes | - |
+| schema_name | string | no | - |
| partition_spec | string | no | - |
| split_row | int | no | 10000 |
| read_columns | Array | no | - |
@@ -40,6 +42,13 @@ Used to read data from Maxcompute.
`accesskey` Your Maxcompute accessKey which cloud be access from Alibaba Cloud.
+### sts_token [string]
+
+`sts_token` Your MaxCompute STS Token for temporary authentication. **Note:**
If `sts_token` is provided, `accessId` and `accesskey` are strictly required.
+
+> **Passwordless Authentication (ECS RAM Role, Environment Variables, etc.)**
+> To use passwordless authentication seamlessly, simply leave `accessId`,
`accesskey`, and `sts_token` all blank. The connector will automatically fall
back to the Aliyun DefaultCredentialsProvider chain (Environment Variables,
System Properties, CLI Profiles, OIDC, ECS RAM Roles).
+
### endpoint [string]
`endpoint` Your Maxcompute endpoint start with http.
@@ -56,6 +65,16 @@ Used to read data from Maxcompute.
`partition_spec` This spec of Maxcompute partition table eg:ds='20220101'.
+### schema_name [string]
+
+`schema_name` The MaxCompute Schema name (the namespace between Project and
Table).
+Only required when the table resides in a **non-default schema** within your
MaxCompute project.
+See [Schema-related
operations](https://www.alibabacloud.com/help/en/maxcompute/user-guide/schema-related-operations).
+
+When using `table_list`, each entry can specify its own `schema_name`, which
overrides the top-level value.
+
+Default: not set (uses the project default schema).
+
### split_row [int]
`split_row` Number of rows per split, default: 10000.
diff --git a/docs/zh/connectors/sink/Maxcompute.md
b/docs/zh/connectors/sink/Maxcompute.md
index eeb3f9710f..87f3984d9c 100644
--- a/docs/zh/connectors/sink/Maxcompute.md
+++ b/docs/zh/connectors/sink/Maxcompute.md
@@ -16,11 +16,13 @@ import ChangeLog from
'../changelog/connector-maxcompute.md';
| 参数名 | 类型 | 必须 | 默认值 |
|----------------|---------|------|--------|
-| accessId | string | 是 | - |
-| accesskey | string | 是 | - |
+| accessId | string | 否 | - |
+| accesskey | string | 否 | - |
+| sts_token | string | 否 | - |
| endpoint | string | 是 | - |
| project | string | 是 | - |
| table_name | string | 是 | - |
+| schema_name | string | 否 | - |
| partition_spec | string | 否 | - |
| overwrite | boolean | 否 | false |
| insert_strategy| string | no | upload |
@@ -34,6 +36,13 @@ import ChangeLog from '../changelog/connector-maxcompute.md';
`accesskey` 您的 Maxcompute accessKey,可从阿里云访问。
+### sts_token [string]
+
+`sts_token` 您的 MaxCompute STS Token,用于临时认证。 **注意:** 如果提供了 `sts_token`,则必须同时提供
`accessId` 和 `accesskey`。
+
+> **免密认证 (ECS RAM Role, 环境变量等)**
+> 要使用免密认证,只需将 `accessId`、`accesskey` 和 `sts_token` 全部留空不填。连接器将自动回退到阿里云默认凭据链
(DefaultCredentialsProvider) 读取凭证(包括环境变量、系统属性、CLI 配置文件、OIDC 以及 ECS RAM 角色)。
+
### endpoint [string]
`endpoint` 您的 Maxcompute endpoint,以 http 开头。
@@ -50,6 +59,14 @@ import ChangeLog from '../changelog/connector-maxcompute.md';
`partition_spec` Maxcompute 分区表的规范,例如:ds='20220101'。
+### schema_name [string]
+
+`schema_name` MaxCompute Schema 名称(Project 与 Table 之间的命名空间)。
+仅当表位于 MaxCompute 项目的**非默认 Schema** 时才需要设置。
+参见 [Schema
相关操作](https://help.aliyun.com/zh/maxcompute/user-guide/schema-related-operations)。
+
+默认值:不设置(使用项目默认 Schema)。
+
### overwrite [boolean]
`overwrite` 是否覆盖表或分区,默认值:false。
diff --git a/docs/zh/connectors/source/Maxcompute.md
b/docs/zh/connectors/source/Maxcompute.md
index 4048fb00af..78b7f86591 100644
--- a/docs/zh/connectors/source/Maxcompute.md
+++ b/docs/zh/connectors/source/Maxcompute.md
@@ -20,11 +20,13 @@ import ChangeLog from
'../changelog/connector-maxcompute.md';
| 名称 | 类型 | 必需 | 默认值 |
|----------------|--------|----|---------------|
-| accessId | string | 是 | - |
-| accesskey | string | 是 | - |
+| accessId | string | 否 | - |
+| accesskey | string | 否 | - |
+| sts_token | string | 否 | - |
| endpoint | string | 是 | - |
| project | string | 是 | - |
| table_name | string | 是 | - |
+| schema_name | string | 否 | - |
| partition_spec | string | 否 | - |
| split_row | int | 否 | 10000 |
| read_columns | Array | 否 | - |
@@ -34,11 +36,18 @@ import ChangeLog from
'../changelog/connector-maxcompute.md';
### accessId [string]
-`accessId` 您的 Maxcompute 密钥 Id, 可以从阿里云访问哪个云.
+`accessId` 您的 Maxcompute 密钥 Id.
### accesskey [string]
-`accesskey` Your Maxcompute 密钥, 可以从阿里云访问哪个云.
+`accesskey` 您的 Maxcompute 密钥.
+
+### sts_token [string]
+
+`sts_token` 您的 MaxCompute STS Token,用于临时认证。 **注意:** 如果提供了 `sts_token`,则必须同时提供
`accessId` 和 `accesskey`。
+
+> **免密认证 (ECS RAM Role, 环境变量等)**
+> 要使用免密认证,只需将 `accessId`、`accesskey` 和 `sts_token` 全部留空不填。连接器将自动回退到阿里云默认凭据链
(DefaultCredentialsProvider) 读取凭证(包括环境变量、系统属性、CLI 配置文件、OIDC 以及 ECS RAM 角色)。
### endpoint [string]
@@ -56,6 +65,16 @@ import ChangeLog from '../changelog/connector-maxcompute.md';
`partition_spec` Maxcompute分区表的此规范,例如:ds='20220101'.
+### schema_name [string]
+
+`schema_name` MaxCompute Schema 名称(Project 与 Table 之间的命名空间)。
+仅当表位于 MaxCompute 项目的**非默认 Schema** 时才需要设置。
+参见 [Schema
相关操作](https://help.aliyun.com/zh/maxcompute/user-guide/schema-related-operations)。
+
+使用 `table_list` 时,每个条目可以单独指定 `schema_name`,会覆盖顶层的值。
+
+默认值:不设置(使用项目默认 Schema)。
+
### split_row [int]
`split_row` 每次拆分的行数,默认值: 10000.
diff --git a/seatunnel-connectors-v2/connector-maxcompute/pom.xml
b/seatunnel-connectors-v2/connector-maxcompute/pom.xml
index 00ce56b703..e095231668 100644
--- a/seatunnel-connectors-v2/connector-maxcompute/pom.xml
+++ b/seatunnel-connectors-v2/connector-maxcompute/pom.xml
@@ -30,7 +30,7 @@
<name>SeaTunnel : Connectors V2 : Maxcompute</name>
<properties>
- <maxcompute.version>0.51.0</maxcompute.version>
+ <maxcompute.version>0.51.2</maxcompute.version>
<commons.lang3.version>3.18.0</commons.lang3.version>
</properties>
@@ -47,13 +47,13 @@
</dependency>
<dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>${commons.lang3.version}</version>
+ <groupId>org.apache.seatunnel</groupId>
+ <artifactId>connector-common</artifactId>
+ <version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.seatunnel</groupId>
- <artifactId>connector-common</artifactId>
+ <artifactId>seatunnel-commons-lang3</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
@@ -63,6 +63,18 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <version>9.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm-commons</artifactId>
+ <version>9.6</version>
+ </dependency>
+ </dependencies>
<executions>
<execution>
<goals>
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java
index 36ed0deb20..9c7fc5c7d9 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java
@@ -46,7 +46,6 @@ import com.aliyun.odps.Projects;
import com.aliyun.odps.Table;
import com.aliyun.odps.Tables;
import com.aliyun.odps.account.Account;
-import com.aliyun.odps.account.AliyunAccount;
import com.aliyun.odps.task.SQLTask;
import com.aliyun.odps.type.TypeInfo;
import lombok.extern.slf4j.Slf4j;
@@ -73,10 +72,7 @@ public class MaxComputeCatalog implements Catalog {
@Override
public void open() throws CatalogException {
- account =
- new AliyunAccount(
- readonlyConfig.get(MaxcomputeBaseOptions.ACCESS_ID),
- readonlyConfig.get(MaxcomputeBaseOptions.ACCESS_KEY));
+ account = MaxcomputeUtil.getAccount(readonlyConfig);
}
@Override
@@ -134,7 +130,7 @@ public class MaxComputeCatalog implements Catalog {
@Override
public boolean tableExists(TablePath tablePath) throws CatalogException {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
com.aliyun.odps.Tables tables = odps.tables();
return tables.exists(tablePath.getTableName());
} catch (OdpsException e) {
@@ -157,7 +153,7 @@ public class MaxComputeCatalog implements Catalog {
Table odpsTable;
com.aliyun.odps.TableSchema odpsSchema;
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
odpsTable =
MaxcomputeUtil.parseTable(
odps, tablePath.getDatabaseName(),
tablePath.getTableName());
@@ -204,7 +200,7 @@ public class MaxComputeCatalog implements Catalog {
public void createTable(TablePath tablePath, CatalogTable table, boolean
ignoreIfExists)
throws TableAlreadyExistException, DatabaseNotExistException,
CatalogException {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
SQLTask.run(
odps,
MaxComputeCatalogUtil.getCreateTableStatement(
@@ -222,7 +218,7 @@ public class MaxComputeCatalog implements Catalog {
public void dropTable(TablePath tablePath, boolean ignoreIfNotExists)
throws TableNotExistException, CatalogException {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
SQLTask.run(odps,
MaxComputeCatalogUtil.getDropTableQuery(tablePath, ignoreIfNotExists))
.waitForSuccess();
} catch (OdpsException e) {
@@ -246,7 +242,7 @@ public class MaxComputeCatalog implements Catalog {
public void truncateTable(TablePath tablePath, boolean ignoreIfNotExists)
throws TableNotExistException, CatalogException {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
Table odpsTable = odps.tables().get(tablePath.getTableName());
if (odpsTable.isPartitioned()
&& StringUtils.isNotEmpty(
@@ -265,7 +261,7 @@ public class MaxComputeCatalog implements Catalog {
public void createPartition(TablePath tablePath, PartitionSpec
partitionSpec) {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
Table odpsTable = odps.tables().get(tablePath.getTableName());
odpsTable.createPartition(partitionSpec, true);
} catch (Exception e) {
@@ -275,7 +271,7 @@ public class MaxComputeCatalog implements Catalog {
public void truncatePartition(TablePath tablePath, PartitionSpec
partitionSpec) {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
Table odpsTable = odps.tables().get(tablePath.getTableName());
odpsTable.deletePartition(partitionSpec, true);
odpsTable.createPartition(partitionSpec, true);
@@ -292,7 +288,7 @@ public class MaxComputeCatalog implements Catalog {
@Override
public void executeSql(TablePath tablePath, String sql) {
try {
- Odps odps = getOdps(tablePath.getDatabaseName());
+ Odps odps = getOdps(tablePath.getDatabaseName(),
tablePath.getSchemaName());
String[] sqls = sql.split(";");
for (String s : sqls) {
if (!s.trim().isEmpty()) {
@@ -324,10 +320,22 @@ public class MaxComputeCatalog implements Catalog {
}
}
+ /**
+ * Creates an ODPS client for the given project. When {@code schemaName}
is non-blank, sets it
+ * as the current schema so that subsequent ODPS API calls resolve tables
within that MaxCompute
+ * Schema namespace.
+ */
private Odps getOdps(String project) {
+ return getOdps(project, null);
+ }
+
+ private Odps getOdps(String project, String schemaName) {
Odps odps = new Odps(account);
odps.setEndpoint(readonlyConfig.get(MaxcomputeBaseOptions.ENDPOINT));
odps.setDefaultProject(project);
+ if (StringUtils.isNotEmpty(schemaName)) {
+ odps.setCurrentSchema(schemaName);
+ }
return odps;
}
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalogFactory.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalogFactory.java
index b8f6187617..b40c1d4ae0 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalogFactory.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalogFactory.java
@@ -44,14 +44,16 @@ public class MaxComputeCatalogFactory implements
CatalogFactory {
public OptionRule optionRule() {
return OptionRule.builder()
.required(
- MaxcomputeBaseOptions.ACCESS_ID,
- MaxcomputeBaseOptions.ACCESS_KEY,
MaxcomputeBaseOptions.ENDPOINT,
MaxcomputeBaseOptions.PROJECT,
MaxcomputeBaseOptions.TABLE_NAME)
.optional(
+ MaxcomputeBaseOptions.ACCESS_ID,
+ MaxcomputeBaseOptions.ACCESS_KEY,
+ MaxcomputeBaseOptions.STS_TOKEN,
MaxcomputeBaseOptions.PARTITION_SPEC,
MaxcomputeBaseOptions.SPLIT_ROW,
+ MaxcomputeBaseOptions.SCHEMA_NAME,
ConnectorCommonOptions.SCHEMA)
.build();
}
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/config/MaxcomputeBaseOptions.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/config/MaxcomputeBaseOptions.java
index c581430e89..c445f58503 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/config/MaxcomputeBaseOptions.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/config/MaxcomputeBaseOptions.java
@@ -38,6 +38,11 @@ public class MaxcomputeBaseOptions implements Serializable {
.noDefaultValue()
.withDescription(
"Your Maxcompute accessKey which cloud be access
from Alibaba Cloud");
+ public static final Option<String> STS_TOKEN =
+ Options.key("sts_token")
+ .stringType()
+ .noDefaultValue()
+ .withDescription("Your Maxcompute stsToken for temporary
access");
public static final Option<String> ENDPOINT =
Options.key("endpoint")
.stringType()
@@ -56,6 +61,15 @@ public class MaxcomputeBaseOptions implements Serializable {
.noDefaultValue()
.withDescription("Target Maxcompute table name eg: fake");
+ public static final Option<String> SCHEMA_NAME =
+ Options.key("schema_name")
+ .stringType()
+ .noDefaultValue()
+ .withDescription(
+ "The MaxCompute Schema name (namespace between
Project and Table). "
+ + "Only required when the table resides in
a non-default schema. "
+ + "See
https://www.alibabacloud.com/help/en/maxcompute/user-guide/schema-related-operations");
+
public static final Option<String> PARTITION_SPEC =
Options.key("partition_spec")
.stringType()
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSinkFactory.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSinkFactory.java
index d58f9a4eba..d4467c788b 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSinkFactory.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSinkFactory.java
@@ -41,13 +41,15 @@ public class MaxcomputeSinkFactory implements
TableSinkFactory {
public OptionRule optionRule() {
return OptionRule.builder()
.required(
- MaxcomputeSinkOptions.ACCESS_ID,
- MaxcomputeSinkOptions.ACCESS_KEY,
MaxcomputeSinkOptions.ENDPOINT,
MaxcomputeSinkOptions.PROJECT,
MaxcomputeSinkOptions.TABLE_NAME)
.optional(
+ MaxcomputeSinkOptions.ACCESS_ID,
+ MaxcomputeSinkOptions.ACCESS_KEY,
+ MaxcomputeSinkOptions.STS_TOKEN,
MaxcomputeSinkOptions.PARTITION_SPEC,
+ MaxcomputeSinkOptions.SCHEMA_NAME,
MaxcomputeSinkOptions.OVERWRITE,
MaxcomputeSinkOptions.SCHEMA_SAVE_MODE,
MaxcomputeSinkOptions.DATA_SAVE_MODE,
@@ -68,6 +70,9 @@ public class MaxcomputeSinkFactory implements
TableSinkFactory {
TableIdentifier.of(
context.getCatalogTable().getCatalogName(),
context.getOptions().get(MaxcomputeSinkOptions.PROJECT),
+ context.getOptions()
+
.getOptional(MaxcomputeSinkOptions.SCHEMA_NAME)
+ .orElse(null),
context.getOptions().get(MaxcomputeSinkOptions.TABLE_NAME)),
context.getCatalogTable()));
}
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSource.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSource.java
index 84f3370fe6..1a03a23b48 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSource.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSource.java
@@ -69,6 +69,9 @@ public class MaxcomputeSource
TableIdentifier.of(
"maxcompute",
readonlyConfig.get(MaxcomputeSourceOptions.PROJECT),
+ readonlyConfig
+
.getOptional(MaxcomputeSourceOptions.SCHEMA_NAME)
+ .orElse(null),
readonlyConfig.get(MaxcomputeSourceOptions.TABLE_NAME)),
catalogTable);
tables.put(
@@ -90,9 +93,19 @@ public class MaxcomputeSource
.orElse(
readonlyConfig.get(
MaxcomputeSourceOptions.PROJECT));
+ // schema_name: per-table override, falls back to
top-level value
+ String schemaName =
+ subReadonlyConfig
+
.getOptional(MaxcomputeSourceOptions.SCHEMA_NAME)
+ .orElse(
+ readonlyConfig
+ .getOptional(
+
MaxcomputeSourceOptions.SCHEMA_NAME)
+ .orElse(null));
TablePath tablePath =
TablePath.of(
project,
+ schemaName,
subReadonlyConfig.get(MaxcomputeSourceOptions.TABLE_NAME));
String partitionSpec =
subReadonlyConfig
@@ -139,6 +152,9 @@ public class MaxcomputeSource
TablePath tablePath =
TablePath.of(
readonlyConfig.get(MaxcomputeSourceOptions.PROJECT),
+ readonlyConfig
+
.getOptional(MaxcomputeSourceOptions.SCHEMA_NAME)
+ .orElse(null),
readonlyConfig.get(MaxcomputeSourceOptions.TABLE_NAME));
tables.put(
tablePath,
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSourceFactory.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSourceFactory.java
index c720fef204..65b1be3839 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSourceFactory.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/source/MaxcomputeSourceFactory.java
@@ -42,13 +42,14 @@ public class MaxcomputeSourceFactory implements
TableSourceFactory {
@Override
public OptionRule optionRule() {
return OptionRule.builder()
- .required(
+ .required(MaxcomputeSourceOptions.ENDPOINT)
+ .optional(
MaxcomputeSourceOptions.ACCESS_ID,
MaxcomputeSourceOptions.ACCESS_KEY,
- MaxcomputeSourceOptions.ENDPOINT)
- .optional(
+ MaxcomputeSourceOptions.STS_TOKEN,
MaxcomputeSourceOptions.PARTITION_SPEC,
MaxcomputeSourceOptions.SPLIT_ROW,
+ MaxcomputeSourceOptions.SCHEMA_NAME,
ConnectorCommonOptions.SCHEMA,
MaxcomputeSourceOptions.PROJECT,
MaxcomputeSourceOptions.READ_COLUMNS,
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeOutputFormat.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeOutputFormat.java
index a4a3dfd847..f3fee4f965 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeOutputFormat.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeOutputFormat.java
@@ -185,13 +185,14 @@ public class MaxcomputeOutputFormat {
readonlyConfig.get(MaxcomputeSinkOptions.PROJECT),
readonlyConfig.get(MaxcomputeSinkOptions.TABLE_NAME))
.setPartitionSpec(partitionSpec)
+
.setSchemaName(tunnel.getConfig().getOdps().getCurrentSchema())
.build();
-
} else {
upsertSession =
tunnel.buildUpsertSession(
readonlyConfig.get(MaxcomputeSinkOptions.PROJECT),
readonlyConfig.get(MaxcomputeSinkOptions.TABLE_NAME))
+
.setSchemaName(tunnel.getConfig().getOdps().getCurrentSchema())
.build();
}
}
@@ -208,20 +209,16 @@ public class MaxcomputeOutputFormat {
private void initializeInsertSession() throws TunnelException {
TableTunnel tunnel = MaxcomputeUtil.getTableTunnel(readonlyConfig);
+ PartitionSpec partitionSpec = null;
if
(readonlyConfig.getOptional(MaxcomputeSinkOptions.PARTITION_SPEC).isPresent()) {
- PartitionSpec partitionSpec =
+ partitionSpec =
new
PartitionSpec(readonlyConfig.get(MaxcomputeSinkOptions.PARTITION_SPEC));
- uploadSession =
- tunnel.createUploadSession(
- readonlyConfig.get(MaxcomputeSinkOptions.PROJECT),
-
readonlyConfig.get(MaxcomputeSinkOptions.TABLE_NAME),
- partitionSpec);
-
- } else {
- uploadSession =
- tunnel.createUploadSession(
- readonlyConfig.get(MaxcomputeSinkOptions.PROJECT),
-
readonlyConfig.get(MaxcomputeSinkOptions.TABLE_NAME));
}
+ uploadSession =
+ MaxcomputeUtil.buildUploadSession(
+ tunnel,
+ readonlyConfig.get(MaxcomputeSinkOptions.PROJECT),
+ readonlyConfig.get(MaxcomputeSinkOptions.TABLE_NAME),
+ partitionSpec);
}
}
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtil.java
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtil.java
index 910b2db0bf..94e7b53aa7 100644
---
a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtil.java
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtil.java
@@ -25,11 +25,14 @@ import
org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
import
org.apache.seatunnel.connectors.seatunnel.maxcompute.config.MaxcomputeBaseOptions;
import
org.apache.seatunnel.connectors.seatunnel.maxcompute.exception.MaxcomputeConnectorException;
+import com.aliyun.credentials.provider.DefaultCredentialsProvider;
import com.aliyun.odps.Odps;
import com.aliyun.odps.PartitionSpec;
import com.aliyun.odps.Table;
import com.aliyun.odps.account.Account;
+import com.aliyun.odps.account.AklessAccount;
import com.aliyun.odps.account.AliyunAccount;
+import com.aliyun.odps.account.StsAccount;
import com.aliyun.odps.tunnel.TableTunnel;
import com.aliyun.odps.tunnel.TunnelException;
import lombok.extern.slf4j.Slf4j;
@@ -50,14 +53,32 @@ public class MaxcomputeUtil {
return tableTunnel;
}
+ public static Account getAccount(ReadonlyConfig readonlyConfig) {
+ String stsToken =
readonlyConfig.getOptional(MaxcomputeBaseOptions.STS_TOKEN).orElse(null);
+ String accessId =
readonlyConfig.getOptional(MaxcomputeBaseOptions.ACCESS_ID).orElse(null);
+ String accessKey =
+
readonlyConfig.getOptional(MaxcomputeBaseOptions.ACCESS_KEY).orElse(null);
+
+ if (StringUtils.isNotEmpty(stsToken)) {
+ if (StringUtils.isEmpty(accessId) ||
StringUtils.isEmpty(accessKey)) {
+ throw new IllegalArgumentException(
+ "accessId and accesskey must be provided when
sts_token is used.");
+ }
+ return new StsAccount(accessId, accessKey, stsToken);
+ } else if (StringUtils.isNotEmpty(accessId) &&
StringUtils.isNotEmpty(accessKey)) {
+ return new AliyunAccount(accessId, accessKey);
+ } else {
+ return new AklessAccount(new DefaultCredentialsProvider());
+ }
+ }
+
public static Odps getOdps(ReadonlyConfig readonlyConfig) {
- Account account =
- new AliyunAccount(
- readonlyConfig.get(MaxcomputeBaseOptions.ACCESS_ID),
- readonlyConfig.get(MaxcomputeBaseOptions.ACCESS_KEY));
+ Account account = getAccount(readonlyConfig);
Odps odps = new Odps(account);
odps.setEndpoint(readonlyConfig.get(MaxcomputeBaseOptions.ENDPOINT));
odps.setDefaultProject(readonlyConfig.get(MaxcomputeBaseOptions.PROJECT));
+ odps.setCurrentSchema(
+
readonlyConfig.getOptional(MaxcomputeBaseOptions.SCHEMA_NAME).orElse(null));
return odps;
}
@@ -140,4 +161,18 @@ public class MaxcomputeUtil {
.setPartitionSpec(partitionSpec)
.build();
}
+
+ /**
+ * Note: Unlike DownloadSession and UpsertSession, we do not need to
explicitly pass the schema
+ * name to the builder here. The underlying
`TableTunnel.createUploadSession` automatically
+ * inherits the schema_name directly from
`tunnel.getConfig().getOdps().getCurrentSchema()`.
+ */
+ public static TableTunnel.UploadSession buildUploadSession(
+ TableTunnel tunnel, String projectName, String tableName,
PartitionSpec partitionSpec)
+ throws TunnelException {
+ if (partitionSpec != null) {
+ return tunnel.createUploadSession(projectName, tableName,
partitionSpec);
+ }
+ return tunnel.createUploadSession(projectName, tableName);
+ }
}
diff --git
a/seatunnel-connectors-v2/connector-maxcompute/src/test/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtilTest.java
b/seatunnel-connectors-v2/connector-maxcompute/src/test/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtilTest.java
new file mode 100644
index 0000000000..e3aa017bdd
--- /dev/null
+++
b/seatunnel-connectors-v2/connector-maxcompute/src/test/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/util/MaxcomputeUtilTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.seatunnel.connectors.seatunnel.maxcompute.util;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import com.aliyun.odps.account.Account;
+import com.aliyun.odps.account.AklessAccount;
+import com.aliyun.odps.account.AliyunAccount;
+import com.aliyun.odps.account.StsAccount;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MaxcomputeUtilTest {
+
+ // --- getAccount() tests ---
+
+ /**
+ * When only accessId + accessKey are supplied, the connector must use a
standard AliyunAccount
+ * (long-lived static AK/SK).
+ */
+ @Test
+ void testGetAccountReturnsAliyunAccountWhenAkSkProvided() {
+ Map<String, Object> config = new HashMap<>();
+ config.put("accessId", "my-id");
+ config.put("accesskey", "my-key");
+
+ Account account =
MaxcomputeUtil.getAccount(ReadonlyConfig.fromMap(config));
+
+ Assertions.assertInstanceOf(AliyunAccount.class, account);
+ }
+
+ /**
+ * When accessId + accessKey + sts_token are all supplied, the connector
must use an StsAccount
+ * (temporary credential).
+ */
+ @Test
+ void testGetAccountReturnsStsAccountWhenAllThreeProvided() {
+ Map<String, Object> config = new HashMap<>();
+ config.put("accessId", "my-id");
+ config.put("accesskey", "my-key");
+ config.put("sts_token", "my-sts-token");
+
+ Account account =
MaxcomputeUtil.getAccount(ReadonlyConfig.fromMap(config));
+
+ Assertions.assertInstanceOf(StsAccount.class, account);
+ }
+
+ /**
+ * When none of the access credentials are supplied, the connector must
fall back to the
+ * DefaultCredentialsProvider chain (AklessAccount), enabling passwordless
auth via ECS RAM
+ * roles, environment variables, etc.
+ */
+ @Test
+ void testGetAccountReturnsAklessAccountWhenNothingProvided() {
+ Account account = MaxcomputeUtil.getAccount(ReadonlyConfig.fromMap(new
HashMap<>()));
+
+ Assertions.assertInstanceOf(AklessAccount.class, account);
+ }
+
+ /**
+ * Providing sts_token WITHOUT accessId/accessKey must fail fast with an
explicit
+ * IllegalArgumentException instead of letting a NullPointerException
surface at runtime inside
+ * the ODPS SDK.
+ */
+ @Test
+ void testGetAccountThrowsWhenStsTokenProvidedWithoutAkSk() {
+ Map<String, Object> configMissingBoth = new HashMap<>();
+ configMissingBoth.put("sts_token", "my-sts-token");
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
MaxcomputeUtil.getAccount(ReadonlyConfig.fromMap(configMissingBoth)),
+ "Expected IllegalArgumentException when sts_token is set but
accessId/accesskey are missing");
+ }
+
+ /** Providing sts_token with only accessId (no accessKey) must also fail
fast. */
+ @Test
+ void testGetAccountThrowsWhenStsTokenProvidedWithoutAccessKey() {
+ Map<String, Object> configMissingKey = new HashMap<>();
+ configMissingKey.put("accessId", "my-id");
+ configMissingKey.put("sts_token", "my-sts-token");
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
MaxcomputeUtil.getAccount(ReadonlyConfig.fromMap(configMissingKey)));
+ }
+
+ /** Providing sts_token with only accessKey (no accessId) must also fail
fast. */
+ @Test
+ void testGetAccountThrowsWhenStsTokenProvidedWithoutAccessId() {
+ Map<String, Object> configMissingId = new HashMap<>();
+ configMissingId.put("accesskey", "my-key");
+ configMissingId.put("sts_token", "my-sts-token");
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
MaxcomputeUtil.getAccount(ReadonlyConfig.fromMap(configMissingId)));
+ }
+
+ // --- getOdps() / schema_name propagation test ---
+
+ /**
+ * When schema_name is provided, it must be propagated to
Odps.getCurrentSchema(). This verifies
+ * that non-default MaxCompute schemas are correctly set on the Odps
client, enabling
+ * schema-aware table routing for source and sink operations.
+ */
+ @Test
+ void testGetOdpsSetsCurrentSchemaWhenSchemaNameProvided() {
+ Map<String, Object> config = new HashMap<>();
+ config.put("accessId", "my-id");
+ config.put("accesskey", "my-key");
+ config.put("endpoint", "http://service.odps.aliyun.com/api");
+ config.put("project", "my_project");
+ config.put("schema_name", "my_schema");
+
+ com.aliyun.odps.Odps odps =
MaxcomputeUtil.getOdps(ReadonlyConfig.fromMap(config));
+
+ Assertions.assertEquals("my_schema", odps.getCurrentSchema());
+ }
+
+ /**
+ * When schema_name is absent, Odps.getCurrentSchema() must remain null,
letting the SDK fall
+ * back to the project's default schema transparently.
+ */
+ @Test
+ void testGetOdpsLeavesCurrentSchemaEmptyWhenSchemaNameAbsent() {
+ Map<String, Object> config = new HashMap<>();
+ config.put("accessId", "my-id");
+ config.put("accesskey", "my-key");
+ config.put("endpoint", "http://service.odps.aliyun.com/api");
+ config.put("project", "my_project");
+
+ com.aliyun.odps.Odps odps =
MaxcomputeUtil.getOdps(ReadonlyConfig.fromMap(config));
+
+ Assertions.assertNull(odps.getCurrentSchema());
+ }
+}