Merge pull request #10077 from zhang2014/fix/ISSUES-10056

ISSUES-10056 support identifier argument for MySQL Database engine
This commit is contained in:
alexey-milovidov 2020-04-08 22:59:09 +03:00 committed by GitHub
commit db4270b60c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 39 additions and 18 deletions

View File

@ -3,7 +3,7 @@ toc_priority: 30
toc_title: MySQL toc_title: MySQL
--- ---
# Mysql {#mysql} # MySQL {#mysql}
Allows to connect to databases on a remote MySQL server and perform `INSERT` and `SELECT` queries to exchange data between ClickHouse and MySQL. Allows to connect to databases on a remote MySQL server and perform `INSERT` and `SELECT` queries to exchange data between ClickHouse and MySQL.
@ -19,7 +19,7 @@ You cannot perform the following queries:
``` sql ``` sql
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster] CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', 'database', 'user', 'password') ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')
``` ```
**Engine Parameters** **Engine Parameters**

View File

@ -6,8 +6,6 @@
Не поддерживаемые виды запросов: Не поддерживаемые виды запросов:
- `ATTACH`/`DETACH`
- `DROP`
- `RENAME` - `RENAME`
- `CREATE TABLE` - `CREATE TABLE`
- `ALTER` - `ALTER`
@ -16,7 +14,7 @@
``` sql ``` sql
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster] CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', 'database', 'user', 'password') ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')
``` ```
**Параметры движка** **Параметры движка**

View File

@ -7,8 +7,6 @@ MySQL引擎用于将远程的MySQL服务器中的表映射到ClickHouse中
但您无法对其执行以下操作: 但您无法对其执行以下操作:
- `ATTACH`/`DETACH`
- `DROP`
- `RENAME` - `RENAME`
- `CREATE TABLE` - `CREATE TABLE`
- `ALTER` - `ALTER`
@ -17,7 +15,7 @@ MySQL引擎用于将远程的MySQL服务器中的表映射到ClickHouse中
``` sql ``` sql
CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster] CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]
ENGINE = MySQL('host:port', 'database', 'user', 'password') ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')
``` ```
**MySQL数据库引擎参数** **MySQL数据库引擎参数**

View File

