/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
*/
package org.apache.cassandra.cql3.functions;
import java.util.List;
import com.google.common.collect.ArrayListMultimap;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.AssignementTestable;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.exceptions.InvalidRequestException;
public abstract class Functions
{
private Functions() {}
// If we ever allow this to be populated at runtime, this will need to be thread safe.
private static final ArrayListMultimap<String, Function.Factory> declared = ArrayListMultimap.create();
static
{
// All method sharing the same name must have the same returnType. We could find a way to make that clear.
declared.put("token", TokenFct.factory);
declared.put("now", AbstractFunction.factory(TimeuuidFcts.nowFct));
declared.put("mintimeuuid", AbstractFunction.factory(TimeuuidFcts.minTimeuuidFct));
declared.put("maxtimeuuid", AbstractFunction.factory(TimeuuidFcts.maxTimeuuidFct));
declared.put("dateof", AbstractFunction.factory(TimeuuidFcts.dateOfFct));
declared.put("unixtimestampof", AbstractFunction.factory(TimeuuidFcts.unixTimestampOfFct));
for (CQL3Type type : CQL3Type.Native.values())
{
// Note: because text and varchar ends up being synonimous, 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)
continue;
Function toBlob = BytesConversionFcts.makeToBlobFunction(type.getType());
Function fromBlob = BytesConversionFcts.makeFromBlobFunction(type.getType());
declared.put(toBlob.name(), AbstractFunction.factory(toBlob));
declared.put(fromBlob.name(), AbstractFunction.factory(fromBlob));
}
declared.put("varcharasblob", AbstractFunction.factory(BytesConversionFcts.VarcharAsBlobFct));
declared.put("blobasvarchar", AbstractFunction.factory(BytesConversionFcts.BlobAsVarcharFact));
}
public static AbstractType<?> getReturnType(String functionName, String ksName, String cfName)
{
List<Function.Factory> factories = declared.get(functionName.toLowerCase());
return factories.isEmpty()
? null // That's ok, we'll complain later
: factories.get(0).create(ksName, cfName).returnType();
}
public static ColumnSpecification makeArgSpec(ColumnSpecification receiver, Function fun, int i)
{
return new ColumnSpecification(receiver.ksName,
receiver.cfName,
new ColumnIdentifier("arg" + i + "(" + fun.name() + ")", true),
fun.argsType().get(i));
}
public static Function get(String name, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) throws InvalidRequestException
{
List<Function.Factory> factories = declared.get(name.toLowerCase());
if (factories.isEmpty())
throw new InvalidRequestException(String.format("Unknown CQL3 function %s called", name));
// Fast path if there is not choice
if (factories.size() == 1)
{
Function fun = factories.get(0).create(receiver.ksName, receiver.cfName);
validateTypes(fun, providedArgs, receiver);
return fun;
}
Function candidate = null;
for (Function.Factory factory : factories)
{
Function toTest = factory.create(receiver.ksName, receiver.cfName);
if (!isValidType(toTest, providedArgs, receiver))
continue;
if (candidate == null)
candidate = toTest;
else
throw new InvalidRequestException(String.format("Ambiguous call to function %s (can match both type signature %s and %s): use type casts to disambiguate", name, signature(candidate), signature(toTest)));
}
if (candidate == null)
throw new InvalidRequestException(String.format("Invalid call to function %s, none of its type signature matches (known type signatures: %s)", name, signatures(factories, receiver)));
return candidate;
}
private static void validateTypes(Function fun, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver) throws InvalidRequestException
{
if (!receiver.type.asCQL3Type().equals(fun.returnType().asCQL3Type()))
throw new InvalidRequestException(String.format("Type error: cannot assign result of function %s (type %s) to %s (type %s)", fun.name(), fun.returnType().asCQL3Type(), receiver, receiver.type.asCQL3Type()));
if (providedArgs.size() != fun.argsType().size())
throw new InvalidRequestException(String.format("Invalid number of arguments in call to function %s: %d required but %d provided", fun.name(), fun.argsType().size(), providedArgs.size()));
for (int i = 0; i < providedArgs.size(); i++)
{
AssignementTestable 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(receiver, fun, i);
if (!provided.isAssignableTo(expected))
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 boolean isValidType(Function fun, List<? extends AssignementTestable> providedArgs, ColumnSpecification receiver)
{
if (!receiver.type.asCQL3Type().equals(fun.returnType().asCQL3Type()))
return false;
if (providedArgs.size() != fun.argsType().size())
return false;
for (int i = 0; i < providedArgs.size(); i++)
{
AssignementTestable 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(receiver, fun, i);
if (!provided.isAssignableTo(expected))
return false;
}
return true;
}
private static String signature(Function fun)
{
List<AbstractType<?>> args = fun.argsType();
StringBuilder sb = new StringBuilder();
sb.append("(");
for (int i = 0; i < args.size(); i++)
{
if (i > 0) sb.append(", ");
sb.append(args.get(i).asCQL3Type());
}
sb.append(") -> ");
sb.append(fun.returnType().asCQL3Type());
return sb.toString();
}
private static String signatures(List<Function.Factory> factories, ColumnSpecification receiver)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < factories.size(); i++)
{
if (i > 0) sb.append(", ");
sb.append(signature(factories.get(i).create(receiver.ksName, receiver.cfName)));
}
return sb.toString();
}
}