Repository: cassandra Updated Branches: refs/heads/trunk d6a4b4548 -> cb5897f3c
http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/test/unit/org/apache/cassandra/cql3/UFIdentificationTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFIdentificationTest.java b/test/unit/org/apache/cassandra/cql3/UFIdentificationTest.java new file mode 100644 index 0000000..cfa42b3 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/UFIdentificationTest.java @@ -0,0 +1,375 @@ +/* + * 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; + +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.functions.Function; +import org.apache.cassandra.cql3.statements.BatchStatement; +import org.apache.cassandra.cql3.statements.ModificationStatement; +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("frozen<" + userType + ">"); + } + + @Test + public void testSimpleModificationStatement() throws Throwable + { + assertFunctions(cql("INSERT INTO %s (key, t_sc) VALUES (0, %s)", functionCall(tFunc, "'foo'")), tFunc); + assertFunctions(cql("INSERT INTO %s (key, i_cc) VALUES (0, %s)", functionCall(iFunc, "1")), iFunc); + assertFunctions(cql("INSERT INTO %s (key, t_cc) VALUES (0, %s)", functionCall(tFunc, "'foo'")), tFunc); + assertFunctions(cql("INSERT INTO %s (key, i_val) VALUES (0, %s)", functionCall(iFunc, "1")), iFunc); + assertFunctions(cql("INSERT INTO %s (key, l_val) VALUES (0, %s)", functionCall(lFunc, "[1]")), lFunc); + assertFunctions(cql("INSERT INTO %s (key, s_val) VALUES (0, %s)", functionCall(sFunc, "{1}")), sFunc); + assertFunctions(cql("INSERT INTO %s (key, m_val) VALUES (0, %s)", functionCall(mFunc, "{1:1}")), mFunc); + assertFunctions(cql("INSERT INTO %s (key, udt_val) VALUES (0,%s)", functionCall(udtFunc, "{i : 1, t : 'foo'}")), udtFunc); + assertFunctions(cql("INSERT INTO %s (key, u_val) VALUES (0, %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, m_val) VALUES (0, %s)", mapValue), iFunc, iFunc2); + + String listValue = String.format("[%s]", functionCall(iFunc, "1")); + assertFunctions(cql("INSERT INTO %s (key, l_val) VALUES (0, %s)", listValue), iFunc); + + String setValue = String.format("{%s}", functionCall(iFunc, "1")); + assertFunctions(cql("INSERT INTO %s (key, s_val) VALUES (0, %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, udt_val) VALUES (0, %s)", udtValue), iFunc, tFunc); + } + + @Test + public void testModificationStatementWithConditions() throws Throwable + { + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 IF t_sc=%s", functionCall(tFunc, "'foo'")), tFunc); + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 IF i_val=%s", functionCall(iFunc, "1")), iFunc); + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 IF l_val=%s", functionCall(lFunc, "[1]")), lFunc); + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 IF s_val=%s", functionCall(sFunc, "{1}")), sFunc); + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 IF m_val=%s", functionCall(mFunc, "{1:1}")), mFunc); + + + String iFunc2 = createEchoFunction("int"); + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 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 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 IF l_val[%s] = %s", + functionCall(iFunc, "1"), + functionCall(iFunc2, "1")), + iFunc, iFunc2); + assertFunctions(cql("UPDATE %s SET i_val=0 WHERE key=0 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 usesFunction & 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", functionCall(tFunc, "'foo'")), tFunc); + assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND i_cc=%s AND t_cc='foo'", functionCall(iFunc, "1")), iFunc); + assertFunctions(cql("SELECT i_val FROM %s WHERE key=0 AND i_cc=0 AND t_cc=%s", 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", + 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 + ")" + + " RETURNS " + type + + " LANGUAGE java" + + " AS ' return input;'"); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/cb5897f3/test/unit/org/apache/cassandra/cql3/UFTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/UFTest.java b/test/unit/org/apache/cassandra/cql3/UFTest.java index b3cc545..3c6baf8 100644 --- a/test/unit/org/apache/cassandra/cql3/UFTest.java +++ b/test/unit/org/apache/cassandra/cql3/UFTest.java @@ -118,16 +118,35 @@ public class UFTest extends CQLTester Assert.assertEquals(1, Functions.find(parseFunctionName(fSin)).size()); - ResultMessage.Prepared prepared = QueryProcessor.prepare( + // create a pairs of Select and Inserts. One statement in each pair uses the function so when we + // drop it those statements should be removed from the cache in QueryProcessor. The other statements + // should be unaffected. + + ResultMessage.Prepared preparedSelect1 = QueryProcessor.prepare( String.format("SELECT key, %s(d) FROM %s.%s", fSin, KEYSPACE, currentTable()), ClientState.forInternalCalls(), false); - Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); + ResultMessage.Prepared preparedSelect2 = QueryProcessor.prepare( + String.format("SELECT key FROM %s.%s", KEYSPACE, currentTable()), + ClientState.forInternalCalls(), false); + ResultMessage.Prepared preparedInsert1 = QueryProcessor.prepare( + String.format("INSERT INTO %s.%s (key, d) VALUES (?, %s(?))", KEYSPACE, currentTable(), fSin), + ClientState.forInternalCalls(), false); + ResultMessage.Prepared preparedInsert2 = QueryProcessor.prepare( + String.format("INSERT INTO %s.%s (key, d) VALUES (?, ?)", KEYSPACE, currentTable()), + ClientState.forInternalCalls(), false); + + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect2.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert2.statementId)); execute("DROP FUNCTION " + fSin + "(double);"); - Assert.assertNull(QueryProcessor.instance.getPrepared(prepared.statementId)); - - // + // the statements which use the dropped function should be removed from cache, with the others remaining + Assert.assertNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect2.statementId)); + Assert.assertNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert2.statementId)); execute("CREATE FUNCTION " + fSin + " ( input double ) " + "RETURNS double " + @@ -136,14 +155,24 @@ public class UFTest extends CQLTester Assert.assertEquals(1, Functions.find(fSinName).size()); - prepared = QueryProcessor.prepare( + preparedSelect1= QueryProcessor.prepare( String.format("SELECT key, %s(d) FROM %s.%s", fSin, KEYSPACE, currentTable()), ClientState.forInternalCalls(), false); - Assert.assertNotNull(QueryProcessor.instance.getPrepared(prepared.statementId)); + preparedInsert1 = QueryProcessor.prepare( + String.format("INSERT INTO %s.%s (key, d) VALUES (?, %s(?))", KEYSPACE, currentTable(), fSin), + ClientState.forInternalCalls(), false); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); dropPerTestKeyspace(); - Assert.assertNull(QueryProcessor.instance.getPrepared(prepared.statementId)); + // again, only the 2 statements referencing the function should be removed from cache + // this time because the statements select from tables in KEYSPACE, only the function + // is scoped to KEYSPACE_PER_TEST + Assert.assertNull(QueryProcessor.instance.getPrepared(preparedSelect1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedSelect2.statementId)); + Assert.assertNull(QueryProcessor.instance.getPrepared(preparedInsert1.statementId)); + Assert.assertNotNull(QueryProcessor.instance.getPrepared(preparedInsert2.statementId)); } @Test
