Skip to content

Commit 3cfbe9a

Browse files
committed
[GRAMMAR REMOVAL] Internally handle highlight instead of expose as a command
Signed-off-by: Jialiang Liang <jiallian@amazon.com>
1 parent e38284d commit 3cfbe9a

32 files changed

Lines changed: 360 additions & 346 deletions

core/src/main/java/org/opensearch/sql/ast/dsl/AstDSL.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.opensearch.sql.ast.tree.Filter;
6262
import org.opensearch.sql.ast.tree.Head;
6363
import org.opensearch.sql.ast.tree.Highlight;
64+
import org.opensearch.sql.ast.tree.HighlightConfig;
6465
import org.opensearch.sql.ast.tree.Limit;
6566
import org.opensearch.sql.ast.tree.MinSpanBin;
6667
import org.opensearch.sql.ast.tree.MvCombine;
@@ -585,8 +586,12 @@ public static Trendline trendline(
585586
return new Trendline(sortField, Arrays.asList(computations)).attach(input);
586587
}
587588

588-
public static Highlight highlight(UnresolvedPlan input, List<String> highlightArgs) {
589-
return new Highlight(highlightArgs).attach(input);
589+
public static Highlight highlight(UnresolvedPlan input, HighlightConfig highlightConfig) {
590+
return new Highlight(highlightConfig).attach(input);
591+
}
592+
593+
public static Highlight highlight(UnresolvedPlan input, List<String> highlightFields) {
594+
return new Highlight(new HighlightConfig(highlightFields)).attach(input);
590595
}
591596

592597
public static AppendPipe appendPipe(UnresolvedPlan input, UnresolvedPlan subquery) {

core/src/main/java/org/opensearch/sql/ast/tree/Highlight.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
public class Highlight extends UnresolvedPlan {
2222

2323
private UnresolvedPlan child;
24-
private final List<String> highlightArgs;
24+
private final HighlightConfig highlightConfig;
2525

2626
@Override
2727
public Highlight attach(UnresolvedPlan child) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.ast.tree;
7+
8+
import java.util.List;
9+
10+
/**
11+
* Bundles highlight configuration: field names (or wildcards) and optional pre/post tags and
12+
* fragment size. Supports both the simple array format ({@code ["*"]}) and the rich OSD object
13+
* format with {@code pre_tags}, {@code post_tags}, {@code fields}, and {@code fragment_size}.
14+
*/
15+
public record HighlightConfig(
16+
List<String> fields, List<String> preTags, List<String> postTags, Integer fragmentSize) {
17+
18+
/** Convenience constructor for the simple array format (fields only, no tag/size overrides). */
19+
public HighlightConfig(List<String> fields) {
20+
this(fields, null, null, null);
21+
}
22+
}

core/src/main/java/org/opensearch/sql/calcite/CalcitePlanContext.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.calcite.rex.RexNode;
2323
import org.apache.calcite.tools.FrameworkConfig;
2424
import org.opensearch.sql.ast.expression.UnresolvedExpression;
25+
import org.opensearch.sql.ast.tree.HighlightConfig;
2526
import org.opensearch.sql.calcite.utils.CalciteToolsHelper;
2627
import org.opensearch.sql.calcite.utils.CalciteToolsHelper.OpenSearchRelBuilder;
2728
import org.opensearch.sql.common.setting.Settings;
@@ -45,6 +46,7 @@ public class CalcitePlanContext {
4546
private static final ThreadLocal<Boolean> legacyPreferredFlag =
4647
ThreadLocal.withInitial(() -> true);
4748

49+
@Getter @Setter private HighlightConfig highlightConfig;
4850
@Getter @Setter private boolean isResolvingJoinCondition = false;
4951
@Getter @Setter private boolean isResolvingSubquery = false;
5052
@Getter @Setter private boolean inCoalesceFunction = false;

core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,18 @@ public RelNode visitRelation(Relation node, CalcitePlanContext context) {
230230
RelNode scan = context.relBuilder.peek();
231231

232232
if (scan instanceof AliasFieldsWrappable) {
233-
return ((AliasFieldsWrappable) scan).wrapProjectForAliasFields(context.relBuilder);
233+
((AliasFieldsWrappable) scan).wrapProjectForAliasFields(context.relBuilder);
234234
}
235-
return scan;
235+
236+
// Wrap with LogicalHighlight if highlight config is set on context (from API request)
237+
if (context.getHighlightConfig() != null) {
238+
RelNode input = context.relBuilder.build();
239+
LogicalHighlight highlight = LogicalHighlight.create(input, context.getHighlightConfig());
240+
context.relBuilder.push(highlight);
241+
context.setHighlightConfig(null); // Clear for join safety
242+
}
243+
244+
return context.relBuilder.peek();
236245
}
237246

238247
// This is a tool method to add an existed RelOptTable to builder stack, not used for now
@@ -3192,11 +3201,9 @@ static ChartConfig fromArguments(ArgumentMap argMap) {
31923201

31933202
@Override
31943203
public RelNode visitHighlight(Highlight node, CalcitePlanContext context) {
3204+
context.setHighlightConfig(node.getHighlightConfig());
31953205
visitChildren(node, context);
3196-
RelNode input = context.relBuilder.build();
3197-
LogicalHighlight highlight = LogicalHighlight.create(input, node.getHighlightArgs());
3198-
context.relBuilder.push(highlight);
3199-
return highlight;
3206+
return context.relBuilder.peek();
32003207
}
32013208

32023209
@Override

core/src/main/java/org/opensearch/sql/calcite/plan/rel/LogicalHighlight.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,32 @@
1616
import org.apache.calcite.rel.type.RelDataType;
1717
import org.apache.calcite.rel.type.RelDataTypeFactory;
1818
import org.apache.calcite.sql.type.SqlTypeName;
19+
import org.opensearch.sql.ast.tree.HighlightConfig;
1920
import org.opensearch.sql.expression.HighlightExpression;
2021

2122
/**
2223
* Logical relational node representing a PPL {@code | highlight} command. Stores the highlight
23-
* arguments (terms to highlight or {@code *} for wildcard) and adds a {@code _highlight} column to
24-
* the output row type. An optimizer rule ({@code HighlightIndexScanRule}) pushes this node down
25-
* into the OpenSearch index scan.
24+
* configuration (fields, pre/post tags, fragment size) and adds a {@code _highlight} column to the
25+
* output row type. An optimizer rule ({@code HighlightIndexScanRule}) pushes this node down into
26+
* the OpenSearch index scan.
2627
*/
2728
public class LogicalHighlight extends SingleRel {
2829

29-
@Getter private final List<String> highlightArgs;
30+
@Getter private final HighlightConfig highlightConfig;
3031
private final RelDataType highlightRowType;
3132

3233
protected LogicalHighlight(
3334
RelOptCluster cluster,
3435
RelTraitSet traitSet,
3536
RelNode input,
36-
List<String> highlightArgs,
37+
HighlightConfig highlightConfig,
3738
RelDataType highlightRowType) {
3839
super(cluster, traitSet, input);
39-
this.highlightArgs = highlightArgs;
40+
this.highlightConfig = highlightConfig;
4041
this.highlightRowType = highlightRowType;
4142
}
4243

43-
public static LogicalHighlight create(RelNode input, List<String> highlightArgs) {
44+
public static LogicalHighlight create(RelNode input, HighlightConfig highlightConfig) {
4445
final RelOptCluster cluster = input.getCluster();
4546
RelTraitSet traitSet = cluster.traitSetOf(Convention.NONE);
4647

@@ -54,7 +55,7 @@ public static LogicalHighlight create(RelNode input, List<String> highlightArgs)
5455
HighlightExpression.HIGHLIGHT_FIELD, typeFactory.createSqlType(SqlTypeName.ANY));
5556
}
5657

57-
return new LogicalHighlight(cluster, traitSet, input, highlightArgs, schemaBuilder.build());
58+
return new LogicalHighlight(cluster, traitSet, input, highlightConfig, schemaBuilder.build());
5859
}
5960

6061
@Override
@@ -66,11 +67,11 @@ protected RelDataType deriveRowType() {
6667
public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
6768
assert traitSet.containsIfApplicable(Convention.NONE);
6869
return new LogicalHighlight(
69-
getCluster(), traitSet, sole(inputs), highlightArgs, highlightRowType);
70+
getCluster(), traitSet, sole(inputs), highlightConfig, highlightRowType);
7071
}
7172

7273
@Override
7374
public RelWriter explainTerms(RelWriter pw) {
74-
return super.explainTerms(pw).item("highlightArgs", highlightArgs);
75+
return super.explainTerms(pw).item("highlightConfig", highlightConfig.fields());
7576
}
7677
}

docs/category.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
"user/ppl/cmd/fillnull.md",
2323
"user/ppl/cmd/grok.md",
2424
"user/ppl/cmd/head.md",
25-
"user/ppl/cmd/highlight.md",
2625
"user/ppl/cmd/join.md",
2726
"user/ppl/cmd/lookup.md",
2827
"user/ppl/cmd/mvcombine.md",

docs/user/ppl/cmd/highlight.md

Lines changed: 0 additions & 144 deletions
This file was deleted.

docs/user/ppl/interfaces/endpoint.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,55 @@ Expected output (trimmed):
201201
- Plan node names use Calcite physical operator names (for example, `EnumerableCalc` or `CalciteEnumerableIndexScan`).
202202
- Plan `time_ms` is inclusive of child operators and represents wall-clock time; overlapping work can make summed plan times exceed `summary.total_time_ms`.
203203
- Scan nodes reflect operator wall-clock time; background prefetch can make scan time smaller than total request latency.
204+
205+
## Highlight
206+
207+
You can add a `highlight` parameter to the PPL request body to enable search-result highlighting. When enabled, the response includes a `_highlight` field containing matching fragments with the specified tags.
208+
209+
Two formats are supported:
210+
211+
### Simple array format
212+
213+
Pass a JSON array of field names or wildcards. Use `["*"]` to highlight all fields that match the search query.
214+
215+
```bash ppl
216+
curl -sS -H 'Content-Type: application/json' \
217+
-X POST localhost:9200/_plugins/_ppl \
218+
-d '{
219+
"query": "source=accounts \"Holmes\"",
220+
"highlight": ["*"]
221+
}'
222+
```
223+
224+
### Object format (OpenSearch Dashboards)
225+
226+
Pass a JSON object with `fields`, `pre_tags`, `post_tags`, and `fragment_size`. This is the format used by OpenSearch Dashboards.
227+
228+
```bash ppl
229+
curl -sS -H 'Content-Type: application/json' \
230+
-X POST localhost:9200/_plugins/_ppl \
231+
-d '{
232+
"query": "source=accounts \"Holmes\"",
233+
"highlight": {
234+
"pre_tags": ["@opensearch-dashboards-highlighted-field@"],
235+
"post_tags": ["@/opensearch-dashboards-highlighted-field@"],
236+
"fields": {"*": {}},
237+
"fragment_size": 2147483647
238+
}
239+
}'
240+
```
241+
242+
### Parameters
243+
244+
| Parameter | Type | Required | Description |
245+
|-----------------|-----------------|----------|--------------------------------------------------------------------------------------------------------------|
246+
| `fields` | Object | Yes | An object whose keys are field names or wildcards (e.g. `{"*": {}}`) to highlight. |
247+
| `pre_tags` | Array of string | No | Tags inserted before highlighted tokens. Defaults to `<em>`. |
248+
| `post_tags` | Array of string | No | Tags inserted after highlighted tokens. Defaults to `</em>`. |
249+
| `fragment_size` | Integer | No | Maximum character size of a highlight fragment. Defaults to `2147483647` (returns the full field value). |
250+
251+
### Notes
252+
253+
- Highlighting requires a search query in the PPL statement (e.g. `source=accounts "Holmes"`). Without a query, the `_highlight` field will be empty.
254+
- In the simple array format, `["*"]` highlights all fields. Specific terms like `["error", "login"]` highlight those terms across all fields.
255+
- In the object format, only the keys of the `fields` object are used; per-field options inside the value objects are currently ignored.

0 commit comments

Comments
 (0)