diff --git a/config/site_settings.yml b/config/site_settings.yml index 09b886ee5fb..46699dcd6ce 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1792,6 +1792,9 @@ backups: hidden: true search: + use_pg_headlines_for_excerpt: + default: false + hidden: true search_ranking_normalization: default: "0" hidden: true diff --git a/lib/search.rb b/lib/search.rb index 35adbf943cb..efc870b465a 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -2,6 +2,7 @@ class Search DIACRITICS ||= /([\u0300-\u036f]|[\u1AB0-\u1AFF]|[\u1DC0-\u1DFF]|[\u20D0-\u20FF])/ + HIGHLIGHT_CSS_CLASS = 'search-highlight' cattr_accessor :preloaded_topic_custom_fields self.preloaded_topic_custom_fields = Set.new @@ -726,12 +727,18 @@ class Search def single_topic(id) if @opts[:restrict_to_archetype].present? archetype = @opts[:restrict_to_archetype] == Archetype.default ? Archetype.default : Archetype.private_message - post = Post.joins(:topic) - .where("topics.id = :id AND topics.archetype = :archetype AND posts.post_number = 1", id: id, archetype: archetype) - .first + + post = posts_scope + .joins(:topic) + .find_by( + "topics.id = :id AND topics.archetype = :archetype AND posts.post_number = 1", + id: id, + archetype: archetype + ) else - post = Post.find_by(topic_id: id, post_number: 1) + post = posts_scope.find_by(topic_id: id, post_number: 1) end + return nil unless @guardian.can_see?(post) @results.add(post) @@ -1096,7 +1103,7 @@ class Search def aggregate_posts(post_sql) return [] unless post_sql - posts_eager_loads(Post) + posts_scope(posts_eager_loads(Post)) .joins("JOIN (#{post_sql}) x ON x.id = posts.topic_id AND x.post_number = posts.post_number") .order('row_number') end @@ -1128,7 +1135,7 @@ class Search def topic_search if @search_context.is_a?(Topic) - posts = posts_eager_loads(posts_query(limit)) + posts = posts_scope(posts_eager_loads(posts_query(limit))) .where('posts.topic_id = ?', @search_context.id) posts.each do |post| @@ -1150,4 +1157,17 @@ class Search query.includes(topic: topic_eager_loads) end + def posts_scope(default_scope = Post.all) + if SiteSetting.use_pg_headlines_for_excerpt + default_scope + .joins("INNER JOIN post_search_data pd ON pd.post_id = posts.id") + .select( + "TS_HEADLINE(#{default_ts_config}, pd.raw_data, PLAINTO_TSQUERY('#{@term.present? ? PG::Connection.escape_string(@term) : nil}'), 'ShortWord=0, MaxFragments=1, MinWords=50, MaxWords=51, StartSel='''', StopSel=''''') AS headline", + default_scope.arel.projections + ) + else + default_scope + end + end + end diff --git a/lib/search/grouped_search_results.rb b/lib/search/grouped_search_results.rb index ba112b50344..cac0c52897f 100644 --- a/lib/search/grouped_search_results.rb +++ b/lib/search/grouped_search_results.rb @@ -85,8 +85,12 @@ class Search } if post.post_search_data.version > SearchIndexer::MIN_POST_REINDEX_VERSION - opts[:cooked] = post.post_search_data.raw_data - opts[:scrub] = false + if SiteSetting.use_pg_headlines_for_excerpt + return post.headline + else + opts[:cooked] = post.post_search_data.raw_data + opts[:scrub] = false + end else opts[:cooked] = post.cooked end diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index d0def198fbc..5a7de3bf611 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -410,27 +410,31 @@ describe Search do end let(:expected_blurb) do - "...quire content longer than the typical test post raw content. It really is some long content, folks. elephant" + "hundred characters to satisfy any test conditions that require content longer than the typical test post raw content. It really is some long content, folks. elephant" end it 'returns the post' do + SiteSetting.use_pg_headlines_for_excerpt = true + result = Search.execute('elephant', type_filter: 'topic', include_blurbs: true ) - expect(result.posts).to contain_exactly(reply) - expect(result.blurb(reply)).to eq(expected_blurb) + expect(result.posts.map(&:id)).to contain_exactly(reply.id) + expect(result.blurb(result.posts.first)).to eq(expected_blurb) end it 'returns the right post and blurb for searches with phrase' do + SiteSetting.use_pg_headlines_for_excerpt = true + result = Search.execute('"elephant"', type_filter: 'topic', include_blurbs: true ) - expect(result.posts).to contain_exactly(reply) - expect(result.blurb(reply)).to eq(expected_blurb) + expect(result.posts.map(&:id)).to contain_exactly(reply.id) + expect(result.blurb(result.posts.first)).to eq(expected_blurb) end it 'applies a small penalty to closed topic when ranking' do diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb index bb49a6ad224..7a54ced1a24 100644 --- a/spec/requests/search_controller_spec.rb +++ b/spec/requests/search_controller_spec.rb @@ -99,6 +99,8 @@ describe SearchController do end it "can search correctly" do + SiteSetting.use_pg_headlines_for_excerpt = true + get "/search/query.json", params: { term: 'awesome' } @@ -109,11 +111,11 @@ describe SearchController do expect(data['posts'].length).to eq(2) expect(data['posts'][0]['id']).to eq(awesome_post_2.id) - expect(data['posts'][0]['blurb']).to eq(awesome_post_2.raw) + expect(data['posts'][0]['blurb']).to eq("this is my really awesome post") expect(data['topics'][0]['id']).to eq(awesome_post_2.topic_id) expect(data['posts'][1]['id']).to eq(awesome_post.id) - expect(data['posts'][1]['blurb']).to eq(awesome_post.raw) + expect(data['posts'][1]['blurb']).to eq("this is my really awesome post") expect(data['topics'][1]['id']).to eq(awesome_post.topic_id) end