Skip to content

Conversation

@cimequinox
Copy link
Contributor

@cimequinox cimequinox commented Nov 13, 2025

?parameter support for LIKE / RLIKE

GitHub issue #131356
ESQL operators - LIKE / RLIKE https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/operators#esql-like
ESQL REST API - Passing parameters https://www.elastic.co/docs/reference/query-languages/esql/esql-rest#esql-rest-params
Previous parameter Pull Requests #122459 #115061 #112905

Background

The ES|QL LIKE and RLIKE operators currently support matching a field against a pattern string or list of pattern strings. For example

POST /_query
{
  "query": """
    FROM employees
    | WHERE first_name RLIKE ".leja.*"
    | KEEP first_name, last_name
  """
}

ESQL 131356 would like to extend this to support use of ? rest query placeholder parameters. E.g.

POST /_query
{
  "query": """
    FROM employees
    | WHERE first_name RLIKE ?pattern
    | KEEP first_name, last_name
  """,
  "params": [{"pattern" : ".leja.*"}]
}

Implementation Plan

  1. Change the production rules in the Antlr grammar for LIKE and RLIKE.
  2. Generate new EsqlBaseParser files with gradle
  3. Change methods of ExpressionBuilder affected by the change.
  4. Get changes to pass all current tests.
  5. Add new unit tests
  6. Add new CsvTests if the csv spec framework can simulate rest query parameters.
  7. Add new RestEsqlTestCase tests
  8. Add new yamlRestTests
  9. Update documentation for LIKE and RLIKE functions
  10. Revise based on feedback

Antlr Grammar

ESQL 131356 will make a minor change to x-pack/plugin/esql/src/main/antlr/parser/Expression.g4 which holds the parser production rules for the LIKE and RLIKE operators.

regexBooleanExpression
    : valueExpression (NOT)? LIKE string                               #likeExpression
    | valueExpression (NOT)? RLIKE string                              #rlikeExpression
    | valueExpression (NOT)? LIKE LP string  (COMMA string )* RP       #likeListExpression
    | valueExpression (NOT)? RLIKE LP string  (COMMA string )* RP      #rlikeListExpression
    ;

Supporting ? parameters in the parser can be accomplished with a simple extension

regexBooleanExpression
    : valueExpression (NOT)? LIKE stringOrParameter                                     #likeExpression
    | valueExpression (NOT)? RLIKE stringOrParameter                                    #rlikeExpression
    | valueExpression (NOT)? LIKE LP stringOrParameter  (COMMA stringOrParameter )* RP  #likeListExpre...
    | valueExpression (NOT)? RLIKE LP stringOrParameter (COMMA stringOrParameter )* RP  #rlikeListExpr...
    ;

stringOrParameter
    : string
    | parameter
    ;

Generated files

% ./gradlew :x-pack:plugin:esql:regen

will generate updated x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser files

EsqlBaseParser.interp EsqlBaseParser.java EsqlBaseParserBaseListener.java EsqlBaseParserBaseVisitor.java EsqlBaseParserListener.java EsqlBaseParserVisitor.java

ESQL 131356 should require no manual changes to these files.

QueryParam

The ES|QL parser represents ? placeholders as QueryParam records.
This is listed here only for reference - ESQL 131356 should require no change to this record.

public record QueryParam(String name, Object value, DataType type, ...  classification) {
  public String nameValue() {
    return "{" + (this.name == null ? "" : this.name + ":") + this.value + "}";
  }
  @Override
  public String toString() {
    return value + " [" + name + "][" + type + "][" + classification + "]";
  }
}

ExpressionBuilder

ESQL 131356 will require minor changes to the ExpressionBuilder.

visitParam

The ES|QL parser converts QueryParam records into Literals in the visitParam function as indicated with ◀───. Listed here for reference - ESQL 131356 should require no change to this function.

private Expression visitParam(EsqlBaseParser.ParameterContext ctx, QueryParam param) {
  Source source = source(ctx);
  DataType type = param.type();
  var value = param.value();    ◀───
  ParserUtils.ParamClassification classification = param.classification();
  // RequestXContent does not allow null value for identifier or pattern
  if (value != null && classification != VALUE) {
    if (classification == PATTERN) {
      // let visitQualifiedNamePattern create a real UnresolvedNamePattern with Automaton
      return new UnresolvedNamePattern(source, null, value.toString(), value.toString());
    } else {
      return new UnresolvedAttribute(source, value.toString());
    }
  }
  if ((type == KEYWORD || type == TEXT)) {
    if (value instanceof String) {
      value = BytesRefs.toBytesRef(value);   ◀───
    } else if (value instanceof List<?> list) {
      value = list.stream().map(v -> v instanceof String ? BytesRefs.toBytesRef(v) : v)    ◀───
            .toList();
    }
  }
  return new Literal(source, value, type);    ◀───
}

visitLikeExpression, visitLikeListExpression

visitRlikeExpression, visitRlikeListExpression

These ExpressionBuilder functions construct LIKE and RLIKE objects for subsequent processing by ES|QL. They currently handle strings and lists of strings. For example

@Override
public Expression visitLikeExpression(EsqlBaseParser.LikeExpressionContext ctx) {
  Source source = source(ctx);
  Expression left = expression(ctx.valueExpression());
  Literal patternLiteral = visitString(ctx.string());   ◀───
  try {
    WildcardPattern pattern =
      new WildcardPattern(BytesRefs.toString(patternLiteral.fold(FoldContext.small())));    ◀───
    WildcardLike result = new WildcardLike(source, left, pattern);
    return ctx.NOT() == null ? result : new Not(source, result);
  } catch (InvalidArgumentException e) {
    throw new ParsingException(source, "Invalid pattern for LIKE [{}]: [{}]",
      patternLiteral, e.getMessage());
  }
}

ESQL 131356 will require minor changes to these to support parameters and lists of parameters by delegating to appropriate helper functions. For example

@Override
public Expression visitLikeExpression(EsqlBaseParser.LikeExpressionContext ctx) {
  Source source = source(ctx);
  Expression left = expression(ctx.valueExpression());
  StringOrParameterContext right = ctx.stringOrParameter();    ◀───
  try {
    String patternString = stringFromStringOrParameter(right);     ◀───
    WildcardPattern pattern = new WildcardPattern(patternString);     ◀───
    WildcardLike result = new WildcardLike(source, left, pattern);
    return ctx.NOT() == null ? result : new Not(source, result);
  } catch (InvalidArgumentException e) {
    throw new ParsingException(source, "Invalid pattern for LIKE [{}]: [{}]",
      patternLiteral, e.getMessage());
  }
}

Helper functions such as stringFromStringOrParameter will provide the logic ESQL 131356 needs to handle parser objects in a uniform way and raise exceptions with appropriate error messages for unsupported parameter types.

Test Changes

Unit Tests

ESQL 131356 will require minor enhancements to the unit tests which use QueryParam including the following files under x-pack/plugin/esql/src/test/java/org/elasticsearch

Test
xpack/esql/parser/StatementParserTests.java
xpack/esql/analysis/AnalyzerTests.java
xpack/esql/analysis/VerifierTests.java
xpack/esql/optimizer/OptimizerVerificationTests.java
xpack/esql/action/EsqlQueryRequestTests.java

Update: given that the changes for ESQL 131356 all occur in the parsing stage and involve no analyzer or optimizer changes, there should be no need for additional analysis.VerifierTests or optimizer.OptimizerVerificationTests. The parser.StatementParserTests and analysis.AnalyzerTests should suffice to cover all relevant code paths. Similarly no query request tests should be needed as ESQL 131356 makes no changes to the parameter protocol or request marshalling.

CsvTest Tests

At the time of this writing it is unclear to the author if the CsvTest framework supports rest parameters. If it does, ESQL 131356 will add new queries to the where-like.csv-spec test.
The CsvTests do not support rest parameters.

