Skip to content
Merged
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
82 changes: 78 additions & 4 deletions core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkArgument;

Expand Down Expand Up @@ -1133,15 +1134,19 @@ public static SqlSingleOperandTypeChecker same(int operandCount,
* Operand type-checking strategy where two operands must both be in the
* same string type family.
*/
private static final ImmutableList<SqlTypeFamily> STRING_SUB_FAMILIES =
ImmutableList.of(SqlTypeFamily.CHARACTER, SqlTypeFamily.BINARY);
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME =
STRING_STRING.and(SAME_SAME);
withSignatureGenerator(STRING_STRING.and(SAME_SAME),
sameSubFamilySignatureGenerator(2, STRING_SUB_FAMILIES));

/**
* Operand type-checking strategy where three operands must all be in the
* same string type family.
*/
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_SAME =
STRING_STRING_STRING.and(SAME_SAME_SAME);
withSignatureGenerator(STRING_STRING_STRING.and(SAME_SAME_SAME),
sameSubFamilySignatureGenerator(3, STRING_SUB_FAMILIES));

public static final SqlSingleOperandTypeChecker STRING_STRING_INTEGER =
family(SqlTypeFamily.STRING, SqlTypeFamily.STRING, SqlTypeFamily.INTEGER);
Expand Down Expand Up @@ -1192,8 +1197,8 @@ public static SqlSingleOperandTypeChecker same(int operandCount,
* same string type family and last type is INTEGER.
*/
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_INTEGER =
STRING_STRING_INTEGER.and(SAME_SAME_INTEGER);

withSignatureGenerator(STRING_STRING_INTEGER.and(SAME_SAME_INTEGER),
sameSubFamilySignatureGenerator(2, STRING_SUB_FAMILIES, SqlTypeFamily.INTEGER));
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_OR_ARRAY_SAME_SAME =
or(STRING_SAME_SAME,
and(OperandTypes.SAME_SAME, family(SqlTypeFamily.ARRAY, SqlTypeFamily.ARRAY)));
Expand Down Expand Up @@ -1499,6 +1504,75 @@ private RecordTypeWithOneFieldChecker(Predicate<SqlTypeName> predicate) {
return !validationError;
}
}
/**
* Wraps a {@link SqlSingleOperandTypeChecker}, overriding only its
* {@link SqlOperandTypeChecker#getAllowedSignatures}, while delegating
* everything else unchanged. Used instead of
* {@link CompositeOperandTypeChecker#withGenerator}, which always returns
* a plain {@link CompositeOperandTypeChecker} and would lose single-operand
* checking.
*/
private static SqlSingleOperandTypeChecker withSignatureGenerator(
SqlSingleOperandTypeChecker checker,
BiFunction<SqlOperator, String, String> signatureGenerator) {
return new SqlSingleOperandTypeChecker() {
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding,
SqlNode operand, int iFormalOperand, boolean throwOnFailure) {
// STRING_SAME_SAME-style checkers evaluate family membership AND
// cross-operand comparability together; there's no meaningful way to
// validate a single operand in isolation, so fall back to the full
// multi-operand check.
return checker.checkOperandTypes(callBinding, throwOnFailure);
}

@Override public boolean checkOperandTypes(SqlCallBinding callBinding,
boolean throwOnFailure) {
// Explicitly override rather than relying on the interface default
// (which would route through checkSingleOperandType(operand(0), 0)
// and only ever check the first operand).
return checker.checkOperandTypes(callBinding, throwOnFailure);
}

@Override public SqlOperandCountRange getOperandCountRange() {
return checker.getOperandCountRange();
}

@Override public boolean isOptional(int i) {
return checker.isOptional(i);
}

@Override public Consistency getConsistency() {
return checker.getConsistency();
}

@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return signatureGenerator.apply(op, opName);
}
};
}

