Repository: cassandra
Updated Branches:
  refs/heads/trunk 1ecb70165 -> 6618bd89d


Accept dollar-quoted strings in CQL

Patch by Robert Stupp; reviewed by Tyler Hobbs for CASSANDRA-7769


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/6618bd89
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/6618bd89
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/6618bd89

Branch: refs/heads/trunk
Commit: 6618bd89d3d51b06a6c12ec947cce089807a4190
Parents: 1ecb701
Author: Robert Stupp <sn...@snazy.de>
Authored: Tue Sep 23 15:19:13 2014 -0500
Committer: Tyler Hobbs <ty...@datastax.com>
Committed: Tue Sep 23 15:19:13 2014 -0500

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 pylib/cqlshlib/cql3handling.py                  |  5 +-
 pylib/cqlshlib/cqlhandling.py                   |  2 +-
 src/java/org/apache/cassandra/cql3/Cql.g        | 24 +++++--
 .../apache/cassandra/cql3/ErrorCollector.java   |  6 +-
 .../org/apache/cassandra/cql3/CQLTester.java    | 11 +--
 .../org/apache/cassandra/cql3/PgStringTest.java | 76 ++++++++++++++++++++
 test/unit/org/apache/cassandra/cql3/UFTest.java | 20 ++++++
 8 files changed, 128 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index e36ef8f..267c4c2 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0
+ * Accept dollar quoted strings in CQL (CASSANDRA-7769)
  * Make assassinate a first class command (CASSANDRA-7935)
  * Support IN clause on any clustering column (CASSANDRA-4762)
  * Improve compaction logging (CASSANDRA-7818)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/pylib/cqlshlib/cql3handling.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py
