clickhouse-native 0.9.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/ext/clickhouse_native/client.cpp +115 -6
  3. data/ext/clickhouse_native/extconf.rb +30 -3
  4. data/ext/clickhouse_native/patches/0002-carry-settings-into-insert.patch +82 -0
  5. data/ext/clickhouse_native/vendor/clickhouse-cpp/.github/workflows/bazel.yml +120 -0
  6. data/ext/clickhouse_native/vendor/clickhouse-cpp/.github/workflows/cross-repo-bug-relay.yml +17 -0
  7. data/ext/clickhouse_native/vendor/clickhouse-cpp/.github/workflows/linux.yml +22 -23
  8. data/ext/clickhouse_native/vendor/clickhouse-cpp/.github/workflows/macos.yml +22 -21
  9. data/ext/clickhouse_native/vendor/clickhouse-cpp/.github/workflows/windows_mingw.yml +29 -36
  10. data/ext/clickhouse_native/vendor/clickhouse-cpp/.github/workflows/windows_msvc.yml +29 -36
  11. data/ext/clickhouse_native/vendor/clickhouse-cpp/.gitignore +6 -0
  12. data/ext/clickhouse_native/vendor/clickhouse-cpp/AI_POLICY.md +13 -0
  13. data/ext/clickhouse_native/vendor/clickhouse-cpp/BUILD.bazel +167 -0
  14. data/ext/clickhouse_native/vendor/clickhouse-cpp/CMakeLists.txt +2 -1
  15. data/ext/clickhouse_native/vendor/clickhouse-cpp/MODULE.bazel +17 -0
  16. data/ext/clickhouse_native/vendor/clickhouse-cpp/MODULE.bazel.lock +503 -0
  17. data/ext/clickhouse_native/vendor/clickhouse-cpp/README.md +32 -6
  18. data/ext/clickhouse_native/vendor/clickhouse-cpp/ci/docker-compose/config.xml +53 -0
  19. data/ext/clickhouse_native/vendor/clickhouse-cpp/ci/docker-compose/users.xml +35 -0
  20. data/ext/clickhouse_native/vendor/clickhouse-cpp/ci/docker-compose.yml +22 -0
  21. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/CMakeLists.txt +11 -0
  22. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/base/sslsocket.cpp +24 -0
  23. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/block.cpp +1 -1
  24. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/block.h +2 -1
  25. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/client.cpp +293 -136
  26. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/client.h +31 -2
  27. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/array.cpp +12 -0
  28. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/array.h +17 -7
  29. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/bool.cpp +79 -0
  30. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/bool.h +62 -0
  31. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/factory.cpp +16 -0
  32. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/itemview.cpp +2 -0
  33. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/itemview.h +6 -2
  34. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/json.cpp +102 -0
  35. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/json.h +82 -0
  36. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/lowcardinality.cpp +2 -1
  37. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/string.cpp +7 -2
  38. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/tuple.cpp +48 -5
  39. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/columns/tuple.h +14 -1
  40. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/query.h +2 -2
  41. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/server_exception.h +0 -3
  42. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/types/type_parser.cpp +43 -0
  43. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/types/type_parser.h +9 -0
  44. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/types/types.cpp +61 -11
  45. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/types/types.h +18 -2
  46. data/ext/clickhouse_native/vendor/clickhouse-cpp/clickhouse/version.h +1 -1
  47. data/lib/clickhouse_native/client.rb +1 -1
  48. data/lib/clickhouse_native/pool.rb +22 -31
  49. data/lib/clickhouse_native/version.rb +1 -1
  50. data/lib/clickhouse_native.rb +1 -0
  51. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e0bcd3868daa1a69af028ff62e4c0104c82ceac165acfa12f40418259d74929
4
- data.tar.gz: e40d67544c4b9fb99fbf0ecebf655dd572055fc4d8301521cf0958af1a4531c1
3
+ metadata.gz: 984a63d34de19388f777cfe71ecd0a844d22f0b67b89e9fddfe07c5c0ac1cc05
4
+ data.tar.gz: 78f829e40f715aef0264e1419cb9b06b97faab1ac38f33329c5da6ada1dd251b
5
5
  SHA512:
