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:
Ekaterina Dimitrova 2023-02-09 15:00:05 -05:00
parent ff6ad4e48a
commit a0ec0ae303
25 changed files with 65 additions and 1150 deletions

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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()

View File

@ -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));
}
}
}

View File

@ -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?';");
}
}

View File

@ -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)

View File

@ -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

View File

@ -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",