Class: Redis::Commands::Search::Query
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | lib/redis/commands/modules/search/query.rb |
Overview
A fluent builder for FT.SEARCH query strings and their options.
Chainable setter methods (+filter+, #paging, #sort_by, ...) return self so calls can
be strung together, and a predicate DSL (+tag+/+text+/+numeric+ combined with and_+/+or_)
builds the query string itself. #to_redis_args renders the whole thing into the argument
array passed to FT.SEARCH.
Class Method Summary
-
.build { ... } ⇒ Query
Build a
Queryby evaluatingblockin the context of a fresh instance. - .new(base = nil) ⇒ Query constructor
Instance Attribute Summary
- #filters ⇒ Hash, ... readonly
- #geo_filters ⇒ Hash, ... readonly
- #highlight_options ⇒ Array, ... rw
- #options ⇒ Hash, ... readonly
- #return_fields ⇒ Array, ... rw
- #return_fields_decode ⇒ Hash, ... readonly
- #summarize_options ⇒ Array, ... rw
Instance Method Summary
-
#add_predicate(predicate) ⇒ self
Add a built predicate to the current collection.
-
#and_ { ... } ⇒ self
Open an AND group: predicates added inside
blockare joined with a space. -
#dialect(dialect_version) ⇒ self
Set the query
DIALECTversion. -
#evaluate { ... } ⇒ Object?
Evaluate
blockagainst this query (used to populate it via the DSL). -
#expander(expander_name) ⇒ self
Set the query
EXPANDER(custom query expander). - #expander_value Internal use only
-
#explain_score ⇒ self
Return an explanation of the score of each document (+EXPLAINSCORE+).
-
#filter(field, min, max = nil) ⇒ self
Add a numeric
FILTERclause. -
#geo_filter(field, lon, lat, radius, unit = 'km') ⇒ self
Add a
GEOFILTERclause restricting results to a radius around a point. -
#highlight(fields: nil, tags: ["<b>", "</b>"]) ⇒ self
Configure result +HIGHLIGHT+ing.
-
#in_order ⇒ self
Require query terms to appear in the same order as in the query (+INORDER+).
- #in_order_value Internal use only
-
#language(lang) ⇒ self
Set the query
LANGUAGEused for stemming. - #language_value Internal use only
-
#limit_fields(*fields) ⇒ self
Restrict the search to the given fields (+INFIELDS+).
- #limit_fields_value Internal use only
-
#limit_ids(*ids) ⇒ self
Restrict the search to the given document ids (+INKEYS+).
-
#limit_ids_value
Internal getters used by Index#search to read accumulated state - unlike the chainable setters above, these return the stored value rather than
self. -
#new_collection(type) { ... } ⇒ self
Push a new predicate collection of
type, evaluate the block against it, then fold it back into the parent collection. -
#no_content ⇒ self
Return only document ids, without their contents (+NOCONTENT+).
- #no_content_value Internal use only
-
#no_stopwords ⇒ self
Do not filter out stopwords (+NOSTOPWORDS+).
- #no_stopwords_value Internal use only
-
#numeric(field) ⇒ NumericField
Begin a numeric-field predicate bound to this query (call
.gt+/+.lt+/+.betweenon it). -
#or_ { ... } ⇒ self
Open an OR group: predicates added inside
blockare joined with {|}. -
#paging(offset, limit) ⇒ self
Set the
LIMIT(paging) for the result set. -
#return(*fields) ⇒ self
Set the fields to
RETURN, replacing any previously configured ones. -
#return_field(field, as_field: nil, decode_field: true) ⇒ self
Append a single field to
RETURN, optionally aliased and/or JSON-decoded. -
#scorer(scorer_name) ⇒ self
Set the scoring function (+SCORER+).
-
#slop(value) ⇒ self
Set the allowed
SLOP(number of intervening terms) for phrase matching. - #slop_value Internal use only
-
#sort_by(field, order = nil, asc: true) ⇒ self
Set the
SORTBYfield and direction. -
#summarize(fields: nil, separator: "...", len: 20, frags: 3) ⇒ self
Configure result +SUMMARIZE+ation (fragment extraction around matches).
-
#tag(field) ⇒ TagField
Begin a tag-field predicate bound to this query (call
.eqon the result). -
#text(field) ⇒ TextField
Begin a text-field predicate bound to this query (call
.matchon the result). -
#timeout(milliseconds) ⇒ self
Set the query
TIMEOUT. - #timeout_value Internal use only
-
#to_redis_args ⇒ Array
Render the query and all configured options into the
FT.SEARCHargument array. -
#verbatim ⇒ self
Disable stemming for the query (+VERBATIM+).
- #verbatim_value Internal use only
-
#with_payloads ⇒ self
Return the payload attached to each document (+WITHPAYLOADS+).
- #with_payloads_value Internal use only
-
#with_scores ⇒ self
Return the relevance score of each document (+WITHSCORES+).
- #append_boolean_options(args) private
- #append_dialect(args) private
- #append_expander(args) private
- #append_filters(args) private
- #append_geo_filters(args) private
- #append_highlight_options(args) private
- #append_in_order(args) private
- #append_language(args) private
- #append_limit(args) private
- #append_limit_fields(args) private
- #append_limit_ids(args) private
- #append_return_fields(args) private
- #append_scorer(args) private
- #append_slop(args) private
- #append_sort_by(args) private
- #append_summarize_options(args) private
- #append_timeout(args) private
- #append_with_scores(args) private
-
#coerce_filter_bound(value)
private
Validate a FILTER bound.
- #query_string private
Constructor Details
.new(base = nil) ⇒ Query
# File 'lib/redis/commands/modules/search/query.rb', line 30
def initialize(base = nil) @base = base @predicate_collection = [PredicateCollection.new(:and)] @filters = [] @options = { dialect: DEFAULT_DIALECT } # Default dialect, matching redis-py @return_fields = [] @return_fields_decode = {} @summarize_options = nil @highlight_options = nil @language = nil @verbatim = false @no_stopwords = false @with_payloads = false @slop = nil @in_order = false @no_content = false @limit_ids = nil end
Class Method Details
.build { ... } ⇒ Query
Build a Query by evaluating block in the context of a fresh instance.
# File 'lib/redis/commands/modules/search/query.rb', line 56
def self.build(&block) instance = new instance.instance_eval(&block) instance end
Instance Attribute Details
#filters ⇒ Hash, ... (readonly)
# File 'lib/redis/commands/modules/search/query.rb', line 23
attr_reader :, :return_fields_decode, :filters, :geo_filters
#geo_filters ⇒ Hash, ... (readonly)
# File 'lib/redis/commands/modules/search/query.rb', line 23
attr_reader :, :return_fields_decode, :filters, :geo_filters
#highlight_options ⇒ Array, ... (rw)
# File 'lib/redis/commands/modules/search/query.rb', line 27
attr_accessor :return_fields, :, :
#options ⇒ Hash, ... (readonly)
# File 'lib/redis/commands/modules/search/query.rb', line 23
attr_reader :, :return_fields_decode, :filters, :geo_filters
#return_fields ⇒ Array, ... (rw)
# File 'lib/redis/commands/modules/search/query.rb', line 27
attr_accessor :return_fields, :, :
#return_fields_decode ⇒ Hash, ... (readonly)
# File 'lib/redis/commands/modules/search/query.rb', line 23
attr_reader :, :return_fields_decode, :filters, :geo_filters
#summarize_options ⇒ Array, ... (rw)
# File 'lib/redis/commands/modules/search/query.rb', line 27
attr_accessor :return_fields, :, :
Instance Method Details
#add_predicate(predicate) ⇒ self
Add a built predicate to the current collection.
# File 'lib/redis/commands/modules/search/query.rb', line 398
def add_predicate(predicate) @predicate_collection.last.add(predicate) self end
#and_ { ... } ⇒ self
Open an AND group: predicates added inside block are joined with a space.
# File 'lib/redis/commands/modules/search/query.rb', line 390
def and_(&block) new_collection(:and, &block) end
#append_boolean_options(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 489
def (args) args << "NOCONTENT" if @no_content args << "VERBATIM" if @verbatim args << "NOSTOPWORDS" if @no_stopwords args << "WITHPAYLOADS" if @with_payloads end
#append_dialect(args) (private)
[ GitHub ]#append_expander(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 586
def (args) args.concat(["EXPANDER", @expander]) if @expander end
#append_filters(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 520
def append_filters(args) @filters.each do |field, min, max| args.concat(["FILTER", field, min, max]) end end
#append_geo_filters(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 540
def append_geo_filters(args) @geo_filters&.each do |field, lon, lat, radius, unit| args.concat(["GEOFILTER", field, lon, lat, radius, unit]) end end
#append_highlight_options(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 564
def (args) if @highlight_options args << "HIGHLIGHT" if @highlight_options[:fields].any? args.concat(["FIELDS", @highlight_options[:fields].size, *@highlight_options[:fields].map(&:to_s)]) end args.concat(["TAGS", *@highlight_options[:]]) end end
#append_in_order(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 590
def append_in_order(args) args << "INORDER" if @in_order end
#append_language(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 582
def append_language(args) args.concat(["LANGUAGE", @language]) if @language end
#append_limit(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 600
def append_limit(args) if @options[:limit] args.concat(["LIMIT", @options[:limit][0], @options[:limit][1]]) end end
#append_limit_fields(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 508
def append_limit_fields(args) if @limit_fields && !@limit_fields.empty? args.concat(["INFIELDS", @limit_fields.size, *@limit_fields.map(&:to_s)]) end end
#append_limit_ids(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 514
def append_limit_ids(args) if @limit_ids && !@limit_ids.empty? args.concat(["INKEYS", @limit_ids.size, *@limit_ids]) end end
#append_return_fields(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 546
def append_return_fields(args) if @return_fields && !@return_fields.empty? args.concat(["RETURN", @return_fields.size, *@return_fields]) end end
#append_scorer(args) (private)
[ GitHub ]#append_slop(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 574
def append_slop(args) args.concat(["SLOP", @slop]) if @slop end
#append_sort_by(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 594
def append_sort_by(args) if @options[:sortby] args.concat(["SORTBY", @options[:sortby][0], @options[:sortby][1]]) end end
#append_summarize_options(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 552
def (args) if @summarize_options args << "SUMMARIZE" if @summarize_options[:fields].any? args.concat(["FIELDS", @summarize_options[:fields].size, *@summarize_options[:fields].map(&:to_s)]) end args.concat(["FRAGS", @summarize_options[:frags]]) args.concat(["LEN", @summarize_options[:len]]) args.concat(["SEPARATOR", @summarize_options[:separator]]) end end
#append_timeout(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 578
def append_timeout(args) args.concat(["TIMEOUT", @timeout]) if @timeout end
#append_with_scores(args) (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 500
def append_with_scores(args) # EXPLAINSCORE is only valid alongside WITHSCORES, and requesting an explanation implies # wanting the score, so emit WITHSCORES whenever either is set. This keeps #explain_score # self-sufficient rather than producing an invalid EXPLAINSCORE-without-WITHSCORES command. args << "WITHSCORES" if @options[:withscores] || @options[:explainscore] args << "EXPLAINSCORE" if @options[:explainscore] end
#coerce_filter_bound(value) (private)
Validate a FILTER bound. Numeric values pass through; strings must be a valid RediSearch numeric bound — an optional exclusive "(" prefix followed by a number or +inf/-inf. Anything else (e.g. injected query syntax) raises, matching the guarded predicate DSL.
# File 'lib/redis/commands/modules/search/query.rb', line 529
def coerce_filter_bound(value) return value if value.is_a?(Numeric) token = value.to_s body = token.start_with?("(") ? token[1..] : token return token if body.match?(/\A[+-]?inf\z/i) Float(body) # raises ArgumentError unless body is a plain number token end
#dialect(dialect_version) ⇒ self
Set the query DIALECT version.
# File 'lib/redis/commands/modules/search/query.rb', line 304
def dialect(dialect_version) @options[:dialect] = dialect_version self end
#evaluate { ... } ⇒ Object?
Evaluate block against this query (used to populate it via the DSL).
# File 'lib/redis/commands/modules/search/query.rb', line 473
def evaluate(&block) if block_given? instance_eval(&block) end end
#expander(expander_name) ⇒ self
Set the query EXPANDER (custom query expander).
# File 'lib/redis/commands/modules/search/query.rb', line 189
def () @expander = self end
#expander_value
# File 'lib/redis/commands/modules/search/query.rb', line 370
def @expander end
#explain_score ⇒ self
Return an explanation of the score of each document (+EXPLAINSCORE+).
# File 'lib/redis/commands/modules/search/query.rb', line 312
def explain_score @options[:explainscore] = true self end
#filter(field, min, max = nil) ⇒ self
Add a numeric FILTER clause.
Bounds are validated so a malformed or untrusted value can't inject query syntax, the
same guarantee the numeric-predicate DSL gives. Beyond plain numbers, the RediSearch
bound forms inf+/+-inf and the exclusive ( prefix (e.g. "(10") are accepted.
# File 'lib/redis/commands/modules/search/query.rb', line 73
def filter(field, min, max = nil) max ||= min @filters << [field, coerce_filter_bound(min), coerce_filter_bound(max)] self end
#geo_filter(field, lon, lat, radius, unit = 'km') ⇒ self
Add a GEOFILTER clause restricting results to a radius around a point.
# File 'lib/redis/commands/modules/search/query.rb', line 87
def geo_filter(field, lon, lat, radius, unit = 'km') @geo_filters ||= [] @geo_filters << [field, lon, lat, radius, unit] self end
#highlight(fields: nil, tags: ["<b>", "</b>"]) ⇒ self
Configure result +HIGHLIGHT+ing.
# File 'lib/redis/commands/modules/search/query.rb', line 275
def highlight(fields: nil, tags: ["<b>", "</b>"]) @highlight_options = { fields: Array(fields), tags: } self end
#in_order ⇒ self
Require query terms to appear in the same order as in the query (+INORDER+).
# File 'lib/redis/commands/modules/search/query.rb', line 239
def in_order @in_order = true self end
#in_order_value
# File 'lib/redis/commands/modules/search/query.rb', line 355
def in_order_value @in_order end
#language(lang) ⇒ self
Set the query LANGUAGE used for stemming.
# File 'lib/redis/commands/modules/search/query.rb', line 172
def language(lang) @language = lang self end
#language_value
# File 'lib/redis/commands/modules/search/query.rb', line 325
def language_value @language end
#limit_fields(*fields) ⇒ self
Restrict the search to the given fields (+INFIELDS+).
# File 'lib/redis/commands/modules/search/query.rb', line 265
def limit_fields(*fields) @limit_fields = fields self end
#limit_fields_value
# File 'lib/redis/commands/modules/search/query.rb', line 365
def limit_fields_value @limit_fields end
#limit_ids(*ids) ⇒ self
Restrict the search to the given document ids (+INKEYS+).
# File 'lib/redis/commands/modules/search/query.rb', line 97
def limit_ids(*ids) @limit_ids = ids self end
#limit_ids_value
Internal getters used by Index#search to read accumulated state - unlike the
chainable setters above, these return the stored value rather than self.
:nodoc:
# File 'lib/redis/commands/modules/search/query.rb', line 320
def limit_ids_value @limit_ids end
#new_collection(type) { ... } ⇒ self
Push a new predicate collection of type, evaluate the block against it, then fold it
back into the parent collection.
# File 'lib/redis/commands/modules/search/query.rb', line 409
def new_collection(type) collection = PredicateCollection.new(type) @predicate_collection << collection yield if block_given? @predicate_collection.pop @predicate_collection.last.add(collection) self end
#no_content ⇒ self
Return only document ids, without their contents (+NOCONTENT+).
# File 'lib/redis/commands/modules/search/query.rb', line 256
def no_content @no_content = true self end
#no_content_value
# File 'lib/redis/commands/modules/search/query.rb', line 340
def no_content_value @no_content end
#no_stopwords ⇒ self
Do not filter out stopwords (+NOSTOPWORDS+).
# File 'lib/redis/commands/modules/search/query.rb', line 197
def no_stopwords @no_stopwords = true self end
#no_stopwords_value
# File 'lib/redis/commands/modules/search/query.rb', line 335
def no_stopwords_value @no_stopwords end
#numeric(field) ⇒ NumericField
Begin a numeric-field predicate bound to this query (call .gt+/+.lt+/+.between on it).
# File 'lib/redis/commands/modules/search/query.rb', line 438
def numeric(field) NumericField.new(field, self) end
#or_ { ... } ⇒ self
Open an OR group: predicates added inside block are joined with {|}.
# File 'lib/redis/commands/modules/search/query.rb', line 382
def or_(&block) new_collection(:or, &block) end
#paging(offset, limit) ⇒ self
Set the LIMIT (paging) for the result set.
# File 'lib/redis/commands/modules/search/query.rb', line 107
def paging(offset, limit) @options[:limit] = [offset, limit] self end
#query_string (private)
[ GitHub ]# File 'lib/redis/commands/modules/search/query.rb', line 481
def query_string if @predicate_collection.first.predicates.empty? @base || "*" else @predicate_collection.first.to_s end end
#return(*fields) ⇒ self
Set the fields to RETURN, replacing any previously configured ones.
# File 'lib/redis/commands/modules/search/query.rb', line 138
def return(*fields) @return_fields = fields # Replacing the RETURN list drops any decode flags set via #return_field; otherwise a # stale flag would keep decoding an overlapping field name in the new list. @return_fields_decode = {} self end
#return_field(field, as_field: nil, decode_field: true) ⇒ self
Append a single field to RETURN, optionally aliased and/or JSON-decoded.
# File 'lib/redis/commands/modules/search/query.rb', line 152
def return_field(field, as_field: nil, decode_field: true) @return_fields ||= [] @return_fields_decode ||= {} @return_fields << field # Results are keyed by the alias when one is given (Redis returns the value under AS), # so the decode flag must be keyed the same way or hashify_fields won't match it. @return_fields_decode[as_field || field] = decode_field if as_field @return_fields << "AS" << as_field end self end
#scorer(scorer_name) ⇒ self
Set the scoring function (+SCORER+).
# File 'lib/redis/commands/modules/search/query.rb', line 214
def scorer(scorer_name) @options[:scorer] = scorer_name self end
#slop(value) ⇒ self
Set the allowed SLOP (number of intervening terms) for phrase matching.
# File 'lib/redis/commands/modules/search/query.rb', line 231
def slop(value) @slop = value self end
#slop_value
# File 'lib/redis/commands/modules/search/query.rb', line 350
def slop_value @slop end
#sort_by(field, order = nil, asc: true) ⇒ self
Set the SORTBY field and direction.
Supports both styles: a positional order ("ASC"/"DESC" symbol or string) and the
asc: keyword. When order is given it takes precedence over asc:.
# File 'lib/redis/commands/modules/search/query.rb', line 121
def sort_by(field, order = nil, asc: true) # Support both old style (order as symbol/string) and new style (asc as boolean) direction = if order.nil? # New style: use asc parameter asc ? "ASC" : "DESC" else # Old style: order is a symbol or string order.to_s.upcase end @options[:sortby] = [field, direction] self end
#summarize(fields: nil, separator: "...", len: 20, frags: 3) ⇒ self
Configure result +SUMMARIZE+ation (fragment extraction around matches).
# File 'lib/redis/commands/modules/search/query.rb', line 290
def summarize(fields: nil, separator: "...", len: 20, frags: 3) @summarize_options = { fields: Array(fields), separator: separator, len: len, frags: frags } self end
#tag(field) ⇒ TagField
Begin a tag-field predicate bound to this query (call .eq on the result).
#text(field) ⇒ TextField
Begin a text-field predicate bound to this query (call .match on the result).
#timeout(milliseconds) ⇒ self
Set the query TIMEOUT.
# File 'lib/redis/commands/modules/search/query.rb', line 248
def timeout(milliseconds) @timeout = milliseconds self end
#timeout_value
# File 'lib/redis/commands/modules/search/query.rb', line 360
def timeout_value @timeout end
#to_redis_args ⇒ Array
Render the query and all configured options into the FT.SEARCH argument array.
# File 'lib/redis/commands/modules/search/query.rb', line 446
def to_redis_args args = [query_string] (args) append_scorer(args) append_with_scores(args) append_limit_fields(args) append_limit_ids(args) append_filters(args) append_geo_filters(args) append_return_fields(args) (args) (args) append_slop(args) append_timeout(args) append_language(args) (args) append_in_order(args) append_sort_by(args) append_limit(args) append_dialect(args) args.flatten end
#verbatim ⇒ self
Disable stemming for the query (+VERBATIM+).
# File 'lib/redis/commands/modules/search/query.rb', line 180
def verbatim @verbatim = true self end
#verbatim_value
# File 'lib/redis/commands/modules/search/query.rb', line 330
def verbatim_value @verbatim end
#with_payloads ⇒ self
Return the payload attached to each document (+WITHPAYLOADS+).
# File 'lib/redis/commands/modules/search/query.rb', line 222
def with_payloads @with_payloads = true self end
#with_payloads_value
# File 'lib/redis/commands/modules/search/query.rb', line 345
def with_payloads_value @with_payloads end
#with_scores ⇒ self
Return the relevance score of each document (+WITHSCORES+).
# File 'lib/redis/commands/modules/search/query.rb', line 205
def with_scores @options[:withscores] = true self end