mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-22 07:31:57 +00:00
CLICKHOUSE-3878: Move last interaction with ODBC from main code to bridge, split Handlers file on three different, slightly remove copy-paste code
This commit is contained in:
parent
b38cc88d9d
commit
f6c9587d0a
@ -1,12 +1,30 @@
|
|||||||
add_library (clickhouse-odbc-bridge-lib
|
add_library (clickhouse-odbc-bridge-lib
|
||||||
Handlers.cpp
|
PingHandler.cpp
|
||||||
|
MainHandler.cpp
|
||||||
|
ColumnInfoHandler.cpp
|
||||||
HandlerFactory.cpp
|
HandlerFactory.cpp
|
||||||
ODBCBridge.cpp
|
ODBCBridge.cpp
|
||||||
|
validateODBCConnectionString.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries (clickhouse-odbc-bridge-lib clickhouse_common_io daemon dbms)
|
target_link_libraries (clickhouse-odbc-bridge-lib clickhouse_common_io daemon dbms)
|
||||||
target_include_directories (clickhouse-odbc-bridge-lib PUBLIC ${ClickHouse_SOURCE_DIR}/libs/libdaemon/include)
|
target_include_directories (clickhouse-odbc-bridge-lib PUBLIC ${ClickHouse_SOURCE_DIR}/libs/libdaemon/include)
|
||||||
|
|
||||||
|
if (USE_POCO_SQLODBC)
|
||||||
|
target_link_libraries (clickhouse-odbc-bridge-lib ${Poco_SQLODBC_LIBRARY})
|
||||||
|
target_include_directories (clickhouse-odbc-bridge-lib SYSTEM PRIVATE ${ODBC_INCLUDE_DIRECTORIES} ${Poco_SQLODBC_INCLUDE_DIRS})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (USE_POCO_DATAODBC)
|
||||||
|
target_link_libraries (clickhouse-odbc-bridge-lib ${Poco_DataODBC_LIBRARY})
|
||||||
|
target_include_directories (clickhouse-odbc-bridge-lib SYSTEM PRIVATE ${ODBC_INCLUDE_DIRECTORIES} ${Poco_DataODBC_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
if (ENABLE_TESTS)
|
||||||
|
add_subdirectory (tests)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (CLICKHOUSE_SPLIT_BINARY)
|
if (CLICKHOUSE_SPLIT_BINARY)
|
||||||
add_executable (clickhouse-odbc-bridge odbc-bridge.cpp)
|
add_executable (clickhouse-odbc-bridge odbc-bridge.cpp)
|
||||||
target_link_libraries (clickhouse-odbc-bridge clickhouse-odbc-bridge-lib)
|
target_link_libraries (clickhouse-odbc-bridge clickhouse-odbc-bridge-lib)
|
||||||
|
123
dbms/programs/odbc-bridge/ColumnInfoHandler.cpp
Normal file
123
dbms/programs/odbc-bridge/ColumnInfoHandler.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "ColumnInfoHandler.h"
|
||||||
|
#if USE_POCO_SQLODBC || USE_POCO_DATAODBC
|
||||||
|
#include <Poco/Data/ODBC/ODBCException.h>
|
||||||
|
#include <Poco/Data/ODBC/SessionImpl.h>
|
||||||
|
#include <Poco/Data/ODBC/Utility.h>
|
||||||
|
#include <Poco/Net/HTTPServerRequest.h>
|
||||||
|
#include <Poco/Net/HTTPServerResponse.h>
|
||||||
|
|
||||||
|
#include <DataTypes/DataTypeFactory.h>
|
||||||
|
#include <IO/WriteBufferFromHTTPServerResponse.h>
|
||||||
|
#include <IO/WriteHelpers.h>
|
||||||
|
#include <Common/HTMLForm.h>
|
||||||
|
#include <common/logger_useful.h>
|
||||||
|
#include <ext/scope_guard.h>
|
||||||
|
#include "validateODBCConnectionString.h"
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
DataTypePtr getDataType(SQLSMALLINT type)
|
||||||
|
{
|
||||||
|
const auto & factory = DataTypeFactory::instance();
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case SQL_INTEGER:
|
||||||
|
return factory.get("Int32");
|
||||||
|
case SQL_SMALLINT:
|
||||||
|
return factory.get("Int16");
|
||||||
|
case SQL_FLOAT:
|
||||||
|
return factory.get("Float32");
|
||||||
|
case SQL_REAL:
|
||||||
|
return factory.get("Float32");
|
||||||
|
case SQL_DOUBLE:
|
||||||
|
return factory.get("Float64");
|
||||||
|
case SQL_DATETIME:
|
||||||
|
return factory.get("DateTime");
|
||||||
|
case SQL_TYPE_TIMESTAMP:
|
||||||
|
return factory.get("DateTime");
|
||||||
|
case SQL_TYPE_DATE:
|
||||||
|
return factory.get("Date");
|
||||||
|
default:
|
||||||
|
return factory.get("String");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ODBCColumnsInfoHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response)
|
||||||
|
{
|
||||||
|
Poco::Net::HTMLForm params(request, request.stream());
|
||||||
|
LOG_TRACE(log, "Request URI: " + request.getURI());
|
||||||
|
|
||||||
|
auto process_error = [&response, this](const std::string & message) {
|
||||||
|
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
if (!response.sent())
|
||||||
|
response.send() << message << std::endl;
|
||||||
|
LOG_WARNING(log, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!params.has("table"))
|
||||||
|
{
|
||||||
|
process_error("No 'table' param in request URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!params.has("connection_string"))
|
||||||
|
{
|
||||||
|
process_error("No 'connection_string' in request URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string table_name = params.get("table");
|
||||||
|
std::string connection_string = params.get("connection_string");
|
||||||
|
LOG_TRACE(log, "Will fetch info for table '" << table_name << "'");
|
||||||
|
LOG_TRACE(log, "Got connection str '" << connection_string << "'");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Poco::Data::ODBC::SessionImpl session(validateODBCConnectionString(connection_string), DBMS_DEFAULT_CONNECT_TIMEOUT_SEC);
|
||||||
|
SQLHDBC hdbc = session.dbc().handle();
|
||||||
|
|
||||||
|
SQLHSTMT hstmt = nullptr;
|
||||||
|
|
||||||
|
if (Poco::Data::ODBC::Utility::isError(SQLAllocStmt(hdbc, &hstmt)))
|
||||||
|
throw Poco::Data::ODBC::ODBCException("Could not allocate connection handle.");
|
||||||
|
|
||||||
|
SCOPE_EXIT(SQLFreeStmt(hstmt, SQL_DROP));
|
||||||
|
|
||||||
|
/// TODO Why not do SQLColumns instead?
|
||||||
|
std::string query = "SELECT * FROM " + table_name + " WHERE 1 = 0";
|
||||||
|
if (Poco::Data::ODBC::Utility::isError(Poco::Data::ODBC::SQLPrepare(hstmt, reinterpret_cast<SQLCHAR *>(&query[0]), query.size())))
|
||||||
|
throw Poco::Data::ODBC::DescriptorException(session.dbc());
|
||||||
|
|
||||||
|
if (Poco::Data::ODBC::Utility::isError(SQLExecute(hstmt)))
|
||||||
|
throw Poco::Data::ODBC::StatementException(hstmt);
|
||||||
|
|
||||||
|
SQLSMALLINT cols = 0;
|
||||||
|
if (Poco::Data::ODBC::Utility::isError(SQLNumResultCols(hstmt, &cols)))
|
||||||
|
throw Poco::Data::ODBC::StatementException(hstmt);
|
||||||
|
|
||||||
|
/// TODO cols not checked
|
||||||
|
|
||||||
|
NamesAndTypesList columns;
|
||||||
|
for (SQLSMALLINT ncol = 1; ncol <= cols; ++ncol)
|
||||||
|
{
|
||||||
|
SQLSMALLINT type = 0;
|
||||||
|
/// TODO Why 301?
|
||||||
|
SQLCHAR column_name[301];
|
||||||
|
/// TODO Result is not checked.
|
||||||
|
Poco::Data::ODBC::SQLDescribeCol(hstmt, ncol, column_name, sizeof(column_name), NULL, &type, NULL, NULL, NULL);
|
||||||
|
columns.emplace_back(reinterpret_cast<char *>(column_name), getDataType(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteBufferFromHTTPServerResponse out(request, response, keep_alive_timeout);
|
||||||
|
writeStringBinary(columns.toString(), out);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
process_error("Error getting columns from ODBC '" + getCurrentExceptionMessage(false) + "'");
|
||||||
|
tryLogCurrentException(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
29
dbms/programs/odbc-bridge/ColumnInfoHandler.h
Normal file
29
dbms/programs/odbc-bridge/ColumnInfoHandler.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Common/config.h>
|
||||||
|
#include <Interpreters/Context.h>
|
||||||
|
#include <Poco/Logger.h>
|
||||||
|
#include <Poco/Net/HTTPRequestHandler.h>
|
||||||
|
|
||||||
|
#if USE_POCO_SQLODBC || USE_POCO_DATAODBC
|
||||||
|
/** The structure of the table is taken from the mysql query "SELECT * FROM table WHERE 1=0".
|
||||||
|
* If there is no such table, an exception is thrown.
|
||||||
|
*/
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
class ODBCColumnsInfoHandler : public Poco::Net::HTTPRequestHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ODBCColumnsInfoHandler(size_t keep_alive_timeout_, std::shared_ptr<Context> context_)
|
||||||
|
: log(&Poco::Logger::get("ODBCColumnsInfoHandler")), keep_alive_timeout(keep_alive_timeout_), context(context_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Poco::Logger * log;
|
||||||
|
size_t keep_alive_timeout;
|
||||||
|
std::shared_ptr<Context> context;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
@ -1,4 +1,6 @@
|
|||||||
#include "HandlerFactory.h"
|
#include "HandlerFactory.h"
|
||||||
|
#include "PingHandler.h"
|
||||||
|
#include "ColumnInfoHandler.h"
|
||||||
#include <Common/HTMLForm.h>
|
#include <Common/HTMLForm.h>
|
||||||
|
|
||||||
#include <Poco/Ext/SessionPoolHelpers.h>
|
#include <Poco/Ext/SessionPoolHelpers.h>
|
||||||
@ -9,15 +11,24 @@ namespace DB
|
|||||||
{
|
{
|
||||||
Poco::Net::HTTPRequestHandler * HandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & request)
|
Poco::Net::HTTPRequestHandler * HandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & request)
|
||||||
{
|
{
|
||||||
const auto & uri = request.getURI();
|
Poco::URI uri{request.getURI()};
|
||||||
LOG_TRACE(log, "Request URI: " + uri);
|
LOG_TRACE(log, "Request URI: " + uri.toString());
|
||||||
|
|
||||||
if (uri == "/ping" && request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET)
|
if (uri.getPath() == "/ping" && request.getMethod() == Poco::Net::HTTPRequest::HTTP_GET)
|
||||||
return new PingHandler(keep_alive_timeout);
|
return new PingHandler(keep_alive_timeout);
|
||||||
|
|
||||||
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
|
if (request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
|
||||||
return new ODBCHandler(pool_map, keep_alive_timeout, context);
|
{
|
||||||
|
|
||||||
|
if (uri.getPath() == "/colinfo")
|
||||||
|
#if USE_POCO_SQLODBC || USE_POCO_DATAODBC
|
||||||
|
return new ODBCColumnsInfoHandler(keep_alive_timeout, context);
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
return new ODBCHandler(pool_map, keep_alive_timeout, context);
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
#include <Poco/Logger.h>
|
#include <Poco/Logger.h>
|
||||||
#include <Poco/Net/HTTPRequestHandler.h>
|
#include <Poco/Net/HTTPRequestHandler.h>
|
||||||
#include <Poco/Net/HTTPRequestHandlerFactory.h>
|
#include <Poco/Net/HTTPRequestHandlerFactory.h>
|
||||||
#include "Handlers.h"
|
#include "MainHandler.h"
|
||||||
|
#include "ColumnInfoHandler.h"
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
@ -13,7 +14,7 @@
|
|||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
/** Factory for '/ping' and '/' handlers.
|
/** Factory for '/ping', '/' and '/colinfo' handlers.
|
||||||
* Also stores Session pools for ODBC connections
|
* Also stores Session pools for ODBC connections
|
||||||
*/
|
*/
|
||||||
class HandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
|
class HandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
|
||||||
|
@ -1,30 +1,24 @@
|
|||||||
#include "Handlers.h"
|
#include "MainHandler.h"
|
||||||
#include <Common/HTMLForm.h>
|
|
||||||
|
#include "validateODBCConnectionString.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <DataStreams/IBlockOutputStream.h>
|
|
||||||
#include <DataStreams/copyData.h>
|
#include <DataStreams/copyData.h>
|
||||||
#include <DataTypes/DataTypeFactory.h>
|
#include <DataTypes/DataTypeFactory.h>
|
||||||
#include <Dictionaries/ODBCBlockInputStream.h>
|
#include <Dictionaries/ODBCBlockInputStream.h>
|
||||||
#include <Formats/BinaryRowInputStream.h>
|
#include <Formats/BinaryRowInputStream.h>
|
||||||
#include <Formats/FormatFactory.h>
|
#include <Formats/FormatFactory.h>
|
||||||
#include <IO/ReadBufferFromIStream.h>
|
|
||||||
#include <IO/WriteBufferFromHTTPServerResponse.h>
|
#include <IO/WriteBufferFromHTTPServerResponse.h>
|
||||||
#include <IO/WriteHelpers.h>
|
#include <IO/WriteHelpers.h>
|
||||||
#include <Interpreters/Context.h>
|
#include <Interpreters/Context.h>
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
#include <boost/tokenizer.hpp>
|
|
||||||
#include <Poco/Ext/SessionPoolHelpers.h>
|
#include <Poco/Ext/SessionPoolHelpers.h>
|
||||||
#include <Poco/Net/HTTPServerRequest.h>
|
#include <Poco/Net/HTTPServerRequest.h>
|
||||||
#include <Poco/Net/HTTPServerResponse.h>
|
#include <Poco/Net/HTTPServerResponse.h>
|
||||||
|
#include <Common/HTMLForm.h>
|
||||||
#include <common/logger_useful.h>
|
#include <common/logger_useful.h>
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
namespace ErrorCodes
|
|
||||||
{
|
|
||||||
extern const int BAD_REQUEST_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::unique_ptr<Block> parseColumns(std::string && column_string)
|
std::unique_ptr<Block> parseColumns(std::string && column_string)
|
||||||
@ -44,7 +38,7 @@ ODBCHandler::PoolPtr ODBCHandler::getPool(const std::string & connection_str)
|
|||||||
if (!pool_map->count(connection_str))
|
if (!pool_map->count(connection_str))
|
||||||
{
|
{
|
||||||
pool_map->emplace(connection_str, createAndCheckResizePocoSessionPool([connection_str] {
|
pool_map->emplace(connection_str, createAndCheckResizePocoSessionPool([connection_str] {
|
||||||
return std::make_shared<Poco::Data::SessionPool>("ODBC", connection_str);
|
return std::make_shared<Poco::Data::SessionPool>("ODBC", validateODBCConnectionString(connection_str));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return pool_map->at(connection_str);
|
return pool_map->at(connection_str);
|
||||||
@ -129,18 +123,4 @@ void ODBCHandler::handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Ne
|
|||||||
tryLogCurrentException(log);
|
tryLogCurrentException(log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PingHandler::handleRequest(Poco::Net::HTTPServerRequest & /*request*/, Poco::Net::HTTPServerResponse & response)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setResponseDefaultHeaders(response, keep_alive_timeout);
|
|
||||||
const char * data = "Ok.\n";
|
|
||||||
response.sendBuffer(data, strlen(data));
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
tryLogCurrentException("PingHandler");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -46,15 +46,4 @@ private:
|
|||||||
PoolPtr getPool(const std::string & connection_str);
|
PoolPtr getPool(const std::string & connection_str);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Simple ping handler, answers "Ok." to GET request
|
|
||||||
*/
|
|
||||||
class PingHandler : public Poco::Net::HTTPRequestHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PingHandler(size_t keep_alive_timeout_) : keep_alive_timeout(keep_alive_timeout_) {}
|
|
||||||
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t keep_alive_timeout;
|
|
||||||
};
|
|
||||||
}
|
}
|
22
dbms/programs/odbc-bridge/PingHandler.cpp
Normal file
22
dbms/programs/odbc-bridge/PingHandler.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#include "PingHandler.h"
|
||||||
|
#include <Poco/Net/HTTPServerRequest.h>
|
||||||
|
#include <Poco/Net/HTTPServerResponse.h>
|
||||||
|
#include <Common/Exception.h>
|
||||||
|
#include <IO/HTTPCommon.h>
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
void PingHandler::handleRequest(Poco::Net::HTTPServerRequest & /*request*/, Poco::Net::HTTPServerResponse & response)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setResponseDefaultHeaders(response, keep_alive_timeout);
|
||||||
|
const char * data = "Ok.\n";
|
||||||
|
response.sendBuffer(data, strlen(data));
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
tryLogCurrentException("PingHandler");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
dbms/programs/odbc-bridge/PingHandler.h
Normal file
17
dbms/programs/odbc-bridge/PingHandler.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Poco/Net/HTTPRequestHandler.h>
|
||||||
|
|
||||||
|
namespace DB
|
||||||
|
{
|
||||||
|
/** Simple ping handler, answers "Ok." to GET request
|
||||||
|
*/
|
||||||
|
class PingHandler : public Poco::Net::HTTPRequestHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PingHandler(size_t keep_alive_timeout_) : keep_alive_timeout(keep_alive_timeout_) {}
|
||||||
|
void handleRequest(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse & response) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t keep_alive_timeout;
|
||||||
|
};
|
||||||
|
}
|
2
dbms/programs/odbc-bridge/tests/CMakeLists.txt
Normal file
2
dbms/programs/odbc-bridge/tests/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
add_executable (validate-odbc-connection-string validate-odbc-connection-string.cpp)
|
||||||
|
target_link_libraries (validate-odbc-connection-string clickhouse-odbc-bridge-lib)
|
@ -1,6 +1,6 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
#include <Common/validateODBCConnectionString.h>
|
#include "../validateODBCConnectionString.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace DB;
|
using namespace DB;
|
@ -5,7 +5,7 @@
|
|||||||
#include <common/find_first_symbols.h>
|
#include <common/find_first_symbols.h>
|
||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
#include <Common/StringUtils/StringUtils.h>
|
#include <Common/StringUtils/StringUtils.h>
|
||||||
#include <Common/validateODBCConnectionString.h>
|
#include "validateODBCConnectionString.h"
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
@ -1,12 +1,11 @@
|
|||||||
#include <Common/ODBCBridgeHelper.h>
|
#include <Common/ODBCBridgeHelper.h>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <Common/validateODBCConnectionString.h>
|
|
||||||
#include <IO/ReadHelpers.h>
|
#include <IO/ReadHelpers.h>
|
||||||
#include <IO/ReadWriteBufferFromHTTP.h>
|
#include <IO/ReadWriteBufferFromHTTP.h>
|
||||||
|
#include <Poco/Net/HTTPRequest.h>
|
||||||
#include <Poco/Path.h>
|
#include <Poco/Path.h>
|
||||||
#include <Poco/Util/AbstractConfiguration.h>
|
#include <Poco/Util/AbstractConfiguration.h>
|
||||||
#include <Poco/Net/HTTPRequest.h>
|
|
||||||
#include <Common/ShellCommand.h>
|
#include <Common/ShellCommand.h>
|
||||||
#include <common/logger_useful.h>
|
#include <common/logger_useful.h>
|
||||||
#include <ext/range.h>
|
#include <ext/range.h>
|
||||||
@ -18,8 +17,9 @@ namespace ErrorCodes
|
|||||||
{
|
{
|
||||||
extern const int EXTERNAL_SERVER_IS_NOT_RESPONDING;
|
extern const int EXTERNAL_SERVER_IS_NOT_RESPONDING;
|
||||||
}
|
}
|
||||||
ODBCBridgeHelper::ODBCBridgeHelper(const Configuration & config_, const Poco::Timespan & http_timeout_, const std::string & connection_string_)
|
ODBCBridgeHelper::ODBCBridgeHelper(
|
||||||
: config(config_), http_timeout(http_timeout_), connection_string(validateODBCConnectionString(connection_string_))
|
const Configuration & config_, const Poco::Timespan & http_timeout_, const std::string & connection_string_)
|
||||||
|
: config(config_), http_timeout(http_timeout_), connection_string(connection_string_)
|
||||||
{
|
{
|
||||||
size_t bridge_port = config.getUInt("odbc_bridge.port", DEFAULT_PORT);
|
size_t bridge_port = config.getUInt("odbc_bridge.port", DEFAULT_PORT);
|
||||||
std::string bridge_host = config.getString("odbc_bridge.host", DEFAULT_HOST);
|
std::string bridge_host = config.getString("odbc_bridge.host", DEFAULT_HOST);
|
||||||
@ -102,4 +102,30 @@ void ODBCBridgeHelper::startODBCBridgeSync() const
|
|||||||
throw Exception("ODBCBridgeHelper: clickhouse-odbc-bridge is not responding", ErrorCodes::EXTERNAL_SERVER_IS_NOT_RESPONDING);
|
throw Exception("ODBCBridgeHelper: clickhouse-odbc-bridge is not responding", ErrorCodes::EXTERNAL_SERVER_IS_NOT_RESPONDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Poco::URI ODBCBridgeHelper::getMainURI() const
|
||||||
|
{
|
||||||
|
size_t bridge_port = config.getUInt("odbc_bridge.port", DEFAULT_PORT);
|
||||||
|
std::string bridge_host = config.getString("odbc_bridge.host", DEFAULT_HOST);
|
||||||
|
|
||||||
|
Poco::URI main_uri;
|
||||||
|
main_uri.setHost(bridge_host);
|
||||||
|
main_uri.setPort(bridge_port);
|
||||||
|
main_uri.setScheme("http");
|
||||||
|
main_uri.setPath(MAIN_HANDLER);
|
||||||
|
return main_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poco::URI ODBCBridgeHelper::getColInfoURI() const
|
||||||
|
{
|
||||||
|
size_t bridge_port = config.getUInt("odbc_bridge.port", DEFAULT_PORT);
|
||||||
|
std::string bridge_host = config.getString("odbc_bridge.host", DEFAULT_HOST);
|
||||||
|
|
||||||
|
Poco::URI col_info_uri;
|
||||||
|
col_info_uri.setHost(bridge_host);
|
||||||
|
col_info_uri.setPort(bridge_port);
|
||||||
|
col_info_uri.setScheme("http");
|
||||||
|
col_info_uri.setPath(COL_INFO_HANDLER);
|
||||||
|
return col_info_uri;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ public:
|
|||||||
static constexpr inline auto DEFAULT_FORMAT = "RowBinary";
|
static constexpr inline auto DEFAULT_FORMAT = "RowBinary";
|
||||||
static constexpr inline auto PING_HANDLER = "/ping";
|
static constexpr inline auto PING_HANDLER = "/ping";
|
||||||
static constexpr inline auto MAIN_HANDLER = "/";
|
static constexpr inline auto MAIN_HANDLER = "/";
|
||||||
|
static constexpr inline auto COL_INFO_HANDLER = "/colinfo";
|
||||||
static constexpr inline auto PING_OK_ANSWER = "Ok.";
|
static constexpr inline auto PING_OK_ANSWER = "Ok.";
|
||||||
|
|
||||||
ODBCBridgeHelper(const Configuration & config_, const Poco::Timespan & http_timeout_, const std::string & connection_string_);
|
ODBCBridgeHelper(const Configuration & config_, const Poco::Timespan & http_timeout_, const std::string & connection_string_);
|
||||||
@ -44,5 +45,8 @@ public:
|
|||||||
|
|
||||||
void startODBCBridge() const;
|
void startODBCBridge() const;
|
||||||
void startODBCBridgeSync() const;
|
void startODBCBridgeSync() const;
|
||||||
|
|
||||||
|
Poco::URI getMainURI() const;
|
||||||
|
Poco::URI getColInfoURI() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,3 @@ target_link_libraries (cow_columns clickhouse_common_io)
|
|||||||
|
|
||||||
add_executable (stopwatch stopwatch.cpp)
|
add_executable (stopwatch stopwatch.cpp)
|
||||||
target_link_libraries (stopwatch clickhouse_common_io)
|
target_link_libraries (stopwatch clickhouse_common_io)
|
||||||
|
|
||||||
add_executable (validate-odbc-connection-string validate-odbc-connection-string.cpp)
|
|
||||||
target_link_libraries (validate-odbc-connection-string dbms)
|
|
||||||
|
@ -75,13 +75,7 @@ ODBCDictionarySource::ODBCDictionarySource(const DictionaryStructure & dict_stru
|
|||||||
timeouts{ConnectionTimeouts::getHTTPTimeouts(context.getSettingsRef())},
|
timeouts{ConnectionTimeouts::getHTTPTimeouts(context.getSettingsRef())},
|
||||||
global_context(context)
|
global_context(context)
|
||||||
{
|
{
|
||||||
const auto & global_config = context.getConfigRef();
|
bridge_url = odbc_bridge_helper.getMainURI();
|
||||||
size_t bridge_port = global_config.getUInt("odbc_bridge.port", ODBCBridgeHelper::DEFAULT_PORT);
|
|
||||||
std::string bridge_host = global_config.getString("odbc_bridge.host", ODBCBridgeHelper::DEFAULT_HOST);
|
|
||||||
|
|
||||||
bridge_url.setHost(bridge_host);
|
|
||||||
bridge_url.setPort(bridge_port);
|
|
||||||
bridge_url.setScheme("http");
|
|
||||||
|
|
||||||
auto url_params = odbc_bridge_helper.getURLParams(sample_block.getNamesAndTypesList().toString(), max_block_size);
|
auto url_params = odbc_bridge_helper.getURLParams(sample_block.getNamesAndTypesList().toString(), max_block_size);
|
||||||
for (const auto & [name, value] : url_params)
|
for (const auto & [name, value] : url_params)
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#include <Storages/StorageFactory.h>
|
#include <Storages/StorageFactory.h>
|
||||||
#include <Storages/StorageODBC.h>
|
#include <Storages/StorageODBC.h>
|
||||||
#include <Storages/transformQueryForExternalDatabase.h>
|
#include <Storages/transformQueryForExternalDatabase.h>
|
||||||
#include <Poco/Ext/SessionPoolHelpers.h>
|
|
||||||
#include <Poco/Util/AbstractConfiguration.h>
|
#include <Poco/Util/AbstractConfiguration.h>
|
||||||
#include <common/logger_useful.h>
|
#include <common/logger_useful.h>
|
||||||
|
|
||||||
@ -38,13 +37,7 @@ StorageODBC::StorageODBC(const std::string & table_name_,
|
|||||||
, remote_table_name(remote_table_name_)
|
, remote_table_name(remote_table_name_)
|
||||||
, log(&Poco::Logger::get("StorageODBC"))
|
, log(&Poco::Logger::get("StorageODBC"))
|
||||||
{
|
{
|
||||||
const auto & config = context_global.getConfigRef();
|
uri = odbc_bridge_helper.getMainURI();
|
||||||
size_t bridge_port = config.getUInt("odbc_bridge.port", ODBCBridgeHelper::DEFAULT_PORT);
|
|
||||||
std::string bridge_host = config.getString("odbc_bridge.host", ODBCBridgeHelper::DEFAULT_HOST);
|
|
||||||
|
|
||||||
uri.setHost(bridge_host);
|
|
||||||
uri.setPort(bridge_port);
|
|
||||||
uri.setScheme("http");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string StorageODBC::getReadMethod() const
|
std::string StorageODBC::getReadMethod() const
|
||||||
|
@ -6,13 +6,3 @@ list(REMOVE_ITEM clickhouse_table_functions_headers ITableFunction.h TableFuncti
|
|||||||
|
|
||||||
add_library(clickhouse_table_functions ${clickhouse_table_functions_sources})
|
add_library(clickhouse_table_functions ${clickhouse_table_functions_sources})
|
||||||
target_link_libraries(clickhouse_table_functions clickhouse_storages_system dbms ${Poco_Foundation_LIBRARY})
|
target_link_libraries(clickhouse_table_functions clickhouse_storages_system dbms ${Poco_Foundation_LIBRARY})
|
||||||
|
|
||||||
if (USE_POCO_SQLODBC)
|
|
||||||
target_link_libraries (clickhouse_table_functions ${Poco_SQLODBC_LIBRARY})
|
|
||||||
target_include_directories (clickhouse_table_functions SYSTEM PRIVATE ${ODBC_INCLUDE_DIRECTORIES} ${Poco_SQLODBC_INCLUDE_DIRS})
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (USE_POCO_DATAODBC)
|
|
||||||
target_link_libraries (clickhouse_table_functions ${Poco_DataODBC_LIBRARY})
|
|
||||||
target_include_directories (clickhouse_table_functions SYSTEM PRIVATE ${ODBC_INCLUDE_DIRECTORIES} ${Poco_DataODBC_INCLUDE_DIRS})
|
|
||||||
endif ()
|
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
#include <TableFunctions/TableFunctionODBC.h>
|
#include <TableFunctions/TableFunctionODBC.h>
|
||||||
|
|
||||||
#if USE_POCO_SQLODBC || USE_POCO_DATAODBC
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <ext/scope_guard.h>
|
#include <ext/scope_guard.h>
|
||||||
|
|
||||||
#include <DataTypes/DataTypeFactory.h>
|
#include <DataTypes/DataTypeFactory.h>
|
||||||
#include <Interpreters/evaluateConstantExpression.h>
|
#include <Interpreters/evaluateConstantExpression.h>
|
||||||
|
#include <IO/ReadHelpers.h>
|
||||||
|
#include <IO/ReadWriteBufferFromHTTP.h>
|
||||||
#include <Parsers/ASTFunction.h>
|
#include <Parsers/ASTFunction.h>
|
||||||
#include <Parsers/ASTLiteral.h>
|
#include <Parsers/ASTLiteral.h>
|
||||||
|
#include <Poco/Net/HTTPRequest.h>
|
||||||
#include <Storages/StorageODBC.h>
|
#include <Storages/StorageODBC.h>
|
||||||
#include <TableFunctions/ITableFunction.h>
|
#include <TableFunctions/ITableFunction.h>
|
||||||
#include <TableFunctions/TableFunctionFactory.h>
|
#include <TableFunctions/TableFunctionFactory.h>
|
||||||
#include <Common/Exception.h>
|
#include <Common/Exception.h>
|
||||||
#include <Common/typeid_cast.h>
|
#include <Common/typeid_cast.h>
|
||||||
|
#include <Common/ODBCBridgeHelper.h>
|
||||||
#include <Core/Defines.h>
|
#include <Core/Defines.h>
|
||||||
|
|
||||||
#include <Poco/Data/ODBC/ODBCException.h>
|
|
||||||
#include <Poco/Data/ODBC/SessionImpl.h>
|
|
||||||
#include <Poco/Data/ODBC/Utility.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
@ -28,33 +26,6 @@ namespace ErrorCodes
|
|||||||
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataTypePtr getDataType(SQLSMALLINT type)
|
|
||||||
{
|
|
||||||
const auto & factory = DataTypeFactory::instance();
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case SQL_INTEGER:
|
|
||||||
return factory.get("Int32");
|
|
||||||
case SQL_SMALLINT:
|
|
||||||
return factory.get("Int16");
|
|
||||||
case SQL_FLOAT:
|
|
||||||
return factory.get("Float32");
|
|
||||||
case SQL_REAL:
|
|
||||||
return factory.get("Float32");
|
|
||||||
case SQL_DOUBLE:
|
|
||||||
return factory.get("Float64");
|
|
||||||
case SQL_DATETIME:
|
|
||||||
return factory.get("DateTime");
|
|
||||||
case SQL_TYPE_TIMESTAMP:
|
|
||||||
return factory.get("DateTime");
|
|
||||||
case SQL_TYPE_DATE:
|
|
||||||
return factory.get("Date");
|
|
||||||
default:
|
|
||||||
return factory.get("String");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StoragePtr TableFunctionODBC::executeImpl(const ASTPtr & ast_function, const Context & context) const
|
StoragePtr TableFunctionODBC::executeImpl(const ASTPtr & ast_function, const Context & context) const
|
||||||
{
|
{
|
||||||
const ASTFunction & args_func = typeid_cast<const ASTFunction &>(*ast_function);
|
const ASTFunction & args_func = typeid_cast<const ASTFunction &>(*ast_function);
|
||||||
@ -73,41 +44,19 @@ StoragePtr TableFunctionODBC::executeImpl(const ASTPtr & ast_function, const Con
|
|||||||
|
|
||||||
std::string connection_string = static_cast<const ASTLiteral &>(*args[0]).value.safeGet<String>();
|
std::string connection_string = static_cast<const ASTLiteral &>(*args[0]).value.safeGet<String>();
|
||||||
std::string table_name = static_cast<const ASTLiteral &>(*args[1]).value.safeGet<String>();
|
std::string table_name = static_cast<const ASTLiteral &>(*args[1]).value.safeGet<String>();
|
||||||
|
const auto & config = context.getConfigRef();
|
||||||
|
ODBCBridgeHelper helper(config, context.getSettingsRef().http_receive_timeout.value, connection_string);
|
||||||
|
helper.startODBCBridgeSync();
|
||||||
|
|
||||||
Poco::Data::ODBC::SessionImpl session(connection_string, DBMS_DEFAULT_CONNECT_TIMEOUT_SEC);
|
Poco::URI col_info_uri = helper.getColInfoURI();
|
||||||
SQLHDBC hdbc = session.dbc().handle();
|
col_info_uri.addQueryParameter("connection_string", connection_string);
|
||||||
|
col_info_uri.addQueryParameter("table", table_name);
|
||||||
|
|
||||||
SQLHSTMT hstmt = nullptr;
|
ReadWriteBufferFromHTTP buf(col_info_uri, Poco::Net::HTTPRequest::HTTP_POST, nullptr);
|
||||||
|
|
||||||
if (Poco::Data::ODBC::Utility::isError(SQLAllocStmt(hdbc, &hstmt)))
|
std::string col_info;
|
||||||
throw Poco::Data::ODBC::ODBCException("Could not allocate connection handle.");
|
readStringBinary(col_info, buf);
|
||||||
|
NamesAndTypesList columns = NamesAndTypesList::parse(col_info);
|
||||||
SCOPE_EXIT(SQLFreeStmt(hstmt, SQL_DROP));
|
|
||||||
|
|
||||||
/// TODO Why not do SQLColumns instead?
|
|
||||||
std::string query = "SELECT * FROM " + table_name + " WHERE 1 = 0";
|
|
||||||
if (Poco::Data::ODBC::Utility::isError(Poco::Data::ODBC::SQLPrepare(hstmt, reinterpret_cast<SQLCHAR *>(&query[0]), query.size())))
|
|
||||||
throw Poco::Data::ODBC::DescriptorException(session.dbc());
|
|
||||||
|
|
||||||
if (Poco::Data::ODBC::Utility::isError(SQLExecute(hstmt)))
|
|
||||||
throw Poco::Data::ODBC::StatementException(hstmt);
|
|
||||||
|
|
||||||
SQLSMALLINT cols = 0;
|
|
||||||
if (Poco::Data::ODBC::Utility::isError(SQLNumResultCols(hstmt, &cols)))
|
|
||||||
throw Poco::Data::ODBC::StatementException(hstmt);
|
|
||||||
|
|
||||||
/// TODO cols not checked
|
|
||||||
|
|
||||||
NamesAndTypesList columns;
|
|
||||||
for (SQLSMALLINT ncol = 1; ncol <= cols; ++ncol)
|
|
||||||
{
|
|
||||||
SQLSMALLINT type = 0;
|
|
||||||
/// TODO Why 301?
|
|
||||||
SQLCHAR column_name[301];
|
|
||||||
/// TODO Result is not checked.
|
|
||||||
Poco::Data::ODBC::SQLDescribeCol(hstmt, ncol, column_name, sizeof(column_name), NULL, &type, NULL, NULL, NULL);
|
|
||||||
columns.emplace_back(reinterpret_cast<char *>(column_name), getDataType(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = StorageODBC::create(table_name, connection_string, "", table_name, ColumnsDescription{columns}, context);
|
auto result = StorageODBC::create(table_name, connection_string, "", table_name, ColumnsDescription{columns}, context);
|
||||||
result->startup();
|
result->startup();
|
||||||
@ -120,5 +69,3 @@ void registerTableFunctionODBC(TableFunctionFactory & factory)
|
|||||||
factory.registerFunction<TableFunctionODBC>();
|
factory.registerFunction<TableFunctionODBC>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Common/config.h>
|
#include <Common/config.h>
|
||||||
#if USE_POCO_SQLODBC || USE_POCO_DATAODBC
|
|
||||||
|
|
||||||
#include <TableFunctions/ITableFunction.h>
|
#include <TableFunctions/ITableFunction.h>
|
||||||
|
|
||||||
|
|
||||||
namespace DB
|
namespace DB
|
||||||
{
|
{
|
||||||
/* odbc (odbc connect string, table) - creates a temporary StorageODBC.
|
/* odbc (odbc connect string, table) - creates a temporary StorageODBC.
|
||||||
* The structure of the table is taken from the mysql query "SELECT * FROM table WHERE 1=0".
|
|
||||||
* If there is no such table, an exception is thrown.
|
|
||||||
*/
|
*/
|
||||||
class TableFunctionODBC : public ITableFunction
|
class TableFunctionODBC : public ITableFunction
|
||||||
{
|
{
|
||||||
@ -24,5 +20,3 @@ private:
|
|||||||
StoragePtr executeImpl(const ASTPtr & ast_function, const Context & context) const override;
|
StoragePtr executeImpl(const ASTPtr & ast_function, const Context & context) const override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
Loading…
Reference in New Issue
Block a user