提交 298869cc 编写于 作者: J johnymontana 提交者: Michael Hunger

Add apoc.cypher.runFirstColumn, apoc.coll.sortMulti, apoc.coll.flatten functions

1. Executes a Cypher statement and returns first column only, optionally collected into an array.
2. Sorts a collection of maps by multiple fields and optionally returns a subset
3. Flattens a nested list

Used by neo4j-graphql-js
上级 b40e7358
......@@ -524,6 +524,7 @@ CALL apoc.generate.simple([2,2,2,2], null, null)
[cols="1m,5"]
|===
| CALL apoc.cypher.run(fragment, params) yield value | executes reading fragment with the given parameters
| apoc.cypher.runFirstColumn(statement, params, [expectMultiplevalues]) | function that executes statement with given parameters returning first column only, if expectMultipleValues is true will collect results into a list
| CALL apoc.cypher.runFile(file or url) yield row, result | runs each statement in the file, all semicolon separated - currently no schema operations
| CALL apoc.cypher.runFiles([files or urls]) yield row, result | runs each statement in the files, all semicolon separated
| CALL apoc.cypher.runSchemaFile(file or url) - allows only schema operations, runs each schema statement in the file, all semicolon separated
......@@ -724,6 +725,8 @@ Sometimes type information gets lost, these functions help you to coerce an "Any
| apoc.coll.duplicates(coll) | returns a list of duplicate items in the collection
| apoc.coll.duplicatesWithCount(coll) | returns a list of duplicate items in the collection and their count, keyed by `item` and `count` (e.g., `[{item: xyz, count:2}, {item:zyx, count:5}]`)
| apoc.coll.occurrences(coll, item) | returns the count of the given item in the collection
| apoc.coll.sortMulti | sort list of maps by several sort fields (ascending with ^ prefix) and optionally applies limit and skip
| apoc.coll.flatten | flattens a nested list
|===
=== Lookup Functions
......
package apoc.coll;
import org.neo4j.helpers.collection.Pair;
import org.neo4j.kernel.impl.util.statistics.IntCounter;
import org.neo4j.procedure.*;
import apoc.result.*;
......@@ -9,10 +10,12 @@ import org.neo4j.graphdb.Relationship;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static org.neo4j.helpers.collection.Pair.*;
public class Coll {
......@@ -390,6 +393,13 @@ public class Coll {
return occurrences;
}
@UserFunction
@Description("apoc.coll.flatten(coll) - flattens nested list")
public List<Object> flatten(@Name("coll") List<List<Object>> coll) {
return coll.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
@UserFunction
@Description("apoc.coll.reverse(coll) - returns reversed list")
public List<Object> reverse(@Name("coll") List<Object> coll) {
......@@ -404,4 +414,43 @@ public class Coll {
return reversed;
}
@UserFunction("apoc.coll.sortMulti")
@Description("apoc.coll.sortMulti(coll, ['^name','age'],[limit],[skip]) - sort list of maps by several sort fields (ascending with ^ prefix) and optionally applies limit and skip")
public List<Map<String,Object>> sortMulti(@Name("coll") java.util.List<Map<String,Object>> coll,
@Name(value="orderFields", defaultValue = "[]") java.util.List<String> orderFields,
@Name(value="limit", defaultValue = "-1") long limit,
@Name(value="skip", defaultValue = "0") long skip) {
List<Map<String,Object>> result = new ArrayList<>(coll);
if (orderFields != null && !orderFields.isEmpty()) {
List<Pair<String, Boolean>> fields = orderFields.stream().map(v -> {
boolean asc = v.charAt(0) == '^';
return of(asc ? v.substring(1) : v, asc);
}).collect(Collectors.toList());
Comparator<Map<String, Comparable<Object>>> compare = (o1, o2) -> {
int a = 0;
for (Pair<String, Boolean> s : fields) {
if (a != 0) break;
String name = s.first();
Comparable<Object> v1 = o1.get(name);
Comparable<Object> v2 = o2.get(name);
if (v1 != v2) {
int cmp = (v1 == null) ? -1 : (v2 == null) ? 1 : v1.compareTo(v2);
a = (s.other()) ? cmp : -cmp;
}
}
return a;
};
Collections.sort((List<Map<String, Comparable<Object>>>) (List) result, compare);
}
if (skip > 0 && limit != -1L) return result.subList ((int)skip, (int)(skip + limit));
if (skip > 0) return result.subList ((int)skip, result.size());
if (limit != -1L) return result.subList (0, (int)limit);
return result;
}
}
package apoc.cypher;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Result;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.UserFunction;
import java.util.Collections;
import java.util.Map;
import static apoc.cypher.Cypher.withParamMapping;
/**
* Created by lyonwj on 9/29/17.
*/
public class CypherFunctions {
@Context
public GraphDatabaseService db;
@UserFunction
@Description("apoc.cypher.runFirstColumn(statement, params, expectMultipleValues) - executes statement with given parameters, returns first column only, if expectMultipleValues is true will collect results into an array")
public Object runFirstColumn(@Name("cypher") String statement, @Name("params") Map<String, Object> params, @Name(value = "expectMultipleValues",defaultValue = "true") boolean expectMultipleValues) {
if (params == null) params = Collections.emptyMap();
try (Result result = db.execute(withParamMapping(statement, params.keySet()), params)) {
String firstColumn = result.columns().get(0);
try (ResourceIterator<Object> iter = result.columnAs(firstColumn)) {
if (expectMultipleValues) return iter.stream().toArray();
return iter.hasNext() ? iter.next() : null;
}
}
}
}
......@@ -231,6 +231,18 @@ public class CollTest {
});
}
@Test
public void testSortMapsMulti() throws Exception {
testCall(db,
"RETURN apoc.coll.sortMulti([{name:'foo'},{name:'bar',age:32},{name:'bar',age:42}], ['^name','age'],1,1) as maps",
(row) -> {
List<Map> maps = (List<Map>) row.get("maps");
assertEquals(1, maps.size());
assertEquals("bar", maps.get(0).get("name"));
assertEquals(32L, maps.get(0).get("age")); // 2nd element
});
}
@Test
public void testSetOperations() throws Exception {
testCall(db, "RETURN apoc.coll.union([1,2],[3,2]) AS value", r -> assertEquals(asSet(asList(1L, 2L, 3L)), asSet((Iterable) r.get("value"))));
......@@ -480,4 +492,9 @@ public class CollTest {
testCall(db, "RETURN apoc.coll.reverse([1,2,1,3,2,5,2,3,1,2]) as value",
(row) -> assertEquals(asList(2l, 1l, 3l, 2l, 5l, 2l, 3l, 1l, 2l, 1l), row.get("value")));
}
}
\ No newline at end of file
@Test
public void testFlatten() throws Exception {
testCall(db, "RETURN apoc.coll.flatten([[1,2],[3,4],[4],[5,6,7]]) as value",
(row) -> assertEquals(asList(1L,2L,3L,4L,4L,5L,6L,7L), row.get("value")));
}
}
......@@ -14,17 +14,13 @@ import org.neo4j.helpers.collection.Iterators;
import org.neo4j.test.TestGraphDatabaseFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import static apoc.util.MapUtil.map;
import static apoc.util.TestUtil.testCall;
import static apoc.util.TestUtil.testResult;
import static apoc.util.Util.withMapping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.*;
/**
* @author mh
......@@ -46,6 +42,7 @@ public class CypherTest {
.newGraphDatabase();
TestUtil.registerProcedure(db, Cypher.class);
TestUtil.registerProcedure(db, Utils.class);
TestUtil.registerProcedure(db, CypherFunctions.class);
}
@AfterClass
......@@ -81,6 +78,19 @@ public class CypherTest {
r -> assertEquals(10L, ((Map) r.get("value")).get("b")));
}
@Test
public void testRunFirstColumn() throws Exception {
testCall(db, "RETURN apoc.cypher.runFirstColumn('RETURN a + 7 AS b', {a: 3}, false) AS s",
r -> assertEquals(10L, (r.get("s"))));
}
@Test
public void testRunFirstColumnMultipleValues() throws Exception {
Object[] expected = new Object[]{1L, 2L, 3L};
testCall(db, "RETURN apoc.cypher.runFirstColumn('UNWIND [1, 2, 3] AS e RETURN e', {}, true) AS arr",
r -> assertArrayEquals(expected, (Object[])(r.get("arr"))));
}
@Test
public void testParallel() throws Exception {
int size = 10_000;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册