このコミットが含まれているのは:
@@ -5,11 +5,11 @@ class CountsTableQuery
|
||||
|
||||
KEYS = ['resource', 'definition', 'mode', 'at', 'execution_at', 'from', 'through',
|
||||
'timezone', 'rows', 'cols', 'conditions', 'drill', 'tags', 'tag_refs', 'match',
|
||||
'tag', 'kind', 'resource_id', 'user_id', 'page', 'limit', 'sort', 'locale'].freeze
|
||||
'tag', 'kind', 'resource_id', 'user_id', 'page', 'limit', 'sort', 'locale', 'measures', 'column_order'].freeze
|
||||
attr_reader :population, :axes, :page, :limit, :mode
|
||||
|
||||
def initialize(params)
|
||||
raise ArgumentError if (params.keys - KEYS).any?
|
||||
QueryState.validate!(params, KEYS + ['measures'])
|
||||
raise ArgumentError unless params.values.all? { _1.is_a?(String) }
|
||||
|
||||
params = CountsDefaults.for(params).slice(*KEYS).merge(params)
|
||||
@@ -24,14 +24,14 @@ class CountsTableQuery
|
||||
raise ArgumentError unless page.between?(1, 1_000_000) && limit.between?(1, 100)
|
||||
raise ArgumentError unless ['summary', 'details'].include?(mode)
|
||||
|
||||
@sort = params.fetch('sort', '').split(',').map { _1.split(':', -1) }
|
||||
@sort = QueryState.list(params.fetch('sort', '')).map { _1.split(':', -1) }
|
||||
allowed = mode == 'summary' ? axes.all.each_index.map { "a#{ _1 }" } + ['count'] :
|
||||
@definition.fields.keys + ['is_latest'] +
|
||||
(@definition.resource == 'posts' ? ['tags', 'parents'] : []) +
|
||||
(@definition.resource == 'tags' ? ['parents', 'deprecated'] : [])
|
||||
raise ArgumentError if @sort.map(&:first).uniq.length != @sort.length
|
||||
@sort.each do |key, direction, *extra|
|
||||
measure = mode == 'summary' && key&.match?(/\Ameasure_[0-9a-f]+\z/)
|
||||
measure = mode == 'summary' && key&.match?(/\Am[0-9]{1,2}\z/)
|
||||
measure_values(key) if measure
|
||||
if extra.any? || (!allowed.include?(key) && !measure) || !['asc', 'desc'].include?(direction)
|
||||
raise ArgumentError
|
||||
@@ -46,7 +46,8 @@ class CountsTableQuery
|
||||
end
|
||||
|
||||
def metadata
|
||||
{ page:, limit:, execution_at: population.at.iso8601(6), at: @params['at'].presence,
|
||||
{ population_count_scope: 'all_recorded_versions', population_count: HistoryAccess.population_count(@definition.model),
|
||||
page:, limit:, execution_at: population.at.iso8601(6), at: @params['at'].presence,
|
||||
definition: population.history ? 'history' : 'latest',
|
||||
timezone: population.timezone.identifier, rows: axes.rows, cols: axes.columns,
|
||||
fields: @definition.metadata,
|
||||
@@ -76,7 +77,7 @@ class CountsTableQuery
|
||||
measure_joins = []
|
||||
extras = []
|
||||
row_sort = @sort.each_with_index.filter_map do |(key, direction), index|
|
||||
if key == 'count' || key.start_with?('measure_')
|
||||
if key == 'count' || key.match?(/\Am[0-9]+\z/)
|
||||
values = key == 'count' ? [] : measure_values(key)
|
||||
relation = measure_relation(values)
|
||||
matches = row_keys.map { "grouped.#{ _1 } <=> sort#{ index }.#{ _1 }" }
|
||||
@@ -125,7 +126,12 @@ class CountsTableQuery
|
||||
row_values = records.map { |record| row_keys.map { record[_1] } }.uniq
|
||||
row_depths = CountsPivotLayout.depths(row_keys, @sort)
|
||||
column_keys = axes.columns.each_index.map { "a#{ axes.rows.length + _1 }" }
|
||||
column_depths = CountsPivotLayout.depths(column_keys, @sort, measures: false)
|
||||
column_sort = @sort
|
||||
if @params.key?('column_order') && column_keys.any? &&
|
||||
!(@sort.first&.first == 'count' || @sort.first&.first&.match?(/\Am[0-9]+\z/))
|
||||
column_sort = [[column_keys.first, 'asc']] + @sort
|
||||
end
|
||||
column_depths = CountsPivotLayout.depths(column_keys, column_sort, measures: false)
|
||||
row_headers = CountsPivotLayout.headers(row_values, row_depths,
|
||||
next_values: next_row && row_keys.map { next_row[_1] }, grand: axes.rows.any?)
|
||||
if axes.rows.empty? && axes.columns.any?
|
||||
@@ -197,6 +203,15 @@ class CountsTableQuery
|
||||
order = directions.map { |key, direction|
|
||||
"#{ key } IS NULL ASC, #{ key } #{ direction.upcase }"
|
||||
}.join(', ')
|
||||
custom = QueryState.records(@params, 'column_order')
|
||||
if custom.any?
|
||||
raise ArgumentError unless custom.map { _1['value'] }.uniq.length == custom.length
|
||||
|
||||
cases = custom.each_with_index.map do |record, index|
|
||||
"WHEN #{ keys.first } <=> #{ @connection.quote(record['value']) } THEN #{ index }"
|
||||
end.join(' ')
|
||||
order = "CASE #{ cases } ELSE #{ custom.length } END, #{ order }"
|
||||
end
|
||||
values = @connection.select_rows(<<~SQL)
|
||||
#{ prefix }
|
||||
SELECT /*+ MAX_EXECUTION_TIME(10000) */ DISTINCT #{ keys.join(', ') } FROM cells
|
||||
@@ -225,7 +240,11 @@ class CountsTableQuery
|
||||
end
|
||||
|
||||
def measure_values(key)
|
||||
value = JSON.parse([key.delete_prefix('measure_')].pack('H*'))
|
||||
records = QueryState.records(@params, 'measures').select { _1['key'] == key }
|
||||
raise ArgumentError if records.empty?
|
||||
raise ArgumentError unless records.map { _1['axis'] }.sort == (0...records.length).to_a
|
||||
|
||||
value = records.sort_by { _1['axis'] }.map { _1['value'] }
|
||||
unless (value.is_a?(Array) && value.length <= axes.columns.length &&
|
||||
value.all? { _1.nil? || _1.is_a?(String) || _1.is_a?(Numeric) })
|
||||
raise ArgumentError
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする