Skip to content

Commit 1bf526d

Browse files
committed
Check composition type for only calcite's built-in operators
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent 77d49a1 commit 1bf526d

2 files changed

Lines changed: 93 additions & 66 deletions

File tree

core/src/main/java/org/opensearch/sql/expression/function/PPLFuncImpTable.java

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -186,23 +186,18 @@ void registerOperator(BuiltinFunctionName functionName, SqlOperator operator) {
186186
// Only the composite operand type checker for UDFs are concerned here.
187187
if (operator instanceof SqlUserDefinedFunction
188188
&& typeChecker instanceof CompositeOperandTypeChecker compositeTypeChecker) {
189-
register(functionName, createCompositeFunctionImp(operator, compositeTypeChecker));
189+
// UDFs implement their own composite type checkers, which always use OR logic for argument
190+
// types. Verifying the composition type would require accessing a protected field in
191+
// CompositeOperandTypeChecker. If access to this field is not allowed, type checking will
192+
// be skipped, so we avoid checking the composition type here.
193+
register(functionName, createCompositeFunctionImp(operator, compositeTypeChecker, false));
190194
} else if (typeChecker instanceof ImplicitCastOperandTypeChecker implicitCastTypeChecker) {
191195
register(functionName, createImplicitCastFunctionImp(operator, implicitCastTypeChecker));
192196
} else if (typeChecker instanceof CompositeOperandTypeChecker compositeTypeChecker) {
193-
try {
194-
// If compositeTypeChecker contains operand checkers other than family type checkers or
195-
// other than OR compositions, this will throw an IllegalArgumentException.
196-
register(functionName, createCompositeFunctionImp(operator, compositeTypeChecker));
197-
} catch (IllegalArgumentException e) {
198-
// register without type checker
199-
logger.debug(
200-
"Cannot create composite type checker for function: {}. Will skip its type checking",
201-
functionName);
202-
register(
203-
functionName,
204-
(RexBuilder builder, RexNode... node) -> builder.makeCall(operator, node));
205-
}
197+
// If compositeTypeChecker contains operand checkers other than family type checkers or
198+
// other than OR compositions, the function with be registered with a null type checker,
199+
// which means the function will not be type checked.
200+
register(functionName, createCompositeFunctionImp(operator, compositeTypeChecker, true));
206201
} else {
207202
logger.debug(
208203
"Cannot create type checker for function: {}. Will skip its type checking",
@@ -221,7 +216,9 @@ private static SqlOperandTypeChecker extractTypeCheckerFromUDF(
221216
}
222217

223218
private static FunctionImp createCompositeFunctionImp(
224-
SqlOperator operator, CompositeOperandTypeChecker typeChecker) {
219+
SqlOperator operator,
220+
CompositeOperandTypeChecker typeChecker,
221+
boolean checkCompositionType) {
225222
return new FunctionImp() {
226223
@Override
227224
public RexNode resolve(RexBuilder builder, RexNode... args) {
@@ -230,7 +227,17 @@ public RexNode resolve(RexBuilder builder, RexNode... args) {
230227

231228
@Override
232229
public PPLTypeChecker getTypeChecker() {
233-
return wrapComposite(typeChecker);
230+
try {
231+
return wrapComposite(typeChecker, checkCompositionType);
232+
} catch (IllegalArgumentException | UnsupportedOperationException e) {
233+
logger.debug(
234+
String.format(
235+
"Failed to create composite type checker for operator: %s. Will skip its type"
236+
+ " checking",
237+
operator.getName()),
238+
e);
239+
return null;
240+
}
234241
}
235242
};
236243
}
@@ -250,6 +257,36 @@ public PPLTypeChecker getTypeChecker() {
250257
};
251258
}
252259

260+
private static FunctionImp createFunctionImpWithTypeChecker(
261+
BiFunction<RexBuilder, RexNode, RexNode> resolver, PPLTypeChecker typeChecker) {
262+
return new FunctionImp1() {
263+
@Override
264+
public RexNode resolve(RexBuilder builder, RexNode arg1) {
265+
return resolver.apply(builder, arg1);
266+
}
267+
268+
@Override
269+
public PPLTypeChecker getTypeChecker() {
270+
return typeChecker;
271+
}
272+
};
273+
}
274+
275+
private static FunctionImp createFunctionImpWithTypeChecker(
276+
TriFunction<RexBuilder, RexNode, RexNode, RexNode> resolver, PPLTypeChecker typeChecker) {
277+
return new FunctionImp2() {
278+
@Override
279+
public RexNode resolve(RexBuilder builder, RexNode arg1, RexNode arg2) {
280+
return resolver.apply(builder, arg1, arg2);
281+
}
282+
283+
@Override
284+
public PPLTypeChecker getTypeChecker() {
285+
return typeChecker;
286+
}
287+
};
288+
}
289+
253290
void populate() {
254291
// Register std operator
255292
registerOperator(AND, SqlStdOperatorTable.AND);
@@ -537,34 +574,4 @@ public PPLTypeChecker getTypeChecker() {
537574
return family(booleanFamily, booleanFamily);
538575
}
539576
}
540-
541-
private static FunctionImp createFunctionImpWithTypeChecker(
542-
BiFunction<RexBuilder, RexNode, RexNode> resolver, PPLTypeChecker typeChecker) {
543-
return new FunctionImp1() {
544-
@Override
545-
public RexNode resolve(RexBuilder builder, RexNode arg1) {
546-
return resolver.apply(builder, arg1);
547-
}
548-
549-
@Override
550-
public PPLTypeChecker getTypeChecker() {
551-
return typeChecker;
552-
}
553-
};
554-
}
555-
556-
private static FunctionImp createFunctionImpWithTypeChecker(
557-
TriFunction<RexBuilder, RexNode, RexNode, RexNode> resolver, PPLTypeChecker typeChecker) {
558-
return new FunctionImp2() {
559-
@Override
560-
public RexNode resolve(RexBuilder builder, RexNode arg1, RexNode arg2) {
561-
return resolver.apply(builder, arg1, arg2);
562-
}
563-
564-
@Override
565-
public PPLTypeChecker getTypeChecker() {
566-
return typeChecker;
567-
}
568-
};
569-
}
570577
}

core/src/main/java/org/opensearch/sql/expression/function/PPLTypeChecker.java

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.opensearch.sql.expression.function;
77

88
import java.lang.reflect.Field;
9+
import java.lang.reflect.InaccessibleObjectException;
910
import java.util.ArrayList;
1011
import java.util.List;
1112
import java.util.stream.Collectors;
@@ -17,7 +18,6 @@
1718
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
1819
import org.apache.calcite.sql.type.SqlTypeFamily;
1920
import org.apache.calcite.sql.type.SqlTypeName;
20-
import org.apache.logging.log4j.LogManager;
2121
import org.opensearch.sql.calcite.utils.UserDefinedFunctionUtils;
2222

2323
/**
@@ -225,30 +225,29 @@ static PPLFamilyTypeCheckerWrapper wrapFamily(ImplicitCastOperandTypeChecker typ
225225
* are instances of {@link ImplicitCastOperandTypeChecker}. If any rule does not meet this
226226
* requirement, an {@link IllegalArgumentException} is thrown.
227227
*
228+
* <p>Additionally, if {@code checkCompositionType} is true, the method checks if the composition
229+
* type of the provided {@code CompositeOperandTypeChecker} is OR via reflection. If it is not, an
230+
* {@link IllegalArgumentException} is thrown. If the reflective access to the composition field
231+
* of CompositeOperandTypeChecker fails, an {@link UnsupportedOperationException} is thrown.
232+
*
228233
* @param typeChecker the Calcite {@link CompositeOperandTypeChecker} to wrap
234+
* @param checkCompositionType if true, checks if the composition type is OR.
229235
* @return a {@link PPLCompositeTypeChecker} that delegates type checking to the wrapped rules
230236
* @throws IllegalArgumentException if any rule is not an {@link ImplicitCastOperandTypeChecker}
231237
*/
232-
static PPLCompositeTypeChecker wrapComposite(CompositeOperandTypeChecker typeChecker) {
233-
try {
234-
Field compositionField = CompositeOperandTypeChecker.class.getDeclaredField("composition");
235-
compositionField.setAccessible(true);
236-
CompositeOperandTypeChecker.Composition composition =
237-
(CompositeOperandTypeChecker.Composition) compositionField.get(typeChecker);
238-
if (composition != CompositeOperandTypeChecker.Composition.OR) {
239-
throw new IllegalArgumentException(
240-
String.format(
241-
"Currently only OR compositions of ImplicitCastOperandTypeChecker are supported,"
242-
+ " but got %s composition",
243-
composition.name()));
238+
static PPLCompositeTypeChecker wrapComposite(
239+
CompositeOperandTypeChecker typeChecker, boolean checkCompositionType)
240+
throws IllegalArgumentException, UnsupportedOperationException {
241+
if (checkCompositionType) {
242+
try {
243+
if (!isCompositionOr(typeChecker)) {
244+
throw new IllegalArgumentException(
245+
"Currently only support CompositeOperandTypeChecker with a OR composition");
246+
}
247+
} catch (ReflectiveOperationException | InaccessibleObjectException | SecurityException e) {
248+
throw new UnsupportedOperationException(
249+
String.format("Failed to check composition type of %s", typeChecker), e);
244250
}
245-
} catch (IllegalAccessException e) {
246-
LogManager.getLogger(PPLTypeChecker.class).error(e);
247-
} catch (NoSuchFieldException e) {
248-
LogManager.getLogger(PPLTypeChecker.class)
249-
.error(
250-
"Failed to access the composition field of CompositeOperandTypeChecker. "
251-
+ "This may indicate a change in the Calcite library.");
252251
}
253252

254253
for (SqlOperandTypeChecker rule : typeChecker.getRules()) {
@@ -308,4 +307,25 @@ private static String getFamilySignature(List<SqlTypeFamily> families) {
308307
+ families.stream().map(SqlTypeFamily::toString).collect(Collectors.joining(","))
309308
+ "]";
310309
}
310+
311+
/**
312+
* Checks if the provided {@link CompositeOperandTypeChecker} is of type OR composition.
313+
*
314+
* <p>This method uses reflection to access the protected "composition" field of the
315+
* CompositeOperandTypeChecker class.
316+
*
317+
* @param typeChecker the CompositeOperandTypeChecker to check
318+
* @return true if the composition is OR, false otherwise
319+
*/
320+
private static boolean isCompositionOr(CompositeOperandTypeChecker typeChecker)
321+
throws NoSuchFieldException,
322+
IllegalAccessException,
323+
InaccessibleObjectException,
324+
SecurityException {
325+
Field compositionField = CompositeOperandTypeChecker.class.getDeclaredField("composition");
326+
compositionField.setAccessible(true);
327+
CompositeOperandTypeChecker.Composition composition =
328+
(CompositeOperandTypeChecker.Composition) compositionField.get(typeChecker);
329+
return composition == CompositeOperandTypeChecker.Composition.OR;
330+
}
311331
}

0 commit comments

Comments
 (0)