generated from amazon-archives/__template_Custom
-
Notifications
You must be signed in to change notification settings - Fork 180
Support multisearch command in calcite
#4332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
RyanL1997
merged 38 commits into
opensearch-project:main
from
ahkcs:feat/multisearch_cmd
Oct 2, 2025
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
2f0c405
Initial implementation for multisearch command
ahkcs 4f9c5a6
timestamp interleaving
ahkcs b09dd09
removal
ahkcs 2965078
update test
ahkcs 00c4a6e
fix tests
ahkcs 001a5a2
remove streaming command
ahkcs 4dfca30
update doctest
ahkcs 7a91631
fix doctest
ahkcs d006e11
add CalciteExplainIT
ahkcs bd7f591
formatting
ahkcs 5579ce2
Anonymizer test
ahkcs b202948
fixes
ahkcs 54259a0
removal
ahkcs 7ccf26a
update explainIT
ahkcs 8639c92
fix test
ahkcs 3fc1998
fix anonymizerTest
ahkcs b5d7c44
fixes
ahkcs 4cb8daf
update grammar
ahkcs cdbd9c4
update explainIT
ahkcs 343ff88
update error handling
ahkcs f1d1b23
update explainIT to use yaml
ahkcs 4bb0c3a
removal
ahkcs 8232eac
add schema null filling test cases
ahkcs 2efc13b
make @timestamp priority timestamp
ahkcs 243981b
fix
ahkcs 71d9736
fixes
ahkcs 4c25348
fix test
ahkcs 7038bf5
update doc
ahkcs b6312e1
fix IT
ahkcs 1540f65
doc
ahkcs 25b6016
remove duplication handling logic
ahkcs 9bd54df
better formatting
ahkcs 915495c
fixes
ahkcs 4bb996f
CI
ahkcs bd249bf
fix explainIT
ahkcs f5546e1
Extract SchemaUnifier
ahkcs dfd8def
trim doctest dataset
ahkcs d719b80
remove template change
ahkcs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
core/src/main/java/org/opensearch/sql/ast/tree/Multisearch.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /* | ||
| * Copyright OpenSearch Contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package org.opensearch.sql.ast.tree; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
| import java.util.List; | ||
| import lombok.EqualsAndHashCode; | ||
| import lombok.Getter; | ||
| import lombok.ToString; | ||
| import org.opensearch.sql.ast.AbstractNodeVisitor; | ||
|
|
||
| /** Logical plan node for Multisearch operation. Combines results from multiple search queries. */ | ||
| @Getter | ||
| @ToString | ||
| @EqualsAndHashCode(callSuper = false) | ||
| public class Multisearch extends UnresolvedPlan { | ||
|
|
||
| private UnresolvedPlan child; | ||
| private final List<UnresolvedPlan> subsearches; | ||
|
|
||
| public Multisearch(List<UnresolvedPlan> subsearches) { | ||
| this.subsearches = subsearches; | ||
| } | ||
|
|
||
| @Override | ||
| public Multisearch attach(UnresolvedPlan child) { | ||
| this.child = child; | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public List<UnresolvedPlan> getChild() { | ||
| if (this.child == null) { | ||
| return ImmutableList.copyOf(subsearches); | ||
| } else { | ||
| return ImmutableList.<UnresolvedPlan>builder().add(this.child).addAll(subsearches).build(); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public <T, C> T accept(AbstractNodeVisitor<T, C> nodeVisitor, C context) { | ||
| return nodeVisitor.visitMultisearch(this, context); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
core/src/main/java/org/opensearch/sql/calcite/SchemaUnifier.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| /* | ||
| * Copyright OpenSearch Contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package org.opensearch.sql.calcite; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import org.apache.calcite.rel.RelNode; | ||
| import org.apache.calcite.rel.type.RelDataType; | ||
| import org.apache.calcite.rel.type.RelDataTypeField; | ||
| import org.apache.calcite.rex.RexNode; | ||
| import org.apache.calcite.sql.validate.SqlValidatorUtil; | ||
|
|
||
| /** | ||
| * Utility class for unifying schemas across multiple RelNodes with type conflict resolution. Uses | ||
| * the same strategy as append command - renames conflicting fields to avoid type conflicts. | ||
| */ | ||
| public class SchemaUnifier { | ||
|
|
||
| /** | ||
| * Builds a unified schema for multiple nodes with type conflict resolution. | ||
| * | ||
| * @param nodes List of RelNodes to unify schemas for | ||
| * @param context Calcite plan context | ||
| * @return List of projected RelNodes with unified schema | ||
| */ | ||
| public static List<RelNode> buildUnifiedSchemaWithConflictResolution( | ||
| List<RelNode> nodes, CalcitePlanContext context) { | ||
| if (nodes.isEmpty()) { | ||
| return new ArrayList<>(); | ||
| } | ||
|
|
||
| if (nodes.size() == 1) { | ||
| return nodes; | ||
| } | ||
|
|
||
| // Step 1: Build the unified schema by processing all nodes | ||
| List<SchemaField> unifiedSchema = buildUnifiedSchema(nodes); | ||
|
|
||
| // Step 2: Create projections for each node to align with unified schema | ||
| List<RelNode> projectedNodes = new ArrayList<>(); | ||
| List<String> fieldNames = | ||
| unifiedSchema.stream().map(SchemaField::getName).collect(Collectors.toList()); | ||
|
|
||
| for (RelNode node : nodes) { | ||
| List<RexNode> projection = buildProjectionForNode(node, unifiedSchema, context); | ||
| RelNode projectedNode = context.relBuilder.push(node).project(projection, fieldNames).build(); | ||
| projectedNodes.add(projectedNode); | ||
| } | ||
|
|
||
| // Step 3: Unify names to handle type conflicts (this creates age0, age1, etc.) | ||
| List<String> uniqueNames = | ||
| SqlValidatorUtil.uniquify(fieldNames, SqlValidatorUtil.EXPR_SUGGESTER, true); | ||
|
|
||
| // Step 4: Re-project with unique names if needed | ||
| if (!uniqueNames.equals(fieldNames)) { | ||
| List<RelNode> renamedNodes = new ArrayList<>(); | ||
| for (RelNode node : projectedNodes) { | ||
| RelNode renamedNode = | ||
| context.relBuilder.push(node).project(context.relBuilder.fields(), uniqueNames).build(); | ||
| renamedNodes.add(renamedNode); | ||
| } | ||
| return renamedNodes; | ||
| } | ||
|
|
||
| return projectedNodes; | ||
| } | ||
|
|
||
| /** | ||
| * Builds a unified schema by merging fields from all nodes. Fields with the same name but | ||
| * different types are added as separate entries (which will be renamed during uniquification). | ||
| * | ||
| * @param nodes List of RelNodes to merge schemas from | ||
| * @return List of SchemaField representing the unified schema (may contain duplicate names) | ||
| */ | ||
| private static List<SchemaField> buildUnifiedSchema(List<RelNode> nodes) { | ||
| List<SchemaField> schema = new ArrayList<>(); | ||
| Map<String, Set<RelDataType>> seenFields = new HashMap<>(); | ||
|
|
||
| for (RelNode node : nodes) { | ||
| for (RelDataTypeField field : node.getRowType().getFieldList()) { | ||
| String fieldName = field.getName(); | ||
| RelDataType fieldType = field.getType(); | ||
|
|
||
| // Track which (name, type) combinations we've seen | ||
| Set<RelDataType> typesForName = seenFields.computeIfAbsent(fieldName, k -> new HashSet<>()); | ||
|
|
||
| if (!typesForName.contains(fieldType)) { | ||
| // New field or same name with different type - add to schema | ||
| schema.add(new SchemaField(fieldName, fieldType)); | ||
| typesForName.add(fieldType); | ||
| } | ||
| // If we've seen this exact (name, type) combination, skip it | ||
| } | ||
| } | ||
|
|
||
| return schema; | ||
| } | ||
|
|
||
| /** | ||
| * Builds a projection for a node to align with the unified schema. For each field in the unified | ||
| * schema: - If the node has a matching field with the same type, use it - Otherwise, project NULL | ||
| * | ||
| * @param node The node to build projection for | ||
| * @param unifiedSchema List of SchemaField representing the unified schema | ||
| * @param context Calcite plan context | ||
| * @return List of RexNode representing the projection | ||
| */ | ||
| private static List<RexNode> buildProjectionForNode( | ||
| RelNode node, List<SchemaField> unifiedSchema, CalcitePlanContext context) { | ||
| Map<String, RelDataTypeField> nodeFieldMap = | ||
| node.getRowType().getFieldList().stream() | ||
| .collect(Collectors.toMap(RelDataTypeField::getName, field -> field)); | ||
|
|
||
| List<RexNode> projection = new ArrayList<>(); | ||
| for (SchemaField schemaField : unifiedSchema) { | ||
| String fieldName = schemaField.getName(); | ||
| RelDataType expectedType = schemaField.getType(); | ||
| RelDataTypeField nodeField = nodeFieldMap.get(fieldName); | ||
|
|
||
| if (nodeField != null && nodeField.getType().equals(expectedType)) { | ||
| // Field exists with matching type - use it | ||
| projection.add(context.rexBuilder.makeInputRef(node, nodeField.getIndex())); | ||
| } else { | ||
| // Field missing or type mismatch - project NULL | ||
| projection.add(context.rexBuilder.makeNullLiteral(expectedType)); | ||
| } | ||
| } | ||
|
|
||
| return projection; | ||
| } | ||
|
|
||
| /** Represents a field in the unified schema with name and type. */ | ||
| private static class SchemaField { | ||
| private final String name; | ||
| private final RelDataType type; | ||
|
|
||
| SchemaField(String name, RelDataType type) { | ||
| this.name = name; | ||
| this.type = type; | ||
| } | ||
|
|
||
| String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| RelDataType getType() { | ||
| return type; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.