From 1d0d85b988c7645874237ad9985897e6201941e9 Mon Sep 17 00:00:00 2001 From: Xinxing <49302071+xinxingi@users.noreply.github.com> Date: Thu, 20 Jul 2023 17:15:22 +0800 Subject: [PATCH] [Feature] doris add datasource (#14391) * Added doris data source plug-in * Update DbType.java * XML configuration * doc and ui * Missing catch of NumberFormatException * code format --------- Co-authored-by: xiangzihao <460888207@qq.com> --- docs/configs/docsdev.js | 8 + docs/docs/en/guide/datasource/doris.md | 23 +++ docs/docs/zh/guide/datasource/doris.md | 17 ++ docs/img/new_ui/dev/datasource/doris.png | Bin 0 -> 73852 bytes .../common/constants/DataSourceConstants.java | 1 + .../dolphinscheduler-datasource-all/pom.xml | 6 + .../dolphinscheduler-datasource-doris/pom.xml | 46 ++++++ .../plugin/doris/DorisDataSourceChannel.java | 30 ++++ .../doris/DorisDataSourceChannelFactory.java | 33 ++++ .../plugin/doris/DorisDataSourceClient.java | 28 ++++ .../doris/param/DorisConnectionParam.java | 37 +++++ .../doris/param/DorisDataSourceParamDTO.java | 41 +++++ .../doris/param/DorisDataSourceProcessor.java | 155 ++++++++++++++++++ .../DorisDataSourceChannelFactoryTest.java | 33 ++++ .../doris/DorisDataSourceChannelTest.java | 39 +++++ .../param/DorisDataSourceProcessorTest.java | 125 ++++++++++++++ .../provider/JDBCDataSourceProviderTest.java | 64 ++++++++ .../doris/utils/DataSourceUtilsTest.java | 151 +++++++++++++++++ dolphinscheduler-datasource-plugin/pom.xml | 1 + .../dolphinscheduler/spi/enums/DbType.java | 3 +- .../src/service/modules/data-source/types.ts | 1 + .../src/views/datasource/list/use-form.ts | 5 + .../components/node/fields/use-datasource.ts | 5 + 23 files changed, 851 insertions(+), 1 deletion(-) create mode 100644 docs/docs/en/guide/datasource/doris.md create mode 100644 docs/docs/zh/guide/datasource/doris.md create mode 100644 docs/img/new_ui/dev/datasource/doris.png create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/pom.xml create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannel.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactory.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceClient.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisConnectionParam.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceParamDTO.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessor.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactoryTest.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelTest.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessorTest.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/provider/JDBCDataSourceProviderTest.java create mode 100644 dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/utils/DataSourceUtilsTest.java diff --git a/docs/configs/docsdev.js b/docs/configs/docsdev.js index 85e681b17f..d94250e9af 100644 --- a/docs/configs/docsdev.js +++ b/docs/configs/docsdev.js @@ -358,6 +358,10 @@ export default { { title: 'HANA', link: '/en-us/docs/dev/user_doc/guide/datasource/hana.html', + }, + { + title: 'doris', + link: '/en-us/docs/dev/user_doc/guide/datasource/doris.html', } ], }, @@ -1067,6 +1071,10 @@ export default { { title: 'HANA', link: '/zh-cn/docs/dev/user_doc/guide/datasource/hana.html', + }, + { + title: 'Doris', + link: '/zh-cn/docs/dev/user_doc/guide/datasource/doris.html', } ], }, diff --git a/docs/docs/en/guide/datasource/doris.md b/docs/docs/en/guide/datasource/doris.md new file mode 100644 index 0000000000..b2d376a7d6 --- /dev/null +++ b/docs/docs/en/guide/datasource/doris.md @@ -0,0 +1,23 @@ +# DORIS + +![doris](../../../../img/new_ui/dev/datasource/doris.png) + +## Datasource Parameters + +| **Datasource** | **Description** | +|----------------------------|---------------------------------------------------------------------------------------| +| Datasource | Select DORIS. | +| Datasource name | Enter the name of the DataSource. | +| Description | Enter a description of the DataSource. | +| IP/Host Name | Enter the DORIS service IP.(If there are multiple IPs, please separate them with `,`) | +| Port | Enter the DORIS service port. | +| Username | Set the username for DORIS connection. | +| Password | Set the password for DORIS connection. | +| Database name | Enter the database name of the DORIS connection. | +| Jdbc connection parameters | Parameter settings for DORIS connection, in JSON format. | + +## Native Supported + +No, read section example in [datasource-setting](../howto/datasource-setting.md) `DataSource Center` section to activate +this datasource. + diff --git a/docs/docs/zh/guide/datasource/doris.md b/docs/docs/zh/guide/datasource/doris.md new file mode 100644 index 0000000000..9b63503530 --- /dev/null +++ b/docs/docs/zh/guide/datasource/doris.md @@ -0,0 +1,17 @@ +# DORIS 数据源 + +![DORIS](../../../../img/new_ui/dev/datasource/doris.png) + +- 数据源:选择 DORIS +- 数据源名称:输入数据源的名称 +- 描述:输入数据源的描述 +- IP 主机名:输入连接 DORIS 的 IP(如有多个IP,请用`,`分隔) +- 端口:输入连接 DORIS 的端口 +- 用户名:设置连接 DORIS 的用户名 +- 密码:设置连接 DORIS 的密码 +- 数据库名:输入连接 DORIS 的数据库名称 +- Jdbc 连接参数:用于 DORIS 连接的参数设置,以 JSON 形式填写 + +## 是否原生支持 + +否,使用前需请参考 [数据源配置](../howto/datasource-setting.md) 中的 "数据源中心" 章节激活数据源。 diff --git a/docs/img/new_ui/dev/datasource/doris.png b/docs/img/new_ui/dev/datasource/doris.png new file mode 100644 index 0000000000000000000000000000000000000000..29d1043fa50b8962738193339e43f62e1c606837 GIT binary patch literal 73852 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 Uh~xQwL6dB_?7tmudolphinscheduler-datasource-vertica ${project.version} + + + org.apache.dolphinscheduler + dolphinscheduler-datasource-doris + ${project.version} + diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/pom.xml b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/pom.xml new file mode 100644 index 0000000000..a0d31b23e8 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/pom.xml @@ -0,0 +1,46 @@ + + + + 4.0.0 + + org.apache.dolphinscheduler + dolphinscheduler-datasource-plugin + dev-SNAPSHOT + + + dolphinscheduler-datasource-doris + jar + ${project.artifactId} + + + + org.apache.dolphinscheduler + dolphinscheduler-spi + + + org.apache.dolphinscheduler + dolphinscheduler-datasource-api + + + + mysql + mysql-connector-java + + + diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannel.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannel.java new file mode 100644 index 0000000000..33e0a9c501 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannel.java @@ -0,0 +1,30 @@ +/* + * 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.dolphinscheduler.plugin.doris; + +import org.apache.dolphinscheduler.spi.datasource.BaseConnectionParam; +import org.apache.dolphinscheduler.spi.datasource.DataSourceChannel; +import org.apache.dolphinscheduler.spi.datasource.DataSourceClient; +import org.apache.dolphinscheduler.spi.enums.DbType; + +public class DorisDataSourceChannel implements DataSourceChannel { + + @Override + public DataSourceClient createDataSourceClient(BaseConnectionParam baseConnectionParam, DbType dbType) { + return null; + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactory.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactory.java new file mode 100644 index 0000000000..5fe7bda4f0 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactory.java @@ -0,0 +1,33 @@ +/* + * 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.dolphinscheduler.plugin.doris; + +import org.apache.dolphinscheduler.spi.datasource.DataSourceChannel; +import org.apache.dolphinscheduler.spi.datasource.DataSourceChannelFactory; + +public class DorisDataSourceChannelFactory implements DataSourceChannelFactory { + + @Override + public DataSourceChannel create() { + return new DorisDataSourceChannel(); + } + + @Override + public String getName() { + return "doris"; + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceClient.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceClient.java new file mode 100644 index 0000000000..ae84cbbb8a --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceClient.java @@ -0,0 +1,28 @@ +/* + * 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.dolphinscheduler.plugin.doris; + +import org.apache.dolphinscheduler.plugin.datasource.api.client.CommonDataSourceClient; +import org.apache.dolphinscheduler.spi.datasource.BaseConnectionParam; +import org.apache.dolphinscheduler.spi.enums.DbType; + +public class DorisDataSourceClient extends CommonDataSourceClient { + + public DorisDataSourceClient(BaseConnectionParam baseConnectionParam, DbType dbType) { + super(baseConnectionParam, dbType); + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisConnectionParam.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisConnectionParam.java new file mode 100644 index 0000000000..c6257038e7 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisConnectionParam.java @@ -0,0 +1,37 @@ +/* + * 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.dolphinscheduler.plugin.doris.param; + +import org.apache.dolphinscheduler.spi.datasource.BaseConnectionParam; + +public class DorisConnectionParam extends BaseConnectionParam { + + @Override + public String toString() { + return "DorisConnectionParam{" + + "user='" + user + '\'' + + ", password='" + password + '\'' + + ", address='" + address + '\'' + + ", database='" + database + '\'' + + ", jdbcUrl='" + jdbcUrl + '\'' + + ", driverLocation='" + driverLocation + '\'' + + ", driverClassName='" + driverClassName + '\'' + + ", validationQuery='" + validationQuery + '\'' + + ", other='" + other + '\'' + + '}'; + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceParamDTO.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceParamDTO.java new file mode 100644 index 0000000000..44b4b1e3c4 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceParamDTO.java @@ -0,0 +1,41 @@ +/* + * 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.dolphinscheduler.plugin.doris.param; + +import org.apache.dolphinscheduler.plugin.datasource.api.datasource.BaseDataSourceParamDTO; +import org.apache.dolphinscheduler.spi.enums.DbType; + +public class DorisDataSourceParamDTO extends BaseDataSourceParamDTO { + + @Override + public String toString() { + return "DorisDataSourceParamDTO{" + + "name='" + name + '\'' + + ", note='" + note + '\'' + + ", host='" + host + '\'' + + ", port=" + port + + ", database='" + database + '\'' + + ", userName='" + userName + '\'' + + ", password='" + password + '\'' + + ", other='" + other + '\'' + + '}'; + } + @Override + public DbType getType() { + return DbType.DORIS; + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessor.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessor.java new file mode 100644 index 0000000000..99f115c2c8 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/main/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessor.java @@ -0,0 +1,155 @@ +/* + * 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.dolphinscheduler.plugin.doris.param; + +import org.apache.dolphinscheduler.common.constants.Constants; +import org.apache.dolphinscheduler.common.constants.DataSourceConstants; +import org.apache.dolphinscheduler.common.utils.JSONUtils; +import org.apache.dolphinscheduler.plugin.datasource.api.datasource.AbstractDataSourceProcessor; +import org.apache.dolphinscheduler.plugin.datasource.api.datasource.BaseDataSourceParamDTO; +import org.apache.dolphinscheduler.plugin.datasource.api.datasource.DataSourceProcessor; +import org.apache.dolphinscheduler.plugin.datasource.api.utils.PasswordUtils; +import org.apache.dolphinscheduler.spi.datasource.ConnectionParam; +import org.apache.dolphinscheduler.spi.enums.DbType; + +import org.apache.commons.collections4.MapUtils; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.auto.service.AutoService; + +@AutoService(DataSourceProcessor.class) +public class DorisDataSourceProcessor extends AbstractDataSourceProcessor { + + @Override + public BaseDataSourceParamDTO castDatasourceParamDTO(String paramJson) { + return JSONUtils.parseObject(paramJson, DorisDataSourceParamDTO.class); + } + + @Override + public BaseDataSourceParamDTO createDatasourceParamDTO(String connectionJson) throws NumberFormatException { + DorisConnectionParam connectionParams = (DorisConnectionParam) createConnectionParams(connectionJson); + DorisDataSourceParamDTO dorisDataSourceParamDTO = new DorisDataSourceParamDTO(); + + dorisDataSourceParamDTO.setUserName(connectionParams.getUser()); + dorisDataSourceParamDTO.setDatabase(connectionParams.getDatabase()); + dorisDataSourceParamDTO.setOther(connectionParams.getOther()); + + String address = connectionParams.getAddress(); + String[] hostSeperator = address.split(Constants.DOUBLE_SLASH); + String[] hostPortArrays = hostSeperator[hostSeperator.length - 1].split(Constants.COMMA); + + dorisDataSourceParamDTO.setPort(Integer.parseInt(hostPortArrays[0].split(Constants.COLON)[1])); + + for (int i = 0; i < hostPortArrays.length; i++) { + hostPortArrays[i] = hostPortArrays[i].split(Constants.COLON)[0]; + } + dorisDataSourceParamDTO.setHost(String.join(",", hostPortArrays)); + + return dorisDataSourceParamDTO; + } + + @Override + public ConnectionParam createConnectionParams(BaseDataSourceParamDTO datasourceParam) { + DorisDataSourceParamDTO mysqlDatasourceParam = (DorisDataSourceParamDTO) datasourceParam; + String[] hosts = datasourceParam.getHost().split(Constants.COMMA); + + for (int i = 0; i < hosts.length; i++) { + hosts[i] = String.format(Constants.FORMAT_S_S_COLON, hosts[i], mysqlDatasourceParam.getPort()); + } + + String address = String.format("%s%s", DataSourceConstants.JDBC_MYSQL_LOADBALANCE, String.join(",", hosts)); + String jdbcUrl = String.format(Constants.FORMAT_S_S, address, mysqlDatasourceParam.getDatabase()); + + DorisConnectionParam mysqlConnectionParam = new DorisConnectionParam(); + mysqlConnectionParam.setJdbcUrl(jdbcUrl); + mysqlConnectionParam.setDatabase(mysqlDatasourceParam.getDatabase()); + mysqlConnectionParam.setAddress(address); + mysqlConnectionParam.setUser(mysqlDatasourceParam.getUserName()); + mysqlConnectionParam.setPassword(PasswordUtils.encodePassword(mysqlDatasourceParam.getPassword())); + mysqlConnectionParam.setDriverClassName(getDatasourceDriver()); + mysqlConnectionParam.setValidationQuery(getValidationQuery()); + mysqlConnectionParam.setOther(mysqlDatasourceParam.getOther()); + + return mysqlConnectionParam; + } + + @Override + public ConnectionParam createConnectionParams(String connectionJson) { + return JSONUtils.parseObject(connectionJson, DorisConnectionParam.class); + } + + @Override + public String getDatasourceDriver() { + return DataSourceConstants.COM_MYSQL_CJ_JDBC_DRIVER; + } + + @Override + public String getValidationQuery() { + return DataSourceConstants.MYSQL_VALIDATION_QUERY; + } + + @Override + public String getJdbcUrl(ConnectionParam connectionParam) { + DorisConnectionParam mysqlConnectionParam = (DorisConnectionParam) connectionParam; + String jdbcUrl = mysqlConnectionParam.getJdbcUrl(); + if (MapUtils.isNotEmpty(mysqlConnectionParam.getOther())) { + return String.format("%s?%s", jdbcUrl, transformOther(mysqlConnectionParam.getOther())); + } + return String.format("%s", jdbcUrl); + } + + @Override + public Connection getConnection(ConnectionParam connectionParam) throws ClassNotFoundException, SQLException { + DorisConnectionParam dorisConnectionParam = (DorisConnectionParam) connectionParam; + Class.forName(getDatasourceDriver()); + + return DriverManager.getConnection(getJdbcUrl(connectionParam), dorisConnectionParam.getUser(), + PasswordUtils.decodePassword(dorisConnectionParam.getPassword())); + } + + @Override + public DbType getDbType() { + return DbType.DORIS; + } + + @Override + public DataSourceProcessor create() { + return new DorisDataSourceProcessor(); + } + + private String transformOther(Map paramMap) { + if (MapUtils.isEmpty(paramMap)) { + return null; + } + Map otherMap = new HashMap<>(); + paramMap.forEach((k, v) -> otherMap.put(k, v)); + if (MapUtils.isEmpty(otherMap)) { + return null; + } + List otherList = new ArrayList<>(); + otherMap.forEach((key, value) -> otherList.add(String.format("%s=%s", key, value))); + return String.join("&", otherList); + } + +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactoryTest.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactoryTest.java new file mode 100644 index 0000000000..66f3753f2e --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelFactoryTest.java @@ -0,0 +1,33 @@ +/* + * 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.dolphinscheduler.plugin.doris; + +import org.apache.dolphinscheduler.spi.datasource.DataSourceChannel; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DorisDataSourceChannelFactoryTest { + + @Test + public void testCreate() { + DorisDataSourceChannelFactory sourceChannelFactory = new DorisDataSourceChannelFactory(); + DataSourceChannel dataSourceChannel = sourceChannelFactory.create(); + Assertions.assertNotNull(dataSourceChannel); + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelTest.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelTest.java new file mode 100644 index 0000000000..114dd7aa9d --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/DorisDataSourceChannelTest.java @@ -0,0 +1,39 @@ +/* + * 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.dolphinscheduler.plugin.doris; + +import org.apache.dolphinscheduler.plugin.doris.param.DorisConnectionParam; +import org.apache.dolphinscheduler.spi.enums.DbType; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class DorisDataSourceChannelTest { + + @Test + public void testCreateDataSourceClient() { + DorisDataSourceChannel sourceChannel = Mockito.mock(DorisDataSourceChannel.class); + DorisDataSourceClient dataSourceClient = Mockito.mock(DorisDataSourceClient.class); + Mockito.when(sourceChannel.createDataSourceClient(Mockito.any(), Mockito.any())).thenReturn(dataSourceClient); + Assertions.assertNotNull(sourceChannel.createDataSourceClient(new DorisConnectionParam(), DbType.DORIS)); + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessorTest.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessorTest.java new file mode 100644 index 0000000000..8939070c5a --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/param/DorisDataSourceProcessorTest.java @@ -0,0 +1,125 @@ +/* + * 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.dolphinscheduler.plugin.doris.param; + +import org.apache.dolphinscheduler.common.constants.DataSourceConstants; +import org.apache.dolphinscheduler.plugin.datasource.api.utils.PasswordUtils; +import org.apache.dolphinscheduler.spi.enums.DbType; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class DorisDataSourceProcessorTest { + + private DorisDataSourceProcessor dorisDatasourceProcessor = new DorisDataSourceProcessor(); + + @Test + public void testCreateConnectionParams() { + Map props = new HashMap<>(); + props.put("serverTimezone", "utc"); + DorisDataSourceParamDTO dorisDatasourceParamDTO = new DorisDataSourceParamDTO(); + + dorisDatasourceParamDTO.setUserName("root"); + dorisDatasourceParamDTO.setPassword("123456"); + dorisDatasourceParamDTO.setHost("localhost"); + dorisDatasourceParamDTO.setPort(3306); + dorisDatasourceParamDTO.setDatabase("default"); + dorisDatasourceParamDTO.setOther(props); + try (MockedStatic mockedPasswordUtils = Mockito.mockStatic(PasswordUtils.class)) { + Mockito.when(PasswordUtils.encodePassword(Mockito.anyString())).thenReturn("test"); + DorisConnectionParam connectionParams = (DorisConnectionParam) dorisDatasourceProcessor + .createConnectionParams(dorisDatasourceParamDTO); + Assertions.assertEquals("jdbc:mysql:loadbalance://localhost:3306", connectionParams.getAddress()); + Assertions.assertEquals("jdbc:mysql:loadbalance://localhost:3306/default", connectionParams.getJdbcUrl()); + } + + dorisDatasourceParamDTO.setUserName("root"); + dorisDatasourceParamDTO.setPassword("123456"); + dorisDatasourceParamDTO.setHost("localhost,localhost1"); + dorisDatasourceParamDTO.setPort(3306); + dorisDatasourceParamDTO.setDatabase("default"); + dorisDatasourceParamDTO.setOther(props); + try (MockedStatic mockedPasswordUtils = Mockito.mockStatic(PasswordUtils.class)) { + Mockito.when(PasswordUtils.encodePassword(Mockito.anyString())).thenReturn("test"); + DorisConnectionParam connectionParams = (DorisConnectionParam) dorisDatasourceProcessor + .createConnectionParams(dorisDatasourceParamDTO); + Assertions.assertEquals("jdbc:mysql:loadbalance://localhost:3306,localhost1:3306", + connectionParams.getAddress()); + Assertions.assertEquals("jdbc:mysql:loadbalance://localhost:3306,localhost1:3306/default", + connectionParams.getJdbcUrl()); + } + } + + @Test + public void testCreateConnectionParams2() { + String connectionJson = "{\"user\":\"root\",\"password\":\"123456\",\"address\":\"jdbc:mysql://localhost:3306\"" + + ",\"database\":\"default\",\"jdbcUrl\":\"jdbc:mysql://localhost:3306/default\"}"; + DorisConnectionParam connectionParams = (DorisConnectionParam) dorisDatasourceProcessor + .createConnectionParams(connectionJson); + Assertions.assertNotNull(connectionJson); + Assertions.assertEquals("root", connectionParams.getUser()); + } + + @Test + public void testGetDatasourceDriver() { + Assertions.assertEquals(DataSourceConstants.COM_MYSQL_CJ_JDBC_DRIVER, + dorisDatasourceProcessor.getDatasourceDriver()); + } + + @Test + public void testGetJdbcUrl() { + DorisConnectionParam dorisConnectionParam = new DorisConnectionParam(); + dorisConnectionParam.setJdbcUrl( + "jdbc:mysql://localhost:3306/default?allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false"); + Assertions.assertEquals( + "jdbc:mysql://localhost:3306/default?allowLoadLocalInfile=false&autoDeserialize=false&allowLocalInfile=false&allowUrlInLocalInfile=false", + dorisDatasourceProcessor.getJdbcUrl(dorisConnectionParam)); + } + + @Test + public void testGetDbType() { + Assertions.assertEquals(DbType.DORIS, dorisDatasourceProcessor.getDbType()); + } + + @Test + public void testGetValidationQuery() { + Assertions.assertEquals(DataSourceConstants.MYSQL_VALIDATION_QUERY, + dorisDatasourceProcessor.getValidationQuery()); + } + + @Test + public void testGetDatasourceUniqueId() { + DorisConnectionParam dorisConnectionParam = new DorisConnectionParam(); + dorisConnectionParam.setJdbcUrl("jdbc:mysql://localhost:3306/default"); + dorisConnectionParam.setUser("root"); + dorisConnectionParam.setPassword("123456"); + try (MockedStatic mockedPasswordUtils = Mockito.mockStatic(PasswordUtils.class)) { + Mockito.when(PasswordUtils.encodePassword(Mockito.anyString())).thenReturn("123456"); + Assertions.assertEquals("doris@root@123456@jdbc:mysql://localhost:3306/default", + dorisDatasourceProcessor.getDatasourceUniqueId(dorisConnectionParam, DbType.DORIS)); + } + } +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/provider/JDBCDataSourceProviderTest.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/provider/JDBCDataSourceProviderTest.java new file mode 100644 index 0000000000..d12420a071 --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/provider/JDBCDataSourceProviderTest.java @@ -0,0 +1,64 @@ +/* + * 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.dolphinscheduler.plugin.doris.provider; + +import org.apache.dolphinscheduler.plugin.datasource.api.provider.JDBCDataSourceProvider; +import org.apache.dolphinscheduler.plugin.doris.param.DorisConnectionParam; +import org.apache.dolphinscheduler.spi.enums.DbType; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.zaxxer.hikari.HikariDataSource; + +@ExtendWith(MockitoExtension.class) +public class JDBCDataSourceProviderTest { + + @Test + public void testCreateJdbcDataSource() { + try ( + MockedStatic mockedJDBCDataSourceProvider = + Mockito.mockStatic(JDBCDataSourceProvider.class)) { + HikariDataSource dataSource = Mockito.mock(HikariDataSource.class); + mockedJDBCDataSourceProvider + .when(() -> JDBCDataSourceProvider.createJdbcDataSource(Mockito.any(), Mockito.any())) + .thenReturn(dataSource); + Assertions.assertNotNull( + JDBCDataSourceProvider.createJdbcDataSource(new DorisConnectionParam(), DbType.DORIS)); + } + } + + @Test + public void testCreateOneSessionJdbcDataSource() { + try ( + MockedStatic mockedJDBCDataSourceProvider = + Mockito.mockStatic(JDBCDataSourceProvider.class)) { + HikariDataSource dataSource = Mockito.mock(HikariDataSource.class); + mockedJDBCDataSourceProvider + .when(() -> JDBCDataSourceProvider.createOneSessionJdbcDataSource(Mockito.any(), Mockito.any())) + .thenReturn(dataSource); + Assertions.assertNotNull( + JDBCDataSourceProvider.createOneSessionJdbcDataSource(new DorisConnectionParam(), DbType.DORIS)); + } + } + +} diff --git a/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/utils/DataSourceUtilsTest.java b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/utils/DataSourceUtilsTest.java new file mode 100644 index 0000000000..010ea2971f --- /dev/null +++ b/dolphinscheduler-datasource-plugin/dolphinscheduler-datasource-doris/src/test/java/org/apache/dolphinscheduler/plugin/doris/utils/DataSourceUtilsTest.java @@ -0,0 +1,151 @@ +/* + * 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.dolphinscheduler.plugin.doris.utils; + +import org.apache.dolphinscheduler.common.utils.JSONUtils; +import org.apache.dolphinscheduler.common.utils.PropertyUtils; +import org.apache.dolphinscheduler.plugin.datasource.api.plugin.DataSourceClientProvider; +import org.apache.dolphinscheduler.plugin.datasource.api.utils.CommonUtils; +import org.apache.dolphinscheduler.plugin.datasource.api.utils.DataSourceUtils; +import org.apache.dolphinscheduler.plugin.datasource.api.utils.PasswordUtils; +import org.apache.dolphinscheduler.plugin.doris.param.DorisConnectionParam; +import org.apache.dolphinscheduler.plugin.doris.param.DorisDataSourceParamDTO; +import org.apache.dolphinscheduler.spi.datasource.ConnectionParam; +import org.apache.dolphinscheduler.spi.enums.DbType; + +import java.sql.Connection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class DataSourceUtilsTest { + + @Test + public void testCheckDatasourceParamOne() { + DorisDataSourceParamDTO dorisDatasourceParamDTO = new DorisDataSourceParamDTO(); + dorisDatasourceParamDTO.setHost("localhost,localhost1"); + dorisDatasourceParamDTO.setDatabase("default"); + Map other = new HashMap<>(); + other.put("serverTimezone", "Asia/Shanghai"); + other.put("queryTimeout", "-1"); + other.put("characterEncoding", "utf8"); + dorisDatasourceParamDTO.setOther(other); + DataSourceUtils.checkDatasourceParam(dorisDatasourceParamDTO); + Assertions.assertTrue(true); + Assertions.assertEquals("localhost,localhost1", dorisDatasourceParamDTO.getHost()); + } + + @Test + public void testBuildConnectionParamTwo() { + DorisDataSourceParamDTO dorisDatasourceParamDTO = new DorisDataSourceParamDTO(); + dorisDatasourceParamDTO.setHost("localhost"); + dorisDatasourceParamDTO.setDatabase("default"); + dorisDatasourceParamDTO.setUserName("root"); + dorisDatasourceParamDTO.setPort(3306); + dorisDatasourceParamDTO.setPassword("123456"); + + try ( + MockedStatic mockedStaticPasswordUtils = Mockito.mockStatic(PasswordUtils.class); + MockedStatic mockedStaticCommonUtils = Mockito.mockStatic(CommonUtils.class)) { + mockedStaticPasswordUtils.when(() -> PasswordUtils.encodePassword(Mockito.anyString())) + .thenReturn("123456"); + mockedStaticCommonUtils.when(CommonUtils::getKerberosStartupState).thenReturn(false); + ConnectionParam connectionParam = DataSourceUtils.buildConnectionParams(dorisDatasourceParamDTO); + Assertions.assertNotNull(connectionParam); + } + } + + @Test + public void testBuildConnectionParamsThree() { + DorisDataSourceParamDTO dorisDatasourceParamDTO = new DorisDataSourceParamDTO(); + dorisDatasourceParamDTO.setHost("localhost"); + dorisDatasourceParamDTO.setDatabase("default"); + dorisDatasourceParamDTO.setUserName("root"); + dorisDatasourceParamDTO.setPort(3306); + dorisDatasourceParamDTO.setPassword("123456"); + ConnectionParam connectionParam = + DataSourceUtils.buildConnectionParams(DbType.DORIS, JSONUtils.toJsonString(dorisDatasourceParamDTO)); + Assertions.assertNotNull(connectionParam); + } + + @Test + public void testGetConnection() throws ExecutionException { + try ( + MockedStatic mockedStaticPropertyUtils = Mockito.mockStatic(PropertyUtils.class); + MockedStatic mockedStaticDataSourceClientProvider = + Mockito.mockStatic(DataSourceClientProvider.class)) { + mockedStaticPropertyUtils.when(() -> PropertyUtils.getLong("kerberos.expire.time", 24L)).thenReturn(24L); + DataSourceClientProvider clientProvider = Mockito.mock(DataSourceClientProvider.class); + mockedStaticDataSourceClientProvider.when(DataSourceClientProvider::getInstance).thenReturn(clientProvider); + + Connection connection = Mockito.mock(Connection.class); + Mockito.when(clientProvider.getConnection(Mockito.any(), Mockito.any())).thenReturn(connection); + + DorisConnectionParam connectionParam = new DorisConnectionParam(); + connectionParam.setUser("root"); + connectionParam.setPassword("123456"); + connection = DataSourceClientProvider.getInstance().getConnection(DbType.DORIS, connectionParam); + + Assertions.assertNotNull(connection); + } + } + + @Test + public void testGetJdbcUrl() { + DorisConnectionParam dorisConnectionParam = new DorisConnectionParam(); + dorisConnectionParam.setJdbcUrl("jdbc:mysql://localhost,localhost2:3308?allowLoadLocalInfile=false"); + String jdbcUrl = DataSourceUtils.getJdbcUrl(DbType.DORIS, dorisConnectionParam); + Assertions.assertEquals( + "jdbc:mysql://localhost,localhost2:3308?allowLoadLocalInfile=false", + jdbcUrl); + } + + @Test + public void testBuildDatasourceParamDTO() { + DorisConnectionParam connectionParam = new DorisConnectionParam(); + connectionParam.setJdbcUrl( + "jdbc:mysql://localhost,localhost2:3308?allowLoadLocalInfile=false"); + connectionParam.setAddress("jdbc:mysql://localhost:3308,localhost2:3308"); + connectionParam.setUser("root"); + connectionParam.setPassword("123456"); + + Assertions.assertNotNull( + DataSourceUtils.buildDatasourceParamDTO(DbType.DORIS, JSONUtils.toJsonString(connectionParam))); + + } + + @Test + public void testGetDatasourceProcessor() { + Assertions.assertNotNull(DataSourceUtils.getDatasourceProcessor(DbType.DORIS)); + } + + @Test + public void testGetDatasourceProcessorError() { + Assertions.assertThrows(Exception.class, () -> { + DataSourceUtils.getDatasourceProcessor(null); + }); + } +} diff --git a/dolphinscheduler-datasource-plugin/pom.xml b/dolphinscheduler-datasource-plugin/pom.xml index 4882382cd7..79261be779 100644 --- a/dolphinscheduler-datasource-plugin/pom.xml +++ b/dolphinscheduler-datasource-plugin/pom.xml @@ -51,6 +51,7 @@ dolphinscheduler-datasource-databend dolphinscheduler-datasource-snowflake dolphinscheduler-datasource-vertica + dolphinscheduler-datasource-doris diff --git a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/DbType.java b/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/DbType.java index a937713f6b..169f0ad9bf 100644 --- a/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/DbType.java +++ b/dolphinscheduler-spi/src/main/java/org/apache/dolphinscheduler/spi/enums/DbType.java @@ -50,7 +50,8 @@ public enum DbType { DATABEND(19, "databend"), SNOWFLAKE(20, "snowflake"), VERTICA(21, "vertica"), - HANA(22, "hana"); + HANA(22, "hana"), + DORIS(23, "doris"); private static final Map DB_TYPE_MAP = Arrays.stream(DbType.values()).collect(toMap(DbType::getCode, Functions.identity())); diff --git a/dolphinscheduler-ui/src/service/modules/data-source/types.ts b/dolphinscheduler-ui/src/service/modules/data-source/types.ts index a493f4119a..a01618e4b3 100644 --- a/dolphinscheduler-ui/src/service/modules/data-source/types.ts +++ b/dolphinscheduler-ui/src/service/modules/data-source/types.ts @@ -37,6 +37,7 @@ type IDataBase = | 'DATABEND' | 'SNOWFLAKE' | 'HANA' + | 'DORIS' type IDataBaseLabel = | 'MYSQL' diff --git a/dolphinscheduler-ui/src/views/datasource/list/use-form.ts b/dolphinscheduler-ui/src/views/datasource/list/use-form.ts index 06d7ca3e0e..3f9229e162 100644 --- a/dolphinscheduler-ui/src/views/datasource/list/use-form.ts +++ b/dolphinscheduler-ui/src/views/datasource/list/use-form.ts @@ -401,6 +401,11 @@ export const datasourceType: IDataBaseOptionKeys = { value: 'HANA', label: 'HANA', defaultPort: 30015 + }, + DORIS: { + value: 'DORIS', + label: 'DORIS', + defaultPort: 9030 } } diff --git a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-datasource.ts b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-datasource.ts index 9d6bfba71d..25640583d7 100644 --- a/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-datasource.ts +++ b/dolphinscheduler-ui/src/views/projects/task/components/node/fields/use-datasource.ts @@ -132,6 +132,11 @@ export function useDatasource( id: 22, code: 'HANA', disabled: false + }, + { + id: 23, + code: 'DORIS', + disabled: false } ]