このコミットが含まれているのは:
2026-10-27 18:49:58 +09:00
コミット f9f31812a9
22個のファイルの変更、201行の追加、59行の削除
+52 -11
ファイルの表示
@@ -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)