Class QuerySettingDef<T>

java.lang.Object
org.elasticsearch.xpack.esql.plan.QuerySettingDef<T>

public final class QuerySettingDef<T> extends Object
The typed handle for one ES|QL query setting. Declared as a public static final constant on QuerySettings.

Mental model

A setting is a typed knob. Users always supply it via in-query SET. For tooling that builds requests programmatically, a setting can also be exposed in the request body. We declare the knob once; the framework wires both surfaces, resolves precedence automatically (default < body < SET), and gives downstream code one typed read.

What you specify

Required:
  • a name — used as both the SET key and the body key,
  • a value type — picked by choosing a factory.
Optional:
  • withDefault — value readers see when no source supplied one;
  • withRequestBody — opt the setting into the body under settings.<name>;
  • withAliasAtRoot / withAliasAt — extra body paths, used only for BWC with settings whose top-level body fields predate this framework (time_zone, project_routing, approximation);
  • withValidator — value-level check with runtime context;
  • withReconciler — custom cross-source merge (see Reconciliation);
  • withPreview, withSnapshotOnly, withServerlessOnly — lifecycle.
Inferred: body parser wiring, SET dispatch, the precedence fold (default < body < SET, applied per setting via its reconciler), the read API. The one thing you write outside the declaration is adding the constant to QuerySettings.ALL to register it.

How to declare a setting

  1. Pick a factory matching the value type.
  2. Chain only the modifiers you need.
  3. End with build(), which validates and constructs the setting; add its constant to QuerySettings.ALL to register it.

1. SET-only

Accepted in queries as SET foo='x';. Not reachable from the body.

   public static final QuerySettingDef<String> FOO = QuerySettingDef.string("foo").build();
 

2. SET + body parameter

Also accepted as "settings": { "bar": "x" }. Tooling that constructs the body without splicing the query string uses this form.

   public static final QuerySettingDef<String> BAR = QuerySettingDef
       .string("bar").withDefault("hello").withRequestBody().build();
 

3. SET + body parameter + legacy top-level alias

Same as case 2, plus accepted at the top-level body field. Reserved for BWC with the three settings whose top-level fields predate this framework.

   public static final QuerySettingDef<ZoneId> TIME_ZONE = QuerySettingDef
       .string("time_zone", s -> ZoneId.of(s).normalized())
       .withDefault(ZoneOffset.UTC)
       .withRequestBody()
       .withAliasAtRoot()
       .build();
 

4. Structured value with a custom reconciler

Use this shape when a SET and a body contribution each fill different fields and you want them combined rather than last-wins.

   public static final QuerySettingDef<ApproximationSettings> APPROXIMATION = QuerySettingDef
       .object("approximation", ApproximationSettings::fromXContent, ApproximationSettings::parse)
       .withRequestBody()
       .withAliasAtRoot()
       .withReconciler((prev, cur) ->
           new ApproximationSettings.Builder(false).merge(prev).merge(cur).build())
       .streamFormat((out, value) -> value.writeTo(out), ApproximationSettings::new) // object/builder must set this
       .build();
 

Reading


   ZoneId tz = QuerySettings.TIME_ZONE.get(resolved);
 

Reconciliation

The same setting can arrive from default, body, and SET in one query, so reconciliation is unavoidable. The framework folds the three in ascending precedence default < body < SET. The default fold is last-wins — correct for any scalar. Override with withReconciler only when a structured value's fields should combine across sources rather than replace.

Factories

string(String), string(String, FromString) (function errors surface as the user-visible message), bool(String), object(String, JsonReader, ExpressionReader), builder(String).

When things go wrong

Three places things can fail, and each behaves the way the consumer of that surface should expect.
  • Build time. build() refuses an incoherent declaration — for example, a body-exposed setting that has no JSON reader. The node won't start, so these surface at boot rather than in production.
  • Parse time. An unknown key is a typo the client can act on, so both surfaces reject it — the body path with a 400 naming the field, the SET path with a ParsingException. A known-but-deprecated key (see QuerySettingDef.Builder.withDeprecated(java.lang.String)) is accepted with a deprecation warning on either surface. Type mismatches throw on either path.
  • Resolve time. A snapshotOnly setting supplied on a non-snapshot build throws. If a validator was declared, it runs against the resolved value with access to cluster context (snapshot-mode flag, cross-project mode, etc).