diff --git a/core/src/main/java/org/opensearch/sql/planner/physical/ProjectOperator.java b/core/src/main/java/org/opensearch/sql/planner/physical/ProjectOperator.java index 496e4e6ddb..adc05108eb 100644 --- a/core/src/main/java/org/opensearch/sql/planner/physical/ProjectOperator.java +++ b/core/src/main/java/org/opensearch/sql/planner/physical/ProjectOperator.java @@ -9,8 +9,10 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -52,10 +54,23 @@ public boolean hasNext() { return input.hasNext(); } + private String handleDuplicateColumns(String newName, Set takenNames) { + if (takenNames.contains(newName)) { + int appendedNum = 1; + while (takenNames.contains(String.format("%s%d", newName, appendedNum))) { + appendedNum++; + } + newName = String.format("%s%d", newName, appendedNum); + } + takenNames.add(newName); + return newName; + } + @Override public ExprValue next() { ExprValue inputValue = input.next(); ImmutableMap.Builder mapBuilder = new Builder<>(); + Set columns = new HashSet<>(); // ParseExpression will always override NamedExpression when identifier conflicts // TODO needs a better implementation, see https://github.com/opensearch-project/sql/issues/458 @@ -64,25 +79,27 @@ public ExprValue next() { Optional optionalParseExpression = namedParseExpressions.stream() .filter(parseExpr -> parseExpr.getNameOrAlias().equals(expr.getNameOrAlias())) .findFirst(); + if (optionalParseExpression.isEmpty()) { - mapBuilder.put(expr.getNameOrAlias(), exprValue); + mapBuilder.put(handleDuplicateColumns(expr.getNameOrAlias(), columns), exprValue); continue; } NamedExpression parseExpression = optionalParseExpression.get(); ExprValue sourceFieldValue = inputValue.bindingTuples() .resolve(((ParseExpression) parseExpression.getDelegated()).getSourceField()); + String columnName = handleDuplicateColumns(parseExpression.getNameOrAlias(), columns); if (sourceFieldValue.isMissing()) { // source field will be missing after stats command, read from inputValue if it exists // otherwise do nothing since it should not appear as a field ExprValue tupleValue = - ExprValueUtils.getTupleValue(inputValue).get(parseExpression.getNameOrAlias()); + ExprValueUtils.getTupleValue(inputValue).get(columnName); if (tupleValue != null) { - mapBuilder.put(parseExpression.getNameOrAlias(), tupleValue); + mapBuilder.put(columnName, tupleValue); } } else { ExprValue parsedValue = parseExpression.valueOf(inputValue.bindingTuples()); - mapBuilder.put(parseExpression.getNameOrAlias(), parsedValue); + mapBuilder.put(columnName, parsedValue); } } return ExprTupleValue.fromExprValueMap(mapBuilder.build());