pg_search 2.3.7 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +38 -37
  3. data/.github/workflows/codeql.yml +86 -0
  4. data/.gitignore +0 -1
  5. data/.standard.yml +2 -2
  6. data/CHANGELOG.md +81 -64
  7. data/CONTRIBUTING.md +1 -1
  8. data/Gemfile +4 -2
  9. data/LICENSE +1 -1
  10. data/README.md +89 -87
  11. data/Rakefile +1 -7
  12. data/doc/plans/2026-09-05-arel-stack.md +43 -0
  13. data/doc/plans/2026-09-06-arel-remaining-expressions.md +48 -0
  14. data/lib/pg_search/configuration/association.rb +37 -23
  15. data/lib/pg_search/configuration/column.rb +17 -12
  16. data/lib/pg_search/configuration/foreign_column.rb +3 -5
  17. data/lib/pg_search/configuration.rb +10 -26
  18. data/lib/pg_search/features/dmetaphone.rb +3 -7
  19. data/lib/pg_search/features/feature.rb +8 -4
  20. data/lib/pg_search/features/trigram.rb +6 -20
  21. data/lib/pg_search/features/tsearch.rb +47 -61
  22. data/lib/pg_search/features.rb +2 -0
  23. data/lib/pg_search/migration/generator.rb +1 -5
  24. data/lib/pg_search/model.rb +6 -8
  25. data/lib/pg_search/multisearch/rebuilder.rb +69 -64
  26. data/lib/pg_search/normalizer.rb +40 -11
  27. data/lib/pg_search/scope_options.rb +32 -49
  28. data/lib/pg_search/version.rb +1 -1
  29. data/pg_search.gemspec +5 -5
  30. data/spec/integration/associations_spec.rb +133 -0
  31. data/spec/integration/deprecation_spec.rb +5 -1
  32. data/spec/integration/pagination_spec.rb +1 -0
  33. data/spec/integration/pg_search_spec.rb +78 -1
  34. data/spec/integration/single_table_inheritance_spec.rb +2 -0
  35. data/spec/lib/pg_search/configuration/association_spec.rb +34 -58
  36. data/spec/lib/pg_search/configuration/column_spec.rb +73 -15
  37. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +53 -20
  38. data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
  39. data/spec/lib/pg_search/features/trigram_spec.rb +8 -9
  40. data/spec/lib/pg_search/features/tsearch_spec.rb +8 -8
  41. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +251 -211
  42. data/spec/lib/pg_search/multisearch_spec.rb +16 -16
  43. data/spec/lib/pg_search/multisearchable_spec.rb +8 -0
  44. data/spec/lib/pg_search/normalizer_spec.rb +64 -11
  45. data/spec/lib/pg_search_spec.rb +6 -21
  46. data/spec/spec_helper.rb +0 -13
  47. metadata +11 -15
  48. data/spec/.rubocop.yml +0 -27
  49. data/spec/integration/.rubocop.yml +0 -11
@@ -19,7 +19,8 @@ describe PgSearch::Configuration::Association do
19
19
 
20
20
  model do
21
21
  include PgSearch::Model
22
- has_one :avatar, class_name: "Avatar"
22
+
23
+ has_one :avatar
23
24
  belongs_to :site
24
25
 
25
26
  pg_search_scope :with_avatar, associated_against: {avatar: :url}
@@ -34,7 +35,8 @@ describe PgSearch::Configuration::Association do
34
35
 
35
36
  model do
36
37
  include PgSearch::Model
37
- has_many :users, class_name: "User"
38
+
39
+ has_many :users
38
40
 
39
41
  pg_search_scope :with_users, associated_against: {users: :name}
40
42
  end
@@ -48,27 +50,6 @@ describe PgSearch::Configuration::Association do
48
50
  expect(association.table_name).to eq Avatar.table_name
49
51
  end
50
52
  end
