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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +38 -37
- data/.github/workflows/codeql.yml +86 -0
- data/.gitignore +0 -1
- data/.standard.yml +2 -2
- data/CHANGELOG.md +81 -64
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +4 -2
- data/LICENSE +1 -1
- data/README.md +89 -87
- data/Rakefile +1 -7
- data/doc/plans/2026-09-05-arel-stack.md +43 -0
- data/doc/plans/2026-09-06-arel-remaining-expressions.md +48 -0
- data/lib/pg_search/configuration/association.rb +37 -23
- data/lib/pg_search/configuration/column.rb +17 -12
- data/lib/pg_search/configuration/foreign_column.rb +3 -5
- data/lib/pg_search/configuration.rb +10 -26
- data/lib/pg_search/features/dmetaphone.rb +3 -7
- data/lib/pg_search/features/feature.rb +8 -4
- data/lib/pg_search/features/trigram.rb +6 -20
- data/lib/pg_search/features/tsearch.rb +47 -61
- data/lib/pg_search/features.rb +2 -0
- data/lib/pg_search/migration/generator.rb +1 -5
- data/lib/pg_search/model.rb +6 -8
- data/lib/pg_search/multisearch/rebuilder.rb +69 -64
- data/lib/pg_search/normalizer.rb +40 -11
- data/lib/pg_search/scope_options.rb +32 -49
- data/lib/pg_search/version.rb +1 -1
- data/pg_search.gemspec +5 -5
- data/spec/integration/associations_spec.rb +133 -0
- data/spec/integration/deprecation_spec.rb +5 -1
- data/spec/integration/pagination_spec.rb +1 -0
- data/spec/integration/pg_search_spec.rb +78 -1
- data/spec/integration/single_table_inheritance_spec.rb +2 -0
- data/spec/lib/pg_search/configuration/association_spec.rb +34 -58
- data/spec/lib/pg_search/configuration/column_spec.rb +73 -15
- data/spec/lib/pg_search/configuration/foreign_column_spec.rb +53 -20
- data/spec/lib/pg_search/features/dmetaphone_spec.rb +2 -2
- data/spec/lib/pg_search/features/trigram_spec.rb +8 -9
- data/spec/lib/pg_search/features/tsearch_spec.rb +8 -8
- data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +251 -211
- data/spec/lib/pg_search/multisearch_spec.rb +16 -16
- data/spec/lib/pg_search/multisearchable_spec.rb +8 -0
- data/spec/lib/pg_search/normalizer_spec.rb +64 -11
- data/spec/lib/pg_search_spec.rb +6 -21
- data/spec/spec_helper.rb +0 -13
- metadata +11 -15
- data/spec/.rubocop.yml +0 -27
- 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
|
-
|
|
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
|
-
|
|
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 "#
|
|
115
|
-
let(:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
129
|
-
expect(
|
|
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 "#
|
|
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 "
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
with_model :
|
|
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 "
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
t.string "title"
|
|
16
|
-
t.belongs_to :another_model, index: false
|
|
17
|
-
end
|
|
18
|
+
model do
|
|
19
|
+
include PgSearch::Model
|
|
18
20
|
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
end
|
|
23
|
+
pg_search_scope :with_title, associated_against: {associated_model: :title}
|
|
25
24
|
end
|
|
25
|
+
end
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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")
|
|
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")
|
|
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) {
|
|
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")
|
|
23
|
+
coalesce(cast(#{Model.quoted_table_name}."name" AS text), '')
|
|
24
24
|
|| ' '
|
|
25
|
-
|| coalesce((#{Model.quoted_table_name}."content")
|
|
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
|
-
|
|
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\")
|
|
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")
|
|
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")
|
|
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")
|
|
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")
|
|
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\")
|
|
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")
|
|
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")
|
|
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 = "…", 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")
|
|
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 = "…", HighlightAll = FALSE'))}
|
|
256
256
|
|
|
257
257
|
expect(highlight_sql).to eq(expected_sql)
|
|
258
258
|
end
|