6
- metadata.gz: ace9d3e809fa558064f42d2ee66bce667f8514fb06f88d2eb2e24c09f244994e32365acae4f3a3c110025f6a3de947a242d766a72fa72dfaa560fbaf8825c9a0
7
- data.tar.gz: a935ba30d5f897254492157e82bf98be08e202cd8b619de923c43b0b25ae07f3112d98b20768152dcd44c605eb57db7220e70c240e318415c9992bfb7bd8bf1b
6
+ metadata.gz: c579da78d2185108712c92e902217c2e508655533c2e78efc7e36ae358a826f809f41d884684b8fcb0e1b36749bc80be5f23b76c85ebf081423adb615d73928b
7
+ data.tar.gz: 93259646a6f150ed271202e3eaca075e0d1cf8d707aed5ac2ac9b530061de47cf1a65082289718857b367dd5b8d764c73cf270c34d080018ce8caac540d8bab6
@@ -7,6 +7,7 @@
7
7
  #include <clickhouse/columns/decimal.h>
8
8
  #include <clickhouse/columns/enum.h>
9
9
  #include <clickhouse/columns/factory.h>
10
+ #include <clickhouse/columns/json.h>
10
11
  #include <clickhouse/columns/lowcardinality.h>
11
12
  #include <clickhouse/columns/map.h>
12
13
  #include <clickhouse/columns/numeric.h>
@@ -16,11 +17,13 @@
16
17
  #include <clickhouse/exceptions.h>
17
18
  #include <clickhouse/types/types.h>
18
19
 
20
+ #include <chrono>
19
21
  #include <cstdint>
20
22
  #include <exception>
21
23
  #include <memory>
22
24
  #include <string>
23
25
  #include <system_error>
26
+ #include <utility>
24
27
  #include <vector>
25
28
 
26
29
  using namespace clickhouse;
@@ -188,6 +191,17 @@ static VALUE value_at(const ColumnRef& col, size_t idx, const std::string& decla
188
191
  return rb_utf8_str_new(sv.data(), sv.size());
189
192
  }
190
193
 
