diff --git a/.gitlab/ci/gitlab-gems.gitlab-ci.yml b/.gitlab/ci/gitlab-gems.gitlab-ci.yml index d972139ad6ee1f428289607668bb3ae7a775832f..ff961877309ac298cb06d8db38e6ef011881eb5a 100644 --- a/.gitlab/ci/gitlab-gems.gitlab-ci.yml +++ b/.gitlab/ci/gitlab-gems.gitlab-ci.yml @@ -8,3 +8,6 @@ include: - local: .gitlab/ci/templates/gem.gitlab-ci.yml inputs: gem_name: "gitlab-utils" + - local: .gitlab/ci/templates/gem.gitlab-ci.yml + inputs: + gem_name: "click_house-client" diff --git a/Gemfile b/Gemfile index 9ce66bdb455d0c7ec29d6f98e82c76dd354079ae..51d5ba5c3a6394c53897e9c2c33624b168e9a113 100644 --- a/Gemfile +++ b/Gemfile @@ -326,6 +326,7 @@ gem 'sassc-rails', '~> 2.1.0' gem 'autoprefixer-rails', '10.2.5.1' gem 'terser', '1.0.2' +gem 'click_house-client', path: 'gems/click_house-client', require: 'click_house/client' gem 'addressable', '~> 2.8' gem 'tanuki_emoji', '~> 0.6' gem 'gon', '~> 6.4.0' diff --git a/Gemfile.lock b/Gemfile.lock index c2de0e0deb628d6349dc16e816a1d24acbf941e5..80e92e603b1e67b88a3536ece6af192a7b72453a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,14 @@ PATH activerecord-gitlab (0.1.0) activerecord (>= 6.1.7.3) +PATH + remote: gems/click_house-client + specs: + click_house-client (0.1.0) + activesupport (< 8) + addressable (~> 2.8) + json (~> 2.6.3) + PATH remote: gems/gitlab-rspec specs: @@ -1733,6 +1741,7 @@ DEPENDENCIES carrierwave (~> 1.3) charlock_holmes (~> 0.7.7) circuitbox (= 2.0.0) + click_house-client! cloud_profiler_agent (~> 0.0.0)! commonmarker (~> 0.23.9) concurrent-ruby (~> 1.1) diff --git a/config/initializers/click_house.rb b/config/initializers/click_house.rb new file mode 100644 index 0000000000000000000000000000000000000000..7fc216d9a59469651dd6ce22515df6913f667e6c --- /dev/null +++ b/config/initializers/click_house.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +return unless File.exist?(Rails.root.join('config/click_house.yml')) + +raw_config = Rails.application.config_for(:click_house) +return if raw_config.blank? + +ClickHouse::Client.configure do |config| + raw_config.each do |database_identifier, db_config| + config.register_database(database_identifier, + database: db_config[:database], + url: db_config[:url], + username: db_config[:username], + password: db_config[:password], + variables: db_config[:variables] || {} + ) + end + + config.json_parser = Gitlab::Json + config.http_post_proc = ->(url, headers, body) do + options = { + headers: headers, + body: body, + allow_local_requests: Rails.env.development? || Rails.env.test? + } + + response = Gitlab::HTTP.post(url, options) + ClickHouse::Client::Response.new(response.body, response.code) + end +end diff --git a/gems/click_house-client/.gitlab-ci.yml b/gems/click_house-client/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..0384da8a893186a86b2dd149f572f634b0e99b67 --- /dev/null +++ b/gems/click_house-client/.gitlab-ci.yml @@ -0,0 +1,4 @@ +include: + - local: gems/gem.gitlab-ci.yml + inputs: + gem_name: "click_house-client" diff --git a/gems/click_house-client/.rspec b/gems/click_house-client/.rspec new file mode 100644 index 0000000000000000000000000000000000000000..34c5164d9b56c7d528f061c97f2d2fe02c834bdd --- /dev/null +++ b/gems/click_house-client/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/gems/click_house-client/.rubocop.yml b/gems/click_house-client/.rubocop.yml new file mode 100644 index 0000000000000000000000000000000000000000..24d88acff8184e2f58d75a857654d39868082ac9 --- /dev/null +++ b/gems/click_house-client/.rubocop.yml @@ -0,0 +1,17 @@ +inherit_from: + - ../../.rubocop.yml + +CodeReuse/ActiveRecord: + Enabled: false + +Rails/ApplicationRecord: + Enabled: false + +AllCops: + TargetRubyVersion: 3.0 + +Naming/FileName: + Exclude: + - spec/**/*.rb + - lib/gitlab/rspec.rb + - lib/gitlab/rspec/all.rb diff --git a/gems/click_house-client/Gemfile b/gems/click_house-client/Gemfile new file mode 100644 index 0000000000000000000000000000000000000000..be173b205f70152dd1e0a0319f131e51c49eb9cd --- /dev/null +++ b/gems/click_house-client/Gemfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec diff --git a/gems/click_house-client/Gemfile.lock b/gems/click_house-client/Gemfile.lock new file mode 100644 index 0000000000000000000000000000000000000000..088e39c07fb226c4620fc9f1f03f026fc4056559 --- /dev/null +++ b/gems/click_house-client/Gemfile.lock @@ -0,0 +1,102 @@ +PATH + remote: . + specs: + click_house-client (0.1.0) + activesupport (< 8) + addressable (~> 2.8) + json (~> 2.6.3) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.0.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.4) + public_suffix (>= 2.0.2, < 6.0) + ast (2.4.2) + concurrent-ruby (1.2.2) + diff-lcs (1.5.0) + gitlab-styles (10.1.0) + rubocop (~> 1.50.2) + rubocop-graphql (~> 0.18) + rubocop-performance (~> 1.15) + rubocop-rails (~> 2.17) + rubocop-rspec (~> 2.22) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + json (2.6.3) + minitest (5.18.1) + parallel (1.23.0) + parser (3.2.2.3) + ast (~> 2.4.1) + racc + public_suffix (5.0.1) + racc (1.7.1) + rack (3.0.8) + rainbow (3.1.1) + rake (13.0.6) + regexp_parser (2.8.1) + rexml (3.2.5) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) + rubocop (1.50.2) + json (~> 2.3) + parallel (~> 1.10) + parser (>= 3.2.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.18.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.23.1) + rubocop (~> 1.33) + rubocop-graphql (0.19.0) + rubocop (>= 0.87, < 2) + rubocop-performance (1.18.0) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.20.2) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.22.0) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + ruby-progressbar (1.13.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.4.2) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + click_house-client! + gitlab-styles (~> 10.1.0) + rake (~> 13.0) + rspec (~> 3.0) + rubocop + rubocop-rspec + +BUNDLED WITH + 2.4.15 diff --git a/gems/click_house-client/README.md b/gems/click_house-client/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6afabcad1a021514e28c8f1c6f2a9f54e63bcb5c --- /dev/null +++ b/gems/click_house-client/README.md @@ -0,0 +1,3 @@ +ClickHouse::Client + +This Gem provides a simple way to query ClickHouse databases using the HTTP interface. diff --git a/gems/click_house-client/click_house-client.gemspec b/gems/click_house-client/click_house-client.gemspec new file mode 100644 index 0000000000000000000000000000000000000000..5544065ef17077d0151360ad05cdc0df237509a5 --- /dev/null +++ b/gems/click_house-client/click_house-client.gemspec @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +Gem::Specification.new do |spec| + spec.name = "click_house-client" + spec.version = "0.1.0" + spec.authors = ["group::optimize"] + spec.email = ["engineering@gitlab.com"] + + spec.summary = "GitLab's client to interact with ClickHouse" + spec.description = "This Gem provides a simple way to query ClickHouse databases using the HTTP interface." + spec.homepage = "https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/click_house-client" + spec.license = "MIT" + spec.required_ruby_version = ">= 3.0" + + spec.add_runtime_dependency "activesupport", "< 8" + spec.add_runtime_dependency "addressable", "~> 2.8" + spec.add_runtime_dependency 'json', '~> 2.6.3' + + spec.add_development_dependency 'gitlab-styles', '~> 10.1.0' + spec.add_development_dependency "rake", "~> 13.0" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency 'rubocop' + spec.add_development_dependency 'rubocop-rspec' +end diff --git a/gems/click_house-client/lib/click_house/client.rb b/gems/click_house-client/lib/click_house/client.rb new file mode 100644 index 0000000000000000000000000000000000000000..1c8da64f38f7002616f7fe23b99db9a053a5032f --- /dev/null +++ b/gems/click_house-client/lib/click_house/client.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'addressable' +require 'json' +require 'active_support/time' +require_relative "client/database" +require_relative "client/configuration" +require_relative "client/formatter" +require_relative "client/response" + +module ClickHouse + module Client + class << self + def configuration + @configuration ||= Configuration.new + end + + def configure + yield(configuration) + configuration.validate! + end + end + + Error = Class.new(StandardError) + ConfigurationError = Class.new(Error) + DatabaseError = Class.new(Error) + + def self.execute(query, database, configuration = self.configuration) + db = configuration.databases[database] + raise ConfigurationError, "The database '#{database}' is not configured" unless db + + response = configuration.http_post_proc.call( + db.uri.to_s, + db.headers, + "#{query} FORMAT JSON" # always return JSON + ) + + raise DatabaseError, response.body unless response.success? + + Formatter.format(configuration.json_parser.parse(response.body)) + end + end +end diff --git a/gems/click_house-client/lib/click_house/client/configuration.rb b/gems/click_house-client/lib/click_house/client/configuration.rb new file mode 100644 index 0000000000000000000000000000000000000000..882b37993dc83a804ca9f9ad1fdc5815fbd2dc3e --- /dev/null +++ b/gems/click_house-client/lib/click_house/client/configuration.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module ClickHouse + module Client + class Configuration + # Configuration options: + # + # *register_database* (method): registers a database, the following arguments are required: + # - database: database name + # - url: URL and port to the HTTP interface + # - username + # - password + # - variables (optional): configuration for the client + # + # *http_post_proc*: A callable object for invoking the HTTP request. + # The object must handle the following parameters: url, headers, body + # and return a Gitlab::ClickHouse::Client::Response object. + # + # *json_parser*: object for parsing JSON strings, it should respond to the "parse" method + # + # Example: + # + # Gitlab::ClickHouse::Client.configure do |c| + # c.register_database(:main, + # database: 'gitlab_clickhouse_test', + # url: 'http://localhost:8123', + # username: 'default', + # password: 'clickhouse', + # variables: { + # join_use_nulls: 1 # treat JOINs as per SQL standard + # } + # ) + # + # c.http_post_proc = lambda do |url, headers, body| + # options = { + # headers: headers, + # body: body, + # allow_local_requests: false + # } + # + # response = Gitlab::HTTP.post(url, options) + # Gitlab::ClickHouse::Client::Response.new(response.body, response.code) + # end + # + # c.json_parser = JSON + # end + attr_accessor :http_post_proc, :json_parser + attr_reader :databases + + def initialize + @databases = {} + @http_post_proc = nil + @json_parser = JSON + end + + def register_database(name, **args) + raise ConfigurationError, "The database '#{name}' is already registered" if @databases.key?(name) + + @databases[name] = Database.new(**args) + end + + def validate! + raise ConfigurationError, "The 'http_post_proc' option is not configured" unless @http_post_proc + raise ConfigurationError, "The 'json_parser' option is not configured" unless @json_parser + end + end + end +end diff --git a/gems/click_house-client/lib/click_house/client/database.rb b/gems/click_house-client/lib/click_house/client/database.rb new file mode 100644 index 0000000000000000000000000000000000000000..beeb2a8cbd6ce0a326266de279eb81bac827beb8 --- /dev/null +++ b/gems/click_house-client/lib/click_house/client/database.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ClickHouse + module Client + class Database + attr_reader :database + + def initialize(database:, url:, username:, password:, variables: {}) + @database = database + @url = url + @username = username + @password = password + @variables = variables.merge(database: database).freeze + end + + def uri + @uri ||= begin + parsed = Addressable::URI.parse(@url) + parsed.query_values = @variables + parsed + end + end + + def headers + @headers ||= { + 'X-ClickHouse-User' => @username, + 'X-ClickHouse-Key' => @password + }.freeze + end + end + end +end diff --git a/gems/click_house-client/lib/click_house/client/formatter.rb b/gems/click_house-client/lib/click_house/client/formatter.rb new file mode 100644 index 0000000000000000000000000000000000000000..bb60d8db7f736d185e71a77a2b1606446f9148c9 --- /dev/null +++ b/gems/click_house-client/lib/click_house/client/formatter.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ClickHouse + module Client + class Formatter + DEFAULT = ->(value) { value } + + TYPE_CASTERS = { + 'UInt64' => ->(value) { Integer(value) }, + "DateTime64(6, 'UTC')" => ->(value) { ActiveSupport::TimeZone["UTC"].parse(value) } + }.freeze + + def self.format(result) + name_type_mapping = result['meta'].each_with_object({}) do |column, hash| + hash[column['name']] = column['type'] + end + + result['data'].map do |row| + row.each_with_object({}) do |(column, value), casted_row| + caster = TYPE_CASTERS.fetch(name_type_mapping[column], DEFAULT) + + casted_row[column] = caster.call(value) + end + end + end + end + end +end diff --git a/gems/click_house-client/lib/click_house/client/response.rb b/gems/click_house-client/lib/click_house/client/response.rb new file mode 100644 index 0000000000000000000000000000000000000000..898f0b0e0243f164a8b3ceb1feb414f19be0fede --- /dev/null +++ b/gems/click_house-client/lib/click_house/client/response.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ClickHouse + module Client + class Response + attr_reader :body + + def initialize(body, http_status_code) + @body = body + @http_status_code = http_status_code + end + + def success? + @http_status_code == 200 + end + end + end +end diff --git a/gems/click_house-client/spec/click_house/client/configuration_spec.rb b/gems/click_house-client/spec/click_house/client/configuration_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8cbd64ca6502de68d4b8aa198511581216bb3f33 --- /dev/null +++ b/gems/click_house-client/spec/click_house/client/configuration_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ClickHouse::Client::Configuration do + subject(:configuration) do + config = described_class.new + config.http_post_proc = -> {} + config.json_parser = Object + config + end + + describe '#register_database' do + let(:database_options) do + { + database: 'test_db', + url: 'http://localhost:3333', + username: 'user', + password: 'pass', + variables: { + join_use_nulls: 1 + } + } + end + + it 'registers a database' do + configuration.register_database(:my_db, **database_options) + + expect(configuration.databases.size).to eq(1) + database = configuration.databases[:my_db] + + expect(database.uri.to_s).to eq('http://localhost:3333?database=test_db&join_use_nulls=1') + end + + context 'when adding the same DB multiple times' do + it 'raises error' do + configuration.register_database(:my_db, **database_options) + expect do + configuration.register_database(:my_db, **database_options) + end.to raise_error(ClickHouse::Client::ConfigurationError, /database 'my_db' is already registered/) + end + end + end + + describe '#validate!' do + context 'when `http_post_proc` option is not configured' do + it 'raises error' do + configuration.http_post_proc = nil + + expect do + configuration.validate! + end.to raise_error(ClickHouse::Client::ConfigurationError, /'http_post_proc' option is not configured/) + end + end + + context 'when `json_parser` option is not configured' do + it 'raises error' do + configuration.json_parser = nil + + expect do + configuration.validate! + end.to raise_error(ClickHouse::Client::ConfigurationError, /'json_parser' option is not configured/) + end + end + end +end diff --git a/gems/click_house-client/spec/click_house/client/database_spec.rb b/gems/click_house-client/spec/click_house/client/database_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..112b2ee12b17482d5481ab5f436140748d5888b2 --- /dev/null +++ b/gems/click_house-client/spec/click_house/client/database_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ClickHouse::Client::Database do + subject(:database) do + described_class.new( + database: 'test_db', + url: 'http://localhost:3333', + username: 'user', + password: 'pass', + variables: { + join_use_nulls: 1 + } + ) + end + + describe '#uri' do + it 'builds the correct URL' do + expect(database.uri.to_s).to eq('http://localhost:3333?database=test_db&join_use_nulls=1') + end + end + + describe '#headers' do + it 'returns the correct headers' do + expect(database.headers).to eq({ + 'X-ClickHouse-User' => 'user', + 'X-ClickHouse-Key' => 'pass' + }) + end + end +end diff --git a/gems/click_house-client/spec/click_house/client_spec.rb b/gems/click_house-client/spec/click_house/client_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..19850bc722745d43f51f4172669d133991dc2f69 --- /dev/null +++ b/gems/click_house-client/spec/click_house/client_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +RSpec.describe ClickHouse::Client do + describe '#execute' do + # Assuming we have a DB table with the following schema + # + # CREATE TABLE issues ( + # `id` UInt64, + # `title` String DEFAULT '', + # `description` Nullable(String), + # `created_at` DateTime64(6, 'UTC') DEFAULT now(), + # `updated_at` DateTime64(6, 'UTC') DEFAULT now() + # ) + # ENGINE = ReplacingMergeTree(updated_at) + # ORDER BY (id) + + let(:query_result_fixture) { File.expand_path('../fixtures/query_result.json', __dir__) } + + let(:database_config) do + { + database: 'test_db', + url: 'http://localhost:3333', + username: 'user', + password: 'pass', + variables: { + join_use_nulls: 1 + } + } + end + + let(:configuration) do + ClickHouse::Client::Configuration.new.tap do |config| + config.register_database(:test_db, **database_config) + config.http_post_proc = ->(_url, _headers, _query) { + body = File.read(query_result_fixture) + ClickHouse::Client::Response.new(body, 200) + } + end + end + + it 'parses the results and returns the data as array of hashes' do + result = described_class.execute('SELECT * FROM issues', :test_db, configuration) + + timestamp1 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:44') + timestamp2 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:50') + timestamp3 = ActiveSupport::TimeZone["UTC"].parse('2023-06-21 13:33:40') + + expect(result).to eq([ + { + 'id' => 2, + 'title' => 'Title 2', + 'description' => 'description', + 'created_at' => timestamp1, + 'updated_at' => timestamp1 + }, + { + 'id' => 3, + 'title' => 'Title 3', + 'description' => nil, + 'created_at' => timestamp2, + 'updated_at' => timestamp2 + }, + { + 'id' => 1, + 'title' => 'Title 1', + 'description' => 'description', + 'created_at' => timestamp3, + 'updated_at' => timestamp3 + } + ]) + end + + context 'when the DB is not configured' do + it 'raises erro' do + expect do + described_class.execute('SELECT * FROM issues', :different_db, configuration) + end.to raise_error(ClickHouse::Client::ConfigurationError, /not configured/) + end + end + + context 'when error response is returned' do + let(:configuration) do + ClickHouse::Client::Configuration.new.tap do |config| + config.register_database(:test_db, **database_config) + config.http_post_proc = ->(_url, _headers, _query) { + ClickHouse::Client::Response.new('some error', 404) + } + end + end + + it 'raises error' do + expect do + described_class.execute('SELECT * FROM issues', :test_db, configuration) + end.to raise_error(ClickHouse::Client::DatabaseError, 'some error') + end + end + end +end diff --git a/gems/click_house-client/spec/fixtures/query_result.json b/gems/click_house-client/spec/fixtures/query_result.json new file mode 100644 index 0000000000000000000000000000000000000000..872b0e5b6edbefab6683ca67cd23944b79a0daed --- /dev/null +++ b/gems/click_house-client/spec/fixtures/query_result.json @@ -0,0 +1,53 @@ +{ + "meta": [ + { + "name": "id", + "type": "UInt64" + }, + { + "name": "title", + "type:": "String" + }, + { + "name": "description", + "type": "Nullable(String)" + }, + { + "name": "created_at", + "type": "DateTime64(6, 'UTC')" + }, + { + "name": "updated_at", + "type": "DateTime64(6, 'UTC')" + } + ], + "data": [ + { + "id": "2", + "title": "Title 2", + "description": "description", + "created_at": "2023-06-21 13:33:44.000000", + "updated_at": "2023-06-21 13:33:44.000000" + }, + { + "id": "3", + "title": "Title 3", + "description": null, + "created_at": "2023-06-21 13:33:50.000000", + "updated_at": "2023-06-21 13:33:50.000000" + }, + { + "id": "1", + "title": "Title 1", + "description": "description", + "created_at": "2023-06-21 13:33:40.000000", + "updated_at": "2023-06-21 13:33:40.000000" + } + ], + "rows": 3, + "statistics": { + "elapsed": 0.001581789, + "rows_read": 3, + "bytes_read": 172 + } +} diff --git a/gems/click_house-client/spec/spec_helper.rb b/gems/click_house-client/spec/spec_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..d2b5e59c9a370b7fec656c646229b1f63c937a87 --- /dev/null +++ b/gems/click_house-client/spec/spec_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "click_house/client" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/spec/lib/gitlab/database/click_house_client_spec.rb b/spec/lib/gitlab/database/click_house_client_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9260475fa4fdb9b4bab8defc95cd4aed4804333a --- /dev/null +++ b/spec/lib/gitlab/database/click_house_client_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'ClickHouse::Client', feature_category: :database do + it 'does not have any databases configured' do + databases = ClickHouse::Client.configuration.databases + + expect(databases).to be_empty + end +end