Function queries

This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A function query evaluates a small expression — built from field values, constants, and nested functions — to a numeric value per document, and that value can then become a score, a filter bound, a sortable column, or an extra column in the response. This is Solr’s mechanism for scoring or ranking by something other than text relevance: popularity, price, geographic distance, recency, or any combination the built-in function catalog can express. See Function Queries for the complete reference and function list. Function queries are what relevance & scoring reaches for to blend a custom signal into the score, and they are invoked through the query-parser mechanism covered in query parsers.

Function query syntax: \{!func} and \{!frange}

A function is not valid Lucene query syntax by itself — it has to be wrapped so Solr knows to evaluate it as a function rather than parse it as a field:query clause. Two dedicated query parsers do that:

Parser Role

\{!func}

Turns the whole function into a scoring query: every document gets the function’s value as its score, and there is no separate match condition — it matches everything the function can be evaluated over.

\{!frange}

Turns the function into an unscored range filter: only documents whose function value falls within l (lower bound) and u (upper bound) match, exactly like a range query but over a computed value instead of a stored field.

# {!func}: score purely by a recip() decay of price -- cheaper items score higher.
curl --get "http://localhost:8983/solr/books/select" \
  --data-urlencode 'q={!func}recip(price,1,1000,1000)' \
  --data-urlencode 'fl=id,title,price,score'

# {!frange}: filter to documents whose computed "value density" falls in [1,5],
# combined with a normal text query.
curl --get "http://localhost:8983/solr/books/select" \
  --data-urlencode 'q=title:earthsea' \
  --data-urlencode 'fq={!frange l=1 u=5}div(popularity,price)'
# https://solr.apache.org/guide/solr/latest/query-guide/function-queries.html

A function can also appear without either parser, as the value of the special val pseudo-field inside a lucene/edismax query string (val:"recip(price,1,1000,1000)"), or as one of the boost functions the DisMax/eDisMax parsers accept directly — bf (an additively-combined boost function, itself effectively \{!func}) and eDisMax’s boost (a multiplicatively-combined one). Nested calls, field references, and numeric/quoted-string constants are all valid function arguments, and whitespace inside the argument list is fine as long as the whole expression stays one unbroken token when it is not quoted.

Returning functions as pseudo-fields

Any function can be listed in fl alongside real field names; Solr evaluates it per hit and adds it to the response under the literal function text as its key — a pseudo-field, computed but never stored. This is the way to see a ranking signal’s actual value without adding a stored field for it, or to hand a client a derived number (a discount, a distance, a normalized score) it would otherwise have to compute itself.

curl --get "http://localhost:8983/solr/books/select" \
  --data-urlencode 'q=*:*' \
  --data-urlencode 'fl=id,title,price,score,sum(price,shipping_f),div(popularity,price)'
# each hit gains "sum(price,shipping_f)" and "div(popularity,price)" keys alongside id/title/price/score
# https://solr.apache.org/guide/solr/latest/query-guide/function-queries.html

fl accepts an alias for a function pseudo-field with alias:function(…​) syntax (fl=discount:sum(price,shipping_f)), which is worth using whenever the raw function text would be an awkward JSON/response key for a client to consume.

Sorting on functions

sort accepts a function exactly where it accepts a field name, each followed by asc/desc — useful whenever the ranking should follow a computed value (distance from a point, a blended popularity/price score) rather than any single stored field.

# Rank by "value for money" -- popularity per unit price -- highest first.
curl --get "http://localhost:8983/solr/books/select" \
  --data-urlencode 'q=*:*' \
  --data-urlencode 'sort=div(popularity,price) desc'
# https://solr.apache.org/guide/solr/latest/query-guide/function-queries.html

Sorting on a function is more expensive than sorting on a docValues-backed field because Solr evaluates the expression for every candidate document rather than reading a precomputed column; for a sort that is used constantly, consider computing and storing the value at index time instead if the inputs rarely change.

The function catalog

The full catalog lives at Function Queries; the groups below are the ones reached for most often.

Math functions

sum, sub, product, div, abs, pow, sqrt, log, min, max, map (piecewise remapping of a value range), and linear(x,m,c) (m*x+c) cover arithmetic on field values and constants, nested arbitrarily deep — div(sum(a,b),product(c,d)) is a valid single function.

Relevancy functions

  • query(subquery, default) — runs another query as a function, returning its score for a matching document or default for a non-matching one. This is the standard way to fold a second, independently-scored query into an arithmetic expression alongside non-textual signals, e.g. product(query(\{!edismax v=$qq}),recip(price,1,1000,1000)).

  • scale(function,min,max) — linearly rescales a function’s output range across the whole result set into [min,max]. Because it depends on the min/max actually observed for the current query, it needs a second pass over the candidates and is comparatively expensive; use it to bring differently-scaled signals (a raw popularity count and a 0..1 text score) onto comparable footing before combining them.

  • recip(x,m,a,b) — computes a/(m*x+b), a reciprocal decay that is large when x is small and approaches 0 as x grows; the standard shape for "prefer cheap/near/old-less" signals such as price or distance without a hard cutoff. recip(price,1,1000,1000) in the examples above is 1000 / (price + 1000).

  • norm(field) — exposes the Lucene index-time norm for a field, when norms are enabled.

Distance functions

dist(power,x1,y1,x2,y2,…​) computes a Minkowski-family distance (power 2 is Euclidean, 1 is Manhattan) between two vectors given as function or field arguments; sqedist is the squared Euclidean variant (cheaper — skips the square root — when only relative ordering matters); hsin/geodist compute great-circle (haversine) distance between two lat/lon points, the function form of the same geospatial distance used by the \{!geofilt}/\{!bbox} query parsers on spatial fields; strdist scores string similarity (edit distance, Jaro-Winkler, and others) for fuzzy-matching two text values as a number rather than a match/no-match filter. vectorSimilarity() computes the configured similarity between two dense vectors and is the scoring primitive behind dense vector search's kNN query.

Boolean functions

and, or, xor, not combine boolean-valued functions; exists(field) tests whether a field has a value on the document; gt, gte, lt, lte, eq compare two functions/constants; if(test, value_if_true, value_if_false) is the general-purpose conditional every one of the above typically feeds into.

Date functions

ms(date) converts a date field or constant to epoch milliseconds; ms(date1,date2) returns the difference in milliseconds between two dates — the building block for a recency signal such as recip(ms(NOW,published_dt),3.16e-11,1,1), which decays smoothly as published_dt recedes from NOW.

Custom functions: writing a ValueSource

When the catalog above cannot express a signal, Solr’s function-query layer is itself extensible: a ValueSourceParser plugin, declared in solrconfig.xml and backed by a Java ValueSource implementation, adds a new named function that behaves exactly like a built-in one everywhere functions are accepted — \{!func}, fl, sort, bf/boost. Writing one is a genuine Java plugin (implement ValueSource.getValues() to produce a per-segment FunctionValues), so treat it as a last resort after checking whether map(), if(), or a query()-wrapped subquery already covers the case; for a lighter-weight escape hatch, Solr also ships a JavaScript-expression ValueSource (the Expression function) that lets a short script stand in for a full plugin class. See Solr Plugins for how plugin classes (ValueSourceParser among them) are packaged and registered.

Continue with Relevance & scoring for how function queries combine with text relevance and boosting, or Query parsers for the func/frange parsers' place among Solr’s other query parsers.