未验证 提交 2410a2e2 编写于 作者: S Stefan Armbruster 提交者: GitHub

support both, single command or list of commands for apoc.systemdb.execute (#1628)

* fixes 1627: support both, a single command or a list of commands for apoc.systemdb.execute

* don't commit once tx has failed
上级 4151e67a
......@@ -2,8 +2,6 @@
= SystemDB
:description: This section describes procedures that can be used to access the contents of Neo4j system db.
In Neo4j 4.0 the concept of multi-database was introduced. There's now a database called `system` which contains some
internal information, e.g. configured permissions. Those can be exposed by APOC.
......@@ -16,7 +14,7 @@ and might change within minor release updates.
[cols="5m,5"]
|===
| apoc.systemdb.graph | returns a `nodes` and `relationships` stored in systemdb.
| apoc.systemdb.execute | executes an DDL command on system db, yields `row` for further processing
| apoc.systemdb.execute | executes an DDL command or a list of DDL commands on system db, yields `row` for further processing
|===
.isType example
......@@ -24,4 +22,5 @@ and might change within minor release updates.
----
CALL apoc.systemdb.graph() YIELD nodes, relationships RETURN *;
CALL apoc.systemdb.execute('SHOW DATABASES') YIELD row RETURN row.name as dbName;
CALL apoc.systemdb.execute(["CREATE USER foo", "GRANT ROLE myRole TO foo"])
----
......@@ -12,10 +12,12 @@ import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.internal.helpers.collection.Iterables;
import org.neo4j.internal.kernel.api.security.SecurityContext;
import org.neo4j.kernel.impl.coreapi.TransactionImpl;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -28,7 +30,7 @@ public class SystemDb {
@Context
public ApocConfig apocConfig;
@Context
public SecurityContext securityContext;
......@@ -63,9 +65,26 @@ public class SystemDb {
}
@Procedure
public Stream<RowResult> execute(@Name("DDL command") String command, @Name(value="params", defaultValue = "{}") Map<String ,Object> params) {
public Stream<RowResult> execute(@Name("DDL commands, either a string or a list of strings") Object ddlStringOrList, @Name(value="params", defaultValue = "{}") Map<String ,Object> params) {
Util.checkAdmin(securityContext,"apoc.systemdb.execute");
return withSystemDbTransaction(tx -> tx.execute(command, params).stream().map(map -> new RowResult(map)));
List<String> commands;
if (ddlStringOrList instanceof String) {
commands = Collections.singletonList((String)ddlStringOrList);
} else if (ddlStringOrList instanceof List) {
commands = (List<String>) ddlStringOrList;
} else {
throw new IllegalArgumentException("don't know how to handle " + ddlStringOrList + ". Supply either a string or a list of strings");
}
Transaction tx = apocConfig.getSystemDb().beginTx(); // we can't use try-with-resources otherwise tx gets closed too early
return commands.stream().flatMap(command -> tx.execute(command, params).stream().map(RowResult::new)).onClose(() -> {
boolean isOpen = ((TransactionImpl) tx).isOpen(); // no other way to check if a tx is still open
if (isOpen) {
tx.commit();
}
tx.close();
});
}
private <T> T withSystemDbTransaction(Function<Transaction, T> function) {
......
......@@ -19,7 +19,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class SystemDbTest {
......@@ -39,11 +40,11 @@ public class SystemDbTest {
List<Relationship> relationships = (List<Relationship>) map.get("relationships");
assertEquals(4, nodes.size());
assertEquals( 2, nodes.stream().filter( node -> "Database".equals(Iterables.single(node.getLabels()).name())).count());
assertEquals( 1, nodes.stream().filter( node -> "User".equals(Iterables.single(node.getLabels()).name())).count());
assertEquals( 1, nodes.stream().filter( node -> "Version".equals(Iterables.single(node.getLabels()).name())).count());
Set<String> names = nodes.stream().map(node -> (String)node.getProperty("name")).filter(Objects::nonNull).collect(Collectors.toSet());
org.hamcrest.MatcherAssert.assertThat( names, Matchers.containsInAnyOrder("neo4j", "system"));
assertEquals(2, nodes.stream().filter(node -> "Database".equals(Iterables.single(node.getLabels()).name())).count());
assertEquals(1, nodes.stream().filter(node -> "User".equals(Iterables.single(node.getLabels()).name())).count());
assertEquals(1, nodes.stream().filter(node -> "Version".equals(Iterables.single(node.getLabels()).name())).count());
Set<String> names = nodes.stream().map(node -> (String) node.getProperty("name")).filter(Objects::nonNull).collect(Collectors.toSet());
org.hamcrest.MatcherAssert.assertThat(names, Matchers.containsInAnyOrder("neo4j", "system"));
assertTrue(relationships.isEmpty());
});
......@@ -59,4 +60,19 @@ public class SystemDbTest {
));
});
}
@Test
public void testExecuteMultipleStatements() {
// we have two databases, so asking twice returns 4
assertEquals(4, TestUtil.count(db, "CALL apoc.systemdb.execute(['SHOW DATABASES','SHOW DATABASES'])"));
}
@Test
public void testWriteStatements() {
// count exhaust the result - this is important here
TestUtil.count(db, "CALL apoc.systemdb.execute([\"CREATE USER dummy SET PASSWORD '123'\"])");
assertEquals(2l, TestUtil.count(db, "CALL apoc.systemdb.execute('SHOW USERS')"));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册