docs/custom_query.md
custom_filter and custom_scoreBleve exposes two query nodes for embedding-defined per-hit logic:
custom_filter: keep or drop each candidate hit.custom_score: mutate each candidate hit score.Bleve itself only executes callbacks that were already provided/attached by the embedding application. It does not interpret any embedder-specific payload such as callback source, "params", or requested fields.
CustomFilterQuery contains:
searcher.CustomFilterFunc callback attached by the embedderCustomScoreQuery contains:
searcher.CustomScoreFunc callback attached by the embedderEmbedding applications can construct the nodes directly with callbacks:
filterQuery := query.NewCustomFilterQueryWithFilter(childQuery,
func(d *search.DocumentMatch) bool {
return true
},
nil, // fields; nil when callback does not need field values
nil, // payload; nil when no source/params round-trip is needed
)
scoreQuery := query.NewCustomScoreQueryWithScorer(childQuery,
func(d *search.DocumentMatch) float64 {
return d.Score
},
nil, // fields
nil, // payload
)
Constructor arguments after the callback are:
fields: field names to preload into d.Fields via doc values before the callback runspayload: non-query custom attributes that must survive JSON round-trips (for example distributed fan-out)Pass nil for either when not needed:
payload := map[string]interface{}{
"params": map[string]interface{}{"weight": 0.05},
"source": "function score(doc, params) { ... }",
}
scoreQuery := query.NewCustomScoreQueryWithScorer(childQuery,
func(d *search.DocumentMatch) float64 {
f := d.Fields
return d.Score + (f["abv"].(float64) * 0.05)
},
[]string{"abv", "ibu"},
payload,
)
Payload rules:
source and paramsquery in payload; query always comes from the child query argumentfields in payload; fields come from the dedicated constructor argumentnil as the fourth argument when payload round-trip is not neededWhen the fields constructor argument is non-empty, bleve loads the listed
field values into DocumentMatch.Fields before invoking the callback. Field
values are read from doc values (column-oriented, memory-mapped), not
stored fields.
Doc values must be enabled for every field listed in "fields". If a field
does not have doc values enabled in the index mapping, its value will be silently
absent from DocumentMatch.Fields at callback time — no error is raised, the
field simply won't appear. This can cause filters to drop all hits or scores to
compute incorrectly if the callback assumes the field is present.
Doc values are enabled by default for all field types (DocValues: true) and
for dynamic mappings (DocValuesDynamic: true), so no extra configuration is
needed unless these defaults have been explicitly disabled. If they have been
disabled, re-enable them:
fm := mapping.NewTextFieldMapping()
fm.DocValues = true // default is true; only needed if previously disabled
imap.DefaultMapping.AddFieldMappingsAt("genre", fm)
For dynamic mappings, ensure docvalues_dynamic has not been set to false on
the document mapping.
Datetime fields require an explicit datetime mapping. Dynamic mapping indexes
date-like strings as text, which means the standard analyzer tokenizes the value
(e.g. "2010-07-22 20:00:20" becomes ["2010", "07", "22", ...]). The doc
value then contains tokens instead of the original date, making comparisons
fail. To use datetime fields in custom_filter or custom_score callbacks, add an
explicit field mapping with "type": "datetime" in the index definition.
At the bleve layer, doc values are decoded based on the field's mapping type:
| Mapping type | Go type in d.Fields | Notes |
|---|---|---|
text / keyword | string | Raw text bytes |
number | float64 | IEEE 754 decoded |
boolean | bool | true / false |
datetime | float64 | Epoch nanoseconds stored as a floating-point value |
fields is non-empty, a DocValueReader is created for those fields.custom_filter wraps the child with CustomFilterSearcher.custom_score wraps the child with CustomScoreSearcher.DocumentMatch.Fields, then the callback is invoked.*search.DocumentMatch; it does not receive SearchContext."custom filter query must have a query""custom score query must have a query""custom filter query must have a filter callback""custom score query must have a score callback"