Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/pg_search/configuration/column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def full_name
"#{table_name}.#{column_name}"
end

def to_arel
Arel.sql(to_sql)
end

def to_sql
"coalesce((#{expression})::text, '')"
end
Expand Down
10 changes: 4 additions & 6 deletions lib/pg_search/features/dmetaphone.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ def initialize(normalizer_to_wrap)
@normalizer_to_wrap = normalizer_to_wrap
end

def add_normalization(original_sql)
otherwise_normalized_sql = Arel.sql(
normalizer_to_wrap.add_normalization(original_sql)
)
def add_normalization(original_node)
otherwise_normalized_node = normalizer_to_wrap.add_normalization(original_node)

Arel::Nodes::NamedFunction.new(
"pg_search_dmetaphone",
[otherwise_normalized_sql]
).to_sql
[otherwise_normalized_node]
)
end

private
Expand Down
15 changes: 13 additions & 2 deletions lib/pg_search/features/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def self.valid_options
%i[only sort_only]
end

delegate :connection, :quoted_table_name, to: :@model
delegate :connection, :arel_table, to: :@model

def initialize(query, options, all_columns, model, normalizer)
@query = query
Expand All @@ -24,8 +24,19 @@ def initialize(query, options, all_columns, model, normalizer)

attr_reader :query, :options, :all_columns, :model, :normalizer

# Join columns with spaces, like: column1 || ' ' || column2 || ' ' || column3...
def document
columns.map(&:to_sql).join(" || ' ' || ")
columns.map(&:to_arel).inject do |memo, column_node|
Arel::Nodes::InfixOperation.new(
"||",
Arel::Nodes::InfixOperation.new(
"||",
memo,
Arel::Nodes.build_quoted(" ")
),
column_node
)
end
end

def columns
Expand Down
6 changes: 3 additions & 3 deletions lib/pg_search/features/trigram.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ def similarity
end

def normalized_document
Arel::Nodes::Grouping.new(Arel.sql(normalize(document)))
Arel::Nodes::Grouping.new(normalize(document))
end

def normalized_query
sanitized_query = connection.quote(query)
Arel.sql(normalize(sanitized_query))
sanitized_query = Arel::Nodes.build_quoted(query)
normalize(sanitized_query)
end
end
end
Expand Down
70 changes: 36 additions & 34 deletions lib/pg_search/features/tsearch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ def self.valid_options

def conditions
Arel::Nodes::Grouping.new(
Arel::Nodes::InfixOperation.new("@@", arel_wrap(tsdocument), arel_wrap(tsquery))
Arel::Nodes::InfixOperation.new("@@", tsdocument, tsquery)
)
end

def rank
arel_wrap(tsearch_rank)
Arel::Nodes::NamedFunction.new("ts_rank", [
tsdocument,
tsquery,
normalization
])
end

def highlight
Expand All @@ -29,8 +33,8 @@ def highlight
def ts_headline
Arel::Nodes::NamedFunction.new("ts_headline", [
dictionary,
arel_wrap(document),
arel_wrap(tsquery),
document,
tsquery,
Arel::Nodes.build_quoted(ts_headline_options)
]).to_sql
end
Expand Down Expand Up @@ -72,7 +76,7 @@ def deprecated_headline_options

warn(
"pg_search 3.0 will no longer accept :#{deprecated_key} as an argument to :ts_headline, " \
"use :#{key} instead.",
"use :#{key} instead.",
category: :deprecated,
uplevel: 1
)
Expand Down Expand Up @@ -106,11 +110,11 @@ def tsquery_for_term(unsanitized_term)

sanitized_term = unsanitized_term.gsub(DISALLOWED_TSQUERY_CHARACTERS, " ")

term_sql = Arel.sql(normalize(connection.quote(sanitized_term)))
term_sql = normalize(Arel::Nodes.build_quoted(sanitized_term))

tsquery = tsquery_expression(term_sql, negated: negated, prefix: options[:prefix])

Arel::Nodes::NamedFunction.new("to_tsquery", [dictionary, tsquery]).to_sql
Arel::Nodes::NamedFunction.new("to_tsquery", [dictionary, tsquery])
end

# After this, the SQL expression evaluates to a string containing the term surrounded by single-quotes.
Expand All @@ -131,29 +135,34 @@ def tsquery_expression(term_sql, negated:, prefix:)
end

def tsquery
return "''" if query.blank?
return Arel::Nodes.build_quoted(query) if query.blank?

operator = tsquery_operator

query_terms = query.split.compact
tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
tsquery_terms.join(options[:any_word] ? " || " : " && ")
Arel::Nodes::Grouping.new(
query
.split
.compact
.map { tsquery_for_term(_1) }
.inject { Arel::Nodes::InfixOperation.new(operator, _1, _2) }
)
end

def tsdocument
tsdocument_terms = (columns_to_use || []).map do |search_column|
column_to_tsvector(search_column)
end
def tsquery_operator = options[:any_word] ? "||" : "&&"

if options[:tsvector_column]
tsvector_columns = Array.wrap(options[:tsvector_column])
def tsdocument
tsdocument_terms = [
*(columns_to_use || []).map { column_to_tsvector(_1) },
*Array.wrap(options[:tsvector_column]).map { arel_table[_1] }
]

tsdocument_terms << tsvector_columns.map do |tsvector_column|
column_name = connection.quote_column_name(tsvector_column)
return Arel::Nodes.build_quoted(nil) if tsdocument_terms.empty?

