/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate licenses this file * to you under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.metadata; import com.google.common.base.Preconditions; import io.crate.types.*; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.function.Predicate; import java.util.function.UnaryOperator; /** * Static constructors for generating signature operators and argument type matchers to be used * with {@link FunctionResolver#getSignature}. */ public class Signature { public static final SignatureOperator EMPTY = dataTypes -> dataTypes.isEmpty() ? dataTypes : null; public static final SignatureOperator SIGNATURES_SINGLE_NUMERIC = of(ArgMatcher.NUMERIC); public static final SignatureOperator SIGNATURES_SINGLE_ANY = numArgs(1); public static final SignatureOperator SIGNATURES_ALL_OF_SAME = withStrictVarArgs(ArgMatcher.ANY); public static final Predicate<DataType> IS_UNDEFINED = DataTypes.UNDEFINED::equals; /** * Creates an operator which takes its argument matchers from an iterator. The size of the * validated arguments is not checked, however it needs to be greater or equal to the * number of matchers returned by the iterator; * * @param matchers the argument matcher iterator * @return a new signature operator */ public static SignatureOperator ofIterable(Iterable<ArgMatcher> matchers) { return new IterableArgs(matchers); } /** * Creates an operator which matches if the given positional argument matchers match the signature. * * @param matchers positional argument matchers * @return a new signature operator */ public static SignatureOperator of(ArgMatcher... matchers) { if (matchers.length == 0) { return EMPTY; } return new StandardArgs(matchers); } /** * Returns an operator which matches and returns its input if the number of arguments in a signature are in a * specific range. * * @param minSize the minimum number of arguments the signature is required to have. * @param maxSize the maximum number of arguments the signature is required to have. * @return an new signature operator */ public static SignatureOperator numArgs(int minSize, int maxSize) { Preconditions.checkArgument(minSize <= maxSize, "minSize needs to be smaller than maxSize"); return dataTypes -> (dataTypes.size() >= minSize && dataTypes.size() <= maxSize) ? dataTypes : null; } /** * Returns an operator which matches and returns its input if the number of arguments matches. * * @param numArgs the number of arguments the signature is required to have. * @return an new signature operator */ public static SignatureOperator numArgs(int numArgs) { Preconditions.checkArgument(numArgs >= 0, "numArgs must not be negative"); if (numArgs == 0) { return EMPTY; } return in -> in.size() == numArgs ? in : null; } /** * Returns an operator which allows for a variable number of arguments. It matches if the given positional argument * matchers match the signature. Additionally the final argument matcher is allowed to be repeated. * * @param matchers positional argument matchers * @return a new signature operator */ public static SignatureOperator withLenientVarArgs(ArgMatcher... matchers) { Preconditions.checkArgument(matchers.length > 0, "at least one matcher is required"); return new StandardArgs(true, false, matchers); } /** * Returns an operator which allows for a variable number of arguments. It is the more strict version of * {@link #withLenientVarArgs(ArgMatcher...)}. It checks that the actual variable arguments are of the same type * and also normalizes undefined variable argument types if at least one of them is defined. * * @param matchers positional argument matchers * @return a new signature operator */ public static SignatureOperator withStrictVarArgs(ArgMatcher... matchers) { Preconditions.checkArgument(matchers.length > 0, "at least one matcher is required"); return new StandardArgs(true, true, matchers); } /** * Returns an operator which matches exactly the given types. If a type is undefined it will be rewritten to * the according type at that position. * * @param dataTypes the list of types to match * @return a new signature operator */ public static SignatureOperator of(List<DataType> dataTypes) { if (dataTypes.isEmpty()) { return EMPTY; } ArgMatcher[] matchers = new ArgMatcher[dataTypes.size()]; for (int i = 0; i < matchers.length; i++) { matchers[i] = ArgMatcher.rewriteTo(dataTypes.get(i)); } return new StandardArgs(matchers); } /** * Convinience method for calling {@link #of(List<DataType>)} with varArgs. */ public static SignatureOperator of(DataType... dataTypes) { return of(Arrays.asList(dataTypes)); } public interface SignatureOperator extends UnaryOperator<List<DataType>> { /** * Adds a precondition before the operator which should evaluate to true in order for the operator * to be evaluated. * * @param predicate a predicate which represents the precondition * @return a new operator with the predicate prepended */ default SignatureOperator preCondition(Predicate<List<DataType>> predicate) { return dataTypes -> predicate.test(dataTypes) ? this.apply(dataTypes) : null; } /** * Adds a secondary operator to this operator, which is executed with the result of this operator if a match * occurs. * * @param secondary the operator which will be evaluated with this operator's success result * @return a new operator with the secondary appended */ default SignatureOperator and(SignatureOperator secondary) { return dataTypes -> { List<DataType> n = this.apply(dataTypes); if (n != null) { return secondary.apply(n); } return null; }; } } /** * Interface for validating and normalizing an argument type of a function signature. * <p> * This interface is a unary operator for {@link DataType} which returns a normalized type if the given input type * is valid. Implementations can either pass through the input or replace the type if normalization is required. * For example if the input type is null implementations might return a specific type if possible. * <p> * The operator returns null if the input does not match its criteria. */ public interface ArgMatcher extends UnaryOperator<DataType> { ArgMatcher ANY = dt -> dt; ArgMatcher NUMERIC = of(DataTypes.NUMERIC_PRIMITIVE_TYPES::contains); ArgMatcher ANY_ARRAY = of(dt -> dt instanceof ArrayType); ArgMatcher ANY_SET = of(dt -> dt instanceof SetType); ArgMatcher ANY_COLLECTION = of(dt -> dt instanceof CollectionType); ArgMatcher STRING = rewriteTo(DataTypes.STRING); ArgMatcher BOOLEAN = rewriteTo(DataTypes.BOOLEAN); ArgMatcher INTEGER = rewriteTo(DataTypes.INTEGER); ArgMatcher OBJECT = rewriteTo(DataTypes.OBJECT); /** * Creates a matcher witch matches any of the given types or the undefined type. * * @param allowedTypes the allowed types * @return a new argument matcher */ static ArgMatcher of(DataType... allowedTypes) { return dt -> { for (DataType allowedType : allowedTypes) { if (allowedType.equals(dt)) { return dt; } } return IS_UNDEFINED.test(dt) ? dt : null; }; } /** * Creates a matcher which matches if any of the given input predicates matches or the input is of type undefined. * * @param predicates the predicates to match * @return a new argument matcher */ @SafeVarargs static ArgMatcher of(final Predicate<DataType>... predicates) { return dt -> { for (Predicate<DataType> predicate : predicates) { if (predicate.test(dt)) { return dt; } } return IS_UNDEFINED.test(dt) ? dt : null; }; } /** * Creates a matcher which matches one given type and rewrites the undefined type to the required type. * * @param targetType the type to match agains * @return a new argument matcher */ static ArgMatcher rewriteTo(final DataType targetType) { return dt -> { if (targetType.equals(dt) || IS_UNDEFINED.test(dt)) { return targetType; } return null; }; } } /** * Runs matchers consumed from an iterator */ private static class IterableArgs implements SignatureOperator { private final Iterable<ArgMatcher> expected; private IterableArgs(Iterable<ArgMatcher> expected) { this.expected = expected; } @Override public List<DataType> apply(List<DataType> dataTypes) { Iterator<ArgMatcher> iter = expected.iterator(); for (DataType dataType : dataTypes) { if (!iter.hasNext()) return null; DataType repl = iter.next().apply(dataType); if (repl == null) { return null; } else if (repl != dataType) { return mutatedCopy(dataTypes); } } return dataTypes; } private List<DataType> mutatedCopy(List<DataType> in) { ArrayList<DataType> out = new ArrayList<>(in.size()); Iterator<ArgMatcher> iter = expected.iterator(); for (DataType dataType : in) { if (!iter.hasNext()) return null; DataType repl = iter.next().apply(dataType); if (repl == null) { return null; } out.add(repl); } return out; } } /** * A signature operator which supports standard signatures as in Java, including varargs. */ private static class StandardArgs implements Signature.SignatureOperator { private final ArgMatcher[] matchers; private final boolean varArgs; private final boolean checkVarArgTypes; private StandardArgs(ArgMatcher[] matchers) { this(false, false, matchers); } private StandardArgs(boolean hasVarArgs, boolean strictVarArgTypes, ArgMatcher[] matchers) { assert matchers.length > 0 : "VarArgs requires at least one matcher"; assert !strictVarArgTypes || hasVarArgs : "checkVarargTypes is not applicable if not hasVarArgs"; this.matchers = matchers; this.varArgs = hasVarArgs; this.checkVarArgTypes = strictVarArgTypes; } /** * Checks if all non undefined types in the given list are the same, beginning @start index and returns the * common type. If all types are undefined, undefined is returned. */ private static DataType getCommonType(List<DataType> dataTypes, int start) { DataType commonType = null; for (int i = start; i < dataTypes.size(); i++) { DataType dataType = dataTypes.get(i); if (IS_UNDEFINED.test(dataType)) { continue; } if (commonType == null) { commonType = dataType; } else if (!commonType.equals(dataType)) { return null; } } return commonType == null ? DataTypes.UNDEFINED : commonType; } @Override public List<DataType> apply(List<DataType> dataTypes) { ArgMatcher varArgMatcher = matchers[matchers.length - 1]; if (dataTypes.size() != matchers.length) { if (varArgs && dataTypes.size() > matchers.length) { if (checkVarArgTypes) { DataType commonType = getCommonType(dataTypes, matchers.length - 1); if (commonType == null) { return null; } else if (!IS_UNDEFINED.test(commonType)) { // enforce the common type in varArgs varArgMatcher = dt -> commonType; } } } else { return null; } } return normalize(dataTypes, varArgMatcher); } @Nullable private List<DataType> normalize(List<DataType> dataTypes, ArgMatcher varArgMatcher){ for (int i = 0; i < dataTypes.size(); i++) { ArgMatcher matcher = i < matchers.length - 1 ? matchers[i] : varArgMatcher; DataType dataType = dataTypes.get(i); DataType repl = matcher.apply(dataType); if (repl == null) { return null; } else if (repl != dataType) { return mutatedCopy(dataTypes, varArgMatcher); } } return dataTypes; } private List<DataType> mutatedCopy(List<DataType> in, ArgMatcher varArgMatcher) { ArrayList<DataType> out = new ArrayList<>(in.size()); for (int i = 0; i < in.size(); i++) { ArgMatcher matcher = i < matchers.length - 1 ? matchers[i] : varArgMatcher; DataType dt = matcher.apply(in.get(i)); if (dt == null) { return null; } out.add(dt); } return out; } } }