194
+ case Type::JSON: {
195
+ // clickhouse-cpp's ColumnJSON is string-backed (it relies on the
196
+ // server's output_format_native_write_json_as_string). Decode the
197
+ // raw JSON text into a Ruby object so a JSON column reads back like
198
+ // Map does — a Hash — rather than a string the caller must re-parse.
199
+ auto sv = col->As<ColumnJSON>()->At(idx);
200
+ VALUE str = rb_utf8_str_new(sv.data(), sv.size());
201
+ VALUE rb_mJSON = rb_const_get(rb_cObject, rb_intern("JSON"));
202
+ return rb_funcall(rb_mJSON, rb_intern("parse"), 1, str);
203
+ }
204
+
191
205
  case Type::Date: {
192
206
  // Return a Ruby Date so as_json/to_s gives "YYYY-MM-DD" instead of
193
207
  // a full ISO8601 timestamp. CH Date is days since 1970-01-01 UTC.
@@ -327,6 +341,8 @@ static void append_default(const ColumnRef& col) {
327
341
  case Type::Float64: col->As<ColumnFloat64>()->Append(0); return;
328
342
  case Type::String: col->As<ColumnString>()->Append(std::string_view()); return;
329
343
  case Type::FixedString: col->As<ColumnFixedString>()->Append(std::string_view()); return;
344
+ // CH rejects an empty string as JSON; the empty value is "{}".
345
+ case Type::JSON: col->As<ColumnJSON>()->Append(std::string_view("{}")); return;
330
346
  case Type::Date: col->As<ColumnDate>()->Append(0); return;
331
347
  case Type::Date32: col->As<ColumnDate32>()->Append(0); return;
332
348
  case Type::DateTime: col->As<ColumnDateTime>()->Append(0); return;
@@ -490,6 +506,19 @@ static void append_value(const ColumnRef& col, VALUE value) {
490
506
  return;
491
507
  }
492
508
 
509
+ case Type::JSON: {
510
+ // A String is taken as already-serialized JSON text and passed
511
+ // through verbatim; anything else (Hash / Array / ...) is rendered
512
+ // via #to_json. nil is handled earlier via append_default ("{}").
513
+ VALUE str = RB_TYPE_P(value, T_STRING)
514
+ ? value
515
+ : rb_funcall(value, rb_intern("to_json"), 0);
516
+ StringValue(str);
517
+ col->As<ColumnJSON>()->Append(
518
+ std::string_view(RSTRING_PTR(str), RSTRING_LEN(str)));
519
+ return;
520
+ }
521
+
493
522
  case Type::Date: col->As<ColumnDate>()->Append(static_cast<std::time_t>(coerce_to_date_epoch(value))); return;
494
523
  case Type::Date32: col->As<ColumnDate32>()->Append(static_cast<std::time_t>(coerce_to_date_epoch(value))); return;
495
524
  case Type::DateTime: {
@@ -639,6 +668,11 @@ static void append_value(const ColumnRef& col, VALUE value) {
639
668
 
640
669
  struct CHClient {
641
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;
642
676
  };
643
677
 
644
678
  static void ch_client_free(void* p) {
@@ -680,6 +714,18 @@ static uint16_t kwarg_uint16(VALUE kwargs, const char* key, uint16_t fallback) {
680
714
  return static_cast<uint16_t>(NUM2UINT(v));
681
715
  }
682
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
+
683
729
  static CompressionMethod kwarg_compression(VALUE kwargs) {
684
730
  VALUE v = rb_hash_lookup2(kwargs, ID2SYM(rb_intern("compression")), Qundef);
685
731
  if (v == Qundef || NIL_P(v)) return CompressionMethod::None;
@@ -715,6 +761,16 @@ static int apply_settings_cb(VALUE key, VALUE val, VALUE arg) {
715
761
  return ST_CONTINUE;
716
762
  }
717
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
+
718
774
  // Read a `settings:` Hash out of the parsed kwargs and stamp each entry
719
775
  // onto `q` as a per-query setting. No-op if kwargs is nil or settings is
720
776
  // missing/empty. Raises TypeError if settings is not a Hash.
@@ -726,6 +782,28 @@ static void apply_settings(Query& q, VALUE kwargs) {
726
782
  rb_hash_foreach(settings, apply_settings_cb, reinterpret_cast<VALUE>(&q));
727
783
  }
728
784
 
785
+ // Same as apply_settings, but first defaults
786
+ // output_format_native_write_json_as_string=1 so JSON columns come back as
787
+ // strings (clickhouse-cpp's ColumnJSON can only read them that way). The flag
788
+ // is 0 (not "important"), so servers that don't know the setting ignore it
789
+ // instead of erroring. User-supplied settings are applied afterwards and win,
790
+ // so callers can still override it.
791
+ static void apply_read_settings(Query& q, VALUE kwargs) {
792
+ q.SetSetting("output_format_native_write_json_as_string",
793
+ QuerySettingsField{"1", 0});
794
+ apply_settings(q, kwargs);
795
+ }
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
+
729
807
  // Client.new(host:, port:, database:, user:, password:)
730
808
  static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
731
809
  VALUE kwargs = Qnil;
@@ -739,12 +817,35 @@ static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
739
817
  std::string password = kwarg_str(kwargs, "password", "");
740
818
  CompressionMethod compression = kwarg_compression(kwargs);
741
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
+
742
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
+
743
841
  try {
744
842
  ClientOptions opts;
745
843
  opts.SetHost(host).SetPort(port)
746
844
  .SetDefaultDatabase(database).SetUser(user).SetPassword(password)
747
- .SetCompressionMethod(compression);
845
+ .SetCompressionMethod(compression)
846
+ .SetPingBeforeQuery(ping_before_query)
847
+ .TcpKeepAlive(tcp_keepalive)
848
+ .SetRetryTimeout(std::chrono::seconds(retry_timeout));
748
849
  c->client = std::make_unique<Client>(opts);
749
850
  } catch (const std::exception& e) {
750
851
  raise_mapped_ex(e);
@@ -753,6 +854,9 @@ static VALUE ch_client_initialize(int argc, VALUE* argv, VALUE self) {
753
854
  rb_ivar_set(self, rb_intern("@host"), rb_utf8_str_new(host.data(), host.size()));
754
855
  rb_ivar_set(self, rb_intern("@port"), UINT2NUM(port));
755
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));
756
860
 
757
861
  VALUE logger = rb_hash_lookup2(kwargs, ID2SYM(rb_intern("logger")), Qnil);
758
862
  rb_ivar_set(self, rb_intern("@logger"), logger);
@@ -796,6 +900,7 @@ static VALUE ch_client_execute(int argc, VALUE* argv, VALUE self) {
796
900
  if (!c->client) rb_raise(err_connection, "clickhouse-native: client is closed");
797
901
 
798
902
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
903
+ apply_default_settings(q, c);
799
904
  apply_settings(q, kwargs);
800
905
 
801
906
  ExecuteNoGVL args{c->client.get(), &q, nullptr};
@@ -827,7 +932,8 @@ static VALUE ch_client_query(int argc, VALUE* argv, VALUE self) {
827
932
  try {
828
933
  std::vector<ID> col_ids;
829
934
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
830
- apply_settings(q, kwargs);
935
+ apply_default_settings(q, c);
936
+ apply_read_settings(q, kwargs);
831
937
  q.OnData([&](const Block& block) {
832
938
  size_t ncols = block.GetColumnCount();
833
939
  size_t nrows = block.GetRowCount();
@@ -872,7 +978,8 @@ static VALUE ch_client_query_value(int argc, VALUE* argv, VALUE self) {
872
978
  VALUE out = Qnil;
873
979
  bool seen = false;
874
980
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
875
- apply_settings(q, kwargs);
981
+ apply_default_settings(q, c);
982
+ apply_read_settings(q, kwargs);
876
983
  q.OnData([&](const Block& block) {
877
984
  if (seen) return;
878
985
  if (block.GetRowCount() == 0 || block.GetColumnCount() == 0) return;
@@ -897,6 +1004,7 @@ struct InsertNoGVL {
897
1004
  Client* client;
898
1005
  const std::string* table;
899
1006
  const Block* block;
1007
+ const std::vector<std::pair<std::string, std::string>>* settings;
900
1008
  std::exception_ptr err;
901
1009
  };
902
1010
  } // namespace
@@ -904,7 +1012,7 @@ struct InsertNoGVL {
904
1012
  static void* insert_no_gvl(void* data) {
905
1013
  auto* a = static_cast<InsertNoGVL*>(data);
906
1014
  try {
907
- a->client->Insert(*a->table, *a->block);
1015
+ a->client->Insert(*a->table, *a->block, *a->settings);
908
1016
  } catch (...) {
909
1017
  a->err = std::current_exception();
910
1018
  }
@@ -970,7 +1078,7 @@ static VALUE ch_client_insert_block(VALUE self, VALUE rb_table, VALUE rb_columns
970
1078
  block.AppendColumn(names[i], cols[i]);
971
1079
  }
972
1080
 
973
- InsertNoGVL args{c->client.get(), &table, &block, nullptr};
1081
+ InsertNoGVL args{c->client.get(), &table, &block, &c->default_settings, nullptr};
974
1082
  rb_thread_call_without_gvl(insert_no_gvl, &args, insert_unblock, &args);
975
1083
  if (args.err) {
976
1084
  try { c->client->ResetConnection(); } catch (...) {}
@@ -1071,7 +1179,8 @@ static VALUE ch_client_query_each(int argc, VALUE* argv, VALUE self) {
1071
1179
 
1072
1180
  QueryEachState state{rb_block_proc(), {}, 0, false};
1073
1181
  Query q(std::string(RSTRING_PTR(rb_sql), RSTRING_LEN(rb_sql)));
1074
- apply_settings(q, kwargs);
1182
+ apply_default_settings(q, c);
1183
+ apply_read_settings(q, kwargs);
1075
1184
  q.OnDataCancelable([&state](const Block& block) -> bool {
1076
1185
  if (state.aborted) return false;
1077
1186
  YieldBlockArgs ya{&block, &state};
@@ -22,16 +22,39 @@ def fatal(msg)
22
22
  abort "clickhouse-native: cannot build extension"
23
23
  end
24
24
 
25
+ # Bundler `git:` installs don't fetch submodules unless the Gemfile sets
26
+ # `submodules: true`, which leaves vendor/clickhouse-cpp empty. When we're
27
+ # sitting in a git checkout, fetch it ourselves so the build can proceed
28
+ # without every consumer having to remember that flag. Released gems and
29
+ # `submodules: recursive` checkouts already have the tree, so this is a no-op
30
+ # there.
31
+ unless File.exist?(File.join(VENDOR, "CMakeLists.txt"))
32
+ in_git_checkout = system(
33
+ "git", "-C", EXT_DIR, "rev-parse", "--is-inside-work-tree",
34
+ out: File::NULL, err: File::NULL
35
+ )
36
+ if in_git_checkout
37
+ warn "clickhouse-native: vendored clickhouse-cpp is missing; " \
38
+ "running `git submodule update --init --recursive`…"
39
+ system("git", "-C", EXT_DIR, "submodule", "update", "--init", "--recursive")
40
+ end
41
+ end
42
+
25
43
  unless File.exist?(File.join(VENDOR, "CMakeLists.txt"))
26
44
  fatal <<~MSG
27
45
  clickhouse-cpp submodule not found at:
28
46
  #{VENDOR}
29
47
 
30
- If you cloned this gem's repo, run:
48
+ Installing from a git source? Bundler skips submodules unless you add
49
+ `submodules: true` to the gem line:
50
+
51
+ gem "clickhouse-native", git: "...", submodules: true
52
+
53
+ From a manual clone, run:
31
54
  git submodule update --init --recursive
32
55
 
33
- If you see this during `gem install`, please report a bug — the
34
- gemspec should have bundled the submodule tree.
56
+ If you see this while installing a released gem from RubyGems, please
57
+ report a bug the published .gem should bundle the submodule tree.
35
58
  MSG
36
59
  end
37
60
 
@@ -97,6 +120,10 @@ configure_args = [
97
120
  "-DBUILD_BENCHMARK=OFF",
98
121
  "-DBUILD_TESTS=OFF",
99
122
  "-DWITH_OPENSSL=OFF",
123
+ # clickhouse-cpp v2.6.2 added a distinct Bool column; this gem relies on
124
+ # Bool normalising to UInt8 (see the declared-type handling in client.cpp).
125
+ # Pin the upstream default ON so a future default flip can't break us.
126
+ "-DCH_MAP_BOOL_TO_UINT8=ON",
100
127
  "-DCMAKE_POSITION_INDEPENDENT_CODE=ON",
101
128
  "-DCMAKE_BUILD_TYPE=Release"
102
129
  ]
@@ -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,120 @@
1
+ name: Bazel
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 0 * * 1'
6
+ push:
7
+ branches: [ '*' ]
8
+ pull_request:
9
+ branches: [ master ]
10
+
11
+ release:
12
+ types:
13
+ - published
14
+ - prereleased
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ build:
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ os: [ubuntu-24.04, macos-latest, windows-latest]
25
+ tls: [boringssl, openssl, 'no']
26
+
27
+ runs-on: ${{ matrix.os }}
28
+
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+ with:
32
+ fetch-depth: 100
33
+ fetch-tags: true
34
+
35
+ - name: Setup Bazel
36
+ uses: bazel-contrib/setup-bazel@0.15.0
37
+ with:
38
+ bazelisk-cache: true
39
+ disk-cache: true
40
+ repository-cache: true
41
+
42
+ - name: Build project
43
+ run: bazel build //... --@clickhouse-cpp-client//:tls=${{ matrix.tls }}
44
+
45
+ - name: Run unit tests
46
+ run: bazel test //ut:unit_tests --@clickhouse-cpp-client//:tls=${{ matrix.tls }} --test_output=errors
47
+
48
+ # //ut:e2e_tests is tagged `manual`, so `bazel build //...` above
49
+ # skips it; build it explicitly so the run step below doesn't pay
50
+ # for compilation under the running-server timeout.
51
+ - name: Build e2e tests
52
+ run: bazel build //ut:e2e_tests --@clickhouse-cpp-client//:tls=${{ matrix.tls }}
53
+
54
+ # The e2e suite needs a live server on localhost:9000. Each OS starts
55
+ # one the same way the CMake build's per-OS workflows do, since hosted
56
+ # runners can't all run a Linux container the same way:
57
+ # * Linux — docker-compose (linux.yml)
58
+ # * macOS — the native ClickHouse build run as a process (macos.yml)
59
+ # * Windows — the Linux container under WSL2 + podman (windows_msvc.yml)
60
+ - name: Start ClickHouse (Linux, docker-compose)
61
+ if: runner.os == 'Linux'
62
+ uses: hoverkraft-tech/compose-action@v2.0.1
63
+ with:
64
+ compose-file: ci/docker-compose.yml
65
+ down-flags: --volumes
66
+
67
+ - name: Start ClickHouse (macOS, native binary)
68
+ if: runner.os == 'macOS'
69
+ working-directory: ${{ runner.temp }}
70
+ run: |
71
+ curl https://builds.clickhouse.com/25.12/macos-aarch64/clickhouse -o clickhouse
72
+ chmod +x ./clickhouse
73
+ sudo mkdir -p /var/lib/clickhouse /var/log/clickhouse-server
74
+ sudo chown -R "$USER" /var/lib/clickhouse /var/log/clickhouse-server
75
+ nohup ./clickhouse server --config-file="$GITHUB_WORKSPACE/ci/docker-compose/config.xml" > clickhouse.log 2>&1 &
76
+ for i in {1..60}; do
77
+ if curl -fsS http://localhost:8123/ > /dev/null; then
78
+ echo "ClickHouse is ready"
79
+ exit 0
80
+ fi
81
+ sleep 1
82
+ done
83
+ echo "ClickHouse failed to start"
84
+ tail -200 clickhouse.log || true
85
+ exit 1
86
+
87
+ - name: Enable WSL (Windows)
88
+ if: runner.os == 'Windows'
89
+ uses: Vampire/setup-wsl@v5
90
+ with:
91
+ distribution: Ubuntu-24.04
92
+ additional-packages:
93
+ podman
94
+ podman-compose
95
+
96
+ - name: Start ClickHouse (Windows, WSL + podman)
97
+ if: runner.os == 'Windows'
98
+ shell: wsl-bash {0}
99
+ run: |
100
+ cd $(wslpath -u "${{ github.workspace }}/ci/")
101
+ podman-compose up -d
102
+ timeout 60s bash -c \
103
+ 'until curl -s -o /dev/null -w "%{http_code}" http://localhost:8123 | grep -q "200"; do sleep 2; done'
104
+ curl -s http://localhost:8123/?query=SELECT%20VERSION%28%29
105
+
106
+ - name: Wait for ClickHouse (Linux)
107
+ if: runner.os == 'Linux'
108
+ run: |
109
+ for i in {1..60}; do
110
+ if curl -fsS http://localhost:8123/ > /dev/null; then
111
+ echo "ClickHouse is ready"
112
+ exit 0
113
+ fi
114
+ sleep 1
115
+ done
116
+ echo "ClickHouse failed to start"
117
+ exit 1
118
+
119
+ - name: Run e2e tests
120
+ run: bazel test //ut:e2e_tests --@clickhouse-cpp-client//:tls=${{ matrix.tls }} --test_output=errors
@@ -0,0 +1,17 @@
1
+ name: Relay bugs for cross-repo investigation
2
+
3
+ # Relays newly-opened issues to ClickHouse/integrations-ai-playground for
4
+ # cross-repo investigation.
5
+
6
+ on:
7
+ issues:
8
+ types: [opened]
9
+
10
+ permissions: {}
11
+
12
+ jobs:
13
+ relay:
14
+ uses: ClickHouse/integrations-shared-workflows/.github/workflows/cross-repo-bug-relay.yml@main
15
+ secrets:
16
+ WORKFLOW_AUTH_PUBLIC_APP_ID: ${{ secrets.WORKFLOW_AUTH_PUBLIC_APP_ID }}
17
+ WORKFLOW_AUTH_PUBLIC_PRIVATE_KEY: ${{ secrets.WORKFLOW_AUTH_PUBLIC_PRIVATE_KEY }}
@@ -15,10 +15,9 @@ on:
15
15
 
16
16
  env:
17
17
  BUILD_TYPE: Release
18
- CLICKHOUSE_SERVER_IMAGE: "clickhouse/clickhouse-server:25.10"
19
- CLICKHOUSE_SECURE_HOST: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_HOST_SMT_PROD }}
20
- CLICKHOUSE_SECURE_USER: default
21
- CLICKHOUSE_SECURE_PASSWORD: ${{ secrets.INTEGRATIONS_TEAM_TESTS_CLOUD_PASSWORD_SMT_PROD }}
18
+
19
+ permissions:
20
+ contents: read
22
21
 
23
22
  jobs:
24
23
  build:
@@ -83,7 +82,7 @@ jobs:
83
82
  runs-on: ${{matrix.os}}
84
83
 
85
84
  steps:
86
- - uses: actions/checkout@v4
85
+ - uses: actions/checkout@v6
87
86
  with:
88
87
  fetch-depth: 100
89
88
  fetch-tags: true
@@ -96,22 +95,13 @@ jobs:
96
95
  ${{matrix.COMPILER_INSTALL}} \
97
96
  ${{matrix.DEPENDENCIES_INSTALL}}
98
97
 
99
- - name: Install dependencies - Docker
100
- run: |
101
- sudo apt remove -y docker docker-engine docker.io containerd runc
102
- sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release
103
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
104
- echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
105
- sudo apt update -q
106
- sudo apt install docker-ce docker-ce-cli containerd.io
107
- sudo docker run hello-world
108
-
109
98
  - name: Configure project
110
99
  run: |
111
100
  cmake \
112
101
  -D CMAKE_C_COMPILER=${{matrix.C_COMPILER}} \
113
102
  -D CMAKE_CXX_COMPILER=${{matrix.CXX_COMPILER}} \
114
103
  -D CMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
104
+ -D CH_MAP_BOOL_TO_UINT8=OFF \
115
105
  -D BUILD_TESTS=ON \
116
106
  ${{matrix.SSL_CMAKE_OPTION}} \
117
107
  ${{matrix.DEPENDENCIES_CMAKE_OPTIONS}} \
@@ -125,15 +115,24 @@ jobs:
125
115
  --config ${{env.BUILD_TYPE}} \
126
116
  --target all
127
117
 
128
- - name: Test - Start ClickHouse server in background
129
- run: |
130
- docker pull ${CLICKHOUSE_SERVER_IMAGE}
131
- docker run -d --name clickhouse -p 9000:9000 -e CLICKHOUSE_SKIP_USER_SETUP=1 ${CLICKHOUSE_SERVER_IMAGE}
132
- docker ps -a
133
- docker stats -a --no-stream
134
- ## Check and wait until CH is ready to accept connections
135
- docker exec clickhouse bash -c 'for i in {1..10}; do echo checking if clickhouse server is started attempt \#$i; if ( grep -q "<Information> Application: Ready for connections." /var/log/clickhouse-server/clickhouse-server.log ); then echo seems like clickhouse server is started; exit 0; fi; sleep 1; done; exit -1'
118
+ - name: Start ClickHouse in Docker
119
+ uses: hoverkraft-tech/compose-action@v2.0.1
120
+ with:
121
+ compose-file: ci/docker-compose.yml
122
+ down-flags: --volumes
136
123
 
124
+ - name: Check if ClickHouse is available
125
+ run: |
126
+ for i in {1..60}; do
127
+ if curl -fsS http://localhost:8123/ > /dev/null; then
128
+ echo "ClickHouse is ready"
129
+ exit 0
130
+ fi
131
+ sleep 1
132
+ done
133
+ echo "ClickHouse failed to start"
134
+ exit 1
135
+
137
136
  - name: Test
138
137
  working-directory: ${{github.workspace}}/build/ut
139
138
  run: ./clickhouse-cpp-ut