From b4999acadd3783e0953f04729cafc2fe0598db87 Mon Sep 17 00:00:00 2001 From: Sam Saffron Date: Sat, 29 Feb 2020 15:40:54 +1100 Subject: [PATCH] PERF: improve performance of category topic list In some cases CTE caused pathologically bad query plans. This optimises it so query runs by itself and caches for lifetime of the topic query object. This lightweight caching is done cause topic query will often execute two queries (one for pinned and one for non pinned) --- lib/topic_query.rb | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/topic_query.rb b/lib/topic_query.rb index bfb787ce9f1..f77d37e7464 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -669,20 +669,11 @@ class TopicQuery if options[:no_subcategories] result = result.where('categories.id = ?', category_id) else - sql = <<~SQL - categories.id IN ( - WITH RECURSIVE subcategories AS ( - SELECT :category_id id, 1 depth - UNION - SELECT categories.id, (subcategories.depth + 1) depth - FROM categories - JOIN subcategories ON subcategories.id = categories.parent_category_id - WHERE subcategories.depth < :max_category_nesting - ) - SELECT subcategories.id FROM subcategories - ) AND (categories.id = :category_id OR topics.id != categories.topic_id) + result = result.where(<<~SQL, subcategory_ids: subcategory_ids(category_id), category_id: category_id) + categories.id in (:subcategory_ids) AND ( + categories.topic_id <> topics.id OR categories.id = :category_id + ) SQL - result = result.where(sql, category_id: category_id, max_category_nesting: SiteSetting.max_category_nesting) end result = result.references(:categories) @@ -1061,6 +1052,29 @@ class TopicQuery private + def subcategory_ids(category_id) + @subcategory_ids ||= {} + @subcategory_ids[category_id] ||= + begin + sql = <<~SQL + WITH RECURSIVE subcategories AS ( + SELECT :category_id id, 1 depth + UNION + SELECT categories.id, (subcategories.depth + 1) depth + FROM categories + JOIN subcategories ON subcategories.id = categories.parent_category_id + WHERE subcategories.depth < :max_category_nesting + ) + SELECT id FROM subcategories + SQL + DB.query_single( + sql, + category_id: category_id, + max_category_nesting: SiteSetting.max_category_nesting + ) + end + end + def sanitize_sql_array(input) ActiveRecord::Base.public_send(:sanitize_sql_array, input.join(',')) end