Repository: cassandra Updated Branches: refs/heads/cassandra-2.2 90fc8969a -> 6b1bd1745
Make SELECT JSON and toJson() threadsafe Patch by Ivan Ryndin and Tyler Hobbs; reviewed by Sergio Bossa for CASSANDRA-11048 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/6b1bd174 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/6b1bd174 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/6b1bd174 Branch: refs/heads/cassandra-2.2 Commit: 6b1bd1745ab9f04f9e379e6d22764b97693ba2ad Parents: 90fc896 Author: Tyler Hobbs <tylerlho...@gmail.com> Authored: Thu Feb 11 10:26:47 2016 -0600 Committer: Tyler Hobbs <tylerlho...@gmail.com> Committed: Thu Feb 11 10:26:47 2016 -0600 ---------------------------------------------------------------------- CHANGES.txt | 1 + src/java/org/apache/cassandra/cql3/Json.java | 10 ++- .../cassandra/cql3/selection/Selection.java | 2 +- .../apache/cassandra/db/marshal/AsciiType.java | 2 +- .../apache/cassandra/db/marshal/MapType.java | 2 +- .../apache/cassandra/db/marshal/UTF8Type.java | 2 +- .../apache/cassandra/db/marshal/UserType.java | 2 +- .../cql3/validation/entities/JsonTest.java | 70 +++++++++++++++++--- 8 files changed, 76 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 5674b9d..3aca720 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 2.2.6 + * Make SELECT JSON and toJson() threadsafe (CASSANDRA-11048) * Fix SELECT on tuple relations for mixed ASC/DESC clustering order (CASSANDRA-7281) * (cqlsh) Support utf-8/cp65001 encoding on Windows (CASSANDRA-11030) * Fix paging on DISTINCT queries repeats result when first row in partition changes (CASSANDRA-10010) http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/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 5284793..78fbd08 100644 --- a/src/java/org/apache/cassandra/cql3/Json.java +++ b/src/java/org/apache/cassandra/cql3/Json.java @@ -35,10 +35,16 @@ public class Json { public static final ObjectMapper JSON_OBJECT_MAPPER = new ObjectMapper(); - public static final JsonStringEncoder JSON_STRING_ENCODER = new JsonStringEncoder(); - public static final ColumnIdentifier JSON_COLUMN_ID = new ColumnIdentifier("[json]", true); + /** + * Quotes string contents using standard JSON quoting. + */ + public static String quoteAsJsonString(String s) + { + return new String(JsonStringEncoder.getInstance().quoteAsString(s)); + } + public static Object decodeJson(String json) { try http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/src/java/org/apache/cassandra/cql3/selection/Selection.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/selection/Selection.java b/src/java/org/apache/cassandra/cql3/selection/Selection.java index f6925b2..6016059 100644 --- a/src/java/org/apache/cassandra/cql3/selection/Selection.java +++ b/src/java/org/apache/cassandra/cql3/selection/Selection.java @@ -375,7 +375,7 @@ public abstract class Selection ByteBuffer buffer = row.get(i); sb.append('"'); - sb.append(Json.JSON_STRING_ENCODER.quoteAsString(columnName)); + sb.append(Json.quoteAsJsonString(columnName)); sb.append("\": "); if (buffer == null) sb.append("null"); http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/src/java/org/apache/cassandra/db/marshal/AsciiType.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/AsciiType.java b/src/java/org/apache/cassandra/db/marshal/AsciiType.java index 953fc09..2356c1c 100644 --- a/src/java/org/apache/cassandra/db/marshal/AsciiType.java +++ b/src/java/org/apache/cassandra/db/marshal/AsciiType.java @@ -88,7 +88,7 @@ public class AsciiType extends AbstractType<String> { try { - return '"' + new String(Json.JSON_STRING_ENCODER.quoteAsString(ByteBufferUtil.string(buffer, Charset.forName("US-ASCII")))) + '"'; + return '"' + Json.quoteAsJsonString(ByteBufferUtil.string(buffer, Charset.forName("US-ASCII"))) + '"'; } catch (CharacterCodingException exc) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/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 22f4df1..434702a 100644 --- a/src/java/org/apache/cassandra/db/marshal/MapType.java +++ b/src/java/org/apache/cassandra/db/marshal/MapType.java @@ -244,7 +244,7 @@ public class MapType<K, V> extends CollectionType<Map<K, V>> if (key.startsWith("\"")) sb.append(key); else - sb.append('"').append(Json.JSON_STRING_ENCODER.quoteAsString(key)).append('"'); + sb.append('"').append(Json.quoteAsJsonString(key)).append('"'); sb.append(": "); sb.append(values.toJSONString(CollectionSerializer.readValue(buffer, protocolVersion), protocolVersion)); http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/src/java/org/apache/cassandra/db/marshal/UTF8Type.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/db/marshal/UTF8Type.java b/src/java/org/apache/cassandra/db/marshal/UTF8Type.java index 3fd175c..3b93d9a 100644 --- a/src/java/org/apache/cassandra/db/marshal/UTF8Type.java +++ b/src/java/org/apache/cassandra/db/marshal/UTF8Type.java @@ -67,7 +67,7 @@ public class UTF8Type extends AbstractType<String> { try { - return '"' + new String(Json.JSON_STRING_ENCODER.quoteAsString(ByteBufferUtil.string(buffer, Charset.forName("UTF-8")))) + '"'; + return '"' + Json.quoteAsJsonString(ByteBufferUtil.string(buffer, Charset.forName("UTF-8"))) + '"'; } catch (CharacterCodingException exc) { http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/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 23e2790..40a35b5 100644 --- a/src/java/org/apache/cassandra/db/marshal/UserType.java +++ b/src/java/org/apache/cassandra/db/marshal/UserType.java @@ -206,7 +206,7 @@ public class UserType extends TupleType name = "\"" + name + "\""; sb.append('"'); - sb.append(Json.JSON_STRING_ENCODER.quoteAsString(name)); + sb.append(Json.quoteAsJsonString(name)); sb.append("\": "); ByteBuffer valueBuffer = buffers[i]; http://git-wip-us.apache.org/repos/asf/cassandra/blob/6b1bd174/test/unit/org/apache/cassandra/cql3/validation/entities/JsonTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/JsonTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/JsonTest.java index 7f8fa0b..57df1e6 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/JsonTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/JsonTest.java @@ -25,6 +25,7 @@ import org.apache.cassandra.serializers.SimpleDateSerializer; import org.apache.cassandra.serializers.TimeSerializer; import org.apache.cassandra.utils.ByteBufferUtil; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -32,11 +33,12 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetAddress; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.UUID; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class JsonTest extends CQLTester @@ -927,11 +929,11 @@ public class JsonTest extends CQLTester // 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 fullKey1 = String.format("{\"%s\": \"%s\"}", Json.quoteAsJsonString(innerKey1), "a"); + String stringKey1 = Json.quoteAsJsonString(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)); + String fullKey2 = String.format("{\"%s\": \"%s\"}", Json.quoteAsJsonString(innerKey2), "b"); + String stringKey2 = Json.quoteAsJsonString(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}}")); @@ -955,4 +957,56 @@ public class JsonTest extends CQLTester execute("INSERT INTO %s JSON ?", "{\"k\": 0, \"a\": {\"a\": 0, \"b\": [1, 2, 3], \"c\": null}, \"b\": null}"); assertRows(execute("SELECT k, a.a, a.b, a.c, b FROM %s"), row(0, 0, set(1, 2, 3), null, null)); } + + // done for CASSANDRA-11048 + @Test + public void testJsonTreadSafety() throws Throwable + { + int numThreads = 10; + final int numRows = 10000; + + createTable("CREATE TABLE %s (" + + "k text PRIMARY KEY, " + + "v text)"); + + for (int i = 0; i < numRows; i++) + execute("INSERT INTO %s (k, v) VALUES (?, ?)", "" + i, "" + i); + + long seed = System.nanoTime(); + System.out.println("Seed " + seed); + final Random rand = new Random(seed); + + final Runnable worker = new Runnable() + { + @Override + public void run() + { + try + { + for (int i = 0; i < numRows; i++) + { + String key = "" + rand.nextInt(numRows); + assertRows(execute("SELECT JSON * FROM %s WHERE k = ?", key), + row(String.format("{\"k\": \"%s\", \"v\": \"%s\"}", key, key))); + } + } + catch (Throwable exc) + { + exc.printStackTrace(); + fail(exc.getMessage()); + } + } + }; + + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + List<Future> futures = new ArrayList<>(); + for (int i = 0; i < numThreads; i++) + futures.add(executor.submit(worker)); + + for (Future future : futures) + future.get(10, TimeUnit.SECONDS); + + executor.shutdown(); + Assert.assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + } }