clickhouse-native 0.11.1 → 0.12.0
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 +255 -84
- data/lib/clickhouse_native/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4bbe836a1758637fb9dd0a157505037b8bc45d42d253d261b3a349cecaf9716
|
|
4
|
+
data.tar.gz: 7c0cd5bbea6944c2c0672780088d4342cdf207227bce2c52f388986e9b26bf15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3abc7862bbf8125ca8168d5a28337ebdf1e11c1f8a2965fff597ccaa4d27eaea70f2116d93b54d2822f6f6fd3fca237e8a864d1cf30e91c0195bb4e730f22be
|
|
7
|
+
data.tar.gz: 101b7ecc586641e3c0df0f14f0dbd977a450d578e029fbc220f1b86218d4e84ef4a22d5c29eaec1c1e463db96e5fdc9a2b26fb5efdfa0cf1c6780a40aee1d047
|
|
@@ -804,6 +804,64 @@ static void apply_default_settings(Query& q, CHClient* c) {
|
|
|
804
804
|
}
|
|
805
805
|
}
|
|
806
806
|
|
|
807
|
+
// ------------------------------------------------------------------
|
|
808
|
+
// Connect / reconnect, off the GVL
|
|
809
|
+
// ------------------------------------------------------------------
|
|
810
|
+
|
|
811
|
+
namespace {
|
|
812
|
+
struct ResetNoGVL {
|
|
813
|
+
Client* client;
|
|
814
|
+
std::exception_ptr err;
|
|
815
|
+
bool ran;
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
struct ConnectNoGVL {
|
|
819
|
+
const ClientOptions* opts;
|
|
820
|
+
std::unique_ptr<Client>* out;
|
|
821
|
+
std::exception_ptr err;
|
|
822
|
+
};
|
|
823
|
+
} // namespace
|
|
824
|
+
|
|
825
|
+
static void* connect_no_gvl(void* data) {
|
|
826
|
+
auto* a = static_cast<ConnectNoGVL*>(data);
|
|
827
|
+
try { *a->out = std::make_unique<Client>(*a->opts); }
|
|
828
|
+
catch (...) { a->err = std::current_exception(); }
|
|
829
|
+
return nullptr;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
static void* reset_no_gvl(void* data) {
|
|
833
|
+
auto* a = static_cast<ResetNoGVL*>(data);
|
|
834
|
+
a->ran = true;
|
|
835
|
+
try { a->client->ResetConnection(); } catch (...) { a->err = std::current_exception(); }
|
|
836
|
+
return nullptr;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// ResetConnection() opens a fresh socket and re-handshakes, so it blocks on the
|
|
840
|
+
// network exactly like a query does. Every error path calls it, which is when
|
|
841
|
+
// the server is least likely to answer promptly - the worst moment to be
|
|
842
|
+
// holding the GVL. No unblock function: there is no in-flight query to cancel,
|
|
843
|
+
// and the half-built connection is this thread's.
|
|
844
|
+
//
|
|
845
|
+
// _gvl2, not the ordinary one: every caller is already reporting some other
|
|
846
|
+
// error, and the ordinary variant checks pending interrupts as it retakes the
|
|
847
|
+
// GVL and can longjmp out. That would jump between an rb_protect tag and the
|
|
848
|
+
// rb_jump_tag meant to re-raise it, or out of the active catch handler in
|
|
849
|
+
// insert_block, and would lose the error the caller came here to report.
|
|
850
|
+
//
|
|
851
|
+
// _gvl2 declines to run `func` at all when an interrupt is already pending,
|
|
852
|
+
// though, so the reset is not guaranteed - and one caller needs it to happen:
|
|
853
|
+
// the `break` out of query_each keeps its client, on the understanding that
|
|
854
|
+
// the reset here left it clean (see Pool#discard_unless_clean). Falling back
|
|
855
|
+
// to the plain call covers that. Holding the GVL for a reconnect is the lesser
|
|
856
|
+
// evil against handing back a connection with a reply still draining into it.
|
|
857
|
+
// Failures stay swallowed either way: the pool discards on every other path.
|
|
858
|
+
static void reset_connection_no_gvl(Client* client) {
|
|
859
|
+
if (!client) return;
|
|
860
|
+
ResetNoGVL args{client, nullptr, false};
|
|
861
|
+
rb_thread_call_without_gvl2(reset_no_gvl, &args, nullptr, nullptr);
|
|
862
|
+
if (!args.ran) { try { client->ResetConnection(); } catch (...) {} }
|
|
863
|
+
}
|
|
864
|
+
|
|
807
865
|
// Client.new(host:, port:, database:, user:, password:)
|
|
808
866
|
static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
809
867
|
VALUE kwargs = Qnil;
|
|
@@ -838,7 +896,7 @@ static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
|
838
896
|
reinterpret_cast<VALUE>(&c->default_settings));
|
|
839
897
|
}
|
|
840
898
|
|
|
841
|
-
|
|
899
|
+
{
|
|
842
900
|
ClientOptions opts;
|
|
843
901
|
opts.SetHost(host).SetPort(port)
|
|
844
902
|
.SetDefaultDatabase(database).SetUser(user).SetPassword(password)
|
|
@@ -846,9 +904,17 @@ static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
|
|
|
846
904
|
.SetPingBeforeQuery(ping_before_query)
|
|
847
905
|
.TcpKeepAlive(tcp_keepalive)
|
|
848
906
|
.SetRetryTimeout(std::chrono::seconds(retry_timeout));
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
907
|
+
// The constructor connects and handshakes, and retries for
|
|
908
|
+
// SetRetryTimeout seconds against an unreachable host - long enough to
|
|
909
|
+
// freeze every other thread if it ran under the GVL. No unblock
|
|
910
|
+
// function: there is no in-flight query to cancel, and the socket being
|
|
911
|
+
// built belongs to this thread.
|
|
912
|
+
ConnectNoGVL args{&opts, &c->client, nullptr};
|
|
913
|
+
rb_thread_call_without_gvl(connect_no_gvl, &args, nullptr, nullptr);
|
|
914
|
+
if (args.err) {
|
|
915
|
+
try { std::rethrow_exception(args.err); }
|
|
916
|
+
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
917
|
+
}
|
|
852
918
|
}
|
|
853
919
|
|
|
854
920
|
rb_ivar_set(self, rb_intern("@host"), rb_utf8_str_new(host.data(), host.size()));
|
|
@@ -922,7 +988,7 @@ static VALUE ch_client_execute(int argc, VALUE* argv, VALUE self) {
|
|
|
922
988
|
// so this covers what is left: an unblock function that fires without a
|
|
923
989
|
// longjmp behind it, from a trap handler that does not raise or an
|
|
924
990
|
// interrupt Thread.handle_interrupt has deferred.
|
|
925
|
-
if (!args.cancelled)
|
|
991
|
+
if (!args.cancelled) reset_connection_no_gvl(c->client.get());
|
|
926
992
|
try { std::rethrow_exception(args.err); }
|
|
927
993
|
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
928
994
|
}
|
|
@@ -930,82 +996,171 @@ static VALUE ch_client_execute(int argc, VALUE* argv, VALUE self) {
|
|
|
930
996
|
}
|
|
931
997
|
|
|
932
998
|
// ------------------------------------------------------------------
|
|
933
|
-
// query() —
|
|
934
|
-
//
|
|
999
|
+
// query() / query_value() — buffer the result, releasing the GVL while the
|
|
1000
|
+
// server streams. The data callback is the only part that needs Ruby, so it
|
|
1001
|
+
// takes the GVL back per block (as query_each does) rather than the whole
|
|
1002
|
+
// call holding it: a multi-second read otherwise stops every other thread in
|
|
1003
|
+
// the process, which starves timers and lock heartbeats.
|
|
935
1004
|
// ------------------------------------------------------------------
|
|
936
1005
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1006
|
+
namespace {
|
|
1007
|
+
struct CollectState {
|
|
1008
|
+
VALUE rows; // Qnil for query_value, which keeps `out` instead
|
|
1009
|
+
VALUE out;
|
|
1010
|
+
bool first_only;
|
|
1011
|
+
bool seen;
|
|
1012
|
+
std::vector<ID> col_ids;
|
|
1013
|
+
int exc_tag;
|
|
1014
|
+
std::exception_ptr cpp_err;
|
|
1015
|
+
bool aborted;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
struct CollectBlockArgs {
|
|
1019
|
+
const Block* block;
|
|
1020
|
+
CollectState* state;
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
struct QueryNoGVL {
|
|
1024
|
+
Client* client;
|
|
1025
|
+
const Query* query;
|
|
1026
|
+
CollectState* state;
|
|
1027
|
+
std::exception_ptr err;
|
|
1028
|
+
bool cancelled;
|
|
1029
|
+
};
|
|
1030
|
+
} // namespace
|
|
1031
|
+
|
|
1032
|
+
// The decode throw is caught here, inside the frame rb_protect calls, so it
|
|
1033
|
+
// never unwinds through rb_protect itself - doing that walks a setjmp frame
|
|
1034
|
+
// whose epilogue never runs and takes the interpreter down. Carried out via
|
|
1035
|
+
// state instead; run_buffered_query rethrows it after the no-GVL region.
|
|
1036
|
+
static VALUE collect_rows_body(VALUE arg) {
|
|
1037
|
+
auto* args = reinterpret_cast<CollectBlockArgs*>(arg);
|
|
1038
|
+
const Block& block = *args->block;
|
|
1039
|
+
auto* state = args->state;
|
|
943
1040
|
|
|
944
|
-
VALUE rows = rb_ary_new();
|
|
945
1041
|
try {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
if (col_ids.empty()) {
|
|
955
|
-
col_ids.reserve(ncols);
|
|
956
|
-
for (size_t i = 0; i < ncols; i++) {
|
|
957
|
-
const std::string& name = block.GetColumnName(i);
|
|
958
|
-
col_ids.push_back(rb_intern2(name.data(), name.size()));
|
|
959
|
-
}
|
|
1042
|
+
size_t ncols = block.GetColumnCount();
|
|
1043
|
+
size_t nrows = block.GetRowCount();
|
|
1044
|
+
if (nrows == 0 || ncols == 0) return Qnil;
|
|
1045
|
+
|
|
1046
|
+
if (state->first_only) {
|
|
1047
|
+
if (!state->seen) {
|
|
1048
|
+
state->out = value_at(block[0], 0, block.GetColumnType(0));
|
|
1049
|
+
state->seen = true;
|
|
960
1050
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1051
|
+
return Qnil;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (state->col_ids.empty()) {
|
|
1055
|
+
state->col_ids.reserve(ncols);
|
|
1056
|
+
for (size_t i = 0; i < ncols; i++) {
|
|
1057
|
+
const std::string& name = block.GetColumnName(i);
|
|
1058
|
+
state->col_ids.push_back(rb_intern2(name.data(), name.size()));
|
|
968
1059
|
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1060
|
+
}
|
|
1061
|
+
for (size_t r = 0; r < nrows; r++) {
|
|
1062
|
+
VALUE h = rb_hash_new();
|
|
1063
|
+
for (size_t cc = 0; cc < ncols; cc++) {
|
|
1064
|
+
rb_hash_aset(h, ID2SYM(state->col_ids[cc]),
|
|
1065
|
+
value_at(block[cc], r, block.GetColumnType(cc)));
|
|
1066
|
+
}
|
|
1067
|
+
rb_ary_push(state->rows, h);
|
|
1068
|
+
}
|
|
1069
|
+
} catch (...) {
|
|
1070
|
+
state->cpp_err = std::current_exception();
|
|
1071
|
+
state->aborted = true;
|
|
975
1072
|
}
|
|
976
1073
|
return Qnil;
|
|
977
1074
|
}
|
|
978
1075
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1076
|
+
static void* with_gvl_collect(void* data) {
|
|
1077
|
+
auto* args = static_cast<CollectBlockArgs*>(data);
|
|
1078
|
+
int tag = 0;
|
|
1079
|
+
rb_protect(collect_rows_body, reinterpret_cast<VALUE>(args), &tag);
|
|
1080
|
+
if (tag != 0) {
|
|
1081
|
+
args->state->exc_tag = tag;
|
|
1082
|
+
args->state->aborted = true;
|
|
1083
|
+
}
|
|
1084
|
+
return nullptr;
|
|
1085
|
+
}
|
|
982
1086
|
|
|
983
|
-
static
|
|
1087
|
+
static void* query_no_gvl(void* data) {
|
|
1088
|
+
auto* a = static_cast<QueryNoGVL*>(data);
|
|
1089
|
+
try {
|
|
1090
|
+
a->client->Execute(*a->query);
|
|
1091
|
+
} catch (...) {
|
|
1092
|
+
a->err = std::current_exception();
|
|
1093
|
+
}
|
|
1094
|
+
return nullptr;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// See execute_unblock: cancel the in-flight query, never tear the streams down
|
|
1098
|
+
// from another thread.
|
|
1099
|
+
static void query_unblock(void* data) {
|
|
1100
|
+
auto* a = static_cast<QueryNoGVL*>(data);
|
|
1101
|
+
a->state->aborted = true;
|
|
1102
|
+
a->cancelled = true;
|
|
1103
|
+
try { a->client->CancelInFlight(); } catch (...) {}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
static VALUE run_buffered_query(VALUE self, int argc, VALUE* argv, bool first_only) {
|
|
984
1107
|
VALUE rb_sql, kwargs = Qnil;
|
|
985
1108
|
rb_scan_args(argc, argv, "1:", &rb_sql, &kwargs);
|
|
986
1109
|
Check_Type(rb_sql, T_STRING);
|
|
987
1110
|
CHClient* c = as_client(self);
|
|
988
1111
|
if (!c->client) rb_raise(err_connection, "clickhouse-native: client is closed");
|
|
989
1112
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1113
|
+
// `state` lives on this thread's machine stack, and that is what marks
|
|
1114
|
+
// state.rows and state.out while the GVL is dropped below: the GC scans the
|
|
1115
|
+
// stack conservatively, and the context saved at the release covers the
|
|
1116
|
+
// no-GVL window. Moving CollectState off the stack would remove the only
|
|
1117
|
+
// thing keeping the result alive.
|
|
1118
|
+
CollectState state{first_only ? Qnil : rb_ary_new(), Qnil,
|
|
1119
|
+
first_only, false, {}, 0, nullptr, false};
|
|
1120
|
+
Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
|
|
1121
|
+
apply_default_settings(q, c);
|
|
1122
|
+
apply_read_settings(q, kwargs);
|
|
1123
|
+
q.OnDataCancelable([&state](const Block& block) -> bool {
|
|
1124
|
+
if (state.aborted) return false;
|
|
1125
|
+
// query_value keeps reading to leave the connection clean, but has
|
|
1126
|
+
// nothing left to do once it holds its cell - taking the GVL per block
|
|
1127
|
+
// to discard it costs a round trip each time on a large result.
|
|
1128
|
+
if (state.first_only && state.seen) return true;
|
|
1129
|
+
CollectBlockArgs ca{&block, &state};
|
|
1130
|
+
rb_thread_call_with_gvl(with_gvl_collect, &ca);
|
|
1131
|
+
return !state.aborted;
|
|
1132
|
+
});
|
|
1133
|
+
QueryNoGVL args{c->client.get(), &q, &state, nullptr, false};
|
|
1134
|
+
|
|
1135
|
+
rb_thread_call_without_gvl(query_no_gvl, &args, query_unblock, &args);
|
|
1136
|
+
|
|
1137
|
+
// A decode throw caught in the data callback is the real error; the read
|
|
1138
|
+
// that follows it only reports the abort, so this is checked first.
|
|
1139
|
+
if (state.cpp_err) {
|
|
1140
|
+
reset_connection_no_gvl(c->client.get());
|
|
1141
|
+
try { std::rethrow_exception(state.cpp_err); }
|
|
1142
|
+
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
1007
1143
|
}
|
|
1008
|
-
|
|
1144
|
+
if (args.err) {
|
|
1145
|
+
if (!args.cancelled) reset_connection_no_gvl(c->client.get());
|
|
1146
|
+
if (state.exc_tag) rb_jump_tag(state.exc_tag);
|
|
1147
|
+
try { std::rethrow_exception(args.err); }
|
|
1148
|
+
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
1149
|
+
}
|
|
1150
|
+
if (state.exc_tag) {
|
|
1151
|
+
reset_connection_no_gvl(c->client.get());
|
|
1152
|
+
rb_jump_tag(state.exc_tag);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return first_only ? state.out : state.rows;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
static VALUE ch_client_query(int argc, VALUE* argv, VALUE self) {
|
|
1159
|
+
return run_buffered_query(self, argc, argv, false);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
static VALUE ch_client_query_value(int argc, VALUE* argv, VALUE self) {
|
|
1163
|
+
return run_buffered_query(self, argc, argv, true);
|
|
1009
1164
|
}
|
|
1010
1165
|
|
|
1011
1166
|
// ------------------------------------------------------------------
|
|
@@ -1096,12 +1251,12 @@ static VALUE ch_client_insert_block(VALUE self, VALUE rb_table, VALUE rb_columns
|
|
|
1096
1251
|
InsertNoGVL args{c->client.get(), &table, &block, &c->default_settings, nullptr, false};
|
|
1097
1252
|
rb_thread_call_without_gvl(insert_no_gvl, &args, insert_unblock, &args);
|
|
1098
1253
|
if (args.err) {
|
|
1099
|
-
if (!args.cancelled)
|
|
1254
|
+
if (!args.cancelled) reset_connection_no_gvl(c->client.get());
|
|
1100
1255
|
try { std::rethrow_exception(args.err); }
|
|
1101
1256
|
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
1102
1257
|
}
|
|
1103
1258
|
} catch (const std::exception& e) {
|
|
1104
|
-
|
|
1259
|
+
reset_connection_no_gvl(c->client.get());
|
|
1105
1260
|
raise_mapped_ex(e);
|
|
1106
1261
|
}
|
|
1107
1262
|
return LONG2NUM(nrows);
|
|
@@ -1116,6 +1271,7 @@ struct QueryEachState {
|
|
|
1116
1271
|
VALUE user_proc;
|
|
1117
1272
|
std::vector<ID> col_ids;
|
|
1118
1273
|
int exc_tag;
|
|
1274
|
+
std::exception_ptr cpp_err;
|
|
1119
1275
|
bool aborted;
|
|
1120
1276
|
};
|
|
1121
1277
|
|
|
@@ -1133,27 +1289,35 @@ struct QueryEachNoGVL {
|
|
|
1133
1289
|
};
|
|
1134
1290
|
} // namespace
|
|
1135
1291
|
|
|
1292
|
+
// See collect_rows_body: the decode throw is caught inside this frame so it
|
|
1293
|
+
// never unwinds through rb_protect.
|
|
1136
1294
|
static VALUE yield_rows_body(VALUE arg) {
|
|
1137
1295
|
auto* args = reinterpret_cast<YieldBlockArgs*>(arg);
|
|
1138
1296
|
const Block& block = *args->block;
|
|
1139
1297
|
auto* state = args->state;
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1298
|
+
|
|
1299
|
+
try {
|
|
1300
|
+
size_t ncols = block.GetColumnCount();
|
|
1301
|
+
size_t nrows = block.GetRowCount();
|
|
1302
|
+
if (nrows == 0) return Qnil;
|
|
1303
|
+
if (state->col_ids.empty() && ncols > 0) {
|
|
1304
|
+
state->col_ids.reserve(ncols);
|
|
1305
|
+
for (size_t i = 0; i < ncols; i++) {
|
|
1306
|
+
const std::string& name = block.GetColumnName(i);
|
|
1307
|
+
state->col_ids.push_back(rb_intern2(name.data(), name.size()));
|
|
1308
|
+
}
|
|
1148
1309
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1310
|
+
for (size_t r = 0; r < nrows; r++) {
|
|
1311
|
+
VALUE h = rb_hash_new();
|
|
1312
|
+
for (size_t cc = 0; cc < ncols; cc++) {
|
|
1313
|
+
rb_hash_aset(h, ID2SYM(state->col_ids[cc]),
|
|
1314
|
+
value_at(block[cc], r, block.GetColumnType(cc)));
|
|
1315
|
+
}
|
|
1316
|
+
rb_funcall(state->user_proc, rb_intern("call"), 1, h);
|
|
1155
1317
|
}
|
|
1156
|
-
|
|
1318
|
+
} catch (...) {
|
|
1319
|
+
state->cpp_err = std::current_exception();
|
|
1320
|
+
state->aborted = true;
|
|
1157
1321
|
}
|
|
1158
1322
|
return Qnil;
|
|
1159
1323
|
}
|
|
@@ -1194,7 +1358,7 @@ static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
|
|
|
1194
1358
|
CHClient* c = as_client(self);
|
|
1195
1359
|
if (!c->client) rb_raise(err_connection, "clickhouse-native: client is closed");
|
|
1196
1360
|
|
|
1197
|
-
QueryEachState state{rb_block_proc(), {}, 0, false};
|
|
1361
|
+
QueryEachState state{rb_block_proc(), {}, 0, nullptr, false};
|
|
1198
1362
|
Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
|
|
1199
1363
|
apply_default_settings(q, c);
|
|
1200
1364
|
apply_read_settings(q, kwargs);
|
|
@@ -1208,14 +1372,21 @@ static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
|
|
|
1208
1372
|
|
|
1209
1373
|
rb_thread_call_without_gvl(query_each_no_gvl, &args, query_each_unblock, &args);
|
|
1210
1374
|
|
|
1375
|
+
// A decode throw caught in the data callback is the real error; the read
|
|
1376
|
+
// that follows it only reports the abort, so this is checked first.
|
|
1377
|
+
if (state.cpp_err) {
|
|
1378
|
+
reset_connection_no_gvl(c->client.get());
|
|
1379
|
+
try { std::rethrow_exception(state.cpp_err); }
|
|
1380
|
+
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
1381
|
+
}
|
|
1211
1382
|
if (args.err) {
|
|
1212
|
-
if (!args.cancelled)
|
|
1383
|
+
if (!args.cancelled) reset_connection_no_gvl(c->client.get());
|
|
1213
1384
|
if (state.exc_tag) rb_jump_tag(state.exc_tag);
|
|
1214
1385
|
try { std::rethrow_exception(args.err); }
|
|
1215
1386
|
catch (const std::exception& e) { raise_mapped_ex(e); }
|
|
1216
1387
|
}
|
|
1217
1388
|
if (state.exc_tag) {
|
|
1218
|
-
|
|
1389
|
+
reset_connection_no_gvl(c->client.get());
|
|
1219
1390
|
rb_jump_tag(state.exc_tag);
|
|
1220
1391
|
}
|
|
1221
1392
|
return self;
|
|
@@ -1270,7 +1441,7 @@ static VALUE ch_client_server_version(VALUE self) {
|
|
|
1270
1441
|
static VALUE ch_client_reset_connection(VALUE self) {
|
|
1271
1442
|
CHClient* c = as_client(self);
|
|
1272
1443
|
if (!c->client) return Qnil;
|
|
1273
|
-
|
|
1444
|
+
reset_connection_no_gvl(c->client.get());
|
|
1274
1445
|
return Qtrue;
|
|
1275
1446
|
}
|
|
1276
1447
|
|