このコミットが含まれているのは:
@@ -9,6 +9,7 @@ class CountsTableQuery
|
||||
attr_reader :population, :axes, :page, :limit, :mode
|
||||
|
||||
def initialize(params)
|
||||
params = CountsResourceDefinition.normalise_params(params)
|
||||
QueryState.validate!(params, KEYS + ['measures'])
|
||||
raise ArgumentError unless params.values.all? { _1.is_a?(String) }
|
||||
|
||||
@@ -26,7 +27,7 @@ class CountsTableQuery
|
||||
|
||||
@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.fields.keys + ['latest'] +
|
||||
(@definition.resource == 'posts' ? ['tags', 'parents'] : []) +
|
||||
(@definition.resource == 'tags' ? ['parents', 'deprecated'] : [])
|
||||
raise ArgumentError if @sort.map(&:first).uniq.length != @sort.length
|
||||
@@ -43,9 +44,10 @@ class CountsTableQuery
|
||||
ApplicationRecord.transaction do
|
||||
result = metadata.merge(mode == 'summary' ? summary : details)
|
||||
visible = mode == 'summary' ? result[:visible_output_count] : result[:count]
|
||||
totals = output_totals(visible)
|
||||
result.merge(visible_output_count: visible,
|
||||
output_count: visible,
|
||||
output_count_scope: 'authorised_results',
|
||||
**totals,
|
||||
output_count_scope: 'before_authorisation',
|
||||
output_count_unit: 'table_body_rows',
|
||||
page_grand_total_repeated: mode == 'summary' && axes.rows.any? &&
|
||||
result[:group_count] > limit)
|
||||
@@ -54,12 +56,8 @@ class CountsTableQuery
|
||||
|
||||
def metadata
|
||||
@population_counts ||= HistoryAccess.population_counts(@definition.model)
|
||||
# Global exclusions are not exclusions for this filtered query. Do not expose
|
||||
# private predicate matches through a condition-dependent count.
|
||||
exclusions_known = @population_counts[:excluded_count].zero?
|
||||
@population_counts.merge(
|
||||
excluded_count: exclusions_known ? 0 : nil,
|
||||
excluded_count_scope: exclusions_known ? 'current_query' : 'unavailable_private_history',
|
||||
excluded_count: nil, excluded_count_scope: 'current_query',
|
||||
population_count_scope: 'all_recorded_versions',
|
||||
page:, limit:, execution_at: population.at.iso8601(6), at: @params['at'].presence,
|
||||
definition: population.history ? 'history' : 'latest',
|
||||
@@ -186,7 +184,7 @@ class CountsTableQuery
|
||||
[key, row.public_send(@definition.column(key))]
|
||||
end
|
||||
attributes.merge('id' => row.id, 'resource_id' => row[@definition.keys.first],
|
||||
'is_latest' => row.attributes['counts_latest'].to_i == 1,
|
||||
'latest' => row.attributes['counts_latest'].to_i == 1,
|
||||
'created_by_user' => row.created_by_user_id &&
|
||||
{ id: row.created_by_user_id, name: users[row.created_by_user_id] })
|
||||
end
|
||||
@@ -217,12 +215,55 @@ class CountsTableQuery
|
||||
"(SELECT COUNT(*) FROM (SELECT #{ keys } FROM cells GROUP BY #{ keys }) subtotal_rows)"
|
||||
end
|
||||
expressions = [total.to_s, '1'] + subtotals
|
||||
@output_row_count ||= @connection.select_value(<<~SQL).to_i
|
||||
@connection.select_value(<<~SQL).to_i
|
||||
#{ prefix }
|
||||
SELECT /*+ MAX_EXECUTION_TIME(10000) */ #{ expressions.join(' + ') } AS output_rows
|
||||
SQL
|
||||
end
|
||||
|
||||
def output_totals(visible_rows)
|
||||
return @output_totals if @output_totals
|
||||
|
||||
if HistoryAccess.admin? || @population_counts[:excluded_count].zero?
|
||||
return @output_totals = { output_count: visible_rows, excluded_count: 0 }
|
||||
end
|
||||
# Only numerical aggregates leave this population. Labels, snapshots and
|
||||
# detail pagination continue to use the authorised population exclusively.
|
||||
source = CountsPopulation.new(
|
||||
@params.merge('execution_at' => population.at.iso8601(6)), authorisation_flg: false)
|
||||
records = source.scope.except(:select).optimizer_hints('MAX_EXECUTION_TIME(10000)').count
|
||||
denied = source.scope.except(:select)
|
||||
.where(Arel.sql("NOT (#{ HistoryAccess.sql(@definition.model, 'v') })"))
|
||||
.optimizer_hints('MAX_EXECUTION_TIME(10000)').count
|
||||
rows = records
|
||||
if mode == 'summary'
|
||||
unrestricted_axes = CountsAxes.new(source)
|
||||
relation = source.scope
|
||||
joins = unrestricted_axes.joins
|
||||
relation = relation.joins(joins) unless joins.empty?
|
||||
expressions = unrestricted_axes.expressions
|
||||
keys = axes.rows.each_index.map { "a#{ _1 }" }
|
||||
if keys.empty?
|
||||
rows = 1
|
||||
else
|
||||
selected = expressions.each_with_index.map { |expression, index|
|
||||
"#{ expression } AS a#{ index }"
|
||||
}
|
||||
sql = relation.except(:select).select(Arel.sql(selected.join(', ')))
|
||||
.distinct.to_sql
|
||||
prefix = "WITH cells AS (#{ sql })"
|
||||
groups = @connection.select_value(<<~SQL).to_i
|
||||
#{ prefix }
|
||||
SELECT /*+ MAX_EXECUTION_TIME(10000) */ COUNT(*) FROM (
|
||||
SELECT #{ keys.join(', ') } FROM cells GROUP BY #{ keys.join(', ') }
|
||||
) output_groups
|
||||
SQL
|
||||
rows = output_row_count(prefix, keys, CountsPivotLayout.depths(keys, @sort), groups)
|
||||
end
|
||||
end
|
||||
@output_totals = { output_count: rows, excluded_count: denied }
|
||||
end
|
||||
|
||||
def locale = @locale ||= Locale.find_by!(code: @params.fetch('locale', 'ja'))
|
||||
|
||||
def limited_columns(prefix)
|
||||
@@ -284,7 +325,7 @@ class CountsTableQuery
|
||||
end
|
||||
|
||||
def detail_expression(key)
|
||||
return "(#{ population.latest_sql })" if key == 'is_latest'
|
||||
return "(#{ population.latest_sql })" if key == 'latest'
|
||||
aliases = { 'tags' => 'tags_json', 'parents' => @definition.resource == 'posts' ?
|
||||
'parent_post_ids' : 'parent_tag_ids', 'deprecated' => 'deprecated_at' }
|
||||
field = aliases.fetch(key, key)
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする