You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ty...@apache.org on 2015/05/13 19:39:09 UTC
cassandra git commit: Fix string encoding of JSON map keys
Repository: cassandra
Updated Branches:
refs/heads/trunk e379f978b -> b7be1980b
Fix string encoding of JSON map keys
Patch by Tyler Hobbs; reviewed by Jonathan Ellis for CASSANDRA-9190
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/b7be1980
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/b7be1980
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/b7be1980
Branch: refs/heads/trunk
Commit: b7be1980b1b41731f27433cd80743ec76b4beab1
Parents: e379f97
Author: Tyler Hobbs <ty...@gmail.com>
Authored: Wed May 13 12:38:26 2015 -0500
Committer: Tyler Hobbs <ty...@gmail.com>
Committed: Wed May 13 12:38:26 2015 -0500
----------------------------------------------------------------------
src/java/org/apache/cassandra/cql3/Json.java | 12 +++
.../apache/cassandra/db/marshal/ListType.java | 4 +
.../apache/cassandra/db/marshal/MapType.java | 12 ++-
.../apache/cassandra/db/marshal/SetType.java | 4 +
.../apache/cassandra/db/marshal/TupleType.java | 8 +-
.../apache/cassandra/db/marshal/UserType.java | 3 +
.../org/apache/cassandra/cql3/JsonTest.java | 87 ++++++++++++++++++++
7 files changed, 125 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/src/java/org/apache/cassandra/cql3/Json.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/cql3/Json.java b/src/java/org/apache/cassandra/cql3/Json.java
index 905f6e0..e4bce29 100644
--- a/src/java/org/apache/cassandra/cql3/Json.java
+++ b/src/java/org/apache/cassandra/cql3/Json.java
@@ -39,6 +39,18 @@ public class Json
public static final ColumnIdentifier JSON_COLUMN_ID = new ColumnIdentifier("[json]", true);
+ public static Object decodeJson(String json)
+ {
+ try
+ {
+ return JSON_OBJECT_MAPPER.readValue(json, Object.class);
+ }
+ catch (IOException exc)
+ {
+ throw new MarshalException("Error decoding JSON string: " + exc.getMessage());
+ }
+ }
+
public interface Raw
{
public Prepared prepareAndCollectMarkers(CFMetaData metadata, Collection<ColumnDefinition> receivers, VariableSpecifications boundNames);
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/src/java/org/apache/cassandra/db/marshal/ListType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/ListType.java b/src/java/org/apache/cassandra/db/marshal/ListType.java
index bae8043..03f39d7 100644
--- a/src/java/org/apache/cassandra/db/marshal/ListType.java
+++ b/src/java/org/apache/cassandra/db/marshal/ListType.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.*;
+import org.apache.cassandra.cql3.Json;
import org.apache.cassandra.cql3.Lists;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.db.Cell;
@@ -180,6 +181,9 @@ public class ListType<T> extends CollectionType<List<T>>
@Override
public Term fromJSONObject(Object parsed) throws MarshalException
{
+ if (parsed instanceof String)
+ parsed = Json.decodeJson((String) parsed);
+
if (!(parsed instanceof List))
throw new MarshalException(String.format(
"Expected a list, but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/src/java/org/apache/cassandra/db/marshal/MapType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/MapType.java b/src/java/org/apache/cassandra/db/marshal/MapType.java
index 3ed3dd1..983710b 100644
--- a/src/java/org/apache/cassandra/db/marshal/MapType.java
+++ b/src/java/org/apache/cassandra/db/marshal/MapType.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.*;
+import org.apache.cassandra.cql3.Json;
import org.apache.cassandra.cql3.Maps;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.db.Cell;
@@ -200,6 +201,9 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
@Override
public Term fromJSONObject(Object parsed) throws MarshalException
{
+ if (parsed instanceof String)
+ parsed = Json.decodeJson((String) parsed);
+
if (!(parsed instanceof Map))
throw new MarshalException(String.format(
"Expected a map, but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
@@ -229,7 +233,13 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
if (i > 0)
sb.append(", ");
- sb.append(keys.toJSONString(CollectionSerializer.readValue(buffer, protocolVersion), protocolVersion));
+ // map keys must be JSON strings, so convert non-string keys to strings
+ String key = keys.toJSONString(CollectionSerializer.readValue(buffer, protocolVersion), protocolVersion);
+ if (key.startsWith("\""))
+ sb.append(key);
+ else
+ sb.append('"').append(Json.JSON_STRING_ENCODER.quoteAsString(key)).append('"');
+
sb.append(": ");
sb.append(values.toJSONString(CollectionSerializer.readValue(buffer, protocolVersion), protocolVersion));
}
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/src/java/org/apache/cassandra/db/marshal/SetType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/SetType.java b/src/java/org/apache/cassandra/db/marshal/SetType.java
index 372555a..78aac25 100644
--- a/src/java/org/apache/cassandra/db/marshal/SetType.java
+++ b/src/java/org/apache/cassandra/db/marshal/SetType.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.*;
+import org.apache.cassandra.cql3.Json;
import org.apache.cassandra.cql3.Sets;
import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.db.Cell;
@@ -154,6 +155,9 @@ public class SetType<T> extends CollectionType<Set<T>>
@Override
public Term fromJSONObject(Object parsed) throws MarshalException
{
+ if (parsed instanceof String)
+ parsed = Json.decodeJson((String) parsed);
+
if (!(parsed instanceof List))
throw new MarshalException(String.format(
"Expected a list (representing a set), but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/src/java/org/apache/cassandra/db/marshal/TupleType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/TupleType.java b/src/java/org/apache/cassandra/db/marshal/TupleType.java
index 6093137..e874cb6 100644
--- a/src/java/org/apache/cassandra/db/marshal/TupleType.java
+++ b/src/java/org/apache/cassandra/db/marshal/TupleType.java
@@ -25,10 +25,7 @@ import java.util.List;
import com.google.common.base.Objects;
-import org.apache.cassandra.cql3.CQL3Type;
-import org.apache.cassandra.cql3.Constants;
-import org.apache.cassandra.cql3.Term;
-import org.apache.cassandra.cql3.Tuples;
+import org.apache.cassandra.cql3.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.*;
@@ -233,6 +230,9 @@ public class TupleType extends AbstractType<ByteBuffer>
@Override
public Term fromJSONObject(Object parsed) throws MarshalException
{
+ if (parsed instanceof String)
+ parsed = Json.decodeJson((String) parsed);
+
if (!(parsed instanceof List))
throw new MarshalException(String.format(
"Expected a list representation of a tuple, but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/src/java/org/apache/cassandra/db/marshal/UserType.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java
index 45c5f0e..5879d6b 100644
--- a/src/java/org/apache/cassandra/db/marshal/UserType.java
+++ b/src/java/org/apache/cassandra/db/marshal/UserType.java
@@ -145,6 +145,9 @@ public class UserType extends TupleType
@Override
public Term fromJSONObject(Object parsed) throws MarshalException
{
+ if (parsed instanceof String)
+ parsed = Json.decodeJson((String) parsed);
+
if (!(parsed instanceof Map))
throw new MarshalException(String.format(
"Expected a map, but got a %s: %s", parsed.getClass().getSimpleName(), parsed));
http://git-wip-us.apache.org/repos/asf/cassandra/blob/b7be1980/test/unit/org/apache/cassandra/cql3/JsonTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/cql3/JsonTest.java b/test/unit/org/apache/cassandra/cql3/JsonTest.java
index 47d6ddd..305502e 100644
--- a/test/unit/org/apache/cassandra/cql3/JsonTest.java
+++ b/test/unit/org/apache/cassandra/cql3/JsonTest.java
@@ -843,6 +843,93 @@ public class JsonTest extends CQLTester
}
@Test
+ public void testInsertJsonSyntaxWithNonNativeMapKeys() throws Throwable
+ {
+ // JSON doesn't allow non-string keys, so we accept string representations of any type as map keys and
+ // return maps with string keys when necessary.
+
+ String typeName = createType("CREATE TYPE %s (a int)");
+ createTable("CREATE TABLE %s (" +
+ "k int PRIMARY KEY, " +
+ "intmap map<int, boolean>, " +
+ "bigintmap map<bigint, boolean>, " +
+ "varintmap map<varint, boolean>, " +
+ "booleanmap map<boolean, boolean>, " +
+ "floatmap map<float, boolean>, " +
+ "doublemap map<double, boolean>, " +
+ "decimalmap map<decimal, boolean>, " +
+ "tuplemap map<frozen<tuple<int, text>>, boolean>, " +
+ "udtmap map<frozen<" + typeName + ">, boolean>, " +
+ "setmap map<frozen<set<int>>, boolean>, " +
+ "listmap map<frozen<list<int>>, boolean>, " +
+ "textsetmap map<frozen<set<text>>, boolean>, " +
+ "nestedsetmap map<frozen<map<set<text>, text>>, boolean>, " +
+ "frozensetmap frozen<map<set<int>, boolean>>)");
+
+ // int keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"intmap\": {\"0\": true, \"1\": false}}");
+ assertRows(execute("SELECT JSON k, intmap FROM %s"), row("{\"k\": 0, \"intmap\": {\"0\": true, \"1\": false}}"));
+
+ // bigint keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"bigintmap\": {\"0\": true, \"1\": false}}");
+ assertRows(execute("SELECT JSON k, bigintmap FROM %s"), row("{\"k\": 0, \"bigintmap\": {\"0\": true, \"1\": false}}"));
+
+ // varint keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"varintmap\": {\"0\": true, \"1\": false}}");
+ assertRows(execute("SELECT JSON k, varintmap FROM %s"), row("{\"k\": 0, \"varintmap\": {\"0\": true, \"1\": false}}"));
+
+ // boolean keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"booleanmap\": {\"true\": true, \"false\": false}}");
+ assertRows(execute("SELECT JSON k, booleanmap FROM %s"), row("{\"k\": 0, \"booleanmap\": {\"false\": false, \"true\": true}}"));
+
+ // float keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"floatmap\": {\"1.23\": true, \"4.56\": false}}");
+ assertRows(execute("SELECT JSON k, floatmap FROM %s"), row("{\"k\": 0, \"floatmap\": {\"1.23\": true, \"4.56\": false}}"));
+
+ // double keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"doublemap\": {\"1.23\": true, \"4.56\": false}}");
+ assertRows(execute("SELECT JSON k, doublemap FROM %s"), row("{\"k\": 0, \"doublemap\": {\"1.23\": true, \"4.56\": false}}"));
+
+ // decimal keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"decimalmap\": {\"1.23\": true, \"4.56\": false}}");
+ assertRows(execute("SELECT JSON k, decimalmap FROM %s"), row("{\"k\": 0, \"decimalmap\": {\"1.23\": true, \"4.56\": false}}"));
+
+ // tuple<int, text> keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"tuplemap\": {\"[0, \\\"a\\\"]\": true, \"[1, \\\"b\\\"]\": false}}");
+ assertRows(execute("SELECT JSON k, tuplemap FROM %s"), row("{\"k\": 0, \"tuplemap\": {\"[0, \\\"a\\\"]\": true, \"[1, \\\"b\\\"]\": false}}"));
+
+ // UDT keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"udtmap\": {\"{\\\"a\\\": 0}\": true, \"{\\\"a\\\": 1}\": false}}");
+ assertRows(execute("SELECT JSON k, udtmap FROM %s"), row("{\"k\": 0, \"udtmap\": {\"{\\\"a\\\": 0}\": true, \"{\\\"a\\\": 1}\": false}}"));
+
+ // set<int> keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"setmap\": {\"[0, 1, 2]\": true, \"[3, 4, 5]\": false}}");
+ assertRows(execute("SELECT JSON k, setmap FROM %s"), row("{\"k\": 0, \"setmap\": {\"[0, 1, 2]\": true, \"[3, 4, 5]\": false}}"));
+
+ // list<int> keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"listmap\": {\"[0, 1, 2]\": true, \"[3, 4, 5]\": false}}");
+ assertRows(execute("SELECT JSON k, listmap FROM %s"), row("{\"k\": 0, \"listmap\": {\"[0, 1, 2]\": true, \"[3, 4, 5]\": false}}"));
+
+ // set<text> keys
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"textsetmap\": {\"[\\\"0\\\", \\\"1\\\"]\": true, \"[\\\"3\\\", \\\"4\\\"]\": false}}");
+ assertRows(execute("SELECT JSON k, textsetmap FROM %s"), row("{\"k\": 0, \"textsetmap\": {\"[\\\"0\\\", \\\"1\\\"]\": true, \"[\\\"3\\\", \\\"4\\\"]\": false}}"));
+
+ // map<set<text>, text> keys
+ String innerKey1 = "[\"0\", \"1\"]";
+ String fullKey1 = String.format("{\"%s\": \"%s\"}", new String(Json.JSON_STRING_ENCODER.quoteAsString(innerKey1)), "a");
+ String stringKey1 = new String(Json.JSON_STRING_ENCODER.quoteAsString(fullKey1));
+ String innerKey2 = "[\"3\", \"4\"]";
+ String fullKey2 = String.format("{\"%s\": \"%s\"}", new String(Json.JSON_STRING_ENCODER.quoteAsString(innerKey2)), "b");
+ String stringKey2 = new String(Json.JSON_STRING_ENCODER.quoteAsString(fullKey2));
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"nestedsetmap\": {\"" + stringKey1 + "\": true, \"" + stringKey2 + "\": false}}");
+ assertRows(execute("SELECT JSON k, nestedsetmap FROM %s"), row("{\"k\": 0, \"nestedsetmap\": {\"" + stringKey1 + "\": true, \"" + stringKey2 + "\": false}}"));
+
+ // set<int> keys in a frozen map
+ execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"frozensetmap\": {\"[0, 1, 2]\": true, \"[3, 4, 5]\": false}}");
+ assertRows(execute("SELECT JSON k, frozensetmap FROM %s"), row("{\"k\": 0, \"frozensetmap\": {\"[0, 1, 2]\": true, \"[3, 4, 5]\": false}}"));
+ }
+
+ @Test
public void testInsertJsonSyntaxWithTuplesAndUDTs() throws Throwable
{
String typeName = createType("CREATE TYPE %s (a int, b frozen<set<int>>, c tuple<int, int>)");