Deal with conflicts between system functions and UDFs patch by Robert Stupp; reviewed by Benjamin Lerer for CASSANDRA-7813
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b4d7f3be Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b4d7f3be Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b4d7f3be Branch: refs/heads/trunk Commit: b4d7f3bed0687b449f6a275d9dd675e25d794aeb Parents: 41a35ec Author: Robert Stupp <sn...@snazy.de> Authored: Fri Nov 14 18:18:38 2014 +0300 Committer: Aleksey Yeschenko <alek...@apache.org> Committed: Fri Nov 14 18:20:29 2014 +0300 ---------------------------------------------------------------------- CHANGES.txt | 4 +- build.xml | 2 + pylib/cqlshlib/cql3handling.py | 2 +- src/java/org/apache/cassandra/auth/Auth.java | 6 +- .../org/apache/cassandra/config/KSMetaData.java | 1 + .../org/apache/cassandra/cql3/Attributes.java | 6 + .../org/apache/cassandra/cql3/CQLStatement.java | 2 + .../apache/cassandra/cql3/ColumnCondition.java | 16 +- src/java/org/apache/cassandra/cql3/Cql.g | 8 +- .../org/apache/cassandra/cql3/Operation.java | 6 +- .../apache/cassandra/cql3/QueryProcessor.java | 29 +- src/java/org/apache/cassandra/cql3/Term.java | 12 + .../org/apache/cassandra/cql3/UserTypes.java | 9 + .../cassandra/cql3/functions/FunctionCall.java | 13 +- .../cassandra/cql3/functions/FunctionName.java | 36 +- .../cassandra/cql3/functions/Functions.java | 27 +- .../cql3/functions/NativeFunction.java | 8 +- .../cassandra/cql3/functions/UDFunction.java | 21 +- .../selection/AbstractFunctionSelector.java | 7 +- .../cassandra/cql3/selection/Selection.java | 10 + .../cassandra/cql3/selection/Selector.java | 5 + .../cql3/selection/SelectorFactories.java | 8 + .../cql3/statements/BatchStatement.java | 10 + .../statements/CreateFunctionStatement.java | 28 +- .../cql3/statements/DropFunctionStatement.java | 28 +- .../cql3/statements/ModificationStatement.java | 20 +- .../cql3/statements/ParsedStatement.java | 5 + .../cassandra/cql3/statements/Restriction.java | 2 + .../cql3/statements/SelectStatement.java | 22 +- .../statements/SingleColumnRestriction.java | 40 ++ .../org/apache/cassandra/db/DefsTables.java | 5 +- .../org/apache/cassandra/db/SystemKeyspace.java | 7 +- .../cassandra/service/IMigrationListener.java | 6 +- .../cassandra/service/MigrationManager.java | 26 +- .../org/apache/cassandra/transport/Event.java | 24 +- .../org/apache/cassandra/transport/Server.java | 8 +- .../apache/cassandra/cql3/AggregationTest.java | 10 +- .../org/apache/cassandra/cql3/PgStringTest.java | 4 +- test/unit/org/apache/cassandra/cql3/UFTest.java | 378 +++++++++++++------ 39 files changed, 637 insertions(+), 224 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index f250edc..ff255d8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,7 @@ 3.0 * Fix aggregate fn results on empty selection, result column name, and cqlsh parsing (CASSANDRA-8229) - * Mark sstables as repaired after full repair (CASSANDRA-7586) + * Mark sstables as repaired after full repair (CASSANDRA-7586) * Extend Descriptor to include a format value and refactor reader/writer apis (CASSANDRA-7443) * Integrate JMH for microbenchmarks (CASSANDRA-8151) * Keep sstable levels when bootstrapping (CASSANDRA-7460) @@ -15,7 +15,7 @@ * Remove YamlFileNetworkTopologySnitch (CASSANDRA-7917) * Do anticompaction in groups (CASSANDRA-6851) * Support pure user-defined functions (CASSANDRA-7395, 7526, 7562, 7740, 7781, 7929, - 7924, 7812, 8063) + 7924, 7812, 8063, 7813) * Permit configurable timestamps with cassandra-stress (CASSANDRA-7416) * Move sstable RandomAccessReader to nio2, which allows using the FILE_SHARE_DELETE flag on Windows (CASSANDRA-4050) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/build.xml ---------------------------------------------------------------------- diff --git a/build.xml b/build.xml index c4e27a7..c7aa83e 100644 --- a/build.xml +++ b/build.xml @@ -212,6 +212,8 @@ <arg value="${build.src.java}/org/apache/cassandra/cql3/Cql.g" /> <arg value="-fo" /> <arg value="${build.src.gen-java}/org/apache/cassandra/cql3/" /> + <arg value="-Xmaxinlinedfastates"/> + <arg value="10"/> <!-- default is 60 --> </java> </target> http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/pylib/cqlshlib/cql3handling.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 261161c..f8a3069 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -209,7 +209,7 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; <mapLiteral> ::= "{" <term> ":" <term> ( "," <term> ":" <term> )* "}" ; -<functionName> ::= <identifier> ( ":" ":" <identifier> )? +<functionName> ::= <identifier> ( "." <identifier> )? | "TOKEN" ; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/auth/Auth.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/Auth.java b/src/java/org/apache/cassandra/auth/Auth.java index 07c9a67..8c12df6 100644 --- a/src/java/org/apache/cassandra/auth/Auth.java +++ b/src/java/org/apache/cassandra/auth/Auth.java @@ -337,7 +337,7 @@ public class Auth implements AuthMBean { } - public void onDropFunction(String namespace, String functionName) + public void onDropFunction(String ksName, String functionName) { } @@ -353,7 +353,7 @@ public class Auth implements AuthMBean { } - public void onCreateFunction(String namespace, String functionName) + public void onCreateFunction(String ksName, String functionName) { } @@ -369,7 +369,7 @@ public class Auth implements AuthMBean { } - public void onUpdateFunction(String namespace, String functionName) + public void onUpdateFunction(String ksName, String functionName) { } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/config/KSMetaData.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/config/KSMetaData.java b/src/java/org/apache/cassandra/config/KSMetaData.java index d3ff62c..494f98b 100644 --- a/src/java/org/apache/cassandra/config/KSMetaData.java +++ b/src/java/org/apache/cassandra/config/KSMetaData.java @@ -185,6 +185,7 @@ public final class KSMetaData mutation.delete(SystemKeyspace.SCHEMA_COLUMNS_TABLE, timestamp); mutation.delete(SystemKeyspace.SCHEMA_TRIGGERS_TABLE, timestamp); mutation.delete(SystemKeyspace.SCHEMA_USER_TYPES_TABLE, timestamp); + mutation.delete(SystemKeyspace.SCHEMA_FUNCTIONS_TABLE, timestamp); mutation.delete(SystemKeyspace.BUILT_INDEXES_TABLE, timestamp); return mutation; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/Attributes.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Attributes.java b/src/java/org/apache/cassandra/cql3/Attributes.java index df40b0c..851e1b4 100644 --- a/src/java/org/apache/cassandra/cql3/Attributes.java +++ b/src/java/org/apache/cassandra/cql3/Attributes.java @@ -45,6 +45,12 @@ public class Attributes this.timeToLive = timeToLive; } + public boolean usesFunction(String ksName, String functionName) + { + return (timestamp != null && timestamp.usesFunction(ksName, functionName)) + || (timeToLive != null && timeToLive.usesFunction(ksName, functionName)); + } + public boolean isTimestampSet() { return timestamp != null; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/CQLStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/CQLStatement.java b/src/java/org/apache/cassandra/cql3/CQLStatement.java index a1642ef..d555ec3 100644 --- a/src/java/org/apache/cassandra/cql3/CQLStatement.java +++ b/src/java/org/apache/cassandra/cql3/CQLStatement.java @@ -58,4 +58,6 @@ public interface CQLStatement * @param state the current query state */ public ResultMessage executeInternal(QueryState state, QueryOptions options) throws RequestValidationException, RequestExecutionException; + + boolean usesFunction(String ksName, String functionName); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/ColumnCondition.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/ColumnCondition.java b/src/java/org/apache/cassandra/cql3/ColumnCondition.java index fc45fdc..7daec02 100644 --- a/src/java/org/apache/cassandra/cql3/ColumnCondition.java +++ b/src/java/org/apache/cassandra/cql3/ColumnCondition.java @@ -34,15 +34,12 @@ import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.transport.Server; import org.apache.cassandra.utils.ByteBufferUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * A CQL3 condition on the value of a column or collection element. For example, "UPDATE .. IF a = 0". */ public class ColumnCondition { - private static final Logger logger = LoggerFactory.getLogger(ColumnCondition.class); public final ColumnDefinition column; @@ -96,6 +93,19 @@ public class ColumnCondition return new ColumnCondition(column, collectionElement, inMarker, null, Operator.IN); } + public boolean usesFunction(String ksName, String functionName) + { + if (collectionElement != null && collectionElement.usesFunction(ksName, functionName)) + return true; + if (value != null && value.usesFunction(ksName, functionName)) + return true; + if (inValues != null) + for (Term value : inValues) + if (value != null && value.usesFunction(ksName, functionName)) + return true; + return false; + } + /** * Collects the column specification for the bind variables of this operation. * http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/Cql.g ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Cql.g b/src/java/org/apache/cassandra/cql3/Cql.g index 37e94b7..4c051e3 100644 --- a/src/java/org/apache/cassandra/cql3/Cql.g +++ b/src/java/org/apache/cassandra/cql3/Cql.g @@ -310,7 +310,7 @@ selectionFunctionArgs returns [List<Selectable.Raw> a] selectCountClause returns [List<RawSelector> expr] @init{ ColumnIdentifier alias = new ColumnIdentifier("count", false); } - : K_COUNT '(' countArgument ')' (K_AS c=ident { alias = c; })? { $expr = new ArrayList<RawSelector>(); $expr.add( new RawSelector(new Selectable.WithFunction.Raw(new FunctionName("countRows"), Collections.<Selectable.Raw>emptyList()), alias));} + : K_COUNT '(' countArgument ')' (K_AS c=ident { alias = c; })? { $expr = new ArrayList<RawSelector>(); $expr.add( new RawSelector(new Selectable.WithFunction.Raw(FunctionName.nativeFunction("countRows"), Collections.<Selectable.Raw>emptyList()), alias));} ; countArgument @@ -977,12 +977,12 @@ intValue returns [Term.Raw value] ; functionName returns [FunctionName s] - : f=allowedFunctionName { $s = new FunctionName(f); } - | b=allowedFunctionName '::' f=allowedFunctionName { $s = new FunctionName(b, f); } + : (ks=keyspaceName '.')? f=allowedFunctionName { $s = new FunctionName(ks, f); } ; allowedFunctionName returns [String s] - : f=IDENT { $s = $f.text; } + : f=IDENT { $s = $f.text.toLowerCase(); } + | f=QUOTED_NAME { $s = $f.text; } | u=unreserved_function_keyword { $s = u; } | K_TOKEN { $s = "token"; } | K_COUNT { $s = "count"; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/Operation.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Operation.java b/src/java/org/apache/cassandra/cql3/Operation.java index 816acb2..583158b 100644 --- a/src/java/org/apache/cassandra/cql3/Operation.java +++ b/src/java/org/apache/cassandra/cql3/Operation.java @@ -19,7 +19,6 @@ package org.apache.cassandra.cql3; import java.nio.ByteBuffer; -import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.db.ColumnFamily; import org.apache.cassandra.db.composites.Composite; @@ -56,6 +55,11 @@ public abstract class Operation this.t = t; } + public boolean usesFunction(String ksName, String functionName) + { + return t != null && t.usesFunction(ksName, functionName); + } + /** * @return whether the operation requires a read of the previous value to be executed * (only lists setterByIdx, discard and discardByIdx requires that). http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/QueryProcessor.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 680f9f2..cd56075 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -31,6 +31,7 @@ import org.antlr.runtime.*; import org.github.jamm.MemoryMeter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.cql3.functions.*; import org.apache.cassandra.cql3.statements.*; import org.apache.cassandra.db.*; import org.apache.cassandra.db.composites.*; @@ -591,11 +592,20 @@ public class QueryProcessor implements QueryHandler public void onCreateKeyspace(String ksName) { } public void onCreateColumnFamily(String ksName, String cfName) { } public void onCreateUserType(String ksName, String typeName) { } - public void onCreateFunction(String namespace, String functionName) { } + public void onCreateFunction(String ksName, String functionName) { + if (Functions.getOverloadCount(new FunctionName(ksName, functionName)) > 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 onUpdateKeyspace(String ksName) { } public void onUpdateColumnFamily(String ksName, String cfName) { } public void onUpdateUserType(String ksName, String typeName) { } - public void onUpdateFunction(String namespace, String functionName) { } + public void onUpdateFunction(String ksName, String functionName) { } public void onDropKeyspace(String ksName) { @@ -608,6 +618,17 @@ public class QueryProcessor implements QueryHandler } public void onDropUserType(String ksName, String typeName) { } - public void onDropFunction(String namespace, String functionName) { } - } + public void onDropFunction(String ksName, String functionName) { + removeInvalidPreparedStatementsForFunction(preparedStatements.values().iterator(), ksName, functionName); + removeInvalidPreparedStatementsForFunction(thriftPreparedStatements.values().iterator(), ksName, functionName); + } + + private void removeInvalidPreparedStatementsForFunction(Iterator<ParsedStatement.Prepared> iterator, + String ksName, String functionName) + { + while (iterator.hasNext()) + if (iterator.next().statement.usesFunction(ksName, functionName)) + iterator.remove(); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/Term.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/Term.java b/src/java/org/apache/cassandra/cql3/Term.java index 3f4d728..7e20df8 100644 --- a/src/java/org/apache/cassandra/cql3/Term.java +++ b/src/java/org/apache/cassandra/cql3/Term.java @@ -67,6 +67,8 @@ public interface Term */ public abstract boolean containsBindMarker(); + boolean usesFunction(String ksName, String functionName); + /** * A parsed, non prepared (thus untyped) term. * @@ -115,6 +117,11 @@ public interface Term public void collectMarkerSpecification(VariableSpecifications boundNames) {} public Terminal bind(QueryOptions options) { return this; } + public boolean usesFunction(String ksName, String functionName) + { + return false; + } + // While some NonTerminal may not have bind markers, no Term can be Terminal // with a bind marker public boolean containsBindMarker() @@ -156,6 +163,11 @@ public interface Term */ public abstract class NonTerminal implements Term { + public boolean usesFunction(String ksName, String functionName) + { + return false; + } + public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException { Terminal t = bind(options); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/UserTypes.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/UserTypes.java b/src/java/org/apache/cassandra/cql3/UserTypes.java index 22063ff..934344c 100644 --- a/src/java/org/apache/cassandra/cql3/UserTypes.java +++ b/src/java/org/apache/cassandra/cql3/UserTypes.java @@ -148,6 +148,15 @@ public abstract class UserTypes this.values = values; } + public boolean usesFunction(String ksName, String functionName) + { + if (values != null) + for (Term value : values) + if (value != null && value.usesFunction(ksName, functionName)) + return true; + return false; + } + public boolean containsBindMarker() { for (Term t : values) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index 3b80fc0..efaa12a 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -41,6 +41,11 @@ public class FunctionCall extends Term.NonTerminal this.terms = terms; } + public boolean usesFunction(String ksName, String functionName) + { + return fun.name().keyspace.equals(ksName) && fun.name().name.equals(functionName); + } + public void collectMarkerSpecification(VariableSpecifications boundNames) { for (Term t : terms) @@ -54,7 +59,7 @@ public class FunctionCall extends Term.NonTerminal public ByteBuffer bindAndGet(QueryOptions options) throws InvalidRequestException { - List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(terms.size()); + List<ByteBuffer> buffers = new ArrayList<>(terms.size()); for (Term t : terms) { // For now, we don't allow nulls as argument as no existing function needs it and it @@ -110,7 +115,7 @@ public class FunctionCall extends Term.NonTerminal public static class Raw implements Term.Raw { - private final FunctionName name; + private FunctionName name; private final List<Term.Raw> terms; public Raw(FunctionName name, List<Term.Raw> terms) @@ -140,7 +145,7 @@ public class FunctionCall extends Term.NonTerminal throw new InvalidRequestException(String.format("Incorrect number of arguments specified for function %s (expected %d, found %d)", fun.name(), fun.argTypes().size(), terms.size())); - List<Term> parameters = new ArrayList<Term>(terms.size()); + List<Term> parameters = new ArrayList<>(terms.size()); boolean allTerminal = true; for (int i = 0; i < terms.size(); i++) { @@ -160,7 +165,7 @@ public class FunctionCall extends Term.NonTerminal // All parameters must be terminal private static ByteBuffer execute(ScalarFunction fun, List<Term> parameters) throws InvalidRequestException { - List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(parameters.size()); + List<ByteBuffer> buffers = new ArrayList<>(parameters.size()); for (Term t : parameters) { assert t instanceof Term.Terminal; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/functions/FunctionName.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionName.java b/src/java/org/apache/cassandra/cql3/functions/FunctionName.java index 814bbbf..460e7a6 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionName.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionName.java @@ -19,32 +19,40 @@ package org.apache.cassandra.cql3.functions; import com.google.common.base.Objects; -public class FunctionName +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.SystemKeyspace; + +public final class FunctionName { - public final String namespace; + public final String keyspace; public final String name; - // Use by toString rather than built from 'bundle' and 'name' so as to - // preserve the original case. - private final String displayName; + public static FunctionName nativeFunction(String name) + { + return new FunctionName(SystemKeyspace.NAME, name); + } - public FunctionName(String name) + public FunctionName(String keyspace, String name) { - this("", name); + assert name != null : "Name parameter must not be null"; + this.keyspace = keyspace != null ? keyspace : null; + this.name = name; } - public FunctionName(String namespace, String name) + public FunctionName asNativeFunction() { - this.namespace = namespace.toLowerCase(); - this.name = name.toLowerCase(); + return FunctionName.nativeFunction(name); + } - this.displayName = namespace.isEmpty() ? name : namespace + "::" + name; + public boolean hasKeyspace() + { + return keyspace != null; } @Override public final int hashCode() { - return Objects.hashCode(namespace, name); + return Objects.hashCode(keyspace, name); } @Override @@ -54,13 +62,13 @@ public class FunctionName return false; FunctionName that = (FunctionName)o; - return Objects.equal(this.namespace, that.namespace) + return Objects.equal(this.keyspace, that.keyspace) && Objects.equal(this.name, that.name); } @Override public String toString() { - return displayName; + return keyspace == null ? name : keyspace + "." + name; } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/functions/Functions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/Functions.java b/src/java/org/apache/cassandra/cql3/functions/Functions.java index 62de2bc..7021475 100644 --- a/src/java/org/apache/cassandra/cql3/functions/Functions.java +++ b/src/java/org/apache/cassandra/cql3/functions/Functions.java @@ -37,7 +37,7 @@ public abstract class Functions // We special case the token function because that's the only function whose argument types actually // depend on the table on which the function is called. Because it's the sole exception, it's easier // to handle it as a special case. - private static final FunctionName TOKEN_FUNCTION_NAME = new FunctionName("token"); + private static final FunctionName TOKEN_FUNCTION_NAME = FunctionName.nativeFunction("token"); private static final String SELECT_UDFS = "SELECT * FROM " + SystemKeyspace.NAME + '.' + SystemKeyspace.SCHEMA_FUNCTIONS_TABLE; @@ -108,6 +108,11 @@ public abstract class Functions fun.argTypes().get(i)); } + public static int getOverloadCount(FunctionName name) + { + return declared.get(name).size(); + } + public static Function get(String keyspace, FunctionName name, List<? extends AssignmentTestable> providedArgs, @@ -115,10 +120,25 @@ public abstract class Functions String receiverCf) throws InvalidRequestException { - if (name.equals(TOKEN_FUNCTION_NAME)) + if (name.hasKeyspace() + ? name.equals(TOKEN_FUNCTION_NAME) + : name.name.equals(TOKEN_FUNCTION_NAME.name)) return new TokenFct(Schema.instance.getCFMetaData(receiverKs, receiverCf)); - List<Function> candidates = declared.get(name); + List<Function> candidates; + if (!name.hasKeyspace()) + { + // function name not fully qualified + candidates = new ArrayList<>(); + // add 'SYSTEM' (native) candidates + candidates.addAll(declared.get(name.asNativeFunction())); + // add 'current keyspace' candidates + candidates.addAll(declared.get(new FunctionName(keyspace, name.name))); + } + else + // function name is fully qualified (keyspace + name) + candidates = declared.get(name); + if (candidates.isEmpty()) return null; @@ -165,6 +185,7 @@ public abstract class Functions public static Function find(FunctionName name, List<AbstractType<?>> argTypes) { + assert name.hasKeyspace() : "function name not fully qualified"; for (Function f : declared.get(name)) { if (f.argTypes().equals(argTypes)) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java b/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java index d658d9d..bff7688 100644 --- a/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/NativeFunction.java @@ -28,12 +28,7 @@ public abstract class NativeFunction extends AbstractFunction { protected NativeFunction(String name, AbstractType<?> returnType, AbstractType<?>... argTypes) { - this(new FunctionName(name), returnType, argTypes); - } - - protected NativeFunction(FunctionName name, AbstractType<?> returnType, AbstractType<?>... argTypes) - { - super(name, Arrays.asList(argTypes), returnType); + super(FunctionName.nativeFunction(name), Arrays.asList(argTypes), returnType); } // Most of our functions are pure, the other ones should override this @@ -47,4 +42,3 @@ public abstract class NativeFunction extends AbstractFunction return true; } } - http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/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 index bf011a7..42418c6 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -30,7 +30,6 @@ 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.*; @@ -152,8 +151,8 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct private static Mutation makeSchemaMutation(FunctionName name) { - CompositeType kv = (CompositeType)SystemKeyspace.SchemaFunctionsTable.getKeyValidator(); - return new Mutation(SystemKeyspace.NAME, kv.decompose(name.namespace, name.name)); + UTF8Type kv = (UTF8Type)SystemKeyspace.SchemaFunctionsTable.getKeyValidator(); + return new Mutation(SystemKeyspace.NAME, kv.decompose(name.keyspace)); } public Mutation toSchemaDrop(long timestamp) @@ -161,7 +160,7 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct Mutation mutation = makeSchemaMutation(name); ColumnFamily cf = mutation.addOrGet(SystemKeyspace.SCHEMA_FUNCTIONS_TABLE); - Composite prefix = SystemKeyspace.SchemaFunctionsTable.comparator.make(computeSignature(argTypes)); + Composite prefix = SystemKeyspace.SchemaFunctionsTable.comparator.make(name.name, computeSignature(argTypes)); int ldt = (int) (System.currentTimeMillis() / 1000); cf.addAtom(new RangeTombstone(prefix, prefix.end(), timestamp, ldt)); @@ -173,7 +172,7 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct Mutation mutation = makeSchemaMutation(name); ColumnFamily cf = mutation.addOrGet(SystemKeyspace.SCHEMA_FUNCTIONS_TABLE); - Composite prefix = SystemKeyspace.SchemaFunctionsTable.comparator.make(computeSignature(argTypes)); + Composite prefix = SystemKeyspace.SchemaFunctionsTable.comparator.make(name.name, computeSignature(argTypes)); CFRowAdder adder = new CFRowAdder(cf, prefix, timestamp); adder.resetCollection("argument_names"); @@ -194,9 +193,9 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct public static UDFunction fromSchema(UntypedResultSet.Row row) { - String namespace = row.getString("namespace"); - String fname = row.getString("name"); - FunctionName name = new FunctionName(namespace, fname); + String ksName = row.getString("keyspace_name"); + String functionName = row.getString("function_name"); + FunctionName name = new FunctionName(ksName, functionName); List<String> names = row.getList("argument_names", UTF8Type.instance); List<String> types = row.getList("argument_types", UTF8Type.instance); @@ -251,12 +250,12 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct } } - public static Map<ByteBuffer, UDFunction> fromSchema(Row row) + public static Map<Composite, UDFunction> fromSchema(Row row) { UntypedResultSet results = QueryProcessor.resultify("SELECT * FROM system." + SystemKeyspace.SCHEMA_FUNCTIONS_TABLE, row); - Map<ByteBuffer, UDFunction> udfs = new HashMap<>(results.size()); + Map<Composite, UDFunction> udfs = new HashMap<>(results.size()); for (UntypedResultSet.Row result : results) - udfs.put(result.getBlob("signature"), fromSchema(result)); + udfs.put(SystemKeyspace.SchemaFunctionsTable.comparator.make(result.getString("function_name"), result.getBlob("signature")), fromSchema(result)); return udfs; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java b/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java index 4660e1d..3778d41 100644 --- a/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java +++ b/src/java/org/apache/cassandra/cql3/selection/AbstractFunctionSelector.java @@ -49,7 +49,7 @@ abstract class AbstractFunctionSelector<T extends Function> extends Selector { if (factories.doesAggregation() && !factories.containsOnlyAggregateFunctions()) throw new InvalidRequestException(String.format("the %s function arguments must be either all aggregates or all none aggregates", - fun.name().name)); + fun.name())); } return new Factory() @@ -67,6 +67,11 @@ abstract class AbstractFunctionSelector<T extends Function> extends Selector return fun.returnType(); } + public boolean usesFunction(String ksName, String functionName) + { + return fun.name().keyspace.equals(ksName) && fun.name().name.equals(functionName); + } + public Selector newInstance() { return fun.isAggregate() ? new AggregateFunctionSelector(fun, factories.newInstances()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/selection/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selection.java b/src/java/org/apache/cassandra/cql3/selection/Selection.java index 7c3d34c..7c7dab7 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selection.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selection.java @@ -82,6 +82,11 @@ public abstract class Selection return columns.size() - 1; } + public boolean usesFunction(String ksName, String functionName) + { + return false; + } + private static boolean isUsingFunction(List<RawSelector> rawSelectors) { for (RawSelector rawSelector : rawSelectors) @@ -346,6 +351,11 @@ public abstract class Selection throw new InvalidRequestException("the select clause must either contains only aggregates or none"); } + public boolean usesFunction(String ksName, String functionName) + { + return factories.usesFunction(ksName, functionName); + } + public boolean isAggregate() { return factories.containsOnlyAggregateFunctions(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/selection/Selector.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selector.java b/src/java/org/apache/cassandra/cql3/selection/Selector.java index 889da70..f2c729b 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selector.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selector.java @@ -40,6 +40,11 @@ public abstract class Selector implements AssignmentTestable */ public static abstract class Factory { + public boolean usesFunction(String ksName, String functionName) + { + return false; + } + /** * Returns the column specification corresponding to the output value of the selector instances created by * this factory. http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/selection/SelectorFactories.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/SelectorFactories.java b/src/java/org/apache/cassandra/cql3/selection/SelectorFactories.java index 6922994..4d3e974 100644 --- a/src/java/org/apache/cassandra/cql3/selection/SelectorFactories.java +++ b/src/java/org/apache/cassandra/cql3/selection/SelectorFactories.java @@ -89,6 +89,14 @@ final class SelectorFactories implements Iterable<Selector.Factory> } } + public boolean usesFunction(String ksName, String functionName) + { + for (Factory factory : factories) + if (factory != null && factory.usesFunction(ksName, functionName)) + return true; + return false; + } + /** * Checks if this <code>SelectorFactories</code> contains only factories for aggregates. * http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java index f0874c1..2db00df 100644 --- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java @@ -77,6 +77,16 @@ public class BatchStatement implements CQLStatement, MeasurableForPreparedCache this.hasConditions = hasConditions; } + public boolean usesFunction(String ksName, String functionName) + { + if (attrs.usesFunction(ksName, functionName)) + return true; + for (ModificationStatement statement : statements) + if (statement.usesFunction(ksName, functionName)) + return true; + return false; + } + public long measureForPreparedCache(MemoryMeter meter) { long size = meter.measure(this) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/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 712a474..c41fb08 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateFunctionStatement.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.List; import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.functions.*; @@ -31,6 +32,7 @@ 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.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; /** @@ -40,7 +42,7 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement { private final boolean orReplace; private final boolean ifNotExists; - private final FunctionName functionName; + private FunctionName functionName; private final String language; private final String body; private final boolean deterministic; @@ -70,17 +72,31 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement this.ifNotExists = ifNotExists; } - public void checkAccess(ClientState state) throws UnauthorizedException + public void prepareKeyspace(ClientState state) throws InvalidRequestException + { + if (!functionName.hasKeyspace() && state.getRawKeyspace() != null) + functionName = new FunctionName(state.getKeyspace(), functionName.name); + + if (!functionName.hasKeyspace()) + throw new InvalidRequestException("You need to be logged in a keyspace or use a fully qualified function name"); + + ThriftValidation.validateKeyspaceNotSystem(functionName.keyspace); + } + + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { // TODO CASSANDRA-7557 (function DDL permission) - state.hasAllKeyspacesAccess(Permission.CREATE); + state.hasKeyspaceAccess(functionName.keyspace, Permission.CREATE); } public void validate(ClientState state) throws InvalidRequestException { if (ifNotExists && orReplace) throw new InvalidRequestException("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives"); + + if (Schema.instance.getKSMetaData(functionName.keyspace) == null) + throw new InvalidRequestException(String.format("Cannot add function '%s' to non existing keyspace '%s'.", functionName.name, functionName.keyspace)); } public Event.SchemaChange changeEvent() @@ -98,7 +114,7 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement 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()); + argTypes.add(rawType.prepare(functionName.keyspace).getType()); AbstractType<?> returnType = rawReturnType.prepare(null).getType(); @@ -110,10 +126,6 @@ public final class CreateFunctionStatement extends SchemaAlteringStatement 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())); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/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 78c8607..5aaf9b1 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropFunctionStatement.java @@ -29,6 +29,7 @@ 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.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; /** @@ -36,7 +37,7 @@ import org.apache.cassandra.transport.Event; */ public final class DropFunctionStatement extends SchemaAlteringStatement { - private final FunctionName functionName; + private FunctionName functionName; private final boolean ifExists; private final List<CQL3Type.Raw> argRawTypes; private final boolean argsPresent; @@ -52,11 +53,22 @@ public final class DropFunctionStatement extends SchemaAlteringStatement this.ifExists = ifExists; } - public void checkAccess(ClientState state) throws UnauthorizedException + public void prepareKeyspace(ClientState state) throws InvalidRequestException + { + if (!functionName.hasKeyspace() && state.getRawKeyspace() != null) + functionName = new FunctionName(state.getKeyspace(), functionName.name); + + if (!functionName.hasKeyspace()) + throw new InvalidRequestException("You need to be logged in a keyspace or use a fully qualified function name"); + + ThriftValidation.validateKeyspaceNotSystem(functionName.keyspace); + } + + public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException { // TODO CASSANDRA-7557 (function DDL permission) - state.hasAllKeyspacesAccess(Permission.DROP); + state.hasKeyspaceAccess(functionName.keyspace, Permission.DROP); } /** @@ -88,11 +100,7 @@ public final class DropFunctionStatement extends SchemaAlteringStatement 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()); - } + argTypes.add(rawType.prepare(functionName.keyspace).getType()); Function old; if (argsPresent) @@ -125,10 +133,6 @@ public final class DropFunctionStatement extends SchemaAlteringStatement old = olds.get(0); } - if (old.isNative()) - throw new InvalidRequestException(String.format("Cannot drop function '%s' because it is a " + - "native (built-in) function", functionName)); - MigrationManager.announceFunctionDrop((UDFunction)old, isLocalOnly); return true; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index 61f6401..7dc9c66 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -22,7 +22,6 @@ import java.util.*; import com.google.common.base.Function; import com.google.common.collect.Iterables; -import org.apache.cassandra.db.marshal.AbstractType; import org.github.jamm.MemoryMeter; import org.apache.cassandra.auth.Permission; @@ -88,6 +87,25 @@ public abstract class ModificationStatement implements CQLStatement, MeasurableF this.attrs = attrs; } + public boolean usesFunction(String ksName, String functionName) + { + if (attrs.usesFunction(ksName, functionName)) + return true; + for (Restriction restriction : processedKeys.values()) + if (restriction != null && restriction.usesFunction(ksName, functionName)) + return true; + for (Operation operation : columnOperations) + if (operation != null && operation.usesFunction(ksName, functionName)) + return true; + for (ColumnCondition condition : columnConditions) + if (condition != null && condition.usesFunction(ksName, functionName)) + return true; + for (ColumnCondition condition : staticConditions) + if (condition != null && condition.usesFunction(ksName, functionName)) + return true; + return false; + } + public long measureForPreparedCache(MemoryMeter meter) { return meter.measure(this) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java b/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java index d048327..bcce9ce 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ParsedStatement.java @@ -61,4 +61,9 @@ public abstract class ParsedStatement this(statement, Collections.<ColumnSpecification>emptyList()); } } + + public boolean usesFunction(String ksName, String functionName) + { + return false; + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/statements/Restriction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/Restriction.java b/src/java/org/apache/cassandra/cql3/statements/Restriction.java index 659ed95..b264156 100644 --- a/src/java/org/apache/cassandra/cql3/statements/Restriction.java +++ b/src/java/org/apache/cassandra/cql3/statements/Restriction.java @@ -49,6 +49,8 @@ public interface Restriction // Not supported by Slice, but it's convenient to have here public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException; + boolean usesFunction(String ksName, String functionName); + public static interface EQ extends Restriction {} public static interface IN extends Restriction http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index f214774..621c4db 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -56,8 +56,6 @@ import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Encapsulates a completely parsed SELECT query, including the target @@ -66,8 +64,6 @@ import org.slf4j.LoggerFactory; */ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache { - private static final Logger logger = LoggerFactory.getLogger(SelectStatement.class); - private static final int DEFAULT_COUNT_PAGE_SIZE = 10000; private final int boundTerms; @@ -125,6 +121,24 @@ public class SelectStatement implements CQLStatement, MeasurableForPreparedCache initStaticColumnsInfo(); } + public boolean usesFunction(String ksName, String functionName) + { + if (selection.usesFunction(ksName, functionName)) + return true; + if (limit != null && limit.usesFunction(ksName, functionName)) + return true; + for (Restriction restriction : metadataRestrictions.values()) + if (restriction != null && restriction.usesFunction(ksName, functionName)) + return true; + for (Restriction restriction : keyRestrictions) + if (restriction != null && restriction.usesFunction(ksName, functionName)) + return true; + for (Restriction restriction : columnRestrictions) + if (restriction != null && restriction.usesFunction(ksName, functionName)) + return true; + return false; + } + private void initStaticColumnsInfo() { if (!cfm.hasStaticColumns()) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java index b1c6ccc..b6ca640 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java +++ b/src/java/org/apache/cassandra/cql3/statements/SingleColumnRestriction.java @@ -43,6 +43,11 @@ public abstract class SingleColumnRestriction implements Restriction this.onToken = onToken; } + public boolean usesFunction(String ksName, String functionName) + { + return value != null && value.usesFunction(ksName, functionName); + } + public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException { return Collections.singletonList(value.bindAndGet(options)); @@ -94,6 +99,15 @@ public abstract class SingleColumnRestriction implements Restriction this.values = values; } + public boolean usesFunction(String ksName, String functionName) + { + if (values != null) + for (Term value : values) + if (value != null && value.usesFunction(ksName, functionName)) + return true; + return false; + } + public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException { List<ByteBuffer> buffers = new ArrayList<>(values.size()); @@ -153,6 +167,11 @@ public abstract class SingleColumnRestriction implements Restriction this.marker = marker; } + public boolean usesFunction(String ksName, String functionName) + { + return false; + } + public List<ByteBuffer> values(QueryOptions options) throws InvalidRequestException { Term.MultiItemTerminal lval = (Term.MultiItemTerminal)marker.bind(options); @@ -216,6 +235,14 @@ public abstract class SingleColumnRestriction implements Restriction this.onToken = onToken; } + public boolean usesFunction(String ksName, String functionName) + { + for (Term value : bounds) + if (value != null && value.usesFunction(ksName, functionName)) + return true; + return false; + } + public boolean isSlice() { return true; @@ -343,6 +370,19 @@ public abstract class SingleColumnRestriction implements Restriction private List<Term> values; // for CONTAINS private List<Term> keys; // for CONTAINS_KEY + public boolean usesFunction(String ksName, String functionName) + { + if (values != null) + for (Term value : values) + if (value != null && value.usesFunction(ksName, functionName)) + return true; + if (keys != null) + for (Term key : keys) + if (key != null && key.usesFunction(ksName, functionName)) + return true; + return false; + } + public boolean hasContains() { return values != null; http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/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 a02f65e..bcb0893 100644 --- a/src/java/org/apache/cassandra/db/DefsTables.java +++ b/src/java/org/apache/cassandra/db/DefsTables.java @@ -37,6 +37,7 @@ 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.composites.Composite; import org.apache.cassandra.db.filter.QueryFilter; import org.apache.cassandra.db.marshal.AsciiType; import org.apache.cassandra.db.marshal.UserType; @@ -303,7 +304,7 @@ public class DefsTables MapDifference<DecoratedKey, ColumnFamily> diff = Maps.difference(before, after); - // New namespace with functions + // New keyspace with functions for (Map.Entry<DecoratedKey, ColumnFamily> entry : diff.entriesOnlyOnRight().entrySet()) if (entry.getValue().hasColumns()) created.addAll(UDFunction.fromSchema(new Row(entry.getKey(), entry.getValue())).values()); @@ -315,7 +316,7 @@ public class DefsTables if (pre.hasColumns() && post.hasColumns()) { - MapDifference<ByteBuffer, UDFunction> delta = + MapDifference<Composite, UDFunction> delta = Maps.difference(UDFunction.fromSchema(new Row(entry.getKey(), pre)), UDFunction.fromSchema(new Row(entry.getKey(), post))); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/db/SystemKeyspace.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java index 49c1502..7806d5f 100644 --- a/src/java/org/apache/cassandra/db/SystemKeyspace.java +++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java @@ -182,11 +182,12 @@ public final class SystemKeyspace + "PRIMARY KEY ((keyspace_name), type_name))") .gcGraceSeconds(WEEK); + public static final CFMetaData SchemaFunctionsTable = compile(SCHEMA_FUNCTIONS_TABLE, "user defined function definitions", "CREATE TABLE %s (" - + "namespace text," - + "name text," + + "keyspace_name text," + + "function_name text," + "signature blob," + "argument_names list<text>," + "argument_types list<text>," @@ -194,7 +195,7 @@ public final class SystemKeyspace + "deterministic boolean," + "language text," + "return_type text," - + "PRIMARY KEY ((namespace, name), signature))") + + "PRIMARY KEY ((keyspace_name), function_name, signature))") .gcGraceSeconds(WEEK); public static final CFMetaData BuiltIndexesTable = http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/service/IMigrationListener.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/IMigrationListener.java b/src/java/org/apache/cassandra/service/IMigrationListener.java index b4eb392..bc67e8a 100644 --- a/src/java/org/apache/cassandra/service/IMigrationListener.java +++ b/src/java/org/apache/cassandra/service/IMigrationListener.java @@ -22,16 +22,16 @@ public interface IMigrationListener public void onCreateKeyspace(String ksName); public void onCreateColumnFamily(String ksName, String cfName); public void onCreateUserType(String ksName, String typeName); - public void onCreateFunction(String namespace, String functionName); + public void onCreateFunction(String ksName, String functionName); public void onUpdateKeyspace(String ksName); public void onUpdateColumnFamily(String ksName, String cfName); public void onUpdateUserType(String ksName, String typeName); - public void onUpdateFunction(String namespace, String functionName); + public void onUpdateFunction(String ksName, String functionName); public void onDropKeyspace(String ksName); public void onDropColumnFamily(String ksName, String cfName); public void onDropUserType(String ksName, String typeName); - public void onDropFunction(String namespace, String functionName); + public void onDropFunction(String ksName, String functionName); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/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 a5d4628..8c3199f 100644 --- a/src/java/org/apache/cassandra/service/MigrationManager.java +++ b/src/java/org/apache/cassandra/service/MigrationManager.java @@ -38,8 +38,11 @@ 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.AggregateFunction; +import org.apache.cassandra.cql3.functions.ScalarFunction; import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.db.*; +import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.exceptions.AlreadyExistsException; import org.apache.cassandra.exceptions.ConfigurationException; @@ -177,19 +180,36 @@ public class MigrationManager public void notifyCreateFunction(UDFunction udf) { for (IMigrationListener listener : listeners) - listener.onCreateFunction(udf.name().namespace, udf.name().name); + listener.onCreateFunction(udf.name().keyspace, udf.name().name); } public void notifyUpdateFunction(UDFunction udf) { for (IMigrationListener listener : listeners) - listener.onUpdateFunction(udf.name().namespace, udf.name().name); + listener.onUpdateFunction(udf.name().keyspace, udf.name().name); } public void notifyDropFunction(UDFunction udf) { for (IMigrationListener listener : listeners) - listener.onDropFunction(udf.name().namespace, udf.name().name); + listener.onDropFunction(udf.name().keyspace, udf.name().name); + } + + private List<String> asString(List<AbstractType<?>> abstractTypes) + { + List<String> r = new ArrayList<>(abstractTypes.size()); + for (AbstractType<?> abstractType : abstractTypes) + r.add(abstractType.asCQL3Type().toString()); + return r; + } + + private String udType(UDFunction udf) + { + if (udf instanceof ScalarFunction) + return "scalar"; + if (udf instanceof AggregateFunction) + return "aggregate"; + return ""; } public void notifyUpdateKeyspace(KSMetaData ksm) http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/transport/Event.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/transport/Event.java b/src/java/org/apache/cassandra/transport/Event.java index 85943cf..9962599 100644 --- a/src/java/org/apache/cassandra/transport/Event.java +++ b/src/java/org/apache/cassandra/transport/Event.java @@ -208,15 +208,15 @@ public abstract class Event public final Change change; public final Target target; - public final String keyOrNamespace; + public final String keyspace; public final String tableOrTypeOrFunction; - public SchemaChange(Change change, Target target, String keyOrNamespace, String tableOrTypeOrFunction) + public SchemaChange(Change change, Target target, String keyspace, String tableOrTypeOrFunction) { super(Type.SCHEMA_CHANGE); this.change = change; this.target = target; - this.keyOrNamespace = keyOrNamespace; + this.keyspace = keyspace; this.tableOrTypeOrFunction = tableOrTypeOrFunction; if (target != Target.KEYSPACE) assert this.tableOrTypeOrFunction != null : "Table or type should be set for non-keyspace schema change events"; @@ -252,7 +252,7 @@ public abstract class Event { CBUtil.writeEnumValue(change, dest); CBUtil.writeEnumValue(target, dest); - CBUtil.writeString(keyOrNamespace, dest); + CBUtil.writeString(keyspace, dest); if (target != Target.KEYSPACE) CBUtil.writeString(tableOrTypeOrFunction, dest); } @@ -263,13 +263,13 @@ public abstract class Event // For the v1/v2 protocol, we have no way to represent type changes, so we simply say the keyspace // was updated. See CASSANDRA-7617. CBUtil.writeEnumValue(Change.UPDATED, dest); - CBUtil.writeString(keyOrNamespace, dest); + CBUtil.writeString(keyspace, dest); CBUtil.writeString("", dest); } else { CBUtil.writeEnumValue(change, dest); - CBUtil.writeString(keyOrNamespace, dest); + CBUtil.writeString(keyspace, dest); CBUtil.writeString(target == Target.KEYSPACE ? "" : tableOrTypeOrFunction, dest); } } @@ -281,7 +281,7 @@ public abstract class Event { int size = CBUtil.sizeOfEnumValue(change) + CBUtil.sizeOfEnumValue(target) - + CBUtil.sizeOfString(keyOrNamespace); + + CBUtil.sizeOfString(keyspace); if (target != Target.KEYSPACE) size += CBUtil.sizeOfString(tableOrTypeOrFunction); @@ -293,11 +293,11 @@ public abstract class Event if (target == Target.TYPE) { return CBUtil.sizeOfEnumValue(Change.UPDATED) - + CBUtil.sizeOfString(keyOrNamespace) + + CBUtil.sizeOfString(keyspace) + CBUtil.sizeOfString(""); } return CBUtil.sizeOfEnumValue(change) - + CBUtil.sizeOfString(keyOrNamespace) + + CBUtil.sizeOfString(keyspace) + CBUtil.sizeOfString(target == Target.KEYSPACE ? "" : tableOrTypeOrFunction); } } @@ -305,13 +305,13 @@ public abstract class Event @Override public String toString() { - return change + " " + target + " " + keyOrNamespace + (tableOrTypeOrFunction == null ? "" : "." + tableOrTypeOrFunction); + return change + " " + target + " " + keyspace + (tableOrTypeOrFunction == null ? "" : "." + tableOrTypeOrFunction); } @Override public int hashCode() { - return Objects.hashCode(change, target, keyOrNamespace, tableOrTypeOrFunction); + return Objects.hashCode(change, target, keyspace, tableOrTypeOrFunction); } @Override @@ -323,7 +323,7 @@ public abstract class Event SchemaChange scc = (SchemaChange)other; return Objects.equal(change, scc.change) && Objects.equal(target, scc.target) - && Objects.equal(keyOrNamespace, scc.keyOrNamespace) + && Objects.equal(keyspace, scc.keyspace) && Objects.equal(tableOrTypeOrFunction, scc.tableOrTypeOrFunction); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/src/java/org/apache/cassandra/transport/Server.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/transport/Server.java b/src/java/org/apache/cassandra/transport/Server.java index f8822a5..15fad88 100644 --- a/src/java/org/apache/cassandra/transport/Server.java +++ b/src/java/org/apache/cassandra/transport/Server.java @@ -27,8 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; @@ -411,7 +409,7 @@ public class Server implements CassandraDaemon.Server server.connectionTracker.send(new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TYPE, ksName, typeName)); } - public void onCreateFunction(String namespace, String functionName) + public void onCreateFunction(String ksName, String functionName) { } @@ -430,7 +428,7 @@ public class Server implements CassandraDaemon.Server server.connectionTracker.send(new Event.SchemaChange(Event.SchemaChange.Change.UPDATED, Event.SchemaChange.Target.TYPE, ksName, typeName)); } - public void onUpdateFunction(String namespace, String functionName) + public void onUpdateFunction(String ksName, String functionName) { } @@ -449,7 +447,7 @@ public class Server implements CassandraDaemon.Server server.connectionTracker.send(new Event.SchemaChange(Event.SchemaChange.Change.DROPPED, Event.SchemaChange.Target.TYPE, ksName, typeName)); } - public void onDropFunction(String namespace, String functionName) + public void onDropFunction(String ksName, String functionName) { } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/test/unit/org/apache/cassandra/cql3/AggregationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/AggregationTest.java b/test/unit/org/apache/cassandra/cql3/AggregationTest.java index 99db62a..859fe65 100644 --- a/test/unit/org/apache/cassandra/cql3/AggregationTest.java +++ b/test/unit/org/apache/cassandra/cql3/AggregationTest.java @@ -37,7 +37,7 @@ public class AggregationTest extends CQLTester assertColumnNames(execute("SELECT COUNT(*) FROM %s"), "count"); assertRows(execute("SELECT COUNT(*) FROM %s"), row(0L)); assertColumnNames(execute("SELECT max(b), min(b), sum(b), avg(b) , max(c), sum(c), avg(c), sum(d), avg(d) FROM %s"), - "max(b)", "min(b)", "sum(b)", "avg(b)" , "max(c)", "sum(c)", "avg(c)", "sum(d)", "avg(d)"); + "system.max(b)", "system.min(b)", "system.sum(b)", "system.avg(b)" , "system.max(c)", "system.sum(c)", "system.avg(c)", "system.sum(d)", "system.avg(d)"); assertRows(execute("SELECT max(b), min(b), sum(b), avg(b) , max(c), sum(c), avg(c), sum(d), avg(d) FROM %s"), row(null, null, 0, 0, null, 0.0, 0.0, new BigDecimal("0"), new BigDecimal("0"))); @@ -94,15 +94,15 @@ public class AggregationTest extends CQLTester { createTable("CREATE TABLE %s (a int primary key, b timeuuid, c double, d double)"); - execute("CREATE OR REPLACE FUNCTION copySign(magnitude double, sign double) RETURNS double LANGUAGE JAVA\n" + + execute("CREATE OR REPLACE FUNCTION "+KEYSPACE+".copySign(magnitude double, sign double) RETURNS double LANGUAGE JAVA\n" + "AS 'return Double.valueOf(Math.copySign(magnitude.doubleValue(), sign.doubleValue()));';"); - assertColumnNames(execute("SELECT max(a), max(unixTimestampOf(b)) FROM %s"), "max(a)", "max(unixtimestampof(b))"); + assertColumnNames(execute("SELECT max(a), max(unixTimestampOf(b)) FROM %s"), "system.max(a)", "system.max(system.unixtimestampof(b))"); assertRows(execute("SELECT max(a), max(unixTimestampOf(b)) FROM %s"), row(null, null)); - assertColumnNames(execute("SELECT max(a), unixTimestampOf(max(b)) FROM %s"), "max(a)", "unixtimestampof(max(b))"); + assertColumnNames(execute("SELECT max(a), unixTimestampOf(max(b)) FROM %s"), "system.max(a)", "system.unixtimestampof(system.max(b))"); assertRows(execute("SELECT max(a), unixTimestampOf(max(b)) FROM %s"), row(null, null)); - assertColumnNames(execute("SELECT max(copySign(c, d)) FROM %s"), "max(copysign(c, d))"); + assertColumnNames(execute("SELECT max(copySign(c, d)) FROM %s"), "system.max("+KEYSPACE+".copysign(c, d))"); assertRows(execute("SELECT max(copySign(c, d)) FROM %s"), row((Object) null)); execute("INSERT INTO %s (a, b, c, d) VALUES (1, maxTimeuuid('2011-02-03 04:05:00+0000'), -1.2, 2.1)"); http://git-wip-us.apache.org/repos/asf/cassandra/blob/b4d7f3be/test/unit/org/apache/cassandra/cql3/PgStringTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/PgStringTest.java b/test/unit/org/apache/cassandra/cql3/PgStringTest.java index 856a255..1870a9a 100644 --- a/test/unit/org/apache/cassandra/cql3/PgStringTest.java +++ b/test/unit/org/apache/cassandra/cql3/PgStringTest.java @@ -26,7 +26,7 @@ public class PgStringTest extends CQLTester @Test public void testPgSyleFunction() throws Throwable { - execute("create or replace function pg::pgfun1 ( input double ) returns text language java\n" + + execute("create or replace function "+KEYSPACE+".pgfun1 ( input double ) returns text language java\n" + "AS $$return \"foobar\";$$"); } @@ -70,7 +70,7 @@ public class PgStringTest extends CQLTester public void testMarkerPgFail() throws Throwable { // must throw SyntaxException - not StringIndexOutOfBoundsException or similar - execute("create function foo::pgfun1 ( input double ) returns text language java\n" + + execute("create function "+KEYSPACE+".pgfun1 ( input double ) returns text language java\n" + "AS $javasrc$return 0L;$javasrc$;"); } }