Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/click_house')
-rw-r--r--spec/lib/click_house/bind_index_manager_spec.rb33
-rw-r--r--spec/lib/click_house/query_builder_spec.rb334
-rw-r--r--spec/lib/click_house/redactor_spec.rb166
3 files changed, 533 insertions, 0 deletions
diff --git a/spec/lib/click_house/bind_index_manager_spec.rb b/spec/lib/click_house/bind_index_manager_spec.rb
new file mode 100644
index 00000000000..1c659017c63
--- /dev/null
+++ b/spec/lib/click_house/bind_index_manager_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::BindIndexManager, feature_category: :database do
+ describe '#next_bind_str' do
+ context 'when initialized without a start index' do
+ let(:bind_manager) { described_class.new }
+
+ it 'starts from index 1 by default' do
+ expect(bind_manager.next_bind_str).to eq('$1')
+ end
+
+ it 'increments the bind string on subsequent calls' do
+ bind_manager.next_bind_str
+ expect(bind_manager.next_bind_str).to eq('$2')
+ end
+ end
+
+ context 'when initialized with a start index' do
+ let(:bind_manager) { described_class.new(2) }
+
+ it 'starts from the given index' do
+ expect(bind_manager.next_bind_str).to eq('$2')
+ end
+
+ it 'increments the bind string on subsequent calls' do
+ bind_manager.next_bind_str
+ expect(bind_manager.next_bind_str).to eq('$3')
+ end
+ end
+ end
+end
diff --git a/spec/lib/click_house/query_builder_spec.rb b/spec/lib/click_house/query_builder_spec.rb
new file mode 100644
index 00000000000..9e3f1118eeb
--- /dev/null
+++ b/spec/lib/click_house/query_builder_spec.rb
@@ -0,0 +1,334 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::QueryBuilder, feature_category: :database do
+ let(:table_name) { :test_table }
+ let(:builder) { described_class.new(table_name) }
+
+ shared_examples "generates correct sql on multiple calls to `to_sql`" do |method_name, argument1, argument2|
+ it 'returns the same SQL when called multiple times on the same builder' do
+ query_builder = builder.public_send(method_name, argument1)
+ first_sql = query_builder.to_sql
+ second_sql = query_builder.to_sql
+
+ expect(first_sql).to eq(second_sql)
+ end
+
+ it 'returns different SQL when called multiple times on different builders' do
+ query_builder = builder.public_send(method_name, argument1)
+ query_builder_2 = query_builder.public_send(method_name, argument2)
+
+ first_sql = query_builder.to_sql
+ second_sql = query_builder_2.to_sql
+
+ expect(first_sql).not_to eq(second_sql)
+ end
+ end
+
+ describe "#initialize" do
+ it 'initializes with correct table' do
+ expect(builder.table.name).to eq(table_name.to_s)
+ end
+ end
+
+ describe '#where' do
+ context 'with simple conditions' do
+ it 'builds correct where query' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" = 'value1'
+ AND "test_table"."column2" = 'value2'
+ SQL
+
+ sql = builder.where(column1: 'value1', column2: 'value2').to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+ end
+
+ context 'with array conditions' do
+ it 'builds correct where query' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" IN (1, 2, 3)
+ SQL
+
+ sql = builder.where(column1: [1, 2, 3]).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+ end
+
+ it_behaves_like "generates correct sql on multiple calls to `to_sql`", :where, { column1: 'value1' },
+ { column2: 'value2' }
+
+ context 'with supported arel nodes' do
+ it 'builds a query using the In node' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" IN ('value1', 'value2')
+ SQL
+
+ sql = builder.where(builder.table[:column1].in(%w[value1 value2])).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds a query using the Equality node' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" = 'value1'
+ SQL
+
+ sql = builder.where(builder.table[:column1].eq('value1')).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds a query using the LessThan node' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" < 5
+ SQL
+
+ sql = builder.where(builder.table[:column1].lt(5)).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds a query using the LessThanOrEqual node' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" <= 5
+ SQL
+
+ sql = builder.where(builder.table[:column1].lteq(5)).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds a query using the GreaterThan node' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" > 5
+ SQL
+
+ sql = builder.where(builder.table[:column1].gt(5)).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds a query using the GreaterThanOrEqual node' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" >= 5
+ SQL
+
+ sql = builder.where(builder.table[:column1].gteq(5)).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+ end
+
+ context 'with unsupported arel nodes' do
+ it 'raises an error for the unsupported node' do
+ expect do
+ builder.where(builder.table[:column1].not_eq('value1')).to_sql
+ end.to raise_error(ArgumentError, /Unsupported Arel node type for QueryBuilder:/)
+ end
+ end
+ end
+
+ describe '#select' do
+ it 'builds correct select query with single field' do
+ expected_sql = <<~SQL.chomp
+ SELECT "test_table"."column1" FROM "test_table"
+ SQL
+
+ sql = builder.select(:column1).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds correct select query with multiple fields' do
+ expected_sql = <<~SQL.chomp
+ SELECT "test_table"."column1", "test_table"."column2" FROM "test_table"
+ SQL
+
+ sql = builder.select(:column1, :column2).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'adds new fields on multiple calls without duplicating' do
+ expected_sql = <<~SQL.chomp
+ SELECT "test_table"."column1", "test_table"."column2" FROM "test_table"
+ SQL
+
+ sql = builder.select(:column1).select(:column2).select(:column1).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it_behaves_like "generates correct sql on multiple calls to `to_sql`", :select, :column1, :column2
+ end
+
+ describe '#order' do
+ it 'builds correct order query with direction :desc' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ ORDER BY "test_table"."column1" DESC
+ SQL
+
+ sql = builder.order(:column1, :desc).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'builds correct order query with default direction asc' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ ORDER BY "test_table"."column1" ASC
+ SQL
+
+ sql = builder.order(:column1).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'appends orderings on multiple calls' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ ORDER BY "test_table"."column1" DESC,
+ "test_table"."column2" ASC
+ SQL
+
+ sql = builder.order(:column1, :desc).order(:column2, :asc).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'appends orderings for the same column when ordered multiple times' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ ORDER BY "test_table"."column1" DESC,
+ "test_table"."column1" ASC
+ SQL
+
+ sql = builder.order(:column1, :desc).order(:column1, :asc).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'raises error for invalid direction' do
+ expect do
+ builder.order(:column1, :invalid)
+ end.to raise_error(ArgumentError, "Invalid order direction 'invalid'. Must be :asc or :desc")
+ end
+
+ it_behaves_like "generates correct sql on multiple calls to `to_sql`", :order, :column1, :column2
+ end
+
+ describe '#limit' do
+ it 'builds correct limit query' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ LIMIT 10
+ SQL
+
+ sql = builder.limit(10).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'overrides previous limit value when called multiple times' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ LIMIT 20
+ SQL
+
+ sql = builder.limit(10).limit(20).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+ end
+
+ describe '#offset' do
+ it 'builds correct offset query' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ OFFSET 5
+ SQL
+
+ sql = builder.offset(5).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+
+ it 'overrides previous offset value when called multiple times' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ OFFSET 10
+ SQL
+
+ sql = builder.offset(5).offset(10).to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+ end
+
+ describe '#to_sql' do
+ it 'delegates to the Arel::SelectManager' do
+ expect(builder.send(:manager)).to receive(:to_sql)
+
+ builder.to_sql
+ end
+ end
+
+ describe '#to_redacted_sql' do
+ it 'calls ::ClickHouse::Redactor correctly' do
+ expect(::ClickHouse::Redactor).to receive(:redact).with(builder)
+
+ builder.to_redacted_sql
+ end
+ end
+
+ describe '#apply_conditions!' do
+ it 'applies conditions to the manager' do
+ manager = builder.send(:manager)
+ condition = Arel::Nodes::Equality.new(builder.table[:column1], 'value1')
+ builder.conditions << condition
+
+ expect(manager).to receive(:where).with(condition)
+
+ builder.send(:apply_conditions!)
+ end
+ end
+
+ describe 'method chaining', :freeze_time do
+ it 'builds correct SQL query when methods are chained' do
+ expected_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT "test_table"."column1", "test_table"."column2"
+ FROM "test_table"
+ WHERE "test_table"."column1" = 'value1'
+ AND "test_table"."column2" = 'value2'
+ AND "test_table"."created_at" <= '#{Date.today}'
+ ORDER BY "test_table"."column1" DESC
+ LIMIT 10
+ OFFSET 5
+ SQL
+
+ sql = builder
+ .select(:column1, :column2)
+ .where(column1: 'value1', column2: 'value2')
+ .where(builder.table[:created_at].lteq(Date.today))
+ .order(:column1, 'desc')
+ .limit(10)
+ .offset(5)
+ .to_sql
+
+ expect(sql).to eq(expected_sql)
+ end
+ end
+end
diff --git a/spec/lib/click_house/redactor_spec.rb b/spec/lib/click_house/redactor_spec.rb
new file mode 100644
index 00000000000..d8354b6cbb9
--- /dev/null
+++ b/spec/lib/click_house/redactor_spec.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::Redactor, feature_category: :database do
+ let(:builder) { ClickHouse::QueryBuilder.new(:test_table) }
+
+ describe '.redact' do
+ context 'when given simple conditions' do
+ let(:new_builder) { builder.where(column1: 'value1', column2: 'value2') }
+ let(:redacted_query) { described_class.redact(new_builder) }
+
+ it 'redacts equality conditions correctly' do
+ expected_redacted_sql = <<~SQL.chomp.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" = $1
+ AND "test_table"."column2" = $2
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+ end
+
+ context 'when given IN conditions' do
+ let(:new_builder) { builder.where(column1: %w[value1 value2 value3]) }
+ let(:redacted_query) { described_class.redact(new_builder) }
+
+ it 'redacts IN conditions correctly' do
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" IN ($1, $2, $3)
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+ end
+
+ context 'with supported arel nodes' do
+ it 'redacts a query using the In node' do
+ new_builder = builder.where(builder.table[:column1].in(%w[value1 value2]))
+ redacted_query = described_class.redact(new_builder)
+
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" IN ($1, $2)
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+
+ it 'redacts a query using the Equality node' do
+ new_builder = builder.where(builder.table[:column1].eq('value1'))
+ redacted_query = described_class.redact(new_builder)
+
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" = $1
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+
+ it 'redacts a query using the LessThan node' do
+ new_builder = builder.where(builder.table[:column1].lt(5))
+ redacted_query = described_class.redact(new_builder)
+
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" < $1
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+
+ it 'redacts a query using the LessThanOrEqual node' do
+ new_builder = builder.where(builder.table[:column1].lteq(5))
+ redacted_query = described_class.redact(new_builder)
+
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" <= $1
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+
+ it 'redacts a query using the GreaterThan node' do
+ new_builder = builder.where(builder.table[:column1].gt(5))
+ redacted_query = described_class.redact(new_builder)
+
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" > $1
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+
+ it 'redacts a query using the GreaterThanOrEqual node' do
+ new_builder = builder.where(builder.table[:column1].gteq(5))
+ redacted_query = described_class.redact(new_builder)
+
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" >= $1
+ SQL
+
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+ end
+
+ context 'with unsupported arel nodes' do
+ let(:unsupported_node) { Arel::Nodes::NotEqual.new(Arel::Table.new(:test_table)[:column1], 'value1') }
+ let(:manager) do
+ instance_double(
+ 'Arel::SelectManager',
+ constraints: [],
+ where: true,
+ to_sql: "SELECT * FROM \"test_table\""
+ )
+ end
+
+ let(:mocked_builder) do
+ instance_double(
+ 'ClickHouse::QueryBuilder',
+ conditions: [unsupported_node],
+ manager: manager
+ )
+ end
+
+ it 'raises an error for the unsupported node' do
+ expect do
+ described_class.redact(mocked_builder)
+ end.to raise_error(ArgumentError, /Unsupported Arel node type for Redactor:/)
+ end
+ end
+
+ context 'when method chaining is used' do
+ let(:new_builder) do
+ builder.where(column1: 'value1').where(column2: 'value2').where(builder.table[:column3].gteq(5))
+ end
+
+ let(:redacted_query) { described_class.redact(new_builder) }
+
+ it 'redacts chained conditions correctly' do
+ expected_redacted_sql = <<~SQL.lines(chomp: true).join(' ')
+ SELECT * FROM "test_table"
+ WHERE "test_table"."column1" = $1
+ AND "test_table"."column2" = $2
+ AND "test_table"."column3" >= $3
+ SQL
+ expect(redacted_query).to eq(expected_redacted_sql)
+ end
+ end
+
+ context 'when calling .redact multiple times' do
+ let(:new_builder) { builder.where(column1: 'value1', column2: 'value2') }
+ let(:first_redacted_query) { described_class.redact(new_builder) }
+ let(:second_redacted_query) { described_class.redact(new_builder) }
+
+ it 'produces consistent redacted SQL' do
+ expect(first_redacted_query).to eq(second_redacted_query)
+ end
+ end
+ end
+end