| 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 | |