Description
While working on adding support of named ES|QL query parameters to Elasticsearch clients, we encountered API modelling difficulties that led us to find a number of inconsistencies. Here below an analysis and a proposal to fix this inconsistency.
Analysis
ES|QL query parameters come in two flavors:
-
Positional parameters:
query: "from foo | where x == ? and y == ?", params: [1, 2]
The position can also be explicit using 1-based indices, e.g.
x == ?1 and y == ?2
. -
Named parameters:
query: "from foo | where x == ?x and y == ?y", params: [{x: 1}, {y: 2}]
Named parameters are an array of named values instead of a dictionary. This allows for some surprising valid requests:
-
Last named parameter wins:
query: "from foo | where x == ?x and y == ?y", params: [{x: 1}, {y: 2}, {x: 3}]
Value
3
is used forx
. The duplication would not be possible with a dictionary of values. -
Positional parameters in the query and named parameter values:
query: "from foo | where x == ? and y == ?", params: [{x: 1}, {y: 2}]
That should not be allowed (note that a mixed expression like
where x == ? and y == ?y
isn't allowed).
Representing named values as an array of key-value pairs also causes issues to represent it in the Elasticsearch API specification. It's type is:
params: Array<Value> | Array<SingleKeyDictionary<String, Value>>>
There is no obvious discriminant that would allow users to state their intent (single value or K/V). Also, in languages like Java, lists are type-erased, meaning that would have to be an untyped List<Object>
to account for both variants. And deserializing a params
property would require some very custom code like what is done in the server.
Note also that if in the future ES|QL allows complex values (i.e. JSON objects) as parameters, we can no more make the distinction between positional parameters with a complex value and named parameters.
Proposal
Named query parameters should be represented as a dictionary (a JSON object), the array being used only for positional parameters.
// named
from foo | where x == ?x and y == ?y", params: {x: 1, y: 2}]
// positional
from foo | where x == ? and y == ?", params: [1, 2]
That way we avoid the issues outlined above and can unambiguously distinguish positional and named parameters. Positional access should also be forbidden for named parameters.
To avoid a breaking API change with the existing syntax, this would come in addition to the existing array-of-named-values format, which would be considered legacy and removed from the docs to discourage its usage (it would have to be removed though if we ever add complex parameter values).