From 35668435090eb47cf8c5e704243510b6cee35a7b Mon Sep 17 00:00:00 2001 From: Aleksey Yeschenko Date: Tue, 30 Jun 2015 15:02:10 +0300 Subject: [PATCH] Improve handling of UDA and UDF metadata patch by Aleksey Yeschenko; reviewed by Robert Stupp for CASSANDRA-9665 --- CHANGES.txt | 5 +- .../cassandra/auth/FunctionResource.java | 10 +- .../apache/cassandra/config/KSMetaData.java | 43 +++- .../org/apache/cassandra/config/Schema.java | 117 +++++++--- .../apache/cassandra/cql3/QueryProcessor.java | 59 +++-- .../cql3/functions/AggregateFcts.java | 49 +++- .../cql3/functions/BytesConversionFcts.java | 26 ++- .../cql3/functions/FunctionCall.java | 6 +- .../{Functions.java => FunctionResolver.java} | 221 ++---------------- .../cassandra/cql3/functions/TimeFcts.java | 18 ++ .../cassandra/cql3/functions/UDAggregate.java | 23 +- .../cassandra/cql3/functions/UDFunction.java | 5 +- .../cassandra/cql3/functions/UuidFcts.java | 8 +- .../cassandra/cql3/selection/Selectable.java | 2 +- .../statements/CreateAggregateStatement.java | 10 +- .../statements/CreateFunctionStatement.java | 10 +- .../statements/DropAggregateStatement.java | 12 +- .../statements/DropFunctionStatement.java | 24 +- .../cql3/statements/DropTypeStatement.java | 27 +-- .../apache/cassandra/db/SystemKeyspace.java | 27 ++- .../apache/cassandra/schema/Functions.java | 216 +++++++++++++++++ .../cassandra/schema/LegacySchemaTables.java | 58 +++-- .../cassandra/service/MigrationManager.java | 6 +- .../cql3/validation/entities/UFAuthTest.java | 6 +- .../cql3/validation/entities/UFTest.java | 41 ++-- .../operations/AggregationTest.java | 15 +- 26 files changed, 621 insertions(+), 423 deletions(-) rename src/java/org/apache/cassandra/cql3/functions/{Functions.java => FunctionResolver.java} (53%) create mode 100644 src/java/org/apache/cassandra/schema/Functions.java diff --git a/CHANGES.txt b/CHANGES.txt index 206b15d790ae..aa3bdb4524c1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,4 @@ -3.0: - * Improve log output from unit tests (CASSANDRA-9528) +3.0 * Add algorithmic token allocation (CASSANDRA-7032) * Add nodetool command to replay batchlog (CASSANDRA-9547) * Make file buffer cache independent of paths being read (CASSANDRA-8897) @@ -39,7 +38,7 @@ Merged from 2.1: * Fix bug in cardinality check when compacting (CASSANDRA-9580) * Fix memory leak in Ref due to ConcurrentLinkedQueue.remove() behaviour (CASSANDRA-9549) * Make rebuild only run one at a time (CASSANDRA-9119) -Merged from 2.0 +Merged from 2.0: * Improve trace messages for RR (CASSANDRA-9479) * Fix suboptimal secondary index selection when restricted clustering column is also indexed (CASSANDRA-9631) diff --git a/src/java/org/apache/cassandra/auth/FunctionResource.java b/src/java/org/apache/cassandra/auth/FunctionResource.java index 14215411226a..2c5b8a10785a 100644 --- a/src/java/org/apache/cassandra/auth/FunctionResource.java +++ b/src/java/org/apache/cassandra/auth/FunctionResource.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; import com.google.common.base.Joiner; @@ -31,7 +32,6 @@ import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.FunctionName; -import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.TypeParser; @@ -244,7 +244,7 @@ public boolean exists() case KEYSPACE: return Schema.instance.getKeyspaces().contains(keyspace); case FUNCTION: - return Functions.find(getFunctionName(), argTypes) != null; + return Schema.instance.findFunction(getFunctionName(), argTypes).isPresent(); } throw new AssertionError(); } @@ -258,9 +258,9 @@ public Set applicablePermissions() return COLLECTION_LEVEL_PERMISSIONS; case FUNCTION: { - Function function = Functions.find(getFunctionName(), argTypes); - assert function != null : "Unable to find function object for resource " + toString(); - return function.isAggregate() ? AGGREGATE_FUNCTION_PERMISSIONS : SCALAR_FUNCTION_PERMISSIONS; + Optional function = Schema.instance.findFunction(getFunctionName(), argTypes); + assert function.isPresent() : "Unable to find function object for resource " + toString(); + return function.get().isAggregate() ? AGGREGATE_FUNCTION_PERMISSIONS : SCALAR_FUNCTION_PERMISSIONS; } } throw new AssertionError(); diff --git a/src/java/org/apache/cassandra/config/KSMetaData.java b/src/java/org/apache/cassandra/config/KSMetaData.java index 1537aae80366..a325a8048f5a 100644 --- a/src/java/org/apache/cassandra/config/KSMetaData.java +++ b/src/java/org/apache/cassandra/config/KSMetaData.java @@ -23,6 +23,7 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.locator.*; +import org.apache.cassandra.schema.Functions; import org.apache.cassandra.service.StorageService; public final class KSMetaData @@ -34,13 +35,14 @@ public final class KSMetaData public final boolean durableWrites; public final UTMetaData userTypes; + public final Functions functions; public KSMetaData(String name, Class strategyClass, Map strategyOptions, boolean durableWrites) { - this(name, strategyClass, strategyOptions, durableWrites, Collections.emptyList(), new UTMetaData()); + this(name, strategyClass, strategyOptions, durableWrites, Collections.emptyList(), new UTMetaData(), Functions.none()); } public KSMetaData(String name, @@ -49,7 +51,17 @@ public KSMetaData(String name, boolean durableWrites, Iterable cfDefs) { - this(name, strategyClass, strategyOptions, durableWrites, cfDefs, new UTMetaData()); + this(name, strategyClass, strategyOptions, durableWrites, cfDefs, new UTMetaData(), Functions.none()); + } + + public KSMetaData(String name, + Class strategyClass, + Map strategyOptions, + boolean durableWrites, + Iterable cfDefs, + Functions functions) + { + this(name, strategyClass, strategyOptions, durableWrites, cfDefs, new UTMetaData(), functions); } private KSMetaData(String name, @@ -57,7 +69,8 @@ private KSMetaData(String name, Map strategyOptions, boolean durableWrites, Iterable cfDefs, - UTMetaData userTypes) + UTMetaData userTypes, + Functions functions) { this.name = name; this.strategyClass = strategyClass == null ? NetworkTopologyStrategy.class : strategyClass; @@ -68,6 +81,7 @@ private KSMetaData(String name, this.cfMetaData = Collections.unmodifiableMap(cfmap); this.durableWrites = durableWrites; this.userTypes = userTypes; + this.functions = functions; } // For new user created keyspaces (through CQL) @@ -82,7 +96,7 @@ public static KSMetaData newKeyspace(String name, String strategyName, Map strategyClass, Map options, boolean durablesWrites, Iterable cfDefs) { - return new KSMetaData(name, strategyClass, options, durablesWrites, cfDefs, new UTMetaData()); + return new KSMetaData(name, strategyClass, options, durablesWrites, cfDefs, new UTMetaData(), Functions.none()); } public KSMetaData cloneWithTableRemoved(CFMetaData table) @@ -91,7 +105,7 @@ public KSMetaData cloneWithTableRemoved(CFMetaData table) List newTables = new ArrayList<>(cfMetaData().values()); newTables.remove(table); assert newTables.size() == cfMetaData().size() - 1; - return cloneWith(newTables, userTypes); + return cloneWith(newTables, userTypes, functions); } public KSMetaData cloneWithTableAdded(CFMetaData table) @@ -100,12 +114,17 @@ public KSMetaData cloneWithTableAdded(CFMetaData table) List newTables = new ArrayList<>(cfMetaData().values()); newTables.add(table); assert newTables.size() == cfMetaData().size() + 1; - return cloneWith(newTables, userTypes); + return cloneWith(newTables, userTypes, functions); } - public KSMetaData cloneWith(Iterable tables, UTMetaData types) + public KSMetaData cloneWith(Iterable tables, UTMetaData types, Functions functions) { - return new KSMetaData(name, strategyClass, strategyOptions, durableWrites, tables, types); + return new KSMetaData(name, strategyClass, strategyOptions, durableWrites, tables, types, functions); + } + + public KSMetaData cloneWith(Functions functions) + { + return new KSMetaData(name, strategyClass, strategyOptions, durableWrites, cfMetaData.values(), userTypes, functions); } public static KSMetaData testMetadata(String name, Class strategyClass, Map strategyOptions, CFMetaData... cfDefs) @@ -121,7 +140,7 @@ public static KSMetaData testMetadataNotDurable(String name, Class getKeyspaces() return keyspaces.keySet(); } - /** - * @return collection of the metadata about all keyspaces registered in the system (system and non-system) - */ - public Collection getKeyspaceDefinitions() - { - return keyspaces.values(); - } - /** * Update (or insert) new keyspace definition * @@ -362,6 +356,45 @@ public void purge(CFMetaData cfm) cfm.markPurged(); } + /* Function helpers */ + + /** + * Get all function overloads with the specified name + * + * @param name fully qualified function name + * @return an empty list if the keyspace or the function name are not found; + * a non-empty collection of {@link Function} otherwise + */ + public Collection getFunctions(FunctionName name) + { + if (!name.hasKeyspace()) + throw new IllegalArgumentException(String.format("Function name must be fully quallified: got %s", name)); + + KSMetaData ksm = getKSMetaData(name.keyspace); + return ksm == null + ? Collections.emptyList() + : ksm.functions.get(name); + } + + /** + * Find the function with the specified name + * + * @param name fully qualified function name + * @param argTypes function argument types + * @return an empty {@link Optional} if the keyspace or the function name are not found; + * a non-empty optional of {@link Function} otherwise + */ + public Optional findFunction(FunctionName name, List> argTypes) + { + if (!name.hasKeyspace()) + throw new IllegalArgumentException(String.format("Function name must be fully quallified: got %s", name)); + + KSMetaData ksm = getKSMetaData(name.keyspace); + return ksm == null + ? Optional.empty() + : ksm.functions.find(name, argTypes); + } + /* Version control */ /** @@ -420,7 +453,7 @@ public void updateKeyspace(String ksName) { KSMetaData oldKsm = getKSMetaData(ksName); assert oldKsm != null; - KSMetaData newKsm = LegacySchemaTables.createKeyspaceFromName(ksName).cloneWith(oldKsm.cfMetaData().values(), oldKsm.userTypes); + KSMetaData newKsm = LegacySchemaTables.createKeyspaceFromName(ksName).cloneWith(oldKsm.cfMetaData().values(), oldKsm.userTypes, oldKsm.functions); setKeyspaceDefinition(newKsm); @@ -552,57 +585,67 @@ public void dropType(UserType ut) public void addFunction(UDFunction udf) { - logger.info("Loading {}", udf); - - Functions.addOrReplaceFunction(udf); - + addFunctionInternal(udf); MigrationManager.instance.notifyCreateFunction(udf); } public void updateFunction(UDFunction udf) { - logger.info("Updating {}", udf); - - Functions.addOrReplaceFunction(udf); - + updateFunctionInternal(udf); MigrationManager.instance.notifyUpdateFunction(udf); } public void dropFunction(UDFunction udf) { - logger.info("Drop {}", udf); - - // TODO: this is kind of broken as this remove all overloads of the function name - Functions.removeFunction(udf.name(), udf.argTypes()); - + dropFunctionInternal(udf); MigrationManager.instance.notifyDropFunction(udf); } - public void addAggregate(UDAggregate udf) + public void addAggregate(UDAggregate uda) { - logger.info("Loading {}", udf); - - Functions.addOrReplaceFunction(udf); + addFunctionInternal(uda); + MigrationManager.instance.notifyCreateAggregate(uda); + } - MigrationManager.instance.notifyCreateAggregate(udf); + public void updateAggregate(UDAggregate uda) + { + updateFunctionInternal(uda); + MigrationManager.instance.notifyUpdateAggregate(uda); } - public void updateAggregate(UDAggregate udf) + public void dropAggregate(UDAggregate uda) { - logger.info("Updating {}", udf); + dropFunctionInternal(uda); + MigrationManager.instance.notifyDropAggregate(uda); + } - Functions.addOrReplaceFunction(udf); + private void addFunctionInternal(Function fun) + { + assert fun instanceof UDFunction || fun instanceof UDAggregate; - MigrationManager.instance.notifyUpdateAggregate(udf); + KSMetaData oldKsm = getKSMetaData(fun.name().keyspace); + assert oldKsm != null; + KSMetaData newKsm = oldKsm.cloneWith(oldKsm.functions.with(fun)); + setKeyspaceDefinition(newKsm); } - public void dropAggregate(UDAggregate udf) + private void updateFunctionInternal(Function fun) { - logger.info("Drop {}", udf); + assert fun instanceof UDFunction || fun instanceof UDAggregate; + + KSMetaData oldKsm = getKSMetaData(fun.name().keyspace); + assert oldKsm != null; + KSMetaData newKsm = oldKsm.cloneWith(oldKsm.functions.without(fun.name(), fun.argTypes()).with(fun)); + setKeyspaceDefinition(newKsm); + } - // TODO: this is kind of broken as this remove all overloads of the function name - Functions.removeFunction(udf.name(), udf.argTypes()); + private void dropFunctionInternal(Function fun) + { + assert fun instanceof UDFunction || fun instanceof UDAggregate; - MigrationManager.instance.notifyDropAggregate(udf); + KSMetaData oldKsm = getKSMetaData(fun.name().keyspace); + assert oldKsm != null; + KSMetaData newKsm = oldKsm.cloneWith(oldKsm.functions.without(fun.name(), fun.argTypes())); + setKeyspaceDefinition(newKsm); } } diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index b36920238cee..8db8554158a9 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -36,9 +36,9 @@ import com.googlecode.concurrentlinkedhashmap.EvictionListener; import org.antlr.runtime.*; import org.apache.cassandra.concurrent.ScheduledExecutors; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.FunctionName; -import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.cql3.statements.*; import org.apache.cassandra.db.*; import org.apache.cassandra.db.rows.RowIterator; @@ -602,24 +602,26 @@ else if (statement instanceof BatchStatement) return ksName.equals(statementKsName) && (cfName == null || cfName.equals(statementCfName)); } - public void onCreateFunction(String ksName, String functionName, List> argTypes) { - if (Functions.getOverloadCount(new FunctionName(ksName, functionName)) > 1) + public void onCreateFunction(String ksName, String functionName, List> argTypes) + { + onCreateFunctionInternal(ksName, functionName, argTypes); + } + + public void onCreateAggregate(String ksName, String aggregateName, List> argTypes) + { + onCreateFunctionInternal(ksName, aggregateName, argTypes); + } + + private static void onCreateFunctionInternal(String ksName, String functionName, List> argTypes) + { + // in case there are other overloads, we have to remove all overloads since argument type + // matching may change (due to type casting) + if (Schema.instance.getKSMetaData(ksName).functions.get(new FunctionName(ksName, functionName)).size() > 1) { - // in case there are other overloads, we have to remove all overloads since argument type - // matching may change (due to type casting) removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName); removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName); } } - public void onCreateAggregate(String ksName, String aggregateName, List> argTypes) { - if (Functions.getOverloadCount(new FunctionName(ksName, aggregateName)) > 1) - { - // in case there are other overloads, we have to remove all overloads since argument type - // matching may change (due to type casting) - removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, aggregateName); - removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, aggregateName); - } - } public void onUpdateColumnFamily(String ksName, String cfName, boolean columnsDidChange) { @@ -642,35 +644,26 @@ public void onDropColumnFamily(String ksName, String cfName) public void onDropFunction(String ksName, String functionName, List> argTypes) { - removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName); - removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName); + onDropFunctionInternal(ksName, functionName, argTypes); } public void onDropAggregate(String ksName, String aggregateName, List> argTypes) { - removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, aggregateName); - removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, aggregateName); + onDropFunctionInternal(ksName, aggregateName, argTypes); + } + + private static void onDropFunctionInternal(String ksName, String functionName, List> argTypes) + { + removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName); + removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName); } private static void removeInvalidPreparedStatementsForFunction(Iterator statements, final String ksName, final String functionName) { - final Predicate matchesFunction = new Predicate() - { - public boolean apply(Function f) - { - return ksName.equals(f.name().keyspace) && functionName.equals(f.name().name); - } - }; - - Iterators.removeIf(statements, new Predicate() - { - public boolean apply(ParsedStatement.Prepared statement) - { - return Iterables.any(statement.statement.getFunctions(), matchesFunction); - } - }); + Predicate matchesFunction = f -> ksName.equals(f.name().keyspace) && functionName.equals(f.name().name); + Iterators.removeIf(statements, statement -> Iterables.any(statement.statement.getFunctions(), matchesFunction)); } } } diff --git a/src/java/org/apache/cassandra/cql3/functions/AggregateFcts.java b/src/java/org/apache/cassandra/cql3/functions/AggregateFcts.java index 865dfbf267c0..8c36864cba60 100644 --- a/src/java/org/apache/cassandra/cql3/functions/AggregateFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/AggregateFcts.java @@ -20,21 +20,54 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import org.apache.cassandra.db.marshal.AbstractType; -import org.apache.cassandra.db.marshal.DecimalType; -import org.apache.cassandra.db.marshal.DoubleType; -import org.apache.cassandra.db.marshal.FloatType; -import org.apache.cassandra.db.marshal.Int32Type; -import org.apache.cassandra.db.marshal.IntegerType; -import org.apache.cassandra.db.marshal.LongType; +import org.apache.cassandra.cql3.CQL3Type; +import org.apache.cassandra.db.marshal.*; /** * Factory methods for aggregate functions. */ public abstract class AggregateFcts { + public static Collection all() + { + Collection functions = new ArrayList<>(); + + functions.add(countRowsFunction); + + // sum for primitives + functions.add(sumFunctionForInt32); + functions.add(sumFunctionForLong); + functions.add(sumFunctionForFloat); + functions.add(sumFunctionForDouble); + functions.add(sumFunctionForDecimal); + functions.add(sumFunctionForVarint); + + // avg for primitives + functions.add(avgFunctionForInt32); + functions.add(avgFunctionForLong); + functions.add(avgFunctionForFloat); + functions.add(avgFunctionForDouble); + functions.add(avgFunctionForDecimal); + functions.add(avgFunctionForVarint); + + // count, max, and min for all standard types + for (CQL3Type type : CQL3Type.Native.values()) + { + if (type != CQL3Type.Native.VARCHAR) // varchar and text both mapping to UTF8Type + { + functions.add(AggregateFcts.makeCountFunction(type.getType())); + functions.add(AggregateFcts.makeMaxFunction(type.getType())); + functions.add(AggregateFcts.makeMinFunction(type.getType())); + } + } + + return functions; + } + /** * The function used to count the number of rows of a result set. This function is called when COUNT(*) or COUNT(1) * is specified. @@ -55,7 +88,7 @@ public void reset() public ByteBuffer compute(int protocolVersion) { - return ((LongType) returnType()).decompose(Long.valueOf(count)); + return ((LongType) returnType()).decompose(count); } public void addInput(int protocolVersion, List values) diff --git a/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java b/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java index ddb33fc09354..d9c6a529288e 100644 --- a/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/BytesConversionFcts.java @@ -18,8 +18,11 @@ package org.apache.cassandra.cql3.functions; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.BytesType; import org.apache.cassandra.db.marshal.UTF8Type; @@ -29,6 +32,27 @@ public abstract class BytesConversionFcts { + public static Collection all() + { + Collection functions = new ArrayList<>(); + + // 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. + for (CQL3Type type : CQL3Type.Native.values()) + { + if (type != CQL3Type.Native.VARCHAR && type != CQL3Type.Native.BLOB) + { + functions.add(makeToBlobFunction(type.getType())); + functions.add(makeFromBlobFunction(type.getType())); + } + } + + functions.add(VarcharAsBlobFct); + functions.add(BlobAsVarcharFct); + + return functions; + } + // Most of the XAsBlob and blobAsX functions are basically no-op since everything is // bytes internally. They only "trick" the type system. public static Function makeToBlobFunction(AbstractType fromType) @@ -74,7 +98,7 @@ public ByteBuffer execute(int protocolVersion, List parameters) } }; - public static final Function BlobAsVarcharFact = new NativeScalarFunction("blobasvarchar", UTF8Type.instance, BytesType.instance) + public static final Function BlobAsVarcharFct = new NativeScalarFunction("blobasvarchar", UTF8Type.instance, BytesType.instance) { public ByteBuffer execute(int protocolVersion, List parameters) { diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index 4f53c9847473..b25d079e4f7f 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -123,7 +123,7 @@ public Raw(FunctionName name, List terms) public Term prepare(String keyspace, ColumnSpecification receiver) throws InvalidRequestException { - Function fun = Functions.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); + Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); if (fun == null) throw new InvalidRequestException(String.format("Unknown function %s called", name)); if (fun.isAggregate()) @@ -145,7 +145,7 @@ public Term prepare(String keyspace, ColumnSpecification receiver) throws Invali List parameters = new ArrayList<>(terms.size()); for (int i = 0; i < terms.size(); i++) { - Term t = terms.get(i).prepare(keyspace, Functions.makeArgSpec(receiver.ksName, receiver.cfName, scalarFun, i)); + Term t = terms.get(i).prepare(keyspace, FunctionResolver.makeArgSpec(receiver.ksName, receiver.cfName, scalarFun, i)); parameters.add(t); } @@ -160,7 +160,7 @@ public AssignmentTestable.TestResult testAssignment(String keyspace, ColumnSpeci // later with a more helpful error message that if we were to return false here. try { - Function fun = Functions.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); + Function fun = FunctionResolver.get(keyspace, name, terms, receiver.ksName, receiver.cfName, receiver.type); // Because fromJson() can return whatever type the receiver is, we'll always get EXACT_MATCH. To // handle potentially ambiguous function calls with fromJson() as an argument, always return diff --git a/src/java/org/apache/cassandra/cql3/functions/Functions.java b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java similarity index 53% rename from src/java/org/apache/cassandra/cql3/functions/Functions.java rename to src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java index 018c35cfcfb6..be2daaec03bf 100644 --- a/src/java/org/apache/cassandra/cql3/functions/Functions.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionResolver.java @@ -19,102 +19,26 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CopyOnWriteArrayList; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.*; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.service.MigrationListener; -import org.apache.cassandra.service.MigrationManager; -public abstract class Functions +import static java.util.stream.Collectors.joining; + +public final class FunctionResolver { + private FunctionResolver() + { + } + // 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> 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.sumFunctionForInt32); - declare(AggregateFcts.sumFunctionForLong); - declare(AggregateFcts.sumFunctionForFloat); - declare(AggregateFcts.sumFunctionForDouble); - declare(AggregateFcts.sumFunctionForDecimal); - declare(AggregateFcts.sumFunctionForVarint); - declare(AggregateFcts.avgFunctionForInt32); - declare(AggregateFcts.avgFunctionForLong); - declare(AggregateFcts.avgFunctionForFloat); - declare(AggregateFcts.avgFunctionForDouble); - declare(AggregateFcts.avgFunctionForVarint); - declare(AggregateFcts.avgFunctionForDecimal); - - MigrationManager.instance.register(new FunctionsMigrationListener()); - } - - private static void declare(Function fun) - { - synchronized (declared) - { - List functions = declared.get(fun.name()); - if (functions == null) - { - functions = new CopyOnWriteArrayList<>(); - List 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, @@ -123,11 +47,6 @@ public static ColumnSpecification makeArgSpec(String receiverKs, String receiver 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 @@ -163,19 +82,21 @@ public static Function get(String keyspace, return FromJsonFct.getInstance(receiverType); } - List candidates; + Collection candidates; if (!name.hasKeyspace()) { // function name not fully qualified candidates = new ArrayList<>(); // add 'SYSTEM' (native) candidates - candidates.addAll(find(name.asNativeFunction())); + candidates.addAll(Schema.instance.getFunctions(name.asNativeFunction())); // add 'current keyspace' candidates - candidates.addAll(find(new FunctionName(keyspace, name.name))); + candidates.addAll(Schema.instance.getFunctions(new FunctionName(keyspace, name.name))); } else + { // function name is fully qualified (keyspace + name) - candidates = find(name); + candidates = Schema.instance.getFunctions(name); + } if (candidates.isEmpty()) return null; @@ -183,7 +104,7 @@ public static Function get(String keyspace, // Fast path if there is only one choice if (candidates.size() == 1) { - Function fun = candidates.get(0); + Function fun = candidates.iterator().next(); validateTypes(keyspace, fun, providedArgs, receiverKs, receiverCf); return fun; } @@ -207,32 +128,15 @@ public static Function get(String keyspace, 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))); + name, format(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))); + name, format(compatibles))); return compatibles.get(0); } - public static List find(FunctionName name) - { - List functions = declared.get(name); - return functions != null ? functions : Collections.emptyList(); - } - - public static Function find(FunctionName name, List> 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, @@ -290,97 +194,8 @@ private static AssignmentTestable.TestResult matchAguments(String keyspace, return res; } - private static String toString(List 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> argTypes) - { - assert name.hasKeyspace() : "function name " + name + " not fully qualified"; - synchronized (declared) - { - List 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 getReferencesTo(Function old) + private static String format(Collection funs) { - List references = new ArrayList<>(); - for (List functions : declared.values()) - for (Function function : functions) - if (function.hasReferenceTo(old)) - references.add(function); - return references; - } - - public static Collection all() - { - List all = new ArrayList<>(); - for (List 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> t1, List> 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; - } - - 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); - } + return funs.stream().map(Function::toString).collect(joining(", ")); } } diff --git a/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java b/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java index a4623cd3fd7a..93d6d3bdf45b 100644 --- a/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/TimeFcts.java @@ -18,9 +18,11 @@ package org.apache.cassandra.cql3.functions; import java.nio.ByteBuffer; +import java.util.Collection; import java.util.Date; import java.util.List; +import com.google.common.collect.ImmutableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +35,22 @@ public abstract class TimeFcts { public static Logger logger = LoggerFactory.getLogger(TimeFcts.class); + public static Collection all() + { + return ImmutableList.of(nowFct, + minTimeuuidFct, + maxTimeuuidFct, + dateOfFct, + unixTimestampOfFct, + timeUuidtoDate, + timeUuidToTimestamp, + timeUuidToUnixTimestamp, + timestampToUnixTimestamp, + timestampToDate, + dateToUnixTimestamp, + dateToTimestamp); + } + public static final Function nowFct = new NativeScalarFunction("now", TimeUUIDType.instance) { public ByteBuffer execute(int protocolVersion, List parameters) diff --git a/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java b/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java index f153aed95e90..aeee3ecd5f20 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java @@ -25,8 +25,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.schema.Functions; /** * Base class for user-defined-aggregates. @@ -181,14 +183,19 @@ public void reset() private static ScalarFunction resolveScalar(FunctionName aName, FunctionName fName, List> argTypes) throws InvalidRequestException { - Function func = Functions.find(fName, argTypes); - if (func == null) + Optional fun = Schema.instance.findFunction(fName, argTypes); + if (!fun.isPresent()) throw new InvalidRequestException(String.format("Referenced state function '%s %s' for aggregate '%s' does not exist", - fName, Arrays.toString(UDHelper.driverTypes(argTypes)), aName)); - if (!(func instanceof ScalarFunction)) + fName, + Arrays.toString(UDHelper.driverTypes(argTypes)), + aName)); + + if (!(fun.get() instanceof ScalarFunction)) throw new InvalidRequestException(String.format("Referenced state function '%s %s' for aggregate '%s' is not a scalar function", - fName, Arrays.toString(UDHelper.driverTypes(argTypes)), aName)); - return (ScalarFunction) func; + fName, + Arrays.toString(UDHelper.driverTypes(argTypes)), + aName)); + return (ScalarFunction) fun.get(); } @Override @@ -199,8 +206,8 @@ public boolean equals(Object o) UDAggregate that = (UDAggregate) o; return Objects.equal(name, that.name) - && Functions.typeEquals(argTypes, that.argTypes) - && Functions.typeEquals(returnType, that.returnType) + && Functions.typesMatch(argTypes, that.argTypes) + && Functions.typesMatch(returnType, that.returnType) && Objects.equal(stateFunction, that.stateFunction) && Objects.equal(finalFunction, that.finalFunction) && Objects.equal(stateType, that.stateType) diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java index aa6d5552466b..77e4afede729 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -33,6 +33,7 @@ import org.apache.cassandra.cql3.*; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.schema.Functions; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.utils.ByteBufferUtil; @@ -267,8 +268,8 @@ public boolean equals(Object o) UDFunction that = (UDFunction)o; return Objects.equal(name, that.name) && Objects.equal(argNames, that.argNames) - && Functions.typeEquals(argTypes, that.argTypes) - && Functions.typeEquals(returnType, that.returnType) + && Functions.typesMatch(argTypes, that.argTypes) + && Functions.typesMatch(returnType, that.returnType) && Objects.equal(language, that.language) && Objects.equal(body, that.body); } diff --git a/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java b/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java index 0aa3ac451193..32adbdceb58c 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java +++ b/src/java/org/apache/cassandra/cql3/functions/UuidFcts.java @@ -18,14 +18,18 @@ package org.apache.cassandra.cql3.functions; import java.nio.ByteBuffer; -import java.util.List; -import java.util.UUID; +import java.util.*; import org.apache.cassandra.db.marshal.UUIDType; import org.apache.cassandra.serializers.UUIDSerializer; public abstract class UuidFcts { + public static Collection all() + { + return Collections.singleton(uuidFct); + } + public static final Function uuidFct = new NativeScalarFunction("uuid", UUIDType.instance) { public ByteBuffer execute(int protocolVersion, List parameters) diff --git a/src/java/org/apache/cassandra/cql3/selection/Selectable.java b/src/java/org/apache/cassandra/cql3/selection/Selectable.java index ee134ee8b810..717fe7c1a91c 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selectable.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selectable.java @@ -148,7 +148,7 @@ public Selector.Factory newSelectorFactory(CFMetaData cfm, if (functionName.equalsNativeFunction(ToJsonFct.NAME)) fun = ToJsonFct.getInstance(factories.getReturnTypes()); else - fun = Functions.get(cfm.ksName, functionName, factories.newInstances(), cfm.ksName, cfm.cfName, null); + fun = FunctionResolver.get(cfm.ksName, functionName, factories.newInstances(), cfm.ksName, cfm.cfName, null); if (fun == null) throw new InvalidRequestException(String.format("Unknown function '%s'", functionName)); diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java index 039993fc9613..16d9fc5587db 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateAggregateStatement.java @@ -90,7 +90,7 @@ public Prepared prepare() List> stateArgs = stateArguments(stateType, argTypes); stateFunc = validateFunctionKeyspace(stateFunc, stateArgs); - Function f = Functions.find(stateFunc, stateArgs); + Function f = Schema.instance.findFunction(stateFunc, stateArgs).orElse(null); if (!(f instanceof ScalarFunction)) throw new InvalidRequestException("State function " + stateFuncSig(stateFunc, stateTypeRaw, argRawTypes) + " does not exist or is not a scalar function"); stateFunction = (ScalarFunction)f; @@ -103,7 +103,7 @@ public Prepared prepare() { List> finalArgs = Collections.>singletonList(stateType); finalFunc = validateFunctionKeyspace(finalFunc, finalArgs); - f = Functions.find(finalFunc, finalArgs); + f = Schema.instance.findFunction(finalFunc, finalArgs).orElse(null); if (!(f instanceof ScalarFunction)) throw new InvalidRequestException("Final function " + finalFunc + '(' + stateTypeRaw + ") does not exist or is not a scalar function"); finalFunction = (ScalarFunction) f; @@ -156,7 +156,7 @@ private FunctionName validateFunctionKeyspace(FunctionName func, ListDROP AGGREGATE statement parsed from a CQL query. + * A {@code DROP AGGREGATE} statement parsed from a CQL query. */ public final class DropAggregateStatement extends SchemaAlteringStatement { @@ -85,7 +87,7 @@ public Event.SchemaChange changeEvent() public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException { - List olds = Functions.find(functionName); + Collection olds = Schema.instance.getFunctions(functionName); if (!argsPresent && olds != null && olds.size() > 1) throw new InvalidRequestException(String.format("'DROP AGGREGATE %s' matches multiple function definitions; " + @@ -101,7 +103,7 @@ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationEx Function old; if (argsPresent) { - old = Functions.find(functionName, argTypes); + old = Schema.instance.findFunction(functionName, argTypes).orElse(null); if (old == null || !(old instanceof AggregateFunction)) { if (ifExists) @@ -120,13 +122,13 @@ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationEx } else { - if (olds == null || olds.isEmpty() || !(olds.get(0) instanceof AggregateFunction)) + if (olds == null || olds.isEmpty() || !(olds.iterator().next() instanceof AggregateFunction)) { if (ifExists) return false; throw new InvalidRequestException(String.format("Cannot drop non existing aggregate '%s'", functionName)); } - old = olds.get(0); + old = olds.iterator().next(); } if (old.isNative()) diff --git a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java index 3957d97fc556..d6d7925a68ed 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java @@ -18,12 +18,15 @@ package org.apache.cassandra.cql3.statements; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import com.google.common.base.Joiner; import org.apache.cassandra.auth.FunctionResource; import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.KSMetaData; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.marshal.AbstractType; @@ -36,7 +39,7 @@ import org.apache.cassandra.transport.Event; /** - * A DROP FUNCTION statement parsed from a CQL query. + * A {@code DROP FUNCTION} statement parsed from a CQL query. */ public final class DropFunctionStatement extends SchemaAlteringStatement { @@ -113,7 +116,7 @@ public void checkAccess(ClientState state) throws UnauthorizedException, Invalid @Override public void validate(ClientState state) { - List olds = Functions.find(functionName); + Collection olds = Schema.instance.getFunctions(functionName); if (!argsPresent && olds != null && olds.size() > 1) throw new InvalidRequestException(String.format("'DROP FUNCTION %s' matches multiple function definitions; " + @@ -142,9 +145,10 @@ public boolean announceMigration(boolean isLocalOnly) throws RequestValidationEx throw new InvalidRequestException(getMissingFunctionError()); } - List references = Functions.getReferencesTo(old); - if (!references.isEmpty()) - throw new InvalidRequestException(String.format("Function '%s' still referenced by %s", old, references)); + KSMetaData ksm = Schema.instance.getKSMetaData(old.name().keyspace); + Collection referrers = ksm.functions.aggregatesUsingFunction(old); + if (!referrers.isEmpty()) + throw new InvalidRequestException(String.format("Function '%s' still referenced by %s", old, referrers)); MigrationManager.announceFunctionDrop((UDFunction) old, isLocalOnly); @@ -158,7 +162,7 @@ private String getMissingFunctionError() sb.append(functionName); if (argsPresent) sb.append(Joiner.on(", ").join(argRawTypes)); - sb.append("'"); + sb.append('\''); return sb.toString(); } @@ -167,7 +171,7 @@ private Function findFunction() Function old; if (argsPresent) { - old = Functions.find(functionName, argTypes); + old = Schema.instance.findFunction(functionName, argTypes).orElse(null); if (old == null || !(old instanceof ScalarFunction)) { return null; @@ -175,11 +179,11 @@ private Function findFunction() } else { - List olds = Functions.find(functionName); - if (olds == null || olds.isEmpty() || !(olds.get(0) instanceof ScalarFunction)) + Collection olds = Schema.instance.getFunctions(functionName); + if (olds == null || olds.isEmpty() || !(olds.iterator().next() instanceof ScalarFunction)) return null; - old = olds.get(0); + old = olds.iterator().next(); } return old; } diff --git a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java index 8ad4f6ca3436..5edac5896985 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropTypeStatement.java @@ -21,7 +21,6 @@ import org.apache.cassandra.config.*; import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.functions.Function; -import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.*; import org.apache.cassandra.service.ClientState; @@ -74,30 +73,24 @@ public void validate(ClientState state) throws RequestValidationException // we drop and 2) existing tables referencing the type (maybe in a nested // way). - for (Function function : Functions.all()) + for (Function function : ksm.functions) { if (isUsedBy(function.returnType())) throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by function %s", name, function)); + for (AbstractType argType : function.argTypes()) if (isUsedBy(argType)) throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by function %s", name, function)); } - for (KSMetaData ksm2 : Schema.instance.getKeyspaceDefinitions()) - { - for (UserType ut : ksm2.userTypes.getAllTypes().values()) - { - if (ut.keyspace.equals(name.getKeyspace()) && ut.name.equals(name.getUserTypeName())) - continue; - if (isUsedBy(ut)) - throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by user type %s", name, ut.asCQL3Type())); - } - - for (CFMetaData cfm : ksm2.cfMetaData().values()) - for (ColumnDefinition def : cfm.allColumns()) - if (isUsedBy(def.type)) - throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by table %s.%s", name, cfm.ksName, cfm.cfName)); - } + for (UserType ut : ksm.userTypes.getAllTypes().values()) + if (!ut.name.equals(name.getUserTypeName()) && isUsedBy(ut)) + throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by user type %s", name, ut.asCQL3Type())); + + for (CFMetaData cfm : ksm.cfMetaData().values()) + for (ColumnDefinition def : cfm.allColumns()) + if (isUsedBy(def.type)) + throw new InvalidRequestException(String.format("Cannot drop user type %s as it is still used by table %s.%s", name, cfm.ksName, cfm.cfName)); } private boolean isUsedBy(AbstractType toCheck) throws RequestValidationException diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index 34c617f5c3cb..1e15321acf4d 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -36,6 +36,7 @@ import org.apache.cassandra.config.KSMetaData; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.db.commitlog.ReplayPosition; import org.apache.cassandra.db.compaction.CompactionHistoryTabularData; @@ -51,6 +52,7 @@ import org.apache.cassandra.locator.LocalStrategy; import org.apache.cassandra.metrics.RestorableMeter; import org.apache.cassandra.net.MessagingService; +import org.apache.cassandra.schema.Functions; import org.apache.cassandra.schema.LegacySchemaTables; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.paxos.Commit; @@ -64,6 +66,10 @@ public final class SystemKeyspace { + private SystemKeyspace() + { + } + private static final Logger logger = LoggerFactory.getLogger(SystemKeyspace.class); // Used to indicate that there was a previous version written to the legacy (pre 1.2) @@ -246,11 +252,11 @@ public final class SystemKeyspace private static final CFMetaData AvailableRanges = compile(AVAILABLE_RANGES, - "Available keyspace/ranges during bootstrap/replace that are ready to be served", + "available keyspace/ranges during bootstrap/replace that are ready to be served", "CREATE TABLE %s (" - + "keyspace_name text PRIMARY KEY," - + "ranges set" - + ")"); + + "keyspace_name text," + + "ranges set," + + "PRIMARY KEY ((keyspace_name)))"); private static CFMetaData compile(String name, String description, String schema) { @@ -275,7 +281,18 @@ public static KSMetaData definition() SSTableActivity, SizeEstimates, AvailableRanges)); - return new KSMetaData(NAME, LocalStrategy.class, Collections.emptyMap(), true, tables); + + return new KSMetaData(NAME, LocalStrategy.class, Collections.emptyMap(), true, tables, functions()); + } + + private static Functions functions() + { + return Functions.builder() + .add(UuidFcts.all()) + .add(TimeFcts.all()) + .add(BytesConversionFcts.all()) + .add(AggregateFcts.all()) + .build(); } private static volatile Map> truncationRecords; diff --git a/src/java/org/apache/cassandra/schema/Functions.java b/src/java/org/apache/cassandra/schema/Functions.java new file mode 100644 index 000000000000..8d73e48a2c5c --- /dev/null +++ b/src/java/org/apache/cassandra/schema/Functions.java @@ -0,0 +1,216 @@ +/* + * 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.schema; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableMultimap; + +import org.apache.cassandra.cql3.functions.*; +import org.apache.cassandra.db.marshal.AbstractType; + +import static com.google.common.collect.Iterables.filter; + +/** + * An immutable container for a keyspace's UDAs and UDFs (and, in case of {@link org.apache.cassandra.db.SystemKeyspace}, + * native functions and aggregates). + */ +public final class Functions implements Iterable +{ + private final ImmutableMultimap functions; + + private Functions(Builder builder) + { + functions = builder.functions.build(); + } + + public static Builder builder() + { + return new Builder(); + } + + public static Functions none() + { + return builder().build(); + } + + public Iterator iterator() + { + return functions.values().iterator(); + } + + public Stream stream() + { + return functions.values().stream(); + } + + /** + * @return a stream of keyspace's UDFs + */ + public Stream udfs() + { + return stream().filter(f -> f instanceof UDFunction).map(f -> (UDFunction) f); + } + + /** + * @return a stream of keyspace's UDAs + */ + public Stream udas() + { + return stream().filter(f -> f instanceof UDAggregate).map(f -> (UDAggregate) f); + } + + /** + * @return a collection of aggregates that use the provided function as either a state or a final function + * @param function the referree function + */ + public Collection aggregatesUsingFunction(Function function) + { + return udas().filter(uda -> uda.hasReferenceTo(function)).collect(Collectors.toList()); + } + + /** + * Get all function overloads with the specified name + * + * @param name fully qualified function name + * @return an empty list if the function name is not found; a non-empty collection of {@link Function} otherwise + */ + public Collection get(FunctionName name) + { + return functions.get(name); + } + + /** + * Find the function with the specified name + * + * @param name fully qualified function name + * @param argTypes function argument types + * @return an empty {@link Optional} if the function name is not found; a non-empty optional of {@link Function} otherwise + */ + public Optional find(FunctionName name, List> argTypes) + { + return get(name).stream() + .filter(fun -> typesMatch(fun.argTypes(), argTypes)) + .findAny(); + } + + /* + * 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 typesMatch(AbstractType t1, AbstractType t2) + { + return t1.asCQL3Type().toString().equals(t2.asCQL3Type().toString()); + } + + public static boolean typesMatch(List> t1, List> t2) + { + if (t1.size() != t2.size()) + return false; + + for (int i = 0; i < t1.size(); i++) + if (!typesMatch(t1.get(i), t2.get(i))) + return false; + + return true; + } + + /** + * Create a Functions instance with the provided function added + */ + public Functions with(Function fun) + { + if (find(fun.name(), fun.argTypes()).isPresent()) + throw new IllegalStateException(String.format("Function %s already exists", fun.name())); + + return builder().add(this).add(fun).build(); + } + + /** + * Creates a Functions instance with the function with the provided name and argument types removed + */ + public Functions without(FunctionName name, List> argTypes) + { + Function fun = + find(name, argTypes).orElseThrow(() -> new IllegalStateException(String.format("Function %s doesn't exists", name))); + + return builder().add(filter(this, f -> f != fun)).build(); + } + + @Override + public boolean equals(Object o) + { + return this == o || (o instanceof Functions && functions.equals(((Functions) o).functions)); + } + + @Override + public int hashCode() + { + return functions.hashCode(); + } + + @Override + public String toString() + { + return functions.values().toString(); + } + + public static final class Builder + { + final ImmutableMultimap.Builder functions = new ImmutableMultimap.Builder<>(); + + private Builder() + { + } + + public Functions build() + { + return new Functions(this); + } + + public Builder add(Function fun) + { + functions.put(fun.name(), fun); + return this; + } + + public Builder add(Function... funs) + { + for (Function fun : funs) + add(fun); + return this; + } + + public Builder add(Iterable funs) + { + funs.forEach(this::add); + return this; + } + } +} diff --git a/src/java/org/apache/cassandra/schema/LegacySchemaTables.java b/src/java/org/apache/cassandra/schema/LegacySchemaTables.java index 1348d125775f..42c67db0e1b8 100644 --- a/src/java/org/apache/cassandra/schema/LegacySchemaTables.java +++ b/src/java/org/apache/cassandra/schema/LegacySchemaTables.java @@ -55,8 +55,12 @@ import static org.apache.cassandra.utils.FBUtilities.json; /** system.schema_* tables used to store keyspace/table/type attributes prior to C* 3.0 */ -public class LegacySchemaTables +public final class LegacySchemaTables { + private LegacySchemaTables() + { + } + private static final Logger logger = LoggerFactory.getLogger(LegacySchemaTables.class); public static final String KEYSPACES = "schema_keyspaces"; @@ -220,17 +224,10 @@ public static Collection readSchemaFromSystemTables() DecoratedKey key = partition.partitionKey(); readSchemaPartitionForKeyspaceAndApply(USERTYPES, key, - types -> readSchemaPartitionForKeyspaceAndApply(COLUMNFAMILIES, key, tables -> keyspaces.add(createKeyspaceFromSchemaPartitions(partition, tables, types))) - ); - - // Will be moved away in #6717 - readSchemaPartitionForKeyspaceAndApply(FUNCTIONS, key, - functions -> { createFunctionsFromFunctionsPartition(functions).forEach(function -> org.apache.cassandra.cql3.functions.Functions.addOrReplaceFunction(function)); return null; } - ); - - // Will be moved away in #6717 - readSchemaPartitionForKeyspaceAndApply(AGGREGATES, key, - aggregates -> { createAggregatesFromAggregatesPartition(aggregates).forEach(aggregate -> org.apache.cassandra.cql3.functions.Functions.addOrReplaceFunction(aggregate)); return null; } + types -> readSchemaPartitionForKeyspaceAndApply(COLUMNFAMILIES, key, + tables -> readSchemaPartitionForKeyspaceAndApply(FUNCTIONS, key, + functions -> readSchemaPartitionForKeyspaceAndApply(AGGREGATES, key, + aggregates -> keyspaces.add(createKeyspaceFromSchemaPartitions(partition, tables, types, functions, aggregates))))) ); } } @@ -651,7 +648,7 @@ public static Mutation makeCreateKeyspaceMutation(KSMetaData keyspace, long time return makeCreateKeyspaceMutation(keyspace, timestamp, true); } - private static Mutation makeCreateKeyspaceMutation(KSMetaData keyspace, long timestamp, boolean withTablesAndTypesAndFunctions) + public static Mutation makeCreateKeyspaceMutation(KSMetaData keyspace, long timestamp, boolean withTablesAndTypesAndFunctions) { // Note that because Keyspaces is a COMPACT TABLE, we're really only setting static columns internally and shouldn't set any clustering. RowUpdateBuilder adder = new RowUpdateBuilder(Keyspaces, timestamp, keyspace.name); @@ -664,11 +661,10 @@ private static Mutation makeCreateKeyspaceMutation(KSMetaData keyspace, long tim if (withTablesAndTypesAndFunctions) { - for (UserType type : keyspace.userTypes.getAllTypes().values()) - addTypeToSchemaMutation(type, timestamp, mutation); - - for (CFMetaData table : keyspace.cfMetaData().values()) - addTableToSchemaMutation(table, timestamp, true, mutation); + keyspace.userTypes.getAllTypes().values().forEach(type -> addTypeToSchemaMutation(type, timestamp, mutation)); + keyspace.cfMetaData().values().forEach(table -> addTableToSchemaMutation(table, timestamp, true, mutation)); + keyspace.functions.udfs().forEach(udf -> addFunctionToSchemaMutation(udf, timestamp, mutation)); + keyspace.functions.udas().forEach(uda -> addAggregateToSchemaMutation(uda, timestamp, mutation)); } return mutation; @@ -684,11 +680,20 @@ public static Mutation makeDropKeyspaceMutation(KSMetaData keyspace, long timest return mutation; } - private static KSMetaData createKeyspaceFromSchemaPartitions(RowIterator serializedKeyspace, RowIterator serializedTables, RowIterator serializedTypes) + private static KSMetaData createKeyspaceFromSchemaPartitions(RowIterator serializedKeyspace, + RowIterator serializedTables, + RowIterator serializedTypes, + RowIterator serializedFunctions, + RowIterator seriazliedAggregates) { Collection tables = createTablesFromTablesPartition(serializedTables); UTMetaData types = new UTMetaData(createTypesFromPartition(serializedTypes)); - return createKeyspaceFromSchemaPartition(serializedKeyspace).cloneWith(tables, types); + + Collection udfs = createFunctionsFromFunctionsPartition(serializedFunctions); + Collection udas = createAggregatesFromAggregatesPartition(seriazliedAggregates); + Functions functions = org.apache.cassandra.schema.Functions.builder().add(udfs).add(udas).build(); + + return createKeyspaceFromSchemaPartition(serializedKeyspace).cloneWith(tables, types, functions); } public static KSMetaData createKeyspaceFromName(String keyspace) @@ -1360,7 +1365,7 @@ private static UDFunction createFunctionFromFunctionRow(UntypedResultSet.Row row String body = row.getString("body"); boolean calledOnNullInput = row.getBoolean("called_on_null_input"); - org.apache.cassandra.cql3.functions.Function existing = org.apache.cassandra.cql3.functions.Functions.find(name, argTypes); + org.apache.cassandra.cql3.functions.Function existing = Schema.instance.findFunction(name, argTypes).orElse(null); if (existing instanceof UDFunction) { // This check prevents duplicate compilation of effectively the same UDF. @@ -1513,15 +1518,4 @@ public static ByteBuffer functionSignatureWithTypes(AbstractFunction fun) strList.add(argType.asCQL3Type().toString()); return list.decompose(strList); } - - public static ByteBuffer functionSignatureWithNameAndTypes(AbstractFunction fun) - { - ListType list = ListType.getInstance(UTF8Type.instance, false); - List strList = new ArrayList<>(fun.argTypes().size() + 2); - strList.add(fun.name().keyspace); - strList.add(fun.name().name); - for (AbstractType argType : fun.argTypes()) - strList.add(argType.asCQL3Type().toString()); - return list.decompose(strList); - } } diff --git a/src/java/org/apache/cassandra/service/MigrationManager.java b/src/java/org/apache/cassandra/service/MigrationManager.java index 908767211d10..3b9508314d15 100644 --- a/src/java/org/apache/cassandra/service/MigrationManager.java +++ b/src/java/org/apache/cassandra/service/MigrationManager.java @@ -182,7 +182,6 @@ public void notifyCreateFunction(UDFunction udf) listener.onCreateFunction(udf.name().keyspace, udf.name().name, udf.argTypes()); } - public void notifyCreateAggregate(UDAggregate udf) { for (MigrationListener listener : listeners) @@ -205,6 +204,9 @@ public void notifyUpdateUserType(UserType ut) { for (MigrationListener listener : listeners) listener.onUpdateUserType(ut.keyspace, ut.getNameAsString()); + + // FIXME: remove when we get rid of AbstractType in metadata. Doesn't really belong anywhere. + Schema.instance.getKSMetaData(ut.keyspace).functions.udfs().forEach(f -> f.userTypeUpdated(ut.keyspace, ut.getNameAsString())); } public void notifyUpdateFunction(UDFunction udf) @@ -323,7 +325,7 @@ public static void announceKeyspaceUpdate(KSMetaData ksm, boolean announceLocall throw new ConfigurationException(String.format("Cannot update non existing keyspace '%s'.", ksm.name)); logger.info(String.format("Update Keyspace '%s' From %s To %s", ksm.name, oldKsm, ksm)); - announce(LegacySchemaTables.makeCreateKeyspaceMutation(ksm, FBUtilities.timestampMicros()), announceLocally); + announce(LegacySchemaTables.makeCreateKeyspaceMutation(ksm, FBUtilities.timestampMicros(), false), announceLocally); } public static void announceColumnFamilyUpdate(CFMetaData cfm, boolean fromThrift) throws ConfigurationException diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/UFAuthTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/UFAuthTest.java index ee2055708131..25fe227a1392 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFAuthTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFAuthTest.java @@ -30,12 +30,12 @@ import org.apache.cassandra.auth.*; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.Attributes; import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.cql3.functions.FunctionName; -import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.cql3.statements.BatchStatement; import org.apache.cassandra.cql3.statements.ModificationStatement; import org.apache.cassandra.cql3.CQLTester; @@ -618,12 +618,12 @@ private FunctionResource functionResource(String functionName) // It is here to avoid having to duplicate the functionality of CqlParser // for transforming cql types into AbstractTypes FunctionName fn = parseFunctionName(functionName); - List functions = Functions.find(fn); + Collection functions = Schema.instance.getFunctions(fn); assertEquals(String.format("Expected a single function definition for %s, but found %s", functionName, functions.size()), 1, functions.size()); - return FunctionResource.function(fn.keyspace, fn.name, functions.get(0).argTypes()); + return FunctionResource.function(fn.keyspace, fn.name, functions.iterator().next().argTypes()); } private String functionCall(String functionName, String...args) diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java index 7bd208fb4649..e9436f8186d7 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFTest.java @@ -37,10 +37,11 @@ import com.datastax.driver.core.*; import com.datastax.driver.core.exceptions.InvalidQueryException; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.KSMetaData; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.functions.FunctionName; -import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.db.marshal.CollectionType; @@ -119,7 +120,7 @@ public void testFunctionDropOnKeyspaceDrop() throws Throwable FunctionName fSinName = parseFunctionName(fSin); - Assert.assertEquals(1, Functions.find(parseFunctionName(fSin)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fSin)).size()); assertRows(execute("SELECT function_name, language FROM system.schema_functions WHERE keyspace_name=?", KEYSPACE_PER_TEST), row(fSinName.name, "java")); @@ -128,7 +129,7 @@ public void testFunctionDropOnKeyspaceDrop() throws Throwable assertRows(execute("SELECT function_name, language FROM system.schema_functions WHERE keyspace_name=?", KEYSPACE_PER_TEST)); - Assert.assertEquals(0, Functions.find(fSinName).size()); + Assert.assertEquals(0, Schema.instance.getFunctions(fSinName).size()); } @Test @@ -145,7 +146,7 @@ public void testFunctionDropPreparedStatement() throws Throwable FunctionName fSinName = parseFunctionName(fSin); - Assert.assertEquals(1, Functions.find(parseFunctionName(fSin)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fSin)).size()); // create a pairs of Select and Inserts. One statement in each pair uses the function so when we // drop it those statements should be removed from the cache in QueryProcessor. The other statements @@ -183,7 +184,7 @@ public void testFunctionDropPreparedStatement() throws Throwable "LANGUAGE java " + "AS 'return Double.valueOf(Math.sin(input));'"); - Assert.assertEquals(1, Functions.find(fSinName).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(fSinName).size()); preparedSelect1= QueryProcessor.prepare( String.format("SELECT key, %s(d) FROM %s.%s", fSin, KEYSPACE, currentTable()), @@ -298,7 +299,7 @@ public void checkDelayedValuesCorrectlyIdentifyFunctionsInUse(boolean dropKeyspa "RETURNS double " + "LANGUAGE javascript " + "AS 'input'"); - Assert.assertEquals(1, Functions.find(parseFunctionName(function)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(function)).size()); List prepared = new ArrayList<>(); // prepare statements which use the function to provide a DelayedValue @@ -1292,7 +1293,7 @@ public void testUserTypeDrop() throws Throwable FunctionName fNameName = parseFunctionName(fName); - Assert.assertEquals(1, Functions.find(fNameName).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(fNameName).size()); ResultMessage.Prepared prepared = QueryProcessor.prepare(String.format("SELECT key, %s(udt) FROM %s.%s", fName, KEYSPACE, currentTable()), ClientState.forInternalCalls(), false); @@ -1309,7 +1310,7 @@ public void testUserTypeDrop() throws Throwable Assert.assertNull(QueryProcessor.instance.getPrepared(prepared.statementId)); // function stays - Assert.assertEquals(1, Functions.find(fNameName).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(fNameName).size()); } @Test @@ -1422,7 +1423,7 @@ public void testJavaUserTypeAddFieldWithReplace() throws Throwable "AS $$return " + " udt.getString(\"txt\");$$;", fName1replace, type)); - Assert.assertEquals(1, Functions.find(parseFunctionName(fName1replace)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName1replace)).size()); execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + "CALLED ON NULL INPUT " + "RETURNS int " + @@ -1430,7 +1431,7 @@ public void testJavaUserTypeAddFieldWithReplace() throws Throwable "AS $$return " + " Integer.valueOf(udt.getInt(\"i\"));$$;", fName2replace, type)); - Assert.assertEquals(1, Functions.find(parseFunctionName(fName2replace)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName2replace)).size()); execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + "CALLED ON NULL INPUT " + "RETURNS double " + @@ -1438,7 +1439,7 @@ public void testJavaUserTypeAddFieldWithReplace() throws Throwable "AS $$return " + " Double.valueOf(udt.getDouble(\"added\"));$$;", fName3replace, type)); - Assert.assertEquals(1, Functions.find(parseFunctionName(fName3replace)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName3replace)).size()); execute(String.format("CREATE OR REPLACE FUNCTION %s( udt %s ) " + "RETURNS NULL ON NULL INPUT " + "RETURNS %s " + @@ -1446,7 +1447,7 @@ public void testJavaUserTypeAddFieldWithReplace() throws Throwable "AS $$return " + " udt;$$;", fName4replace, type, type)); - Assert.assertEquals(1, Functions.find(parseFunctionName(fName4replace)).size()); + Assert.assertEquals(1, Schema.instance.getFunctions(parseFunctionName(fName4replace)).size()); assertRows(execute("SELECT " + fName1replace + "(udt) FROM %s WHERE key = 2"), row("two")); @@ -2174,10 +2175,18 @@ public void testBrokenFunction() throws Throwable "LANGUAGE JAVA\n" + "AS 'throw new RuntimeException();';"); - UDFunction f = (UDFunction) Functions.find(parseFunctionName(fName)).get(0); - - Functions.addOrReplaceFunction(UDFunction.createBrokenFunction(f.name(), f.argNames(), f.argTypes(), f.returnType(), true, - "java", f.body(), new InvalidRequestException("foo bar is broken"))); + KSMetaData ksm = Schema.instance.getKSMetaData(KEYSPACE_PER_TEST); + UDFunction f = (UDFunction) ksm.functions.get(parseFunctionName(fName)).iterator().next(); + + UDFunction broken = UDFunction.createBrokenFunction(f.name(), + f.argNames(), + f.argTypes(), + f.returnType(), + true, + "java", + f.body(), + new InvalidRequestException("foo bar is broken")); + Schema.instance.setKeyspaceDefinition(ksm.cloneWith(ksm.functions.without(f.name(), f.argTypes()).with(broken))); assertInvalidThrowMessage("foo bar is broken", InvalidRequestException.class, "SELECT key, " + fName + "(dval) FROM %s"); diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java index 3f6fddae13e3..a7ce3a6f34cf 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/AggregationTest.java @@ -27,8 +27,9 @@ import org.junit.Assert; import org.junit.Test; +import org.apache.cassandra.config.KSMetaData; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.QueryProcessor; -import org.apache.cassandra.cql3.functions.Functions; import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.exceptions.FunctionExecutionException; @@ -1014,10 +1015,16 @@ public void testBrokenAggregate() throws Throwable "SFUNC " + shortFunctionName(fState) + " " + "STYPE int "); - UDAggregate f = (UDAggregate) Functions.find(parseFunctionName(a)).get(0); + KSMetaData ksm = Schema.instance.getKSMetaData(keyspace()); + UDAggregate f = (UDAggregate) ksm.functions.get(parseFunctionName(a)).iterator().next(); - Functions.addOrReplaceFunction(UDAggregate.createBroken(f.name(), f.argTypes(), f.returnType(), - null, new InvalidRequestException("foo bar is broken"))); + UDAggregate broken = UDAggregate.createBroken(f.name(), + f.argTypes(), + f.returnType(), + null, + new InvalidRequestException("foo bar is broken")); + + Schema.instance.setKeyspaceDefinition(ksm.cloneWith(ksm.functions.without(f.name(), f.argTypes()).with(broken))); assertInvalidThrowMessage("foo bar is broken", InvalidRequestException.class, "SELECT " + a + "(val) FROM %s");