From 11939fa8b93d77c7344c62c90fcfa72f598e2af4 Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Fri, 12 Aug 2016 13:04:46 -0400
Subject: [PATCH] PERF: Avoid some more count queries when fetching more
 results

---
 lib/search.rb | 51 ++++++++++++++++++++++++++++++---------------------
 1 file changed, 30 insertions(+), 21 deletions(-)

diff --git a/lib/search.rb b/lib/search.rb
index c09907716..91f2833f2 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -566,16 +566,8 @@ class Search
           posts = posts.joins('JOIN users u ON u.id = posts.user_id')
           posts = posts.where("posts.raw  || ' ' || u.username || ' ' || COALESCE(u.name, '') ilike ?", "%#{term_without_quote}%")
         else
-
-
           posts = posts.where("post_search_data.search_data @@ #{ts_query}")
 
-          min_id = Search.min_post_id
-          if min_id > 0
-            fast_query = posts.dup.where("post_search_data.post_id >= #{min_id}")
-            posts = fast_query if fast_query.dup.count >= 50
-          end
-
           exact_terms = @term.scan(/"([^"]+)"/).flatten
           exact_terms.each do |exact|
             posts = posts.where("posts.raw ilike ?", "%#{exact}%")
@@ -687,34 +679,51 @@ class Search
       @ts_query_cache[(locale || query_locale) + " " + @term] ||= Search.ts_query(@term, locale)
     end
 
-    def aggregate_search(opts = {})
+    def wrap_rows(query)
+      "SELECT *, row_number() over() row_number FROM (#{query.to_sql}) xxx"
+    end
 
+    def aggregate_post_sql(opts)
       min_or_max = @order == :latest ? "max" : "min"
 
-      post_sql =
+      query =
         if @order == :likes
           # likes are a pain to aggregate so skip
           posts_query(@limit, private_messages: opts[:private_messages])
             .select('topics.id', "post_number")
-            .to_sql
         else
-          posts_query(@limit, aggregate_search: true,
-                              private_messages: opts[:private_messages])
+          posts_query(@limit, aggregate_search: true, private_messages: opts[:private_messages])
             .select('topics.id', "#{min_or_max}(post_number) post_number")
             .group('topics.id')
-            .to_sql
         end
 
+      min_id = Search.min_post_id
+      if min_id > 0
+        low_set = query.dup.where("post_search_data.post_id < #{min_id}")
+        high_set = query.where("post_search_data.post_id >= #{min_id}")
+
+        return { default: wrap_rows(high_set), remaining: wrap_rows(low_set) }
+      end
+
       # double wrapping so we get correct row numbers
-      post_sql = "SELECT *, row_number() over() row_number FROM (#{post_sql}) xxx"
+      { default: wrap_rows(query) }
+    end
 
-      posts = Post.includes(:topic => :category)
-                  .includes(:user)
-                  .joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number")
-                  .order('row_number')
+    def aggregate_posts(post_sql)
+      return [] unless post_sql
 
-      posts.each do |post|
-        @results.add(post)
+      Post.includes(:topic => :category)
+        .includes(:user)
+        .joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number")
+        .order('row_number')
+    end
+
+    def aggregate_search(opts = {})
+      post_sql = aggregate_post_sql(opts)
+
+      aggregate_posts(post_sql[:default]).each {|p| @results.add(p)}
+      if @results.posts.size < @limit
+        aggregate_posts(post_sql[:remaining]).each {|p| @results.add(p) }
       end
     end