51
-
52
- describe "#join" do
53
- let(:expected_sql) do
54
- <<~SQL.squish
55
- LEFT OUTER JOIN
56
- (SELECT model_id AS id,
57
- #{column_select} AS #{association.columns.first.alias}
58
- FROM "#{User.table_name}"
59
- INNER JOIN "#{association.table_name}"
60
- ON "#{association.table_name}"."user_id" = "#{User.table_name}"."id") #{association.subselect_alias}
61
- ON #{association.subselect_alias}.id = model_id
62
- SQL
63
- end
64
- let(:column_select) do
65
- "\"#{association.table_name}\".\"url\"::text"
66
- end
67
-
68
- it "returns the correct SQL join" do
69
- expect(association.join("model_id")).to eq(expected_sql)
70
- end
71
- end
72
53
  end
73
54
 
74
55
  context "with belongs_to" do
@@ -79,27 +60,6 @@ describe PgSearch::Configuration::Association do
79
60
  expect(association.table_name).to eq Site.table_name
80
61
  end
81
62
  end
82
-
83
- describe "#join" do
84
- let(:expected_sql) do
85
- <<~SQL.squish
86
- LEFT OUTER JOIN
87
- (SELECT model_id AS id,
88
- #{column_select} AS #{association.columns.first.alias}
89
- FROM "#{User.table_name}"
90
- INNER JOIN "#{association.table_name}"
91
- ON "#{association.table_name}"."id" = "#{User.table_name}"."site_id") #{association.subselect_alias}
92
- ON #{association.subselect_alias}.id = model_id
93
- SQL
94
- end
95
- let(:column_select) do
96
- "\"#{association.table_name}\".\"title\"::text"
97
- end
98
-
99
- it "returns the correct SQL join" do
100
- expect(association.join("model_id")).to eq(expected_sql)
101
- end
102
- end
103
63
  end
104
64
 
105
65
  context "with has_many" do
@@ -111,22 +71,38 @@ describe PgSearch::Configuration::Association do
111
71
  end
112
72
  end
113
73
 
114
- describe "#join" do
115
- let(:expected_sql) do
116
- <<~SQL.squish
117
- LEFT OUTER JOIN
118
- (SELECT model_id AS id,
119
- string_agg("#{association.table_name}"."name"::text, ' ') AS #{association.columns.first.alias}
120
- FROM "#{Site.table_name}"
121
- INNER JOIN "#{association.table_name}"
122
- ON "#{association.table_name}"."site_id" = "#{Site.table_name}"."id"
123
- GROUP BY model_id) #{association.subselect_alias}
124
- ON #{association.subselect_alias}.id = model_id
125
- SQL
74
+ describe "#to_arel" do
75
+ let(:projected_column) do
76
+ Site.arel_table.alias(association.subselect_alias)[
77
+ association.columns.first.alias
78
+ ].as("document")
126
79
  end
80
+ let(:joined_sites) do
81
+ Site.joins(association.to_arel(Site.arel_table[:id]))
82
+ end
83
+
84
+ it "ignores NULL inputs without padding the aggregate" do
85
+ site = Site.create!
86
+ site.users.create!(name: nil)
87
+ site.users.create!(name: "visible")
88
+ site.users.create!(name: nil)
89
+
90
+ expect(joined_sites.pluck(projected_column)).to eq ["visible"]
91
+ end
92
+
93
+ it "preserves an all-NULL aggregate" do
94
+ site = Site.create!
95
+ site.users.create!(name: nil)
96
+ site.users.create!(name: nil)
97
+
98
+ expect(joined_sites.pluck(projected_column)).to eq [nil]
99
+ end
100
+
101
+ it "retains the parent when no associated rows exist" do
102
+ site = Site.create!
127
103
 
128
- it "returns the correct SQL join" do
129
- expect(association.join("model_id")).to eq(expected_sql)
104
+ expect(joined_sites.pluck(Site.arel_table[:id])).to eq [site.id]
105
+ expect(joined_sites.pluck(projected_column)).to eq [nil]
130
106
  end
131
107
 