index 3425ce2..69fc277 100644
--- a/pylib/cqlshlib/cql3handling.py
+++ b/pylib/cqlshlib/cql3handling.py
@@ -149,7 +149,10 @@ syntax_rules = r'''
 
 JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
 
-<stringLiteral> ::= /'([^']|'')*'/ ;
+<stringLiteral> ::= <quotedStringLiteral>
+                  | <pgStringLiteral> ;
+<quotedStringLiteral> ::= /'([^']|'')*'/ ;
+<pgStringLiteral> ::= /\$\$.*\$\$/;
 <quotedName> ::=    /"([^"]|"")*"/ ;
 <float> ::=         /-?[0-9]+\.[0-9]+/ ;
 <uuid> ::=          
/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ ;

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/pylib/cqlshlib/cqlhandling.py
----------------------------------------------------------------------
diff --git a/pylib/cqlshlib/cqlhandling.py b/pylib/cqlshlib/cqlhandling.py
index fc3dc20..00ba736 100644
--- a/pylib/cqlshlib/cqlhandling.py
+++ b/pylib/cqlshlib/cqlhandling.py
@@ -302,7 +302,7 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet):
         if tok[0] == 'unclosedName':
             # strip one quote
             return tok[1][1:].replace('""', '"')
-        if tok[0] == 'stringLiteral':
+        if tok[0] == 'quotedStringLiteral':
             # strip quotes
             return tok[1][1:-1].replace("''", "'")
         if tok[0] == 'unclosedString':

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/src/java/org/apache/cassandra/cql3/Cql.g
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Cql.g 
b/src/java/org/apache/cassandra/cql3/Cql.g
index 8c40885..e4bfd32 100644
--- a/src/java/org/apache/cassandra/cql3/Cql.g
+++ b/src/java/org/apache/cassandra/cql3/Cql.g
@@ -517,7 +517,6 @@ createFunctionStatement returns [CreateFunctionStatement 
expr]
               ( body = STRING_LITERAL
                 { bodyOrClassName = $body.text; }
               )
-              /* TODO placeholder for pg-style function body */
             )
           )
       )
@@ -1420,9 +1419,26 @@ fragment Y: ('y'|'Y');
 fragment Z: ('z'|'Z');
 
 STRING_LITERAL
-    @init{ StringBuilder b = new StringBuilder(); }
-    @after{ setText(b.toString()); }
-    : '\'' (c=~('\'') { b.appendCodePoint(c);} | '\'' '\'' { 
b.appendCodePoint('\''); })* '\''
+    @init{
+        StringBuilder txt = new StringBuilder(); // temporary to build 
pg-style-string
+    }
+    @after{ setText(txt.toString()); }
+    :
+      /* pg-style string literal */
+      (
+        '\$' '\$'
+        ( /* collect all input until '$$' is reached again */
+          {  (input.size() - input.index() > 1)
+               && !"$$".equals(input.substring(input.index(), input.index() + 
1)) }?
+             => c=. { txt.appendCodePoint(c); }
+        )*
+        '\$' '\$'
+      )
+      |
+      /* conventional quoted string literal */
+      (
+        '\'' (c=~('\'') { txt.appendCodePoint(c);} | '\'' '\'' { 
txt.appendCodePoint('\''); })* '\''
+      )
     ;
 
 QUOTED_NAME

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/src/java/org/apache/cassandra/cql3/ErrorCollector.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/ErrorCollector.java 
b/src/java/org/apache/cassandra/cql3/ErrorCollector.java
index ad0f703..f49cca4 100644
--- a/src/java/org/apache/cassandra/cql3/ErrorCollector.java
+++ b/src/java/org/apache/cassandra/cql3/ErrorCollector.java
@@ -146,7 +146,9 @@ public final class ErrorCollector implements ErrorListener
         if (!includeQueryStart)
             builder.append("...");
 
-        lines[lineIndex(to)] = lines[lineIndex(to)].substring(0, 
getLastCharPositionInLine(to));
+        String toLine = lines[lineIndex(to)];
+        int toEnd = getLastCharPositionInLine(to);
+        lines[lineIndex(to)] = toEnd >= toLine.length() ? toLine : 
toLine.substring(0, toEnd);
         lines[lineIndex(offending)] = 
highlightToken(lines[lineIndex(offending)], offending);
         lines[lineIndex(from)] = 
lines[lineIndex(from)].substring(from.getCharPositionInLine());
 
@@ -162,7 +164,7 @@ public final class ErrorCollector implements ErrorListener
     /**
      * Checks if the specified tokens are valid.
      *
-     * @param token the tokens to check
+     * @param tokens the tokens to check
      * @return <code>true</code> if all the specified tokens are valid ones,
      * <code>false</code> otherwise.
      */

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/test/unit/org/apache/cassandra/cql3/CQLTester.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/CQLTester.java 
b/test/unit/org/apache/cassandra/cql3/CQLTester.java
index b4a1b09..a182f43 100644
--- a/test/unit/org/apache/cassandra/cql3/CQLTester.java
+++ b/test/unit/org/apache/cassandra/cql3/CQLTester.java
@@ -37,7 +37,6 @@ import org.slf4j.LoggerFactory;
 import org.apache.cassandra.SchemaLoader;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.Schema;
-import org.apache.cassandra.db.ConsistencyLevel;
 import org.apache.cassandra.db.Directories;
 import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.db.marshal.*;
@@ -45,7 +44,6 @@ import org.apache.cassandra.exceptions.*;
 import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.serializers.TypeSerializer;
 import org.apache.cassandra.service.StorageService;
-import org.apache.cassandra.utils.ByteBufferUtil;
 
 /**
  * Base class for CQL tests.
@@ -140,11 +138,7 @@ public abstract class CQLTester
             if (currentTable != null)
                 
Keyspace.open(KEYSPACE).getColumnFamilyStore(currentTable).forceFlush().get();
         }
-        catch (InterruptedException e)
-        {
-            throw new RuntimeException(e);
-        }
-        catch (ExecutionException e)
+        catch (InterruptedException | ExecutionException e)
         {
             throw new RuntimeException(e);
         }
@@ -287,7 +281,6 @@ public abstract class CQLTester
             for (int j = 0; j < meta.size(); j++)
             {
                 ColumnSpecification column = meta.get(j);
-                Object expectedValue = expected[j];
                 ByteBuffer expectedByteValue = makeByteBuffer(expected[j], 
(AbstractType)column.type);
                 ByteBuffer actualValue = 
actual.getBytes(column.name.toString());
 
@@ -307,7 +300,7 @@ public abstract class CQLTester
             Assert.fail(String.format("Got less rows than expected. Expected 
%d but got %d.", rows.length, i));
         }
 
-        Assert.assertTrue(String.format("Got more rows than expected. Expected 
%d but got %d", rows.length, i), i == rows.length);
+        Assert.assertTrue(String.format("Got %s rows than expected. Expected 
%d but got %d", rows.length>i ? "less" : "more", rows.length, i), i == 
rows.length);
     }
 
     protected void assertAllRows(Object[]... rows) throws Throwable

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/test/unit/org/apache/cassandra/cql3/PgStringTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/PgStringTest.java 
b/test/unit/org/apache/cassandra/cql3/PgStringTest.java
new file mode 100644
index 0000000..856a255
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/PgStringTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 org.junit.Test;
+
+import org.apache.cassandra.exceptions.SyntaxException;
+
+public class PgStringTest extends CQLTester
+{
+    @Test
+    public void testPgSyleFunction() throws Throwable
+    {
+        execute("create or replace function pg::pgfun1 ( input double ) 
returns text language java\n" +
+                "AS $$return \"foobar\";$$");
+    }
+
+    @Test
+    public void testPgSyleInsert() throws Throwable
+    {
+        createTable("CREATE TABLE %s (key ascii primary key, val text)");
+
+        // some non-terminated pg-strings
+        assertInvalidSyntax("INSERT INTO %s (key, val) VALUES ($ $key_empty$$, 
$$'' value for empty$$)");
+        assertInvalidSyntax("INSERT INTO %s (key, val) VALUES ($$key_empty$$, 
$$'' value for empty$ $)");
+        assertInvalidSyntax("INSERT INTO %s (key, val) VALUES ($$key_empty$ $, 
$$'' value for empty$$)");
+
+        // different pg-style markers for multiple strings
+        execute("INSERT INTO %s (key, val) VALUES ($$prim$ $ $key$$, $$some '' 
arbitrary value$$)");
+        // same empty pg-style marker for multiple strings
+        execute("INSERT INTO %s (key, val) VALUES ($$key_empty$$, $$'' value 
for empty$$)");
+        // stange but valid pg-style
+        execute("INSERT INTO %s (key, val) VALUES ($$$foo$_$foo$$, $$$'' value 
for empty$$)");
+        // these are conventional quoted strings
+        execute("INSERT INTO %s (key, val) VALUES ('$txt$key$$$$txt$', 
'$txt$'' other value$txt$')");
+
+        assertRows(execute("SELECT key, val FROM %s WHERE key='prim$ $ $key'"),
+                   row("prim$ $ $key", "some '' arbitrary value")
+        );
+        assertRows(execute("SELECT key, val FROM %s WHERE key='key_empty'"),
+                   row("key_empty", "'' value for empty")
+        );
+        assertRows(execute("SELECT key, val FROM %s WHERE key='$foo$_$foo'"),
+                   row("$foo$_$foo", "$'' value for empty")
+        );
+        assertRows(execute("SELECT key, val FROM %s WHERE 
key='$txt$key$$$$txt$'"),
+                   row("$txt$key$$$$txt$", "$txt$' other value$txt$")
+        );
+
+        // invalid syntax
+        assertInvalidSyntax("INSERT INTO %s (key, val) VALUES 
($ascii$prim$$$key$invterm$, $txt$some '' arbitrary value$txt$)");
+    }
+
+    @Test(expected = SyntaxException.class)
+    public void testMarkerPgFail() throws Throwable
+    {
+        // must throw SyntaxException - not StringIndexOutOfBoundsException or 
similar
+        execute("create function foo::pgfun1 ( input double ) returns text 
language java\n" +
+                "AS $javasrc$return 0L;$javasrc$;");
+    }
+}

http://git-wip-us.apache.org/repos/asf/cassandra/blob/6618bd89/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 b2e4b84..3a48500 100644
--- a/test/unit/org/apache/cassandra/cql3/UFTest.java
+++ b/test/unit/org/apache/cassandra/cql3/UFTest.java
@@ -410,4 +410,24 @@ public class UFTest extends CQLTester
         // function throws a RuntimeException which is wrapped by 
InvalidRequestException
         assertInvalid("SELECT key, val, foo::jrtef(val) FROM %s");
     }
+
+    @Test
+    public void testJavaDollarQuotedFunction() throws Throwable
+    {
+        String functionBody = "\n" +
+                              "  // parameter val is of type 
java.lang.Double\n" +
+                              "  /* return type is of type java.lang.Double 
*/\n" +
+                              "  if (input == null) {\n" +
+                              "    return null;\n" +
+                              "  }\n" +
+                              "  double v = Math.sin( input.doubleValue() 
);\n" +
+                              "  return \"'\"+Double.valueOf(v)+'\\\'';\n";
+
+        execute("create function foo::pgfun1 ( input double ) returns text 
language java\n" +
+                "AS $$" + functionBody + "$$;");
+        execute("CREATE FUNCTION foo::pgsin ( input double ) RETURNS double 
USING $$org.apache.cassandra.cql3.UFTest#sin$$");
+
+        assertRows(execute("SELECT language, body FROM system.schema_functions 
WHERE namespace='foo' AND name='pgfun1'"),
+                   row("java", functionBody));
+    }
 }

Reply via email to