1#pragma once
2
3#include <sstream>
4#include <IO/ReadHelpers.h>
5#include <IO/ReadWriteBufferFromHTTP.h>
6#include <Interpreters/Context.h>
7#include <Parsers/IdentifierQuotingStyle.h>
8#include <Poco/File.h>
9#include <Poco/Logger.h>
10#include <Poco/Net/HTTPRequest.h>
11#include <Poco/Path.h>
12#include <Poco/URI.h>
13#include <Poco/Util/AbstractConfiguration.h>
14#include <Common/ShellCommand.h>
15#include <Common/config.h>
16#include <common/logger_useful.h>
17#include <ext/range.h>
18
19namespace DB
20{
21namespace ErrorCodes
22{
23 extern const int EXTERNAL_EXECUTABLE_NOT_FOUND;
24 extern const int EXTERNAL_SERVER_IS_NOT_RESPONDING;
25 extern const int ILLEGAL_TYPE_OF_ARGUMENT;
26}
27
28/**
29 * Class for Helpers for Xdbc-bridges, provide utility methods, not main request
30 */
31class IXDBCBridgeHelper
32{
33public:
34 static constexpr inline auto DEFAULT_FORMAT = "RowBinary";
35
36 virtual std::vector<std::pair<std::string, std::string>> getURLParams(const std::string & cols, UInt64 max_block_size) const = 0;
37 virtual void startBridgeSync() const = 0;
38 virtual Poco::URI getMainURI() const = 0;
39 virtual Poco::URI getColumnsInfoURI() const = 0;
40 virtual IdentifierQuotingStyle getIdentifierQuotingStyle() = 0;
41 virtual String getName() const = 0;
42
43 virtual ~IXDBCBridgeHelper() = default;
44};
45
46using BridgeHelperPtr = std::shared_ptr<IXDBCBridgeHelper>;
47
48template <typename BridgeHelperMixin>
49class XDBCBridgeHelper : public IXDBCBridgeHelper
50{
51private:
52 Poco::Timespan http_timeout;
53
54 std::string connection_string;
55
56 Poco::URI ping_url;
57
58 Poco::Logger * log = &Poco::Logger::get(BridgeHelperMixin::getName() + "BridgeHelper");
59
60 std::optional<IdentifierQuotingStyle> quote_style;
61
62protected:
63 auto getConnectionString() const
64 {
65 return connection_string;
66 }
67
68public:
69 using Configuration = Poco::Util::AbstractConfiguration;
70
71 const Context & context;
72 const Configuration & config;
73
74 static constexpr inline auto DEFAULT_HOST = "localhost";
75 static constexpr inline auto DEFAULT_PORT = BridgeHelperMixin::DEFAULT_PORT;
76 static constexpr inline auto PING_HANDLER = "/ping";
77 static constexpr inline auto MAIN_HANDLER = "/";
78 static constexpr inline auto COL_INFO_HANDLER = "/columns_info";
79 static constexpr inline auto IDENTIFIER_QUOTE_HANDLER = "/identifier_quote";
80 static constexpr inline auto PING_OK_ANSWER = "Ok.";
81
82 XDBCBridgeHelper(const Context & global_context_, const Poco::Timespan & http_timeout_, const std::string & connection_string_)
83 : http_timeout(http_timeout_), connection_string(connection_string_), context(global_context_), config(context.getConfigRef())
84 {
85 size_t bridge_port = config.getUInt(BridgeHelperMixin::configPrefix() + ".port", DEFAULT_PORT);
86 std::string bridge_host = config.getString(BridgeHelperMixin::configPrefix() + ".host", DEFAULT_HOST);
87
88 ping_url.setHost(bridge_host);
89 ping_url.setPort(bridge_port);
90 ping_url.setScheme("http");
91 ping_url.setPath(PING_HANDLER);
92 }
93
94 String getName() const override
95 {
96 return BridgeHelperMixin::getName();
97 }
98
99 IdentifierQuotingStyle getIdentifierQuotingStyle() override
100 {
101 if (!quote_style.has_value())
102 {
103 startBridgeSync();
104
105 auto uri = createBaseURI();
106 uri.setPath(IDENTIFIER_QUOTE_HANDLER);
107 uri.addQueryParameter("connection_string", getConnectionString());
108
109 ReadWriteBufferFromHTTP buf(uri, Poco::Net::HTTPRequest::HTTP_POST, nullptr);
110 std::string character;
111 readStringBinary(character, buf);
112 if (character.length() > 1)
113 throw Exception("Failed to parse quoting style from '" + character + "' for service " + BridgeHelperMixin::serviceAlias(), ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
114 else if (character.length() == 0)
115 quote_style = IdentifierQuotingStyle::None;
116 else if (character[0] == '`')
117 quote_style = IdentifierQuotingStyle::Backticks;
118 else if (character[0] == '"')
119 quote_style = IdentifierQuotingStyle::DoubleQuotes;
120 else
121 throw Exception("Can not map quote identifier '" + character + "' to enum value", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
122 }
123
124 return *quote_style;
125 }
126
127 /**
128 * @todo leaky abstraction - used by external API's
129 */
130 std::vector<std::pair<std::string, std::string>> getURLParams(const std::string & cols, UInt64 max_block_size) const override
131 {
132 std::vector<std::pair<std::string, std::string>> result;
133
134 result.emplace_back("connection_string", connection_string); /// already validated
135 result.emplace_back("columns", cols);
136 result.emplace_back("max_block_size", std::to_string(max_block_size));
137
138 return result;
139 }
140
141 /**
142 * Performs spawn of external daemon
143 */
144 void startBridgeSync() const override
145 {
146 if (!checkBridgeIsRunning())
147 {
148 LOG_TRACE(log, BridgeHelperMixin::serviceAlias() + " is not running, will try to start it");
149 startBridge();
150 bool started = false;
151 for (size_t counter : ext::range(1, 20))
152 {
153 LOG_TRACE(log, "Checking " + BridgeHelperMixin::serviceAlias() + " is running, try " << counter);
154 if (checkBridgeIsRunning())
155 {
156 started = true;
157 break;
158 }
159 std::this_thread::sleep_for(std::chrono::milliseconds(10));
160 }
161 if (!started)
162 throw Exception(BridgeHelperMixin::getName() + "BridgeHelper: " + BridgeHelperMixin::serviceAlias() + " is not responding",
163 ErrorCodes::EXTERNAL_SERVER_IS_NOT_RESPONDING);
164 }
165 }
166
167 /**
168 * URI to fetch the data from external service
169 */
170 Poco::URI getMainURI() const override
171 {
172 auto uri = createBaseURI();
173 uri.setPath(MAIN_HANDLER);
174 return uri;
175 }
176
177 /**
178 * URI to retrieve column description from external service
179 */
180 Poco::URI getColumnsInfoURI() const override
181 {
182 auto uri = createBaseURI();
183 uri.setPath(COL_INFO_HANDLER);
184 return uri;
185 }
186
187protected:
188 Poco::URI createBaseURI() const
189 {
190 Poco::URI uri;
191 uri.setHost(ping_url.getHost());
192 uri.setPort(ping_url.getPort());
193 uri.setScheme("http");
194 return uri;
195 }
196
197private:
198 bool checkBridgeIsRunning() const
199 {
200 try
201 {
202 ReadWriteBufferFromHTTP buf(ping_url, Poco::Net::HTTPRequest::HTTP_GET, nullptr);
203 return checkString(XDBCBridgeHelper::PING_OK_ANSWER, buf);
204 }
205 catch (...)
206 {
207 return false;
208 }
209 }
210
211 /* Contains logic for instantiation of the bridge instance */
212 void startBridge() const
213 {
214 auto cmd = BridgeHelperMixin::startBridge(config, log, http_timeout);
215 context.addXDBCBridgeCommand(std::move(cmd));
216 }
217};
218
219struct JDBCBridgeMixin
220{
221 static constexpr inline auto DEFAULT_PORT = 9019;
222 static const String configPrefix()
223 {
224 return "jdbc_bridge";
225 }
226 static const String serviceAlias()
227 {
228 return "clickhouse-jdbc-bridge";
229 }
230 static const String getName()
231 {
232 return "JDBC";
233 }
234
235 static std::unique_ptr<ShellCommand> startBridge(const Poco::Util::AbstractConfiguration &, const Poco::Logger *, const Poco::Timespan &)
236 {
237 throw Exception("jdbc-bridge is not running. Please, start it manually", ErrorCodes::EXTERNAL_SERVER_IS_NOT_RESPONDING);
238 }
239};
240
241struct ODBCBridgeMixin
242{
243 static constexpr inline auto DEFAULT_PORT = 9018;
244
245 static const String configPrefix()
246 {
247 return "odbc_bridge";
248 }
249 static const String serviceAlias()
250 {
251 return "clickhouse-odbc-bridge";
252 }
253 static const String getName()
254 {
255 return "ODBC";
256 }
257
258 static std::unique_ptr<ShellCommand> startBridge(const Poco::Util::AbstractConfiguration & config, Poco::Logger * log, const Poco::Timespan & http_timeout)
259 {
260 /// Path to executable folder
261 Poco::Path path{config.getString("application.dir", "/usr/bin")};
262
263
264 std::vector<std::string> cmd_args;
265 path.setFileName("clickhouse-odbc-bridge");
266
267 std::stringstream command;
268
269#if !CLICKHOUSE_SPLIT_BINARY
270 cmd_args.push_back("odbc-bridge");
271#endif
272
273 cmd_args.push_back("--http-port");
274 cmd_args.push_back(std::to_string(config.getUInt(configPrefix() + ".port", DEFAULT_PORT)));
275 cmd_args.push_back("--listen-host");
276 cmd_args.push_back(config.getString(configPrefix() + ".listen_host", XDBCBridgeHelper<ODBCBridgeMixin>::DEFAULT_HOST));
277 cmd_args.push_back("--http-timeout");
278 cmd_args.push_back(std::to_string(http_timeout.totalMicroseconds()));
279 if (config.has("logger." + configPrefix() + "_log"))
280 {
281 cmd_args.push_back("--log-path");
282 cmd_args.push_back(config.getString("logger." + configPrefix() + "_log"));
283 }
284 if (config.has("logger." + configPrefix() + "_errlog"))
285 {
286 cmd_args.push_back("--err-log-path");
287 cmd_args.push_back(config.getString("logger." + configPrefix() + "_errlog"));
288 }
289 if (config.has("logger." + configPrefix() + "_level"))
290 {
291 cmd_args.push_back("--log-level");
292 cmd_args.push_back(config.getString("logger." + configPrefix() + "_level"));
293 }
294
295 LOG_TRACE(log, "Starting " + serviceAlias());
296
297 return ShellCommand::executeDirect(path.toString(), cmd_args, true);
298 }
299};
300}
301