Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0f080a5
Fix for top
julian-elastic Jul 9, 2025
8a24206
Fix for sample function
julian-elastic Jul 10, 2025
9dd5502
[CI] Auto commit changes from spotless
Jul 10, 2025
67dc79f
Handle Match function
julian-elastic Jul 10, 2025
923dc02
Handle MatchPhrase function
julian-elastic Jul 11, 2025
29cce29
Handle MultiMatch function
julian-elastic Jul 11, 2025
fdf54dc
Handle QueryString function
julian-elastic Jul 11, 2025
2641cb3
Handle KNN function
julian-elastic Jul 11, 2025
8e2c6b4
Merge remote-tracking branch 'origin/foldable' into foldable
julian-elastic Jul 12, 2025
15dfed3
Migrate tests from VerifierTests to 230_folding.yml
julian-elastic Jul 14, 2025
408c8fd
Remove partiallyFoldable code as it is not needed
julian-elastic Jul 14, 2025
f187139
Fix some of the failing UTs
julian-elastic Jul 14, 2025
d66fdfc
Merge branch 'main' into foldable
julian-elastic Jul 15, 2025
d80dd57
[CI] Auto commit changes from spotless
Jul 15, 2025
6f34981
Remove some debugging code
julian-elastic Jul 15, 2025
4247581
Fix failing UTs in old version
julian-elastic Jul 16, 2025
b2213e1
Merge branch 'main' into foldable
julian-elastic Jul 16, 2025
029f96a
Integrate with knn_function_v3
julian-elastic Jul 16, 2025
18e18a2
Fix UT fails
julian-elastic Jul 17, 2025
23359b5
Fix UT fail
julian-elastic Jul 17, 2025
4007272
Merge branch 'main' into foldable
julian-elastic Jul 17, 2025
9537344
Fix merge error
julian-elastic Jul 17, 2025
8e9d34b
Merge branch 'main' into foldable
julian-elastic Jul 18, 2025
20245b6
Fix merge error
julian-elastic Jul 18, 2025
b2e796c
[CI] Auto commit changes from spotless
Jul 18, 2025
cb676a7
Fix UT error
julian-elastic Jul 18, 2025
910c1d0
Merge branch 'main' into foldable
julian-elastic Jul 18, 2025
91f76d6
Remove isNotNullAndFoldable function completely
julian-elastic Jul 18, 2025
b3ee3a0
Update docs/changelog/130944.yaml
julian-elastic Jul 18, 2025
8dfdf1a
Fix UT failures related to trying to get the datatype on unresolved a…
julian-elastic Jul 18, 2025
4087f30
Merge branch 'main' into foldable
julian-elastic Jul 19, 2025
8e11323
Merge branch 'main' into foldable
julian-elastic Jul 21, 2025
89dfb4e
Update docs/changelog/130944.yaml
julian-elastic Jul 21, 2025
55b9246
Fix failing UTs
julian-elastic Jul 21, 2025
94909ec
Fix failing UT
julian-elastic Jul 21, 2025
2444673
Merge branch 'main' into foldable
julian-elastic Jul 21, 2025
1d730a5
Fix UT error
julian-elastic Jul 21, 2025
6714b8b
Update docs/changelog/130944.yaml
julian-elastic Jul 21, 2025
094aa80
Address code review feedback
julian-elastic Jul 23, 2025
50918a4
Fix checkstyle
julian-elastic Jul 23, 2025
5012a2e
Merge branch 'main' into foldable
julian-elastic Jul 23, 2025
09bbf06
Address code review comments
julian-elastic Jul 24, 2025
9fce42b
Merge branch 'main' into foldable
julian-elastic Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/130944.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 130944
summary: Remove unnecessary calls to Fold
area: ES|QL
type: enhancement
issues:
- 119756
Original file line number Diff line number Diff line change
Expand Up @@ -133,36 +133,6 @@ public static TypeResolution isFoldable(Expression e, String operationName, Para
return TypeResolution.TYPE_RESOLVED;
}

/**
* Is this {@link Expression#foldable()} and not {@code null}.
*
* @deprecated instead of calling this, check for a {@link Literal} containing
* {@code null}. Foldable expressions will be folded by other rules,
* eventually, to a {@link Literal}.
*/
@Deprecated
public static TypeResolution isNotNullAndFoldable(Expression e, String operationName, ParamOrdinal paramOrd) {
TypeResolution resolution = isFoldable(e, operationName, paramOrd);

if (resolution.unresolved()) {
return resolution;
}

if (e.dataType() == DataType.NULL || e.fold(FoldContext.small()) == null) {
resolution = new TypeResolution(
format(
null,
"{}argument of [{}] cannot be null, received [{}]",
paramOrd == null || paramOrd == DEFAULT ? "" : paramOrd.name().toLowerCase(Locale.ROOT) + " ",
operationName,
Expressions.name(e)
)
);
}

return resolution;
}

public static TypeResolution isNotNull(Expression e, String operationName, ParamOrdinal paramOrd) {
if (e.dataType() == DataType.NULL) {
return new TypeResolution(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.esql.expression.function;

import org.elasticsearch.common.lucene.BytesRefs;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
import org.elasticsearch.xpack.esql.common.Failure;
import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.Literal;

import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
import static org.elasticsearch.xpack.esql.common.Failure.fail;

public class FunctionUtils {
/**
* A utility class to validate the type resolution of expressions before and after folding.
* If null is passed for Failures to the constructor, it means we are only type resolution.
* This is usually called when doing pre-folding validation.
* If a {@link Failures} instance is passed, it means we are doing post-folding validation as well.
* This is usually called after folding is done, from during
* {@link org.elasticsearch.xpack.esql.capabilities.PostOptimizationVerificationAware} verification
*/
public static class TypeResolutionValidator {

Expression.TypeResolution typeResolution = Expression.TypeResolution.TYPE_RESOLVED;
@Nullable
private final Failures postFoldingFailures; // null means we are doing pre-folding validation only
private final Expression field;

public TypeResolutionValidator(Expression field, Failures failures) {
this.field = field;
this.postFoldingFailures = failures;
}

public void reportPostFoldingFailure(Failure failure) {
if (postFoldingFailures != null) {
postFoldingFailures.add(failure);
}
}

public void reportPreFoldingFailure(Expression.TypeResolution message) {
typeResolution = message;
if (postFoldingFailures != null) {
postFoldingFailures.add(fail(field, message.message()));
}
}

public Expression.TypeResolution getResolvedType() {
return typeResolution;
}
}

public static Integer limitValue(Expression limitField, String sourceText) {
if (limitField instanceof Literal literal) {
Object value = literal.value();
if (value instanceof Integer intValue) {
return intValue;
}
}
throw new EsqlIllegalArgumentException(format(null, "Limit value must be an integer in [{}], found [{}]", sourceText, limitField));
}

/**
* We check that the limit is not null and that if it is a literal, it is a positive integer
* During postOptimizationVerification folding is already done, so we also verify that it is definitively a literal
*/
public static Expression.TypeResolution resolveTypeLimit(Expression limitField, String sourceText, Failures failures) {
TypeResolutionValidator validator = new TypeResolutionValidator(limitField, failures);
if (limitField == null) {
validator.reportPreFoldingFailure(
new Expression.TypeResolution(format(null, "Limit must be a constant integer in [{}], found [{}]", sourceText, limitField))
);
} else if (limitField instanceof Literal literal) {
if (literal.value() == null) {
validator.reportPreFoldingFailure(
new Expression.TypeResolution(
format(null, "Limit must be a constant integer in [{}], found [{}]", sourceText, limitField)
)
);
} else {
int value = (Integer) literal.value();
if (value <= 0) {
validator.reportPreFoldingFailure(
new Expression.TypeResolution(format(null, "Limit must be greater than 0 in [{}], found [{}]", sourceText, value))
);
}
}
} else {
// it is expected that the expression is a literal after folding
// we fail if it is not a literal
validator.reportPostFoldingFailure(
fail(limitField, "Limit must be a constant integer in [{}], found [{}]", sourceText, limitField)
);
}
return validator.getResolvedType();
}

/**
* We check that the query is not null and that if it is a literal, it is a string
* During postOptimizationVerification folding is already done, so we also verify that it is definitively a literal
*/
public static Expression.TypeResolution resolveTypeQuery(Expression queryField, String sourceText, Failures failures) {
TypeResolutionValidator validator = new TypeResolutionValidator(queryField, failures);
if (queryField == null) {
validator.reportPreFoldingFailure(
new Expression.TypeResolution(format(null, "Query must be a valid string in [{}], found [{}]", sourceText, queryField))
);
} else if (queryField instanceof Literal literal) {
if (literal.value() == null) {
validator.reportPreFoldingFailure(
new Expression.TypeResolution(format(null, "Query value cannot be null in [{}], but got [{}]", sourceText, queryField))
);
}
} else {
// it is expected that the expression is a literal after folding
// we fail if it is not a literal
validator.reportPostFoldingFailure(
fail(queryField, "Query must be a valid string in [{}], found [{}]", sourceText, queryField)
);
}
return validator.getResolvedType();
}

public static Object queryAsObject(Expression queryField, String sourceText) {
if (queryField instanceof Literal literal) {
return literal.value();
}
throw new EsqlIllegalArgumentException(
format(null, "Query value must be a constant string in [{}], found [{}]", sourceText, queryField)
);
}

public static String queryAsString(Expression queryField, String sourceText) {
if (queryField instanceof Literal literal) {
return BytesRefs.toString(literal.value());
}
throw new EsqlIllegalArgumentException(
format(null, "Query value must be a constant string in [{}], found [{}]", sourceText, queryField)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,31 @@
import org.elasticsearch.compute.aggregation.SampleIntAggregatorFunctionSupplier;
import org.elasticsearch.compute.aggregation.SampleLongAggregatorFunctionSupplier;
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
import org.elasticsearch.xpack.esql.capabilities.PostOptimizationVerificationAware;
import org.elasticsearch.xpack.esql.common.Failures;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.FunctionType;
import org.elasticsearch.xpack.esql.expression.function.FunctionUtils;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.planner.ToAggregator;

import java.io.IOException;
import java.util.List;

import static org.elasticsearch.common.logging.LoggerMessageFormat.format;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNull;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
import static org.elasticsearch.xpack.esql.expression.function.FunctionUtils.resolveTypeLimit;

public class Sample extends AggregateFunction implements ToAggregator {
public class Sample extends AggregateFunction implements ToAggregator, PostOptimizationVerificationAware {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Sample", Sample::new);

@FunctionInfo(
Expand Down Expand Up @@ -110,14 +112,14 @@ protected TypeResolution resolveType() {
return new TypeResolution("Unresolved children");
}
var typeResolution = isType(field(), dt -> dt != DataType.UNSIGNED_LONG, sourceText(), FIRST, "any type except unsigned_long").and(
isNotNullAndFoldable(limitField(), sourceText(), SECOND)
isNotNull(limitField(), sourceText(), SECOND)
).and(isType(limitField(), dt -> dt == DataType.INTEGER, sourceText(), SECOND, "integer"));
if (typeResolution.unresolved()) {
return typeResolution;
}
int limit = limitValue();
if (limit <= 0) {
return new TypeResolution(format(null, "Limit must be greater than 0 in [{}], found [{}]", sourceText(), limit));
TypeResolution result = resolveTypeLimit(limitField(), sourceText(), null);
if (result.equals(TypeResolution.TYPE_RESOLVED) == false) {
return result;
}
return TypeResolution.TYPE_RESOLVED;
}
Expand Down Expand Up @@ -164,11 +166,15 @@ Expression limitField() {
}

private int limitValue() {
return (int) limitField().fold(FoldContext.small() /* TODO remove me */);
return FunctionUtils.limitValue(limitField(), sourceText());
}

Expression uuid() {
return parameters().get(1);
}

@Override
public void postOptimizationVerification(Failures failures) {
FunctionUtils.resolveTypeLimit(limitField(), sourceText(), failures);
}
}
Loading
Loading