/**
* Builds an error-message generator that lists one candidate signature per
* given sub-family, each repeated across the "same" operand positions,
* followed by any trailing fixed families (see CALCITE-7749).
*/
private static BiFunction<SqlOperator, String, String> sameSubFamilySignatureGenerator(
int sameOperandCount, List<SqlTypeFamily> subFamilies,
SqlTypeFamily... trailingFamilies) {
return (op, opName) ->
subFamilies.stream()
.map(subFamily -> {
List<String> form = new ArrayList<>();
for (int i = 0; i < sameOperandCount; i++) {
form.add(subFamily.name());
}
for (SqlTypeFamily family : trailingFamilies) {
form.add(family.name());
}
return SqlUtil.getAliasedSignature(op, opName, form);
})
.collect(Collectors.joining(SqlOperator.NL));
}

/** Checker that returns whether a value is a collection (multiset or array)
* of scalar or record values. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -872,8 +872,11 @@ static SqlOperatorTable operatorTableFor(SqlLibrary library) {
@Test void testConcatFails() {
wholeExpr("'a'||x'ff'")
.fails("(?s).*Cannot apply '\\|\\|' to arguments of type "
+ "'<CHAR.1.> \\|\\| <BINARY.1.>'.*Supported form.s.: "
+ "'<STRING> \\|\\| <STRING>.*'");
+ "'<CHAR\\(1\\)> \\|\\| <BINARY\\(1\\)>'.*"
+ "Supported form\\(s\\): "
+ "'<CHARACTER> \\|\\| <CHARACTER>'\\s*"
+ "'<BINARY> \\|\\| <BINARY>'\\s*"
+ "'<EQUIVALENT_TYPE> \\|\\| <EQUIVALENT_TYPE>'.*");
Comment thread
Dwrite marked this conversation as resolved.
}

/** Tests the CONCAT function, which unlike the concat operator ('||') is not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2757,7 +2757,8 @@ private static void checkConcat2Func(SqlOperatorFixture f) {
f.checkFails("^concat('a', x'0a')^",
"Cannot apply 'CONCAT' to arguments of type "
+ "'CONCAT\\(<CHAR\\(1\\)>, <BINARY\\(1\\)>\\)'\\. Supported "
+ "form\\(s\\): 'CONCAT\\(<STRING>, <STRING>\\)'",
+ "form\\(s\\): 'CONCAT\\(<CHARACTER>, <CHARACTER>\\)'\n"
+ "'CONCAT\\(<BINARY>, <BINARY>\\)'",
false);
}

Expand Down Expand Up @@ -11873,7 +11874,8 @@ void checkStartsWith(SqlOperatorFixture f0, FunctionAlias functionAlias) {
f.checkFails("^" + fn + "('aabbcc', x'aa')^",
"Cannot apply '" + fn + "' to arguments of type "
+ "'" + fn + "\\(<CHAR\\(6\\)>, <BINARY\\(1\\)>\\)'\\. Supported "
+ "form\\(s\\): '" + fn + "\\(<STRING>, <STRING>\\)'",
+ "form\\(s\\): '" + fn + "\\(<CHARACTER>, <CHARACTER>\\)'\\n"
+ "'" + fn + "\\(<BINARY>, <BINARY>\\)'",
false);
f.checkNull(fn + "(null, null)");
f.checkNull(fn + "('12345', null)");
Expand Down Expand Up @@ -11913,7 +11915,8 @@ void checkEndsWith(SqlOperatorFixture f0, FunctionAlias functionAlias) {
f.checkFails("^" + fn + "('aabbcc', x'aa')^",
"Cannot apply '" + fn + "' to arguments of type "
+ "'" + fn + "\\(<CHAR\\(6\\)>, <BINARY\\(1\\)>\\)'\\. Supported "
+ "form\\(s\\): '" + fn + "\\(<STRING>, <STRING>\\)'",
+ "form\\(s\\): '" + fn + "\\(<CHARACTER>, <CHARACTER>\\)'\\s*"
+ "'" + fn + "\\(<BINARY>, <BINARY>\\)'",
false);
f.checkNull(fn + "(null, null)");
f.checkNull(fn + "('12345', null)");
Expand Down
Loading