"#{quoted_table_name}.#{column_name}"
Arel::Nodes::Grouping.new(
tsdocument_terms.inject do |memo, term|
Arel::Nodes::InfixOperation.new("||", memo, term)
end
end

tsdocument_terms.join(" || ")
)
end

# From http://www.postgresql.org/docs/8.3/static/textsearch-controls.html
Expand All @@ -169,14 +178,6 @@ def normalization
options[:normalization] || 0
end

def tsearch_rank
Arel::Nodes::NamedFunction.new("ts_rank", [
arel_wrap(tsdocument),
arel_wrap(tsquery),
normalization
]).to_sql
end

def dictionary
Arel::Nodes.build_quoted(options[:dictionary] || :simple)
end
Expand All @@ -196,13 +197,14 @@ def columns_to_use
def column_to_tsvector(search_column)
tsvector = Arel::Nodes::NamedFunction.new(
"to_tsvector",
[dictionary, Arel.sql(normalize(search_column.to_sql))]
).to_sql
[dictionary, normalize(search_column.to_arel)]
)

if search_column.weight.nil?
tsvector
else
"setweight(#{tsvector}, #{connection.quote(search_column.weight)})"
weight = Arel::Nodes.build_quoted(search_column.weight)
Arel::Nodes::NamedFunction.new("setweight", [tsvector, weight])
end
end
end
Expand Down
15 changes: 4 additions & 11 deletions lib/pg_search/normalizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,13 @@ def initialize(config)
@config = config
end

def add_normalization(sql_expression)
return sql_expression unless config.ignore.include?(:accents)

sql_node = case sql_expression
when Arel::Nodes::Node
sql_expression
else
Arel.sql(sql_expression)
end
def add_normalization(node)
return node unless config.ignore.include?(:accents)

Arel::Nodes::NamedFunction.new(
PgSearch.unaccent_function,
[sql_node]
).to_sql
[node]
)
end

private
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/pg_search/features/dmetaphone_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

feature = described_class.new(query, options, columns, Model, normalizer)
expect(feature.rank.to_sql).to eq(
%{(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))}
%{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)}
)
end
end
Expand Down
12 changes: 6 additions & 6 deletions spec/lib/pg_search/features/tsearch_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

feature = described_class.new(query, options, columns, Model, normalizer)
expect(feature.rank.to_sql).to eq(
%{(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))}
%{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)}
)
end

Expand All @@ -41,7 +41,7 @@

feature = described_class.new(query, options, columns, Model, normalizer)
expect(feature.rank.to_sql).to eq(
%{(ts_rank((#{Model.quoted_table_name}."my_tsvector"), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 2))}
%{ts_rank((#{Model.quoted_table_name}."my_tsvector"), (to_tsquery('simple', ''' ' || 'query' || ' ''')), 2)}
)
end
end
Expand Down Expand Up @@ -164,7 +164,7 @@

feature = described_class.new(query, options, columns, Model, normalizer)
expect(feature.highlight.to_sql).to eq(
"(ts_headline('simple', (coalesce((#{Model.quoted_table_name}.\"name\")::text, '')), (to_tsquery('simple', ''' ' || 'query' || ' ''')), ''))"
"(ts_headline('simple', coalesce((#{Model.quoted_table_name}.\"name\")::text, ''), (to_tsquery('simple', ''' ' || 'query' || ' ''')), ''))"
)
end

Expand All @@ -189,7 +189,7 @@

feature = described_class.new(query, options, columns, Model, normalizer)

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>"'))}
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>"'))}

expect(feature.highlight.to_sql).to eq(expected_sql)
end
Expand Down Expand Up @@ -221,7 +221,7 @@

feature = described_class.new(query, options, columns, Model, normalizer)

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'))}
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'))}

expect(feature.highlight.to_sql).to eq(expected_sql)
end
Expand Down Expand Up @@ -252,7 +252,7 @@
feature = described_class.new(query, options, columns, Model, normalizer)

highlight_sql = silence_warnings { feature.highlight.to_sql }
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'))}
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'))}

expect(highlight_sql).to eq(expected_sql)
end
Expand Down
8 changes: 4 additions & 4 deletions spec/lib/pg_search/normalizer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
node = Arel::Nodes::NamedFunction.new("foo", [Arel::Nodes.build_quoted("bar")])

normalizer = described_class.new(config)
expect(normalizer.add_normalization(node)).to eq("unaccent(foo('bar'))")
expect(normalizer.add_normalization(node).to_sql).to eq("unaccent(foo('bar'))")
end

context "when a custom unaccent function is specified" do
Expand All @@ -23,7 +23,7 @@
config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])

normalizer = described_class.new(config)
expect(normalizer.add_normalization(node)).to eq("my_unaccent(foo('bar'))")
expect(normalizer.add_normalization(node).to_sql).to eq("my_unaccent(foo('bar'))")
end
end
end
Expand All @@ -33,7 +33,7 @@
config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])

normalizer = described_class.new(config)
expect(normalizer.add_normalization("foo")).to eq("unaccent(foo)")
expect(normalizer.add_normalization(Arel.sql("foo")).to_sql).to eq("unaccent(foo)")
end

context "when a custom unaccent function is specified" do
Expand All @@ -43,7 +43,7 @@
config = instance_double(PgSearch::Configuration, "config", ignore: [:accents])

normalizer = described_class.new(config)
expect(normalizer.add_normalization("foo")).to eq("my_unaccent(foo)")
expect(normalizer.add_normalization(Arel.sql("foo")).to_sql).to eq("my_unaccent(foo)")
end
end
end
Expand Down