This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch branch-1.2-unstable in repository https://gitbox.apache.org/repos/asf/doris.git
commit 05b875f5218169022c35dda14366a2be8f8b9fe8 Author: Tiewei Fang <43782773+bepppo...@users.noreply.github.com> AuthorDate: Wed Nov 30 11:28:08 2022 +0800 [feature](multi-catalog) support Jdbc catalog (#14527) Issue Number: close #xxx I add jdbc catalog for doris multi-catalog feature. Currently, the jdbc catalog only supports MYSQL DBMS. TODO: support for postgre DB Support for other databases. Problem summary For jdbc catalog, we can create catalog like: CREATE CATALOG jdbc4 PROPERTIES ( "type"="jdbc", "jdbc.user"="root", "jdbc.password"="123456", "jdbc.jdbc_url" = "jdbc:mysql://127.0.0.1:13396/demo?yearIsDateType=false", "jdbc.driver_url" = "file:/mnt/disk2/ftw/tools/jar/mysql-connector-java-5.1.47/mysql-connector-java-5.1.47.jar", "jdbc.driver_class" = "com.mysql.jdbc.Driver" ); Note: yearIsDateType is a param of jdbc: If yearIsDateType configuration property is set to false, then the returned object type is java.sql.Short. If set to true (the default), then the returned object is of type java.sql.Date with the date set to January 1st, at midnight. To compat with mysql, we force the use of yearIsDateType=false in FE. if user sets yearIsDateType=true, doris FE will force to change yearIsDateType=false. --- be/src/vec/exec/vjdbc_connector.cpp | 15 +- .../docker-compose/mysql/init/03-create-table.sql | 30 ++ .../docker-compose/mysql/init/04-insert.sql | 7 +- .../java/org/apache/doris/catalog/JdbcTable.java | 10 +- .../java/org/apache/doris/catalog/OdbcTable.java | 12 +- .../java/org/apache/doris/catalog/TableIf.java | 4 +- .../doris/catalog/external/EsExternalDatabase.java | 10 +- .../doris/catalog/external/EsExternalTable.java | 7 - .../doris/catalog/external/HMSExternalTable.java | 7 - ...rnalDatabase.java => JdbcExternalDatabase.java} | 99 ++--- .../doris/catalog/external/JdbcExternalTable.java | 89 +++++ .../apache/doris/datasource/CatalogFactory.java | 3 + .../org/apache/doris/datasource/CatalogIf.java | 4 + .../org/apache/doris/datasource/CatalogMgr.java | 1 + .../apache/doris/datasource/EsExternalCatalog.java | 36 +- .../apache/doris/datasource/ExternalCatalog.java | 31 +- .../doris/datasource/HMSExternalCatalog.java | 30 -- .../apache/doris/datasource/InitCatalogLog.java | 1 + .../apache/doris/datasource/InitDatabaseLog.java | 1 + .../doris/datasource/JdbcExternalCatalog.java | 175 ++++++++ .../org/apache/doris/external/jdbc/JdbcClient.java | 441 +++++++++++++++++++++ .../doris/external/jdbc/JdbcClientException.java | 28 ++ .../main/java/org/apache/doris/load/ExportJob.java | 3 +- .../org/apache/doris/planner/JdbcScanNode.java | 14 + .../apache/doris/planner/SingleNodePlanner.java | 6 +- .../java/org/apache/doris/udf/JdbcExecutor.java | 3 - .../jdbc_catalog_p0/test_mysql_jdbc_catalog.out | 154 +++++++ .../jdbc_catalog_p0/test_mysql_jdbc_catalog.groovy | 103 +++++ 28 files changed, 1177 insertions(+), 147 deletions(-) diff --git a/be/src/vec/exec/vjdbc_connector.cpp b/be/src/vec/exec/vjdbc_connector.cpp index 8af1159361..f3a7688d38 100644 --- a/be/src/vec/exec/vjdbc_connector.cpp +++ b/be/src/vec/exec/vjdbc_connector.cpp @@ -113,9 +113,18 @@ Status JdbcConnector::open(RuntimeState* state, bool read) { std::string local_location; std::hash<std::string> hash_str; auto function_cache = UserFunctionCache::instance(); - RETURN_IF_ERROR(function_cache->get_jarpath( - std::abs((int64_t)hash_str(_conn_param.resource_name)), _conn_param.driver_path, - _conn_param.driver_checksum, &local_location)); + if (_conn_param.resource_name.empty()) { + // for jdbcExternalTable, _conn_param.resource_name == "" + // so, we use _conn_param.driver_path as key of jarpath + RETURN_IF_ERROR(function_cache->get_jarpath( + std::abs((int64_t)hash_str(_conn_param.driver_path)), _conn_param.driver_path, + _conn_param.driver_checksum, &local_location)); + } else { + RETURN_IF_ERROR(function_cache->get_jarpath( + std::abs((int64_t)hash_str(_conn_param.resource_name)), _conn_param.driver_path, + _conn_param.driver_checksum, &local_location)); + } + TJdbcExecutorCtorParams ctor_params; ctor_params.__set_statement(_sql_str); ctor_params.__set_jdbc_url(_conn_param.jdbc_url); diff --git a/docker/thirdparties/docker-compose/mysql/init/03-create-table.sql b/docker/thirdparties/docker-compose/mysql/init/03-create-table.sql index d09fc95f63..b0e4890013 100644 --- a/docker/thirdparties/docker-compose/mysql/init/03-create-table.sql +++ b/docker/thirdparties/docker-compose/mysql/init/03-create-table.sql @@ -184,4 +184,34 @@ CREATE TABLE doris_test.ex_tb17 ( `order_source` tinyint(4) NULL ); +create table ex_tb18 ( + num_tinyint tinyint, + num_tinyint2 tinyint unsigned, + num_smallint SMALLINT, + num_smallint2 SMALLINT unsigned, + num_mediumint MEDIUMINT, + num_mediumint2 MEDIUMINT unsigned, + num_bigint BIGINT, + num_int int(5), + num_int2 int(5) unsigned, + num_int3 int(5) unsigned zerofill, + num_float float(5, 2), + num_double double(10, 3), + num_decimal decimal(20, 2), + char_value1 char(5), + char_value2 char(100), + varchar_value1 varchar(5), + varchar_value2 varchar(10), + varchar_value3 varchar(100), + text_value TEXT(123) +) engine=innodb charset=utf8; + +create table ex_tb19 ( + date_value date, + time_value time, + year_value year, + datetime_value datetime, + timestamp_value timestamp +) engine=innodb charset=utf8; + diff --git a/docker/thirdparties/docker-compose/mysql/init/04-insert.sql b/docker/thirdparties/docker-compose/mysql/init/04-insert.sql index 5f6bc34c59..684a4137e2 100644 --- a/docker/thirdparties/docker-compose/mysql/init/04-insert.sql +++ b/docker/thirdparties/docker-compose/mysql/init/04-insert.sql @@ -1112,5 +1112,10 @@ INSERT INTO doris_test.ex_tb17 (id,media_order_id,supplier_id,agent_policy_type, (7,3,2,8,5297.81,9,3,23753694.2,96930000.64,'c',7,2,0,'b','e',1,5), (9,3,9,1,4785.38,1,5,95199488.12,94869703.42,'a',4,4,0,'c','d',2,4), (5,6,4,5,9137.82,2,7,26526675.7,90098303.36,'a',6,7,0,'d','e',4,1); +INSERT INTO ex_tb18 VALUES +(1,1,1,1,1,1,1,1,1,1,3.14,13.141,2342.23,'aa','asdawdasdaasdasd','aaa','bbbbbbbb','xaqwdqwdqwdqdwqwdqwdqd','asdadwqdqwddqwdsadqwdas'), +(127,255,32767, 65535, 8388607, 16777215, 9223372036854775807, -2147483648, 2147483647, 4294967295,33.1415926,422113.1411231,2342.23123123,'aa','asdawdasdaasdasd','aaa','bbbbbbbb','xaqwdqwdqwdqd','asdadwqdqwdsadqwdas'), +(-128,255,-32768, 65535, -8388608, 16777215, -9223372036854775808, -2147483648, 2147483647, 4294967295,33.1415926,422113.1411231,2342.23123123,'aa','asdawdasdaasdasd','aaa','bbbbbbbb','xaqwdqwdqwdqd','asdas'); - +INSERT INTO ex_tb19 VALUES +('2022-11-27', '07:09:51', '2022', '2022-11-27 07:09:51', '2022-11-27 07:09:51'); diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/JdbcTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/JdbcTable.java index bba585904b..c2851bb03a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/JdbcTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/JdbcTable.java @@ -29,7 +29,9 @@ import org.apache.doris.thrift.TTableType; import com.google.common.base.Strings; import com.google.common.collect.Maps; +import lombok.Setter; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections.map.CaseInsensitiveMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -37,10 +39,10 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; +@Setter public class JdbcTable extends Table { private static final Logger LOG = LogManager.getLogger(JdbcTable.class); @@ -66,7 +68,7 @@ public class JdbcTable extends Table { private String checkSum; static { - Map<String, TOdbcTableType> tempMap = new HashMap<>(); + Map<String, TOdbcTableType> tempMap = new CaseInsensitiveMap(); tempMap.put("mysql", TOdbcTableType.MYSQL); tempMap.put("postgresql", TOdbcTableType.POSTGRESQL); tempMap.put("sqlserver", TOdbcTableType.SQLSERVER); @@ -85,6 +87,10 @@ public class JdbcTable extends Table { validate(properties); } + public JdbcTable(long id, String name, List<Column> schema, TableType type) { + super(id, name, type, schema); + } + public String getCheckSum() { return checkSum; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OdbcTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/OdbcTable.java index 35e55bb9e4..b2464c257f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OdbcTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OdbcTable.java @@ -74,7 +74,17 @@ public class OdbcTable extends Table { // For different databases, special characters need to be escaped private static String mysqlProperName(String name) { - return "`" + name + "`"; + // In JdbcExternalTable, the name contains databaseName, like: db.table + // So, we should split db and table, then switch to `db`.`table`. + String[] fields = name.split("\\."); + String result = ""; + for (int i = 0; i < fields.length; ++i) { + if (i != 0) { + result += "."; + } + result += ("`" + fields[i] + "`"); + } + return result; } private static String mssqlProperName(String name) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java index 9b831bb524..361ef44ba2 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/TableIf.java @@ -118,7 +118,7 @@ public interface TableIf { */ enum TableType { MYSQL, ODBC, OLAP, SCHEMA, INLINE_VIEW, VIEW, BROKER, ELASTICSEARCH, HIVE, ICEBERG, HUDI, JDBC, - TABLE_VALUED_FUNCTION, HMS_EXTERNAL_TABLE, ES_EXTERNAL_TABLE, MATERIALIZED_VIEW; + TABLE_VALUED_FUNCTION, HMS_EXTERNAL_TABLE, ES_EXTERNAL_TABLE, MATERIALIZED_VIEW, JDBC_EXTERNAL_TABLE; public String toEngineName() { switch (this) { @@ -143,6 +143,7 @@ public interface TableIf { case HUDI: return "Hudi"; case JDBC: + case JDBC_EXTERNAL_TABLE: return "jdbc"; case TABLE_VALUED_FUNCTION: return "Table_Valued_Function"; @@ -171,6 +172,7 @@ public interface TableIf { case HIVE: case HUDI: case JDBC: + case JDBC_EXTERNAL_TABLE: case TABLE_VALUED_FUNCTION: case HMS_EXTERNAL_TABLE: case ES_EXTERNAL_TABLE: diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java index e9b3ce354b..bd0e322c5f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java @@ -23,14 +23,14 @@ import org.apache.doris.datasource.ExternalCatalog; import org.apache.doris.datasource.InitDatabaseLog; import org.apache.doris.persist.gson.GsonPostProcessable; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.gson.annotations.SerializedName; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -118,14 +118,14 @@ public class EsExternalDatabase extends ExternalDatabase<EsExternalTable> implem @Override public Set<String> getTableNamesWithLock() { - // Doesn't need to lock because everytime we call the hive metastore api to get table names. - return new HashSet<>(extCatalog.listTableNames(null, name)); + makeSureInitialized(); + return Sets.newHashSet(tableNameToId.keySet()); } @Override public List<EsExternalTable> getTables() { makeSureInitialized(); - return new ArrayList<>(idToTbl.values()); + return Lists.newArrayList(idToTbl.values()); } @Override diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalTable.java index e2efd6aae1..ca085d4720 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalTable.java @@ -66,13 +66,6 @@ public class EsExternalTable extends ExternalTable { return type.name(); } - /** - * get database name of es table. - */ - public String getDbName() { - return dbName; - } - @Override public TTableDescriptor toThrift() { List<Column> schema = getFullSchema(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/HMSExternalTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/HMSExternalTable.java index 4b09ca70b5..bfefe1f829 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/HMSExternalTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/HMSExternalTable.java @@ -250,13 +250,6 @@ public class HMSExternalTable extends ExternalTable { return 0; } - /** - * get database name of hms table. - */ - public String getDbName() { - return dbName; - } - /** * get the dla type for scan node to get right information. */ diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/JdbcExternalDatabase.java similarity index 70% copy from fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java copy to fe/fe-core/src/main/java/org/apache/doris/catalog/external/JdbcExternalDatabase.java index e9b3ce354b..45d6b1a647 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/EsExternalDatabase.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/JdbcExternalDatabase.java @@ -18,93 +18,67 @@ package org.apache.doris.catalog.external; import org.apache.doris.catalog.Env; -import org.apache.doris.datasource.EsExternalCatalog; import org.apache.doris.datasource.ExternalCatalog; import org.apache.doris.datasource.InitDatabaseLog; +import org.apache.doris.datasource.JdbcExternalCatalog; import org.apache.doris.persist.gson.GsonPostProcessable; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.gson.annotations.SerializedName; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; -/** - * Elasticsearch metastore external database. - */ -public class EsExternalDatabase extends ExternalDatabase<EsExternalTable> implements GsonPostProcessable { - private static final Logger LOG = LogManager.getLogger(EsExternalDatabase.class); +public class JdbcExternalDatabase extends ExternalDatabase<JdbcExternalTable> implements GsonPostProcessable { + private static final Logger LOG = LogManager.getLogger(JdbcExternalDatabase.class); // Cache of table name to table id. private Map<String, Long> tableNameToId = Maps.newConcurrentMap(); @SerializedName(value = "idToTbl") - private Map<Long, EsExternalTable> idToTbl = Maps.newConcurrentMap(); + private Map<Long, JdbcExternalTable> idToTbl = Maps.newConcurrentMap(); /** - * Create Elasticsearch external database. + * Create Jdbc external database. * * @param extCatalog External data source this database belongs to. * @param id database id. * @param name database name. */ - public EsExternalDatabase(ExternalCatalog extCatalog, long id, String name) { + public JdbcExternalDatabase(ExternalCatalog extCatalog, long id, String name) { super(extCatalog, id, name); } - public void replayInitDb(InitDatabaseLog log, ExternalCatalog catalog) { - Map<String, Long> tmpTableNameToId = Maps.newConcurrentMap(); - Map<Long, EsExternalTable> tmpIdToTbl = Maps.newConcurrentMap(); - for (int i = 0; i < log.getRefreshCount(); i++) { - EsExternalTable table = getTableForReplay(log.getRefreshTableIds().get(i)); - tmpTableNameToId.put(table.getName(), table.getId()); - tmpIdToTbl.put(table.getId(), table); - } - for (int i = 0; i < log.getCreateCount(); i++) { - EsExternalTable table = new EsExternalTable(log.getCreateTableIds().get(i), - log.getCreateTableNames().get(i), name, (EsExternalCatalog) catalog); - tmpTableNameToId.put(table.getName(), table.getId()); - tmpIdToTbl.put(table.getId(), table); - } - tableNameToId = tmpTableNameToId; - idToTbl = tmpIdToTbl; - initialized = true; - } - - public void setTableExtCatalog(ExternalCatalog extCatalog) { - for (EsExternalTable table : idToTbl.values()) { - table.setCatalog(extCatalog); - } - } - + // TODO(ftw): drew out the public multiple parts @Override protected void init() { InitDatabaseLog initDatabaseLog = new InitDatabaseLog(); - initDatabaseLog.setType(InitDatabaseLog.Type.ES); + initDatabaseLog.setType(InitDatabaseLog.Type.JDBC); initDatabaseLog.setCatalogId(extCatalog.getId()); initDatabaseLog.setDbId(id); List<String> tableNames = extCatalog.listTableNames(null, name); if (tableNames != null) { Map<String, Long> tmpTableNameToId = Maps.newConcurrentMap(); - Map<Long, EsExternalTable> tmpIdToTbl = Maps.newHashMap(); + Map<Long, JdbcExternalTable> tmpIdToTbl = Maps.newHashMap(); for (String tableName : tableNames) { long tblId; if (tableNameToId != null && tableNameToId.containsKey(tableName)) { tblId = tableNameToId.get(tableName); tmpTableNameToId.put(tableName, tblId); - EsExternalTable table = idToTbl.get(tblId); + JdbcExternalTable table = idToTbl.get(tblId); tmpIdToTbl.put(tblId, table); initDatabaseLog.addRefreshTable(tblId); } else { tblId = Env.getCurrentEnv().getNextId(); tmpTableNameToId.put(tableName, tblId); - EsExternalTable table = new EsExternalTable(tblId, tableName, name, (EsExternalCatalog) extCatalog); + JdbcExternalTable table = new JdbcExternalTable(tblId, tableName, name, + (JdbcExternalCatalog) extCatalog); tmpIdToTbl.put(tblId, table); initDatabaseLog.addCreateTable(tblId, tableName); } @@ -116,20 +90,47 @@ public class EsExternalDatabase extends ExternalDatabase<EsExternalTable> implem Env.getCurrentEnv().getEditLog().logInitExternalDb(initDatabaseLog); } + public void setTableExtCatalog(ExternalCatalog extCatalog) { + for (JdbcExternalTable table : idToTbl.values()) { + table.setCatalog(extCatalog); + } + } + + public void replayInitDb(InitDatabaseLog log, ExternalCatalog catalog) { + Map<String, Long> tmpTableNameToId = Maps.newConcurrentMap(); + Map<Long, JdbcExternalTable> tmpIdToTbl = Maps.newConcurrentMap(); + for (int i = 0; i < log.getRefreshCount(); i++) { + JdbcExternalTable table = getTableForReplay(log.getRefreshTableIds().get(i)); + tmpTableNameToId.put(table.getName(), table.getId()); + tmpIdToTbl.put(table.getId(), table); + } + for (int i = 0; i < log.getCreateCount(); i++) { + JdbcExternalTable table = new JdbcExternalTable(log.getCreateTableIds().get(i), + log.getCreateTableNames().get(i), name, (JdbcExternalCatalog) catalog); + tmpTableNameToId.put(table.getName(), table.getId()); + tmpIdToTbl.put(table.getId(), table); + } + tableNameToId = tmpTableNameToId; + idToTbl = tmpIdToTbl; + initialized = true; + } + + // TODO(ftw): drew @Override public Set<String> getTableNamesWithLock() { - // Doesn't need to lock because everytime we call the hive metastore api to get table names. - return new HashSet<>(extCatalog.listTableNames(null, name)); + makeSureInitialized(); + return Sets.newHashSet(tableNameToId.keySet()); } @Override - public List<EsExternalTable> getTables() { + public List<JdbcExternalTable> getTables() { makeSureInitialized(); - return new ArrayList<>(idToTbl.values()); + return Lists.newArrayList(idToTbl.values()); } + // TODO(ftw): drew @Override - public EsExternalTable getTableNullable(String tableName) { + public JdbcExternalTable getTableNullable(String tableName) { makeSureInitialized(); if (!tableNameToId.containsKey(tableName)) { return null; @@ -138,25 +139,25 @@ public class EsExternalDatabase extends ExternalDatabase<EsExternalTable> implem } @Override - public EsExternalTable getTableNullable(long tableId) { + public JdbcExternalTable getTableNullable(long tableId) { makeSureInitialized(); return idToTbl.get(tableId); } - public EsExternalTable getTableForReplay(long tableId) { + public JdbcExternalTable getTableForReplay(long tableId) { return idToTbl.get(tableId); } @Override public void gsonPostProcess() throws IOException { tableNameToId = Maps.newConcurrentMap(); - for (EsExternalTable tbl : idToTbl.values()) { + for (JdbcExternalTable tbl : idToTbl.values()) { tableNameToId.put(tbl.getName(), tbl.getId()); } rwLock = new ReentrantReadWriteLock(true); } - public void addTableForTest(EsExternalTable tbl) { + public void addTableForTest(JdbcExternalTable tbl) { idToTbl.put(tbl.getId(), tbl); tableNameToId.put(tbl.getName(), tbl.getId()); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/external/JdbcExternalTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/JdbcExternalTable.java new file mode 100644 index 0000000000..b5ac609402 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/external/JdbcExternalTable.java @@ -0,0 +1,89 @@ +// 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.catalog.external; + +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.JdbcTable; +import org.apache.doris.datasource.JdbcExternalCatalog; +import org.apache.doris.thrift.TTableDescriptor; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; + +/** + * Elasticsearch external table. + */ +public class JdbcExternalTable extends ExternalTable { + private static final Logger LOG = LogManager.getLogger(JdbcExternalTable.class); + + private JdbcTable jdbcTable; + + /** + * Create jdbc external table. + * + * @param id Table id. + * @param name Table name. + * @param dbName Database name. + * @param catalog HMSExternalDataSource. + */ + public JdbcExternalTable(long id, String name, String dbName, JdbcExternalCatalog catalog) { + super(id, name, catalog, dbName, TableType.JDBC_EXTERNAL_TABLE); + } + + protected synchronized void makeSureInitialized() { + if (!objectCreated) { + jdbcTable = toJdbcTable(); + objectCreated = true; + } + } + + public JdbcTable getJdbcTable() { + makeSureInitialized(); + return jdbcTable; + } + + @Override + public String getMysqlType() { + return type.name(); + } + + @Override + public TTableDescriptor toThrift() { + makeSureInitialized(); + return jdbcTable.toThrift(); + } + + private JdbcTable toJdbcTable() { + List<Column> schema = getFullSchema(); + JdbcExternalCatalog jdbcCatalog = (JdbcExternalCatalog) catalog; + String fullDbName = this.dbName + "." + this.name; + JdbcTable jdbcTable = new JdbcTable(this.id, fullDbName, schema, TableType.JDBC_EXTERNAL_TABLE); + jdbcTable.setExternalTableName(fullDbName); + jdbcTable.setJdbcTypeName(jdbcCatalog.getDatabaseTypeName()); + jdbcTable.setJdbcUrl(jdbcCatalog.getJdbcUrl()); + jdbcTable.setJdbcUser(jdbcCatalog.getJdbcUser()); + jdbcTable.setJdbcPasswd(jdbcCatalog.getJdbcPasswd()); + jdbcTable.setDriverClass(jdbcCatalog.getDriverClass()); + jdbcTable.setDriverUrl(jdbcCatalog.getDriverUrl()); + jdbcTable.setCheckSum(jdbcCatalog.getCheckSum()); + return jdbcTable; + } + +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogFactory.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogFactory.java index 25ff3ffa5f..da43241508 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogFactory.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogFactory.java @@ -79,6 +79,9 @@ public class CatalogFactory { validateEsCatalogProperties(props); catalog = new EsExternalCatalog(catalogId, name, props); break; + case "jdbc": + catalog = new JdbcExternalCatalog(catalogId, name, props); + break; default: throw new RuntimeException("Unknown catalog type: " + type); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java index 0a6aabc404..a22d72a361 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogIf.java @@ -111,4 +111,8 @@ public interface CatalogIf<T extends DatabaseIf> { return getDbOrException(dbId, s -> new AnalysisException(ErrorCode.ERR_BAD_DB_ERROR.formatErrorMsg(s), ErrorCode.ERR_BAD_DB_ERROR)); } + + default void onClose() { + + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java index 018f8f9f0e..fbd6a75d2c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/CatalogMgr.java @@ -96,6 +96,7 @@ public class CatalogMgr implements Writable, GsonPostProcessable { private CatalogIf removeCatalog(long catalogId) { CatalogIf catalog = idToCatalog.remove(catalogId); if (catalog != null) { + catalog.onClose(); nameToCatalog.remove(catalog.getName()); Env.getCurrentEnv().getExtMetaCacheMgr().removeCache(catalog.getName()); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/EsExternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/EsExternalCatalog.java index 1f8c79cc1d..496eabfc8c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/EsExternalCatalog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/EsExternalCatalog.java @@ -21,8 +21,6 @@ package org.apache.doris.datasource; import org.apache.doris.catalog.Column; import org.apache.doris.catalog.Env; import org.apache.doris.catalog.external.EsExternalDatabase; -import org.apache.doris.catalog.external.ExternalDatabase; -import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.DdlException; import org.apache.doris.external.elasticsearch.EsRestClient; import org.apache.doris.external.elasticsearch.EsUtil; @@ -33,7 +31,6 @@ import lombok.Getter; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.ArrayList; @@ -159,25 +156,15 @@ public class EsExternalCatalog extends ExternalCatalog { @Override public List<String> listTableNames(SessionContext ctx, String dbName) { - return esRestClient.listTable(); - } - - @Nullable - @Override - public ExternalDatabase getDbNullable(String dbName) { makeSureInitialized(); - String realDbName = ClusterNamespace.getNameFromFullName(dbName); - if (!dbNameToId.containsKey(realDbName)) { - return null; + EsExternalDatabase db = (EsExternalDatabase) idToDb.get(dbNameToId.get(dbName)); + if (db != null && db.isInitialized()) { + List<String> names = Lists.newArrayList(); + db.getTables().stream().forEach(table -> names.add(table.getName())); + return names; + } else { + return esRestClient.listTable(); } - return idToDb.get(dbNameToId.get(realDbName)); - } - - @Nullable - @Override - public ExternalDatabase getDbNullable(long dbId) { - makeSureInitialized(); - return idToDb.get(dbId); } @Override @@ -185,15 +172,6 @@ public class EsExternalCatalog extends ExternalCatalog { return esRestClient.existIndex(this.esRestClient.getClient(), tblName); } - @Override - public List<Long> getDbIds() { - return Lists.newArrayList(dbNameToId.values()); - } - - public ExternalDatabase getDbForReplay(long dbId) { - return idToDb.get(dbId); - } - @Override public void gsonPostProcess() throws IOException { super.gsonPostProcess(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java index d50b4d39e5..0eebe17298 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalCatalog.java @@ -22,6 +22,7 @@ import org.apache.doris.catalog.Env; import org.apache.doris.catalog.external.EsExternalDatabase; import org.apache.doris.catalog.external.ExternalDatabase; import org.apache.doris.catalog.external.HMSExternalDatabase; +import org.apache.doris.catalog.external.JdbcExternalDatabase; import org.apache.doris.cluster.ClusterNamespace; import org.apache.doris.common.io.Text; import org.apache.doris.common.io.Writable; @@ -30,10 +31,10 @@ import org.apache.doris.persist.gson.GsonPostProcessable; import org.apache.doris.persist.gson.GsonUtils; import org.apache.doris.qe.MasterCatalogExecutor; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.annotations.SerializedName; import lombok.Data; -import org.apache.commons.lang.NotImplementedException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; @@ -60,7 +61,7 @@ public abstract class ExternalCatalog implements CatalogIf<ExternalDatabase>, Wr protected String type; // save properties of this catalog, such as hive meta store url. @SerializedName(value = "catalogProperty") - protected CatalogProperty catalogProperty = new CatalogProperty(); + protected CatalogProperty catalogProperty; @SerializedName(value = "initialized") private boolean initialized = false; @SerializedName(value = "idToDb") @@ -134,7 +135,7 @@ public abstract class ExternalCatalog implements CatalogIf<ExternalDatabase>, Wr } public ExternalDatabase getDbForReplay(long dbId) { - throw new NotImplementedException(); + return idToDb.get(dbId); } public abstract List<Column> getSchema(String dbName, String tblName); @@ -162,13 +163,25 @@ public abstract class ExternalCatalog implements CatalogIf<ExternalDatabase>, Wr @Nullable @Override public ExternalDatabase getDbNullable(String dbName) { - throw new NotImplementedException(); + makeSureInitialized(); + String realDbName = ClusterNamespace.getNameFromFullName(dbName); + if (!dbNameToId.containsKey(realDbName)) { + return null; + } + return idToDb.get(dbNameToId.get(realDbName)); } @Nullable @Override public ExternalDatabase getDbNullable(long dbId) { - throw new NotImplementedException(); + makeSureInitialized(); + return idToDb.get(dbId); + } + + @Override + public List<Long> getDbIds() { + makeSureInitialized(); + return Lists.newArrayList(dbNameToId.values()); } @Override @@ -217,6 +230,14 @@ public abstract class ExternalCatalog implements CatalogIf<ExternalDatabase>, Wr tmpIdToDb.put(db.getId(), db); } break; + case JDBC: + for (int i = 0; i < log.getCreateCount(); i++) { + JdbcExternalDatabase db = new JdbcExternalDatabase( + this, log.getCreateDbIds().get(i), log.getCreateDbNames().get(i)); + tmpDbNameToId.put(db.getFullName(), db.getId()); + tmpIdToDb.put(db.getId(), db); + } + break; default: break; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/HMSExternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/HMSExternalCatalog.java index dd81e327cb..15cb40fe5c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/HMSExternalCatalog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/HMSExternalCatalog.java @@ -22,7 +22,6 @@ import org.apache.doris.catalog.Env; import org.apache.doris.catalog.HiveMetaStoreClientHelper; import org.apache.doris.catalog.external.ExternalDatabase; import org.apache.doris.catalog.external.HMSExternalDatabase; -import org.apache.doris.cluster.ClusterNamespace; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -30,7 +29,6 @@ import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.metastore.api.FieldSchema; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Map; @@ -129,34 +127,6 @@ public class HMSExternalCatalog extends ExternalCatalog { return client.tableExists(getRealTableName(dbName), tblName); } - @Nullable - @Override - public ExternalDatabase getDbNullable(String dbName) { - makeSureInitialized(); - String realDbName = ClusterNamespace.getNameFromFullName(dbName); - if (!dbNameToId.containsKey(realDbName)) { - return null; - } - return idToDb.get(dbNameToId.get(realDbName)); - } - - @Nullable - @Override - public ExternalDatabase getDbNullable(long dbId) { - makeSureInitialized(); - return idToDb.get(dbId); - } - - @Override - public List<Long> getDbIds() { - makeSureInitialized(); - return Lists.newArrayList(dbNameToId.values()); - } - - public ExternalDatabase getDbForReplay(long dbId) { - return idToDb.get(dbId); - } - public PooledHiveMetaStoreClient getClient() { makeSureInitialized(); return client; diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/InitCatalogLog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/InitCatalogLog.java index 48deec84e6..c7ed1696b2 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InitCatalogLog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InitCatalogLog.java @@ -35,6 +35,7 @@ public class InitCatalogLog implements Writable { enum Type { HMS, ES, + JDBC, UNKNOWN; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/InitDatabaseLog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/InitDatabaseLog.java index eade3d12b8..2471593626 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InitDatabaseLog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InitDatabaseLog.java @@ -35,6 +35,7 @@ public class InitDatabaseLog implements Writable { public enum Type { HMS, ES, + JDBC, UNKNOWN; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/JdbcExternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/JdbcExternalCatalog.java new file mode 100644 index 0000000000..c8b571e47f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/JdbcExternalCatalog.java @@ -0,0 +1,175 @@ +// 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; + +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.external.ExternalDatabase; +import org.apache.doris.catalog.external.JdbcExternalDatabase; +import org.apache.doris.external.jdbc.JdbcClient; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import lombok.Getter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +@Getter +public class JdbcExternalCatalog extends ExternalCatalog { + private static final Logger LOG = LogManager.getLogger(JdbcExternalCatalog.class); + + public static final String PROP_USER = "jdbc.user"; + public static final String PROP_PASSWORD = "jdbc.password"; + public static final String PROP_JDBC_URL = "jdbc.jdbc_url"; + public static final String PROP_DRIVER_URL = "jdbc.driver_url"; + public static final String PROP_DRIVER_CLASS = "jdbc.driver_class"; + + private JdbcClient jdbcClient; + private String databaseTypeName; + private String jdbcUser; + private String jdbcPasswd; + private String jdbcUrl; + private String driverUrl; + private String driverClass; + private String checkSum; + + public JdbcExternalCatalog(long catalogId, String name, Map<String, String> props) { + this.id = catalogId; + this.name = name; + this.type = "jdbc"; + setProperties(props); + this.catalogProperty = new CatalogProperty(); + this.catalogProperty.setProperties(props); + } + + @Override + public void onClose() { + if (jdbcClient != null) { + jdbcClient.closeClient(); + } + } + + private void setProperties(Map<String, String> props) { + jdbcUser = props.getOrDefault(PROP_USER, ""); + jdbcPasswd = props.getOrDefault(PROP_PASSWORD, ""); + jdbcUrl = props.getOrDefault(PROP_JDBC_URL, ""); + handleJdbcUrl(); + driverUrl = props.getOrDefault(PROP_DRIVER_URL, ""); + driverClass = props.getOrDefault(PROP_DRIVER_CLASS, ""); + } + + // `yearIsDateType` is a parameter of JDBC, and the default is `yearIsDateType=true` + // We force the use of `yearIsDateType=false` + private void handleJdbcUrl() { + // delete all space in jdbcUrl + jdbcUrl.replaceAll(" ", ""); + if (jdbcUrl.contains("yearIsDateType=false")) { + return; + } else if (jdbcUrl.contains("yearIsDateType=true")) { + jdbcUrl.replaceAll("yearIsDateType=true", "yearIsDateType=false"); + } else { + String yearIsDateType = "yearIsDateType=false"; + if (jdbcUrl.contains("?")) { + if (jdbcUrl.charAt(jdbcUrl.length() - 1) != '?') { + jdbcUrl += "&"; + } + } else { + jdbcUrl += "?"; + } + jdbcUrl += yearIsDateType; + } + } + + @Override + protected void initLocalObjectsImpl() { + jdbcClient = new JdbcClient(jdbcUser, jdbcPasswd, jdbcUrl, driverUrl, driverClass); + databaseTypeName = jdbcClient.getDbType(); + checkSum = jdbcClient.getCheckSum(); + } + + @Override + protected void init() { + Map<String, Long> tmpDbNameToId = Maps.newConcurrentMap(); + Map<Long, ExternalDatabase> tmpIdToDb = Maps.newConcurrentMap(); + InitCatalogLog initCatalogLog = new InitCatalogLog(); + initCatalogLog.setCatalogId(id); + initCatalogLog.setType(InitCatalogLog.Type.JDBC); + List<String> allDatabaseNames = jdbcClient.getDatabaseNameList(); + for (String dbName : allDatabaseNames) { + long dbId; + if (dbNameToId != null && dbNameToId.containsKey(dbName)) { + dbId = dbNameToId.get(dbName); + tmpDbNameToId.put(dbName, dbId); + ExternalDatabase db = idToDb.get(dbId); + db.setUnInitialized(); + tmpIdToDb.put(dbId, db); + initCatalogLog.addRefreshDb(dbId); + } else { + dbId = Env.getCurrentEnv().getNextId(); + tmpDbNameToId.put(dbName, dbId); + JdbcExternalDatabase db = new JdbcExternalDatabase(this, dbId, dbName); + tmpIdToDb.put(dbId, db); + initCatalogLog.addCreateDb(dbId, dbName); + } + } + dbNameToId = tmpDbNameToId; + idToDb = tmpIdToDb; + Env.getCurrentEnv().getEditLog().logInitCatalog(initCatalogLog); + } + + @Override + public List<String> listDatabaseNames(SessionContext ctx) { + makeSureInitialized(); + return Lists.newArrayList(dbNameToId.keySet()); + } + + @Override + public List<String> listTableNames(SessionContext ctx, String dbName) { + makeSureInitialized(); + JdbcExternalDatabase db = (JdbcExternalDatabase) idToDb.get(dbNameToId.get(dbName)); + if (db != null && db.isInitialized()) { + List<String> names = Lists.newArrayList(); + db.getTables().stream().forEach(table -> names.add(table.getName())); + return names; + } else { + return jdbcClient.getTablesNameList(dbName); + } + } + + @Override + public boolean tableExist(SessionContext ctx, String dbName, String tblName) { + makeSureInitialized(); + return jdbcClient.isTableExist(dbName, tblName); + } + + @Override + public void gsonPostProcess() throws IOException { + super.gsonPostProcess(); + setProperties(this.catalogProperty.getProperties()); + } + + @Override + public List<Column> getSchema(String dbName, String tblName) { + makeSureInitialized(); + return jdbcClient.getColumnsFromJdbc(dbName, tblName); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/external/jdbc/JdbcClient.java b/fe/fe-core/src/main/java/org/apache/doris/external/jdbc/JdbcClient.java new file mode 100644 index 0000000000..9148629d46 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/external/jdbc/JdbcClient.java @@ -0,0 +1,441 @@ +// 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.external.jdbc; + +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.catalog.Type; +import org.apache.doris.common.FeConstants; +import org.apache.doris.common.util.Util; + +import com.google.common.collect.Lists; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import lombok.Data; +import lombok.Getter; +import org.apache.commons.codec.binary.Hex; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +@Getter +public class JdbcClient { + private static final Logger LOG = LogManager.getLogger(JdbcClient.class); + private static final String MYSQL = "MYSQL"; + // private static final String ORACLE = "ORACLE"; + // private static final String SQLSERVER = "SQLSERVER"; + // private static final String POSTGRESQL = "POSTGRESQL"; + private static final int HTTP_TIMEOUT_MS = 10000; + + private String dbType; + private String jdbcUser; + private String jdbcPasswd; + private String jdbcUrl; + private String driverUrl; + private String driverClass; + private String checkSum; + + private URLClassLoader classLoader = null; + + private HikariDataSource dataSource = null; + + + public JdbcClient(String user, String password, String jdbcUrl, String driverUrl, String driverClass) { + this.jdbcUser = user; + this.jdbcPasswd = password; + this.jdbcUrl = jdbcUrl; + this.driverUrl = driverUrl; + this.driverClass = driverClass; + this.dbType = parseDbType(jdbcUrl); + this.checkSum = computeObjectChecksum(); + + try { + // TODO(ftw): The problem here is that the jar package is handled by FE + // and URLClassLoader may load the jar package directly into memory + URL[] urls = {new URL(driverUrl)}; + // set parent ClassLoader to null, we can achieve class loading isolation. + classLoader = URLClassLoader.newInstance(urls, null); + Thread.currentThread().setContextClassLoader(classLoader); + HikariConfig config = new HikariConfig(); + config.setDriverClassName(driverClass); + config.setJdbcUrl(jdbcUrl); + config.setUsername(jdbcUser); + config.setPassword(jdbcPasswd); + config.setMaximumPoolSize(1); + dataSource = new HikariDataSource(config); + } catch (MalformedURLException e) { + throw new JdbcClientException("MalformedURLException to load class about " + driverUrl, e); + } + } + + public void closeClient() { + dataSource.close(); + } + + public String parseDbType(String url) { + if (url.startsWith("jdbc:mysql") || url.startsWith("jdbc:mariadb")) { + return MYSQL; + } + // else if (url.startsWith("jdbc:oracle")) { + // return ORACLE; + // } + // else if (url.startsWith("jdbc:sqlserver")) { + // return SQLSERVER; + // } else if (url.startsWith("jdbc:postgresql")) { + // return POSTGRESQL; + // } + throw new JdbcClientException("Unsupported jdbc database type, please check jdbcUrl: " + jdbcUrl); + } + + public Connection getConnection() throws JdbcClientException { + Connection conn = null; + try { + conn = dataSource.getConnection(); + } catch (Exception e) { + throw new JdbcClientException("Can not connect to jdbc", e); + } + return conn; + } + + // close connection + public void close(Object o) { + if (o == null) { + return; + } + if (o instanceof ResultSet) { + try { + ((ResultSet) o).close(); + } catch (SQLException e) { + throw new JdbcClientException("Can not close ResultSet ", e); + } + } else if (o instanceof Statement) { + try { + ((Statement) o).close(); + } catch (SQLException e) { + throw new JdbcClientException("Can not close Statement ", e); + } + } else if (o instanceof Connection) { + Connection c = (Connection) o; + try { + if (!c.isClosed()) { + c.close(); + } + } catch (SQLException e) { + throw new JdbcClientException("Can not close Connection ", e); + } + } + } + + public void close(ResultSet rs, Statement stmt, Connection conn) { + close(rs); + close(stmt); + close(conn); + } + + public void close(ResultSet rs, Connection conn) { + close(rs); + close(conn); + } + + /** + * get all database name through JDBC + * @return list of database names + */ + public List<String> getDatabaseNameList() { + Connection conn = getConnection(); + Statement stmt = null; + ResultSet rs = null; + List<String> databaseNames = Lists.newArrayList(); + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery("SHOW DATABASES"); + while (rs.next()) { + databaseNames.add(rs.getString(1)); + } + } catch (SQLException e) { + throw new JdbcClientException("failed to get database name list from jdbc", e); + } finally { + close(rs, stmt, conn); + } + return databaseNames; + } + + /** + * get all tables of one database + */ + public List<String> getTablesNameList(String dbName) { + Connection conn = getConnection(); + ResultSet rs = null; + List<String> tablesName = Lists.newArrayList(); + String[] types = { "TABLE", "VIEW" }; + try { + DatabaseMetaData databaseMetaData = conn.getMetaData(); + switch (dbType) { + case MYSQL: + rs = databaseMetaData.getTables(dbName, null, null, types); + break; + default: + throw new JdbcClientException("Unknown database type"); + } + while (rs.next()) { + tablesName.add(rs.getString("TABLE_NAME")); + } + } catch (SQLException e) { + throw new JdbcClientException("failed to get all tables for db %s", e, dbName); + } finally { + close(rs, conn); + } + return tablesName; + } + + public boolean isTableExist(String dbName, String tableName) { + Connection conn = getConnection(); + ResultSet rs = null; + String[] types = { "TABLE", "VIEW" }; + try { + DatabaseMetaData databaseMetaData = conn.getMetaData(); + switch (dbType) { + case MYSQL: + rs = databaseMetaData.getTables(dbName, null, tableName, types); + break; + default: + throw new JdbcClientException("Unknown database type"); + } + if (rs.next()) { + return true; + } else { + return false; + } + } catch (SQLException e) { + throw new JdbcClientException("failed to judge if table exist for table %s in db %s", e, tableName, dbName); + } finally { + close(rs, conn); + } + } + + @Data + private class JdbcFieldSchema { + private String columnName; + // The SQL type of the corresponding java.sql.types (Type ID) + private int dataType; + // The SQL type of the corresponding java.sql.types (Type Name) + private String dataTypeName; + // For CHAR/DATA, columnSize means the maximum number of chars. + // For NUMERIC/DECIMAL, columnSize means precision. + private int columnSize; + private int decimalDigits; + // Base number (usually 10 or 2) + private int numPrecRadix; + // column description + private String remarks; + // This length is the maximum number of bytes for CHAR type + // for utf8 encoding, if columnSize=10, then charOctetLength=30 + // because for utf8 encoding, a Chinese character takes up 3 bytes + private int charOctetLength; + /** + * Whether it is allowed to be NULL + * 0 (columnNoNulls) + * 1 (columnNullable) + * 2 (columnNullableUnknown) + */ + private int nullAble; + } + + /** + * get all columns of one table + */ + public List<JdbcFieldSchema> getJdbcColumnsInfo(String dbName, String tableName) { + Connection conn = getConnection(); + ResultSet rs = null; + List<JdbcFieldSchema> tableSchema = Lists.newArrayList(); + try { + DatabaseMetaData databaseMetaData = conn.getMetaData(); + // getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + // catalog - the catalog of this table, `null` means all catalogs + // schema - The schema of the table; corresponding to tablespace in Oracle + // `null` means get all schema; + // Can contain single-character wildcards ("_"), or multi-character wildcards ("%") + // tableNamePattern - table name + // Can contain single-character wildcards ("_"), or multi-character wildcards ("%") + // columnNamePattern - column name, `null` means get all columns + // Can contain single-character wildcards ("_"), or multi-character wildcards ("%") + rs = databaseMetaData.getColumns(dbName, null, tableName, null); + while (rs.next()) { + JdbcFieldSchema field = new JdbcFieldSchema(); + field.setColumnName(rs.getString("COLUMN_NAME")); + field.setDataType(rs.getInt("DATA_TYPE")); + field.setDataTypeName(rs.getString("TYPE_NAME")); + field.setColumnSize(rs.getInt("COLUMN_SIZE")); + field.setDecimalDigits(rs.getInt("DECIMAL_DIGITS")); + field.setNumPrecRadix(rs.getInt("NUM_PREC_RADIX")); + field.setNullAble(rs.getInt("NULLABLE")); + field.setRemarks(rs.getString("REMARKS")); + field.setCharOctetLength(rs.getInt("CHAR_OCTET_LENGTH")); + tableSchema.add(field); + } + } catch (SQLException e) { + throw new JdbcClientException("failed to get table name list from jdbc for table %s", e, tableName); + } finally { + close(rs, conn); + } + return tableSchema; + } + + public Type jdbcTypeToDoris(JdbcFieldSchema fieldSchema) { + switch (dbType) { + case MYSQL: + return mysqlTypeToDoris(fieldSchema); + default: + throw new JdbcClientException("Unknown database type"); + } + } + + public Type mysqlTypeToDoris(JdbcFieldSchema fieldSchema) { + // For mysql type: "INT UNSIGNED": + // fieldSchema.getDataTypeName().split(" ")[0] == "INT" + // fieldSchema.getDataTypeName().split(" ")[1] == "UNSIGNED" + String[] typeFields = fieldSchema.getDataTypeName().split(" "); + String mysqlType = typeFields[0]; + // For unsigned int, should extend the type. + if (typeFields.length > 1 && "UNSIGNED".equals(typeFields[1])) { + switch (mysqlType) { + case "TINYINT": + return Type.SMALLINT; + case "SMALLINT": + return Type.INT; + case "MEDIUMINT": + return Type.INT; + case "INT": + return Type.BIGINT; + case "BIGINT": + return ScalarType.createStringType(); + default: + throw new JdbcClientException("Unknown UNSIGNED type of mysql, type: [" + mysqlType + "]"); + } + } + switch (mysqlType) { + case "BOOLEAN": + return Type.BOOLEAN; + case "TINYINT": + return Type.TINYINT; + case "SMALLINT": + case "YEAR": + return Type.SMALLINT; + case "MEDIUMINT": + case "INT": + return Type.INT; + case "BIGINT": + return Type.BIGINT; + case "DATE": + return ScalarType.getDefaultDateType(Type.DATE); + case "TIMESTAMP": + return ScalarType.getDefaultDateType(Type.DATETIME); + case "DATETIME": + return ScalarType.getDefaultDateType(Type.DATETIME); + case "FLOAT": + return Type.FLOAT; + case "DOUBLE": + return Type.DOUBLE; + case "DECIMAL": + int precision = fieldSchema.getColumnSize(); + int scale = fieldSchema.getDecimalDigits(); + return ScalarType.createDecimalType(precision, scale); + case "CHAR": + ScalarType charType = ScalarType.createType(PrimitiveType.CHAR); + charType.setLength(fieldSchema.columnSize); + return charType; + case "TIME": + case "VARCHAR": + case "TINYTEXT": + case "TEXT": + case "MEDIUMTEXT": + case "LONGTEXT": + case "TINYBLOB": + case "BLOB": + case "MEDIUMBLOB": + case "LONGBLOB": + case "TINYSTRING": + case "STRING": + case "MEDIUMSTRING": + case "LONGSTRING": + case "JSON": + case "SET": + case "BIT": + case "BINARY": + case "VARBINARY": + return ScalarType.createStringType(); + default: + throw new JdbcClientException("Can not convert mysql data type to doris data type for type [" + + mysqlType + "]"); + } + } + + public List<Column> getColumnsFromJdbc(String dbName, String tableName) { + List<JdbcFieldSchema> jdbcTableSchema = getJdbcColumnsInfo(dbName, tableName); + List<Column> dorisTableSchema = Lists.newArrayListWithCapacity(jdbcTableSchema.size()); + for (JdbcFieldSchema field : jdbcTableSchema) { + dorisTableSchema.add(new Column(field.getColumnName(), + jdbcTypeToDoris(field), true, null, + true, null, field.getRemarks(), + true, null, -1)); + } + return dorisTableSchema; + } + + private String computeObjectChecksum() { + if (FeConstants.runningUnitTest) { + // skip checking checksum when running ut + return ""; + } + + InputStream inputStream = null; + try { + inputStream = Util.getInputStreamFromUrl(driverUrl, null, HTTP_TIMEOUT_MS, HTTP_TIMEOUT_MS); + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] buf = new byte[4096]; + int bytesRead = 0; + do { + bytesRead = inputStream.read(buf); + if (bytesRead < 0) { + break; + } + digest.update(buf, 0, bytesRead); + } while (true); + return Hex.encodeHexString(digest.digest()); + } catch (IOException e) { + throw new JdbcClientException("compute driver checksum from url: " + driverUrl + " meet an IOException."); + } catch (NoSuchAlgorithmException e) { + throw new JdbcClientException( + "compute driver checksum from url: " + driverUrl + " could not find algorithm."); + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/external/jdbc/JdbcClientException.java b/fe/fe-core/src/main/java/org/apache/doris/external/jdbc/JdbcClientException.java new file mode 100644 index 0000000000..1298a0df8f --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/external/jdbc/JdbcClientException.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.doris.external.jdbc; + +public class JdbcClientException extends RuntimeException { + public JdbcClientException(String format, Throwable cause, Object... msg) { + super(String.format(format, msg), cause); + } + + public JdbcClientException(String format, Object... msg) { + super(String.format(format, msg)); + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/load/ExportJob.java b/fe/fe-core/src/main/java/org/apache/doris/load/ExportJob.java index 31c3ac918c..00057dfd67 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/load/ExportJob.java +++ b/fe/fe-core/src/main/java/org/apache/doris/load/ExportJob.java @@ -38,7 +38,6 @@ import org.apache.doris.analysis.TupleDescriptor; import org.apache.doris.catalog.Column; import org.apache.doris.catalog.Database; import org.apache.doris.catalog.Env; -import org.apache.doris.catalog.JdbcTable; import org.apache.doris.catalog.MysqlTable; import org.apache.doris.catalog.OdbcTable; import org.apache.doris.catalog.PrimitiveType; @@ -420,7 +419,7 @@ public class ExportJob implements Writable { scanNode = new MysqlScanNode(new PlanNodeId(0), exportTupleDesc, (MysqlTable) this.exportTable); break; case JDBC: - scanNode = new JdbcScanNode(new PlanNodeId(0), exportTupleDesc, (JdbcTable) this.exportTable); + scanNode = new JdbcScanNode(new PlanNodeId(0), exportTupleDesc, false); break; default: break; diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/JdbcScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/JdbcScanNode.java index 551135e730..b06956fda3 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/JdbcScanNode.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/JdbcScanNode.java @@ -26,6 +26,7 @@ import org.apache.doris.analysis.TupleDescriptor; import org.apache.doris.catalog.Column; import org.apache.doris.catalog.JdbcTable; import org.apache.doris.catalog.OdbcTable; +import org.apache.doris.catalog.external.JdbcExternalTable; import org.apache.doris.common.UserException; import org.apache.doris.statistics.StatisticalType; import org.apache.doris.statistics.StatsRecursiveDerive; @@ -59,6 +60,19 @@ public class JdbcScanNode extends ScanNode { tableName = OdbcTable.databaseProperName(jdbcType, tbl.getJdbcTable()); } + public JdbcScanNode(PlanNodeId id, TupleDescriptor desc, boolean isJdbcExternalTable) { + super(id, desc, "JdbcScanNode", StatisticalType.JDBC_SCAN_NODE); + JdbcTable tbl = null; + if (isJdbcExternalTable) { + JdbcExternalTable jdbcExternalTable = (JdbcExternalTable) (desc.getTable()); + tbl = jdbcExternalTable.getJdbcTable(); + } else { + tbl = (JdbcTable) (desc.getTable()); + } + jdbcType = tbl.getJdbcTableType(); + tableName = OdbcTable.databaseProperName(jdbcType, tbl.getJdbcTable()); + } + @Override public void init(Analyzer analyzer) throws UserException { super.init(analyzer); diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java b/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java index e9b50549c8..5e6b779514 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/SingleNodePlanner.java @@ -55,7 +55,6 @@ import org.apache.doris.catalog.AggregateFunction; import org.apache.doris.catalog.AggregateType; import org.apache.doris.catalog.Column; import org.apache.doris.catalog.FunctionSet; -import org.apache.doris.catalog.JdbcTable; import org.apache.doris.catalog.KeysType; import org.apache.doris.catalog.MysqlTable; import org.apache.doris.catalog.OdbcTable; @@ -1942,7 +1941,7 @@ public class SingleNodePlanner { null, -1); break; case JDBC: - scanNode = new JdbcScanNode(ctx.getNextNodeId(), tblRef.getDesc(), (JdbcTable) tblRef.getTable()); + scanNode = new JdbcScanNode(ctx.getNextNodeId(), tblRef.getDesc(), false); break; case TABLE_VALUED_FUNCTION: scanNode = ((TableValuedFunctionRef) tblRef).getScanNode(ctx.getNextNodeId()); @@ -1953,6 +1952,9 @@ public class SingleNodePlanner { case ES_EXTERNAL_TABLE: scanNode = new EsScanNode(ctx.getNextNodeId(), tblRef.getDesc(), "EsScanNode", true); break; + case JDBC_EXTERNAL_TABLE: + scanNode = new JdbcScanNode(ctx.getNextNodeId(), tblRef.getDesc(), true); + break; default: break; } diff --git a/fe/java-udf/src/main/java/org/apache/doris/udf/JdbcExecutor.java b/fe/java-udf/src/main/java/org/apache/doris/udf/JdbcExecutor.java index ae8b910f91..0a267bd9a6 100644 --- a/fe/java-udf/src/main/java/org/apache/doris/udf/JdbcExecutor.java +++ b/fe/java-udf/src/main/java/org/apache/doris/udf/JdbcExecutor.java @@ -204,7 +204,6 @@ public class JdbcExecutor { try { ClassLoader parent = getClass().getClassLoader(); ClassLoader classLoader = UdfUtils.getClassLoader(driverUrl, parent); - Class.forName(driverClass, true, classLoader); Thread.currentThread().setContextClassLoader(classLoader); HikariConfig config = new HikariConfig(); config.setDriverClassName(driverClass); @@ -226,8 +225,6 @@ public class JdbcExecutor { throw new UdfRuntimeException("Can not find driver file: " + driverUrl, e); } catch (MalformedURLException e) { throw new UdfRuntimeException("MalformedURLException to load class about " + driverUrl, e); - } catch (ClassNotFoundException e) { - throw new UdfRuntimeException("Loading JDBC class error ClassNotFoundException about " + driverClass, e); } catch (SQLException e) { throw new UdfRuntimeException("Initialize datasource failed: ", e); } diff --git a/regression-test/data/jdbc_catalog_p0/test_mysql_jdbc_catalog.out b/regression-test/data/jdbc_catalog_p0/test_mysql_jdbc_catalog.out new file mode 100644 index 0000000000..9c4a43e959 --- /dev/null +++ b/regression-test/data/jdbc_catalog_p0/test_mysql_jdbc_catalog.out @@ -0,0 +1,154 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !db_amount -- +doris_test +information_schema +init_db +mysql +performance_schema +sys + +-- !ex_tb0 -- +111 abc +112 abd +113 abe +114 abf +115 abg + +-- !in_tb -- +111 abc +112 abd +113 abe +114 abf +115 abg + +-- !ex_tb1 -- +{"k1":"v1", "k2":"v2"} + +-- !ex_tb2 -- +123 10 +123 15 +123 20 + +-- !ex_tb3 -- +mus plat_code 1001169339 1590381433914 1590420872639 11 1006061 beijing +mus plat_code 1001169339 1590402594411 1590420872639 11 1006061 beijing +mus plat_code 1001169339 1590406790026 1590420872639 11 1006061 beijing +mus plat_code 1001169339 1590420482288 1590420872639 11 1006061 beijing +mus plat_code 1001169339 1590420872639 1590420872639 11 1006061 beijing + +-- !ex_tb4 -- +1 111 2021-09-01T07:01:01 2021-09-01T08:01:01 1 +2 112 2021-09-02T07:01:01 2021-09-02T08:01:01 1 +3 113 0000-01-01T00:00 2021-12-01T08:01:01 2 +5 115 2021-09-01T07:02:01 2021-09-01T08:01:04 4 +6 116 2021-10-01T07:03:01 2022-09-01T08:02:05 5 + +-- !ex_tb5 -- +1 test_apply_id 123321 zhangsan zhangsan ready ok 2 2022-01-01T02:03:04 + +-- !ex_tb6 -- +639215401565159424 1143681147589283841 test +639237839376089088 1143681147589283841 test123 + +-- !ex_tb7 -- +2 sim 1.000 +2 sim 1.001 +2 sim 1.002 + +-- !ex_tb8 -- +2022-07-15 2222 1 \N +2022-07-15 ddddd 2 0.5 + +-- !ex_tb9 -- +\N +2022-01-01 + +-- !ex_tb10 -- +a 1 2 +b 1 2 +c 1 2 +d 3 2 + +-- !ex_tb11 -- +a 1 +b 1 +c 1 + +-- !ex_tb12 -- +a 1 +b 1 +c 1 + +-- !ex_tb13 -- + +-- !ex_tb14 -- +123 2022-11-02 2022-11-02 8011 oppo +abc 2022-11-02 2022-11-02 8011 agdtb +bca 2022-11-02 2022-11-02 8012 vivo + +-- !ex_tb15 -- +2022-11-04 2022-10-31 2022-11-04 62 5.4103451446E9 7.211386993606482E10 21 10 16 - - 2022-11-04T17:40:19 + +-- !ex_tb16 -- +1 a 0 4 3 6 8 +1 b 0 4 4 8 8 +1 c 0 9 9 5 4 +1 d 0 7 6 1 7 +1 e 0 7 5 6 3 +2 a 0 3 4 1 6 +2 b 0 1 5 4 5 +2 c 0 5 7 9 1 +2 d 0 4 4 8 4 +2 e 0 6 4 7 8 +3 a 0 7 9 4 8 +3 b 0 4 9 8 1 +3 d 0 2 7 1 5 +3 e 0 2 4 3 4 +4 a 0 5 7 4 1 +4 b 0 3 4 2 7 +4 c 0 3 9 3 7 +4 d 0 1 5 6 4 +5 a 0 1 2 2 1 +5 b 0 6 6 2 9 +5 c 0 8 5 7 6 +5 d 0 6 2 7 7 +5 e 0 5 7 9 2 +6 a 0 1 1 8 8 +6 b 0 3 9 1 6 +6 c 0 3 1 3 8 +6 d 0 1 2 4 7 +6 e 0 1 9 7 6 +7 a 0 1 1 3 8 +7 b 0 3 2 8 1 +7 c 0 3 7 7 1 +7 d 0 6 1 5 6 +7 e 0 6 1 3 7 +8 a 0 3 2 8 2 +8 b 0 4 9 4 9 +8 c 0 1 7 1 5 +8 e 0 4 4 5 4 +9 a 0 8 3 9 1 +9 b 0 2 1 4 2 +9 c 0 8 3 9 8 +9 d 0 6 6 5 3 +9 e 0 9 1 9 7 + +-- !ex_tb17 -- +1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 +2 8 9 8 2900.42 1 6 97486621.73 59634489.39 c 3 2 0 a e 7 4 +3 5 7 3 6276.86 8 9 32758730.38 10260499.72 c 8 1 0 d c 9 2 +4 3 7 5 2449.00 6 3 91359059.28 64743145.92 e 7 8 0 b d 8 4 +5 6 4 5 9137.82 2 7 26526675.70 90098303.36 a 6 7 0 d e 4 1 +6 3 6 8 7601.25 4 9 49117098.47 46499188.80 c 3 3 0 c d 4 8 +7 3 2 8 5297.81 9 3 23753694.20 96930000.64 c 7 2 0 b e 1 5 +8 3 6 7 3683.85 5 7 26056250.91 1127755.43 b 7 6 0 d b 4 7 +9 3 9 1 4785.38 1 5 95199488.12 94869703.42 a 4 4 0 c d 2 4 + +-- !ex_tb18 -- +-128 255 -32768 65535 -8388608 16777215 -9223372036854775808 -2147483648 2147483647 4294967295 33.14 422113.141 2342.23 aa asdawdasdaasdasd aaa bbbbbbbb xaqwdqwdqwdqd asdas +1 1 1 1 1 1 1 1 1 1 3.14 13.141 2342.23 aa asdawdasdaasdasd aaa bbbbbbbb xaqwdqwdqwdqdwqwdqwdqd asdadwqdqwddqwdsadqwdas +127 255 32767 65535 8388607 16777215 9223372036854775807 -2147483648 2147483647 4294967295 33.14 422113.141 2342.23 aa asdawdasdaasdasd aaa bbbbbbbb xaqwdqwdqwdqd asdadwqdqwdsadqwdas + +-- !ex_tb19 -- +2022-11-27 07:09:51 2022 2022-11-27T07:09:51 2022-11-27T07:09:51 + diff --git a/regression-test/suites/jdbc_catalog_p0/test_mysql_jdbc_catalog.groovy b/regression-test/suites/jdbc_catalog_p0/test_mysql_jdbc_catalog.groovy new file mode 100644 index 0000000000..514815755c --- /dev/null +++ b/regression-test/suites/jdbc_catalog_p0/test_mysql_jdbc_catalog.groovy @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +suite("test_mysql_jdbc_catalog", "p0") { + String enabled = context.config.otherConfigs.get("enableJdbcTest") + if (enabled != null && enabled.equalsIgnoreCase("true")) { + sql """admin set frontend config ("enable_multi_catalog" = "true")""" + + String catalog_name = "mysql_jdbc_catalog"; + String internal_db_name = "regression_test_jdbc_catalog_p0"; + String ex_db_name = "doris_test"; + String mysql_port = context.config.otherConfigs.get("mysql_57_port"); + String inDorisTable = "doris_in_tb"; + String ex_tb0 = "ex_tb0"; + String ex_tb1 = "ex_tb1"; + String ex_tb2 = "ex_tb2"; + String ex_tb3 = "ex_tb3"; + String ex_tb4 = "ex_tb4"; + String ex_tb5 = "ex_tb5"; + String ex_tb6 = "ex_tb6"; + String ex_tb7 = "ex_tb7"; + String ex_tb8 = "ex_tb8"; + String ex_tb9 = "ex_tb9"; + String ex_tb10 = "ex_tb10"; + String ex_tb11 = "ex_tb11"; + String ex_tb12 = "ex_tb12"; + String ex_tb13 = "ex_tb13"; + String ex_tb14 = "ex_tb14"; + String ex_tb15 = "ex_tb15"; + String ex_tb16 = "ex_tb16"; + String ex_tb17 = "ex_tb17"; + String ex_tb18 = "ex_tb18"; + String ex_tb19 = "ex_tb19"; + + + sql """drop catalog if exists ${catalog_name} """ + + // if use 'com.mysql.cj.jdbc.Driver' here, it will report: ClassNotFound + sql """ CREATE CATALOG ${catalog_name} PROPERTIES ( + "type"="jdbc", + "jdbc.user"="root", + "jdbc.password"="123456", + "jdbc.jdbc_url" = "jdbc:mysql://127.0.0.1:${mysql_port}/doris_test?useSSL=false", + "jdbc.driver_url" = "https://doris-community-test-1308700295.cos.ap-hongkong.myqcloud.com/jdbc_driver/mysql-connector-java-8.0.25.jar", + "jdbc.driver_class" = "com.mysql.cj.jdbc.Driver"); + """ + + sql """ drop table if exists ${inDorisTable} """ + sql """ + CREATE TABLE ${inDorisTable} ( + `id` INT NULL COMMENT "主键id", + `name` string NULL COMMENT "名字" + ) DISTRIBUTED BY HASH(id) BUCKETS 10 + PROPERTIES("replication_num" = "1"); + """ + + sql """switch ${catalog_name}""" + qt_db_amount """ show databases; """ + + sql """ use ${ex_db_name}""" + + order_qt_ex_tb0 """ select id, name from ${ex_tb0} order by id; """ + sql """ insert into internal.${internal_db_name}.${inDorisTable} select id, name from ${ex_tb0}; """ + order_qt_in_tb """ select id, name from internal.${internal_db_name}.${inDorisTable} order by id; """ + + order_qt_ex_tb1 """ select * from ${ex_tb1} order by id; """ + order_qt_ex_tb2 """ select * from ${ex_tb2} order by id; """ + order_qt_ex_tb3 """ select * from ${ex_tb3} order by game_code; """ + order_qt_ex_tb4 """ select * from ${ex_tb4} order by products_id; """ + order_qt_ex_tb5 """ select * from ${ex_tb5} order by id; """ + order_qt_ex_tb6 """ select * from ${ex_tb6} order by id; """ + order_qt_ex_tb7 """ select * from ${ex_tb7} order by id; """ + order_qt_ex_tb8 """ select * from ${ex_tb8} order by uid; """ + order_qt_ex_tb9 """ select * from ${ex_tb9} order by c_date; """ + order_qt_ex_tb10 """ select * from ${ex_tb10} order by aa; """ + order_qt_ex_tb11 """ select * from ${ex_tb11} order by aa; """ + order_qt_ex_tb12 """ select * from ${ex_tb12} order by cc; """ + order_qt_ex_tb13 """ select * from ${ex_tb13} order by name; """ + order_qt_ex_tb14 """ select * from ${ex_tb14} order by tid; """ + order_qt_ex_tb15 """ select * from ${ex_tb15} order by col1; """ + order_qt_ex_tb16 """ select * from ${ex_tb16} order by id; """ + order_qt_ex_tb17 """ select * from ${ex_tb17} order by id; """ + order_qt_ex_tb18 """ select * from ${ex_tb18} order by num_tinyint; """ + order_qt_ex_tb19 """ select * from ${ex_tb19} order by date_value; """ + + + sql """admin set frontend config ("enable_multi_catalog" = "false")""" + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org