Final behavior after discussion

This commit is contained in:
conker84 2017-07-05 17:49:45 +02:00
parent 2f88e9896d
commit 9eb568de34
3 changed files with 106 additions and 81 deletions

View file

@ -36,6 +36,7 @@
<neo4j.driver.version>1.4.0</neo4j.driver.version>
<test.neo4j.kernel.version>3.2.1</test.neo4j.kernel.version>
<neo4j.version>3.2.1</neo4j.version>
<jackson.version>2.8.9</jackson.version>
</properties>
<dependencies>
@ -44,7 +45,13 @@
<artifactId>zeppelin-interpreter</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.graph.neo4j;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@ -45,6 +46,8 @@ import org.neo4j.driver.v1.types.Relationship;
import org.neo4j.driver.v1.types.TypeSystem;
import org.neo4j.driver.v1.util.Pair;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Neo4j interpreter for Zeppelin.
*/
@ -54,13 +57,14 @@ public class Neo4jCypherInterpreter extends Interpreter {
public static final String TAB = "\t";
private static final String MAP_KEY_TEMPLATE = "%s.%s";
private static final String ARAY_KEY_TEMPLATE = "%s[%d]";
private Map<String, String> labels;
private Set<String> types;
private final Neo4jConnectionManager neo4jConnectionManager;
private final ObjectMapper jsonMapper = new ObjectMapper();
public Neo4jCypherInterpreter(Properties properties) {
super(properties);
@ -135,16 +139,6 @@ public class Neo4jCypherInterpreter extends Interpreter {
} else if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.PATH())) {
nodes.addAll(Iterables.asList(field.value().asPath().nodes()));
relationships.addAll(Iterables.asList(field.value().asPath().relationships()));
} else if (field.value().hasType(InternalTypeSystem.TYPE_SYSTEM.LIST())) {
List<Object> list = field.value().asList();
for (Object elem : list) {
List<String> lineList = new ArrayList<>();
setTabularResult(field.key(), elem, columns, lineList,
InternalTypeSystem.TYPE_SYSTEM);
if (!lineList.isEmpty()) {
lines.add(lineList);
}
}
} else {
setTabularResult(field.key(), field.value(), columns, line,
InternalTypeSystem.TYPE_SYSTEM);
@ -175,15 +169,8 @@ public class Neo4jCypherInterpreter extends Interpreter {
setTabularResult(String.format(MAP_KEY_TEMPLATE, key, entry.getKey()), entry.getValue(),
columns, line, typeSystem);
}
} else if (value.hasType(typeSystem.LIST())) {
List<Object> list = value.asList();
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
setTabularResult(String.format(ARAY_KEY_TEMPLATE, key, i), elem, columns,
line, typeSystem);
}
} else {
addLine(key, columns, line, value);
addValueToLine(key, columns, line, value);
}
} else if (obj instanceof Map) {
@SuppressWarnings("unchecked")
@ -192,20 +179,12 @@ public class Neo4jCypherInterpreter extends Interpreter {
setTabularResult(String.format(MAP_KEY_TEMPLATE, key, entry.getKey()), entry.getValue(),
columns, line, typeSystem);
}
} else if (obj instanceof List) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) obj;
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
setTabularResult(String.format(ARAY_KEY_TEMPLATE, key, i), elem, columns,
line, typeSystem);
}
} else {
addLine(key, columns, line, obj);
addValueToLine(key, columns, line, obj);
}
}
private void addLine(String key, List<String> columns, List<String> line, Object value) {
private void addValueToLine(String key, List<String> columns, List<String> line, Object value) {
if (!columns.contains(key)) {
columns.add(key);
}
@ -215,23 +194,43 @@ public class Neo4jCypherInterpreter extends Interpreter {
line.add(null);
}
}
if (value != null) {
if (value instanceof Value) {
Value val = (Value) value;
if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.LIST())) {
value = val.asList();
} else if (val.hasType(InternalTypeSystem.TYPE_SYSTEM.MAP())) {
value = val.asMap();
}
}
if (value instanceof Collection) {
try {
value = jsonMapper.writer().writeValueAsString(value);
} catch (Exception ignored) {}
}
}
line.set(position, value == null ? null : value.toString());
}
private InterpreterResult renderTable(List<String> cols, List<List<String>> lines) {
logger.info("Executing renderTable method");
StringBuilder msg = new StringBuilder(TABLE);
msg.append(NEW_LINE);
msg.append(StringUtils.join(cols, TAB));
msg.append(NEW_LINE);
for (List<String> line : lines) {
if (line.size() < cols.size()) {
for (int i = line.size(); i < cols.size(); i++) {
line.add(null);
}
}
msg.append(StringUtils.join(line, TAB));
StringBuilder msg = null;
if (cols.isEmpty()) {
msg = new StringBuilder();
} else {
msg = new StringBuilder(TABLE);
msg.append(NEW_LINE);
msg.append(StringUtils.join(cols, TAB));
msg.append(NEW_LINE);
for (List<String> line : lines) {
if (line.size() < cols.size()) {
for (int i = line.size(); i < cols.size(); i++) {
line.add(null);
}
}
msg.append(StringUtils.join(line, TAB));
msg.append(NEW_LINE);
}
}
return new InterpreterResult(Code.SUCCESS, msg.toString());
}

View file

@ -68,8 +68,6 @@ public class Neo4jCypherInterpreterTest {
+ "MATCH (n), (m) WHERE id(n) = x AND id(m) = toInt(rand() * 1000) "
+ "CREATE (n)-[:%s]->(m)";
private static final String QUERY_TEMPLATE_SIMPLE_OBJECT = "RETURN %s as object";
@BeforeClass
public static void setUpNeo4jServer() throws Exception {
server = TestServerBuilders.newInProcessBuilder()
@ -106,6 +104,28 @@ public class Neo4jCypherInterpreterTest {
interpreter.close();
}
@Test
public void testTableWithArray() {
interpreter.open();
InterpreterResult result = interpreter.interpret("return 'a' as colA, 'b' as colB, [1, 2, 3] as colC", context);
assertEquals(Code.SUCCESS, result.code());
final String tableResult = "colA\tcolB\tcolC\n\"a\"\t\"b\"\t[1,2,3]\n";
assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY));
result = interpreter.interpret("return 'a' as colA, 'b' as colB, [{key: \"value\"}, {key: 1}] as colC", context);
assertEquals(Code.SUCCESS, result.code());
final String tableResultWithMap = "colA\tcolB\tcolC\n\"a\"\t\"b\"\t[{\"key\":\"value\"},{\"key\":1}]\n";
assertEquals(tableResultWithMap, result.toString().replace("%table ", StringUtils.EMPTY));
}
@Test
public void testCreateIndex() {
interpreter.open();
InterpreterResult result = interpreter.interpret("CREATE INDEX ON :Person(name)", context);
assertEquals(Code.SUCCESS, result.code());
assertEquals(StringUtils.EMPTY, result.toString());
}
@Test
public void testRenderTable() {
interpreter.open();
@ -117,82 +137,81 @@ public class Neo4jCypherInterpreterTest {
assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY));
}
@Test
public void testRenderArray() {
interpreter.open();
final String array = "[0, 1, 2]";
final String tableResult = "object\n0\n1\n2\n";
InterpreterResult result = interpreter.interpret(String.format(QUERY_TEMPLATE_SIMPLE_OBJECT, array), context);
assertEquals(Code.SUCCESS, result.code());
assertEquals(tableResult, result.toString().replace("%table ", StringUtils.EMPTY));
}
@Test
public void testRenderMap() {
interpreter.open();
final String json = "{key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}";
final String jsonQuery = "RETURN {key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]} as object";
final String objectKey = "object.key";
final String objectListKey0Inner = "object.listKey[0].inner";
final String objectListKey1Inner = "object.listKey[1].inner";
InterpreterResult result = interpreter.interpret(String.format(QUERY_TEMPLATE_SIMPLE_OBJECT, json), context);
final String objectListKey = "object.listKey";
InterpreterResult result = interpreter.interpret(jsonQuery, context);
assertEquals(Code.SUCCESS, result.code());
String[] rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
assertEquals(rows.length, 2);
List<String> header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB));
assertEquals(header.contains(objectKey), true);
assertEquals(header.contains(objectListKey0Inner), true);
assertEquals(header.contains(objectListKey1Inner), true);
assertEquals(header.contains(objectListKey), true);
List<String> row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value");
assertEquals(row.get(header.indexOf(objectListKey0Inner)), "Map1");
assertEquals(row.get(header.indexOf(objectListKey1Inner)), "Map2");
assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
final String jsonList = "[{key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]},"
+ "{key: \"value2\", listKey: [{inner: \"Map12\"}, {inner: \"Map22\"}]}]";
result = interpreter.interpret(String.format(QUERY_TEMPLATE_SIMPLE_OBJECT, jsonList), context);
final String query = "WITH [{key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]},"
+ "{key: \"value2\", listKey: [{inner: \"Map12\"}, {inner: \"Map22\"}]}] "
+ "AS array UNWIND array AS object RETURN object";
result = interpreter.interpret(query, context);
assertEquals(Code.SUCCESS, result.code());
rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
assertEquals(rows.length, 3);
header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB));
assertEquals(header.contains(objectKey), true);
assertEquals(header.contains(objectListKey0Inner), true);
assertEquals(header.contains(objectListKey1Inner), true);
assertEquals(header.contains(objectListKey), true);
row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value");
assertEquals(row.get(header.indexOf(objectListKey0Inner)), "Map1");
assertEquals(row.get(header.indexOf(objectListKey1Inner)), "Map2");
assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value2");
assertEquals(row.get(header.indexOf(objectListKey0Inner)), "Map12");
assertEquals(row.get(header.indexOf(objectListKey1Inner)), "Map22");
assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map12\"},{\"inner\":\"Map22\"}]");
final String jsonListWithNull = "[{key: \"value\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]},"
+ "{key: \"value2\", listKey: null}]";
final String objectListKey = "object.listKey";
result = interpreter.interpret(String.format(QUERY_TEMPLATE_SIMPLE_OBJECT, jsonListWithNull), context);
final String jsonListWithNullQuery = "WITH [{key: \"value\", listKey: null},"
+ "{key: \"value2\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}] "
+ "AS array UNWIND array AS object RETURN object";
result = interpreter.interpret(jsonListWithNullQuery, context);
assertEquals(Code.SUCCESS, result.code());
rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
assertEquals(rows.length, 3);
header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1));
assertEquals(header.contains(objectKey), true);
assertEquals(header.contains(objectListKey0Inner), true);
assertEquals(header.contains(objectListKey1Inner), true);
assertEquals(header.contains(objectListKey), true);
row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB, -1));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value");
assertEquals(row.get(header.indexOf(objectListKey0Inner)), "Map1");
assertEquals(row.get(header.indexOf(objectListKey1Inner)), "Map2");
assertEquals(row.get(header.indexOf(objectListKey)), StringUtils.EMPTY);
assertEquals(row.get(header.indexOf(objectListKey)), "");
row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB, -1));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value2");
assertEquals(row.get(header.indexOf(objectListKey0Inner)), "");
assertEquals(row.get(header.indexOf(objectListKey1Inner)), "");
assertEquals(row.get(header.indexOf(objectListKey)), "");
assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
final String jsonListWithoutListKeyQuery = "WITH [{key: \"value\"},"
+ "{key: \"value2\", listKey: [{inner: \"Map1\"}, {inner: \"Map2\"}]}] "
+ "AS array UNWIND array AS object RETURN object";
result = interpreter.interpret(jsonListWithoutListKeyQuery, context);
assertEquals(Code.SUCCESS, result.code());
rows = result.toString().replace("%table ", StringUtils.EMPTY).split(Neo4jCypherInterpreter.NEW_LINE);
assertEquals(rows.length, 3);
header = Arrays.asList(rows[0].split(Neo4jCypherInterpreter.TAB, -1));
assertEquals(header.contains(objectKey), true);
assertEquals(header.contains(objectListKey), true);
row = Arrays.asList(rows[1].split(Neo4jCypherInterpreter.TAB, -1));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value");
assertEquals(row.get(header.indexOf(objectListKey)), StringUtils.EMPTY);
row = Arrays.asList(rows[2].split(Neo4jCypherInterpreter.TAB, -1));
assertEquals(row.size(), header.size());
assertEquals(row.get(header.indexOf(objectKey)), "value2");
assertEquals(row.get(header.indexOf(objectListKey)), "[{\"inner\":\"Map1\"},{\"inner\":\"Map2\"}]");
}
@Test