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 'lib/click_house/query_builder.rb')
-rw-r--r--lib/click_house/query_builder.rb137
1 files changed, 137 insertions, 0 deletions
diff --git a/lib/click_house/query_builder.rb b/lib/click_house/query_builder.rb
new file mode 100644
index 00000000000..a2136420b2f
--- /dev/null
+++ b/lib/click_house/query_builder.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+# rubocop:disable CodeReuse/ActiveRecord
+module ClickHouse
+ class QueryBuilder
+ attr_reader :table
+ attr_accessor :conditions, :manager
+
+ VALID_NODES = [
+ Arel::Nodes::In,
+ Arel::Nodes::Equality,
+ Arel::Nodes::LessThan,
+ Arel::Nodes::LessThanOrEqual,
+ Arel::Nodes::GreaterThan,
+ Arel::Nodes::GreaterThanOrEqual
+ ].freeze
+
+ def initialize(table_name)
+ @table = Arel::Table.new(table_name)
+ @manager = Arel::SelectManager.new(Arel::Table.engine).from(@table).project(Arel.star)
+ @conditions = []
+ end
+
+ # The `where` method currently does only supports IN and equal to queries along
+ # with above listed VALID_NODES.
+ # For example, using a range (start_date..end_date) will result in incorrect SQL.
+ # If you need to query a range, use greater than and less than conditions with Arel.
+ #
+ # Correct usage:
+ # query.where(query.table[:created_at].lteq(Date.today)).to_sql
+ # "SELECT * FROM \"table\" WHERE \"table\".\"created_at\" <= '2023-08-01'"
+ #
+ # This also supports array conditions which will result in an IN query.
+ # query.where(entity_id: [1,2,3]).to_sql
+ # "SELECT * FROM \"table\" WHERE \"table\".\"entity_id\" IN (1, 2, 3)"
+ #
+ # Range support and more `Arel::Nodes` could be considered for future iterations.
+ # @return [ClickHouse::QueryBuilder] New instance of query builder.
+ def where(conditions)
+ validate_condition_type!(conditions)
+
+ new_instance = deep_clone
+
+ if conditions.is_a?(Arel::Nodes::Node)
+ new_instance.conditions << conditions
+ else
+ add_conditions_to(new_instance, conditions)
+ end
+
+ new_instance
+ end
+
+ def select(*fields)
+ new_instance = deep_clone
+
+ existing_fields = new_instance.manager.projections.filter_map do |projection|
+ if projection.is_a?(Arel::Attributes::Attribute)
+ projection.name.to_s
+ elsif projection.to_s == '*'
+ nil
+ end
+ end
+
+ new_projections = existing_fields + fields.map(&:to_s)
+
+ new_instance.manager.projections = new_projections.uniq.map { |field| new_instance.table[field] }
+ new_instance
+ end
+
+ def order(field, direction = :asc)
+ validate_order_direction!(direction)
+
+ new_instance = deep_clone
+
+ new_order = new_instance.table[field].public_send(direction.to_s.downcase) # rubocop:disable GitlabSecurity/PublicSend
+ new_instance.manager.order(new_order)
+
+ new_instance
+ end
+
+ def limit(count)
+ manager.take(count)
+ self
+ end
+
+ def offset(count)
+ manager.skip(count)
+ self
+ end
+
+ def to_sql
+ apply_conditions!
+ manager.to_sql
+ end
+
+ def to_redacted_sql
+ ::ClickHouse::Redactor.redact(self)
+ end
+
+ private
+
+ def validate_condition_type!(condition)
+ return unless condition.is_a?(Arel::Nodes::Node) && VALID_NODES.exclude?(condition.class)
+
+ raise ArgumentError, "Unsupported Arel node type for QueryBuilder: #{condition.class.name}"
+ end
+
+ def add_conditions_to(instance, conditions)
+ conditions.each do |key, value|
+ instance.conditions << if value.is_a?(Array)
+ instance.table[key].in(value)
+ else
+ instance.table[key].eq(value)
+ end
+ end
+ end
+
+ def deep_clone
+ new_instance = self.class.new(table.name)
+ new_instance.manager = manager.clone
+ new_instance.conditions = conditions.map(&:clone)
+ new_instance
+ end
+
+ def apply_conditions!
+ manager.constraints.clear
+ conditions.each { |condition| manager.where(condition) }
+ end
+
+ def validate_order_direction!(direction)
+ return if %w[asc desc].include?(direction.to_s.downcase)
+
+ raise ArgumentError, "Invalid order direction '#{direction}'. Must be :asc or :desc"
+ end
+ end
+end
+# rubocop:enable CodeReuse/ActiveRecord