RestEsqlTestCase

ESQL 131356 will add new tests to RestEsqlTestCase.java file to cover use of LIKE / RLIKE with ? rest query parameter placeholders.

yamlRestTests

ESQL 131356 will add new queries to the 80_text.yml test to make use of ? rest query parameter placeholders in LIKE and RLIKE queries.

---
"like with parameters":
  - requires:
      ... 
  - do:
      esql.query:
        body:
          query: 'FROM test | WHERE job LIKE ?like | KEEP job | LIMIT 2'
          params: [{"like": "IT*"}]

  - length: { values: 1 }
  - match: { values.0: [ "IT Director" ] }

Documentation Changes

ES|QL operators | Reference
ESQL 131356 will update the documentation in the operators reference section of the ES|QL documentation generated with gradle rules which rely on annotations in the following files under x-pack/plugin/esql/src/main/java/org/elasticsearch

Function
xpack/esql/expression/function/scalar/string/regex/RLike.java
xpack/esql/expression/function/scalar/string/regex/WildcardLike.java

Planned approach

The text of ES|QL query and result examples resides in csv-spec test files such as docs.csv-spec.
These are included into the documentation by directives in the detailed description of the @FunctionInfo annotation. For example in RLike.java the directive

<<load-esql-example, file=docs tag=rlikeParams>>

would reference text in the corresponding tag in docs.csv such as:

// tag::rlikeParams[] FROM employees
| WHERE first_name RLIKE ?pattern
| KEEP first_name, last_name
// end::rlikeParams[]
;

Problem

It was expected that ESQL 131356 would add tagged examples to the
docs.csv-spec but that won’t work for this feature because it would
cause the CsvTests to fail since they cannot provide a ?pattern value.

Workaround

Provide the markdown manually.

Miscellaneous

  • Support for “double-param” ?? parameters to allow substitution of arbitrary identifiers won’t be part of this work. If there’s a need for those we can add it in a followup change.

  • We may want to support array parameter values in a followup change. For example the following two queries would be equivalent if we allow an array value as a pattern.

