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 | |
19 | namespace DB |
20 | { |
21 | namespace 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 | */ |
31 | class IXDBCBridgeHelper |
32 | { |
33 | public: |
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 | |
46 | using BridgeHelperPtr = std::shared_ptr<IXDBCBridgeHelper>; |
47 | |
48 | template <typename BridgeHelperMixin> |
49 | class XDBCBridgeHelper : public IXDBCBridgeHelper |
50 | { |
51 | private: |
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 | |
62 | protected: |
63 | auto getConnectionString() const |
64 | { |
65 | return connection_string; |
66 | } |
67 | |
68 | public: |
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 | |
187 | protected: |
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 | |
197 | private: |
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 | |
219 | struct 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 | |
241 | struct 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 | |