diff --git a/apoc-core b/apoc-core index 9b8e78a2b8..9f3273619d 160000 --- a/apoc-core +++ b/apoc-core @@ -1 +1 @@ -Subproject commit 9b8e78a2b868cf63ae89e48b25efc72898acfa20 +Subproject commit 9f3273619d50f5330e359424d52a8649f8f883a6 diff --git a/extended/src/main/java/apoc/custom/CypherProcedures.java b/extended/src/main/java/apoc/custom/CypherProcedures.java index d20aa238f8..90078ea2c9 100644 --- a/extended/src/main/java/apoc/custom/CypherProcedures.java +++ b/extended/src/main/java/apoc/custom/CypherProcedures.java @@ -166,14 +166,16 @@ private void validateProcedure(String statement, List input, Lis private void checkMode(QueryExecutionType.QueryType queryType, Mode mode) { Map map = Map.of(QueryExecutionType.QueryType.WRITE, Mode.WRITE, QueryExecutionType.QueryType.READ_ONLY, Mode.READ, - QueryExecutionType.QueryType.READ_WRITE, Mode.WRITE); + QueryExecutionType.QueryType.READ_WRITE, Mode.WRITE, + QueryExecutionType.QueryType.DBMS, Mode.DBMS, + QueryExecutionType.QueryType.SCHEMA_WRITE, Mode.SCHEMA); if (!map.get(queryType).equals(mode)) { throw new RuntimeException(String.format("The query execution type is %s, but you provided mode %s.\n" + "Supported modes are %s", queryType.name(), mode.name(), - map.values().stream().sorted().collect(Collectors.toList()).toString())); + map.values().stream().sorted().collect(Collectors.toList()))); } } diff --git a/extended/src/main/java/apoc/custom/CypherProceduresHandler.java b/extended/src/main/java/apoc/custom/CypherProceduresHandler.java index 0c0a78c1b2..771835bd29 100644 --- a/extended/src/main/java/apoc/custom/CypherProceduresHandler.java +++ b/extended/src/main/java/apoc/custom/CypherProceduresHandler.java @@ -290,6 +290,7 @@ private String serializeSignatures(List signatures) { public static List deserializeSignatures(String s) { List> mapped = Util.fromJson(s, List.class); + if (mapped.isEmpty()) return ProcedureSignature.VOID; return mapped.stream().map(map -> { String typeString = (String) map.get("type"); if (typeString.endsWith("?")) { diff --git a/extended/src/test/java/apoc/custom/CypherProceduresTest.java b/extended/src/test/java/apoc/custom/CypherProceduresTest.java index 4a9c2daf2d..90e1360ce0 100644 --- a/extended/src/test/java/apoc/custom/CypherProceduresTest.java +++ b/extended/src/test/java/apoc/custom/CypherProceduresTest.java @@ -17,6 +17,7 @@ import org.neo4j.graphdb.Node; import org.neo4j.graphdb.QueryExecutionException; import org.neo4j.graphdb.Transaction; +import org.neo4j.procedure.builtin.BuiltInDbmsProcedures; import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; @@ -24,6 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static apoc.custom.CypherProceduresHandler.FUNCTION; import static apoc.custom.CypherProceduresHandler.PROCEDURE; @@ -48,9 +50,10 @@ public class CypherProceduresTest { @Rule public ExpectedException thrown = ExpectedException.none(); + @Before public void setup() { - TestUtil.registerProcedure(db, CypherProcedures.class); + TestUtil.registerProcedure(db, CypherProcedures.class, BuiltInDbmsProcedures.class); } @AfterAll @@ -584,6 +587,36 @@ public void testIssue2032() { assertProcedureFails(String.format(SIGNATURE_SYNTAX_ERROR, procedureSignature), "call apoc.custom.declareProcedure('" + procedureSignature + "','RETURN $first + $s AS answer')"); } + + @Test + public void testIssue3349() { + String procedure = """ + CALL apoc.custom.declareProcedure( + 'retFunctionNames() :: (name :: STRING)', + 'call dbms.listConfig() YIELD name RETURN name', + 'DBMS' + );"""; + db.executeTransactionally(procedure); + List functions = db.executeTransactionally("CALL custom.retFunctionNames()", Map.of(), result -> result + .stream() + .map(m -> (String) m.get("name")) + .collect(Collectors.toList())); + assertFalse(functions.isEmpty()); + + + String procedureVoid = "CALL apoc.custom.declareProcedure(\n" + + " 'setTxMetadata(meta :: MAP) :: VOID',\n" + + " '\n" + + " CALL tx.setMetaData($meta)" + + " ',\n" + + " 'DBMS'\n" + + ");"; + db.executeTransactionally(procedureVoid); + // This should run without exception + db.executeTransactionally("CALL custom.setTxMetadata($meta)", Map.of( + "meta", Map.of("foo", "bar") + )); + } private void assertProcedureFails(String expectedMessage, String query) {