Skip to content

Commit

Permalink
[3KXXQazJ] Fix bug with serializing arrays of primitive values for js…
Browse files Browse the repository at this point in the history
…on export query (#265) (#3362)
  • Loading branch information
gem-neo4j authored Dec 12, 2022
1 parent f13b6c4 commit 171a827
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 24 deletions.
3 changes: 1 addition & 2 deletions core/src/main/java/apoc/export/json/JsonFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

Expand Down Expand Up @@ -248,7 +247,7 @@ private void write(Reporter reporter, JsonGenerator jsonGenerator, ExportConfig
} else {
jsonGenerator.writeStartArray();
}
Object[] list = value.getClass().isArray() ? (Object[]) value : ((List<Object>) value).toArray();
Object[] list = Meta.Types.toObjectArray(value);
for (Object elem : list) {
write(reporter, jsonGenerator, config, keyName, elem, false);
}
Expand Down
26 changes: 26 additions & 0 deletions core/src/main/java/apoc/meta/Meta.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import apoc.result.VirtualNode;
import apoc.result.VirtualRelationship;
import apoc.util.MapUtil;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Chars;
import com.google.common.primitives.Floats;
import com.google.common.primitives.Shorts;
import org.apache.commons.collections4.CollectionUtils;
import org.neo4j.cypher.export.CypherResultSubGraph;
import org.neo4j.cypher.export.DatabaseSubGraph;
Expand Down Expand Up @@ -126,6 +131,27 @@ public static Types of(Object value) {
return type;
}

public static Object[] toObjectArray(Object value) {
if (value instanceof int[]) {
return Arrays.stream((int[]) value).boxed().toArray();
} else if (value instanceof long[]) {
return Arrays.stream((long[]) value).boxed().toArray();
} else if (value instanceof double[]) {
return Arrays.stream((double[]) value).boxed().toArray();
} else if (value instanceof boolean[]) {
return Booleans.asList((boolean[]) value).toArray();
} else if (value instanceof float[]) {
return Floats.asList((float[]) value).toArray();
} else if (value instanceof byte[]) {
return Bytes.asList((byte[]) value).toArray();
} else if (value instanceof char[]) {
return Chars.asList((char[]) value).toArray();
} else if (value instanceof short[]) {
return Shorts.asList((short[]) value).toArray();
}
return value.getClass().isArray() ? (Object[]) value : ((List<Object>) value).toArray();
}

public static Types of(Class<?> type) {
if (type==null) return NULL;
if (type.isArray()) {
Expand Down
68 changes: 46 additions & 22 deletions core/src/test/java/apoc/export/json/ExportJsonTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import org.neo4j.test.rule.ImpermanentDbmsRule;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

Expand All @@ -38,8 +40,8 @@
public class ExportJsonTest {

private static final String DEFLATE_EXT = ".zz";
private static File directory = new File("target/import");
private static File directoryExpected = new File("../docs/asciidoc/modules/ROOT/examples/data/exportJSON");
private static final File directory = new File("target/import");
private static final File directoryExpected = new File("../docs/asciidoc/modules/ROOT/examples/data/exportJSON");

static { //noinspection ResultOfMethodCallIgnored
directory.mkdirs();
Expand All @@ -62,9 +64,7 @@ public void testExportAllJson() throws Exception {
String filename = "all.json";
TestUtil.testCall(db, "CALL apoc.export.json.all($file,null)",
map("file", filename),
(r) -> {
assertResults(filename, r, "database");
}
(r) -> assertResults(filename, r, "database")
);
assertFileEquals(filename);
}
Expand Down Expand Up @@ -217,7 +217,7 @@ public void testExportPointMapDatetimeStreamJson() throws Exception {
public void testExportListNode() throws Exception {
String filename = "listNode.json";

String query = "MATCH (u:User) RETURN COLLECT(u) as list";
String query = "MATCH (u:User) RETURN COLLECT(u) AS list";

TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename, "query", query),
Expand All @@ -227,7 +227,7 @@ public void testExportListNode() throws Exception {

@Test
public void testExportListNodeWithCompression() {
String query = "MATCH (u:User) RETURN COLLECT(u) as list";
String query = "MATCH (u:User) RETURN COLLECT(u) AS list";
final CompressionAlgo algo = DEFLATE;
String expectedFile = "listNode.json";
String filename = expectedFile + DEFLATE_EXT;
Expand All @@ -248,7 +248,7 @@ private void assertionsListNode(String filename, Map<String, Object> r) {
public void testExportListRel() throws Exception {
String filename = "listRel.json";

String query = "MATCH (u:User)-[rel:KNOWS]->(u2:User) RETURN COLLECT(rel) as list";
String query = "MATCH (u:User)-[rel:KNOWS]->(u2:User) RETURN COLLECT(rel) AS list";

TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)", map("file", filename,"query",query),
(r) -> {
Expand All @@ -263,7 +263,7 @@ public void testExportListRel() throws Exception {
public void testExportListPath() throws Exception {
String filename = "listPath.json";

String query = "MATCH p = (u:User)-[rel]->(u2:User) RETURN COLLECT(p) as list";
String query = "MATCH p = (u:User)-[rel]->(u2:User) RETURN COLLECT(p) AS list";

TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename, "query", query),
Expand Down Expand Up @@ -295,7 +295,7 @@ public void testExportMapPath() throws Exception {
db.executeTransactionally("CREATE (f:User {name:'Mike',age:78,male:true})-[:KNOWS {since: 1850}]->(b:User {name:'John',age:18}),(c:User {age:39})");
String filename = "MapPath.json";

String query = "MATCH path = (u:User)-[rel:KNOWS]->(u2:User) RETURN {key:path} as map, 'Kate' as name";
String query = "MATCH path = (u:User)-[rel:KNOWS]->(u2:User) RETURN {key:path} AS map, 'Kate' AS name";

TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename, "query", query),
Expand All @@ -310,7 +310,6 @@ public void testExportMapPath() throws Exception {
@Test
public void testExportMapRel() throws Exception {
String filename = "MapRel.json";

String query = "MATCH p = (u:User)-[rel:KNOWS]->(u2:User) RETURN rel {.*}";

TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
Expand Down Expand Up @@ -342,7 +341,7 @@ public void testExportMapComplex() throws Exception {
@Test
public void testExportGraphJson() throws Exception {
String filename = "graph.json";
TestUtil.testCall(db, "CALL apoc.graph.fromDB('test',{}) yield graph " +
TestUtil.testCall(db, "CALL apoc.graph.fromDB('test',{}) YIELD graph " +
"CALL apoc.export.json.graph(graph, $file) " +
"YIELD nodes, relationships, properties, file, source,format, time " +
"RETURN *", map("file", filename),
Expand All @@ -353,7 +352,7 @@ public void testExportGraphJson() throws Exception {
@Test
public void testExportQueryJson() throws Exception {
String filename = "query.json";
String query = "MATCH (u:User) return u.age, u.name, u.male, u.kids, labels(u)";
String query = "MATCH (u:User) RETURN u.age, u.name, u.male, u.kids, labels(u)";
TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename, "query", query),
(r) -> {
Expand All @@ -367,7 +366,7 @@ public void testExportQueryJson() throws Exception {
@Test
public void testExportQueryNodesJson() throws Exception {
String filename = "query_nodes.json";
String query = "MATCH (u:User) return u";
String query = "MATCH (u:User) RETURN u";
TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename,"query",query),
(r) -> {
Expand All @@ -381,7 +380,7 @@ public void testExportQueryNodesJson() throws Exception {
@Test
public void testExportQueryTwoNodesJson() throws Exception {
String filename = "query_two_nodes.json";
String query = "MATCH (u:User{name:'Adam'}), (l:User{name:'Jim'}) return u, l";
String query = "MATCH (u:User{name:'Adam'}), (l:User{name:'Jim'}) RETURN u, l";
TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)", map("file", filename, "query", query),
(r) -> {
assertTrue("Should get statement",r.get("source").toString().contains("statement: cols(2)"));
Expand All @@ -395,7 +394,7 @@ public void testExportQueryTwoNodesJson() throws Exception {
@Test
public void testExportQueryNodesJsonParams() throws Exception {
String filename = "query_nodes_param.json";
String query = "MATCH (u:User) WHERE u.age > $age return u";
String query = "MATCH (u:User) WHERE u.age > $age RETURN u";
TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file,{params:{age:10}})",
map("file", filename, "query", query),
(r) -> {
Expand All @@ -409,7 +408,7 @@ public void testExportQueryNodesJsonParams() throws Exception {
@Test
public void testExportQueryNodesJsonCount() throws Exception {
String filename = "query_nodes_count.json";
String query = "MATCH (n) return count(n)";
String query = "MATCH (n) RETURN count(n)";
TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename, "query", query),
(r) -> {
Expand All @@ -425,9 +424,9 @@ public void testExportData() throws Exception {
String filename = "data.json";
TestUtil.testCall(db, "MATCH (nod:User) " +
"MATCH ()-[reels:KNOWS]->() " +
"WITH collect(nod) as node, collect(reels) as rels "+
"WITH collect(nod) AS node, collect(reels) AS rels "+
"CALL apoc.export.json.data(node, rels, $file, null) " +
"YIELD nodes, relationships, properties, file, source,format, time " +
"YIELD nodes, relationships, properties, file, source, format, time " +
"RETURN *",
map("file", filename),
(r) -> {
Expand All @@ -440,7 +439,7 @@ public void testExportData() throws Exception {
@Test
public void testExportDataPath() throws Exception {
String filename = "query_nodes_path.json";
String query = "MATCH p = (u:User)-[rel]->(u2:User) return u, rel, u2, p, u.name";
String query = "MATCH p = (u:User)-[rel]->(u2:User) RETURN u, rel, u2, p, u.name";
TestUtil.testCall(db, "CALL apoc.export.json.query($query,$file)",
map("file", filename, "query", query),
(r) -> {
Expand Down Expand Up @@ -527,7 +526,32 @@ public void testExportWgsPoint() {
});

db.executeTransactionally("MATCH (n:Position) DETACH DELETE n");

}

@Test
public void testExportOfNodeIntArrays() {
db.executeTransactionally(
"CREATE (test:Test { intArray: [1,2,3,4], boolArray: [true,false], floatArray: [1.0,2.0] })");

TestUtil.testCall(db,
"CALL apoc.export.json.query(" +
" \"MATCH (test:Test) RETURN test{.intArray, .boolArray, .floatArray} AS data\"," +
" null," +
" {stream:true}" +
" ) " +
"YIELD data " +
"RETURN data",
(r) -> {
String data = (String) r.get("data");
Map<String, Object> map = Util.fromJson(data, Map.class);
Map<String, Object> arrays = (Map<String, Object>) map.get("data");
assertEquals(new ArrayList<>(Arrays.asList(1L, 2L, 3L, 4L)), arrays.get("intArray"));
assertEquals(new ArrayList<>(Arrays.asList(true, false)), arrays.get("boolArray"));
assertEquals(new ArrayList<>(Arrays.asList(1.0, 2.0)), arrays.get("floatArray"));
}
);

db.executeTransactionally("MATCH (n:Test) DETACH DELETE n");
}

private void assertResults(String filename, Map<String, Object> r, final String source) {
Expand Down Expand Up @@ -561,7 +585,7 @@ private void assertStreamResults(Map<String, Object> r, final String source) {
assertTrue("Should get time greater than 0",((long) r.get("time")) >= 0);
}

private void assertStreamEquals(String fileName, String actualText) {
private void assertStreamEquals(String fileName, String actualText) {
String expectedText = TestUtil.readFileToString(new File(directoryExpected, fileName));
String[] actualArray = actualText.split("\n");
String[] expectArray = expectedText.split("\n");
Expand Down

0 comments on commit 171a827

Please sign in to comment.