clickhouse-native 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/clickhouse_native/client.cpp +100 -13
- data/ext/clickhouse_native/patches/0002-carry-settings-into-insert.patch +82 -0
- data/ext/clickhouse_native/patches/0003-cancelable-socket.patch +136 -0
- data/lib/clickhouse_native/client.rb +1 -1
- data/lib/clickhouse_native/pool.rb +53 -34
- data/lib/clickhouse_native/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dbc7f982fc96317d6b6721bc7162e22d01eac4f4fc9edb307443d24fdee59032
|
|
4
|
+
data.tar.gz: 824150b24641414bfc0ccc4a6e37aa521b768bb73423c1bda9c1bfbefc466044
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cda8a41d58816d63aa064ae3008b20980d35be6f695ff04433bc760ec28c0a286b707bdeb725d5691ac8245533c05d90df4c9c5b3eb13af6b860a179d9349db
|
|
7
|
+
data.tar.gz: caf00f391e65eb920a2d917316fa05c1569b7aac7c89f83a122b149c3305a3b0b40b6dc02afa5bd1cebcd78f0897700d5a2f9fbafb2c0d327da973dd3bc542f6
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
#include <clickhouse/exceptions.h>
|
|
18
18
|
#include <clickhouse/types/types.h>
|
|
19
19
|
|
|
20
|
+
#include <chrono>
|
|
20
21
|
#include <cstdint>
|
|
21
22
|
#include <exception>
|
|
22
23
|
#include <memory>
|
|
23
24
|
#include <string>
|
|
24
25
|
#include <system_error>
|
|
26
|
+
#include <utility>
|
|
25
27
|
#include <vector>
|
|
26
28
|
|
|
27
29
|
using namespace clickhouse;
|
|
@@ -666,6 +668,11 @@ static void append_value(const ColumnRef& col, VALUE value) {
|
|
|
666
668
|
|
|
667
669
|
struct CHClient {
|
|
668
670
|
std::unique_ptr<Client> client;
|
|
671
|
+
// Session settings applied to *every* query as per-query settings, rather
|
|
672
|
+
// than a one-time session `SET`. Per-query settings ride each query packet,
|
|
673
|
+
// so they survive a transparent ping_before_query reconnect (a fresh socket
|
|
674
|
+
// would otherwise lose a session-level SET and fall back to server defaults).
|
|
675
|
+
std::vector<std::pair<std::string, std::string>> default_settings;
|
|
669
676
|
};
|
|
670
677
|
|
|
671
678
|
static void ch_client_free(void* p) {
|
|
@@ -707,6 +714,18 @@ static uint16_t kwarg_uint16(VALUE kwargs, const char* key, uint16_t fallback) {
|
|
|
707
714
|
return static_cast<uint16_t>(NUM2UINT(v));
|
|
708
715
|
}
|
|
709
716
|
|
|
717
|
+
static bool kwarg_bool(VALUE kwargs, const char* key, bool fallback) {
|
|
718
|
+
VALUE v = rb_hash_lookup2(kwargs, ID2SYM(rb_intern(key)), Qundef);
|
|
719
|
+
if (v == Qundef) return fallback;
|
|
720
|
+
return RTEST(v);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
static unsigned int kwarg_uint(VALUE kwargs, const char* key, unsigned int fallback) {
|
|
724
|
+
VALUE v = rb_hash_lookup2(kwargs, ID2SYM(rb_intern(key)), Qundef);
|
|
725
|
+
if (v == Qundef || NIL_P(v)) return fallback;
|
|
726
|
+
return NUM2UINT(v);
|
|
727
|
+
}
|
|
728
|
+
|
|
710
729
|
static CompressionMethod kwarg_compression(VALUE kwargs) {
|
|
711
730
|
VALUE v = rb_hash_lookup2(kwargs, ID2SYM(rb_intern("compression")), Qundef);
|
|
712
731
|
if (v == Qundef || NIL_P(v)) return CompressionMethod::None;
|
|
@@ -742,6 +761,16 @@ static int apply_settings_cb(VALUE key, VALUE val, VALUE arg) {
|
|
|
742
761
|
return ST_CONTINUE;
|
|
743
762
|
}
|
|
744
763
|
|
|
764
|
+
// Collect a `settings:` Hash into a client's default_settings vector at
|
|
765
|
+
// construction. Values are stringified the same way per-query settings are.
|
|
766
|
+
static int collect_setting_cb(VALUE key, VALUE val, VALUE arg) {
|
|
767
|
+
auto* vec = reinterpret_cast<std::vector<std::pair<std::string, std::string>>*>(arg);
|
|
768
|
+
VALUE k = SYMBOL_P(key) ? rb_sym2str(key) : key;
|
|
769
|
+
StringValue(k);
|
|
770
|
+
vec->emplace_back(std::string(RSTRING_PTR(k), RSTRING_LEN(k)), stringify_setting_value(val));
|
|
771
|
+
return ST_CONTINUE;
|
|
772
|
+
}
|
|
773
|
+
|
|
745
774
|
// Read a `settings:` Hash out of the parsed kwargs and stamp each entry
|
|
746
775
|
// onto `q` as a per-query setting. No-op if kwargs is nil or settings is
|
|
747
776
|
// missing/empty. Raises TypeError if settings is not a Hash.
|
|
@@ -765,6 +794,16 @@ static void apply_read_settings(Query& q, VALUE kwargs) {
|
|
|
765
794
|
apply_settings(q, kwargs);
|
|
766
795
|
}
|
|
767
796
|
|
|
797
|
+
// Stamp the client's construction-time `settings:` onto `q`. Marked important
|
|
798
|
+
// (flag 1) so an unknown setting name errors — matching the old session `SET`,
|
|
799
|
+
// which failed loudly instead of silently ignoring a typo'd setting. Applied
|
|
800
|
+
// before per-call settings so an explicit `settings:` on the call still wins.
|
|
801
|
+
static void apply_default_settings(Query& q, CHClient* c) {
|
|
802
|
+
for (const auto& kv : c->default_settings) {
|
|
803
|
+
q.SetSetting(kv.first, QuerySettingsField{kv.second, 1});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
768
807
|
// Client.new(host:, port:, database:, user:, password:)
|
|
769
808
|
static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
770
809
|
VALUE kwargs = Qnil;
|
|
@@ -778,12 +817,35 @@ static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
|
778
817
|
std::string password = kwarg_str(kwargs, "password", "");
|
|
779
818
|
CompressionMethod compression = kwarg_compression(kwargs);
|
|
780
819
|
|
|
820
|
+
// Connection-resilience defaults. Long-lived pooled connections get
|
|
821
|
+
// silently closed by the server (idle_connection_timeout) or an LB, so
|
|
822
|
+
// the next use of a checked-out client would otherwise hit recv()==0 and
|
|
823
|
+
// raise ConnectionError("closed: ..."). ping_before_query makes the driver
|
|
824
|
+
// ping first and transparently reconnect on a dead socket; tcp_keepalive
|
|
825
|
+
// keeps idle sockets healthy at the OS level. retry_timeout is the backoff
|
|
826
|
+
// before each reconnect attempt — kept low (1s) since a pooled reconnect
|
|
827
|
+
// to a live server should be quick and we don't want to stall queries.
|
|
828
|
+
bool ping_before_query = kwarg_bool(kwargs, "ping_before_query", true);
|
|
829
|
+
bool tcp_keepalive = kwarg_bool(kwargs, "tcp_keepalive", true);
|
|
830
|
+
unsigned int retry_timeout = kwarg_uint(kwargs, "retry_timeout", 1);
|
|
831
|
+
|
|
781
832
|
CHClient* c = as_client(self);
|
|
833
|
+
|
|
834
|
+
VALUE settings = rb_hash_lookup2(kwargs, ID2SYM(rb_intern("settings")), Qnil);
|
|
835
|
+
if (!NIL_P(settings)) {
|
|
836
|
+
Check_Type(settings, T_HASH);
|
|
837
|
+
rb_hash_foreach(settings, collect_setting_cb,
|
|
838
|
+
reinterpret_cast<VALUE>(&c->default_settings));
|
|
839
|
+
}
|
|
840
|
+
|
|
782
841
|
try {
|
|
783
842
|
ClientOptions opts;
|
|
784
843
|
opts.SetHost(host).SetPort(port)
|
|
785
844
|
.SetDefaultDatabase(database).SetUser(user).SetPassword(password)
|
|
786
|
-
.SetCompressionMethod(compression)
|
|
845
|
+
.SetCompressionMethod(compression)
|
|
846
|
+
.SetPingBeforeQuery(ping_before_query)
|
|
847
|
+
.TcpKeepAlive(tcp_keepalive)
|
|
848
|
+
.SetRetryTimeout(std::chrono::seconds(retry_timeout));
|
|
787
849
|
c->client = std::make_unique<Client>(opts);
|
|
788
850
|
} catch (const std::exception& e) {
|
|
789
851
|
raise_mapped_ex(e);
|
|
@@ -792,6 +854,9 @@ static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
|
792
854
|
rb_ivar_set(self, rb_intern("@host"), rb_utf8_str_new(host.data(), host.size()));
|
|
793
855
|
rb_ivar_set(self, rb_intern("@port"), UINT2NUM(port));
|
|
794
856
|
rb_ivar_set(self, rb_intern("@database"), rb_utf8_str_new(database.data(), database.size()));
|
|
857
|
+
rb_ivar_set(self, rb_intern("@ping_before_query"), ping_before_query ? Qtrue : Qfalse);
|
|
858
|
+
rb_ivar_set(self, rb_intern("@tcp_keepalive"), tcp_keepalive ? Qtrue : Qfalse);
|
|
859
|
+
rb_ivar_set(self, rb_intern("@retry_timeout"), UINT2NUM(retry_timeout));
|
|
795
860
|
|
|
796
861
|
VALUE logger = rb_hash_lookup2(kwargs, ID2SYM(rb_intern("logger")), Qnil);
|
|
797
862
|
rb_ivar_set(self, rb_intern("@logger"), logger);
|
|
@@ -807,6 +872,7 @@ struct ExecuteNoGVL {
|
|
|
807
872
|
Client* client;
|
|
808
873
|
const Query* query;
|
|
809
874
|
std::exception_ptr err;
|
|
875
|
+
bool cancelled;
|
|
810
876
|
};
|
|
811
877
|
} // namespace
|
|
812
878
|
|
|
@@ -820,11 +886,16 @@ static void* execute_no_gvl(void* data) {
|
|
|
820
886
|
return nullptr;
|
|
821
887
|
}
|
|
822
888
|
|
|
889
|
+
// Unblock functions run on the *interrupting* thread while the blocked thread
|
|
890
|
+
// is still inside Client::Execute(). CancelInFlight() only shuts the socket
|
|
891
|
+
// down; ResetConnection() would free the very streams that thread is reading
|
|
892
|
+
// (Thread#kill from Parallel.in_threads, Timeout, Sidekiq shutdown), which
|
|
893
|
+
// reads back as a garbage packet type and then segfaults. The blocked call
|
|
894
|
+
// returns EOF, raises, and the pool discards the client.
|
|
823
895
|
static void execute_unblock(void* data) {
|
|
824
|
-
// The only safe abort clickhouse-cpp exposes is tearing the connection.
|
|
825
|
-
// On interrupt we kill the socket; the pool will discard this client.
|
|
826
896
|
auto* a = static_cast<ExecuteNoGVL*>(data);
|
|
827
|
-
|
|
897
|
+
a->cancelled = true;
|
|
898
|
+
try { a->client->CancelInFlight(); } catch (...) {}
|
|
828
899
|
}
|
|
829
900
|
|
|
830
901
|
static VALUE ch_client_execute(int argc, VALUE* argv, VALUE self) {
|
|
@@ -835,15 +906,23 @@ static VALUE ch_client_execute(int argc, VALUE* argv, VALUE self) {
|
|
|
835
906
|
if (!c->client) rb_raise(err_connection, "clickhouse-native: client is closed");
|
|
836
907
|
|
|
837
908
|
Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
|
|
909
|
+
apply_default_settings(q, c);
|
|
838
910
|
apply_settings(q, kwargs);
|
|
839
911
|
|
|
840
|
-
ExecuteNoGVL args{c->client.get(), &q, nullptr};
|
|
912
|
+
ExecuteNoGVL args{c->client.get(), &q, nullptr, false};
|
|
841
913
|
rb_thread_call_without_gvl(execute_no_gvl, &args, execute_unblock, &args);
|
|
842
914
|
if (args.err) {
|
|
843
915
|
// clickhouse-cpp may leave the read stream partially consumed when the
|
|
844
916
|
// server exception or an unsupported-type error is thrown mid-block.
|
|
845
917
|
// Reset so the next call on this Client starts from a clean protocol.
|
|
846
|
-
|
|
918
|
+
// Not after a cancel: that socket is already shut down and the caller
|
|
919
|
+
// discards the client, so the connect+handshake buys nothing. A
|
|
920
|
+
// Thread#kill or Timeout never gets here at all — step 5 of
|
|
921
|
+
// rb_thread_call_without_gvl delivers the interrupt before it returns —
|
|
922
|
+
// so this covers what is left: an unblock function that fires without a
|
|
923
|
+
// longjmp behind it, from a trap handler that does not raise or an
|
|
924
|
+
// interrupt Thread.handle_interrupt has deferred.
|
|
925
|
+
if (!args.cancelled) { try { c->client->ResetConnection(); } catch (...) {} }
|
|
847
926
|
try { std::rethrow_exception(args.err); }
|
|
848
927
|
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
849
928
|
}
|
|
@@ -866,6 +945,7 @@ static VALUE ch_client_query(int argc, VALUE* argv, VALUE self) {
|
|
|
866
945
|
try {
|
|
867
946
|
std::vector<ID> col_ids;
|
|
868
947
|
Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
|
|
948
|
+
apply_default_settings(q, c);
|
|
869
949
|
apply_read_settings(q, kwargs);
|
|
870
950
|
q.OnData([&](const Block& block) {
|
|
871
951
|
size_t ncols = block.GetColumnCount();
|
|
@@ -911,6 +991,7 @@ static VALUE ch_client_query_value(int argc, VALUE* argv, VALUE self) {
|
|
|
911
991
|
VALUE out = Qnil;
|
|
912
992
|
bool seen = false;
|
|
913
993
|
Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
|
|
994
|
+
apply_default_settings(q, c);
|
|
914
995
|
apply_read_settings(q, kwargs);
|
|
915
996
|
q.OnData([&](const Block& block) {
|
|
916
997
|
if (seen) return;
|
|
@@ -936,14 +1017,16 @@ struct InsertNoGVL {
|
|
|
936
1017
|
Client* client;
|
|
937
1018
|
const std::string* table;
|
|
938
1019
|
const Block* block;
|
|
1020
|
+
const std::vector<std::pair<std::string, std::string>>* settings;
|
|
939
1021
|
std::exception_ptr err;
|
|
1022
|
+
bool cancelled;
|
|
940
1023
|
};
|
|
941
1024
|
} // namespace
|
|
942
1025
|
|
|
943
1026
|
static void* insert_no_gvl(void* data) {
|
|
944
1027
|
auto* a = static_cast<InsertNoGVL*>(data);
|
|
945
1028
|
try {
|
|
946
|
-
a->client->Insert(*a->table, *a->block);
|
|
1029
|
+
a->client->Insert(*a->table, *a->block, *a->settings);
|
|
947
1030
|
} catch (...) {
|
|
948
1031
|
a->err = std::current_exception();
|
|
949
1032
|
}
|
|
@@ -952,7 +1035,8 @@ static void* insert_no_gvl(void* data) {
|
|
|
952
1035
|
|
|
953
1036
|
static void insert_unblock(void* data) {
|
|
954
1037
|
auto* a = static_cast<InsertNoGVL*>(data);
|
|
955
|
-
|
|
1038
|
+
a->cancelled = true;
|
|
1039
|
+
try { a->client->CancelInFlight(); } catch (...) {}
|
|
956
1040
|
}
|
|
957
1041
|
|
|
958
1042
|
static VALUE ch_client_insert_block(VALUE self, VALUE rb_table, VALUE rb_columns, VALUE rb_rows) {
|
|
@@ -1009,10 +1093,10 @@ static VALUE ch_client_insert_block(VALUE self, VALUE rb_table, VALUE rb_columns
|
|
|
1009
1093
|
block.AppendColumn(names[i], cols[i]);
|
|
1010
1094
|
}
|
|
1011
1095
|
|
|
1012
|
-
InsertNoGVL args{c->client.get(), &table, &block, nullptr};
|
|
1096
|
+
InsertNoGVL args{c->client.get(), &table, &block, &c->default_settings, nullptr, false};
|
|
1013
1097
|
rb_thread_call_without_gvl(insert_no_gvl, &args, insert_unblock, &args);
|
|
1014
1098
|
if (args.err) {
|
|
1015
|
-
try { c->client->ResetConnection(); } catch (...) {}
|
|
1099
|
+
if (!args.cancelled) { try { c->client->ResetConnection(); } catch (...) {} }
|
|
1016
1100
|
try { std::rethrow_exception(args.err); }
|
|
1017
1101
|
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
1018
1102
|
}
|
|
@@ -1045,6 +1129,7 @@ struct QueryEachNoGVL {
|
|
|
1045
1129
|
const Query* query;
|
|
1046
1130
|
QueryEachState* state;
|
|
1047
1131
|
std::exception_ptr err;
|
|
1132
|
+
bool cancelled;
|
|
1048
1133
|
};
|
|
1049
1134
|
} // namespace
|
|
1050
1135
|
|
|
@@ -1097,7 +1182,8 @@ static void* query_each_no_gvl(void* data) {
|
|
|
1097
1182
|
static void query_each_unblock(void* data) {
|
|
1098
1183
|
auto* a = static_cast<QueryEachNoGVL*>(data);
|
|
1099
1184
|
a->state->aborted = true;
|
|
1100
|
-
|
|
1185
|
+
a->cancelled = true;
|
|
1186
|
+
try { a->client->CancelInFlight(); } catch (...) {}
|
|
1101
1187
|
}
|
|
1102
1188
|
|
|
1103
1189
|
static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
|
|
@@ -1110,6 +1196,7 @@ static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
|
|
|
1110
1196
|
|
|
1111
1197
|
QueryEachState state{rb_block_proc(), {}, 0, false};
|
|
1112
1198
|
Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
|
|
1199
|
+
apply_default_settings(q, c);
|
|
1113
1200
|
apply_read_settings(q, kwargs);
|
|
1114
1201
|
q.OnDataCancelable([&state](const Block& block) -> bool {
|
|
1115
1202
|
if (state.aborted) return false;
|
|
@@ -1117,12 +1204,12 @@ static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
|
|
|
1117
1204
|
rb_thread_call_with_gvl(with_gvl_yield, &ya);
|
|
1118
1205
|
return !state.aborted;
|
|
1119
1206
|
});
|
|
1120
|
-
QueryEachNoGVL args{c->client.get(), &q, &state, nullptr};
|
|
1207
|
+
QueryEachNoGVL args{c->client.get(), &q, &state, nullptr, false};
|
|
1121
1208
|
|
|
1122
1209
|
rb_thread_call_without_gvl(query_each_no_gvl, &args, query_each_unblock, &args);
|
|
1123
1210
|
|
|
1124
1211
|
if (args.err) {
|
|
1125
|
-
try { c->client->ResetConnection(); } catch (...) {}
|
|
1212
|
+
if (!args.cancelled) { try { c->client->ResetConnection(); } catch (...) {} }
|
|
1126
1213
|
if (state.exc_tag) rb_jump_tag(state.exc_tag);
|
|
1127
1214
|
try { std::rethrow_exception(args.err); }
|
|
1128
1215
|
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
diff --git a/clickhouse/client.cpp b/clickhouse/client.cpp
|
|
2
|
+
index 34a733d..c9732f4 100644
|
|
3
|
+
--- a/clickhouse/client.cpp
|
|
4
|
+
+++ b/clickhouse/client.cpp
|
|
5
|
+
@@ -211,7 +211,8 @@ public:
|
|
6
|
+
|
|
7
|
+
bool IsSelecting() const { return state_ == State::Selecting; }
|
|
8
|
+
|
|
9
|
+
- void Insert(const std::string& table_name, const std::string& query_id, const Block& block);
|
|
10
|
+
+ void Insert(const std::string& table_name, const std::string& query_id, const Block& block,
|
|
11
|
+
+ const std::vector<std::pair<std::string, std::string>>& settings = {});
|
|
12
|
+
|
|
13
|
+
Block BeginInsert(Query query);
|
|
14
|
+
|
|
15
|
+
@@ -485,7 +486,8 @@ std::string NameToQueryString(const std::string &input)
|
|
16
|
+
return output;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
-void Client::Impl::Insert(const std::string& table_name, const std::string& query_id, const Block& block) {
|
|
20
|
+
+void Client::Impl::Insert(const std::string& table_name, const std::string& query_id, const Block& block,
|
|
21
|
+
+ const std::vector<std::pair<std::string, std::string>>& settings) {
|
|
22
|
+
if (state_ == State::Inserting) {
|
|
23
|
+
throw ValidationError("cannot execute query while inserting, use SendInsertData instead");
|
|
24
|
+
}
|
|
25
|
+
@@ -511,6 +513,9 @@ void Client::Impl::Insert(const std::string& table_name, const std::string& quer
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Query query("INSERT INTO " + table_name + " ( " + fields_section.str() + " ) VALUES", query_id);
|
|
29
|
+
+ for (const auto& kv : settings) {
|
|
30
|
+
+ query.SetSetting(kv.first, QuerySettingsField{kv.second, 1});
|
|
31
|
+
+ }
|
|
32
|
+
SendQuery(query);
|
|
33
|
+
|
|
34
|
+
// Wait for a data packet and return
|
|
35
|
+
@@ -1365,12 +1370,14 @@ bool Client::IsSelecting() const
|
|
36
|
+
return impl_->IsSelecting();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
-void Client::Insert(const std::string& table_name, const Block& block) {
|
|
40
|
+
- impl_->Insert(table_name, Query::default_query_id, block);
|
|
41
|
+
+void Client::Insert(const std::string& table_name, const Block& block,
|
|
42
|
+
+ const std::vector<std::pair<std::string, std::string>>& settings) {
|
|
43
|
+
+ impl_->Insert(table_name, Query::default_query_id, block, settings);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
-void Client::Insert(const std::string& table_name, const std::string& query_id, const Block& block) {
|
|
47
|
+
- impl_->Insert(table_name, query_id, block);
|
|
48
|
+
+void Client::Insert(const std::string& table_name, const std::string& query_id, const Block& block,
|
|
49
|
+
+ const std::vector<std::pair<std::string, std::string>>& settings) {
|
|
50
|
+
+ impl_->Insert(table_name, query_id, block, settings);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Block Client::BeginInsert(const std::string& query) {
|
|
54
|
+
diff --git a/clickhouse/client.h b/clickhouse/client.h
|
|
55
|
+
index a55fd74..6c6ff53 100644
|
|
56
|
+
--- a/clickhouse/client.h
|
|
57
|
+
+++ b/clickhouse/client.h
|
|
58
|
+
@@ -23,6 +23,8 @@
|
|
59
|
+
#include "columns/bool.h"
|
|
60
|
+
|
|
61
|
+
#include <chrono>
|
|
62
|
+
+#include <utility>
|
|
63
|
+
+#include <vector>
|
|
64
|
+
#include <cstdint>
|
|
65
|
+
#include <memory>
|
|
66
|
+
#include <ostream>
|
|
67
|
+
@@ -298,8 +300,13 @@ public:
|
|
68
|
+
bool IsSelecting() const;
|
|
69
|
+
|
|
70
|
+
/// Intends for insert block of data into a table \p table_name.
|
|
71
|
+
- void Insert(const std::string& table_name, const Block& block);
|
|
72
|
+
- void Insert(const std::string& table_name, const std::string& query_id, const Block& block);
|
|
73
|
+
+ /// \p settings are applied to the generated INSERT query as per-query
|
|
74
|
+
+ /// settings (patched-in for clickhouse-native: lets a pooled client carry
|
|
75
|
+
+ /// its session settings into inserts without a session-level SET).
|
|
76
|
+
+ void Insert(const std::string& table_name, const Block& block,
|
|
77
|
+
+ const std::vector<std::pair<std::string, std::string>>& settings = {});
|
|
78
|
+
+ void Insert(const std::string& table_name, const std::string& query_id, const Block& block,
|
|
79
|
+
+ const std::vector<std::pair<std::string, std::string>>& settings = {});
|
|
80
|
+
|
|
81
|
+
/// Start an \p INSERT statement, insert batches of data, then finish the insert.
|
|
82
|
+
Block BeginInsert(const std::string& query);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
diff --git a/clickhouse/base/socket.cpp b/clickhouse/base/socket.cpp
|
|
2
|
+
index 3bb1aa5..f188d3f 100644
|
|
3
|
+
--- a/clickhouse/base/socket.cpp
|
|
4
|
+
+++ b/clickhouse/base/socket.cpp
|
|
5
|
+
@@ -345,6 +345,19 @@ Socket::~Socket() {
|
|
6
|
+
Close();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
+void Socket::Cancel() {
|
|
10
|
+
+ if (handle_ == INVALID_SOCKET) {
|
|
11
|
+
+ return;
|
|
12
|
+
+ }
|
|
13
|
+
+ // shutdown(), not close(): the descriptor stays valid, so a concurrent
|
|
14
|
+
+ // reader returns EOF instead of racing a reused fd number.
|
|
15
|
+
+#if defined(_win_)
|
|
16
|
+
+ shutdown(handle_, SD_BOTH);
|
|
17
|
+
+#else
|
|
18
|
+
+ ::shutdown(handle_, SHUT_RDWR);
|
|
19
|
+
+#endif
|
|
20
|
+
+}
|
|
21
|
+
+
|
|
22
|
+
void Socket::Close() {
|
|
23
|
+
CloseSocket(handle_);
|
|
24
|
+
handle_ = INVALID_SOCKET;
|
|
25
|
+
diff --git a/clickhouse/base/socket.h b/clickhouse/base/socket.h
|
|
26
|
+
index 9bd9ca3..0234b1e 100644
|
|
27
|
+
--- a/clickhouse/base/socket.h
|
|
28
|
+
+++ b/clickhouse/base/socket.h
|
|
29
|
+
@@ -80,6 +80,12 @@ public:
|
|
30
|
+
|
|
31
|
+
virtual std::unique_ptr<InputStream> makeInputStream() const = 0;
|
|
32
|
+
virtual std::unique_ptr<OutputStream> makeOutputStream() const = 0;
|
|
33
|
+
+
|
|
34
|
+
+ /// Unblock a thread parked in recv()/send() on this socket. Unlike
|
|
35
|
+
+ /// closing or replacing the socket it destroys nothing, so it is safe
|
|
36
|
+
+ /// to call from another thread while the owner is mid-read: the owner
|
|
37
|
+
+ /// sees EOF and unwinds through its own frames.
|
|
38
|
+
+ virtual void Cancel() {}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@@ -123,6 +129,8 @@ public:
|
|
43
|
+
std::unique_ptr<InputStream> makeInputStream() const override;
|
|
44
|
+
std::unique_ptr<OutputStream> makeOutputStream() const override;
|
|
45
|
+
|
|
46
|
+
+ void Cancel() override;
|
|
47
|
+
+
|
|
48
|
+
protected:
|
|
49
|
+
Socket(const Socket&) = delete;
|
|
50
|
+
Socket& operator = (const Socket&) = delete;
|
|
51
|
+
diff --git a/clickhouse/client.cpp b/clickhouse/client.cpp
|
|
52
|
+
index c9732f4..88c9b84 100644
|
|
53
|
+
--- a/clickhouse/client.cpp
|
|
54
|
+
+++ b/clickhouse/client.cpp
|
|
55
|
+
@@ -9,6 +9,7 @@
|
|
56
|
+
#include "columns/factory.h"
|
|
57
|
+
|
|
58
|
+
#include <cassert>
|
|
59
|
+
+#include <mutex>
|
|
60
|
+
#include <optional>
|
|
61
|
+
#include <sstream>
|
|
62
|
+
#include <system_error>
|
|
63
|
+
@@ -226,6 +227,8 @@ public:
|
|
64
|
+
|
|
65
|
+
void ResetConnection();
|
|
66
|
+
|
|
67
|
+
+ void CancelInFlight();
|
|
68
|
+
+
|
|
69
|
+
void ResetConnectionEndpoint();
|
|
70
|
+
|
|
71
|
+
const ServerInfo& GetServerInfo() const;
|
|
72
|
+
@@ -313,6 +316,11 @@ private:
|
|
73
|
+
|
|
74
|
+
std::unique_ptr<SocketFactory> socket_factory_;
|
|
75
|
+
|
|
76
|
+
+ /// Guards socket_ against CancelInFlight() on another thread. Covers the
|
|
77
|
+
+ /// swap in ResetConnection() and the displaced socket's destructor, so a
|
|
78
|
+
+ /// canceller can never shutdown() an fd that is closing or already reissued.
|
|
79
|
+
+ std::mutex socket_mutex_;
|
|
80
|
+
+
|
|
81
|
+
std::unique_ptr<InputStream> input_;
|
|
82
|
+
std::unique_ptr<OutputStream> output_;
|
|
83
|
+
std::unique_ptr<SocketBase> socket_;
|
|
84
|
+
@@ -610,8 +618,24 @@ void Client::Impl::Ping() {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
+void Client::Impl::CancelInFlight() {
|
|
89
|
+
+ std::lock_guard<std::mutex> lock(socket_mutex_);
|
|
90
|
+
+ if (socket_) {
|
|
91
|
+
+ socket_->Cancel();
|
|
92
|
+
+ }
|
|
93
|
+
+}
|
|
94
|
+
+
|
|
95
|
+
void Client::Impl::ResetConnection() {
|
|
96
|
+
- InitializeStreams(socket_factory_->connect(options_, current_endpoint_.value()));
|
|
97
|
+
+ auto fresh = socket_factory_->connect(options_, current_endpoint_.value());
|
|
98
|
+
+ {
|
|
99
|
+
+ // InitializeStreams swaps, so `fresh` comes back holding the *old*
|
|
100
|
+
+ // socket; reset it here so its destructor runs under the lock too.
|
|
101
|
+
+ // Left to itself it would die in this function's full-expression,
|
|
102
|
+
+ // outside any lock, which is the window CancelInFlight() must not hit.
|
|
103
|
+
+ std::lock_guard<std::mutex> lock(socket_mutex_);
|
|
104
|
+
+ InitializeStreams(std::move(fresh));
|
|
105
|
+
+ fresh.reset();
|
|
106
|
+
+ }
|
|
107
|
+
state_ = State::Idle;
|
|
108
|
+
|
|
109
|
+
if (!Handshake()) {
|
|
110
|
+
@@ -1408,6 +1432,10 @@ void Client::ResetConnection() {
|
|
111
|
+
impl_->ResetConnection();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
+void Client::CancelInFlight() {
|
|
115
|
+
+ impl_->CancelInFlight();
|
|
116
|
+
+}
|
|
117
|
+
+
|
|
118
|
+
void Client::ResetConnectionEndpoint() {
|
|
119
|
+
impl_->ResetConnectionEndpoint();
|
|
120
|
+
}
|
|
121
|
+
diff --git a/clickhouse/client.h b/clickhouse/client.h
|
|
122
|
+
index 6c6ff53..27be58c 100644
|
|
123
|
+
--- a/clickhouse/client.h
|
|
124
|
+
+++ b/clickhouse/client.h
|
|
125
|
+
@@ -326,6 +326,11 @@ public:
|
|
126
|
+
/// Reset connection with initial params.
|
|
127
|
+
void ResetConnection();
|
|
128
|
+
|
|
129
|
+
+ /// Unblock a thread parked in an Execute()/Insert() socket read.
|
|
130
|
+
+ /// Destroys nothing, so unlike ResetConnection() it is safe to call
|
|
131
|
+
+ /// from another thread while a query is still in flight.
|
|
132
|
+
+ void CancelInFlight();
|
|
133
|
+
+
|
|
134
|
+
const ServerInfo& GetServerInfo() const;
|
|
135
|
+
|
|
136
|
+
/// Get current connected endpoint.
|
|
@@ -8,44 +8,54 @@ module ClickhouseNative
|
|
|
8
8
|
|
|
9
9
|
def initialize(host:, port:, database: "default", user: "default", password: "",
|
|
10
10
|
compression: :none, logger: nil, settings: {},
|
|
11
|
-
pool_size: 5, pool_timeout: 5
|
|
11
|
+
pool_size: 5, pool_timeout: 5,
|
|
12
|
+
ping_before_query: true, tcp_keepalive: true, retry_timeout: 1)
|
|
12
13
|
@host = host
|
|
13
14
|
@port = port
|
|
14
15
|
@database = database
|
|
15
|
-
client_kwargs = {
|
|
16
|
-
|
|
16
|
+
client_kwargs = {
|
|
17
|
+
host:, port:, database:, user:, password:, compression:, logger:, settings:,
|
|
18
|
+
ping_before_query:, tcp_keepalive:, retry_timeout:
|
|
19
|
+
}
|
|
17
20
|
@pool = ConnectionPool.new(size: pool_size, timeout: pool_timeout) do
|
|
18
|
-
|
|
19
|
-
client.execute(@set_sql) if @set_sql
|
|
20
|
-
client
|
|
21
|
+
Client.new(**client_kwargs)
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
# On
|
|
25
|
-
#
|
|
25
|
+
# On an aborted operation, discard the client rather than reuse it: the
|
|
26
|
+
# socket is left in an unknown state. The C++ binding issues
|
|
26
27
|
# ResetConnection, but a subsequent send can still surface buffered
|
|
27
28
|
# protocol errors from the prior aborted operation — those get
|
|
28
|
-
# attributed to whatever SQL we tried next
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# debugging that.
|
|
29
|
+
# attributed to whatever SQL we tried next, producing misleading log
|
|
30
|
+
# lines and re-raises in unrelated code. A fresh socket + handshake is
|
|
31
|
+
# cheap relative to debugging that.
|
|
32
32
|
#
|
|
33
33
|
# ConnectionError gets one automatic retry: pooled connections that
|
|
34
34
|
# have been idle long enough for the server / an LB to FIN them
|
|
35
|
-
# surface as "closed" on the very next recv (errno
|
|
36
|
-
# "closed: Success"
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
35
|
+
# surface as "closed" on the very next recv (errno is whatever stale
|
|
36
|
+
# value was left in the thread — "closed: Success", "closed: Operation
|
|
37
|
+
# now in progress", etc. all mean the same recv()==0). Discarding and
|
|
38
|
+
# re-checking out lands a fresh socket and the operation succeeds. The
|
|
39
|
+
# retry only triggers when the dead-connection error fired before any
|
|
40
|
+
# data was sent, so write operations don't risk double-execution.
|
|
41
|
+
#
|
|
42
|
+
# This is the backstop: with ping_before_query on (the default) the
|
|
43
|
+
# driver already pings and transparently reconnects a dead socket
|
|
44
|
+
# before running the query, so most stale connections never surface as
|
|
45
|
+
# a ConnectionError here at all. This retry still covers the residual
|
|
46
|
+
# race (socket dies between the ping and the query).
|
|
47
|
+
#
|
|
48
|
+
# A bare `rescue` is not enough to spot an abandoned query. Timeout and
|
|
49
|
+
# Sidekiq shutdown raise off Exception rather than StandardError, and
|
|
50
|
+
# Thread#kill raises nothing at all — Parallel.in_threads kills every
|
|
51
|
+
# sibling worker the moment one of them fails. Miss those and the
|
|
52
|
+
# connection goes back in with the server still streaming a response at
|
|
53
|
+
# it; the next checkout reads that leftover as its own, which surfaces
|
|
54
|
+
# as a bogus packet type far from here.
|
|
40
55
|
def with
|
|
41
56
|
attempts = 0
|
|
42
57
|
begin
|
|
43
|
-
@pool.with
|
|
44
|
-
yield client
|
|
45
|
-
rescue
|
|
46
|
-
@pool.discard_current_connection(&:close)
|
|
47
|
-
raise
|
|
48
|
-
end
|
|
58
|
+
@pool.with { |client| discard_unless_clean { yield client } }
|
|
49
59
|
rescue ConnectionError
|
|
50
60
|
attempts += 1
|
|
51
61
|
retry if attempts == 1
|
|
@@ -87,18 +97,27 @@ module ClickhouseNative
|
|
|
87
97
|
|
|
88
98
|
private
|
|
89
99
|
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
# Keep the client only when the block either finished or left of its own
|
|
101
|
+
# accord. A `break` out of a streaming read is deliberate, and the binding
|
|
102
|
+
# has already reconnected the client on its way out (query_each resets the
|
|
103
|
+
# connection before rb_jump_tag), so it is good to reuse — discarding would
|
|
104
|
+
# buy a second connect on top of the one already paid. An exception is an
|
|
105
|
+
# abort, and so is Thread#kill: it raises nothing, so no rescue ever sees
|
|
106
|
+
# it, and its only trace is an "aborting" thread while ensure runs.
|
|
107
|
+
def discard_unless_clean
|
|
108
|
+
finished = false
|
|
109
|
+
begin
|
|
110
|
+
result = yield
|
|
111
|
+
finished = true
|
|
112
|
+
result
|
|
113
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
114
|
+
@pool.discard_current_connection(&:close)
|
|
115
|
+
raise
|
|
116
|
+
ensure
|
|
117
|
+
if !finished && Thread.current.status == "aborting"
|
|
118
|
+
@pool.discard_current_connection(&:close)
|
|
119
|
+
end
|
|
100
120
|
end
|
|
101
|
-
"SET #{parts.join(', ')}"
|
|
102
121
|
end
|
|
103
122
|
end
|
|
104
123
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clickhouse-native
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yuri Smirnov
|
|
@@ -35,6 +35,8 @@ files:
|
|
|
35
35
|
- ext/clickhouse_native/client.cpp
|
|
36
36
|
- ext/clickhouse_native/extconf.rb
|
|
37
37
|
- ext/clickhouse_native/patches/0001-preserve-declared-column-type.patch
|
|
38
|
+
- ext/clickhouse_native/patches/0002-carry-settings-into-insert.patch
|
|
39
|
+
- ext/clickhouse_native/patches/0003-cancelable-socket.patch
|
|
38
40
|
- ext/clickhouse_native/vendor/clickhouse-cpp/.clang-format
|
|
39
41
|
- ext/clickhouse_native/vendor/clickhouse-cpp/.git
|
|
40
42
|
- ext/clickhouse_native/vendor/clickhouse-cpp/.gitattributes
|
|
@@ -323,7 +325,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
323
325
|
- !ruby/object:Gem::Version
|
|
324
326
|
version: '0'
|
|
325
327
|
requirements: []
|
|
326
|
-
rubygems_version: 4.0.
|
|
328
|
+
rubygems_version: 4.0.16
|
|
327
329
|
specification_version: 4
|
|
328
330
|
summary: ClickHouse Ruby driver over the native TCP protocol
|
|
329
331
|
test_files: []
|