gitlab-org--gitlab-foss/lib/gitlab/usage/metrics/query.rb

117 lines
3.7 KiB
Ruby

# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
class Query
class << self
def for(operation, relation, column = nil, **extra)
case operation
when :count
count(relation, column)
when :distinct_count
distinct_count(relation, column)
when :sum
sum(relation, column)
when :average
average(relation, column)
when :estimate_batch_distinct_count
estimate_batch_distinct_count(relation, column)
when :histogram
histogram(relation, column, **extra)
else
raise ArgumentError, "#{operation} operation not supported"
end
end
private
def count(relation, column = nil)
raw_count_sql(relation, column)
end
def distinct_count(relation, column = nil)
raw_count_sql(relation, column, true)
end
def sum(relation, column)
raw_sum_sql(relation, column)
end
def average(relation, column)
raw_average_sql(relation, column)
end
def estimate_batch_distinct_count(relation, column = nil)
raw_count_sql(relation, column, true)
end
# rubocop: disable CodeReuse/ActiveRecord
def histogram(relation, column, buckets:, bucket_size: buckets.size)
count_grouped = relation.group(column).select(Arel.star.count.as('count_grouped'))
cte = Gitlab::SQL::CTE.new(:count_cte, count_grouped)
bucket_segments = bucket_size - 1
width_bucket = Arel::Nodes::NamedFunction
.new('WIDTH_BUCKET', [cte.table[:count_grouped], buckets.first, buckets.last, bucket_segments])
.as('buckets')
query = cte
.table
.project(width_bucket, cte.table[:count])
.group('buckets')
.order('buckets')
.with(cte.to_arel)
query.to_sql
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def raw_count_sql(relation, column, distinct = false)
column ||= relation.primary_key
node = node_to_operate(relation, column)
relation.unscope(:order).select(node.count(distinct)).to_sql
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def raw_sum_sql(relation, column)
node = node_to_operate(relation, column)
relation.unscope(:order).select(node.sum).to_sql
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def raw_average_sql(relation, column)
node = node_to_operate(relation, column)
relation.unscope(:order).select(node.average).to_sql
end
# rubocop: enable CodeReuse/ActiveRecord
def node_to_operate(relation, column)
if join_relation?(relation) && joined_column?(column)
table_name, column_name = column.split(".")
Arel::Table.new(table_name)[column_name]
else
relation.all.table[column]
end
end
def join_relation?(relation)
relation.is_a?(ActiveRecord::Relation) && relation.joins_values.present?
end
# checks if the passed column is of format "table.column"
def joined_column?(column)
column.is_a?(String) && column.include?(".")
end
end
end
end
end
end