/* * 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;'"); } }