Skip to content

Commit a8439d9

Browse files
author
elasticsearchmachine
committed
Initial work for PushScriptsToSource
1 parent 8562ac7 commit a8439d9

File tree

1 file changed

+108
-19
lines changed

1 file changed

+108
-19
lines changed

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/rules/physical/local/PushScriptsToSource.java

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77

88
package org.elasticsearch.xpack.esql.optimizer.rules.physical.local;
99

10-
import org.elasticsearch.index.query.MatchAllQueryBuilder;
11-
import org.elasticsearch.index.query.QueryBuilder;
12-
import org.elasticsearch.index.query.functionscore.ScriptScoreQueryBuilder;
1310
import org.elasticsearch.script.Script;
11+
import org.elasticsearch.search.sort.ScriptSortBuilder;
12+
import org.elasticsearch.search.sort.ScriptSortBuilder.ScriptSortType;
13+
import org.elasticsearch.search.sort.SortBuilder;
14+
import org.elasticsearch.search.sort.SortOrder;
1415
import org.elasticsearch.xpack.esql.core.expression.Alias;
15-
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;
16-
import org.elasticsearch.xpack.esql.core.tree.Source;
16+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
1717
import org.elasticsearch.xpack.esql.core.type.DataType;
18+
import org.elasticsearch.xpack.esql.core.type.EsField;
19+
import org.elasticsearch.xpack.esql.expression.Order;
1820
import org.elasticsearch.xpack.esql.optimizer.LocalPhysicalOptimizerContext;
1921
import org.elasticsearch.xpack.esql.optimizer.PhysicalOptimizerRules;
2022
import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec;
@@ -23,44 +25,131 @@
2325

2426
import java.util.ArrayList;
2527
import java.util.List;
28+
import java.util.Map;
2629

2730
public class PushScriptsToSource extends PhysicalOptimizerRules.ParameterizedOptimizerRule<EvalExec, LocalPhysicalOptimizerContext> {
2831

2932
@Override
3033
protected PhysicalPlan rule(EvalExec evalExec, LocalPhysicalOptimizerContext ctx) {
3134
PhysicalPlan plan = evalExec;
32-
if (evalExec.child() instanceof EsQueryExec esQueryExec) {
35+
if (evalExec.child() instanceof EsQueryExec esQueryExec && esQueryExec.canPushSorts()) {
3336
List<Alias> aliases = evalExec.fields();
3437
boolean aliasPushed = false;
3538
List<Alias> nonPushedAliases = new ArrayList<>();
36-
EvalExec scoreEval = null;
39+
EvalExec scriptEval = null;
3740
for (Alias alias : aliases) {
3841
if (aliasPushed == false && alias.child().isPushable()) {
39-
QueryBuilder queryBuilder = esQueryExec.query();
40-
if (queryBuilder == null) {
41-
queryBuilder = new MatchAllQueryBuilder();
42+
43+
if (esQueryExec.sorts() != null
44+
&& esQueryExec.sorts()
45+
.stream()
46+
.anyMatch(s -> s instanceof ScriptSort ss && ss.field().name().equals(alias.name() + "_script"))) {
47+
// Already added as a sort, skip
48+
nonPushedAliases.add(alias);
49+
continue;
4250
}
43-
Script script = new Script(alias.child().asScript());
44-
ScriptScoreQueryBuilder scriptScore = new ScriptScoreQueryBuilder(queryBuilder, script);
45-
MetadataAttribute scoreAttr = new MetadataAttribute(Source.EMPTY, MetadataAttribute.SCORE, DataType.DOUBLE, false);
46-
Alias scoreAlias = alias.replaceChild(scoreAttr);
47-
EsQueryExec scriptQueryExec = esQueryExec.withQuery(scriptScore);
48-
scriptQueryExec.attrs().add(scoreAttr);
49-
scoreEval = new EvalExec(evalExec.source(), scriptQueryExec, List.of(scoreAlias));
51+
52+
// Create a script from the expression
53+
String scriptText = alias.child().asScript();
54+
Script script = new Script(scriptText);
55+
56+
// Create an EsField for the computed value
57+
DataType dataType = alias.child().dataType();
58+
EsField esField = new EsField(
59+
alias.name(),
60+
dataType,
61+
Map.of(),
62+
false,
63+
EsField.TimeSeriesFieldType.NONE
64+
);
65+
66+
// Create a FieldAttribute for the computed value
67+
FieldAttribute fieldAttr = new FieldAttribute(
68+
alias.source(),
69+
alias.name() + "_script",
70+
esField
71+
);
72+
73+
// Create a script sort that computes the value
74+
// We use NUMBER type for numeric results - adjust based on actual type if needed
75+
ScriptSortType sortType = alias.child().dataType().isNumeric()
76+
? ScriptSortType.NUMBER
77+
: ScriptSortType.STRING;
78+
79+
ScriptSort scriptSort = new ScriptSort(
80+
fieldAttr,
81+
script,
82+
sortType,
83+
Order.OrderDirection.ASC // Direction doesn't matter, we just want the value
84+
);
85+
86+
// Add the sort to the query exec
87+
List<EsQueryExec.Sort> newSorts = new ArrayList<>();
88+
newSorts.add(scriptSort);
89+
EsQueryExec scriptQueryExec = esQueryExec.withSorts(newSorts);
90+
// TODO Keep previous sorts
91+
92+
// Add the field attribute to attrs so it's available in the output
93+
scriptQueryExec.attrs().add(fieldAttr);
94+
95+
// Create an alias that references the field
96+
Alias fieldAlias = alias.replaceChild(fieldAttr);
97+
98+
scriptEval = new EvalExec(evalExec.source(), scriptQueryExec, List.of(fieldAlias));
5099
aliasPushed = true;
51100
} else {
52101
nonPushedAliases.add(alias);
53102
}
54103
}
55104
if (aliasPushed) {
56105
if (nonPushedAliases.isEmpty()) {
57-
plan = scoreEval;
106+
plan = scriptEval;
58107
} else {
59-
plan = new EvalExec(evalExec.source(), scoreEval, nonPushedAliases);
108+
plan = new EvalExec(evalExec.source(), scriptEval, nonPushedAliases);
60109
}
61110
}
62111
}
63112

64113
return plan;
65114
}
115+
116+
/**
117+
* Custom Sort implementation that uses a script to compute the sort value.
118+
* The sort value is made available through a FieldAttribute.
119+
*/
120+
static class ScriptSort implements EsQueryExec.Sort {
121+
private final FieldAttribute field;
122+
private final Script script;
123+
private final ScriptSortType type;
124+
private final Order.OrderDirection direction;
125+
126+
ScriptSort(FieldAttribute field, Script script, ScriptSortType type, Order.OrderDirection direction) {
127+
this.field = field;
128+
this.script = script;
129+
this.type = type;
130+
this.direction = direction;
131+
}
132+
133+
@Override
134+
public SortBuilder<?> sortBuilder() {
135+
ScriptSortBuilder builder = new ScriptSortBuilder(script, type);
136+
builder.order(direction == Order.OrderDirection.ASC ? SortOrder.ASC : SortOrder.DESC);
137+
return builder;
138+
}
139+
140+
@Override
141+
public Order.OrderDirection direction() {
142+
return direction;
143+
}
144+
145+
@Override
146+
public FieldAttribute field() {
147+
return field;
148+
}
149+
150+
@Override
151+
public DataType resulType() {
152+
return field.dataType();
153+
}
154+
}
66155
}

0 commit comments

Comments
 (0)