/*
* 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.*;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.apache.cassandra.cql3.Attributes;
import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.statements.BatchStatement;
import org.apache.cassandra.cql3.statements.ModificationStatement;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.service.ClientState;
import static org.junit.Assert.assertTrue;
/**
* Checks the collection of Function objects returned by CQLStatement.getFunction
* matches expectations. This is intended to verify the various subcomponents of
* the statement (Operations, Terms, Restrictions, RestrictionSet, Selection,
* Selector, SelectorFactories etc) properly report any constituent functions.
* Some purely terminal functions are resolved at preparation, so those are not
* included in the reported list. They still need to be surveyed, to verify the
* calling client has the necessary permissions. UFAuthTest includes tests which
* verify this more thoroughly than we can here.
*/
public class UFIdentificationTest extends CQLTester
{
private com.google.common.base.Function<Function, String> toFunctionNames = new com.google.common.base.Function<Function, String>()
{
public String apply(Function f)
{
return f.name().keyspace + "." + f.name().name;
}
};
String tFunc;
String iFunc;
String lFunc;
String sFunc;
String mFunc;
String uFunc;
String udtFunc;
String userType;
@Before
public void setup() throws Throwable
{
userType = KEYSPACE + "." + createType("CREATE TYPE %s (t text, i int)");
createTable("CREATE TABLE %s (" +
" key int, " +
" t_sc text STATIC," +
" i_cc int, " +
" t_cc text, " +
" i_val int," +
" l_val list<int>," +
" s_val set<int>," +
" m_val map<int, int>," +
" u_val timeuuid," +
" udt_val frozen<" + userType + ">," +
" PRIMARY KEY (key, i_cc, t_cc)" +
")");
tFunc = createEchoFunction("text");
iFunc = createEchoFunction("int");
lFunc = createEchoFunction("list<int>");
sFunc = createEchoFunction("set<int>");
mFunc = createEchoFunction("map<int, int>");
uFunc = createEchoFunction("timeuuid");
udtFunc = createEchoFunction(userType);
}
@Test
public void testSimpleModificationStatement() throws Throwable
{
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, t_sc) VALUES (0, 0, 'A', %s)", functionCall(tFunc, "'foo'")), tFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc) VALUES (0, %s, 'A')", functionCall(iFunc, "1")), iFunc);
assertFunctions(cql("INSERT INTO %s (key, t_cc, i_cc) VALUES (0, %s, 1)", functionCall(tFunc, "'foo'")), tFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, i_val) VALUES (0, 0, 'A', %s)", functionCall(iFunc, "1")), iFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, l_val) VALUES (0, 0, 'A', %s)", functionCall(lFunc, "[1]")), lFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, s_val) VALUES (0, 0, 'A', %s)", functionCall(sFunc, "{1}")), sFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, m_val) VALUES (0, 0, 'A', %s)", functionCall(mFunc, "{1:1}")), mFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, udt_val) VALUES (0, 0, 'A', %s)", functionCall(udtFunc, "{i : 1, t : 'foo'}")), udtFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, u_val) VALUES (0, 0, 'A', %s)", functionCall(uFunc, "now()")), uFunc, "system.now");
}
@Test
public void testNonTerminalCollectionLiterals() throws Throwable
{
String iFunc2 = createEchoFunction("int");
String mapValue = String.format("{%s:%s}", functionCall(iFunc, "1"), functionCall(iFunc2, "1"));
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, m_val) VALUES (0, 0, 'A', %s)", mapValue), iFunc, iFunc2);
String listValue = String.format("[%s]", functionCall(iFunc, "1"));
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, l_val) VALUES (0, 0, 'A', %s)", listValue), iFunc);
String setValue = String.format("{%s}", functionCall(iFunc, "1"));
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, s_val) VALUES (0, 0, 'A', %s)", setValue), iFunc);
}
@Test
public void testNonTerminalUDTLiterals() throws Throwable
{
String udtValue = String.format("{ i: %s, t : %s } ", functionCall(iFunc, "1"), functionCall(tFunc, "'foo'"));
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, udt_val) VALUES (0, 0, 'A', %s)", udtValue), iFunc, tFunc);
}
@Test
public void testModificationStatementWithConditions() throws Throwable
{
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF t_sc=%s", functionCall(tFunc, "'foo'")), tFunc);
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF i_val=%s", functionCall(iFunc, "1")), iFunc);
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF l_val=%s", functionCall(lFunc, "[1]")), lFunc);
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF s_val=%s", functionCall(sFunc, "{1}")), sFunc);
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF m_val=%s", functionCall(mFunc, "{1:1}")), mFunc);
String iFunc2 = createEchoFunction("int");
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF i_val IN (%s, %S)",
functionCall(iFunc, "1"),
functionCall(iFunc2, "2")),
iFunc, iFunc2);
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF u_val=%s",
functionCall(uFunc, "now()")),
uFunc, "system.now");
// conditions on collection elements
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF l_val[%s] = %s",
functionCall(iFunc, "1"),
functionCall(iFunc2, "1")),
iFunc, iFunc2);
assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 AND i_cc = 0 AND t_cc = 'A' IF m_val[%s] = %s",
functionCall(iFunc, "1"),
functionCall(iFunc2, "1")),
iFunc, iFunc2);
}
@Test @Ignore
// Technically, attributes like timestamp and ttl are Terms so could potentially
// resolve to function calls (& so you can call getFunctions on them)
// However, this is currently disallowed by CQL syntax
public void testModificationStatementWithAttributesFromFunction() throws Throwable
{
String longFunc = createEchoFunction("bigint");
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, i_val) VALUES (0, 0, 'foo', 0) USING TIMESTAMP %s",
functionCall(longFunc, "9999")),
longFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, i_val) VALUES (0, 0, 'foo', 0) USING TTL %s",
functionCall(iFunc, "8888")),
iFunc);
assertFunctions(cql("INSERT INTO %s (key, i_cc, t_cc, i_val) VALUES (0, 0, 'foo', 0) USING TIMESTAMP %s AND TTL %s",
functionCall(longFunc, "9999"), functionCall(iFunc, "8888")),
longFunc, iFunc);
}
@Test
public void testModificationStatementWithNestedFunctions() throws Throwable
{
String iFunc2 = createEchoFunction("int");
String iFunc3 = createEchoFunction("int");
String iFunc4 = createEchoFunction("int");
String iFunc5 = createEchoFunction("int");
String iFunc6 = createEchoFunction("int");
String nestedFunctionCall = nestedFunctionCall(iFunc6, iFunc5,
nestedFunctionCall(iFunc4, iFunc3,
nestedFunctionCall(iFunc2, iFunc, "1")));
assertFunctions(cql("DELETE FROM %s WHERE key=%s", nestedFunctionCall),
iFunc, iFunc2, iFunc3, iFunc4, iFunc5, iFunc6);
}
@Test
public void testSelectStatementSimpleRestrictions() throws Throwable
{
assertFunctions(cql("SELECT i_val FROM %s WHERE key=%s", functionCall(iFunc, "1")), iFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND t_sc=%s ALLOW FILTERING", functionCall(tFunc, "'foo'")), tFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND i_cc=%s AND t_cc='foo' ALLOW FILTERING", functionCall(iFunc, "1")), iFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND i_cc=0 AND t_cc=%s ALLOW FILTERING", functionCall(tFunc, "'foo'")), tFunc);
String iFunc2 = createEchoFunction("int");
String tFunc2 = createEchoFunction("text");
assertFunctions(cql("SELECT i_val FROM %s WHERE key=%s AND t_sc=%s AND i_cc=%s AND t_cc=%s ALLOW FILTERING",
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'"),
functionCall(iFunc2, "1"),
functionCall(tFunc2, "'foo'")),
iFunc, tFunc, iFunc2, tFunc2);
}
@Test
public void testSelectStatementRestrictionsWithNestedFunctions() throws Throwable
{
String iFunc2 = createEchoFunction("int");
String iFunc3 = createEchoFunction("int");
String iFunc4 = createEchoFunction("int");
String iFunc5 = createEchoFunction("int");
String iFunc6 = createEchoFunction("int");
String nestedFunctionCall = nestedFunctionCall(iFunc6, iFunc5,
nestedFunctionCall(iFunc3, iFunc4,
nestedFunctionCall(iFunc, iFunc2, "1")));
assertFunctions(cql("SELECT i_val FROM %s WHERE key=%s", nestedFunctionCall),
iFunc, iFunc2, iFunc3, iFunc4, iFunc5, iFunc6);
}
@Test
public void testNonTerminalTupleInSelectRestrictions() throws Throwable
{
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND (i_cc, t_cc) IN ((%s, %s))",
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'")),
iFunc, tFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND (i_cc, t_cc) = (%s, %s)",
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'")),
iFunc, tFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND (i_cc, t_cc) > (%s, %s)",
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'")),
iFunc, tFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND (i_cc, t_cc) < (%s, %s)",
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'")),
iFunc, tFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND (i_cc, t_cc) > (%s, %s) AND (i_cc, t_cc) < (%s, %s)",
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'"),
functionCall(iFunc, "1"),
functionCall(tFunc, "'foo'")),
iFunc, tFunc);
}
@Test
public void testNestedFunctionInTokenRestriction() throws Throwable
{
String iFunc2 = createEchoFunction("int");
assertFunctions(cql("SELECT i_val FROM %s WHERE token(key) = token(%s)", functionCall(iFunc, "1")),
"system.token", iFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE token(key) > token(%s)", functionCall(iFunc, "1")),
"system.token", iFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE token(key) < token(%s)", functionCall(iFunc, "1")),
"system.token", iFunc);
assertFunctions(cql("SELECT i_val FROM %s WHERE token(key) > token(%s) AND token(key) < token(%s)",
functionCall(iFunc, "1"),
functionCall(iFunc2, "1")),
"system.token", iFunc, iFunc2);
}
@Test
public void testSelectStatementSimpleSelections() throws Throwable
{
String iFunc2 = createEchoFunction("int");
execute("INSERT INTO %s (key, i_cc, t_cc, i_val) VALUES (0, 0, 'foo', 0)");
assertFunctions(cql2("SELECT i_val, %s FROM %s WHERE key=0", functionCall(iFunc, "i_val")), iFunc);
assertFunctions(cql2("SELECT i_val, %s FROM %s WHERE key=0", nestedFunctionCall(iFunc, iFunc2, "i_val")), iFunc, iFunc2);
}
@Test
public void testSelectStatementNestedSelections() throws Throwable
{
String iFunc2 = createEchoFunction("int");
execute("INSERT INTO %s (key, i_cc, t_cc, i_val) VALUES (0, 0, 'foo', 0)");
assertFunctions(cql2("SELECT i_val, %s FROM %s WHERE key=0", functionCall(iFunc, "i_val")), iFunc);
assertFunctions(cql2("SELECT i_val, %s FROM %s WHERE key=0", nestedFunctionCall(iFunc, iFunc2, "i_val")), iFunc, iFunc2);
}
@Test
public void testBatchStatement() throws Throwable
{
String iFunc2 = createEchoFunction("int");
List<ModificationStatement> statements = new ArrayList<>();
statements.add(modificationStatement(cql("INSERT INTO %s (key, i_cc, t_cc) VALUES (%s, 0, 'foo')",
functionCall(iFunc, "0"))));
statements.add(modificationStatement(cql("INSERT INTO %s (key, i_cc, t_cc) VALUES (1, %s, 'foo')",
functionCall(iFunc2, "1"))));
statements.add(modificationStatement(cql("INSERT INTO %s (key, i_cc, t_cc) VALUES (2, 2, %s)",
functionCall(tFunc, "'foo'"))));
BatchStatement batch = new BatchStatement(-1, BatchStatement.Type.LOGGED, statements, Attributes.none());
assertFunctions(batch, iFunc, iFunc2, tFunc);
}
@Test
public void testBatchStatementWithConditions() throws Throwable
{
List<ModificationStatement> statements = new ArrayList<>();
statements.add(modificationStatement(cql("UPDATE %s SET i_val = %s WHERE key=0 AND i_cc=0 and t_cc='foo' IF l_val = %s",
functionCall(iFunc, "0"), functionCall(lFunc, "[1]"))));
statements.add(modificationStatement(cql("UPDATE %s SET i_val = %s WHERE key=0 AND i_cc=1 and t_cc='foo' IF s_val = %s",
functionCall(iFunc, "0"), functionCall(sFunc, "{1}"))));
BatchStatement batch = new BatchStatement(-1, BatchStatement.Type.LOGGED, statements, Attributes.none());
assertFunctions(batch, iFunc, lFunc, sFunc);
}
private ModificationStatement modificationStatement(String cql)
{
return (ModificationStatement) QueryProcessor.getStatement(cql, ClientState.forInternalCalls()).statement;
}
private void assertFunctions(String cql, String... function)
{
CQLStatement stmt = QueryProcessor.getStatement(cql, ClientState.forInternalCalls()).statement;
assertFunctions(stmt, function);
}
private void assertFunctions(CQLStatement stmt, String... function)
{
Set<String> expected = com.google.common.collect.Sets.newHashSet(function);
Set<String> actual = com.google.common.collect.Sets.newHashSet(Iterables.transform(stmt.getFunctions(),
toFunctionNames));
assertTrue(com.google.common.collect.Sets.symmetricDifference(expected, actual).isEmpty());
}
private String cql(String template, String... params)
{
String tableName = KEYSPACE + "." + currentTable();
return String.format(template, com.google.common.collect.Lists.asList(tableName, params).toArray());
}
// Alternative query builder - appends the table name to the supplied params,
// for stmts of the form "SELECT x, %s FROM %s WHERE y=0"
private String cql2(String template, String... params)
{
Object[] args = Arrays.copyOf(params, params.length + 1);
args[params.length] = KEYSPACE + "." + currentTable();
return String.format(template, args);
}
private String functionCall(String fName, String... args)
{
return String.format("%s(%s)", fName, Joiner.on(",").join(args));
}
private String nestedFunctionCall(String outer, String inner, String innerArgs)
{
return functionCall(outer, functionCall(inner, innerArgs));
}
private String createEchoFunction(String type) throws Throwable
{
return createFunction(KEYSPACE, type,
"CREATE FUNCTION %s(input " + type + ")" +
" CALLED ON NULL INPUT" +
" RETURNS " + type +
" LANGUAGE java" +
" AS ' return input;'");
}
}