public static Function find(FunctionName name, List<AbstractType<?>> argTypes) { assert name.hasKeyspace() : "function name not fully qualified"; for (Function f : find(name)) { if (typeEquals(f.argTypes(), argTypes)) return f; } return null; }
// Same remarks than for addFunction public static void removeFunction(FunctionName name, List<AbstractType<?>> argTypes) { assert name.hasKeyspace() : "function name " + name + " not fully qualified"; synchronized (declared) { List<Function> functions = find(name); for (int i = 0; i < functions.size(); i++) { Function f = functions.get(i); if (!typeEquals(f.argTypes(), argTypes)) continue; assert !f.isNative(); functions.remove(i); if (functions.isEmpty()) declared.remove(name); return; } } }
public abstract class Functions { // We special case the token function because that's the only function whose argument types // actually // depend on the table on which the function is called. Because it's the sole exception, it's // easier // to handle it as a special case. private static final FunctionName TOKEN_FUNCTION_NAME = FunctionName.nativeFunction("token"); private Functions() {} private static final ConcurrentMap<FunctionName, List<Function>> declared = new ConcurrentHashMap<>(); static { declare(AggregateFcts.countRowsFunction); declare(TimeFcts.nowFct); declare(TimeFcts.minTimeuuidFct); declare(TimeFcts.maxTimeuuidFct); declare(TimeFcts.dateOfFct); declare(TimeFcts.unixTimestampOfFct); declare(TimeFcts.timeUuidtoDate); declare(TimeFcts.timeUuidToTimestamp); declare(TimeFcts.timeUuidToUnixTimestamp); declare(TimeFcts.timestampToDate); declare(TimeFcts.timestampToUnixTimestamp); declare(TimeFcts.dateToTimestamp); declare(TimeFcts.dateToUnixTimestamp); declare(UuidFcts.uuidFct); for (CQL3Type type : CQL3Type.Native.values()) { // Note: because text and varchar ends up being synonymous, our automatic makeToBlobFunction // doesn't work // for varchar, so we special case it below. We also skip blob for obvious reasons. if (type != CQL3Type.Native.VARCHAR && type != CQL3Type.Native.BLOB) { declare(BytesConversionFcts.makeToBlobFunction(type.getType())); declare(BytesConversionFcts.makeFromBlobFunction(type.getType())); } } declare(BytesConversionFcts.VarcharAsBlobFct); declare(BytesConversionFcts.BlobAsVarcharFact); for (CQL3Type type : CQL3Type.Native.values()) { // special case varchar to avoid duplicating functions for UTF8Type if (type != CQL3Type.Native.VARCHAR) { declare(AggregateFcts.makeCountFunction(type.getType())); declare(AggregateFcts.makeMaxFunction(type.getType())); declare(AggregateFcts.makeMinFunction(type.getType())); } } declare(AggregateFcts.sumFunctionForByte); declare(AggregateFcts.sumFunctionForShort); declare(AggregateFcts.sumFunctionForInt32); declare(AggregateFcts.sumFunctionForLong); declare(AggregateFcts.sumFunctionForFloat); declare(AggregateFcts.sumFunctionForDouble); declare(AggregateFcts.sumFunctionForDecimal); declare(AggregateFcts.sumFunctionForVarint); declare(AggregateFcts.sumFunctionForCounter); declare(AggregateFcts.avgFunctionForByte); declare(AggregateFcts.avgFunctionForShort); declare(AggregateFcts.avgFunctionForInt32); declare(AggregateFcts.avgFunctionForLong); declare(AggregateFcts.avgFunctionForFloat); declare(AggregateFcts.avgFunctionForDouble); declare(AggregateFcts.avgFunctionForVarint); declare(AggregateFcts.avgFunctionForDecimal); declare(AggregateFcts.avgFunctionForCounter); declare(RangeAggregateFcts.rangeAggregationFunctionForTimestampToDouble); MigrationManager.instance.register(new FunctionsMigrationListener()); } private static void declare(Function fun) { synchronized (declared) { List<Function> functions = declared.get(fun.name()); if (functions == null) { functions = new CopyOnWriteArrayList<>(); List<Function> existing = declared.putIfAbsent(fun.name(), functions); if (existing != null) functions = existing; } functions.add(fun); } } public static ColumnSpecification makeArgSpec( String receiverKs, String receiverCf, Function fun, int i) { return new ColumnSpecification( receiverKs, receiverCf, new ColumnIdentifier("arg" + i + '(' + fun.name().toString().toLowerCase() + ')', true), fun.argTypes().get(i)); } public static int getOverloadCount(FunctionName name) { return find(name).size(); } /** * @param keyspace the current keyspace * @param name the name of the function * @param providedArgs the arguments provided for the function call * @param receiverKs the receiver's keyspace * @param receiverCf the receiver's table * @param receiverType if the receiver type is known (during inserts, for example), this should be * the type of the receiver * @throws InvalidRequestException */ public static Function get( String keyspace, FunctionName name, List<? extends AssignmentTestable> providedArgs, String receiverKs, String receiverCf, AbstractType<?> receiverType) throws InvalidRequestException { if (name.equalsNativeFunction(TOKEN_FUNCTION_NAME)) return new TokenFct(Schema.instance.getCFMetaData(receiverKs, receiverCf)); // The toJson() function can accept any type of argument, so instances of it are not // pre-declared. Instead, // we create new instances as needed while handling selectors (which is the only place that // toJson() is supported, // due to needing to know the argument types in advance). if (name.equalsNativeFunction(ToJsonFct.NAME)) throw new InvalidRequestException( "toJson() may only be used within the selection clause of SELECT statements"); // Similarly, we can only use fromJson when we know the receiver type (such as inserts) if (name.equalsNativeFunction(FromJsonFct.NAME)) { if (receiverType == null) throw new InvalidRequestException( "fromJson() cannot be used in the selection clause of a SELECT statement"); return FromJsonFct.getInstance(receiverType); } List<Function> candidates; if (!name.hasKeyspace()) { // function name not fully qualified candidates = new ArrayList<>(); // add 'SYSTEM' (native) candidates candidates.addAll(find(name.asNativeFunction())); // add 'current keyspace' candidates candidates.addAll(find(new FunctionName(keyspace, name.name))); } else // function name is fully qualified (keyspace + name) candidates = find(name); if (candidates.isEmpty()) return null; // Fast path if there is only one choice if (candidates.size() == 1) { Function fun = candidates.get(0); validateTypes(keyspace, fun, providedArgs, receiverKs, receiverCf); return fun; } List<Function> compatibles = null; for (Function toTest : candidates) { AssignmentTestable.TestResult r = matchAguments(keyspace, toTest, providedArgs, receiverKs, receiverCf); switch (r) { case EXACT_MATCH: // We always favor exact matches return toTest; case WEAKLY_ASSIGNABLE: if (compatibles == null) compatibles = new ArrayList<>(); compatibles.add(toTest); break; } } if (compatibles == null || compatibles.isEmpty()) throw new InvalidRequestException( String.format( "Invalid call to function %s, none of its type signatures match (known type signatures: %s)", name, toString(candidates))); if (compatibles.size() > 1) throw new InvalidRequestException( String.format( "Ambiguous call to function %s (can be matched by following signatures: %s): use type casts to disambiguate", name, toString(compatibles))); return compatibles.get(0); } public static List<Function> find(FunctionName name) { List<Function> functions = declared.get(name); return functions != null ? functions : Collections.<Function>emptyList(); } public static Function find(FunctionName name, List<AbstractType<?>> argTypes) { assert name.hasKeyspace() : "function name not fully qualified"; for (Function f : find(name)) { if (typeEquals(f.argTypes(), argTypes)) return f; } return null; } // This method and matchArguments are somewhat duplicate, but this method allows us to provide // more precise errors in the common // case where there is no override for a given function. This is thus probably worth the minor // code duplication. private static void validateTypes( String keyspace, Function fun, List<? extends AssignmentTestable> providedArgs, String receiverKs, String receiverCf) throws InvalidRequestException { if (providedArgs.size() != fun.argTypes().size()) throw new InvalidRequestException( String.format( "Invalid number of arguments in call to function %s: %d required but %d provided", fun.name(), fun.argTypes().size(), providedArgs.size())); for (int i = 0; i < providedArgs.size(); i++) { AssignmentTestable provided = providedArgs.get(i); // If the concrete argument is a bind variables, it can have any type. // We'll validate the actually provided value at execution time. if (provided == null) continue; ColumnSpecification expected = makeArgSpec(receiverKs, receiverCf, fun, i); if (!provided.testAssignment(keyspace, expected).isAssignable()) throw new InvalidRequestException( String.format( "Type error: %s cannot be passed as argument %d of function %s of type %s", provided, i, fun.name(), expected.type.asCQL3Type())); } } private static AssignmentTestable.TestResult matchAguments( String keyspace, Function fun, List<? extends AssignmentTestable> providedArgs, String receiverKs, String receiverCf) { if (providedArgs.size() != fun.argTypes().size()) return AssignmentTestable.TestResult.NOT_ASSIGNABLE; // It's an exact match if all are exact match, but is not assignable as soon as any is non // assignable. AssignmentTestable.TestResult res = AssignmentTestable.TestResult.EXACT_MATCH; for (int i = 0; i < providedArgs.size(); i++) { AssignmentTestable provided = providedArgs.get(i); if (provided == null) { res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; continue; } ColumnSpecification expected = makeArgSpec(receiverKs, receiverCf, fun, i); AssignmentTestable.TestResult argRes = provided.testAssignment(keyspace, expected); if (argRes == AssignmentTestable.TestResult.NOT_ASSIGNABLE) return AssignmentTestable.TestResult.NOT_ASSIGNABLE; if (argRes == AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE) res = AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; } return res; } private static String toString(List<Function> funs) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < funs.size(); i++) { if (i > 0) sb.append(", "); sb.append(funs.get(i)); } return sb.toString(); } public static void addOrReplaceFunction(AbstractFunction fun) { // We shouldn't get there unless that function don't exist removeFunction(fun.name(), fun.argTypes()); declare(fun); } // Same remarks than for addFunction public static void removeFunction(FunctionName name, List<AbstractType<?>> argTypes) { assert name.hasKeyspace() : "function name " + name + " not fully qualified"; synchronized (declared) { List<Function> functions = find(name); for (int i = 0; i < functions.size(); i++) { Function f = functions.get(i); if (!typeEquals(f.argTypes(), argTypes)) continue; assert !f.isNative(); functions.remove(i); if (functions.isEmpty()) declared.remove(name); return; } } } public static List<Function> getReferencesTo(Function old) { List<Function> references = new ArrayList<>(); for (List<Function> functions : declared.values()) for (Function function : functions) if (function.hasReferenceTo(old)) references.add(function); return references; } public static Collection<Function> all() { List<Function> all = new ArrayList<>(); for (List<Function> functions : declared.values()) all.addAll(functions); return all; } /* * We need to compare the CQL3 representation of the type because comparing * the AbstractType will fail for example if a UDT has been changed. * Reason is that UserType.equals() takes the field names and types into account. * Example CQL sequence that would fail when comparing AbstractType: * CREATE TYPE foo ... * CREATE FUNCTION bar ( par foo ) RETURNS foo ... * ALTER TYPE foo ADD ... * or * ALTER TYPE foo ALTER ... * or * ALTER TYPE foo RENAME ... */ public static boolean typeEquals(AbstractType<?> t1, AbstractType<?> t2) { return t1.asCQL3Type().toString().equals(t2.asCQL3Type().toString()); } public static boolean typeEquals(List<AbstractType<?>> t1, List<AbstractType<?>> t2) { if (t1.size() != t2.size()) return false; for (int i = 0; i < t1.size(); i++) if (!typeEquals(t1.get(i), t2.get(i))) return false; return true; } public static int typeHashCode(AbstractType<?> t) { return t.asCQL3Type().toString().hashCode(); } public static int typeHashCode(List<AbstractType<?>> types) { int h = 0; for (AbstractType<?> type : types) h = h * 31 + typeHashCode(type); return h; } private static class FunctionsMigrationListener extends MigrationListener { public void onUpdateUserType(String ksName, String typeName) { for (Function function : all()) if (function instanceof UDFunction) ((UDFunction) function).userTypeUpdated(ksName, typeName); } } }
/** * @param keyspace the current keyspace * @param name the name of the function * @param providedArgs the arguments provided for the function call * @param receiverKs the receiver's keyspace * @param receiverCf the receiver's table * @param receiverType if the receiver type is known (during inserts, for example), this should be * the type of the receiver * @throws InvalidRequestException */ public static Function get( String keyspace, FunctionName name, List<? extends AssignmentTestable> providedArgs, String receiverKs, String receiverCf, AbstractType<?> receiverType) throws InvalidRequestException { if (name.equalsNativeFunction(TOKEN_FUNCTION_NAME)) return new TokenFct(Schema.instance.getCFMetaData(receiverKs, receiverCf)); // The toJson() function can accept any type of argument, so instances of it are not // pre-declared. Instead, // we create new instances as needed while handling selectors (which is the only place that // toJson() is supported, // due to needing to know the argument types in advance). if (name.equalsNativeFunction(ToJsonFct.NAME)) throw new InvalidRequestException( "toJson() may only be used within the selection clause of SELECT statements"); // Similarly, we can only use fromJson when we know the receiver type (such as inserts) if (name.equalsNativeFunction(FromJsonFct.NAME)) { if (receiverType == null) throw new InvalidRequestException( "fromJson() cannot be used in the selection clause of a SELECT statement"); return FromJsonFct.getInstance(receiverType); } List<Function> candidates; if (!name.hasKeyspace()) { // function name not fully qualified candidates = new ArrayList<>(); // add 'SYSTEM' (native) candidates candidates.addAll(find(name.asNativeFunction())); // add 'current keyspace' candidates candidates.addAll(find(new FunctionName(keyspace, name.name))); } else // function name is fully qualified (keyspace + name) candidates = find(name); if (candidates.isEmpty()) return null; // Fast path if there is only one choice if (candidates.size() == 1) { Function fun = candidates.get(0); validateTypes(keyspace, fun, providedArgs, receiverKs, receiverCf); return fun; } List<Function> compatibles = null; for (Function toTest : candidates) { AssignmentTestable.TestResult r = matchAguments(keyspace, toTest, providedArgs, receiverKs, receiverCf); switch (r) { case EXACT_MATCH: // We always favor exact matches return toTest; case WEAKLY_ASSIGNABLE: if (compatibles == null) compatibles = new ArrayList<>(); compatibles.add(toTest); break; } } if (compatibles == null || compatibles.isEmpty()) throw new InvalidRequestException( String.format( "Invalid call to function %s, none of its type signatures match (known type signatures: %s)", name, toString(candidates))); if (compatibles.size() > 1) throw new InvalidRequestException( String.format( "Ambiguous call to function %s (can be matched by following signatures: %s): use type casts to disambiguate", name, toString(compatibles))); return compatibles.get(0); }