132
108
  describe "#subselect_alias" do
@@ -3,41 +3,99 @@
3
3
  require "spec_helper"
4
4
 
5
5
  describe PgSearch::Configuration::Column do
6
- describe "#full_name" do
6
+ describe "#to_arel" do
7
7
  with_model :Model do
8
8
  table do |t|
9
9
  t.string :name
10
+ t.integer :count
10
11
  t.json :object
11
12
  end
12
13
  end
13
14
 
14
- it "returns the fully-qualified table and column name" do
15
+ it "coalesces NULL values to an empty string when composed into a query" do
16
+ model = Model.create!(name: nil)
15
17
  column = described_class.new("name", nil, Model)
16
- expect(column.full_name).to eq(%(#{Model.quoted_table_name}."name"))
18
+ query = Model.arel_table
19
+ .project(column.to_arel.as("value"))
20
+ .where(Model.arel_table[:id].eq(model.id))
21
+
22
+ sql = Model.connection.visitor.compile(query.ast)
23
+ expect(Model.connection.select_value(sql)).to eq("")
17
24
  end
18
25
 
19
- it "returns nested json attributes" do
26
+ it "casts a non-text column to text when composed into a query" do
27
+ model = Model.create!(count: 42)
28
+ column = described_class.new("count", nil, Model)
29
+ query = Model.arel_table
30
+ .project(column.to_arel.as("value"))
31
+ .where(Model.arel_table[:id].eq(model.id))
32
+
33
+ sql = Model.connection.visitor.compile(query.ast)
34
+ expect(Model.connection.select_value(sql)).to eq("42")
35
+ end
36
+
37
+ it "evaluates a trusted SQL literal JSON expression when composed into a query" do
38
+ model = Model.create!(object: {name: "hello world"})
20
39
  column = described_class.new(Arel.sql("object->>'name'"), nil, Model)
21
- expect(column.full_name).to eq(%(object->>'name'))
40
+ query = Model.arel_table
41
+ .project(column.to_arel.as("value"))
42
+ .where(Model.arel_table[:id].eq(model.id))
43
+
44
+ sql = Model.connection.visitor.compile(query.ast)
45
+ expect(Model.connection.select_value(sql)).to eq("hello world")
46
+ end
47
+
48
+ context "with quoted identifiers" do
49
+ with_model :QuotedModel do
50
+ table do |t|
51
+ t.string "select"
52
+ t.string "order"
53
+ end
54
+ end
55
+
56
+ it "executes a reserved-word column through a projected Arel node" do
57
+ model = QuotedModel.create!(select: "value1")
58
+ column = described_class.new("select", nil, QuotedModel)
59
+ query = QuotedModel.arel_table
60
+ .project(column.to_arel.as("value"))
61
+ .where(QuotedModel.arel_table[:id].eq(model.id))
62
+
63
+ sql = QuotedModel.connection.visitor.compile(query.ast)
64
+ expect(QuotedModel.connection.select_value(sql)).to eq("value1")
65
+ end
66
+
67
+ it "composes multiple quoted column nodes before execution" do
68
+ model = QuotedModel.create!(select: "abc", order: "def")
69
+ select_column = described_class.new("select", nil, QuotedModel)
70
+ order_column = described_class.new("order", nil, QuotedModel)
71
+ combined = Arel::Nodes::NamedFunction.new(
72
+ "concat",
73
+ [select_column.to_arel, order_column.to_arel]
74
+ )
75
+ query = QuotedModel.arel_table
76
+ .project(combined.as("value"))
77
+ .where(QuotedModel.arel_table[:id].eq(model.id))
78
+
79
+ sql = QuotedModel.connection.visitor.compile(query.ast)
80
+ expect(QuotedModel.connection.select_value(sql)).to eq("abcdef")
81
+ end
22
82
  end
23
83
  end
24
84
 
25
- describe "#to_sql" do
26
- with_model :Model do
85
+ context "when a model alias shadows a physical column" do
86
+ with_model :Book do
27
87
  table do |t|
88
+ t.string :title
28
89
  t.string :name
29
- t.json :object
30
90
  end
31
91
  end
32
92
 
33
- it "returns an expression that casts the column to text and coalesces it with an empty string" do
34
- column = described_class.new("name", nil, Model)
35
- expect(column.to_sql).to eq(%{coalesce((#{Model.quoted_table_name}."name")::text, '')})
36
- end
93
+ it "normalizes the aliased model attribute" do
94
+ Book.create!(title: "physical", name: "alias")
95
+ Book.alias_attribute :title, :name
96
+ column = described_class.new(:title, nil, Book)
37
97
 
38
- it "returns an expression that casts the nested json attribute to text and coalesces it with an empty string" do
39
- column = described_class.new(Arel.sql("object->>'name'"), nil, Model)
40
- expect(column.to_sql).to eq(%{coalesce((object->>'name')::text, '')})
98
+ expect(Book.pluck(column.to_arel)).to eq ["alias"]
41
99
  end
42
100
  end
43
101
  end
@@ -3,36 +3,69 @@
3
3
  require "spec_helper"
4
4
 
5
5
  describe PgSearch::Configuration::ForeignColumn do
6
- describe "#alias" do
7
- with_model :AssociatedModel do
8
- table do |t|
9
- t.string "title"
10
- end
6
+ with_model :AssociatedModel do
7
+ table do |t|
8
+ t.string :title
9
+ end
10
+ end
11
+
12
+ with_model :Model do
13
+ table do |t|
14
+ t.string :name
15
+ t.belongs_to :associated_model, index: false, null: true
11
16
  end
12
17
 
13
- with_model :Model do
14
- table do |t|
15
- t.string "title"
16
- t.belongs_to :another_model, index: false
17
- end
18
+ model do
19
+ include PgSearch::Model
18
20
 
19
- model do
20
- include PgSearch::Model
21
- belongs_to :another_model, class_name: "AssociatedModel"
21
+ belongs_to :associated_model, class_name: "AssociatedModel", optional: true
22
22
 
23
- pg_search_scope :with_another, associated_against: {another_model: :title}
24
- end
23
+ pg_search_scope :with_title, associated_against: {associated_model: :title}
25
24
  end
25
+ end
26
26
 
27
- it "returns a consistent string" do
28
- association = PgSearch::Configuration::Association.new(Model,
29
- :another_model,
30
- :title)
31
- foreign_column = described_class.new("title", nil, Model, association)
27
+ let(:association) do
28
+ PgSearch::Configuration::Association.new(Model, :associated_model, :title)
29
+ end
30
+ let(:foreign_column) { described_class.new("title", nil, Model, association) }
32
31
 
32
+ describe "#alias" do
33
+ it "returns a consistent string" do
33
34
  column_alias = foreign_column.alias
34
35
  expect(column_alias).to be_a String
35
36
  expect(foreign_column.alias).to eq column_alias
36
37
  end
37
38
  end
39
+
40
+ describe "#to_arel" do
41
+ def selected_value(model)
42
+ relation = Model
43
+ .joins(association.to_arel(Model.arel_table[:id]))
44
+ .where(Model.arel_table[:id].eq(model.id))
45
+ .select(foreign_column.to_arel.as("value"))
46
+ sql = Model.connection.visitor.compile(relation.arel.ast)
47
+
48
+ Model.connection.select_value(sql)
49
+ end
50
+
51
+ it "evaluates the associated value through the association join alias" do
52
+ associated = AssociatedModel.create!(title: "hello world")
53
+ model = Model.create!(name: "example", associated_model: associated)
54
+
55
+ expect(selected_value(model)).to eq("hello world")
56
+ end
57
+
58
+ it "coalesces a NULL associated value through the association join alias" do
59
+ associated = AssociatedModel.create!(title: nil)
60
+ model = Model.create!(name: "example", associated_model: associated)
61
+
62
+ expect(selected_value(model)).to eq("")
63
+ end
64
+
65
+ it "coalesces a missing association through the outer join alias" do
66
+ model = Model.create!(name: "example", associated_model: nil)
67
+
68
+ expect(selected_value(model)).to eq("")
69
+ end
70
+ end
38
71
  end
@@ -23,7 +23,7 @@ describe PgSearch::Features::DMetaphone do
23
23
 
24
24
  feature = described_class.new(query, options, columns, Model, normalizer)
25
25
  expect(feature.rank.to_sql).to eq(
26
- %{(ts_rank((to_tsvector('simple', pg_search_dmetaphone(coalesce((#{Model.quoted_table_name}."name")::text, ''))) || to_tsvector('simple', pg_search_dmetaphone(coalesce((#{Model.quoted_table_name}."content")::text, '')))), (to_tsquery('simple', ''' ' || pg_search_dmetaphone('query') || ' ''')), 0))}
26
+ %{(ts_rank((to_tsvector('simple', pg_search_dmetaphone(coalesce(cast(#{Model.quoted_table_name}."name" AS text), ''))) || to_tsvector('simple', pg_search_dmetaphone(coalesce(cast(#{Model.quoted_table_name}."content" AS text), '')))), (to_tsquery('simple', ''' ' || pg_search_dmetaphone('query') || ' ''')), 0))}
27
27
  )
28
28
  end
29
29
  end
@@ -48,7 +48,7 @@ describe PgSearch::Features::DMetaphone do
48
48
 
49
49
  feature = described_class.new(query, options, columns, Model, normalizer)
50
50
  expect(feature.conditions.to_sql).to eq(
51
- %{((to_tsvector('simple', pg_search_dmetaphone(coalesce((#{Model.quoted_table_name}."name")::text, ''))) || to_tsvector('simple', pg_search_dmetaphone(coalesce((#{Model.quoted_table_name}."content")::text, '')))) @@ (to_tsquery('simple', ''' ' || pg_search_dmetaphone('query') || ' ''')))}
51
+ %{((to_tsvector('simple', pg_search_dmetaphone(coalesce(cast(#{Model.quoted_table_name}."name" AS text), ''))) || to_tsvector('simple', pg_search_dmetaphone(coalesce(cast(#{Model.quoted_table_name}."content" AS text), '')))) @@ (to_tsquery('simple', ''' ' || pg_search_dmetaphone('query') || ' ''')))}
52
52
  )
53
53
  end
54
54
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
- require "ostruct"
5
4
 
6
5
  # standard:disable RSpec/MultipleMemoizedHelpers, RSpec/NestedGroups
7
6
  describe PgSearch::Features::Trigram do
@@ -16,13 +15,14 @@ describe PgSearch::Features::Trigram do
16
15
  ]
17
16
  }
18
17
  let(:normalizer) { PgSearch::Normalizer.new(config) }
19
- let(:config) { OpenStruct.new(ignore: []) }
18
+ let(:config) { instance_double(PgSearch::Configuration, ignore: ignore) }
19
+ let(:ignore) { [] }
20
20
 
21
21
  let(:coalesced_columns) do
22
22
  <<~SQL.squish
23
- coalesce((#{Model.quoted_table_name}."name")::text, '')
23
+ coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')
24
24
  || ' '
25
- || coalesce((#{Model.quoted_table_name}."content")::text, '')
25
+ || coalesce(cast(#{Model.quoted_table_name}."content" AS text), '')
26
26
  SQL
27
27
  end
28
28
 
@@ -35,7 +35,6 @@ describe PgSearch::Features::Trigram do
35
35
 
36
36
  describe "conditions" do
37
37
  it "escapes the search document and query" do
38
- config.ignore = []
39
38
  expect(feature.conditions.to_sql).to eq("('#{query}' % (#{coalesced_columns}))")
40
39
  end
41
40
 
@@ -45,15 +44,15 @@ describe PgSearch::Features::Trigram do
45
44
  end
46
45
 
47
46
  it 'uses the "<%" operator when searching by word_similarity' do
48
- config.ignore = []
49
47
  expect(feature.conditions.to_sql).to eq("('#{query}' <% (#{coalesced_columns}))")
50
48
  end
51
49
  end
52
50
 
53
51
  context "when ignoring accents" do
52
+ let(:ignore) { [:accents] }
53
+
54
54
  it "escapes the search document and query, but not the accent function" do
55
- config.ignore = [:accents]
56
- expect(feature.conditions.to_sql).to eq("(unaccent('#{query}') % (unaccent(#{coalesced_columns})))")
55
+ expect(feature.conditions.to_sql).to eq("(regexp_replace(unaccent('#{query}'), '[''?\\:]', '', 'g') % (regexp_replace(unaccent(#{coalesced_columns}), '[''?\\:]', '', 'g')))")
57
56
  end
58
57
  end
59
58
 
@@ -88,7 +87,7 @@ describe PgSearch::Features::Trigram do
88
87
  let(:options) { {only: :name} }
89
88
 
90
89
  it "only searches against the select column" do
91
- coalesced_column = "coalesce((#{Model.quoted_table_name}.\"name\")::text, '')"
90
+ coalesced_column = "coalesce(cast(#{Model.quoted_table_name}.\"name\" AS text), '')"
92
91
  expect(feature.conditions.to_sql).to eq("('#{query}' % (#{coalesced_column}))")
93
92
  end
94
93
  end
@@ -24,7 +24,7 @@ describe PgSearch::Features::TSearch do
24
24
 
25
25
  feature = described_class.new(query, options, columns, Model, normalizer)
26
26
  expect(feature.rank.to_sql).to eq(
27
- %{(ts_rank((to_tsvector('simple', coalesce((#{Model.quoted_table_name}."name")::text, '')) || to_tsvector('simple', coalesce((#{Model.quoted_table_name}."content")::text, ''))), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 0))}
27
+ %{(ts_rank((to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')) || to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."content" AS text), ''))), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 0))}
28
28
  )
29
29
  end
30
30
 
@@ -67,7 +67,7 @@ describe PgSearch::Features::TSearch do
67
67
 
68
68
  feature = described_class.new(query, options, columns, Model, normalizer)
69
69
  expect(feature.conditions.to_sql).to eq(
70
- %{((to_tsvector('simple', coalesce((#{Model.quoted_table_name}."name")::text, '')) || to_tsvector('simple', coalesce((#{Model.quoted_table_name}."content")::text, ''))) @@ (to_tsquery('simple', ''' ' || 'query' || ' ''')))}
70
+ %{((to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')) || to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."content" AS text), ''))) @@ (to_tsquery('simple', ''' ' || 'query' || ' ''')))}
71
71
  )
72
72
  end
73
73
 
@@ -84,7 +84,7 @@ describe PgSearch::Features::TSearch do
84
84
 
85
85
  feature = described_class.new(query, options, columns, Model, normalizer)
86
86
  expect(feature.conditions.to_sql).to eq(
87
- %{((to_tsvector('simple', coalesce((#{Model.quoted_table_name}."name")::text, '')) || to_tsvector('simple', coalesce((#{Model.quoted_table_name}."content")::text, ''))) @@ (to_tsquery('simple', '!' || ''' ' || 'query' || ' ''')))}
87
+ %{((to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')) || to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."content" AS text), ''))) @@ (to_tsquery('simple', '!' || ''' ' || 'query' || ' ''')))}
88
88
  )
89
89
  end
90
90
  end
@@ -102,7 +102,7 @@ describe PgSearch::Features::TSearch do
102
102
 
103
103
  feature = described_class.new(query, options, columns, Model, normalizer)
104
104
  expect(feature.conditions.to_sql).to eq(
105
- %{((to_tsvector('simple', coalesce((#{Model.quoted_table_name}."name")::text, '')) || to_tsvector('simple', coalesce((#{Model.quoted_table_name}."content")::text, ''))) @@ (to_tsquery('simple', ''' ' || '!query' || ' ''')))}
105
+ %{((to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')) || to_tsvector('simple', coalesce(cast(#{Model.quoted_table_name}."content" AS text), ''))) @@ (to_tsquery('simple', ''' ' || '!query' || ' ''')))}
106
106
  )
107
107
  end
108
108
  end
@@ -164,7 +164,7 @@ describe PgSearch::Features::TSearch do
164
164
 
165
165
  feature = described_class.new(query, options, columns, Model, normalizer)
166
166
  expect(feature.highlight.to_sql).to eq(
167
- "(ts_headline('simple', (coalesce((#{Model.quoted_table_name}.\"name\")::text, '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), ''))"
167
+ "(ts_headline('simple', (coalesce(cast(#{Model.quoted_table_name}.\"name\" AS text), '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), ''))"
168
168
  )
169
169
  end
170
170
 
@@ -189,7 +189,7 @@ describe PgSearch::Features::TSearch do
189
189
 
190
190
  feature = described_class.new(query, options, columns, Model, normalizer)
191
191
 
192
- expected_sql = %{(ts_headline('spanish', (coalesce((#{Model.quoted_table_name}."name")::text, '') || ' ' || coalesce((#{Model.quoted_table_name}."content")::text, '')), (to_tsquery('spanish', ''' ' || 'query' || ' ''')), 'StartSel = "<b>", StopSel = "</b>"'))}
192
+ expected_sql = %{(ts_headline('spanish', (coalesce(cast(#{Model.quoted_table_name}."name" AS text), '') || ' ' || coalesce(cast(#{Model.quoted_table_name}."content" AS text), '')), (to_tsquery('spanish', ''' ' || 'query' || ' ''')), 'StartSel = "<b>", StopSel = "</b>"'))}
193
193
 
194
194
  expect(feature.highlight.to_sql).to eq(expected_sql)
195
195
  end
@@ -221,7 +221,7 @@ describe PgSearch::Features::TSearch do
221
221
 
222
222
  feature = described_class.new(query, options, columns, Model, normalizer)
223
223
 
224
- expected_sql = %{(ts_headline('simple', (coalesce((#{Model.quoted_table_name}."name")::text, '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 'StartSel = "<start class=""search"">", StopSel = "<stop>", MaxFragments = 3, MaxWords = 123, MinWords = 456, ShortWord = 4, FragmentDelimiter = "&hellip;", HighlightAll = TRUE'))}
224
+ expected_sql = %{(ts_headline('simple', (coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 'StartSel = "<start class=""search"">", StopSel = "<stop>", MaxFragments = 3, MaxWords = 123, MinWords = 456, ShortWord = 4, FragmentDelimiter = "&hellip;", HighlightAll = TRUE'))}
225
225
 
226
226
  expect(feature.highlight.to_sql).to eq(expected_sql)
227
227
  end
@@ -252,7 +252,7 @@ describe PgSearch::Features::TSearch do
252
252
  feature = described_class.new(query, options, columns, Model, normalizer)
253
253
 
254
254
  highlight_sql = silence_warnings { feature.highlight.to_sql }
255
- expected_sql = %{(ts_headline('simple', (coalesce((#{Model.quoted_table_name}."name")::text, '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 'StartSel = "<start class=""search"">", StopSel = "<stop>", MaxFragments = 3, MaxWords = 123, MinWords = 456, ShortWord = 4, FragmentDelimiter = "&hellip;", HighlightAll = FALSE'))}
255
+ expected_sql = %{(ts_headline('simple', (coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 'StartSel = "<start class=""search"">", StopSel = "<stop>", MaxFragments = 3, MaxWords = 123, MinWords = 456, ShortWord = 4, FragmentDelimiter = "&hellip;", HighlightAll = FALSE'))}
256
256
 
257
257
  expect(highlight_sql).to eq(expected_sql)
258
258
  end