blob: 3107d16e75a284bb26a9d6ed7245868fd3d9d945 [file] [log] [blame]
/*
* 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.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.Duration;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.TimeUUID;
public class FunctionFactoryTest extends CQLTester
{
/**
* A function that just returns its only argument without any changes.
* Calls to this function will try to infer the type of its argument, if missing, from the function's receiver.
*/
private static final FunctionFactory IDENTITY = new FunctionFactory("identity", FunctionParameter.anyType(true))
{
@Override
protected NativeFunction doGetOrCreateFunction(List<AbstractType<?>> argTypes, AbstractType<?> receiverType)
{
return new NativeScalarFunction(name.name, argTypes.get(0), argTypes.get(0))
{
@Override
public ByteBuffer execute(ProtocolVersion protocol, List<ByteBuffer> parameters)
{
return parameters.get(0);
}
};
}
};
/**
* A function that returns the string representation of its only argument.
* Calls to this function won't try to infer the type of its argument, if missing, from the function's receiver.
*/
private static final FunctionFactory TO_STRING = new FunctionFactory("tostring", FunctionParameter.anyType(false))
{
@Override
protected NativeFunction doGetOrCreateFunction(List<AbstractType<?>> argTypes, AbstractType<?> receiverType)
{
return new NativeScalarFunction(name.name, UTF8Type.instance, argTypes.get(0))
{
@Override
public ByteBuffer execute(ProtocolVersion protocol, List<ByteBuffer> parameters)
{
ByteBuffer value = parameters.get(0);
if (value == null)
return null;
return UTF8Type.instance.decompose(argTypes.get(0).compose(value).toString());
}
};
}
};
private static final UUID uuid = UUID.fromString("62c3e96f-55cd-493b-8c8e-5a18883a1698");
private static final TimeUUID timeUUID = TimeUUID.fromString("00346642-2d2f-11ed-a261-0242ac120002");
private static final BigInteger bigint = new BigInteger("12345678901234567890");
private static final BigDecimal bigdecimal = new BigDecimal("1234567890.1234567890");
private static final Date date = new Date();
private static final Duration duration = Duration.newInstance(1, 2, 3);
private static final InetAddress inet = new InetSocketAddress(0).getAddress();
private static final ByteBuffer blob = ByteBufferUtil.hexToBytes("ABCDEF");
@BeforeClass
public static void beforeClass()
{
NativeFunctions.instance.add(IDENTITY);
NativeFunctions.instance.add(TO_STRING);
}
@Test
public void testInvalidNumberOfArguments() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY)");
String msg = "Invalid number of arguments for function system.identity(any)";
assertInvalidMessage(msg, "SELECT identity() FROM %s");
assertInvalidMessage(msg, "SELECT identity(1, 2) FROM %s");
assertInvalidMessage(msg, "SELECT identity('1', '2', '3') FROM %s");
}
@Test
public void testUnknownFunction() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY)");
assertInvalidMessage("Unknown function 'unknown'", "SELECT unknown() FROM %s");
}
@Test
public void testSimpleTypes() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, " +
"tinyint tinyint, " +
"smallint smallint, " +
"int int, " +
"bigint bigint, " +
"float float, " +
"double double, " +
"varint varint, " +
"decimal decimal, " +
"text text, " +
"ascii ascii, " +
"boolean boolean, " +
"date date, " +
"timestamp timestamp, " +
"duration duration, " +
"uuid uuid, " +
"timeuuid timeuuid," +
"inet inet," +
"blob blob)");
// Test with empty table
String select = "SELECT " +
"identity(tinyint), identity(smallint), identity(int), " +
"identity(bigint), identity(float), identity(double), " +
"identity(varint), identity(decimal), identity(text), " +
"identity(ascii), identity(boolean), identity(date), " +
"identity(timestamp), identity(duration), identity(uuid), " +
"identity(timeuuid), identity(inet), identity(blob) " +
"FROM %s";
UntypedResultSet rs = execute(select);
assertColumnNames(rs,
"system.identity(tinyint)", "system.identity(smallint)", "system.identity(int)",
"system.identity(bigint)", "system.identity(float)", "system.identity(double)",
"system.identity(varint)", "system.identity(decimal)", "system.identity(text)",
"system.identity(ascii)", "system.identity(boolean)", "system.identity(date)",
"system.identity(timestamp)", "system.identity(duration)", "system.identity(uuid)",
"system.identity(timeuuid)", "system.identity(inet)", "system.identity(blob)");
assertEmpty(rs);
// Test with not-empty table
Object[] row = row((byte) 1, (short) 1, 123, 123L, 1.23f, 1.23d, bigint, bigdecimal,
"ábc", "abc", true, 1, date, duration, uuid, timeUUID, inet, blob);
execute("INSERT INTO %s (k, tinyint, smallint, int, bigint, float, double, varint, decimal, " +
"text, ascii, boolean, date, timestamp, duration, uuid, timeuuid, inet, blob) " +
"VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", row);
assertRows(execute(select), row);
// Test with bind markers
execute("INSERT INTO %s (k, tinyint, smallint, int, bigint, float, double, varint, decimal, " +
"text, ascii, boolean, date, timestamp, duration, uuid, timeuuid, inet, blob) " +
"VALUES (1, " +
"identity(?), identity(?), identity(?), identity(?), identity(?), identity(?), " +
"identity(?), identity(?), identity(?), identity(?), identity(?), identity(?), " +
"identity(?), identity(?), identity(?), identity(?), identity(?), identity(?))", row);
assertRows(execute(select), row);
// Test literals
testLiteral("(tinyint) 1", (byte) 1);
testLiteral("(smallint) 1", (short) 1);
testLiteral(123);
testLiteral(1234567890123L);
testLiteral(1.23);
testLiteral(1234567.1234567D);
testLiteral(bigint);
testLiteral(bigdecimal);
testLiteral("'abc'", "abc");
testLiteral("'ábc'", "ábc");
testLiteral(true);
testLiteral(false);
testLiteral("(timestamp) '1970-01-01 00:00:00.000+0000'", new Date(0));
testLiteral("(time) '00:00:00.000000'", 0L);
testLiteral(duration);
testLiteral(uuid);
testLiteral(timeUUID);
testLiteral("(inet) '0.0.0.0'", inet);
testLiteral("0x" + ByteBufferUtil.bytesToHex(blob), blob);
}
@Test
public void testSets() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, s set<int>, fs frozen<set<int>>)");
// Test with empty table
String select = "SELECT identity(s), identity(fs) FROM %s";
UntypedResultSet rs = execute(select);
assertColumnNames(rs, "system.identity(s)", "system.identity(fs)");
assertEmpty(rs);
// Test with not-empty table
execute("INSERT INTO %s (k, s, fs) VALUES (1, {1, 2}, {1, 2})");
execute("INSERT INTO %s (k, s, fs) VALUES (2, {1, 2, 3}, {1, 2, 3})");
execute("INSERT INTO %s (k, s, fs) VALUES (3, {2, 1}, {2, 1})");
assertRows(execute(select),
row(set(1, 2), set(1, 2)),
row(set(1, 2, 3), set(1, 2, 3)),
row(set(2, 1), set(2, 1)));
// Test with bind markers
Object[] row = row(set(1, 2, 3), set(4, 5, 6));
execute("INSERT INTO %s (k, s, fs) VALUES (4, identity(?), identity(?))", row);
assertRows(execute("SELECT s, fs FROM %s WHERE k = 4"), row);
// Test literals
testLiteralFails("[]");
testLiteral("{1, 1234567890}", set(1, 1234567890));
testLiteral(String.format("{1, %s}", bigint), set(BigInteger.ONE, bigint));
testLiteral("{'abc'}", set("abc"));
testLiteral("{'ábc'}", set("ábc"));
testLiteral("{'abc', 'ábc'}", set("abc", "ábc"));
testLiteral("{'ábc', 'abc'}", set("ábc", "abc"));
testLiteral("{true}", set(true));
testLiteral("{false}", set(false));
testLiteral(String.format("{%s}", uuid), set(uuid));
testLiteral(String.format("{%s}", timeUUID), set(timeUUID));
testLiteral(String.format("{%s, %s}", uuid, timeUUID), set(uuid, timeUUID.asUUID()));
}
@Test
public void testLists() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, l list<int>, fl frozen<list<int>>)");
// Test with empty table
String select = "SELECT identity(l), identity(fl) FROM %s";
UntypedResultSet rs = execute(select);
assertColumnNames(rs, "system.identity(l)", "system.identity(fl)");
assertEmpty(rs);
// Test with not-empty table
execute("INSERT INTO %s (k, l, fl) VALUES (1, [1, 2], [1, 2])");
execute("INSERT INTO %s (k, l, fl) VALUES (2, [1, 2, 3], [1, 2, 3])");
execute("INSERT INTO %s (k, l, fl) VALUES (3, [2, 1], [2, 1])");
assertRows(execute(select),
row(list(1, 2), list(1, 2)),
row(list(1, 2, 3), list(1, 2, 3)),
row(list(2, 1), list(2, 1)));
// Test with bind markers
Object[] row = row(list(1, 2, 3), list(4, 5, 6));
execute("INSERT INTO %s (k, l, fl) VALUES (4, identity(?), identity(?))", row);
assertRows(execute("SELECT l, fl FROM %s WHERE k = 4"), row);
// Test literals
testLiteralFails("[]");
testLiteral("[1, 1234567890]", list(1, 1234567890));
testLiteral(String.format("[1, %s]", bigint), list(BigInteger.ONE, bigint));
testLiteral("['abc']", list("abc"));
testLiteral("['ábc']", list("ábc"));
testLiteral("['abc', 'ábc']", list("abc", "ábc"));
testLiteral("['ábc', 'abc']", list("ábc", "abc"));
testLiteral("[true]", list(true));
testLiteral("[false]", list(false));
testLiteral(String.format("[%s]", uuid), list(uuid));
testLiteral(String.format("[%s]", timeUUID), list(timeUUID));
testLiteral(String.format("[%s, %s]", uuid, timeUUID), list(uuid, timeUUID.asUUID()));
}
@Test
public void testMaps() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, m map<int, int>, fm frozen<map<int, int>>)");
// Test with empty table
String select = "SELECT identity(m), identity(fm) FROM %s";
UntypedResultSet rs = execute(select);
assertColumnNames(rs, "system.identity(m)", "system.identity(fm)");
assertEmpty(rs);
// Test with not-empty table
execute("INSERT INTO %s (k, m, fm) VALUES (1, {1:10, 2:20}, {1:10, 2:20})");
execute("INSERT INTO %s (k, m, fm) VALUES (2, {1:10, 2:20, 3:30}, {1:10, 2:20, 3:30})");
execute("INSERT INTO %s (k, m, fm) VALUES (3, {2:20, 1:10}, {2:20, 1:10})");
assertRows(execute(select),
row(map(1, 10, 2, 20), map(1, 10, 2, 20)),
row(map(1, 10, 2, 20, 3, 30), map(1, 10, 2, 20, 3, 30)),
row(map(1, 10, 2, 20), map(1, 10, 2, 20)));
// Test with bind markers
Object[] row = row(map(1, 10, 2, 20), map(3, 30, 4, 40));
execute("INSERT INTO %s (k, m, fm) VALUES (4, identity(?), identity(?))", row);
assertRows(execute("SELECT m, fm FROM %s WHERE k = 4"), row);
// Test literals
testLiteralFails("{}");
testLiteralFails("{1: 10, 2: 20}");
testLiteral("(map<int, int>) {1: 10, 2: 20}", map(1, 10, 2, 20));
testLiteral("(map<int, text>) {1: 'abc', 2: 'ábc'}", map(1, "abc", 2, "ábc"));
testLiteral("(map<text, int>) {'abc': 1, 'ábc': 2}", map("abc", 1, "ábc", 2));
}
@Test
public void testTuples() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, t tuple<int, text, boolean>)");
// Test with empty table
String select = "SELECT identity(t) FROM %s";
UntypedResultSet rs = execute(select);
assertColumnNames(rs, "system.identity(t)");
assertEmpty(rs);
// Test with not-empty table
execute("INSERT INTO %s (k, t) VALUES (1, (1, 'a', false))");
execute("INSERT INTO %s (k, t) VALUES (2, (2, 'b', true))");
execute("INSERT INTO %s (k, t) VALUES (3, (3, null, true))");
assertRows(execute(select),
row(tuple(1, "a", false)),
row(tuple(2, "b", true)),
row(tuple(3, null, true)));
// Test with bind markers
Object[] row = row(tuple(4, "d", false));
execute("INSERT INTO %s (k, t) VALUES (4, identity(?))", row);
assertRows(execute("SELECT t FROM %s WHERE k = 4"), row);
// Test literals
testLiteralFails("(1)");
testLiteral("(tuple<int>) (1)", tuple(1));
testLiteral("(1, 'a')", tuple(1, "a"));
testLiteral("(1, 'a', false)", tuple(1, "a", false));
}
@Test
public void testUDTs() throws Throwable
{
String udt = createType("CREATE TYPE %s (x int)");
createTable("CREATE TABLE %s (k int PRIMARY KEY, u frozen<" + udt + ">, fu frozen<" + udt + ">)");
// Test with empty table
String select = "SELECT identity(u), identity(fu) FROM %s";
UntypedResultSet rs = execute(select);
assertColumnNames(rs, "system.identity(u)", "system.identity(fu)");
assertEmpty(rs);
// Test with not-empty table
execute("INSERT INTO %s (k, u, fu) VALUES (1, {x: 2}, null)");
execute("INSERT INTO %s (k, u, fu) VALUES (2, {x: 4}, {x: 6})");
execute("INSERT INTO %s (k, u, fu) VALUES (4, null, {x: 8})");
assertRows(execute(select),
row(userType("x", 2), null),
row(userType("x", 4), userType("x", 6)),
row(null, userType("x", 8)));
// Test with bind markers
Object[] row = row(userType("x", 4), userType("x", 5));
execute("INSERT INTO %s (k, u, fu) VALUES (4, identity(?), identity(?))", row);
assertRows(execute("SELECT u, fu FROM %s WHERE k = 4"), row);
// Test literals
testLiteralFails("{}");
testLiteralFails("{x: 10}");
testLiteral('(' + udt + "){x: 10}", tuple(10));
}
@Test
public void testNestedCalls() throws Throwable
{
createTable("CREATE TABLE %s (k int PRIMARY KEY, v int, t text)");
// Test function that infers parameter type from receiver type
execute("INSERT INTO %s (k, v) VALUES (1, identity(identity(2)))");
assertRows(execute("SELECT v FROM %s WHERE k = 1"), row(2));
execute("INSERT INTO %s (k, v) VALUES (1, identity(identity((int) ?)))", 3);
assertRows(execute("SELECT v FROM %s WHERE k = 1"), row(3));
assertRows(execute("SELECT identity(identity(v)) FROM %s WHERE k = 1"), row(3));
// Test function that does not infer parameter type from receiver type
execute("INSERT INTO %s (k, t) VALUES (1, tostring(tostring(4)))");
assertRows(execute("SELECT t FROM %s WHERE k = 1"), row("4"));
execute("INSERT INTO %s (k, t) VALUES (1, tostring(tostring((int) ?)))", 5);
assertRows(execute("SELECT tostring(tostring(t)) FROM %s WHERE k = 1"), row("5"));
}
private void testLiteral(Object literal) throws Throwable
{
testLiteral(literal, literal);
}
private void testLiteral(Object functionArgs, Object expectedResult) throws Throwable
{
assertRows(execute(String.format("SELECT %s(%s) FROM %%s LIMIT 1", IDENTITY.name(), functionArgs)),
row(expectedResult));
}
private void testLiteralFails(Object functionArgs) throws Throwable
{
assertInvalidMessage("Cannot infer type of argument " + functionArgs,
String.format("SELECT %s(%s) FROM %%s", IDENTITY.name(), functionArgs));
}
}