60 行
2.7 KiB
Ruby
60 行
2.7 KiB
Ruby
# Bounded, authorised candidates at the same recorded point as the Counts query.
|
|
class CountsCandidates
|
|
KEYS = ['resource', 'field', 'query', 'category', 'page', 'locale', 'at',
|
|
'execution_at', 'timezone'].freeze
|
|
LIMIT = 20
|
|
|
|
def initialize(params)
|
|
QueryState.validate!(params, KEYS)
|
|
raise ArgumentError unless params.fetch('resource') == 'posts'
|
|
raise ArgumentError unless ['tags', 'tags_json'].include?(params.fetch('field'))
|
|
|
|
@params = params
|
|
@page = Integer(params.fetch('page', '1'), 10)
|
|
raise ArgumentError unless @page.between?(1, 100_000)
|
|
raise ArgumentError if params.fetch('query', '').bytesize > 1000
|
|
category = params['category']
|
|
raise ArgumentError if category.present? && !Tag.categories.key?(category)
|
|
end
|
|
|
|
def call
|
|
options = @params.slice('locale', 'at', 'execution_at', 'timezone')
|
|
.merge('definition' => 'latest', 'resource' => 'tags')
|
|
internal = CountsPopulation.new(options)
|
|
external = CountsPopulation.new(options.merge('resource' => 'nico_tags',
|
|
'execution_at' => internal.at.iso8601(6)))
|
|
connection = TagVersion.connection
|
|
sources = [[internal, 'internal', 'v.category', 'v.names'],
|
|
[external, 'external', 'NULL', 'JSON_OBJECT()']]
|
|
sources = sources.map do |population, kind, category, names|
|
|
scope = population.scope.except(:select)
|
|
if @params['query'].present?
|
|
pattern = "%#{ ApplicationRecord.sanitize_sql_like(@params['query']) }%"
|
|
scope = scope.where("(v.name LIKE ? OR CAST(v.tag_id AS CHAR) = ? OR EXISTS (" +
|
|
"SELECT 1 FROM JSON_TABLE(COALESCE(JSON_EXTRACT(#{ names }, '$**.name'), " +
|
|
"JSON_ARRAY()), '$[*]' COLUMNS (name TEXT PATH '$')) candidate_name " +
|
|
'WHERE candidate_name.name LIKE ?))', pattern, @params['query'], pattern)
|
|
end
|
|
scope.select(Arel.sql(
|
|
"CONCAT('#{ kind }:', v.tag_id) AS value, v.tag_id AS id, " +
|
|
"v.name AS name, #{ names } AS names, #{ category } AS category, '#{ kind }' AS kind"))
|
|
.to_sql
|
|
end
|
|
clauses = []
|
|
binds = []
|
|
if @params['category'].present?
|
|
clauses << 'category = ?'
|
|
binds << @params['category']
|
|
end
|
|
where = clauses.empty? ? '' : "WHERE #{ clauses.join(' AND ') }"
|
|
sql = <<~SQL
|
|
SELECT /*+ MAX_EXECUTION_TIME(10000) */ * FROM (#{ sources.join(' UNION ALL ') }) choices
|
|
#{ where } ORDER BY kind, id LIMIT #{ LIMIT + 1 } OFFSET #{ (@page - 1) * LIMIT }
|
|
SQL
|
|
rows = connection.select_all(ApplicationRecord.sanitize_sql_array([sql, *binds])).to_a
|
|
rows.each { _1['names'] = JSON.parse(_1['names']) if _1['names'].is_a?(String) }
|
|
{ rows: rows.first(LIMIT), page: @page, more: rows.length > LIMIT,
|
|
execution_at: internal.at.iso8601(6) }
|
|
end
|
|
end
|