mirror of https://github.com/apache/cassandra
Remove scripted UDFs, remove related tests or rewrite some tests to create java UDFs instead, where it makes sense
patch by Ekaterina Dimitrova; reviewed by Michael Semb Wever for CASSANDRA-18252
This commit is contained in:
parent
ff6ad4e48a
commit
a0ec0ae303
|
|
@ -1,4 +1,5 @@
|
|||
4.2
|
||||
* Remove Scripted UDFs internals; hooks to be added later in CASSANDRA-17281 (CASSANDRA-18252)
|
||||
* Update JNA to 5.13.0 (CASSANDRA-18050)
|
||||
* Make virtual tables decide if they implicitly enable ALLOW FILTERING (CASSANDRA-18238)
|
||||
* Add row, tombstone, and sstable count to nodetool profileload (CASSANDRA-18022)
|
||||
|
|
|
|||
|
|
@ -1455,12 +1455,6 @@ trace_type_repair_ttl: 7d
|
|||
# As of Cassandra 3.0 there is a sandbox in place that should prevent execution of evil code.
|
||||
user_defined_functions_enabled: false
|
||||
|
||||
# Enables scripted UDFs (JavaScript UDFs).
|
||||
# Java UDFs are always enabled, if user_defined_functions_enabled is true.
|
||||
# Enable this option to be able to use UDFs with "language javascript" or any custom JSR-223 provider.
|
||||
# This option has no effect, if user_defined_functions_enabled is false.
|
||||
scripted_user_defined_functions_enabled: false
|
||||
|
||||
# Enables encrypting data at-rest (on disk). Different key providers can be plugged in, but the default reads from
|
||||
# a JCE-style keystore. A single keystore can hold multiple keys, but the one referenced by
|
||||
# the "key_alias" is the only key that will be used for encrypt opertaions; previously used keys
|
||||
|
|
|
|||
|
|
@ -351,8 +351,7 @@ Examples:
|
|||
==== User-defined functions
|
||||
|
||||
User-defined functions (UDFs) execute user-provided code in Cassandra.
|
||||
By default, Cassandra supports defining functions in _Java_ and _JavaScript_.
|
||||
Support for other JSR 223 compliant scripting languages, such as Python, Ruby, and Scala, is possible by adding a JAR to the classpath.
|
||||
By default, Cassandra supports defining functions in _Java_.
|
||||
|
||||
UDFs are part of the Cassandra schema, and are automatically propagated to all nodes in the cluster.
|
||||
UDFs can be _overloaded_, so that multiple UDFs with different argument types can have the same function name.
|
||||
|
|
@ -361,8 +360,8 @@ UDFs can be _overloaded_, so that multiple UDFs with different argument types ca
|
|||
[NOTE]
|
||||
.Note
|
||||
====
|
||||
_JavaScript_ user-defined functions have been deprecated. They are planned for removal
|
||||
in the next major release.
|
||||
_JavaScript_ user-defined functions have been deprecated in Cassandra 4.1. In preparation for Cassandra 5.0, their removal is
|
||||
already in progress. For more information - CASSANDRA-17281, CASSANDRA-18252.
|
||||
====
|
||||
|
||||
For example:
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ fi
|
|||
ccm remove test || true # in case an old ccm cluster is left behind
|
||||
ccm create test -n 1 --install-dir=${CASSANDRA_DIR}
|
||||
ccm updateconf "user_defined_functions_enabled: true"
|
||||
ccm updateconf "scripted_user_defined_functions_enabled: true"
|
||||
|
||||
version_from_build=$(ccm node1 versionfrombuild)
|
||||
export pre_or_post_cdc=$(python -c """from distutils.version import LooseVersion
|
||||
|
|
|
|||
|
|
@ -564,6 +564,8 @@ public class Config
|
|||
|
||||
@Replaces(oldName = "enable_user_defined_functions", converter = Converters.IDENTITY, deprecated = true)
|
||||
public boolean user_defined_functions_enabled = false;
|
||||
|
||||
@Deprecated
|
||||
@Replaces(oldName = "enable_scripted_user_defined_functions", converter = Converters.IDENTITY, deprecated = true)
|
||||
public boolean scripted_user_defined_functions_enabled = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -814,7 +814,8 @@ public class DatabaseDescriptor
|
|||
logger.warn("Allowing java.lang.System.* access in UDFs is dangerous and not recommended. Set allow_extra_insecure_udfs: false to disable.");
|
||||
|
||||
if(conf.scripted_user_defined_functions_enabled)
|
||||
logger.warn("JavaScript user-defined functions have been deprecated. You can still use them but the plan is to remove them in the next major version. For more information - CASSANDRA-17280");
|
||||
throw new ConfigurationException("JavaScript user-defined functions were removed in CASSANDRA-18252. " +
|
||||
"Hooks are planned to be introduced as part of CASSANDRA-17280");
|
||||
|
||||
if (conf.commitlog_segment_size.toMebibytes() == 0)
|
||||
throw new ConfigurationException("commitlog_segment_size must be positive, but was "
|
||||
|
|
@ -3579,11 +3580,6 @@ public class DatabaseDescriptor
|
|||
return conf.scripted_user_defined_functions_enabled;
|
||||
}
|
||||
|
||||
public static void enableScriptedUserDefinedFunctions(boolean enableScriptedUserDefinedFunctions)
|
||||
{
|
||||
conf.scripted_user_defined_functions_enabled = enableScriptedUserDefinedFunctions;
|
||||
}
|
||||
|
||||
public static boolean enableUserDefinedFunctionsThreads()
|
||||
{
|
||||
return conf.user_defined_functions_threads_enabled;
|
||||
|
|
|
|||
|
|
@ -1,346 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.cql3.functions;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import javax.script.*;
|
||||
|
||||
import jdk.nashorn.api.scripting.AbstractJSObject;
|
||||
import jdk.nashorn.api.scripting.ClassFilter;
|
||||
import jdk.nashorn.api.scripting.NashornScriptEngine;
|
||||
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
|
||||
import org.apache.cassandra.concurrent.NamedThreadFactory;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.security.SecurityThreadGroup;
|
||||
import org.apache.cassandra.security.ThreadAwareSecurityManager;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
|
||||
final class ScriptBasedUDFunction extends UDFunction
|
||||
{
|
||||
private static final ProtectionDomain protectionDomain;
|
||||
private static final AccessControlContext accessControlContext;
|
||||
|
||||
//
|
||||
// For scripted UDFs we have to rely on the security mechanisms of the scripting engine and
|
||||
// SecurityManager - especially SecurityManager.checkPackageAccess(). Unlike Java-UDFs, strict checking
|
||||
// of class access via the UDF class loader is not possible, since e.g. Nashorn builds its own class loader
|
||||
// (jdk.nashorn.internal.runtime.ScriptLoader / jdk.nashorn.internal.runtime.NashornLoader) configured with
|
||||
// a system class loader.
|
||||
//
|
||||
private static final String[] allowedPackagesArray =
|
||||
{
|
||||
// following required by jdk.nashorn.internal.objects.Global.initJavaAccess()
|
||||
"",
|
||||
"com",
|
||||
"edu",
|
||||
"java",
|
||||
"javax",
|
||||
"javafx",
|
||||
"org",
|
||||
// following required by Nashorn runtime
|
||||
"java.lang",
|
||||
"java.lang.invoke",
|
||||
"java.lang.reflect",
|
||||
"java.nio.charset",
|
||||
"java.util",
|
||||
"java.util.concurrent",
|
||||
"javax.script",
|
||||
"sun.reflect",
|
||||
"jdk.internal.org.objectweb.asm.commons",
|
||||
"jdk.nashorn.internal.runtime",
|
||||
"jdk.nashorn.internal.runtime.linker",
|
||||
// Nashorn / Java 11
|
||||
"java.lang.ref",
|
||||
"java.io",
|
||||
"java.util.function",
|
||||
"jdk.dynalink.linker",
|
||||
"jdk.internal.org.objectweb.asm",
|
||||
"jdk.internal.reflect",
|
||||
"jdk.nashorn.internal.scripts",
|
||||
// following required by Java Driver
|
||||
"java.math",
|
||||
"java.nio",
|
||||
"java.text",
|
||||
"com.google.common.base",
|
||||
"com.google.common.collect",
|
||||
"com.google.common.reflect",
|
||||
// following required by UDF
|
||||
"org.apache.cassandra.cql3.functions.types",
|
||||
"org.apache.cassandra.cql3.functions.types.exceptions",
|
||||
"org.apache.cassandra.cql3.functions.types.utils"
|
||||
};
|
||||
|
||||
// use a JVM standard ExecutorService as ExecutorPlus references internal
|
||||
// classes, which triggers AccessControlException from the UDF sandbox
|
||||
private static final UDFExecutorService executor =
|
||||
new UDFExecutorService(new NamedThreadFactory("UserDefinedScriptFunctions",
|
||||
Thread.MIN_PRIORITY,
|
||||
udfClassLoader,
|
||||
new SecurityThreadGroup("UserDefinedScriptFunctions",
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedPackagesArray))),
|
||||
UDFunction::initializeThread)),
|
||||
"userscripts");
|
||||
|
||||
private static final ClassFilter classFilter = clsName -> secureResource(clsName.replace('.', '/') + ".class");
|
||||
|
||||
private static final NashornScriptEngine scriptEngine;
|
||||
|
||||
|
||||
static
|
||||
{
|
||||
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
|
||||
ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn");
|
||||
NashornScriptEngineFactory factory = engine != null ? (NashornScriptEngineFactory) engine.getFactory() : null;
|
||||
scriptEngine = factory != null ? (NashornScriptEngine) factory.getScriptEngine(new String[]{}, udfClassLoader, classFilter) : null;
|
||||
|
||||
try
|
||||
{
|
||||
protectionDomain = new ProtectionDomain(new CodeSource(new URL("udf", "localhost", 0, "/script", new URLStreamHandler()
|
||||
{
|
||||
protected URLConnection openConnection(URL u)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}), (Certificate[]) null), ThreadAwareSecurityManager.noPermissions);
|
||||
}
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
accessControlContext = new AccessControlContext(new ProtectionDomain[]{ protectionDomain });
|
||||
}
|
||||
|
||||
private final CompiledScript script;
|
||||
private final Object udfContextBinding;
|
||||
|
||||
ScriptBasedUDFunction(FunctionName name,
|
||||
List<ColumnIdentifier> argNames,
|
||||
List<AbstractType<?>> argTypes,
|
||||
AbstractType<?> returnType,
|
||||
boolean calledOnNullInput,
|
||||
String language,
|
||||
String body)
|
||||
{
|
||||
super(name, argNames, argTypes, returnType, calledOnNullInput, language, body);
|
||||
|
||||
if (!"JavaScript".equalsIgnoreCase(language) || scriptEngine == null)
|
||||
throw new InvalidRequestException(String.format("Invalid language '%s' for function '%s'", language, name));
|
||||
|
||||
// execute compilation with no-permissions to prevent evil code e.g. via "static code blocks" / "class initialization"
|
||||
try
|
||||
{
|
||||
this.script = AccessController.doPrivileged((PrivilegedExceptionAction<CompiledScript>) () -> scriptEngine.compile(body),
|
||||
accessControlContext);
|
||||
}
|
||||
catch (PrivilegedActionException x)
|
||||
{
|
||||
Throwable e = x.getCause();
|
||||
logger.info("Failed to compile function '{}' for language {}: ", name, language, e);
|
||||
throw new InvalidRequestException(
|
||||
String.format("Failed to compile function '%s' for language %s: %s", name, language, e));
|
||||
}
|
||||
|
||||
// It's not always possible to simply pass a plain Java object as a binding to Nashorn and
|
||||
// let the script execute methods on it.
|
||||
udfContextBinding = new UDFContextWrapper();
|
||||
}
|
||||
|
||||
protected ExecutorService executor()
|
||||
{
|
||||
return executor;
|
||||
}
|
||||
|
||||
public ByteBuffer executeUserDefined(ProtocolVersion protocolVersion, List<ByteBuffer> parameters)
|
||||
{
|
||||
Object[] params = new Object[argTypes.size()];
|
||||
for (int i = 0; i < params.length; i++)
|
||||
params[i] = compose(protocolVersion, i, parameters.get(i));
|
||||
|
||||
Object result = executeScriptInternal(params);
|
||||
|
||||
return decompose(protocolVersion, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link UDFunction#executeUserDefined(ProtocolVersion, List)} but the first parameter is already in non-serialized form.
|
||||
* Remaining parameters (2nd paramters and all others) are in {@code parameters}.
|
||||
* This is used to prevent superfluous (de)serialization of the state of aggregates.
|
||||
* Means: scalar functions of aggregates are called using this variant.
|
||||
*/
|
||||
protected Object executeAggregateUserDefined(ProtocolVersion protocolVersion, Object firstParam, List<ByteBuffer> parameters)
|
||||
{
|
||||
Object[] params = new Object[argTypes.size()];
|
||||
params[0] = firstParam;
|
||||
for (int i = 1; i < params.length; i++)
|
||||
params[i] = compose(protocolVersion, i, parameters.get(i - 1));
|
||||
|
||||
return executeScriptInternal(params);
|
||||
}
|
||||
|
||||
private Object executeScriptInternal(Object[] params)
|
||||
{
|
||||
ScriptContext scriptContext = new SimpleScriptContext();
|
||||
scriptContext.setAttribute("javax.script.filename", this.name.toString(), ScriptContext.ENGINE_SCOPE);
|
||||
Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
|
||||
for (int i = 0; i < params.length; i++)
|
||||
bindings.put(argNames.get(i).toString(), params[i]);
|
||||
bindings.put("udfContext", udfContextBinding);
|
||||
|
||||
Object result;
|
||||
try
|
||||
{
|
||||
// How to prevent Class.forName() _without_ "help" from the script engine ?
|
||||
// NOTE: Nashorn enforces a special permission to allow class-loading, which is not granted - so it's fine.
|
||||
|
||||
result = script.eval(scriptContext);
|
||||
}
|
||||
catch (ScriptException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (result == null)
|
||||
return null;
|
||||
|
||||
Class<?> javaReturnType = UDHelper.asJavaClass(returnCodec);
|
||||
Class<?> resultType = result.getClass();
|
||||
if (!javaReturnType.isAssignableFrom(resultType))
|
||||
{
|
||||
if (result instanceof Number)
|
||||
{
|
||||
Number rNumber = (Number) result;
|
||||
if (javaReturnType == Integer.class)
|
||||
result = rNumber.intValue();
|
||||
else if (javaReturnType == Long.class)
|
||||
result = rNumber.longValue();
|
||||
else if (javaReturnType == Short.class)
|
||||
result = rNumber.shortValue();
|
||||
else if (javaReturnType == Byte.class)
|
||||
result = rNumber.byteValue();
|
||||
else if (javaReturnType == Float.class)
|
||||
result = rNumber.floatValue();
|
||||
else if (javaReturnType == Double.class)
|
||||
result = rNumber.doubleValue();
|
||||
else if (javaReturnType == BigInteger.class)
|
||||
{
|
||||
if (javaReturnType == Integer.class)
|
||||
result = rNumber.intValue();
|
||||
else if (javaReturnType == Short.class)
|
||||
result = rNumber.shortValue();
|
||||
else if (javaReturnType == Byte.class)
|
||||
result = rNumber.byteValue();
|
||||
else if (javaReturnType == Long.class)
|
||||
result = rNumber.longValue();
|
||||
else if (javaReturnType == Float.class)
|
||||
result = rNumber.floatValue();
|
||||
else if (javaReturnType == Double.class)
|
||||
result = rNumber.doubleValue();
|
||||
else if (javaReturnType == BigInteger.class)
|
||||
{
|
||||
if (rNumber instanceof BigDecimal)
|
||||
result = ((BigDecimal) rNumber).toBigInteger();
|
||||
else if (rNumber instanceof Double || rNumber instanceof Float)
|
||||
result = new BigDecimal(rNumber.toString()).toBigInteger();
|
||||
else
|
||||
result = BigInteger.valueOf(rNumber.longValue());
|
||||
}
|
||||
else if (javaReturnType == BigDecimal.class)
|
||||
// String c'tor of BigDecimal is more accurate than valueOf(double)
|
||||
result = new BigDecimal(rNumber.toString());
|
||||
}
|
||||
else if (javaReturnType == BigDecimal.class)
|
||||
// String c'tor of BigDecimal is more accurate than valueOf(double)
|
||||
result = new BigDecimal(rNumber.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private final class UDFContextWrapper extends AbstractJSObject
|
||||
{
|
||||
private final AbstractJSObject fRetUDT;
|
||||
private final AbstractJSObject fArgUDT;
|
||||
private final AbstractJSObject fRetTup;
|
||||
private final AbstractJSObject fArgTup;
|
||||
|
||||
UDFContextWrapper()
|
||||
{
|
||||
fRetUDT = new AbstractJSObject()
|
||||
{
|
||||
public Object call(Object thiz, Object... args)
|
||||
{
|
||||
return udfContext.newReturnUDTValue();
|
||||
}
|
||||
};
|
||||
fArgUDT = new AbstractJSObject()
|
||||
{
|
||||
public Object call(Object thiz, Object... args)
|
||||
{
|
||||
if (args[0] instanceof String)
|
||||
return udfContext.newArgUDTValue((String) args[0]);
|
||||
if (args[0] instanceof Number)
|
||||
return udfContext.newArgUDTValue(((Number) args[0]).intValue());
|
||||
return super.call(thiz, args);
|
||||
}
|
||||
};
|
||||
fRetTup = new AbstractJSObject()
|
||||
{
|
||||
public Object call(Object thiz, Object... args)
|
||||
{
|
||||
return udfContext.newReturnTupleValue();
|
||||
}
|
||||
};
|
||||
fArgTup = new AbstractJSObject()
|
||||
{
|
||||
public Object call(Object thiz, Object... args)
|
||||
{
|
||||
if (args[0] instanceof String)
|
||||
return udfContext.newArgTupleValue((String) args[0]);
|
||||
if (args[0] instanceof Number)
|
||||
return udfContext.newArgTupleValue(((Number) args[0]).intValue());
|
||||
return super.call(thiz, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Object getMember(String name)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case "newReturnUDTValue":
|
||||
return fRetUDT;
|
||||
case "newArgUDTValue":
|
||||
return fArgUDT;
|
||||
case "newReturnTupleValue":
|
||||
return fRetTup;
|
||||
case "newArgTupleValue":
|
||||
return fArgTup;
|
||||
}
|
||||
return super.getMember(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -266,13 +266,7 @@ public abstract class UDFunction extends UserFunction implements ScalarFunction
|
|||
{
|
||||
assertUdfsEnabled(language);
|
||||
|
||||
switch (language)
|
||||
{
|
||||
case "java":
|
||||
return new JavaBasedUDFunction(name, argNames, argTypes, returnType, calledOnNullInput, body);
|
||||
default:
|
||||
return new ScriptBasedUDFunction(name, argNames, argTypes, returnType, calledOnNullInput, language, body);
|
||||
}
|
||||
return new JavaBasedUDFunction(name, argNames, argTypes, returnType, calledOnNullInput, body);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -448,8 +442,8 @@ public abstract class UDFunction extends UserFunction implements ScalarFunction
|
|||
{
|
||||
if (!DatabaseDescriptor.enableUserDefinedFunctions())
|
||||
throw new InvalidRequestException("User-defined functions are disabled in cassandra.yaml - set user_defined_functions_enabled=true to enable");
|
||||
if (!"java".equalsIgnoreCase(language) && !DatabaseDescriptor.enableScriptedUserDefinedFunctions())
|
||||
throw new InvalidRequestException("Scripted user-defined functions are disabled in cassandra.yaml - set scripted_user_defined_functions_enabled=true to enable if you are aware of the security risks");
|
||||
if (!"java".equalsIgnoreCase(language))
|
||||
throw new InvalidRequestException("Currently only Java UDFs are available in Cassandra. For more information - CASSANDRA-18252 and CASSANDRA-17281");
|
||||
}
|
||||
|
||||
static void initializeThread()
|
||||
|
|
@ -637,8 +631,7 @@ public abstract class UDFunction extends UserFunction implements ScalarFunction
|
|||
}
|
||||
|
||||
/**
|
||||
* Used by UDF implementations (both Java code generated by {@link JavaBasedUDFunction}
|
||||
* and script executor {@link ScriptBasedUDFunction}) to convert the C*
|
||||
* Used by UDF implementations (both Java code generated by {@link JavaBasedUDFunction}) to convert the C*
|
||||
* serialized representation to the Java object representation.
|
||||
*
|
||||
* @param protocolVersion the native protocol version used for serialization
|
||||
|
|
@ -655,8 +648,7 @@ public abstract class UDFunction extends UserFunction implements ScalarFunction
|
|||
}
|
||||
|
||||
/**
|
||||
* Used by UDF implementations (both Java code generated by {@link JavaBasedUDFunction}
|
||||
* and script executor {@link ScriptBasedUDFunction}) to convert the Java
|
||||
* Used by UDF implementations (both Java code generated by {@link JavaBasedUDFunction}) to convert the Java
|
||||
* object representation for the return value to the C* serialized representation.
|
||||
*
|
||||
* @param protocolVersion the native protocol version used for serialization
|
||||
|
|
|
|||
|
|
@ -39,6 +39,6 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
sasi_indexes_enabled: true
|
||||
materialized_views_enabled: true
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ compaction_throughput_mb_per_sec: 64
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size_in_mb: 16
|
||||
enable_user_defined_functions: true
|
||||
enable_scripted_user_defined_functions: true
|
||||
enable_scripted_user_defined_functions: false
|
||||
prepared_statements_cache_size_mb:
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
prepared_statements_cache_size: 1MiB
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
prepared_statements_cache_size: 1MiB
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
prepared_statements_cache_size: 1MiB
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -40,4 +40,4 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
prepared_statements_cache_size: 1MiB
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
prepared_statements_cache_size: 1MiB
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ compaction_throughput: 0MiB/s
|
|||
row_cache_class_name: org.apache.cassandra.cache.OHCProvider
|
||||
row_cache_size: 16MiB
|
||||
user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: true
|
||||
scripted_user_defined_functions_enabled: false
|
||||
prepared_statements_cache_size: 1MiB
|
||||
corrupted_tombstone_strategy: exception
|
||||
stream_entire_sstables: true
|
||||
|
|
|
|||
|
|
@ -495,7 +495,7 @@ public class AuditLoggerTest extends CQLTester
|
|||
{
|
||||
String tblName = createTableName();
|
||||
|
||||
String cql = "CREATE FUNCTION IF NOT EXISTS " + KEYSPACE + "." + tblName + " (column TEXT,num int) RETURNS NULL ON NULL INPUT RETURNS text LANGUAGE javascript AS $$ column.substring(0,num) $$";
|
||||
String cql = "CREATE FUNCTION IF NOT EXISTS " + KEYSPACE + "." + tblName + " (column TEXT,num int) RETURNS NULL ON NULL INPUT RETURNS text LANGUAGE java AS $$ return column.substring(0,num); $$";
|
||||
executeAndAssert(cql, AuditLogEntryType.CREATE_FUNCTION);
|
||||
|
||||
cql = "DROP FUNCTION " + KEYSPACE + "." + tblName;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public class LoadOldYAMLBackwardCompatibilityTest
|
|||
assertEquals(new DurationSpec.IntSecondsBound(604800), config.trace_type_repair_ttl);
|
||||
assertNull(config.prepared_statements_cache_size);
|
||||
assertTrue(config.user_defined_functions_enabled);
|
||||
assertTrue(config.scripted_user_defined_functions_enabled);
|
||||
assertFalse(config.scripted_user_defined_functions_enabled);
|
||||
assertTrue(config.materialized_views_enabled);
|
||||
assertFalse(config.transient_replication_enabled);
|
||||
assertTrue(config.sasi_indexes_enabled);
|
||||
|
|
|
|||
|
|
@ -534,8 +534,8 @@ public class SelectionColumnMappingTest extends CQLTester
|
|||
" CREATE FUNCTION %s (a int, b int)" +
|
||||
" RETURNS NULL ON NULL INPUT" +
|
||||
" RETURNS int" +
|
||||
" LANGUAGE javascript" +
|
||||
" AS 'a + b'")).name;
|
||||
" LANGUAGE java" +
|
||||
" AS ' return a + b;'")).name;
|
||||
|
||||
String aFunc = createAggregate(KEYSPACE, "int, int",
|
||||
" CREATE AGGREGATE %s (int)" +
|
||||
|
|
@ -547,15 +547,15 @@ public class SelectionColumnMappingTest extends CQLTester
|
|||
" CREATE FUNCTION %s (a int)" +
|
||||
" RETURNS NULL ON NULL INPUT" +
|
||||
" RETURNS int" +
|
||||
" LANGUAGE javascript" +
|
||||
" AS 'a+1'");
|
||||
" LANGUAGE java" +
|
||||
" AS ' return a + 1;'");
|
||||
|
||||
String sqFunc = createFunction(KEYSPACE, "int",
|
||||
" CREATE FUNCTION %s (a int)" +
|
||||
" RETURNS NULL ON NULL INPUT" +
|
||||
" RETURNS int" +
|
||||
" LANGUAGE javascript" +
|
||||
" AS 'a*a'");
|
||||
" LANGUAGE java" +
|
||||
" AS ' return a * a;'");
|
||||
|
||||
ColumnMetadata v1 = columnDefinition("v1");
|
||||
SelectionColumns expected = SelectionColumnMapping.newMapping()
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.cql3.validation.entities;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.datastax.driver.core.DataType;
|
||||
import com.datastax.driver.core.TupleType;
|
||||
import com.datastax.driver.core.TupleValue;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
|
||||
public class UFPureScriptTupleCollectionTest extends CQLTester
|
||||
{
|
||||
// Just JavaScript UDFs to check how UDF - especially security/class-loading/sandboxing stuff -
|
||||
// behaves, if no Java UDF has been executed before.
|
||||
|
||||
// Do not add any other test here!
|
||||
// See CASSANDRA-10141
|
||||
|
||||
@Test
|
||||
public void testJavascriptTupleTypeCollection() throws Throwable
|
||||
{
|
||||
String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>";
|
||||
createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)");
|
||||
|
||||
String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup;$$;");
|
||||
String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getDouble(0);$$;");
|
||||
String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS list<double> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getList(1, java.lang.Double.class);$$;");
|
||||
String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS set<text> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getSet(2, java.lang.String.class);$$;");
|
||||
String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef,
|
||||
"CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS map<int, boolean> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" tup.getMap(3, java.lang.Integer.class, java.lang.Boolean.class);$$;");
|
||||
|
||||
List<Double> list = Arrays.asList(1d, 2d, 3d);
|
||||
Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two"));
|
||||
Map<Integer, Boolean> map = new TreeMap<>();
|
||||
map.put(1, true);
|
||||
map.put(2, false);
|
||||
map.put(3, true);
|
||||
|
||||
Object t = tuple(1d, list, set, map);
|
||||
|
||||
execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t);
|
||||
|
||||
assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(t));
|
||||
assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(1d));
|
||||
assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(list));
|
||||
assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(set));
|
||||
assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(map));
|
||||
|
||||
// same test - but via native protocol
|
||||
// we use protocol V3 here to encode the expected version because the server
|
||||
// always serializes Collections using V3 - see CollectionSerializer's
|
||||
// serialize and deserialize methods.
|
||||
TupleType tType = tupleTypeOf(ProtocolVersion.V3,
|
||||
DataType.cdouble(),
|
||||
DataType.list(DataType.cdouble()),
|
||||
DataType.set(DataType.text()),
|
||||
DataType.map(DataType.cint(),
|
||||
DataType.cboolean()));
|
||||
TupleValue tup = tType.newValue(1d, list, set, map);
|
||||
for (ProtocolVersion version : PROTOCOL_VERSIONS)
|
||||
{
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(tup));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(1d));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(list));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(set));
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"),
|
||||
row(map));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,430 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.cql3.validation.entities;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.UntypedResultSet;
|
||||
import org.apache.cassandra.cql3.functions.FunctionName;
|
||||
import org.apache.cassandra.exceptions.FunctionExecutionException;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
|
||||
|
||||
public class UFScriptTest extends CQLTester
|
||||
{
|
||||
// Just JavaScript UDFs to check how UDF - especially security/class-loading/sandboxing stuff -
|
||||
// behaves, if no Java UDF has been executed before.
|
||||
|
||||
// Do not add any other test here - especially none using Java UDFs
|
||||
|
||||
@Test
|
||||
public void testJavascriptSimpleCollections() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, lst list<double>, st set<text>, mp map<int, boolean>)");
|
||||
|
||||
String fName1 = createFunction(KEYSPACE_PER_TEST, "list<double>",
|
||||
"CREATE FUNCTION %s( lst list<double> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS list<double> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'lst;';");
|
||||
String fName2 = createFunction(KEYSPACE_PER_TEST, "set<text>",
|
||||
"CREATE FUNCTION %s( st set<text> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS set<text> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'st;';");
|
||||
String fName3 = createFunction(KEYSPACE_PER_TEST, "map<int, boolean>",
|
||||
"CREATE FUNCTION %s( mp map<int, boolean> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS map<int, boolean> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'mp;';");
|
||||
|
||||
List<Double> list = Arrays.asList(1d, 2d, 3d);
|
||||
Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two"));
|
||||
Map<Integer, Boolean> map = new TreeMap<>();
|
||||
map.put(1, true);
|
||||
map.put(2, false);
|
||||
map.put(3, true);
|
||||
|
||||
execute("INSERT INTO %s (key, lst, st, mp) VALUES (1, ?, ?, ?)", list, set, map);
|
||||
|
||||
assertRows(execute("SELECT lst, st, mp FROM %s WHERE key = 1"),
|
||||
row(list, set, map));
|
||||
|
||||
assertRows(execute("SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"),
|
||||
row(list, set, map));
|
||||
|
||||
for (ProtocolVersion version : PROTOCOL_VERSIONS)
|
||||
assertRowsNet(version,
|
||||
executeNet(version, "SELECT " + fName1 + "(lst), " + fName2 + "(st), " + fName3 + "(mp) FROM %s WHERE key = 1"),
|
||||
row(list, set, map));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptTupleType() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, tup frozen<tuple<double, text, int, boolean>>)");
|
||||
|
||||
String fName = createFunction(KEYSPACE_PER_TEST, "tuple<double, text, int, boolean>",
|
||||
"CREATE FUNCTION %s( tup tuple<double, text, int, boolean> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS tuple<double, text, int, boolean> " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$tup;$$;");
|
||||
|
||||
Object t = tuple(1d, "foo", 2, true);
|
||||
|
||||
execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t);
|
||||
|
||||
assertRows(execute("SELECT tup FROM %s WHERE key = 1"),
|
||||
row(t));
|
||||
|
||||
assertRows(execute("SELECT " + fName + "(tup) FROM %s WHERE key = 1"),
|
||||
row(t));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptUserType() throws Throwable
|
||||
{
|
||||
String type = createType("CREATE TYPE %s (txt text, i int)");
|
||||
|
||||
createTable("CREATE TABLE %s (key int primary key, udt frozen<" + type + ">)");
|
||||
|
||||
String fUdt1 = createFunction(KEYSPACE, type,
|
||||
"CREATE FUNCTION %s( udt " + type + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS " + type + ' ' +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" udt;$$;");
|
||||
String fUdt2 = createFunction(KEYSPACE, type,
|
||||
"CREATE FUNCTION %s( udt " + type + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS text " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" udt.getString(\"txt\");$$;");
|
||||
String fUdt3 = createFunction(KEYSPACE, type,
|
||||
"CREATE FUNCTION %s( udt " + type + " ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" udt.getInt(\"i\");$$;");
|
||||
|
||||
execute("INSERT INTO %s (key, udt) VALUES (1, {txt: 'one', i:1})");
|
||||
|
||||
UntypedResultSet rows = execute("SELECT " + fUdt1 + "(udt) FROM %s WHERE key = 1");
|
||||
Assert.assertEquals(1, rows.size());
|
||||
assertRows(execute("SELECT " + fUdt2 + "(udt) FROM %s WHERE key = 1"),
|
||||
row("one"));
|
||||
assertRows(execute("SELECT " + fUdt3 + "(udt) FROM %s WHERE key = 1"),
|
||||
row(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptUTCollections() throws Throwable
|
||||
{
|
||||
String type = createType("CREATE TYPE %s (txt text, i int)");
|
||||
|
||||
createTable(String.format("CREATE TABLE %%s " +
|
||||
"(key int primary key, lst list<frozen<%s>>, st set<frozen<%s>>, mp map<int, frozen<%s>>)",
|
||||
type, type, type));
|
||||
|
||||
String fName = createFunction(KEYSPACE, "list<frozen<" + type + ">>",
|
||||
"CREATE FUNCTION %s( lst list<frozen<" + type + ">> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS text " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" lst.get(1).getString(\"txt\");$$;");
|
||||
createFunctionOverload(fName, "set<frozen<" + type + ">>",
|
||||
"CREATE FUNCTION %s( st set<frozen<" + type + ">> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS text " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" st.iterator().next().getString(\"txt\");$$;");
|
||||
createFunctionOverload(fName, "map<int, frozen<" + type + ">>",
|
||||
"CREATE FUNCTION %s( mp map<int, frozen<" + type + ">> ) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS text " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS $$" +
|
||||
" mp.get(java.lang.Integer.valueOf(3)).getString(\"txt\");$$;");
|
||||
|
||||
execute("INSERT INTO %s (key, lst, st, mp) values (1, " +
|
||||
// list<frozen<UDT>>
|
||||
"[ {txt: 'one', i:1}, {txt: 'three', i:1}, {txt: 'one', i:1} ] , " +
|
||||
// set<frozen<UDT>>
|
||||
"{ {txt: 'one', i:1}, {txt: 'three', i:3}, {txt: 'two', i:2} }, " +
|
||||
// map<int, frozen<UDT>>
|
||||
"{ 1: {txt: 'one', i:1}, 2: {txt: 'one', i:3}, 3: {txt: 'two', i:2} })");
|
||||
|
||||
assertRows(execute("SELECT " + fName + "(lst) FROM %s WHERE key = 1"),
|
||||
row("three"));
|
||||
assertRows(execute("SELECT " + fName + "(st) FROM %s WHERE key = 1"),
|
||||
row("one"));
|
||||
assertRows(execute("SELECT " + fName + "(mp) FROM %s WHERE key = 1"),
|
||||
row("two"));
|
||||
|
||||
String cqlSelect = "SELECT " + fName + "(lst), " + fName + "(st), " + fName + "(mp) FROM %s WHERE key = 1";
|
||||
assertRows(execute(cqlSelect),
|
||||
row("three", "one", "two"));
|
||||
|
||||
// same test - but via native protocol
|
||||
for (ProtocolVersion version : PROTOCOL_VERSIONS)
|
||||
assertRowsNet(version,
|
||||
executeNet(version, cqlSelect),
|
||||
row("three", "one", "two"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptFunction() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, val double)");
|
||||
|
||||
String functionBody = '\n' +
|
||||
" Math.sin(val);\n";
|
||||
|
||||
String fName = createFunction(KEYSPACE, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS '" + functionBody + "';");
|
||||
|
||||
FunctionName fNameName = parseFunctionName(fName);
|
||||
|
||||
assertRows(execute("SELECT language, body FROM system_schema.functions WHERE keyspace_name=? AND function_name=?",
|
||||
fNameName.keyspace, fNameName.name),
|
||||
row("javascript", functionBody));
|
||||
|
||||
execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
|
||||
execute("INSERT INTO %s (key, val) VALUES (?, ?)", 2, 2d);
|
||||
execute("INSERT INTO %s (key, val) VALUES (?, ?)", 3, 3d);
|
||||
assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
|
||||
row(1, 1d, Math.sin(1d)),
|
||||
row(2, 2d, Math.sin(2d)),
|
||||
row(3, 3d, Math.sin(3d))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptBadReturnType() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, val double)");
|
||||
|
||||
String fName = createFunction(KEYSPACE, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS '\"string\";';");
|
||||
|
||||
execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
|
||||
// throws IRE with ClassCastException
|
||||
assertInvalidMessage("Invalid value for CQL type double", "SELECT key, val, " + fName + "(val) FROM %s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptThrow() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, val double)");
|
||||
|
||||
String fName = createFunction(KEYSPACE, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'throw \"fool\";';");
|
||||
|
||||
execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
|
||||
// throws IRE with ScriptException
|
||||
assertInvalidThrowMessage("fool", FunctionExecutionException.class,
|
||||
"SELECT key, val, " + fName + "(val) FROM %s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptReturnTypeCasting() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, val double)");
|
||||
execute("INSERT INTO %s (key, val) VALUES (?, ?)", 1, 1d);
|
||||
|
||||
Object[][] variations = {
|
||||
new Object[] { "true", "boolean", true },
|
||||
new Object[] { "false", "boolean", false },
|
||||
new Object[] { "100", "tinyint", (byte)100 },
|
||||
new Object[] { "100.", "tinyint", (byte)100 },
|
||||
new Object[] { "100", "smallint", (short)100 },
|
||||
new Object[] { "100.", "smallint", (short)100 },
|
||||
new Object[] { "100", "int", 100 },
|
||||
new Object[] { "100.", "int", 100 },
|
||||
new Object[] { "100", "double", 100d },
|
||||
new Object[] { "100.", "double", 100d },
|
||||
new Object[] { "100", "bigint", 100L },
|
||||
new Object[] { "100.", "bigint", 100L },
|
||||
new Object[] { "100", "varint", BigInteger.valueOf(100L) },
|
||||
new Object[] { "100.", "varint", BigInteger.valueOf(100L) },
|
||||
new Object[] { "parseInt(\"100\");", "decimal", BigDecimal.valueOf(100d) },
|
||||
new Object[] { "100.", "decimal", BigDecimal.valueOf(100d) },
|
||||
};
|
||||
|
||||
for (Object[] variation : variations)
|
||||
{
|
||||
Object functionBody = variation[0];
|
||||
Object returnType = variation[1];
|
||||
Object expectedResult = variation[2];
|
||||
|
||||
String fName = createFunction(KEYSPACE, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS " +returnType + ' ' +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '" + functionBody + ";';");
|
||||
assertRows(execute("SELECT key, val, " + fName + "(val) FROM %s"),
|
||||
row(1, 1d, expectedResult));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptParamReturnTypes() throws Throwable
|
||||
{
|
||||
UUID ruuid = UUID.randomUUID();
|
||||
TimeUUID tuuid = nextTimeUUID();
|
||||
|
||||
createTable("CREATE TABLE %s (key int primary key, " +
|
||||
"tival tinyint, sival smallint, ival int, lval bigint, fval float, dval double, vval varint, ddval decimal, " +
|
||||
"timval time, dtval date, tsval timestamp, uval uuid, tuval timeuuid)");
|
||||
execute("INSERT INTO %s (key, tival, sival, ival, lval, fval, dval, vval, ddval, timval, dtval, tsval, uval, tuval) VALUES " +
|
||||
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 1,
|
||||
(byte)1, (short)1, 1, 1L, 1f, 1d, BigInteger.valueOf(1L), BigDecimal.valueOf(1d), 1L, Integer.MAX_VALUE, new Date(1), ruuid, tuuid);
|
||||
|
||||
Object[][] variations = {
|
||||
new Object[] { "tinyint", "tival", (byte)1, (byte)2 },
|
||||
new Object[] { "smallint", "sival", (short)1, (short)2 },
|
||||
new Object[] { "int", "ival", 1, 2 },
|
||||
new Object[] { "bigint", "lval", 1L, 2L },
|
||||
new Object[] { "float", "fval", 1f, 2f },
|
||||
new Object[] { "double", "dval", 1d, 2d },
|
||||
new Object[] { "varint", "vval", BigInteger.valueOf(1L), BigInteger.valueOf(2L) },
|
||||
new Object[] { "decimal", "ddval", BigDecimal.valueOf(1d), BigDecimal.valueOf(2d) },
|
||||
new Object[] { "time", "timval", 1L, 2L },
|
||||
};
|
||||
|
||||
for (Object[] variation : variations)
|
||||
{
|
||||
Object type = variation[0];
|
||||
Object col = variation[1];
|
||||
Object expected1 = variation[2];
|
||||
Object expected2 = variation[3];
|
||||
String fName = createFunction(KEYSPACE, type.toString(),
|
||||
"CREATE OR REPLACE FUNCTION %s(val " + type + ") " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS " + type + ' ' +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'val+1;';");
|
||||
assertRows(execute("SELECT key, " + col + ", " + fName + '(' + col + ") FROM %s"),
|
||||
row(1, expected1, expected2));
|
||||
}
|
||||
|
||||
variations = new Object[][] {
|
||||
new Object[] { "timestamp","tsval", new Date(1), new Date(1) },
|
||||
new Object[] { "uuid", "uval", ruuid, ruuid },
|
||||
new Object[] { "timeuuid", "tuval", tuuid, tuuid },
|
||||
new Object[] { "date", "dtval", Integer.MAX_VALUE, Integer.MAX_VALUE },
|
||||
};
|
||||
|
||||
for (Object[] variation : variations)
|
||||
{
|
||||
Object type = variation[0];
|
||||
Object col = variation[1];
|
||||
Object expected1 = variation[2];
|
||||
Object expected2 = variation[3];
|
||||
String fName = createFunction(KEYSPACE, type.toString(),
|
||||
"CREATE OR REPLACE FUNCTION %s(val " + type + ") " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS " + type + ' ' +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'val;';");
|
||||
assertRows(execute("SELECT key, " + col + ", " + fName + '(' + col + ") FROM %s"),
|
||||
row(1, expected1, expected2));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptDisabled() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (key int primary key, val double)");
|
||||
|
||||
DatabaseDescriptor.enableScriptedUserDefinedFunctions(false);
|
||||
try
|
||||
{
|
||||
assertInvalid("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".assertNotEnabled(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'Math.sin(val);';");
|
||||
}
|
||||
finally
|
||||
{
|
||||
DatabaseDescriptor.enableScriptedUserDefinedFunctions(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptCompileFailure() throws Throwable
|
||||
{
|
||||
assertInvalidMessage("Failed to compile function 'cql_test_keyspace.scrinv'",
|
||||
"CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'foo bar';");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptInvalidLanguage() throws Throwable
|
||||
{
|
||||
assertInvalidMessage("Invalid language 'artificial_intelligence' for function 'cql_test_keyspace.scrinv'",
|
||||
"CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE artificial_intelligence\n" +
|
||||
"AS 'question for 42?';");
|
||||
}
|
||||
}
|
||||
|
|
@ -130,74 +130,6 @@ public class UFSecurityTest extends CQLTester
|
|||
"LANGUAGE JAVA\n" +
|
||||
"AS '" + typeAndSource[1] + "';");
|
||||
}
|
||||
|
||||
// JavaScript UDFs
|
||||
|
||||
try
|
||||
{
|
||||
String fName = createFunction(KEYSPACE_PER_TEST, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS 'org.apache.cassandra.service.StorageService.instance.isShutdown(); 0;';");
|
||||
execute("SELECT " + fName + "(dval) FROM %s WHERE key=1");
|
||||
Assert.fail("Javascript security check failed");
|
||||
}
|
||||
catch (FunctionExecutionException e)
|
||||
{
|
||||
assertAccessControlException("", e);
|
||||
}
|
||||
|
||||
String[] javascript =
|
||||
{
|
||||
"java.lang.management.ManagmentFactory.getThreadMXBean(); 0;",
|
||||
"new java.io.FileInputStream(\"/tmp/foo\"); 0;",
|
||||
"new java.io.FileOutputStream(\"/tmp/foo\"); 0;",
|
||||
"java.nio.file.FileSystems.getDefault().createFileExclusively(\"./foo_bar_baz\"); 0;",
|
||||
"java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")); 0;",
|
||||
"java.nio.channels.SocketChannel.open(); 0;",
|
||||
"new java.net.ServerSocket().bind(null); 0;",
|
||||
"var thread = new java.lang.Thread(); thread.start(); 0;",
|
||||
"java.lang.System.getProperty(\"foo.bar.baz\"); 0;",
|
||||
"java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); 0;",
|
||||
"java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;",
|
||||
"java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;",
|
||||
// TODO these (ugly) calls are still possible - these can consume CPU (as one could do with an evil loop, too)
|
||||
// "java.lang.Runtime.getRuntime().traceMethodCalls(true); 0;",
|
||||
// "java.lang.Runtime.getRuntime().gc(); 0;",
|
||||
// "java.lang.Runtime.getRuntime(); 0;",
|
||||
};
|
||||
|
||||
for (String script : javascript)
|
||||
{
|
||||
try
|
||||
{
|
||||
String fName = createFunction(KEYSPACE_PER_TEST, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS '" + script + "';");
|
||||
execute("SELECT " + fName + "(dval) FROM %s WHERE key=1");
|
||||
Assert.fail("Javascript security check failed: " + script);
|
||||
}
|
||||
catch (FunctionExecutionException e)
|
||||
{
|
||||
assertAccessControlException(script, e);
|
||||
}
|
||||
}
|
||||
|
||||
String script = "java.lang.Class.forName(\"java.lang.System\"); 0;";
|
||||
String fName = createFunction(KEYSPACE_PER_TEST, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS '" + script + "';");
|
||||
assertInvalidThrowMessage("Java reflection not supported when class filter is present",
|
||||
FunctionExecutionException.class,
|
||||
"SELECT " + fName + "(dval) FROM %s WHERE key=1");
|
||||
}
|
||||
|
||||
private static void assertAccessControlException(String script, FunctionExecutionException e)
|
||||
|
|
@ -250,16 +182,6 @@ public class UFSecurityTest extends CQLTester
|
|||
"AS 'long t=System.currentTimeMillis()+500; while (t>System.currentTimeMillis()) { }; return 0d;';");
|
||||
assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1");
|
||||
|
||||
// Javascript UDF
|
||||
|
||||
fName = createFunction(KEYSPACE_PER_TEST, "double",
|
||||
"CREATE OR REPLACE FUNCTION %s(val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE JAVASCRIPT\n" +
|
||||
"AS 'var t=java.lang.System.currentTimeMillis()+500; while (t>java.lang.System.currentTimeMillis()) { }; 0;';");
|
||||
assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1");
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Error | RuntimeException e)
|
||||
|
|
|
|||
|
|
@ -87,17 +87,17 @@ public class UFTest extends CQLTester
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSchemaChange() throws Throwable
|
||||
public void testSchemaChange()
|
||||
{
|
||||
String f = createFunctionName(KEYSPACE);
|
||||
String functionName = shortFunctionName(f);
|
||||
registerFunction(f, "double, double");
|
||||
|
||||
assertSchemaChange("CREATE OR REPLACE FUNCTION " + f + "(state double, val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
assertSchemaChange("CREATE OR REPLACE FUNCTION " + f + "(state double, val double)" +
|
||||
"CALLED ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';",
|
||||
"LANGUAGE java " +
|
||||
"AS ' return Double.valueOf(Math.max(state.doubleValue(), val.doubleValue())); ';",
|
||||
Change.CREATED,
|
||||
Target.FUNCTION,
|
||||
KEYSPACE, functionName,
|
||||
|
|
@ -108,22 +108,22 @@ public class UFTest extends CQLTester
|
|||
assertSchemaChange("CREATE OR REPLACE FUNCTION " + f + "(state int, val int) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';",
|
||||
"LANGUAGE java " +
|
||||
"AS ' return Integer.valueOf(Math.max(state, val));';",
|
||||
Change.CREATED,
|
||||
Target.FUNCTION,
|
||||
KEYSPACE, functionName,
|
||||
"int", "int");
|
||||
|
||||
assertSchemaChange("CREATE OR REPLACE FUNCTION " + f + "(state int, val int) " +
|
||||
/*assertSchemaChange("CREATE OR REPLACE FUNCTION " + f + "(state int, val int) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string1\";';",
|
||||
"LANGUAGE java " +
|
||||
"AS ' return Integer.valueOf(Math.max(state, val));';",
|
||||
Change.UPDATED,
|
||||
Target.FUNCTION,
|
||||
KEYSPACE, functionName,
|
||||
"int", "int");
|
||||
"int", "int");*/
|
||||
|
||||
assertSchemaChange("DROP FUNCTION " + f + "(double, double)",
|
||||
Change.DROPPED, Target.FUNCTION,
|
||||
|
|
@ -137,8 +137,8 @@ public class UFTest extends CQLTester
|
|||
assertSchemaChange("CREATE OR REPLACE FUNCTION " + fl + "(state list<tuple<int, int>>, val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';",
|
||||
"LANGUAGE java " +
|
||||
"AS ' return val;';",
|
||||
Change.CREATED, Target.FUNCTION,
|
||||
KEYSPACE, shortFunctionName(fl),
|
||||
"list<tuple<int, int>>", "double");
|
||||
|
|
@ -333,8 +333,8 @@ public class UFTest extends CQLTester
|
|||
"CREATE FUNCTION %s ( input double ) " +
|
||||
"CALLED ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'input'");
|
||||
"LANGUAGE java " +
|
||||
"AS 'return Double.valueOf(Math.log(input.doubleValue()));'");
|
||||
Assert.assertEquals(1, Schema.instance.getUserFunctions(parseFunctionName(function)).size());
|
||||
|
||||
List<ResultMessage.Prepared> prepared = new ArrayList<>();
|
||||
|
|
@ -793,11 +793,10 @@ public class UFTest extends CQLTester
|
|||
public void testDuplicateArgNames() throws Throwable
|
||||
{
|
||||
assertInvalidMessage("Duplicate argument names for given function",
|
||||
"CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double, val text) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS text " +
|
||||
"LANGUAGE javascript\n" +
|
||||
"AS '\"foo bar\";';");
|
||||
"CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(input double, input int) " +
|
||||
"CALLED ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE java AS 'return Math.max(input, input)';");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -512,16 +512,16 @@ public class AggregationTest extends CQLTester
|
|||
"CREATE OR REPLACE FUNCTION %s(state double, val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';");
|
||||
"LANGUAGE java " +
|
||||
"AS ' return state;';");
|
||||
|
||||
createFunctionOverload(f,
|
||||
"double, double",
|
||||
"CREATE OR REPLACE FUNCTION %s(state int, val int) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';");
|
||||
"LANGUAGE java " +
|
||||
"AS 'return state;';");
|
||||
|
||||
String a = createAggregateName(KEYSPACE);
|
||||
String aggregateName = shortFunctionName(a);
|
||||
|
|
@ -564,8 +564,8 @@ public class AggregationTest extends CQLTester
|
|||
"CREATE OR REPLACE FUNCTION %s(state double, val list<tuple<int, int>>) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';");
|
||||
"LANGUAGE java " +
|
||||
"AS ' return state;';");
|
||||
|
||||
String a1 = createAggregateName(KEYSPACE);
|
||||
registerAggregate(a1, "list<tuple<int, int>>");
|
||||
|
|
@ -587,16 +587,16 @@ public class AggregationTest extends CQLTester
|
|||
"CREATE OR REPLACE FUNCTION %s(state double, val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';");
|
||||
"LANGUAGE java " +
|
||||
"AS 'return state;';");
|
||||
|
||||
createFunctionOverload(f,
|
||||
"double, double",
|
||||
"CREATE OR REPLACE FUNCTION %s(state int, val int) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';");
|
||||
"LANGUAGE java " +
|
||||
"AS ' return state;';");
|
||||
|
||||
// DROP AGGREGATE must not succeed against a scalar
|
||||
assertInvalidMessage("matches multiple function definitions", "DROP AGGREGATE " + f);
|
||||
|
|
@ -639,8 +639,8 @@ public class AggregationTest extends CQLTester
|
|||
"CREATE OR REPLACE FUNCTION %s(state double, val double) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS double " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"string\";';");
|
||||
"LANGUAGE java " +
|
||||
"AS ' return state;';");
|
||||
|
||||
String a = createAggregate(KEYSPACE,
|
||||
"double",
|
||||
|
|
@ -1138,81 +1138,6 @@ public class AggregationTest extends CQLTester
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptAggregate() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (a int primary key, b int)");
|
||||
execute("INSERT INTO %s (a, b) VALUES (1, 1)");
|
||||
execute("INSERT INTO %s (a, b) VALUES (2, 2)");
|
||||
execute("INSERT INTO %s (a, b) VALUES (3, 3)");
|
||||
|
||||
String fState = createFunction(KEYSPACE,
|
||||
"int, int",
|
||||
"CREATE FUNCTION %s(a int, b int) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'a + b;'");
|
||||
|
||||
String fFinal = createFunction(KEYSPACE,
|
||||
"int",
|
||||
"CREATE FUNCTION %s(a int) " +
|
||||
"RETURNS NULL ON NULL INPUT " +
|
||||
"RETURNS text " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS '\"\"+a'");
|
||||
|
||||
String a = createFunction(KEYSPACE,
|
||||
"int",
|
||||
"CREATE AGGREGATE %s(int) " +
|
||||
"SFUNC " + shortFunctionName(fState) + " " +
|
||||
"STYPE int " +
|
||||
"FINALFUNC " + shortFunctionName(fFinal) + " " +
|
||||
"INITCOND 42");
|
||||
|
||||
// 42 + 1 + 2 + 3 = 48
|
||||
assertRows(execute("SELECT " + a + "(b) FROM %s"), row("48"));
|
||||
|
||||
execute("DROP AGGREGATE " + a + "(int)");
|
||||
|
||||
execute("DROP FUNCTION " + fFinal + "(int)");
|
||||
execute("DROP FUNCTION " + fState + "(int, int)");
|
||||
|
||||
assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptAggregateSimple() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (a int primary key, b int)");
|
||||
execute("INSERT INTO %s (a, b) VALUES (1, 1)");
|
||||
execute("INSERT INTO %s (a, b) VALUES (2, 2)");
|
||||
execute("INSERT INTO %s (a, b) VALUES (3, 3)");
|
||||
|
||||
String fState = createFunction(KEYSPACE,
|
||||
"int, int",
|
||||
"CREATE FUNCTION %s(a int, b int) " +
|
||||
"CALLED ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'a + b;'");
|
||||
|
||||
String a = createAggregate(KEYSPACE,
|
||||
"int, int",
|
||||
"CREATE AGGREGATE %s(int) " +
|
||||
"SFUNC " + shortFunctionName(fState) + " " +
|
||||
"STYPE int ");
|
||||
|
||||
// 1 + 2 + 3 = 6
|
||||
assertRows(execute("SELECT " + a + "(b) FROM %s"), row(6));
|
||||
|
||||
execute("DROP AGGREGATE " + a + "(int)");
|
||||
|
||||
execute("DROP FUNCTION " + fState + "(int, int)");
|
||||
|
||||
assertInvalidMessage("Unknown function", "SELECT " + a + "(b) FROM %s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFunctionDropPreparedStatement() throws Throwable
|
||||
{
|
||||
|
|
@ -1228,8 +1153,8 @@ public class AggregationTest extends CQLTester
|
|||
"CREATE FUNCTION %s(a int, b int) " +
|
||||
"CALLED ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'a + b;'");
|
||||
"LANGUAGE java " +
|
||||
"AS ' return a + b;'");
|
||||
|
||||
String a = createAggregate(otherKS,
|
||||
"int",
|
||||
|
|
@ -1271,8 +1196,8 @@ public class AggregationTest extends CQLTester
|
|||
"CREATE FUNCTION %s(a int, b int) " +
|
||||
"CALLED ON NULL INPUT " +
|
||||
"RETURNS int " +
|
||||
"LANGUAGE javascript " +
|
||||
"AS 'a + b;'");
|
||||
"LANGUAGE java " +
|
||||
"AS ' return a + b;'");
|
||||
|
||||
String a = createAggregate(KEYSPACE,
|
||||
"int",
|
||||
|
|
|
|||
Loading…
Reference in New Issue