clickhouse-native 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3e2cb4cbe829d9afd7e30e0ac29ea828020896c93634852b4baef007348d3b0
4
- data.tar.gz: e5f13079f4de6e64e545f49736a8da6832de954dfe82f1a4d105dd1228680887
3
+ metadata.gz: 984a63d34de19388f777cfe71ecd0a844d22f0b67b89e9fddfe07c5c0ac1cc05
4
+ data.tar.gz: 78f829e40f715aef0264e1419cb9b06b97faab1ac38f33329c5da6ada1dd251b
5
5
  SHA512:
6
- metadata.gz: 9998240b8ca84a4d28ad1fb956c0bf92e1893d4a9ce024fa443eefaa01efb35b657b55d8c2ce92718fd9224a5167fe0201a955f7147ab7499560a331906e2567
7
- data.tar.gz: d097a39e1fa03b051a30fe1fb273a928b7e2e555e05945d32b22a9dd9dc1deaf280193b80ecae146d7d442d513bbfb6e638ba4b63589f5a58171ce4b25ba5b87
6
+ metadata.gz: c579da78d2185108712c92e902217c2e508655533c2e78efc7e36ae358a826f809f41d884684b8fcb0e1b36749bc80be5f23b76c85ebf081423adb615d73928b
7
+ data.tar.gz: 93259646a6f150ed271202e3eaca075e0d1cf8d707aed5ac2ac9b530061de47cf1a65082289718857b367dd5b8d764c73cf270c34d080018ce8caac540d8bab6
@@ -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);
@@ -835,6 +900,7 @@ static VALUE ch_client_execute(int argc, VALUE* argv, VALUE self) {
835
900
  if (!c->client) rb_raise(err_connection, "clickhouse-native: client is closed");
836
901
 
837
902
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
903
+ apply_default_settings(q, c);
838
904
  apply_settings(q, kwargs);
839
905
 
840
906
  ExecuteNoGVL args{c->client.get(), &q, nullptr};
@@ -866,6 +932,7 @@ static VALUE ch_client_query(int argc, VALUE* argv, VALUE self) {
866
932
  try {
867
933
  std::vector<ID> col_ids;
868
934
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
935
+ apply_default_settings(q, c);
869
936
  apply_read_settings(q, kwargs);
870
937
  q.OnData([&](const Block& block) {
871
938
  size_t ncols = block.GetColumnCount();
@@ -911,6 +978,7 @@ static VALUE ch_client_query_value(int argc, VALUE* argv, VALUE self) {
911
978
  VALUE out = Qnil;
912
979
  bool seen = false;
913
980
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
981
+ apply_default_settings(q, c);
914
982
  apply_read_settings(q, kwargs);
915
983
  q.OnData([&](const Block& block) {
916
984
  if (seen) return;
@@ -936,6 +1004,7 @@ struct InsertNoGVL {
936
1004
  Client* client;
937
1005
  const std::string* table;
938
1006
  const Block* block;
1007
+ const std::vector<std::pair<std::string, std::string>>* settings;
939
1008
  std::exception_ptr err;
940
1009
  };
941
1010
  } // namespace
@@ -943,7 +1012,7 @@ struct InsertNoGVL {
943
1012
  static void* insert_no_gvl(void* data) {
944
1013
  auto* a = static_cast<InsertNoGVL*>(data);
945
1014
  try {
946
- a->client->Insert(*a->table, *a->block);
1015
+ a->client->Insert(*a->table, *a->block, *a->settings);
947
1016
  } catch (...) {
948
1017
  a->err = std::current_exception();
949
1018
  }
@@ -1009,7 +1078,7 @@ static VALUE ch_client_insert_block(VALUE self, VALUE rb_table, VALUE rb_columns
1009
1078
  block.AppendColumn(names[i], cols[i]);
1010
1079
  }
1011
1080
 
1012
- InsertNoGVL args{c->client.get(), &table, &block, nullptr};
1081
+ InsertNoGVL args{c->client.get(), &table, &block, &c->default_settings, nullptr};
1013
1082
  rb_thread_call_without_gvl(insert_no_gvl, &args, insert_unblock, &args);
1014
1083
  if (args.err) {
1015
1084
  try { c->client->ResetConnection(); } catch (...) {}
@@ -1110,6 +1179,7 @@ static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
1110
1179
 
1111
1180
  QueryEachState state{rb_block_proc(), {}, 0, false};
1112
1181
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
1182
+ apply_default_settings(q, c);
1113
1183
  apply_read_settings(q, kwargs);
1114
1184
  q.OnDataCancelable([&state](const Block& block) -> bool {
1115
1185
  if (state.aborted) return false;
@@ -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);
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ClickhouseNative
4
4
  class Client
5
- attr_reader :host, :port, :database
5
+ attr_reader :host, :port, :database, :ping_before_query, :tcp_keepalive, :retry_timeout
6
6
 
7
7
  def describe_table(table, db_name: nil)
8
8
  fq = db_name ? "#{db_name}.#{table}" : table
@@ -8,16 +8,17 @@ 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 = { host:, port:, database:, user:, password:, compression:, logger: }
16
- @set_sql = settings_sql(settings)
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
- client = Client.new(**client_kwargs)
19
- client.execute(@set_sql) if @set_sql
20
- client
21
+ Client.new(**client_kwargs)
21
22
  end
22
23
  end
23
24
 
@@ -25,18 +26,24 @@ module ClickhouseNative
25
26
  # path leaves the socket 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 (e.g. the SET reapplying
29
- # session settings), producing misleading log lines and re-raises in
30
- # unrelated code. A fresh socket + handshake is cheap relative to
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 0, message
36
- # "closed: Success"). Discarding and re-checking out lands a fresh
37
- # socket and the operation succeeds. The retry only triggers when
38
- # the dead-connection error fired before any data was sent, so
39
- # write operations don't risk double-execution from this path.
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).
40
47
  def with
41
48
  attempts = 0
42
49
  begin
@@ -84,21 +91,5 @@ module ClickhouseNative
84
91
  def describe_table(table, db_name: nil)
85
92
  with { |c| c.describe_table(table, db_name:) }
86
93
  end
87
-
88
- private
89
-
90
- # Render a `SET key1 = val1, key2 = val2` statement once at pool setup
91
- # so every checked-out connection starts with the same session
92
- # settings. Matches how the HTTP driver injected global_params per
93
- # request. Values: Integer / Float render bare; anything else is
94
- # quoted as a SQL string literal.
95
- def settings_sql(settings)
96
- return nil if settings.nil? || settings.empty?
97
- parts = settings.map do |k, v|
98
- literal = v.is_a?(Numeric) ? v.to_s : "'#{v.to_s.gsub("'", "''")}'"
99
- "#{k} = #{literal}"
100
- end
101
- "SET #{parts.join(', ')}"
102
- end
103
94
  end
104
95
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClickhouseNative
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  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.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Smirnov
@@ -35,6 +35,7 @@ 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
38
39
  - ext/clickhouse_native/vendor/clickhouse-cpp/.clang-format
39
40
  - ext/clickhouse_native/vendor/clickhouse-cpp/.git
40
41
  - ext/clickhouse_native/vendor/clickhouse-cpp/.gitattributes
@@ -323,7 +324,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
323
324
  - !ruby/object:Gem::Version
324
325
  version: '0'
325
326
  requirements: []
326
- rubygems_version: 4.0.10
327
+ rubygems_version: 4.0.16
327
328
  specification_version: 4
328
329
  summary: ClickHouse Ruby driver over the native TCP protocol
329
330
  test_files: []