Repository: cassandra Updated Branches: refs/heads/trunk 75a30c5c2 -> 44fa12ec4
http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/TimeuuidFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/TimeuuidFcts.java b/src/java/org/apache/cassandra/cql3/functions/TimeuuidFcts.java index be20102..9b7bbf0 100644 --- a/src/java/org/apache/cassandra/cql3/functions/TimeuuidFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/TimeuuidFcts.java @@ -29,7 +29,7 @@ import org.apache.cassandra.utils.UUIDGen; public abstract class TimeuuidFcts { - public static final Function nowFct = new AbstractFunction("now", TimeUUIDType.instance) + public static final Function nowFct = new NativeFunction("now", TimeUUIDType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { @@ -43,7 +43,7 @@ public abstract class TimeuuidFcts } }; - public static final Function minTimeuuidFct = new AbstractFunction("mintimeuuid", TimeUUIDType.instance, TimestampType.instance) + public static final Function minTimeuuidFct = new NativeFunction("mintimeuuid", TimeUUIDType.instance, TimestampType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { @@ -55,7 +55,7 @@ public abstract class TimeuuidFcts } }; - public static final Function maxTimeuuidFct = new AbstractFunction("maxtimeuuid", TimeUUIDType.instance, TimestampType.instance) + public static final Function maxTimeuuidFct = new NativeFunction("maxtimeuuid", TimeUUIDType.instance, TimestampType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { @@ -67,7 +67,7 @@ public abstract class TimeuuidFcts } }; - public static final Function dateOfFct = new AbstractFunction("dateof", TimestampType.instance, TimeUUIDType.instance) + public static final Function dateOfFct = new NativeFunction("dateof", TimestampType.instance, TimeUUIDType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { @@ -79,7 +79,7 @@ public abstract class TimeuuidFcts } }; - public static final Function unixTimestampOfFct = new AbstractFunction("unixtimestampof", LongType.instance, TimeUUIDType.instance) + public static final Function unixTimestampOfFct = new NativeFunction("unixtimestampof", LongType.instance, TimeUUIDType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/TokenFct.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/TokenFct.java b/src/java/org/apache/cassandra/cql3/functions/TokenFct.java index 5093a72..2504a66 100644 --- a/src/java/org/apache/cassandra/cql3/functions/TokenFct.java +++ b/src/java/org/apache/cassandra/cql3/functions/TokenFct.java @@ -22,26 +22,17 @@ import java.util.List; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.config.Schema; import org.apache.cassandra.db.composites.CBuilder; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.service.StorageService; -public class TokenFct extends AbstractFunction +public class TokenFct extends NativeFunction { // The actual token function depends on the partitioner used private static final IPartitioner partitioner = StorageService.getPartitioner(); - public static final Function.Factory factory = new Function.Factory() - { - public Function create(String ksName, String cfName) - { - return new TokenFct(Schema.instance.getCFMetaData(ksName, cfName)); - } - }; - private final CFMetaData cfm; public TokenFct(CFMetaData cfm) http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/UDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java new file mode 100644 index 0000000..558fb72 --- /dev/null +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -0,0 +1,250 @@ +/* + * 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.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.*; + +import com.google.common.base.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.*; +import org.apache.cassandra.db.composites.Composite; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.db.marshal.CompositeType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.db.marshal.TypeParser; +import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.utils.FBUtilities; + +/** + * Base class for User Defined Functions. + */ +public abstract class UDFunction extends AbstractFunction +{ + protected static final Logger logger = LoggerFactory.getLogger(UDFunction.class); + + protected final List<ColumnIdentifier> argNames; + + protected final String language; + protected final String body; + private final boolean deterministic; + + protected UDFunction(FunctionName name, + List<ColumnIdentifier> argNames, + List<AbstractType<?>> argTypes, + AbstractType<?> returnType, + String language, + String body, + boolean deterministic) + { + super(name, argTypes, returnType); + this.argNames = argNames; + this.language = language; + this.body = body; + this.deterministic = deterministic; + } + + public static UDFunction create(FunctionName name, + List<ColumnIdentifier> argNames, + List<AbstractType<?>> argTypes, + AbstractType<?> returnType, + String language, + String body, + boolean deterministic) + throws InvalidRequestException + { + switch (language) + { + case "class": return new ReflectionBasedUDF(name, argNames, argTypes, returnType, language, body, deterministic); + default: throw new InvalidRequestException(String.format("Invalid language %s for '%s'", language, name)); + } + } + + /** + * It can happen that a function has been declared (is listed in the scheam) but cannot + * be loaded (maybe only on some nodes). This is the case for instance if the class defining + * the class is not on the classpath for some of the node, or after a restart. In that case, + * we create a "fake" function so that: + * 1) the broken function can be dropped easily if that is what people want to do. + * 2) we return a meaningful error message if the function is executed (something more precise + * than saying that the function doesn't exist) + */ + private static UDFunction createBrokenFunction(FunctionName name, + List<ColumnIdentifier> argNames, + List<AbstractType<?>> argTypes, + AbstractType<?> returnType, + String language, + String body, + final InvalidRequestException reason) + { + return new UDFunction(name, argNames, argTypes, returnType, language, body, true) + { + public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException + { + throw new InvalidRequestException(String.format("Function '%s' exists but hasn't been loaded successfully for the following reason: %s. " + + "Please see the server log for more details", this, reason.getMessage())); + } + }; + } + + // We allow method overloads, so a function is not uniquely identified by its name only, but + // also by its argument types. To distinguish overloads of given function name in the schema + // we use a "signature" which is just a SHA-1 of it's argument types (we could replace that by + // using a "signature" UDT that would be comprised of the function name and argument types, + // which we could then use as clustering column. But as we haven't yet used UDT in system tables, + // We'll left that decision to #6717). + private static ByteBuffer computeSignature(List<AbstractType<?>> argTypes) + { + MessageDigest digest = FBUtilities.newMessageDigest("SHA-1"); + for (AbstractType<?> type : argTypes) + digest.update(type.toString().getBytes(StandardCharsets.UTF_8)); + return ByteBuffer.wrap(digest.digest()); + } + + public boolean isPure() + { + return deterministic; + } + + public boolean isNative() + { + return false; + } + + private static Mutation makeSchemaMutation(FunctionName name) + { + CompositeType kv = (CompositeType)CFMetaData.SchemaFunctionsCf.getKeyValidator(); + return new Mutation(Keyspace.SYSTEM_KS, kv.decompose(name.namespace, name.name)); + } + + // TODO: we should allow removing just one function, not all functions having a given name + public static Mutation dropFromSchema(long timestamp, FunctionName fun) + { + Mutation mutation = makeSchemaMutation(fun); + mutation.delete(SystemKeyspace.SCHEMA_FUNCTIONS_CF, timestamp); + return mutation; + } + + public Mutation toSchemaUpdate(long timestamp) + { + Mutation mutation = makeSchemaMutation(name); + ColumnFamily cf = mutation.addOrGet(SystemKeyspace.SCHEMA_FUNCTIONS_CF); + + Composite prefix = CFMetaData.SchemaFunctionsCf.comparator.make(computeSignature(argTypes)); + CFRowAdder adder = new CFRowAdder(cf, prefix, timestamp); + + adder.resetCollection("argument_names"); + adder.resetCollection("argument_types"); + adder.add("return_type", returnType.toString()); + adder.add("language", language); + adder.add("body", body); + adder.add("deterministic", deterministic); + + for (int i = 0; i < argNames.size(); i++) + { + adder.addListEntry("argument_names", argNames.get(i).bytes); + adder.addListEntry("argument_types", argTypes.get(i).toString()); + } + + return mutation; + } + + public static UDFunction fromSchema(UntypedResultSet.Row row) + { + String namespace = row.getString("namespace"); + String fname = row.getString("name"); + FunctionName name = new FunctionName(namespace, fname); + + List<String> names = row.getList("argument_names", UTF8Type.instance); + List<String> types = row.getList("argument_types", UTF8Type.instance); + + List<ColumnIdentifier> argNames = new ArrayList<>(names.size()); + for (String arg : names) + argNames.add(new ColumnIdentifier(arg, true)); + + List<AbstractType<?>> argTypes = new ArrayList<>(types.size()); + for (String type : types) + argTypes.add(parseType(type)); + + AbstractType<?> returnType = parseType(row.getString("return_type")); + + boolean deterministic = row.getBoolean("deterministic"); + String language = row.getString("language"); + String body = row.getString("body"); + + try + { + return create(name, argNames, argTypes, returnType, language, body, deterministic); + } + catch (InvalidRequestException e) + { + logger.error(String.format("Cannot load function '%s' from schema: this function won't be available (on this node)", name), e); + return createBrokenFunction(name, argNames, argTypes, returnType, language, body, e); + } + } + + private static AbstractType<?> parseType(String str) + { + // We only use this when reading the schema where we shouldn't get an error + try + { + return TypeParser.parse(str); + } + catch (SyntaxException | ConfigurationException e) + { + throw new RuntimeException(e); + } + } + + public static Map<ByteBuffer, UDFunction> fromSchema(Row row) + { + UntypedResultSet results = QueryProcessor.resultify("SELECT * FROM system." + SystemKeyspace.SCHEMA_FUNCTIONS_CF, row); + Map<ByteBuffer, UDFunction> udfs = new HashMap<>(results.size()); + for (UntypedResultSet.Row result : results) + udfs.put(result.getBlob("signature"), fromSchema(result)); + return udfs; + } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof UDFunction)) + return false; + + UDFunction that = (UDFunction)o; + return Objects.equal(this.name, that.name) + && Objects.equal(this.argNames, that.argNames) + && Objects.equal(this.argTypes, that.argTypes) + && Objects.equal(this.returnType, that.returnType) + && Objects.equal(this.language, that.language) + && Objects.equal(this.body, that.body) + && Objects.equal(this.deterministic, that.deterministic); + } + + @Override + public int hashCode() + { + return Objects.hashCode(name, argNames, argTypes, returnType, language, body, deterministic); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java b/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java index 718bcbc..1bf4c17 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java @@ -26,7 +26,7 @@ import org.apache.cassandra.serializers.UUIDSerializer; public abstract class UuidFcts { - public static final Function uuidFct = new AbstractFunction("uuid", UUIDType.instance) + public static final Function uuidFct = new NativeFunction("uuid", UUIDType.instance) { public ByteBuffer execute(List<ByteBuffer> parameters) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java index 47bacd2..a54409e 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java @@ -21,57 +21,50 @@ import java.util.ArrayList; import java.util.List; import org.apache.cassandra.auth.Permission; -import org.apache.cassandra.config.Schema; -import org.apache.cassandra.config.UFMetaData; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.cql3.functions.Functions; -import org.apache.cassandra.cql3.udf.UDFRegistry; +import org.apache.cassandra.cql3.functions.*; +import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; -import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Event; -import org.apache.cassandra.transport.messages.ResultMessage; /** * A <code>CREATE FUNCTION</code> statement parsed from a CQL query. */ public final class CreateFunctionStatement extends SchemaAlteringStatement { - final boolean orReplace; - final boolean ifNotExists; - final String namespace; - final String functionName; - final String qualifiedName; - final String language; - final String body; - final boolean deterministic; - final CQL3Type.Raw returnType; - final List<Argument> arguments; - - private UFMetaData ufMeta; - - public CreateFunctionStatement(String namespace, String functionName, String language, String body, boolean deterministic, - CQL3Type.Raw returnType, List<Argument> arguments, boolean orReplace, boolean ifNotExists) + private final boolean orReplace; + private final boolean ifNotExists; + private final FunctionName functionName; + private final String language; + private final String body; + private final boolean deterministic; + + private final List<ColumnIdentifier> argNames; + private final List<CQL3Type.Raw> argRawTypes; + private final CQL3Type.Raw rawReturnType; + + public CreateFunctionStatement(FunctionName functionName, + String language, + String body, + boolean deterministic, + List<ColumnIdentifier> argNames, + List<CQL3Type.Raw> argRawTypes, + CQL3Type.Raw rawReturnType, + boolean orReplace, + boolean ifNotExists) { - super(); - this.namespace = namespace != null ? namespace : ""; this.functionName = functionName; - this.qualifiedName = UFMetaData.qualifiedName(namespace, functionName); this.language = language; this.body = body; this.deterministic = deterministic; - this.returnType = returnType; - this.arguments = arguments; - assert functionName != null : "null function name"; - assert language != null : "null function language"; - assert body != null : "null function body"; - assert returnType != null : "null function returnType"; - assert arguments != null : "null function arguments"; + this.argNames = argNames; + this.argRawTypes = argRawTypes; + this.rawReturnType = rawReturnType; this.orReplace = orReplace; this.ifNotExists = ifNotExists; } @@ -83,23 +76,10 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement state.hasAllKeyspacesAccess(Permission.CREATE); } - /** - * The <code>CqlParser</code> only goes as far as extracting the keyword arguments - * from these statements, so this method is responsible for processing and - * validating. - * - * @throws org.apache.cassandra.exceptions.InvalidRequestException if arguments are missing or unacceptable - */ - public void validate(ClientState state) throws RequestValidationException + public void validate(ClientState state) throws InvalidRequestException { - if (!namespace.isEmpty() && !namespace.matches("\\w+")) - throw new InvalidRequestException(String.format("\"%s\" is not a valid function name", qualifiedName)); - if (!functionName.matches("\\w+")) - throw new InvalidRequestException(String.format("\"%s\" is not a valid function name", qualifiedName)); - if (namespace.length() > Schema.NAME_LENGTH) - throw new InvalidRequestException(String.format("UDF namespace names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, qualifiedName)); - if (functionName.length() > Schema.NAME_LENGTH) - throw new InvalidRequestException(String.format("UDF function names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, qualifiedName)); + if (ifNotExists && orReplace) + throw new InvalidRequestException("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives"); } public Event.SchemaChange changeEvent() @@ -107,75 +87,34 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement return null; } - public ResultMessage executeInternal(QueryState state, QueryOptions options) - { - try - { - doExecute(); - return super.executeInternal(state, options); - } - catch (RequestValidationException e) - { - throw new RuntimeException(e); - } - } - - public ResultMessage execute(QueryState state, QueryOptions options) throws RequestValidationException - { - doExecute(); - return super.execute(state, options); - } - - private void doExecute() throws RequestValidationException - { - boolean exists = UDFRegistry.hasFunction(qualifiedName); - if (exists && ifNotExists) - throw new InvalidRequestException(String.format("Function '%s' already exists.", qualifiedName)); - if (exists && !orReplace) - throw new InvalidRequestException(String.format("Function '%s' already exists.", qualifiedName)); - - if (namespace.isEmpty() && Functions.contains(functionName)) - throw new InvalidRequestException(String.format("Function name '%s' is reserved by CQL.", qualifiedName)); - - List<Argument> args = arguments; - List<String> argumentNames = new ArrayList<>(args.size()); - List<String> argumentTypes = new ArrayList<>(args.size()); - for (Argument arg : args) - { - argumentNames.add(arg.getName().toString()); - argumentTypes.add(arg.getType().toString()); - } - this.ufMeta = new UFMetaData(namespace, functionName, deterministic, argumentNames, argumentTypes, - returnType.toString(), language, body); - - UDFRegistry.tryCreateFunction(ufMeta); - } - public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - MigrationManager.announceNewFunction(ufMeta, isLocalOnly); - return true; - } - - public static final class Argument - { - final ColumnIdentifier name; - final CQL3Type.Raw type; + List<AbstractType<?>> argTypes = new ArrayList<>(argRawTypes.size()); + for (CQL3Type.Raw rawType : argRawTypes) + // We have no proper keyspace to give, which means that this will break (NPE currently) + // for UDT: #7791 is open to fix this + argTypes.add(rawType.prepare(null).getType()); - public Argument(ColumnIdentifier name, CQL3Type.Raw type) - { - this.name = name; - this.type = type; - } + AbstractType<?> returnType = rawReturnType.prepare(null).getType(); - public ColumnIdentifier getName() + Function old = Functions.find(functionName, argTypes); + if (old != null) { - return name; + if (ifNotExists) + return false; + if (!orReplace) + throw new InvalidRequestException(String.format("Function %s already exists", old)); + + // Means we're replacing the function. We still need to validate that 1) it's not a native function and 2) that the return type + // matches (or that could break existing code badly) + if (old.isNative()) + throw new InvalidRequestException(String.format("Cannot replace native function %s", old)); + if (!old.returnType().isValueCompatibleWith(returnType)) + throw new InvalidRequestException(String.format("Cannot replace function %s, the new return type %s is not compatible with the return type %s of existing function", + functionName, returnType.asCQL3Type(), old.returnType().asCQL3Type())); } - public CQL3Type.Raw getType() - { - return type; - } + MigrationManager.announceNewFunction(UDFunction.create(functionName, argNames, argTypes, returnType, language, body, deterministic), isLocalOnly); + return true; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java index 159f385..4c963a8 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java @@ -17,9 +17,10 @@ */ package org.apache.cassandra.cql3.statements; +import java.util.List; + import org.apache.cassandra.auth.Permission; -import org.apache.cassandra.config.Schema; -import org.apache.cassandra.config.UFMetaData; +import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; @@ -32,17 +33,12 @@ import org.apache.cassandra.transport.Event; */ public final class DropFunctionStatement extends SchemaAlteringStatement { - private final String namespace; - private final String functionName; - private final String qualifiedName; + private final FunctionName functionName; private final boolean ifExists; - public DropFunctionStatement(String namespace, String functionName, boolean ifExists) + public DropFunctionStatement(FunctionName functionName, boolean ifExists) { - super(); - this.namespace = namespace == null ? "" : namespace; this.functionName = functionName; - this.qualifiedName = UFMetaData.qualifiedName(namespace, functionName); this.ifExists = ifExists; } @@ -62,14 +58,6 @@ public final class DropFunctionStatement extends SchemaAlteringStatement */ public void validate(ClientState state) throws RequestValidationException { - if (!namespace.isEmpty() && !namespace.matches("\\w+")) - throw new InvalidRequestException(String.format("\"%s\" is not a valid function name", qualifiedName)); - if (!functionName.matches("\\w+")) - throw new InvalidRequestException(String.format("\"%s\" is not a valid function name", qualifiedName)); - if (namespace.length() > Schema.NAME_LENGTH) - throw new InvalidRequestException(String.format("UDF namespace names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, qualifiedName)); - if (functionName.length() > Schema.NAME_LENGTH) - throw new InvalidRequestException(String.format("UDF function names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, qualifiedName)); } public Event.SchemaChange changeEvent() @@ -77,20 +65,21 @@ public final class DropFunctionStatement extends SchemaAlteringStatement return null; } - // no execute() - drop propagated via MigrationManager - public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - try - { - MigrationManager.announceFunctionDrop(namespace, functionName, isLocalOnly); - return true; - } - catch (InvalidRequestException e) + List<Function> olds = Functions.find(functionName); + if (olds == null || olds.isEmpty()) { if (ifExists) return false; - throw e; + throw new InvalidRequestException(String.format("Cannot drop non existing function '%s'", functionName)); } + + for (Function f : olds) + if (f.isNative()) + throw new InvalidRequestException(String.format("Cannot drop function '%s' because it has native overloads", functionName)); + + MigrationManager.announceFunctionDrop(functionName, isLocalOnly); + return true; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/statements/Selectable.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Selectable.java b/src/java/org/apache/cassandra/cql3/statements/Selectable.java index ab0a5a3..be47d17 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Selectable.java +++ b/src/java/org/apache/cassandra/cql3/statements/Selectable.java @@ -21,6 +21,7 @@ package org.apache.cassandra.cql3.statements; import java.util.List; import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.functions.FunctionName; public interface Selectable { @@ -44,13 +45,11 @@ public interface Selectable public static class WithFunction implements Selectable { - public final String namespace; - public final String functionName; + public final FunctionName functionName; public final List<Selectable> args; - public WithFunction(String namespace, String functionName, List<Selectable> args) + public WithFunction(FunctionName functionName, List<Selectable> args) { - this.namespace = namespace; this.functionName = functionName; this.args = args; } @@ -59,8 +58,6 @@ public interface Selectable public String toString() { StringBuilder sb = new StringBuilder(); - if (!namespace.isEmpty()) - sb.append(namespace).append("::"); sb.append(functionName).append("("); for (int i = 0; i < args.size(); i++) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/statements/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Selection.java b/src/java/org/apache/cassandra/cql3/statements/Selection.java index 325ef15..20211b2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Selection.java +++ b/src/java/org/apache/cassandra/cql3/statements/Selection.java @@ -29,8 +29,6 @@ import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.cql3.udf.UDFunction; -import org.apache.cassandra.cql3.udf.UDFRegistry; import org.apache.cassandra.db.Cell; import org.apache.cassandra.db.CounterCell; import org.apache.cassandra.db.ExpiringCell; @@ -163,25 +161,11 @@ public abstract class Selection args.add(makeSelector(cfm, new RawSelector(rawArg, null), defs, null)); // resolve built-in functions before user defined functions - AbstractType<?> returnType = Functions.getReturnType(withFun.functionName, cfm.ksName, cfm.cfName); - if (returnType == null) - { - UDFunction userFun = UDFRegistry.resolveFunction(withFun.namespace, withFun.functionName, cfm.ksName, cfm.cfName, args); - if (userFun != null) - { - // got a user defined function to call - Function fun = userFun.create(args); - ColumnSpecification spec = makeFunctionSpec(cfm, withFun, fun.returnType(), raw.alias); - if (metadata != null) - metadata.add(spec); - return new FunctionSelector(userFun.create(args), args); - } - throw new InvalidRequestException(String.format("Unknown function '%s'", withFun.namespace.isEmpty() ? withFun.functionName : withFun.namespace + "::" + withFun.functionName)); - } - ColumnSpecification spec = makeFunctionSpec(cfm, withFun, returnType, raw.alias); - Function fun = Functions.get(cfm.ksName, withFun.functionName, args, spec); + Function fun = Functions.get(cfm.ksName, withFun.functionName, args, cfm.ksName, cfm.cfName); + if (fun == null) + throw new InvalidRequestException(String.format("Unknown function '%s'", withFun.functionName)); if (metadata != null) - metadata.add(spec); + metadata.add(makeFunctionSpec(cfm, withFun, fun.returnType(), raw.alias)); return new FunctionSelector(fun, args); } } @@ -208,7 +192,7 @@ public abstract class Selection ColumnIdentifier alias) throws InvalidRequestException { if (returnType == null) - throw new InvalidRequestException(String.format("Unknown function %s called in selection clause", fun.namespace.isEmpty() ? fun.functionName : fun.namespace +"::"+fun.functionName)); + throw new InvalidRequestException(String.format("Unknown function %s called in selection clause", fun.functionName)); return new ColumnSpecification(cfm.ksName, cfm.cfName, @@ -385,14 +369,19 @@ public abstract class Selection } } - private static abstract class Selector implements AssignementTestable + private static abstract class Selector implements AssignmentTestable { public abstract ByteBuffer compute(ResultSetBuilder rs) throws InvalidRequestException; public abstract AbstractType<?> getType(); - public boolean isAssignableTo(String keyspace, ColumnSpecification receiver) + public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpecification receiver) { - return receiver.type.isValueCompatibleWith(getType()); + if (receiver.type.equals(getType())) + return AssignmentTestable.TestResult.EXACT_MATCH; + else if (receiver.type.isValueCompatibleWith(getType())) + return AssignmentTestable.TestResult.WEAKLY_ASSIGNABLE; + else + return AssignmentTestable.TestResult.NOT_ASSIGNABLE; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/udf/UDFFunctionOverloads.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/udf/UDFFunctionOverloads.java b/src/java/org/apache/cassandra/cql3/udf/UDFFunctionOverloads.java deleted file mode 100644 index aa6892a..0000000 --- a/src/java/org/apache/cassandra/cql3/udf/UDFFunctionOverloads.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.udf; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.cassandra.config.UFMetaData; -import org.apache.cassandra.cql3.AssignementTestable; -import org.apache.cassandra.exceptions.InvalidRequestException; - -public final class UDFFunctionOverloads -{ - final Map<String, UFMetaData> signatureMap = new ConcurrentHashMap<>(); - final Map<String, UDFunction> udfInstances = new ConcurrentHashMap<>(); - - public void addAndInit(UFMetaData uf, boolean addIfInvalid) - { - try - { - UDFunction UDFunction = new UDFunction(uf); - udfInstances.put(uf.signature, UDFunction); - } - catch (InvalidRequestException e) - { - uf.invalid = e; - } - - if (uf.invalid == null || addIfInvalid) - signatureMap.put(uf.signature, uf); - } - - public void remove(UFMetaData uf) - { - signatureMap.remove(uf.signature); - udfInstances.remove(uf.signature); - } - - public Collection<UFMetaData> values() - { - return signatureMap.values(); - } - - public boolean isEmpty() - { - return signatureMap.isEmpty(); - } - - public UDFunction resolveFunction(String ksName, String cfName, List<? extends AssignementTestable> args) - throws InvalidRequestException - { - for (UFMetaData candidate : signatureMap.values()) - { - // Currently the UDF implementation must use concrete types (like Double, Integer) instead of base types (like Number). - // To support handling of base types it is necessary to construct new, temporary instances of UDFFunction with the - // signature for the current request in UDFFunction#argsType + UDFFunction#returnType. - // Additionally we need the requested return type (AssignementTestable) has a parameter for this method. - if (candidate.compatibleArgs(ksName, cfName, args)) - { - - // TODO CASSANDRA-7557 (specific per-function EXECUTE permission ??) - - if (candidate.invalid != null) - throw new InvalidRequestException(candidate.invalid.getMessage()); - return udfInstances.get(candidate.signature); - } - } - return null; - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/udf/UDFRegistry.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/udf/UDFRegistry.java b/src/java/org/apache/cassandra/cql3/udf/UDFRegistry.java deleted file mode 100644 index cb3f1a1..0000000 --- a/src/java/org/apache/cassandra/cql3/udf/UDFRegistry.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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.udf; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.cassandra.config.UFMetaData; -import org.apache.cassandra.cql3.AssignementTestable; -import org.apache.cassandra.cql3.QueryProcessor; -import org.apache.cassandra.cql3.UntypedResultSet; -import org.apache.cassandra.cql3.functions.Functions; -import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.db.SystemKeyspace; -import org.apache.cassandra.exceptions.InvalidRequestException; - -/** - * Central registry for user defined functions (CASSANDRA-7395). - * <p/> - * UDFs are maintained in {@code system.schema_functions} table and distributed to all nodes. - * <p/> - * UDFs are not maintained in {@link org.apache.cassandra.cql3.functions.Functions} class to have a strict - * distinction between 'core CQL' functions provided by Cassandra and functions provided by the user. - * 'Core CQL' functions have precedence over UDFs. - */ -public class UDFRegistry -{ - private static final Logger logger = LoggerFactory.getLogger(UDFRegistry.class); - - static final String SELECT_CQL = "SELECT namespace, name, signature, deterministic, argument_names, argument_types, " + - "return_type, language, body FROM " + - Keyspace.SYSTEM_KS + '.' + SystemKeyspace.SCHEMA_FUNCTIONS_CF; - - private static final Map<String, UDFFunctionOverloads> functions = new ConcurrentHashMap<>(); - - public static void init() - { - refreshInitial(); - } - - /** - * Initial loading of all existing UDFs. - */ - public static void refreshInitial() - { - logger.debug("Refreshing UDFs"); - for (UntypedResultSet.Row row : QueryProcessor.executeOnceInternal(SELECT_CQL)) - { - UFMetaData uf = UFMetaData.fromSchema(row); - UDFFunctionOverloads sigMap = functions.get(uf.qualifiedName); - if (sigMap == null) - functions.put(uf.qualifiedName, sigMap = new UDFFunctionOverloads()); - - if (Functions.contains(uf.qualifiedName)) - logger.warn("The UDF '" + uf.functionName + "' cannot be used because it uses the same name as the CQL " + - "function with the same name. You should drop this function but can do a " + - "'DESCRIBE FUNCTION "+uf.functionName+";' in cqlsh before to get more information about it."); - - // add the function to the registry even if it is invalid (to be able to drop it) - sigMap.addAndInit(uf, true); - - if (uf.invalid != null) - logger.error("Loaded invalid UDF : " + uf.invalid.getMessage()); - } - } - - public static boolean hasFunction(String qualifiedName) - { - UDFFunctionOverloads sigMap = functions.get(qualifiedName.toLowerCase()); - return sigMap != null && !sigMap.isEmpty(); - } - - public static UDFunction resolveFunction(String namespace, String functionName, String ksName, String cfName, - List<? extends AssignementTestable> args) - throws InvalidRequestException - { - UDFFunctionOverloads sigMap = functions.get(UFMetaData.qualifiedName(namespace, functionName)); - if (sigMap != null) - return sigMap.resolveFunction(ksName, cfName, args); - return null; - } - - public static void migrateDropFunction(UFMetaData uf) - { - UDFFunctionOverloads sigMap = functions.get(uf.qualifiedName); - if (sigMap == null) - return; - - sigMap.remove(uf); - } - - public static void migrateUpdateFunction(UFMetaData uf) - { - migrateAddFunction(uf); - } - - public static void migrateAddFunction(UFMetaData uf) - { - addFunction(uf, true); - } - - /** - * Used by {@link org.apache.cassandra.cql3.statements.CreateFunctionStatement} to create or replace a new function. - */ - public static void tryCreateFunction(UFMetaData ufMeta) throws InvalidRequestException - { - addFunction(ufMeta, false); - - if (ufMeta.invalid != null) - throw ufMeta.invalid; - } - - private static void addFunction(UFMetaData uf, boolean addIfInvalid) - { - UDFFunctionOverloads sigMap = functions.get(uf.qualifiedName); - if (sigMap == null) - functions.put(uf.qualifiedName, sigMap = new UDFFunctionOverloads()); - - sigMap.addAndInit(uf, addIfInvalid); - } - - public static UDFFunctionOverloads getFunctionSigMap(String qualifiedName) - { - return functions.get(qualifiedName); - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/cql3/udf/UDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/udf/UDFunction.java b/src/java/org/apache/cassandra/cql3/udf/UDFunction.java deleted file mode 100644 index 61e52e5..0000000 --- a/src/java/org/apache/cassandra/cql3/udf/UDFunction.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.udf; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.cassandra.config.UFMetaData; -import org.apache.cassandra.cql3.AssignementTestable; -import org.apache.cassandra.cql3.functions.Function; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.exceptions.InvalidRequestException; - -/** - * UDFunction contains the <i>invokable</i> instance of a user defined function. - * Currently (as of CASSANDRA-7395) only {@code public static} methods in a {@link public} class - * can be invoked. - * CASSANDRA-7562 will introduce Java source code UDFs and CASSANDRA-7526 will introduce JSR-223 scripting languages. - * Invocations of UDFs are routed via this class. - */ -public class UDFunction -{ - private static final Logger logger = LoggerFactory.getLogger(UDFunction.class); - - public final UFMetaData meta; - - public final Method method; - - UDFunction(UFMetaData meta) throws InvalidRequestException - { - this.meta = meta; - - Method m; - switch (meta.language) - { - case "class": - m = resolveClassMethod(); - break; - default: - throw new InvalidRequestException("Invalid UDF language " + meta.language + " for '" + meta.qualifiedName + '\''); - } - this.method = m; - } - - private Method resolveClassMethod() throws InvalidRequestException - { - Class<?> jReturnType = meta.cqlReturnType.getType().getSerializer().getType(); - Class<?> paramTypes[] = new Class[meta.cqlArgumentTypes.size()]; - for (int i = 0; i < paramTypes.length; i++) - paramTypes[i] = meta.cqlArgumentTypes.get(i).getType().getSerializer().getType(); - - String className; - String methodName; - int i = meta.body.indexOf('#'); - if (i != -1) - { - methodName = meta.body.substring(i + 1); - className = meta.body.substring(0, i); - } - else - { - methodName = meta.functionName; - className = meta.body; - } - try - { - Class<?> cls = Class.forName(className, false, Thread.currentThread().getContextClassLoader()); - - Method method = cls.getMethod(methodName, paramTypes); - - if (!Modifier.isStatic(method.getModifiers())) - throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") is not static"); - - if (!jReturnType.isAssignableFrom(method.getReturnType())) - { - throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") " + - "has incompatible return type " + method.getReturnType() + " (not assignable to " + jReturnType + ')'); - } - - return method; - } - catch (ClassNotFoundException e) - { - throw new InvalidRequestException("Class " + className + " does not exist"); - } - catch (NoSuchMethodException e) - { - throw new InvalidRequestException("Method " + className + '.' + methodName + '(' + Arrays.toString(paramTypes) + ") does not exist"); - } - } - - public Function create(List<? extends AssignementTestable> providedArgs) - { - final int argCount = providedArgs.size(); - final List<AbstractType<?>> argsType = new ArrayList<>(argCount); - final AbstractType<?> returnType = meta.cqlReturnType.getType(); - for (int i = 0; i < argCount; i++) - { - AbstractType<?> argType = meta.cqlArgumentTypes.get(i).getType(); - argsType.add(argType); - } - - return new Function() - { - public String name() - { - return meta.qualifiedName; - } - - public List<AbstractType<?>> argsType() - { - return argsType; - } - - public AbstractType<?> returnType() - { - return returnType; - } - - public ByteBuffer execute(List<ByteBuffer> parameters) throws InvalidRequestException - { - Object[] parms = new Object[argCount]; - for (int i = 0; i < parms.length; i++) - { - ByteBuffer bb = parameters.get(i); - if (bb != null) - { - AbstractType<?> argType = argsType.get(i); - parms[i] = argType.compose(bb); - } - } - - Object result; - try - { - result = method.invoke(null, parms); - @SuppressWarnings("unchecked") ByteBuffer r = result != null ? ((AbstractType) returnType).decompose(result) : null; - return r; - } - catch (InvocationTargetException e) - { - Throwable c = e.getCause(); - logger.error("Invocation of UDF {} failed", meta.qualifiedName, c); - throw new InvalidRequestException("Invocation of UDF " + meta.qualifiedName + " failed: " + c); - } - catch (IllegalAccessException e) - { - throw new InvalidRequestException("UDF " + meta.qualifiedName + " invocation failed: " + e); - } - } - - public boolean isPure() - { - return meta.deterministic; - } - }; - } -} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/db/DefsTables.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/DefsTables.java b/src/java/org/apache/cassandra/db/DefsTables.java index e8692a7..f1fe0bf 100644 --- a/src/java/org/apache/cassandra/db/DefsTables.java +++ b/src/java/org/apache/cassandra/db/DefsTables.java @@ -24,9 +24,6 @@ import java.util.*; import com.google.common.collect.Iterables; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; -import org.apache.cassandra.config.UFMetaData; -import org.apache.cassandra.cql3.udf.UDFRegistry; -import org.apache.cassandra.db.commitlog.CommitLog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +32,9 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.KSMetaData; import org.apache.cassandra.config.Schema; import org.apache.cassandra.config.UTMetaData; +import org.apache.cassandra.cql3.functions.Functions; +import org.apache.cassandra.cql3.functions.UDFunction; +import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.filter.QueryFilter; import org.apache.cassandra.db.marshal.AsciiType; @@ -393,8 +393,8 @@ public class DefsTables if (!cfFunctions.hasColumns()) continue; - for (UFMetaData uf : UFMetaData.fromSchema(new Row(entry.getKey(), cfFunctions)).values()) - addFunction(uf); + for (UDFunction udf : UDFunction.fromSchema(new Row(entry.getKey(), cfFunctions)).values()) + addFunction(udf); } for (Map.Entry<DecoratedKey, MapDifference.ValueDifference<ColumnFamily>> modifiedEntry : diff.entriesDiffering().entrySet()) @@ -405,26 +405,26 @@ public class DefsTables if (!prevCFFunctions.hasColumns()) // whole namespace was deleted and now it's re-created { - for (UFMetaData uf : UFMetaData.fromSchema(new Row(namespace, newCFFunctions)).values()) - addFunction(uf); + for (UDFunction udf : UDFunction.fromSchema(new Row(namespace, newCFFunctions)).values()) + addFunction(udf); } else if (!newCFFunctions.hasColumns()) // whole namespace is deleted { - for (UFMetaData uf : UFMetaData.fromSchema(new Row(namespace, prevCFFunctions)).values()) - dropFunction(uf); + for (UDFunction udf : UDFunction.fromSchema(new Row(namespace, prevCFFunctions)).values()) + dropFunction(udf); } else // has modifications in the functions, need to perform nested diff to determine what was really changed { - MapDifference<String, UFMetaData> functionsDiff = Maps.difference(UFMetaData.fromSchema(new Row(namespace, prevCFFunctions)), - UFMetaData.fromSchema(new Row(namespace, newCFFunctions))); + MapDifference<ByteBuffer, UDFunction> functionsDiff = Maps.difference(UDFunction.fromSchema(new Row(namespace, prevCFFunctions)), + UDFunction.fromSchema(new Row(namespace, newCFFunctions))); - for (UFMetaData function : functionsDiff.entriesOnlyOnRight().values()) - addFunction(function); + for (UDFunction udf : functionsDiff.entriesOnlyOnRight().values()) + addFunction(udf); - for (UFMetaData function : functionsDiff.entriesOnlyOnLeft().values()) - dropFunction(function); + for (UDFunction udf : functionsDiff.entriesOnlyOnLeft().values()) + dropFunction(udf); - for (MapDifference.ValueDifference<UFMetaData> tdiff : functionsDiff.entriesDiffering().values()) + for (MapDifference.ValueDifference<UDFunction> tdiff : functionsDiff.entriesDiffering().values()) updateFunction(tdiff.rightValue()); // use the most recent value } } @@ -478,14 +478,14 @@ public class DefsTables MigrationManager.instance.notifyCreateUserType(ut); } - private static void addFunction(UFMetaData uf) + private static void addFunction(UDFunction udf) { - logger.info("Loading {}", uf); + logger.info("Loading {}", udf); - UDFRegistry.migrateAddFunction(uf); + Functions.addFunction(udf); if (!StorageService.instance.isClientMode()) - MigrationManager.instance.notifyCreateFunction(uf); + MigrationManager.instance.notifyCreateFunction(udf); } private static void updateKeyspace(KSMetaData newState) @@ -530,14 +530,14 @@ public class DefsTables MigrationManager.instance.notifyUpdateUserType(ut); } - private static void updateFunction(UFMetaData uf) + private static void updateFunction(UDFunction udf) { - logger.info("Updating {}", uf); + logger.info("Updating {}", udf); - UDFRegistry.migrateUpdateFunction(uf); + Functions.replaceFunction(udf); if (!StorageService.instance.isClientMode()) - MigrationManager.instance.notifyUpdateFunction(uf); + MigrationManager.instance.notifyUpdateFunction(udf); } private static void dropKeyspace(String ksName) @@ -619,14 +619,15 @@ public class DefsTables MigrationManager.instance.notifyDropUserType(ut); } - private static void dropFunction(UFMetaData uf) + private static void dropFunction(UDFunction udf) { - logger.info("Drop {}", uf); + logger.info("Drop {}", udf); - UDFRegistry.migrateDropFunction(uf); + // TODO: this is kind of broken as this remove all overloads of the function name + Functions.removeFunction(udf.name(), udf.argTypes()); if (!StorageService.instance.isClientMode()) - MigrationManager.instance.notifyDropFunction(uf); + MigrationManager.instance.notifyDropFunction(udf); } private static KSMetaData makeNewKeyspaceDefinition(KSMetaData ksm, CFMetaData toExclude) @@ -644,4 +645,3 @@ public class DefsTables SystemKeyspace.forceBlockingFlush(cf); } } - http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/service/CassandraDaemon.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index daa135f..2153c85 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -33,7 +33,6 @@ import javax.management.StandardMBean; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.Uninterruptibles; -import org.apache.cassandra.cql3.udf.UDFRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +43,7 @@ import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; +import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Directories; import org.apache.cassandra.db.Keyspace; @@ -235,8 +235,9 @@ public class CassandraDaemon System.exit(100); } - // load keyspace descriptions. + // load keyspace && function descriptions. DatabaseDescriptor.loadSchemas(); + Functions.loadUDFFromSchema(); // clean up compaction leftovers Map<Pair<String, String>, Map<Integer, UUID>> unfinishedCompactions = SystemKeyspace.getUnfinishedCompactions(); @@ -366,9 +367,6 @@ public class CassandraDaemon if (!FBUtilities.getBroadcastAddress().equals(InetAddress.getLoopbackAddress())) waitForGossipToSettle(); - // UDF - UDFRegistry.init(); - // Thift InetAddress rpcAddr = DatabaseDescriptor.getRpcAddress(); int rpcPort = DatabaseDescriptor.getRpcPort(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/src/java/org/apache/cassandra/service/MigrationManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/MigrationManager.java b/src/java/org/apache/cassandra/service/MigrationManager.java index 6e930e6..b3277ee 100644 --- a/src/java/org/apache/cassandra/service/MigrationManager.java +++ b/src/java/org/apache/cassandra/service/MigrationManager.java @@ -29,8 +29,6 @@ import java.util.concurrent.*; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import org.apache.cassandra.config.UFMetaData; -import org.apache.cassandra.exceptions.InvalidRequestException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,10 +38,14 @@ import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.KSMetaData; import org.apache.cassandra.config.UTMetaData; import org.apache.cassandra.config.Schema; +import org.apache.cassandra.cql3.functions.FunctionName; +import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.db.*; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.exceptions.AlreadyExistsException; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.gms.*; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataOutputPlus; @@ -175,22 +177,22 @@ public class MigrationManager listener.onCreateUserType(ut.keyspace, ut.getNameAsString()); } - public void notifyCreateFunction(UFMetaData uf) + public void notifyCreateFunction(UDFunction udf) { for (IMigrationListener listener : listeners) - listener.onCreateFunction(uf.namespace, uf.functionName); + listener.onCreateFunction(udf.name().namespace, udf.name().name); } - public void notifyUpdateFunction(UFMetaData uf) + public void notifyUpdateFunction(UDFunction udf) { for (IMigrationListener listener : listeners) - listener.onUpdateFunction(uf.namespace, uf.functionName); + listener.onUpdateFunction(udf.name().namespace, udf.name().name); } - public void notifyDropFunction(UFMetaData uf) + public void notifyDropFunction(UDFunction udf) { for (IMigrationListener listener : listeners) - listener.onDropFunction(uf.namespace, uf.functionName); + listener.onDropFunction(udf.name().namespace, udf.name().name); } public void notifyUpdateKeyspace(KSMetaData ksm) @@ -372,24 +374,16 @@ public class MigrationManager announce(addSerializedKeyspace(UTMetaData.dropFromSchema(droppedType, FBUtilities.timestampMicros()), droppedType.keyspace), announceLocally); } - public static void announceFunctionDrop(String namespace, String functionName, boolean announceLocally) throws InvalidRequestException + public static void announceFunctionDrop(FunctionName fun, boolean announceLocally) throws InvalidRequestException { - Mutation mutation = UFMetaData.dropFunction(FBUtilities.timestampMicros(), namespace, functionName); - if (mutation == null) - throw new InvalidRequestException(String.format("Cannot drop non existing function '%s'.", functionName)); - - logger.info(String.format("Drop Function '%s::%s'", namespace, functionName)); - announce(mutation, announceLocally); + logger.info(String.format("Drop Function '%s'", fun)); + announce(UDFunction.dropFromSchema(FBUtilities.timestampMicros(), fun), announceLocally); } - public static void announceNewFunction(UFMetaData function, boolean announceLocally) - throws ConfigurationException + public static void announceNewFunction(UDFunction udf, boolean announceLocally) { - Mutation mutation = UFMetaData.createOrReplaceFunction(FBUtilities.timestampMicros(), function); - if (mutation == null) - throw new ConfigurationException(String.format("Function '%s' already exists.", function.qualifiedName)); - - logger.info(String.format("Create Function '%s'", function)); + Mutation mutation = udf.toSchemaUpdate(FBUtilities.timestampMicros()); + logger.info(String.format("Create Function '%s'", udf.name())); announce(mutation, announceLocally); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/test/unit/org/apache/cassandra/cql3/CQLTester.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java b/test/unit/org/apache/cassandra/cql3/CQLTester.java index cb32577..760878c 100644 --- a/test/unit/org/apache/cassandra/cql3/CQLTester.java +++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java @@ -66,6 +66,10 @@ public abstract class CQLTester private String currentTable; private final Set<String> currentTypes = new HashSet<>(); + // We don't use USE_PREPARED_VALUES in the code below so some test can foce value preparation (if the result + // is not expected to be the same without preparation) + private boolean usePrepared = USE_PREPARED_VALUES; + @BeforeClass public static void setUpClass() throws Throwable { @@ -80,6 +84,9 @@ public abstract class CQLTester @After public void afterTest() throws Throwable { + // Restore standard behavior in case it was changed + usePrepared = USE_PREPARED_VALUES; + if (currentTable == null) return; @@ -162,6 +169,16 @@ public abstract class CQLTester return currentTable; } + protected void forcePreparedValues() + { + this.usePrepared = true; + } + + protected void stopForcingPreparedValues() + { + this.usePrepared = USE_PREPARED_VALUES; + } + protected String createType(String query) { String typeName = "type_" + seqNumber.getAndIncrement(); @@ -222,7 +239,7 @@ public abstract class CQLTester query = String.format(query, KEYSPACE + "." + currentTable); UntypedResultSet rs; - if (USE_PREPARED_VALUES) + if (usePrepared) { logger.info("Executing: {} with values {}", query, formatAllValues(values)); rs = QueryProcessor.executeOnceInternal(query, transformValues(values)); http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/test/unit/org/apache/cassandra/cql3/TypeCastTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/TypeCastTest.java b/test/unit/org/apache/cassandra/cql3/TypeCastTest.java new file mode 100644 index 0000000..7b9c9a2 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/TypeCastTest.java @@ -0,0 +1,54 @@ +/* + * 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; + +import org.junit.Test; + +/** + * Type-casting is mostly using for functions and their use with functions is + * tested in UFTest. This is a few additional "sanity" tests. + */ +public class TypeCastTest extends CQLTester +{ + @Test + public void testTypeCasts() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, t text, a ascii, d double, i int)"); + + // The followings is fine + execute("UPDATE %s SET t = 'foo' WHERE k = ?", 0); + execute("UPDATE %s SET t = (ascii)'foo' WHERE k = ?", 0); + execute("UPDATE %s SET t = (text)(ascii)'foo' WHERE k = ?", 0); + execute("UPDATE %s SET a = 'foo' WHERE k = ?", 0); + execute("UPDATE %s SET a = (ascii)'foo' WHERE k = ?", 0); + + // But trying to put some explicitely type-casted text into an ascii + // column should be rejected (even though the text is actually ascci) + assertInvalid("UPDATE %s SET a = (text)'foo' WHERE k = ?", 0); + + // This is also fine because integer constants works for both integer and float types + execute("UPDATE %s SET i = 3 WHERE k = ?", 0); + execute("UPDATE %s SET i = (int)3 WHERE k = ?", 0); + execute("UPDATE %s SET d = 3 WHERE k = ?", 0); + execute("UPDATE %s SET d = (double)3 WHERE k = ?", 0); + + // But values for ints and doubles are not truly compatible (their binary representation differs) + assertInvalid("UPDATE %s SET d = (int)3 WHERE k = ?", 0); + assertInvalid("UPDATE %s SET i = (double)3 WHERE k = ?", 0); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/44fa12ec/test/unit/org/apache/cassandra/cql3/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java index ec494a6..84161b2 100644 --- a/test/unit/org/apache/cassandra/cql3/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/UFTest.java @@ -33,154 +33,158 @@ public class UFTest extends CQLTester return val != null ? (float)Math.sin(val) : null; } - public Float nonStaticMethod(Float val) - { - return new Float(1.0); - } - - private static Float privateMethod(Float val) - { - return new Float(1.0); - } - - @Test - public void ddlCreateFunction() throws Throwable - { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - - execute("create function foo::cf ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - execute("drop function foo::cf"); - } - - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionFail() throws Throwable - { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - - execute("create function foo::cff ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - execute("create function foo::cff ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - } - - - @Test - public void ddlCreateIfNotExistsFunction() throws Throwable + public static Double badSin(Double val) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - - execute("create function if not exists foo::cfine ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - execute("drop function foo::cfine"); + return 42.0; } - - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionBadClass() throws Throwable + public static String badSinBadReturn(Double val) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - execute("create function foo::cff ( input double ) returns double using 'org.apache.cassandra.cql3.DoesNotExist#doesnotexist'"); + return "foo"; } - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionBadMethod() throws Throwable + public Float nonStaticMethod(Float val) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - execute("create function foo::cff ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#doesnotexist'"); + return new Float(1.0); } - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionBadArgType() throws Throwable + private static Float privateMethod(Float val) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - execute("create function foo::cff ( input text ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); + return new Float(1.0); } - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionBadReturnType() throws Throwable + public static String repeat(String v, Integer n) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - execute("create function foo::cff ( input double ) returns text using 'org.apache.cassandra.cql3.UFTest#sin'"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n; i++) + sb.append(v); + return sb.toString(); } - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionNonStaticMethod() throws Throwable + public static String overloaded(String v) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - execute("create function foo::cff ( input float ) returns float using 'org.apache.cassandra.cql3.UFTest#nonStaticMethod'"); + return "f1"; } - @Test(expected = InvalidRequestException.class) - public void ddlCreateFunctionNonPublicMethod() throws Throwable + public static String overloaded(Integer v) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - execute("create function foo::cff ( input float ) returns float using 'org.apache.cassandra.cql3.UFTest#privateMethod'"); + return "f2"; } - @Test(expected = InvalidRequestException.class) - public void ddlCreateIfNotExistsFunctionFail() throws Throwable + public static String overloaded(String v1, String v2) { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester - - execute("create function if not exists foo::cfinef ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - execute("create function if not exists foo::cfinef ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); + return "f3"; } @Test - public void ddlCreateOrReplaceFunction() throws Throwable - { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester + public void testFunctionCreationAndDrop() throws Throwable + { + createTable("CREATE TABLE %s (key int PRIMARY KEY, d double)"); + + execute("INSERT INTO %s(key, d) VALUES (?, ?)", 1, 1d); + execute("INSERT INTO %s(key, d) VALUES (?, ?)", 2, 2d); + execute("INSERT INTO %s(key, d) VALUES (?, ?)", 3, 3d); + + // creation with a bad class + assertInvalid("CREATE FUNCTION foo::sin1 ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.DoesNotExist#doesnotexist'"); + // and a good class but inexisting method + assertInvalid("CREATE FUNCTION foo::sin2 ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#doesnotexist'"); + // with a non static method + assertInvalid("CREATE FUNCTION foo::sin3 ( input float ) RETURNS float USING 'org.apache.cassandra.cql3.UFTest#nonStaticMethod'"); + // with a non public method + assertInvalid("CREATE FUNCTION foo::sin4 ( input float ) RETURNS float USING 'org.apache.cassandra.cql3.UFTest#privateMethod'"); + + // creation with bad argument types + assertInvalid("CREATE FUNCTION foo::sin5 ( input text ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#sin'"); + // with bad return types + assertInvalid("CREATE FUNCTION foo::sin6 ( input double ) RETURNS text USING 'org.apache.cassandra.cql3.UFTest#sin'"); + + // simple creation + execute("CREATE FUNCTION foo::sin ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#sin'"); + // check we can't recreate the same function + assertInvalid("CREATE FUNCTION foo::sin ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#sin'"); + // but that it doesn't complay with "IF NOT EXISTS" + execute("CREATE FUNCTION IF NOT EXISTS foo::sin ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#sin'"); + + // Validate that it works as expected + assertRows(execute("SELECT key, foo::sin(d) FROM %s"), + row(1, Math.sin(1d)), + row(2, Math.sin(2d)), + row(3, Math.sin(3d)) + ); - execute("create function foo::corf ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - execute("create or replace function foo::corf ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest#sin'"); - } + // Replace the method with incompatible return type + assertInvalid("CREATE OR REPLACE FUNCTION foo::sin ( input double ) RETURNS text USING 'org.apache.cassandra.cql3.UFTest#badSinBadReturn'"); + // proper replacement + execute("CREATE OR REPLACE FUNCTION foo::sin ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#badSin'"); - @Test(expected = InvalidRequestException.class) - public void ddlDropNonExistingFunction() throws Throwable - { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester + // Validate the method as been replaced + assertRows(execute("SELECT key, foo::sin(d) FROM %s"), + row(1, 42.0), + row(2, 42.0), + row(3, 42.0) + ); - execute("drop function foo::dnef"); - } + // same function but without namespace + execute("CREATE FUNCTION sin ( input double ) RETURNS double USING 'org.apache.cassandra.cql3.UFTest#sin'"); + assertRows(execute("SELECT key, sin(d) FROM %s"), + row(1, Math.sin(1d)), + row(2, Math.sin(2d)), + row(3, Math.sin(3d)) + ); - @Test - public void ddlDropIfExistsNonExistingFunction() throws Throwable - { - createTable("CREATE TABLE %s (key int primary key, val double)"); // not used, but required by CQLTester + // Drop with and without namespace + execute("DROP FUNCTION foo::sin"); + execute("DROP FUNCTION sin"); - execute("drop function if exists foo::dienef"); + // Drop unexisting function + assertInvalid("DROP FUNCTION foo::sin"); + // but don't complain with "IF EXISTS" + execute("DROP FUNCTION IF EXISTS foo::sin"); } @Test - public void namespaceUserFunctions() throws Throwable + public void testFunctionExecution() throws Throwable { - createTable("CREATE TABLE %s (key int primary key, val double)"); + createTable("CREATE TABLE %s (v text PRIMARY KEY)"); - execute("create or replace function math::sin ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest'"); + execute("INSERT INTO %s(v) VALUES (?)", "aaa"); - execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); - execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); - execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); + execute("CREATE FUNCTION repeat (v text, n int) RETURNS text USING 'org.apache.cassandra.cql3.UFTest#repeat'"); - assertRows(execute("SELECT key, val, math::sin(val) FROM %s"), - row(1, 1d, Math.sin(1d)), - row(2, 2d, Math.sin(2d)), - row(3, 3d, Math.sin(3d)) - ); + assertRows(execute("SELECT v FROM %s WHERE v=repeat(?, ?)", "a", 3), row("aaa")); + assertEmpty(execute("SELECT v FROM %s WHERE v=repeat(?, ?)", "a", 2)); } @Test - public void nonNamespaceUserFunctions() throws Throwable + public void testFunctionOverloading() throws Throwable { - createTable("CREATE TABLE %s (key int primary key, val double)"); + createTable("CREATE TABLE %s (k text PRIMARY KEY, v int)"); - execute("create or replace function sin ( input double ) returns double using 'org.apache.cassandra.cql3.UFTest'"); + execute("INSERT INTO %s(k, v) VALUES (?, ?)", "f2", 1); - execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d); - execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d); - execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d); + execute("CREATE FUNCTION overloaded(v varchar) RETURNS text USING 'org.apache.cassandra.cql3.UFTest'"); + execute("CREATE OR REPLACE FUNCTION overloaded(i int) RETURNS text USING 'org.apache.cassandra.cql3.UFTest'"); + execute("CREATE OR REPLACE FUNCTION overloaded(v1 text, v2 text) RETURNS text USING 'org.apache.cassandra.cql3.UFTest'"); + execute("CREATE OR REPLACE FUNCTION overloaded(v ascii) RETURNS text USING 'org.apache.cassandra.cql3.UFTest'"); - assertRows(execute("SELECT key, val, sin(val) FROM %s"), - row(1, 1d, Math.sin(1d)), - row(2, 2d, Math.sin(2d)), - row(3, 3d, Math.sin(3d)) + // text == varchar, so this should be considered as a duplicate + assertInvalid("CREATE FUNCTION overloaded(v varchar) RETURNS text USING 'org.apache.cassandra.cql3.UFTest'"); + + assertRows(execute("SELECT overloaded(k), overloaded(v), overloaded(k, k) FROM %s"), + row("f1", "f2", "f3") ); + + forcePreparedValues(); + // This shouldn't work if we use preparation since there no way to know which overload to use + assertInvalid("SELECT v FROM %s WHERE k = overloaded(?)", "foo"); + stopForcingPreparedValues(); + + // but those should since we specifically cast + assertEmpty(execute("SELECT v FROM %s WHERE k = overloaded((text)?)", "foo")); + assertRows(execute("SELECT v FROM %s WHERE k = overloaded((int)?)", 3), row(1)); + assertEmpty(execute("SELECT v FROM %s WHERE k = overloaded((ascii)?)", "foo")); + // And since varchar == text, this should work too + assertEmpty(execute("SELECT v FROM %s WHERE k = overloaded((varchar)?)", "foo")); } }