This is an automated email from the ASF dual-hosted git repository. bereng pushed a commit to branch cassandra-3.11 in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/cassandra-3.11 by this push: new 99e1fcc Materialized views incorrect quoting of UDF 99e1fcc is described below commit 99e1fcc251bd498abab17a59a9fc9593d242671b Author: Bereng <berenguerbl...@gmail.com> AuthorDate: Wed Aug 11 08:43:07 2021 +0200 Materialized views incorrect quoting of UDF patch by Andres de la Peña, Jakub Zytka, Berenguer Blasi; reviewed by Andres de la Peña, Jakub Zytka for CASSANDRA-16836 Co-authored-by: Andres de la Peña <a.penya.gar...@gmail.com> Co-authored-by: Jakub Zytka <jakub.zy...@datastax.com> Co-authored-by: Berenguer Blasi <berenguerbl...@gmail.com> --- .../cassandra/cql3/functions/FunctionCall.java | 2 +- .../cassandra/cql3/functions/FunctionName.java | 18 +++ test/unit/org/apache/cassandra/cql3/ViewTest.java | 122 ++++++++++++++++++++- 3 files changed, 140 insertions(+), 2 deletions(-) diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java index 8dcb3da..c0d616e 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionCall.java @@ -204,7 +204,7 @@ public class FunctionCall extends Term.NonTerminal public String getText() { - return name + terms.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "(", ")")); + return name.toCQLString() + terms.stream().map(Term.Raw::getText).collect(Collectors.joining(", ", "(", ")")); } } } diff --git a/src/java/org/apache/cassandra/cql3/functions/FunctionName.java b/src/java/org/apache/cassandra/cql3/functions/FunctionName.java index aa980e9..efa7adf 100644 --- a/src/java/org/apache/cassandra/cql3/functions/FunctionName.java +++ b/src/java/org/apache/cassandra/cql3/functions/FunctionName.java @@ -20,9 +20,13 @@ package org.apache.cassandra.cql3.functions; import com.google.common.base.Objects; import org.apache.cassandra.config.SchemaConstants; +import org.apache.cassandra.cql3.ColumnIdentifier; public final class FunctionName { + // We special case the token function because that's the only function which name is a reserved keyword + private static final FunctionName TOKEN_FUNCTION_NAME = FunctionName.nativeFunction("token"); + public final String keyspace; public final String name; @@ -79,4 +83,18 @@ public final class FunctionName { return keyspace == null ? name : keyspace + "." + name; } + + /** + * Returns a string representation of the function name that is safe to use directly in CQL queries. + * If necessary, the name components will be double-quoted, and any quotes inside the names will be escaped. + */ + public String toCQLString() + { + String maybeQuotedName = equalsNativeFunction(TOKEN_FUNCTION_NAME) + ? name + : ColumnIdentifier.maybeQuote(name); + return keyspace == null + ? maybeQuotedName + : ColumnIdentifier.maybeQuote(keyspace) + '.' + maybeQuotedName; + } } diff --git a/test/unit/org/apache/cassandra/cql3/ViewTest.java b/test/unit/org/apache/cassandra/cql3/ViewTest.java index 38b1a35..07a518b 100644 --- a/test/unit/org/apache/cassandra/cql3/ViewTest.java +++ b/test/unit/org/apache/cassandra/cql3/ViewTest.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + import com.google.common.util.concurrent.Uninterruptibles; import junit.framework.Assert; @@ -44,6 +46,7 @@ import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.SchemaConstants; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.SystemKeyspace; @@ -52,6 +55,7 @@ import org.apache.cassandra.db.marshal.AsciiType; import org.apache.cassandra.db.view.View; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.schema.SchemaKeyspace; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.FBUtilities; @@ -71,6 +75,7 @@ public class ViewTest extends CQLTester /** Latch used by {@link #testTruncateWhileBuilding()} Byteman injections. */ @SuppressWarnings("unused") private static final CountDownLatch blockViewBuild = new CountDownLatch(1); + private static final AtomicInteger viewNameSeqNumber = new AtomicInteger(); private static final ProtocolVersion protocolVersion = ProtocolVersion.CURRENT; private final List<String> views = new ArrayList<>(); @@ -1503,6 +1508,88 @@ public class ViewTest extends CQLTester } } + @Test + public void testFunctionInWhereClause() throws Throwable + { + // Native token function with lowercase, should be unquoted in the schema where clause + assertEmpty(testFunctionInWhereClause("CREATE TABLE %s (k bigint PRIMARY KEY, v int)", + null, + "CREATE MATERIALIZED VIEW %s AS" + + " SELECT * FROM %%s WHERE k = token(1) AND v IS NOT NULL " + + " PRIMARY KEY (v, k)", + "k = token(1) AND v IS NOT NULL", + "INSERT INTO %s(k, v) VALUES (0, 1)", + "INSERT INTO %s(k, v) VALUES (2, 3)")); + + // Native token function with uppercase, should be unquoted and lowercased in the schema where clause + assertEmpty(testFunctionInWhereClause("CREATE TABLE %s (k bigint PRIMARY KEY, v int)", + null, + "CREATE MATERIALIZED VIEW %s AS" + + " SELECT * FROM %%s WHERE k = TOKEN(1) AND v IS NOT NULL" + + " PRIMARY KEY (v, k)", + "k = token(1) AND v IS NOT NULL", + "INSERT INTO %s(k, v) VALUES (0, 1)", + "INSERT INTO %s(k, v) VALUES (2, 3)")); + + // UDF with lowercase name, shouldn't be quoted in the schema where clause + assertRows(testFunctionInWhereClause("CREATE TABLE %s (k int PRIMARY KEY, v int)", + "CREATE FUNCTION fun()" + + " CALLED ON NULL INPUT" + + " RETURNS int LANGUAGE java" + + " AS 'return 2;'", + "CREATE MATERIALIZED VIEW %s AS " + + " SELECT * FROM %%s WHERE k = fun() AND v IS NOT NULL" + + " PRIMARY KEY (v, k)", + "k = fun() AND v IS NOT NULL", + "INSERT INTO %s(k, v) VALUES (0, 1)", + "INSERT INTO %s(k, v) VALUES (2, 3)"), row(3, 2)); + + // UDF with uppercase name, should be quoted in the schema where clause + assertRows(testFunctionInWhereClause("CREATE TABLE %s (k int PRIMARY KEY, v int)", + "CREATE FUNCTION \"FUN\"()" + + " CALLED ON NULL INPUT" + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return 2;'", + "CREATE MATERIALIZED VIEW %s AS " + + " SELECT * FROM %%s WHERE k = \"FUN\"() AND v IS NOT NULL" + + " PRIMARY KEY (v, k)", + "k = \"FUN\"() AND v IS NOT NULL", + "INSERT INTO %s(k, v) VALUES (0, 1)", + "INSERT INTO %s(k, v) VALUES (2, 3)"), row(3, 2)); + + // UDF with uppercase name conflicting with TOKEN keyword but not with native token function name, + // should be quoted in the schema where clause + assertRows(testFunctionInWhereClause("CREATE TABLE %s (k int PRIMARY KEY, v int)", + "CREATE FUNCTION \"TOKEN\"(x int)" + + " CALLED ON NULL INPUT" + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return x;'", + "CREATE MATERIALIZED VIEW %s AS" + + " SELECT * FROM %%s WHERE k = \"TOKEN\"(2) AND v IS NOT NULL" + + " PRIMARY KEY (v, k)", + "k = \"TOKEN\"(2) AND v IS NOT NULL", + "INSERT INTO %s(k, v) VALUES (0, 1)", + "INSERT INTO %s(k, v) VALUES (2, 3)"), row(3, 2)); + + // UDF with lowercase name conflicting with both TOKEN keyword and native token function name, + // requires specifying the keyspace and should be quoted in the schema where clause + assertRows(testFunctionInWhereClause("CREATE TABLE %s (k int PRIMARY KEY, v int)", + "CREATE FUNCTION \"token\"(x int)" + + " CALLED ON NULL INPUT" + + " RETURNS int" + + " LANGUAGE java" + + " AS 'return x;'", + "CREATE MATERIALIZED VIEW %s AS" + + " SELECT * FROM %%s " + + " WHERE k = " + keyspace() + ".\"token\"(2) AND v IS NOT NULL" + + " PRIMARY KEY (v, k)", + "k = " + keyspace() + ".\"token\"(2) AND v IS NOT NULL", + "INSERT INTO %s(k, v) VALUES (0, 1)", + "INSERT INTO %s(k, v) VALUES (2, 3)"), row(3, 2)); + } + /** * Tests that truncating a table stops the ongoing builds of its materialized views, * so they don't write into the MV data that has been truncated in the base table. @@ -1557,4 +1644,37 @@ public class ViewTest extends CQLTester { return CompactionManager.instance.getPendingTasks() + CompactionManager.instance.getActiveCompactions(); } -} \ No newline at end of file + + private UntypedResultSet testFunctionInWhereClause(String createTableQuery, + String createFunctionQuery, + String createViewQuery, + String expectedSchemaWhereClause, + String... insertQueries) throws Throwable + { + createTable(createTableQuery); + + execute("USE " + keyspace()); + executeNet(protocolVersion, "USE " + keyspace()); + + if (createFunctionQuery != null) + { + execute(createFunctionQuery); + } + + String viewName = "view_" + viewNameSeqNumber.getAndIncrement(); + createView(viewName, createViewQuery); + + // Test the where clause stored in system_schema.views + String schemaQuery = String.format("SELECT where_clause FROM %s.%s WHERE keyspace_name = ? AND view_name = ?", + SchemaConstants.SCHEMA_KEYSPACE_NAME, + SchemaKeyspace.VIEWS); + assertRows(execute(schemaQuery, keyspace(), viewName), row(expectedSchemaWhereClause)); + + for (String insert : insertQueries) + { + execute(insert); + } + + return execute("SELECT * FROM " + viewName); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org