Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -936,8 +936,66 @@ private static SqlTypeName promotedIntegerType(RexNode... args) {
return needsLong ? SqlTypeName.BIGINT : SqlTypeName.INTEGER;
}

/**
* Matches {@code [ARRAY<T>, scalar]} where the scalar is comparable with the array's element
* type. Drives the multi-valued-field comparison overloads: equality on an ARRAY-typed field
* is CONTAINS (any element equals the value), mirroring Lucene's term-match semantics on
* multi-valued fields. The scalar side must not itself be an ARRAY — array-to-array equality
* stays unsupported rather than silently meaning overlap.
*/
private static final PPLTypeChecker ARRAY_ELEMENT_COMPARABLE =
new PPLTypeChecker() {
@Override
public boolean checkOperandTypes(List<RelDataType> types) {
if (types.size() != 2) {
return false;
}
RelDataType arrayType = types.get(0);
RelDataType valueType = types.get(1);
if (arrayType.getSqlTypeName() != SqlTypeName.ARRAY
|| valueType.getSqlTypeName() == SqlTypeName.ARRAY) {
return false;
}
RelDataType elementType = arrayType.getComponentType();
return elementType != null
&& PPLTypeChecker.PPLComparableTypeChecker.isComparable(elementType, valueType);
}

@Override
public String getAllowedSignatures() {
return "[ARRAY,ELEMENT_TYPE]";
}

@Override
public List<List<RelDataType>> getParameterTypes() {
RelDataType anyType = TYPE_FACTORY.createSqlType(SqlTypeName.ANY);
return List.of(List.of(anyType, anyType));
}
};

void populate() {
// register operators for comparison
//
// Equality against a multi-valued (ARRAY-typed) field means CONTAINS, matching
// OpenSearch/Lucene semantics where a term query on a multi-valued field matches a
// document if ANY value equals the term (SortedSetDocValues / inverted-index behavior).
// Registered BEFORE the scalar overloads so an [ARRAY<T>, T] argument pair resolves here;
// scalar comparisons are untouched. array_contains uses element equality (not regex),
// exactly mirroring a Lucene term match.
register(
EQUAL,
(FunctionImp2)
(builder, array, value) ->
builder.makeCall(SqlLibraryOperators.ARRAY_CONTAINS, array, value),
ARRAY_ELEMENT_COMPARABLE);
register(
NOTEQUAL,
(FunctionImp2)
(builder, array, value) ->
builder.makeCall(
SqlStdOperatorTable.NOT,
builder.makeCall(SqlLibraryOperators.ARRAY_CONTAINS, array, value)),
ARRAY_ELEMENT_COMPARABLE);
registerOperator(NOTEQUAL, PPLBuiltinOperators.NOT_EQUALS_IP, SqlStdOperatorTable.NOT_EQUALS);
registerOperator(EQUAL, PPLBuiltinOperators.EQUALS_IP, SqlStdOperatorTable.EQUALS);
registerOperator(GREATER, PPLBuiltinOperators.GREATER_IP, SqlStdOperatorTable.GREATER_THAN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,14 @@ public boolean checkOperandTypes(List<RelDataType> types) {
/**
* Modified from {@link SqlTypeUtil#isComparable(RelDataType, RelDataType)} to
*
* <p>Package-private so the multi-valued-field comparison overloads in {@code
* PPLFuncImpTable} can reuse the same comparability rules for an array's element type.
*
* @param type1 first type
* @param type2 second type
* @return true if the two types are comparable, false otherwise
*/
private static boolean isComparable(RelDataType type1, RelDataType type2) {
static boolean isComparable(RelDataType type1, RelDataType type2) {
if (type1.isStruct() != type2.isStruct()) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.function;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.type.SqlTypeName;
import org.junit.jupiter.api.Test;
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;

class ArrayEqualsResolutionTest {

@Test
void equalsOnArrayColumnResolvesToArrayContains() {
RexBuilder builder = new RexBuilder(OpenSearchTypeFactory.TYPE_FACTORY);
RelDataType varchar =
OpenSearchTypeFactory.TYPE_FACTORY.createSqlType(SqlTypeName.VARCHAR);
RelDataType arrayOfVarchar =
OpenSearchTypeFactory.TYPE_FACTORY.createTypeWithNullability(
OpenSearchTypeFactory.TYPE_FACTORY.createArrayType(varchar, -1), true);

RexNode arrayRef = builder.makeInputRef(arrayOfVarchar, 0);
RexNode literal = builder.makeLiteral("alpha");
System.out.println("array type = " + arrayRef.getType().getSqlTypeName());
System.out.println("literal type = " + literal.getType().getSqlTypeName());

RexNode resolved =
PPLFuncImpTable.INSTANCE.resolve(builder, BuiltinFunctionName.EQUAL, arrayRef, literal);
System.out.println("resolved = " + resolved);
assertEquals("ARRAY_CONTAINS", ((org.apache.calcite.rex.RexCall) resolved).getOperator().getName());
}
}
Loading