@ -4,6 +4,7 @@
#include <Databases/DatabaseMemory.h> #include <Databases/DatabaseMemory.h>
#include <Databases/DatabaseOrdinary.h> #include <Databases/DatabaseOrdinary.h>
#include <Parsers/ASTLiteral.h> #include <Parsers/ASTLiteral.h>
#include <Parsers/ASTIdentifier.h>
#include <Parsers/formatAST.h> #include <Parsers/formatAST.h>
#include <Parsers/ASTCreateQuery.h> #include <Parsers/ASTCreateQuery.h>
#include <Parsers/ASTFunction.h> #include <Parsers/ASTFunction.h>
@ -15,6 +16,7 @@
#if USE_MYSQL #if USE_MYSQL
#include <Databases/DatabaseMySQL.h> #include <Databases/DatabaseMySQL.h>
#include <Interpreters/evaluateConstantExpression.h>
#endif #endif
@ -49,6 +51,15 @@ DatabasePtr DatabaseFactory::get(
} }
} }
template <typename ValueType>
static inline ValueType safeGetLiteralValue(const ASTPtr &ast, const String &engine_name)
{
if (!ast || !ast->as<ASTLiteral>())
throw Exception("Database engine " + engine_name + " requested literal argument.", ErrorCodes::BAD_ARGUMENTS);
return ast->as<ASTLiteral>()->value.safeGet<ValueType>();
}
DatabasePtr DatabaseFactory::getImpl( DatabasePtr DatabaseFactory::getImpl(
const String & database_name, const String & metadata_path, const ASTStorage * engine_define, Context & context) const String & database_name, const String & metadata_path, const ASTStorage * engine_define, Context & context)
{ {
@ -79,11 +90,14 @@ DatabasePtr DatabaseFactory::getImpl(
throw Exception("MySQL Database require mysql_hostname, mysql_database_name, mysql_username, mysql_password arguments.", throw Exception("MySQL Database require mysql_hostname, mysql_database_name, mysql_username, mysql_password arguments.",
ErrorCodes::BAD_ARGUMENTS); ErrorCodes::BAD_ARGUMENTS);
const auto & arguments = engine->arguments->children;
const auto & host_name_and_port = arguments[0]->as<ASTLiteral>()->value.safeGet<String>(); ASTs & arguments = engine->arguments->children;
const auto & database_name_in_mysql = arguments[1]->as<ASTLiteral>()->value.safeGet<String>(); arguments[1] = evaluateConstantExpressionOrIdentifierAsLiteral(arguments[1], context);
const auto & mysql_user_name = arguments[2]->as<ASTLiteral>()->value.safeGet<String>();
const auto & mysql_user_password = arguments[3]->as<ASTLiteral>()->value.safeGet<String>(); const auto & host_name_and_port = safeGetLiteralValue<String>(arguments[0], "MySQL");
const auto & database_name_in_mysql = safeGetLiteralValue<String>(arguments[1], "MySQL");
const auto & mysql_user_name = safeGetLiteralValue<String>(arguments[2], "MySQL");
const auto & mysql_user_password = safeGetLiteralValue<String>(arguments[3], "MySQL");
try try
{ {
@ -114,7 +128,7 @@ DatabasePtr DatabaseFactory::getImpl(
const auto & arguments = engine->arguments->children; const auto & arguments = engine->arguments->children;
const auto cache_expiration_time_seconds = arguments[0]->as<ASTLiteral>()->value.safeGet<UInt64>(); const auto cache_expiration_time_seconds = safeGetLiteralValue<UInt64>(arguments[0], "Lazy");
return std::make_shared<DatabaseLazy>(database_name, metadata_path, cache_expiration_time_seconds, context); return std::make_shared<DatabaseLazy>(database_name, metadata_path, cache_expiration_time_seconds, context);
} }

View File

@ -5,6 +5,7 @@ import pymysql.cursors
import pytest import pytest
from helpers.cluster import ClickHouseCluster from helpers.cluster import ClickHouseCluster
from helpers.client import QueryRuntimeException
cluster = ClickHouseCluster(__file__) cluster = ClickHouseCluster(__file__)
clickhouse_node = cluster.add_instance('node1', main_configs=['configs/remote_servers.xml'], with_mysql=True) clickhouse_node = cluster.add_instance('node1', main_configs=['configs/remote_servers.xml'], with_mysql=True)
@ -92,7 +93,7 @@ def test_clickhouse_dml_for_mysql_database(started_cluster):
with contextlib.closing(MySQLNodeInstance('root', 'clickhouse', '127.0.0.1', port=3308)) as mysql_node: with contextlib.closing(MySQLNodeInstance('root', 'clickhouse', '127.0.0.1', port=3308)) as mysql_node:
mysql_node.query("CREATE DATABASE test_database DEFAULT CHARACTER SET 'utf8'") mysql_node.query("CREATE DATABASE test_database DEFAULT CHARACTER SET 'utf8'")
mysql_node.query('CREATE TABLE `test_database`.`test_table` ( `i``d` int(11) NOT NULL, PRIMARY KEY (`i``d`)) ENGINE=InnoDB;') mysql_node.query('CREATE TABLE `test_database`.`test_table` ( `i``d` int(11) NOT NULL, PRIMARY KEY (`i``d`)) ENGINE=InnoDB;')
clickhouse_node.query("CREATE DATABASE test_database ENGINE = MySQL('mysql1:3306', 'test_database', 'root', 'clickhouse')") clickhouse_node.query("CREATE DATABASE test_database ENGINE = MySQL('mysql1:3306', test_database, 'root', 'clickhouse')")
assert clickhouse_node.query("SELECT count() FROM `test_database`.`test_table`").rstrip() == '0' assert clickhouse_node.query("SELECT count() FROM `test_database`.`test_table`").rstrip() == '0'
clickhouse_node.query("INSERT INTO `test_database`.`test_table`(`i\`d`) select number from numbers(10000)") clickhouse_node.query("INSERT INTO `test_database`.`test_table`(`i\`d`) select number from numbers(10000)")
@ -116,7 +117,17 @@ def test_clickhouse_join_for_mysql_database(started_cluster):
clickhouse_node.query("CREATE TABLE default.t1_remote_mysql AS mysql('mysql1:3306','test','t1_mysql_local','root','clickhouse')") clickhouse_node.query("CREATE TABLE default.t1_remote_mysql AS mysql('mysql1:3306','test','t1_mysql_local','root','clickhouse')")
clickhouse_node.query("CREATE TABLE default.t2_remote_mysql AS mysql('mysql1:3306','test','t2_mysql_local','root','clickhouse')") clickhouse_node.query("CREATE TABLE default.t2_remote_mysql AS mysql('mysql1:3306','test','t2_mysql_local','root','clickhouse')")
assert clickhouse_node.query("SELECT s.pays " assert clickhouse_node.query("SELECT s.pays "
"FROM default.t1_remote_mysql AS s " "FROM default.t1_remote_mysql AS s "
"LEFT JOIN default.t1_remote_mysql AS s_ref " "LEFT JOIN default.t1_remote_mysql AS s_ref "
"ON (s_ref.opco = s.opco AND s_ref.service = s.service)") == '' "ON (s_ref.opco = s.opco AND s_ref.service = s.service)") == ''
mysql_node.query("DROP DATABASE test") mysql_node.query("DROP DATABASE test")
def test_bad_arguments_for_mysql_database_engine(started_cluster):
with contextlib.closing(MySQLNodeInstance('root', 'clickhouse', '127.0.0.1', port=3308)) as mysql_node:
with pytest.raises(QueryRuntimeException) as exception:
mysql_node.query("CREATE DATABASE IF NOT EXISTS test_bad_arguments DEFAULT CHARACTER SET 'utf8'")
clickhouse_node.query("CREATE DATABASE test_database ENGINE = MySQL('mysql1:3306', test_bad_arguments, root, 'clickhouse')")
assert 'Database engine MySQL requested literal argument.' in str(exception.value)
mysql_node.query("DROP DATABASE test_bad_arguments")