Teach it a query type it does not know¶
"In principle" means by this library. If you run the Learning-to-Rank plugin,
sltr is not unreadable to you — and a private plugin's query type was
never going to be in anyone's specification. Register a renderer for it:
use MrDlef\OsQueryDigest\Extension\{ClauseRenderer, RenderedClause};
final class SltrRenderer implements ClauseRenderer
{
public function render(array $body): ?RenderedClause
{
$model = $body['model'] ?? null;
if (!is_string($model)) {
return null; // not a shape I know — leave it opaque
}
return RenderedClause::on('_score', 'sltr')->withParam('model', $model);
}
}
$formatter = Formatter::create(
Options::create()->withClauseRenderer('sltr', new SltrRenderer()),
);
before q=(sltr(?))
after q=(_score:sltr(model=ltr_model_v3))
signature q=(_score:sltr(model=?))
A RenderedClause carries a field, a label and keyed parameters — the same
three things the vector and geo clauses reduce to, rendered the same way. The
knobs are part of the shape, their values are not, so two searches through the
same model share a fingerprint. Returning null is the honest answer for a body
you do not recognise: type(?) is true, and a guess would be a fingerprint built
on a misreading.
Three properties make this safe to hand out:
- A renderer cannot reach a type the library models. The hook sits in the
parser's default branch, so every native type has already returned before it
runs. That is structural, not a list that could drift — registering one for
termdoes nothing at all. - The hash version is marked. Register anything and fingerprints become
q5x:instead ofq5:. Your rules are no longer this library's alone, and that is exactly what a prefix exists to make visible. The twelve hex characters are untouched, soq5:abc…andq5x:abc…are still recognisably the same query. explain()names it. A digest that depended on someone's plugin code reportsextension_rendered, rather than looking like one this library produced on its own.
Extensions receive the raw clause body and return a value object — never a node of the internal tree. An extension point built on that tree would freeze it, and the tree is exactly what changes every time a query type is promoted.
All 65 aggregation types are rendered; 12 of them get a shape tuned for
readability (terms(host,10), date_histogram(@ts,1h), p95(latency)), the
rest render generically as type(field).
Top-level sections that are not modelled (highlight, collapse, rescore, …)
are listed in the notes.