Convert JSON result into an immutable tree of ordinary Java objects (List, Map, String, Double, Integer, Boolean). We use sorted maps, so that the tree is deterministic.
Project: http://git-wip-us.apache.org/repos/asf/incubator-drill/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-drill/commit/32fc5271 Tree: http://git-wip-us.apache.org/repos/asf/incubator-drill/tree/32fc5271 Diff: http://git-wip-us.apache.org/repos/asf/incubator-drill/diff/32fc5271 Branch: refs/heads/master Commit: 32fc5271dc7e115ccde2d442aa97a1db28e20d59 Parents: 7f294ad Author: Julian Hyde <[email protected]> Authored: Fri Feb 15 19:44:05 2013 -0800 Committer: Julian Hyde <[email protected]> Committed: Fri Feb 15 19:44:05 2013 -0800 ---------------------------------------------------------------------- .../org/apache/drill/optiq/EnumerableDrill.java | 75 ++++++++++++-- .../java/org/apache/drill/jdbc/test/JdbcTest.java | 10 +- 2 files changed, 69 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/32fc5271/sandbox/prototype/sqlparser/src/main/java/org/apache/drill/optiq/EnumerableDrill.java ---------------------------------------------------------------------- diff --git a/sandbox/prototype/sqlparser/src/main/java/org/apache/drill/optiq/EnumerableDrill.java b/sandbox/prototype/sqlparser/src/main/java/org/apache/drill/optiq/EnumerableDrill.java index c68bf85..15fe06d 100644 --- a/sandbox/prototype/sqlparser/src/main/java/org/apache/drill/optiq/EnumerableDrill.java +++ b/sandbox/prototype/sqlparser/src/main/java/org/apache/drill/optiq/EnumerableDrill.java @@ -19,6 +19,8 @@ package org.apache.drill.optiq; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import net.hydromatic.linq4j.AbstractEnumerable; import net.hydromatic.linq4j.Enumerable; @@ -38,12 +40,14 @@ import java.util.concurrent.*; * Runtime helper that executes a Drill query and converts it into an * {@link Enumerable}. */ -public class EnumerableDrill<E extends JsonNode> +public class EnumerableDrill<E> extends AbstractEnumerable<E> implements Enumerable<E> { private final LogicalPlan plan; final BlockingQueue queue = new ArrayBlockingQueue(100); + private static final ObjectMapper mapper = createMapper(); + /** Creates a DrillEnumerable. * * @param plan Logical plan @@ -95,7 +99,7 @@ public class EnumerableDrill<E extends JsonNode> // TODO: use a completion service from the container final ExecutorCompletionService<Collection<RunOutcome>> service = new ExecutorCompletionService<Collection<RunOutcome>>( - new ThreadPoolExecutor( 1, 1, 1, TimeUnit.SECONDS, + new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(10))); // Run the plan using an executor. It runs in a different thread, writing @@ -105,7 +109,6 @@ public class EnumerableDrill<E extends JsonNode> final Future<Collection<RunOutcome>> task = runPlan(service); return new Enumerator<E>() { - final ObjectMapper mapper = new ObjectMapper(); private E current; @Override @@ -141,16 +144,66 @@ public class EnumerableDrill<E extends JsonNode> public void reset() { throw new UnsupportedOperationException(); } - - JsonNode parseJson(byte[] bytes) { - try { - return mapper.readTree(bytes); - } catch (IOException e) { - throw new RuntimeException(e); - } - } }; } + + private static ObjectMapper createMapper() { + return new ObjectMapper(); + } + + /** Converts a JSON document, represented as an array of bytes, into a Java + * object (consisting of Map, List, String, Integer, Double, Boolean). */ + static Object parseJson(byte[] bytes) { + try { + return wrapper(mapper.readTree(bytes)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** Converts a JSON node to Java objects ({@link List}, {@link Map}, + * {@link String}, {@link Integer}, {@link Double}, {@link Boolean}. */ + static Object wrapper(JsonNode node) { + switch (node.asToken()) { + case START_OBJECT: + return map((ObjectNode) node); + case START_ARRAY: + return array((ArrayNode) node); + case VALUE_STRING: + return node.asText(); + case VALUE_NUMBER_INT: + return node.asInt(); + case VALUE_NUMBER_FLOAT: + return node.asDouble(); + case VALUE_TRUE: + return Boolean.TRUE; + case VALUE_FALSE: + return Boolean.FALSE; + case VALUE_NULL: + return null; + default: + throw new AssertionError("unexpected: " + node + ": " + node.asToken()); + } + } + + private static List array(ArrayNode node) { + final List<Object> list = new ArrayList<>(); + for (JsonNode jsonNode : node) { + list.add(wrapper(jsonNode)); + } + return Collections.unmodifiableList(list); + } + + private static SortedMap<String, Object> map(ObjectNode node) { + // TreeMap makes the results deterministic. + final TreeMap<String, Object> map = new TreeMap<>(); + final Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); + while (fields.hasNext()) { + Map.Entry<String, JsonNode> next = fields.next(); + map.put(next.getKey(), wrapper(next.getValue())); + } + return Collections.unmodifiableSortedMap(map); + } } // End EnumerableDrill.java http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/32fc5271/sandbox/prototype/sqlparser/src/test/java/org/apache/drill/jdbc/test/JdbcTest.java ---------------------------------------------------------------------- diff --git a/sandbox/prototype/sqlparser/src/test/java/org/apache/drill/jdbc/test/JdbcTest.java b/sandbox/prototype/sqlparser/src/test/java/org/apache/drill/jdbc/test/JdbcTest.java index a6c3bf0..eb0b51a 100644 --- a/sandbox/prototype/sqlparser/src/test/java/org/apache/drill/jdbc/test/JdbcTest.java +++ b/sandbox/prototype/sqlparser/src/test/java/org/apache/drill/jdbc/test/JdbcTest.java @@ -56,11 +56,11 @@ public class JdbcTest extends TestCase { final ResultSet resultSet = statement.executeQuery( "select * from donuts"); assertEquals( - "_extra={\"donuts\":{\"type\":\"donut\",\"ppu\":0.55,\"topping\":[{\"type\":\"None\",\"id\":\"5001\"},{\"type\":\"Glazed\",\"id\":\"5002\"},{\"type\":\"Sugar\",\"id\":\"5005\"},{\"type\":\"Powdered Sugar\",\"id\":\"5007\"},{\"type\":\"Chocolate with Sprinkles\",\"id\":\"5006\"},{\"type\":\"Chocolate\",\"id\":\"5003\"},{\"type\":\"Maple\",\"id\":\"5004\"}],\"name\":\"Cake\",\"sales\":35,\"batters\":{\"batter\":[{\"type\":\"Regular\",\"id\":\"1001\"},{\"type\":\"Chocolate\",\"id\":\"1002\"},{\"type\":\"Blueberry\",\"id\":\"1003\"},{\"type\":\"Devil's Food\",\"id\":\"1004\"}]},\"id\":\"0001\"}}\n" - + "_extra={\"donuts\":{\"type\":\"donut\",\"ppu\":0.69,\"topping\":[{\"type\":\"None\",\"id\":\"5001\"},{\"type\":\"Glazed\",\"id\":\"5002\"},{\"type\":\"Sugar\",\"id\":\"5005\"},{\"type\":\"Chocolate\",\"id\":\"5003\"},{\"type\":\"Maple\",\"id\":\"5004\"}],\"name\":\"Raised\",\"sales\":145,\"batters\":{\"batter\":[{\"type\":\"Regular\",\"id\":\"1001\"}]},\"id\":\"0002\"}}\n" - + "_extra={\"donuts\":{\"type\":\"donut\",\"ppu\":0.55,\"topping\":[{\"type\":\"None\",\"id\":\"5001\"},{\"type\":\"Glazed\",\"id\":\"5002\"},{\"type\":\"Chocolate\",\"id\":\"5003\"},{\"type\":\"Maple\",\"id\":\"5004\"}],\"name\":\"Old Fashioned\",\"sales\":300,\"batters\":{\"batter\":[{\"type\":\"Regular\",\"id\":\"1001\"},{\"type\":\"Chocolate\",\"id\":\"1002\"}]},\"id\":\"0003\"}}\n" - + "_extra={\"donuts\":{\"type\":\"donut\",\"ppu\":0.69,\"topping\":[{\"type\":\"None\",\"id\":\"5001\"},{\"type\":\"Glazed\",\"id\":\"5002\"},{\"type\":\"Sugar\",\"id\":\"5005\"},{\"type\":\"Powdered Sugar\",\"id\":\"5007\"},{\"type\":\"Chocolate with Sprinkles\",\"id\":\"5006\"},{\"type\":\"Chocolate\",\"id\":\"5003\"},{\"type\":\"Maple\",\"id\":\"5004\"}],\"name\":\"Filled\",\"sales\":14,\"batters\":{\"batter\":[{\"type\":\"Regular\",\"id\":\"1001\"},{\"type\":\"Chocolate\",\"id\":\"1002\"},{\"type\":\"Blueberry\",\"id\":\"1003\"},{\"type\":\"Devil's Food\",\"id\":\"1004\"}]},\"id\":\"0004\",\"filling\":[{\"type\":\"None\",\"id\":\"6001\"},{\"type\":\"Raspberry\",\"id\":\"6002\"},{\"type\":\"Lemon\",\"id\":\"6003\"},{\"type\":\"Chocolate\",\"id\":\"6004\"},{\"type\":\"Kreme\",\"id\":\"6005\"}]}}\n" - + "_extra={\"donuts\":{\"type\":\"donut\",\"ppu\":1.0,\"topping\":[{\"type\":\"Glazed\",\"id\":\"5002\"}],\"name\":\"Apple Fritter\",\"sales\":700,\"batters\":{\"batter\":[{\"type\":\"Regular\",\"id\":\"1001\"}]},\"id\":\"0005\"}}\n", + "_extra={donuts={batters={batter=[{id=1001, type=Regular}, {id=1002, type=Chocolate}, {id=1003, type=Blueberry}, {id=1004, type=Devil's Food}]}, id=0001, name=Cake, ppu=0.55, sales=35, topping=[{id=5001, type=None}, {id=5002, type=Glazed}, {id=5005, type=Sugar}, {id=5007, type=Powdered Sugar}, {id=5006, type=Chocolate with Sprinkles}, {id=5003, type=Chocolate}, {id=5004, type=Maple}], type=donut}}\n" + + "_extra={donuts={batters={batter=[{id=1001, type=Regular}]}, id=0002, name=Raised, ppu=0.69, sales=145, topping=[{id=5001, type=None}, {id=5002, type=Glazed}, {id=5005, type=Sugar}, {id=5003, type=Chocolate}, {id=5004, type=Maple}], type=donut}}\n" + + "_extra={donuts={batters={batter=[{id=1001, type=Regular}, {id=1002, type=Chocolate}]}, id=0003, name=Old Fashioned, ppu=0.55, sales=300, topping=[{id=5001, type=None}, {id=5002, type=Glazed}, {id=5003, type=Chocolate}, {id=5004, type=Maple}], type=donut}}\n" + + "_extra={donuts={batters={batter=[{id=1001, type=Regular}, {id=1002, type=Chocolate}, {id=1003, type=Blueberry}, {id=1004, type=Devil's Food}]}, filling=[{id=6001, type=None}, {id=6002, type=Raspberry}, {id=6003, type=Lemon}, {id=6004, type=Chocolate}, {id=6005, type=Kreme}], id=0004, name=Filled, ppu=0.69, sales=14, topping=[{id=5001, type=None}, {id=5002, type=Glazed}, {id=5005, type=Sugar}, {id=5007, type=Powdered Sugar}, {id=5006, type=Chocolate with Sprinkles}, {id=5003, type=Chocolate}, {id=5004, type=Maple}], type=donut}}\n" + + "_extra={donuts={batters={batter=[{id=1001, type=Regular}]}, id=0005, name=Apple Fritter, ppu=1.0, sales=700, topping=[{id=5002, type=Glazed}], type=donut}}\n", toString(resultSet)); resultSet.close(); statement.close();