POST /_query                                │ POST /_query
{                                           │ {
  "query": """                              │   "query": """
    FROM employees                          │     FROM employees
    | WHERE first_name LIKE ?pattern        │     | WHERE first_name LIKE ("A*", "B*")
  """,                                      │   """
  "params": [{"pattern" : ["A*", "B*"]}     │ }
}

Status

  • Write implementation plan
  • Identify appropriate tests and commands to run them
  • Write a mimimal implementation
  • Update documentation and tests
  • Draft PR
  • Complete the implementation
  • Fix tests, update PR and invite reviewers
  • Revise based on feedback
    • ctx.LIKE().getText() instead of hardcoding "LIKE"
    • investigate missing parameter error mechanism see testInvalidPositionalParams
    • add tests for positional and anonymous parameters
    • return a proper error for array valued parameters until they're supported
    • reuse existing visitParam
    • preserve existing locations of functions for git history and ease of review
    • improve source parameter naming
    • add comment explaining need for KEYWORD type check
    • make private LikeQueryParam helper class final
  • Update known limitations after merge

Phase 1 - identify and run tests

  • Update this plan, identifying tests to be updated and how each is run

  • Run esql :x-pack:plugin:esql:test tasks for tests to be updated

    • org.elasticsearch.xpack.esql.parser.StatementParserTests
    • org.elasticsearch.xpack.esql.analysis.AnalyzerTests
  • Run single node esql :x-pack:plugin:esql:qa:server:single-node:javaRestTest tasks

    • org.elasticsearch.xpack.esql.qa.single_node.RestEsqlIT

The commands to run these are

 % ./gradlew :x-pack:plugin:esql:test \
       --tests "org.elasticsearch.xpack.esql.parser.StatementParserTests"
 % ./gradlew :x-pack:plugin:esql:test \
       --tests "org.elasticsearch.xpack.esql.analysis.AnalyzerTests"
 % ./gradlew :x-pack:plugin:esql:qa:server:single-node:javaRestTest \
       --tests "org.elasticsearch.xpack.esql.qa.single_node.RestEsqlIT"

Phase 2 - minimal feature

  • Write new RestEsqlTestCase for the feature
    • testParamsWithLike
  • Run the new test, which should fail because syntax isn’t supported
    • "parsing_exception" reason: "line 1:42: no viable alternative at input 'keyword like ?pattern'"
  • Update the parser to implement the feature
    • visitLikeExpression
    • stringFromStringOrParameter
  • Run the new test, which should now succeed
  • Test manual request works as expected, e.g.
{
    "query": "FROM base_conversion | WHERE string LIKE ?pattern | KEEP string ",
    "params": [{"pattern" : "f*"}]
}

Phase 3a - document

  • Update the examples in the docs.csv-spec included into the documentation

    • x-pack/plugin/esql/qa/testFixtures/src/main/resources/docs.csv-spec
      This won’t work since it causes the CsvSpec tests to fail.
      As a workaround manually add markdown directly in the annotations.
  • Update the annotations of the LIKE and RLIKE functions to indicate parameter support

    • esql/expression/function/scalar/string/regex/RLike.java
    • esql/expression/function/scalar/string/regex/WildcardLike.java

The unit test generates the documentation and docs-builder renders and serves it locally.

% ./gradlew :x-pack:plugin:esql:test -Dtests.class='*Like*Tests'
% docs-builder serve

Open http://localhost:3000/reference/query-languages/esql/functions-operators/operators#esql-like to view the rendered documentation.

Phase 3b - implement

  • Update this plan with relevant details from earlier phases

  • Add capabilities needed by tests and ensure tests check for them

    • EsqlCapabilities.Cap.LIKE_PARAMETER_SUPPORT
  • Update parser/StatementParserTests

    • testLikeRLikeParam
  • Identify and update other tests listed in Phase 1

  • Run unit test with ./gradlew :x-pack:plugin:esql:test --tests

    • org.elasticsearch.xpack.esql.parser.StatementParserTests.testLikeRLikeParam
    • org.elasticsearch.xpack.esql.analysis.AnalyzerTests.testLikeParameters
  • Run integration test with ./gradlew :x-pack:plugin:esql:qa:server:single-node:javaRestTest —tests

    • org.elasticsearch.xpack.esql.qa.single_node.RestEsqlIT
  • Run integration test with ./gradlew :x-pack:plugin:esql:qa:server:single-node:yamlRestTest --tests

    • org.elasticsearch.xpack.esql.qa.single_node.EsqlClientYamlIT \
      -Dtests.rest.suite=esql/80_text --configuration-cache

Phase 4 - prepare draft PR

Phase 5 - finish

  • Update parser for LIKE list, RLIKE and RLIKE list
  • Update ExpressionBuilder for productions
    • visitLikeListExpression
    • visitRLikeExpression
    • visitRLikeListExpression

Phase 6 - fix tests, update PR, invite reviewers

  • Update parser/StatementParserTests for LIKE list, RLIKE and RLIKE list

    • testLikeRLikeParam
  • Update other tests for LIKE list, RLIKE and RLIKE list

  • Run unit test with ./gradlew :x-pack:plugin:esql:test --tests

    • "org.elasticsearch.xpack.esql.parser.StatementParserTests.test*Param"
    • "org.elasticsearch.xpack.esql.analysis.AnalyzerTests.test*Parameters"
  • Run integration test with ./gradlew :x-pack:plugin:esql:qa:server:single-node:javaRestTest -–tests

    • org.elasticsearch.xpack.esql.qa.single_node.RestEsqlIT
  • Run integration test with ./gradlew :x-pack:plugin:esql:qa:server:single-node:yamlRestTest --tests

    • org.elasticsearch.xpack.esql.qa.single_node.EsqlClientYamlIT \
      -Dtests.rest.suite=esql/80_text --configuration-cache
  • Wrap-up

    • Fix any test issues
    • Update draft PR
    • Invite reviewers
    • Address feedback
    • Update known limitations

@elasticsearchmachine elasticsearchmachine added v9.3.0 needs:triage Requires assignment of a team area label labels Nov 13, 2025
@cimequinox cimequinox marked this pull request as draft November 13, 2025 19:27
@cimequinox cimequinox changed the title Esql like parameters 131356 ES|QL: support for parameters in LIKE and RLIKE Nov 13, 2025
@github-actions
Copy link
Contributor

ℹ️ Important: Docs version tagging

👋 Thanks for updating the docs! Just a friendly reminder that our docs are now cumulative. This means all 9.x versions are documented on the same page and published off of the main branch, instead of creating separate pages for each minor version.

We use applies_to tags to mark version-specific features and changes.

Expand for a quick overview

When to use applies_to tags:

✅ At the page level to indicate which products/deployments the content applies to (mandatory)
✅ When features change state (e.g. preview, ga) in a specific version
✅ When availability differs across deployments and environments

What NOT to do:

❌ Don't remove or replace information that applies to an older version
❌ Don't add new information that applies to a specific version without an applies_to tag
❌ Don't forget that applies_to tags can be used at the page, section, and inline level

🤔 Need help?

@cimequinox cimequinox marked this pull request as ready for review November 17, 2025 12:03
@cimequinox cimequinox added the :Analytics/ES|QL AKA ESQL label Nov 17, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) label Nov 17, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

@elasticsearchmachine elasticsearchmachine removed the needs:triage Requires assignment of a team area label label Nov 17, 2025
@cimequinox cimequinox added >enhancement ES|QL-ui Impacts ES|QL UI labels Nov 17, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/kibana-esql (ES|QL-ui)

Copy link
Member

@fang-xing-esql fang-xing-esql left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @cimequinox ! The change in the grammar and parser looks good to me. I added some comment around testing positional and anonymous parameters(they should already work, just to make the tests cover more varieties of parameters), and combining parameter related errors in the ExpressionBuilder into one ParsingException and report them together at the end, the rest look pretty good to me.

Expression left = expression(ctx.valueExpression());
Literal patternLiteral = visitString(ctx.string());
EsqlBaseParser.StringOrParameterContext right = ctx.stringOrParameter();
String patternString = stringFromStringOrParameter(source, "LIKE", right);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use ctx.LIKE().getText() to get the operator name, same for the other three methods below.

Copy link
Contributor Author

@cimequinox cimequinox Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change. Done.

}
if (pctx instanceof EsqlBaseParser.InputNamedOrPositionalParamContext inopctx) {
psrc = source(inopctx);
QueryParam param = paramByNameOrPosition(inopctx.NAMED_OR_POSITIONAL_PARAM());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a mechanism to collect all potential missing-parameter errors in paramByNameOrPosition. These errors are collected into the ParsingContext, and then combined and reported at the end of parsing, allowing users to see all parameter-related issues at once. The other two callers of paramByNameOrPosition can be used as references, they do not error out immediately when a parameter is missing. The same approach can be applied to the two ParsingExceptions later in this method.

testInvalidPositionalParams in StatementParserTests shows multiple query parameter related errors are combined and reported together.

Copy link
Contributor Author

@cimequinox cimequinox Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out.
I'll review the code you mention and revise the error handling.
I've revised the error handling to collect parameter errors instead of raising them immediately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some notes from code I've seen:

Within the ExpressionBuilder for parameter-related logic we try to accumulate parameter related exceptions with context.params().addParsingError() instead of immediately throwing them so that we may provide more complete error reporting if there are multiple problems with parameters.

For example in x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/ExpressionBuilder.java

  │ context.params()
▶▶│        .addParsingError(new ParsingException(source(node), "No parameter is defined ...

then later in x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java

  │ protected LogicalPlan plan(ParseTree ctx) {
  │     LogicalPlan p = ParserUtils.typedParsing(this, ctx, LogicalPlan.class);
  │     if (p instanceof Explain == false && p.anyMatch(logicalPlan -> logicalPlan inst ...
  │         throw new ParsingException(source(ctx), "EXPLAIN does not support downstrea ...
  │     }
  │     if (p instanceof Explain explain && explain.query().anyMatch(logicalPlan -> log ...
  │         // TODO this one is never reached because the Parser fails to understand mu ...
  │         throw new ParsingException(source(ctx), "EXPLAIN cannot be used inside anot ...
  │     }
▶▶│     var errors = this.context.params().parsingErrors();
  │     if (errors.hasNext() == false) {
  │         return p;
  │     } else {
▶▶│         throw ParsingException.combineParsingExceptions(errors);
  │     }
  │ }

However we do throw ParsingException in various other situations.

With respect to this change, I think it is relatively easy to collect LIKE/RLIKE errors if we are also allow temporary use of "invalid" patterns or object for LIKE/RLIKE expressions. I've updated the logic to follow this approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are trying to combine all parameters related errors into one ParsingException and reported them together at the end. For a lot of other types of parsing errors we do throw them immediately.

}
}

public void testParamsWithLike() throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a few more tests that cover positional parameters (fields like ?1) and anonymous parameters (fields like ?)? We ran into issues with named parameters with array value (for example, {"pattern": ["a", "b"]}) previously, so it would be great to include some validation tests for that case as well.

Copy link
Contributor Author

@cimequinox cimequinox Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add some use of positional and anonymous parameters to the tests.
I've changed the StatementParserTests, the AnalyzerTests and the RestEsqlTestCase tests to make use of anonymous, positional and named parameters.

It did not occur to me to consider array values but they could be useful.
For example in addition to
field LIKE (?p1, ?p2) with params {"p1": "a*", "p2": "b*"}
we could allow
field LIKE ?parray with params {"parray": ["a*", "b*"]}
but I can imagine this could get complex if we allow both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did not occur to me to consider array values but they could be useful. For example in addition to field LIKE (?p1, ?p2) with params {"p1": "a*", "p2": "b*"} we could allow field LIKE ?parray with params {"parray": ["a*", "b*"]} but I can imagine this could get complex if we allow both.

Yes, supporting array parameters could add extra complexity, we got a similar request to support array parameters for inlist predicate previously, and it is not supported yet, so it is not required for this PR, making sure clear error messages are returned is good.

Copy link
Contributor Author

@cimequinox cimequinox Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the PR description suggesting we consider array parameter support in a followup change.

Copy link
Contributor Author

@cimequinox cimequinox Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the base_conversion index out of convenience, I ran a few queries passing arrays as parameters and noticed some curious behavior.

This query works as expected:
{
  "query": "FROM base_conversion | WHERE string LIKE (?, ?) | KEEP string",
  "params": ["f*", "0*"]
}
    string     
---------------
0xff           
ff             
0x32           

but this array query returns no results without generating any error

{
  "query": "FROM base_conversion | WHERE string LIKE ?patterns | KEEP string",
  "params": [{"patterns": ["f*", "0*"]}]
}
    string     
---------------

looking at the plan for each, the first becomes

ExchangeSinkExec[[string{f}#116],false]
\_ProjectExec[[string{f}#116]]
  \_FieldExtractExec[string{f}#116]<[],[]>
    \_EsQueryExec[base_conversion], indexMode[standard], ...
  "esql_single_value" : {
    "field" : "string",
    "next" : {                                  ⎞ LIKE with two patterns
      "expressionQueryBuilder" : {              ⎥                                               
        "field" : "string",                     ⎥                                               
        "expression" : "string LIKE (?, ?)"     ⎠                                               
      }
    },
    "source" : "string LIKE (?, ?)@1:30"
  }
}], tags=[]}]]

but the second becomes

ExchangeSinkExec[[string{f}#120],false]
\_ProjectExec[[string{f}#120]]
  \_FieldExtractExec[string{f}#120]<[],[]>
    \_EsQueryExec[base_conversion], indexMode[standard], ...
  "esql_single_value" : {
    "field" : "string",
    "next" : {
      "term" : {                                ⎞ LIKE pattern ["f*","0*] turned into this
        "string" : {                            ⎥                                               
          "value" : "[[66 2a], [30 2a]]",       ⎥                                               
          "boost" : 0.0                         ⎠                                               
        }
      }
    },
    "source" : "string LIKE ?patterns@1:30"
  }
}], tags=[]}]]

A simple WHERE string LIKE ?pattern with "params": [{"pattern" : "f*"}] becomes

ExchangeSinkExec[[string{f}#128],false]
\_ProjectExec[[string{f}#128]]
  \_FieldExtractExec[string{f}#128]<[],[]>
    \_EsQueryExec[base_conversion], indexMode[standard], ...
  "esql_single_value" : {
    "field" : "string",
    "next" : {
      "wildcard" : {                            ⎞ LIKE pattern "f*" was translated to a wildcard
        "string" : {                            ⎥                                               
          "wildcard" : "f*",                    ⎥                                               
          "boost" : 0.0                         ⎠                                               
        }
      }
    },
    "source" : "string LIKE ?pattern@1:30"
  }
}], tags=[]}]]

I think in the array case it slips by because the "type" of the array is the type of the first element so it gets passed farther than expected.

This PR needs a little more error checking to properly prohibit array valued parameters and generate a useful error.

Copy link
Contributor Author

@cimequinox cimequinox Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an invalidListParameter function to explicitly check for list parameters and a test to verify they are prohibited in LIKE/RLIKE expressions for now.

With this change the query which previously passed along the stringified list

{
  "query": "FROM base_conversion | WHERE string LIKE ?patterns | KEEP string",
  "params": [{"patterns": ["f*", "0*"]}]
}

now fails as expected.

{
  "error": {
     ...
    "type": "parsing_exception",
    "reason": "line 1:30: Invalid pattern parameter type for LIKE [?patterns]: expected string, found list"
  },
  "status": 400
}

@ivancea ivancea self-requested a review November 19, 2025 13:46
Comment on lines 894 to 902
if (pctx instanceof EsqlBaseParser.InputParamContext ipctx) {
psrc = source(ipctx);
QueryParam param = paramByToken(ipctx.PARAM());
if (param != null && invalidListParameter(source, psrc, opname, param)) {
return invalid;
}
exp = visitInputParam(ipctx);
}
if (pctx instanceof EsqlBaseParser.InputNamedOrPositionalParamContext inopctx) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we reuse the existing visitParam/visitInputNamedOrPositionalParam by just calling visit(pctx), instead of the 2 ifs?

I'm not sure if that's fully possible, but this function feels like reimplementing code that we have already in those other methods

Copy link
Contributor Author

@cimequinox cimequinox Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Originally I simply delegated to those and added the extra checking for list values later. I can probably simplify this with an additional argument to specify that in this path list values should be rejected.

I've refactored the code a bit to use a helper class which reuses visitParam. I hope the code is clearer.

cimequinox and others added 7 commits November 19, 2025 06:36
…expression/function/scalar/string/regex/RLike.java


ReST ─▶ REST

Co-authored-by: Iván Cea Fontenla <ivancea96@outlook.com>
…expression/function/scalar/string/regex/WildcardLike.java


ReST ─▶ REST

Co-authored-by: Iván Cea Fontenla <ivancea96@outlook.com>
# Conflicts:
#	x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RestEsqlTestCase.java
# Conflicts:
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.interp
#	x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser.java
Copy link
Contributor

@julian-elastic julian-elastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for addressing my comments

Copy link
Member

@fang-xing-esql fang-xing-esql left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @cimequinox!

@cimequinox cimequinox merged commit f80b370 into elastic:main Nov 26, 2025
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Analytics/ES|QL AKA ESQL >enhancement ES|QL-ui Impacts ES|QL UI Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) v9.3.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ES|QL: Add support for named and positional parameters for LIKE/RLIKE operator

5 participants