From f0ae0ca009d7b08c5d6d3c922868851545c5bf3a Mon Sep 17 00:00:00 2001 From: Pontus Melke Date: Thu, 1 Sep 2016 13:53:30 +0200 Subject: [PATCH] Make function be callable from Cypher - Introduce the necessary SPIs for calling functions - Introduce command for executing functions in Cypher --- .../FunctionCallSupportAcceptanceTest.scala | 39 ++++++++---- .../ProcedureCallAcceptanceTest.scala | 7 +-- .../compiler/v3_1/ast/ResolvedCall.scala | 8 +-- ...scala => ResolvedFunctionInvocation.scala} | 24 ++++---- .../commands/ExpressionConverters.scala | 7 ++- .../expressions/FunctionInvocation.scala | 53 +++++++++++++++++ .../executionplan/ProcedureCallMode.scala | 24 ++++++-- .../procs/ProcedureExecutionResult.scala | 6 +- .../v3_1/pipes/ProcedureCallPipe.scala | 10 ++-- .../InternalPlanDescription.scala | 4 +- .../compiler/v3_1/rewriteProcedureCalls.scala | 10 ++-- .../v3_1/spi/DelegatingQueryContext.scala | 20 +++++-- .../compiler/v3_1/spi/PlanContext.scala | 4 +- .../v3_1/spi/ProcedureSignature.scala | 27 ++++----- .../compiler/v3_1/spi/QueryContext.scala | 15 +++-- .../v3_1/RewriteProcedureCallsTest.scala | 6 +- .../compiler/v3_1/ast/CallClauseTest.scala | 14 ++--- .../StatementConvertersTest.scala | 5 +- .../ProcedureCallExecutionPlanTest.scala | 8 +-- .../v3_1/pipes/ProcedureCallPipeTest.scala | 10 ++-- .../planner/LogicalPlanningTestSupport.scala | 8 +-- .../planner/LogicalPlanningTestSupport2.scala | 4 +- .../v3_1/spi/QueryContextAdaptation.scala | 17 ++++-- ...ceptionTranslatingQueryContextFor3_1.scala | 20 +++++-- .../ExceptionTranslatingPlanContext.scala | 4 +- .../v3_1/TransactionBoundPlanContext.scala | 24 ++++++-- .../v3_1/TransactionBoundQueryContext.scala | 59 +++++++++++++++---- .../neo4j/cypher/ProfilerAcceptanceTest.scala | 6 +- .../neo4j/kernel/api/DataWriteOperations.java | 2 + .../org/neo4j/kernel/api/ReadOperations.java | 10 +++- .../kernel/api/SchemaWriteOperations.java | 2 + .../neo4j/kernel/api/dbms/DbmsOperations.java | 2 + .../kernel/api/proc/FunctionSignature.java | 45 ++++++-------- .../kernel/impl/api/OperationsFacade.java | 44 ++++++++++++++ .../dbms/NonTransactionalDbmsOperations.java | 14 +++++ .../kernel/impl/proc/ProcedureRegistry.java | 14 +++-- .../neo4j/kernel/impl/proc/Procedures.java | 7 ++- .../impl/proc/FunctionSignatureTest.java | 10 ---- 38 files changed, 413 insertions(+), 180 deletions(-) rename community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/{ResolvedUserDefinedFunctionInvocation.scala => ResolvedFunctionInvocation.scala} (75%) create mode 100644 community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/commands/expressions/FunctionInvocation.scala diff --git a/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/FunctionCallSupportAcceptanceTest.scala b/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/FunctionCallSupportAcceptanceTest.scala index e9cf1bdb047d4..109affb0dc3c6 100644 --- a/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/FunctionCallSupportAcceptanceTest.scala +++ b/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/FunctionCallSupportAcceptanceTest.scala @@ -21,26 +21,45 @@ package org.neo4j.internal.cypher.acceptance import java.util -import org.neo4j.cypher._ - class FunctionCallSupportAcceptanceTest extends ProcedureCallAcceptanceTest { - ignore("should fail if calling procedure via rule planner") { - an [InternalException] shouldBe thrownBy(execute( - "CYPHER planner=rule CALL db.labels() YIELD label RETURN *" - )) - } - - ignore("should return correctly typed map result (even if converting to and from scala representation internally)") { + test("should return correctly typed map result (even if converting to and from scala representation internally)") { val value = new util.HashMap[String, Any]() value.put("name", "Cypher") value.put("level", 9001) - registerFunctionReturningSingleValue(value) + registerUserFunction(value) // Using graph execute to get a Java value graph.execute("RETURN my.first.value()").stream().toArray.toList should equal(List( + java.util.Collections.singletonMap("my.first.value()", value) + )) + } + + test("should return correctly typed list result (even if converting to and from scala representation internally)") { + val value = new util.ArrayList[Any]() + value.add("Norris") + value.add("Strange") + + registerUserFunction(value) + + // Using graph execute to get a Java value + graph.execute("RETURN my.first.value() AS out").stream().toArray.toList should equal(List( java.util.Collections.singletonMap("out", value) )) } + + test("should return correctly typed stream result (even if converting to and from scala representation internally)") { + val value = new util.ArrayList[Any]() + value.add("Norris") + value.add("Strange") + val stream = value.stream() + + registerUserFunction(stream) + + // Using graph execute to get a Java value + graph.execute("RETURN my.first.value() AS out").stream().toArray.toList should equal(List( + java.util.Collections.singletonMap("out", stream) + )) + } } diff --git a/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/ProcedureCallAcceptanceTest.scala b/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/ProcedureCallAcceptanceTest.scala index 8a5aa7eb1eae5..1c98996717264 100644 --- a/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/ProcedureCallAcceptanceTest.scala +++ b/community/cypher/acceptance-spec-suite/src/test/scala/org/neo4j/internal/cypher/acceptance/ProcedureCallAcceptanceTest.scala @@ -24,10 +24,9 @@ import org.neo4j.cypher._ import org.neo4j.kernel.api.exceptions.ProcedureException import org.neo4j.kernel.api.proc.CallableFunction.BasicFunction import org.neo4j.kernel.api.proc.CallableProcedure.BasicProcedure +import org.neo4j.kernel.api.proc.FunctionSignature._ import org.neo4j.kernel.api.proc.ProcedureSignature._ -import org.neo4j.kernel.api.proc._ -import FunctionSignature._ -import org.neo4j.kernel.api.proc.{Context, Neo4jTypes, ProcedureSignature, FunctionSignature} +import org.neo4j.kernel.api.proc.{Context, Neo4jTypes, ProcedureSignature} abstract class ProcedureCallAcceptanceTest extends ExecutionEngineFunSuite { @@ -57,7 +56,7 @@ abstract class ProcedureCallAcceptanceTest extends ExecutionEngineFunSuite { } } - protected def registerFunctionReturningSingleValue(value: AnyRef) = + protected def registerUserFunction(value: AnyRef) = registerFunction("my.first.value") { builder => val builder = functionSignature(Array("my", "first"), "value") builder.out("out", Neo4jTypes.NTAny) diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedCall.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedCall.scala index 27c1ca104eb9f..83becd48616f5 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedCall.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedCall.scala @@ -19,7 +19,7 @@ */ package org.neo4j.cypher.internal.compiler.v3_1.ast -import org.neo4j.cypher.internal.compiler.v3_1.spi.{ProcedureReadOnlyAccess, ProcedureSignature, QualifiedProcedureName} +import org.neo4j.cypher.internal.compiler.v3_1.spi.{ProcedureReadOnlyAccess, ProcedureSignature, QualifiedName} import org.neo4j.cypher.internal.frontend.v3_1.SemanticCheckResult._ import org.neo4j.cypher.internal.frontend.v3_1._ import org.neo4j.cypher.internal.frontend.v3_1.ast.Expression.SemanticContext @@ -27,10 +27,10 @@ import org.neo4j.cypher.internal.frontend.v3_1.ast._ import org.neo4j.cypher.internal.frontend.v3_1.symbols.{CypherType, _} object ResolvedCall { - def apply(signatureLookup: QualifiedProcedureName => ProcedureSignature)(unresolved: UnresolvedCall): ResolvedCall = { + def apply(signatureLookup: QualifiedName => ProcedureSignature)(unresolved: UnresolvedCall): ResolvedCall = { val UnresolvedCall(_, _, declaredArguments, declaredResults) = unresolved val position = unresolved.position - val signature = signatureLookup(QualifiedProcedureName(unresolved)) + val signature = signatureLookup(QualifiedName(unresolved)) val nonDefaults = signature.inputSignature.flatMap(s => if (s.default.isDefined) None else Some(Parameter(s.name, CTAny)(position))) val callArguments = declaredArguments.getOrElse(nonDefaults) val callResults = declaredResults.getOrElse(signatureResults(signature, position)) @@ -51,7 +51,7 @@ case class ResolvedCall(signature: ProcedureSignature, (val position: InputPosition) extends CallClause { - def qualifiedName: QualifiedProcedureName = signature.name + def qualifiedName: QualifiedName = signature.name def fullyDeclared: Boolean = declaredArguments && declaredResults diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedUserDefinedFunctionInvocation.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedFunctionInvocation.scala similarity index 75% rename from community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedUserDefinedFunctionInvocation.scala rename to community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedFunctionInvocation.scala index 8dd44d0258f19..096191af57af0 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedUserDefinedFunctionInvocation.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/ResolvedFunctionInvocation.scala @@ -19,39 +19,39 @@ */ package org.neo4j.cypher.internal.compiler.v3_1.ast -import org.neo4j.cypher.internal.compiler.v3_1.spi.{UserDefinedFunctionSignature, ProcedureReadOnlyAccess, ProcedureSignature, QualifiedProcedureName} +import org.neo4j.cypher.internal.compiler.v3_1.spi._ import org.neo4j.cypher.internal.frontend.v3_1.SemanticCheckResult._ import org.neo4j.cypher.internal.frontend.v3_1._ import org.neo4j.cypher.internal.frontend.v3_1.ast.Expression.SemanticContext import org.neo4j.cypher.internal.frontend.v3_1.ast._ -import org.neo4j.cypher.internal.frontend.v3_1.ast.functions.UnresolvedFunction -import org.neo4j.cypher.internal.frontend.v3_1.symbols._ -object ResolvedUserDefinedFunctionInvocation { - def apply(signatureLookup: QualifiedProcedureName => Option[UserDefinedFunctionSignature])(unresolved: FunctionInvocation): ResolvedUserDefinedFunctionInvocation = { +object ResolvedFunctionInvocation { + + def apply(signatureLookup: QualifiedName => Option[UserDefinedFunctionSignature])(unresolved: FunctionInvocation): ResolvedFunctionInvocation = { val position = unresolved.position - val name = QualifiedProcedureName(unresolved) + val name = QualifiedName(unresolved) val signature = signatureLookup(name) - ResolvedUserDefinedFunctionInvocation(name, signature, unresolved.args)(position) + ResolvedFunctionInvocation(name, signature, unresolved.args)(position) } } /** * A ResolvedUserDefinedInvocation is a user-defined function where the signature * has been resolve, i.e. verified that it exists in the database + * * @param qualifiedName The qualified name of the function. * @param fcnSignature Either `Some(signature)` if the signature was resolved, or * `None` if the function didn't exist * @param callArguments The argument list to the function * @param position The position in the original query string. */ -case class ResolvedUserDefinedFunctionInvocation(qualifiedName: QualifiedProcedureName, - fcnSignature: Option[UserDefinedFunctionSignature], - callArguments: IndexedSeq[Expression]) - (val position: InputPosition) +case class ResolvedFunctionInvocation(qualifiedName: QualifiedName, + fcnSignature: Option[UserDefinedFunctionSignature], + callArguments: IndexedSeq[Expression]) + (val position: InputPosition) extends Expression with UserDefined { - def coerceArguments: ResolvedUserDefinedFunctionInvocation = fcnSignature match { + def coerceArguments: ResolvedFunctionInvocation = fcnSignature match { case Some(signature) => val optInputFields = signature.inputSignature.map(Some(_)).toStream ++ Stream.continually(None) val coercedArguments = diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/commands/ExpressionConverters.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/commands/ExpressionConverters.scala index 623fd933cba7f..9b7f5d9ebf6b9 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/commands/ExpressionConverters.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/commands/ExpressionConverters.scala @@ -20,15 +20,15 @@ package org.neo4j.cypher.internal.compiler.v3_1.ast.convert.commands import org.neo4j.cypher.internal.compiler.v3_1._ +import org.neo4j.cypher.internal.compiler.v3_1.ast._ import org.neo4j.cypher.internal.compiler.v3_1.ast.convert.commands.PatternConverters._ import org.neo4j.cypher.internal.compiler.v3_1.ast.rewriters.DesugaredMapProjection -import org.neo4j.cypher.internal.compiler.v3_1.ast.{InequalitySeekRangeWrapper, NestedPipeExpression, PrefixSeekRangeWrapper} import org.neo4j.cypher.internal.compiler.v3_1.commands.expressions.ProjectedPath._ -import org.neo4j.cypher.internal.compiler.v3_1.commands.expressions.{InequalitySeekRangeExpression, ProjectedPath, Expression => CommandExpression} +import org.neo4j.cypher.internal.compiler.v3_1.commands.expressions.{Expression => CommandExpression, InequalitySeekRangeExpression, ProjectedPath} import org.neo4j.cypher.internal.compiler.v3_1.commands.predicates.Predicate import org.neo4j.cypher.internal.compiler.v3_1.commands.values.TokenType._ import org.neo4j.cypher.internal.compiler.v3_1.commands.values.UnresolvedRelType -import org.neo4j.cypher.internal.compiler.v3_1.commands.{PathExtractorExpression, predicates, expressions => commandexpressions, values => commandvalues} +import org.neo4j.cypher.internal.compiler.v3_1.commands.{PathExtractorExpression, expressions => commandexpressions, predicates, values => commandvalues} import org.neo4j.cypher.internal.frontend.v3_1.ast._ import org.neo4j.cypher.internal.frontend.v3_1.ast.functions._ import org.neo4j.cypher.internal.frontend.v3_1.helpers.NonEmptyList @@ -289,6 +289,7 @@ object ExpressionConverters { case e: InequalitySeekRangeWrapper => InequalitySeekRangeExpression(e.range.mapBounds(toCommandExpression)) case e: ast.AndedPropertyInequalities => predicates.AndedPropertyComparablePredicates(variable(e.variable), toCommandProperty(e.property), e.inequalities.map(inequalityExpression)) case e: DesugaredMapProjection => commandexpressions.DesugaredMapProjection(e.name.name, e.includeAllProps, mapProjectionItems(e.items)) + case e: ResolvedFunctionInvocation => commandexpressions.FunctionInvocation(e.fcnSignature.get, e.callArguments.map(toCommandExpression)) case e: ast.MapProjection => throw new InternalException("should have been rewritten away") case _ => throw new InternalException(s"Unknown expression type during transformation (${expression.getClass})") diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/commands/expressions/FunctionInvocation.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/commands/expressions/FunctionInvocation.scala new file mode 100644 index 0000000000000..3f37ec932caa3 --- /dev/null +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/commands/expressions/FunctionInvocation.scala @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.cypher.internal.compiler.v3_1.commands.expressions + +import org.neo4j.cypher.internal.compiler.v3_1._ +import org.neo4j.cypher.internal.compiler.v3_1.executionplan.ProcedureCallMode +import org.neo4j.cypher.internal.compiler.v3_1.helpers.RuntimeScalaValueConverter +import org.neo4j.cypher.internal.compiler.v3_1.mutation.GraphElementPropertyFunctions +import org.neo4j.cypher.internal.compiler.v3_1.pipes.QueryState +import org.neo4j.cypher.internal.compiler.v3_1.spi.UserDefinedFunctionSignature +import org.neo4j.cypher.internal.compiler.v3_1.symbols.SymbolTable + +case class FunctionInvocation(signature: UserDefinedFunctionSignature, arguments: IndexedSeq[Expression]) + extends Expression with GraphElementPropertyFunctions { + + private val callMode = ProcedureCallMode.fromAccessMode(signature.accessMode) + + override def apply(ctx: ExecutionContext)(implicit state: QueryState): Any = { + val query = state.query + + val result = callMode.callFunction(query, signature.name, arguments) + + val isGraphKernelResultValue = query.isGraphKernelResultValue _ + val scalaValues = new RuntimeScalaValueConverter(isGraphKernelResultValue, state.typeConverter.asPrivateType) + scalaValues.asDeepScalaValue(result) + } + + override def rewrite(f: (Expression) => Expression) = + f(FunctionInvocation(signature, arguments.map(a => a.rewrite(f)))) + + override def calculateType(symbols: SymbolTable) = signature.outputField.typ + + override def symbolTableDependencies = arguments.flatMap(_.symbolTableDependencies).toSet + + override def toString = s"${signature.name}(${arguments.mkString(",")})" +} diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/ProcedureCallMode.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/ProcedureCallMode.scala index 613f60532e57f..597366c27838e 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/ProcedureCallMode.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/ProcedureCallMode.scala @@ -34,7 +34,9 @@ object ProcedureCallMode { sealed trait ProcedureCallMode { val queryType: InternalQueryType - def call(ctx: QueryContext, name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] + def callProcedure(ctx: QueryContext, name: QualifiedName, args: Seq[Any]): Iterator[Array[AnyRef]] + + def callFunction(ctx: QueryContext, name: QualifiedName, args: Seq[Any]): AnyRef val allowed: Array[String] } @@ -42,14 +44,17 @@ sealed trait ProcedureCallMode { case class LazyReadOnlyCallMode(allowed: Array[String]) extends ProcedureCallMode { override val queryType: InternalQueryType = READ_ONLY - override def call(ctx: QueryContext, name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = + override def callProcedure(ctx: QueryContext, name: QualifiedName, args: Seq[Any]): Iterator[Array[AnyRef]] = ctx.callReadOnlyProcedure(name, args, allowed) + + override def callFunction(ctx: QueryContext, name: QualifiedName, args: Seq[Any]) = + ctx.callReadOnlyFunction(name, args, allowed) } case class EagerReadWriteCallMode(allowed: Array[String]) extends ProcedureCallMode { override val queryType: InternalQueryType = READ_WRITE - override def call(ctx: QueryContext, name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = { + override def callProcedure(ctx: QueryContext, name: QualifiedName, args: Seq[Any]): Iterator[Array[AnyRef]] = { val builder = ArrayBuffer.newBuilder[Array[AnyRef]] val iterator = ctx.callReadWriteProcedure(name, args, allowed) while (iterator.hasNext) { @@ -57,12 +62,15 @@ case class EagerReadWriteCallMode(allowed: Array[String]) extends ProcedureCallM } builder.result().iterator } + + override def callFunction(ctx: QueryContext, name: QualifiedName, args: Seq[Any]) = + ctx.callReadWriteFunction(name, args, allowed) } case class SchemaWriteCallMode(allowed: Array[String]) extends ProcedureCallMode { override val queryType: InternalQueryType = SCHEMA_WRITE - override def call(ctx: QueryContext, name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = { + override def callProcedure(ctx: QueryContext, name: QualifiedName, args: Seq[Any]): Iterator[Array[AnyRef]] = { val builder = ArrayBuffer.newBuilder[Array[AnyRef]] val iterator = ctx.callSchemaWriteProcedure(name, args, allowed) while (iterator.hasNext) { @@ -70,12 +78,15 @@ case class SchemaWriteCallMode(allowed: Array[String]) extends ProcedureCallMode } builder.result().iterator } + + override def callFunction(ctx: QueryContext, name: QualifiedName, args: Seq[Any]) = + ctx.callSchemaWriteFunction(name, args, allowed) } case class DbmsCallMode(allowed: Array[String]) extends ProcedureCallMode { override val queryType: InternalQueryType = DBMS - override def call(ctx: QueryContext, name: QualifiedProcedureName, args: Seq[Any]): Iterator[Array[AnyRef]] = { + override def callProcedure(ctx: QueryContext, name: QualifiedName, args: Seq[Any]): Iterator[Array[AnyRef]] = { val builder = ArrayBuffer.newBuilder[Array[AnyRef]] val iterator = ctx.callDbmsProcedure(name, args, allowed) while (iterator.hasNext) { @@ -83,4 +94,7 @@ case class DbmsCallMode(allowed: Array[String]) extends ProcedureCallMode { } builder.result().iterator } + + override def callFunction(ctx: QueryContext, name: QualifiedName, args: Seq[Any]) = + ctx.callDbmsFunction(name, args, allowed) } diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureExecutionResult.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureExecutionResult.scala index 2f7657688a6c9..416d42c9b8602 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureExecutionResult.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureExecutionResult.scala @@ -24,7 +24,7 @@ import java.util import org.neo4j.cypher.internal.compiler.v3_1.codegen.ResultRowImpl import org.neo4j.cypher.internal.compiler.v3_1.executionplan.{InternalQueryType, ProcedureCallMode, StandardInternalExecutionResult} import org.neo4j.cypher.internal.compiler.v3_1.planDescription.InternalPlanDescription -import org.neo4j.cypher.internal.compiler.v3_1.spi.{QualifiedProcedureName, InternalResultVisitor, QueryContext} +import org.neo4j.cypher.internal.compiler.v3_1.spi.{InternalResultVisitor, QualifiedName, QueryContext} import org.neo4j.cypher.internal.compiler.v3_1.{ExecutionMode, InternalQueryStatistics, ProfileMode, TaskCloser} import org.neo4j.cypher.internal.frontend.v3_1.ProfilerStatisticsNotReadyException @@ -42,7 +42,7 @@ import org.neo4j.cypher.internal.frontend.v3_1.ProfilerStatisticsNotReadyExcepti */ class ProcedureExecutionResult[E <: Exception](context: QueryContext, taskCloser: TaskCloser, - name: QualifiedProcedureName, + name: QualifiedName, callMode: ProcedureCallMode, args: Seq[Any], indexResultNameMappings: Seq[(Int, String)], @@ -55,7 +55,7 @@ class ProcedureExecutionResult[E <: Exception](context: QueryContext, private final val executionResults = executeCall // The signature mode is taking care of eagerization - protected def executeCall = callMode.call(context, name, args) + protected def executeCall = callMode.callProcedure(context, name, args) override protected def createInner = new util.Iterator[util.Map[String, Any]]() { override def next(): util.Map[String, Any] = diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipe.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipe.scala index b173d003e87fd..18126c3a41327 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipe.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipe.scala @@ -25,7 +25,7 @@ import org.neo4j.cypher.internal.compiler.v3_1.executionplan.{AllEffects, Proced import org.neo4j.cypher.internal.compiler.v3_1.helpers.{CollectionSupport, RuntimeJavaValueConverter, RuntimeScalaValueConverter} import org.neo4j.cypher.internal.compiler.v3_1.planDescription.InternalPlanDescription.Arguments.Signature import org.neo4j.cypher.internal.compiler.v3_1.planDescription.{InternalPlanDescription, PlanDescriptionImpl, SingleChild} -import org.neo4j.cypher.internal.compiler.v3_1.spi.{ProcedureSignature, QualifiedProcedureName} +import org.neo4j.cypher.internal.compiler.v3_1.spi.{ProcedureSignature, QualifiedName} import org.neo4j.cypher.internal.frontend.v3_1.symbols.CypherType object ProcedureCallRowProcessing { @@ -39,7 +39,7 @@ case object FlatMapAndAppendToRow extends ProcedureCallRowProcessing case object PassThroughRow extends ProcedureCallRowProcessing case class ProcedureCallPipe(source: Pipe, - name: QualifiedProcedureName, + name: QualifiedName, callMode: ProcedureCallMode, argExprs: Seq[Expression], rowProcessing: ProcedureCallRowProcessing, @@ -73,7 +73,7 @@ case class ProcedureCallPipe(source: Pipe, input flatMap { input => val argValues = argExprs.map(arg => converter.asDeepJavaValue(arg(input)(state))) - val results = callMode.call(qtx, name, argValues) + val results = callMode.callProcedure(qtx, name, argValues) results map { resultValues => resultIndices foreach { case (k, v) => val javaValue = resultValues(k) @@ -92,7 +92,7 @@ case class ProcedureCallPipe(source: Pipe, val qtx = state.query input map { input => val argValues = argExprs.map(arg => converter.asDeepJavaValue(arg(input)(state))) - val results = callMode.call(qtx, name, argValues) + val results = callMode.callProcedure(qtx, name, argValues) // the iterator here should be empty; we'll drain just in case while (results.hasNext) results.next() input @@ -101,7 +101,7 @@ case class ProcedureCallPipe(source: Pipe, override def planDescriptionWithoutCardinality: InternalPlanDescription = { PlanDescriptionImpl(this.id, "ProcedureCall", SingleChild(source.planDescription), Seq( - Signature(QualifiedProcedureName(name.namespace, name.name), argExprs, resultSymbols) + Signature(QualifiedName(name.namespace, name.name), argExprs, resultSymbols) ), variables) } diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/planDescription/InternalPlanDescription.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/planDescription/InternalPlanDescription.scala index e30ee3f97b91a..4dd243e238940 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/planDescription/InternalPlanDescription.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/planDescription/InternalPlanDescription.scala @@ -22,7 +22,7 @@ package org.neo4j.cypher.internal.compiler.v3_1.planDescription import org.neo4j.cypher.internal.compiler.v3_1.commands import org.neo4j.cypher.internal.compiler.v3_1.pipes.{LazyLabel, LazyTypes, SeekArgs => PipeEntityByIdRhs} import org.neo4j.cypher.internal.compiler.v3_1.planDescription.InternalPlanDescription.Arguments._ -import org.neo4j.cypher.internal.compiler.v3_1.spi.QualifiedProcedureName +import org.neo4j.cypher.internal.compiler.v3_1.spi.QualifiedName import org.neo4j.cypher.internal.frontend.v3_1.symbols.CypherType import org.neo4j.cypher.internal.frontend.v3_1.{SemanticDirection, ast} @@ -93,7 +93,7 @@ object InternalPlanDescription { case class KeyExpressions(expressions: Seq[commands.expressions.Expression]) extends Argument case class EntityByIdRhs(value: PipeEntityByIdRhs) extends Argument case class EstimatedRows(value: Double) extends Argument - case class Signature(procedureName: QualifiedProcedureName, + case class Signature(procedureName: QualifiedName, args: Seq[commands.expressions.Expression], results: Seq[(String, CypherType)]) extends Argument case class Version(value: String) extends Argument { diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/rewriteProcedureCalls.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/rewriteProcedureCalls.scala index f724bda351987..eef732037c455 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/rewriteProcedureCalls.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/rewriteProcedureCalls.scala @@ -19,8 +19,8 @@ */ package org.neo4j.cypher.internal.compiler.v3_1 -import org.neo4j.cypher.internal.compiler.v3_1.ast.{ResolvedCall, ResolvedUserDefinedFunctionInvocation} -import org.neo4j.cypher.internal.compiler.v3_1.spi.{ProcedureSignature, QualifiedProcedureName, UserDefinedFunctionSignature} +import org.neo4j.cypher.internal.compiler.v3_1.ast.{ResolvedCall, ResolvedFunctionInvocation} +import org.neo4j.cypher.internal.compiler.v3_1.spi.{ProcedureSignature, QualifiedName, UserDefinedFunctionSignature} import org.neo4j.cypher.internal.frontend.v3_1.ast._ import org.neo4j.cypher.internal.frontend.v3_1.{Rewriter, bottomUp} @@ -28,8 +28,8 @@ import org.neo4j.cypher.internal.frontend.v3_1.{Rewriter, bottomUp} // turns unresolved calls into resolved calls object rewriteProcedureCalls { - def apply(procSignatureLookup: QualifiedProcedureName => ProcedureSignature, - funcSignatureLookup: QualifiedProcedureName => Option[UserDefinedFunctionSignature]) = { + def apply(procSignatureLookup: QualifiedName => ProcedureSignature, + funcSignatureLookup: QualifiedName => Option[UserDefinedFunctionSignature]) = { // rewriter that amends unresolved procedure calls with procedure signature information val resolveCalls = bottomUp(Rewriter.lift { @@ -41,7 +41,7 @@ object rewriteProcedureCalls { coerced case function: FunctionInvocation if function.needsToBeResolved => - val resolved = ResolvedUserDefinedFunctionInvocation(funcSignatureLookup)(function) + val resolved = ResolvedFunctionInvocation(funcSignatureLookup)(function) // We coerce here to ensure that the semantic check run after this rewriter assigns a type // to the coercion expression diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/DelegatingQueryContext.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/DelegatingQueryContext.scala index 4e171727d5377..de0f354b7412e 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/DelegatingQueryContext.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/DelegatingQueryContext.scala @@ -181,18 +181,30 @@ class DelegatingQueryContext(val inner: QueryContext) extends QueryContext { filters: Seq[KernelPredicate[PropertyContainer]]): Iterator[Path] = manyDbHits(inner.allShortestPath(left, right, depth, expander, pathPredicate, filters)) - override def callReadOnlyProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = + override def callReadOnlyProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = singleDbHit(inner.callReadOnlyProcedure(name, args, allowed)) - override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = + override def callReadWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = singleDbHit(inner.callReadWriteProcedure(name, args, allowed)) - override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = + override def callSchemaWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = singleDbHit(inner.callSchemaWriteProcedure(name, args, allowed)) - override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = + override def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = inner.callDbmsProcedure(name, args, allowed) + override def callReadOnlyFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + singleDbHit(inner.callReadOnlyFunction(name, args, allowed)) + + override def callReadWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + singleDbHit(inner.callReadWriteFunction(name, args, allowed)) + + override def callSchemaWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + singleDbHit(inner.callSchemaWriteFunction(name, args, allowed)) + + override def callDbmsFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + inner.callDbmsFunction(name, args, allowed) + override def isGraphKernelResultValue(v: Any): Boolean = inner.isGraphKernelResultValue(v) diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/PlanContext.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/PlanContext.scala index 252eefd5261e1..27cf31d4eb327 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/PlanContext.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/PlanContext.scala @@ -63,6 +63,6 @@ trait PlanContext extends TokenContext with ProcedureSignatureResolver { } trait ProcedureSignatureResolver { - def procedureSignature(name: QualifiedProcedureName): ProcedureSignature - def functionSignature(name: QualifiedProcedureName): Option[UserDefinedFunctionSignature] + def procedureSignature(name: QualifiedName): ProcedureSignature + def functionSignature(name: QualifiedName): Option[UserDefinedFunctionSignature] } diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/ProcedureSignature.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/ProcedureSignature.scala index 72c9816a14884..15f773baa2ccd 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/ProcedureSignature.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/ProcedureSignature.scala @@ -20,10 +20,9 @@ package org.neo4j.cypher.internal.compiler.v3_1.spi import org.neo4j.cypher.internal.frontend.v3_1.ast.{FunctionInvocation, UnresolvedCall} -import org.neo4j.cypher.internal.frontend.v3_1.ast.functions.UnresolvedFunction import org.neo4j.cypher.internal.frontend.v3_1.symbols.CypherType -case class ProcedureSignature(name: QualifiedProcedureName, +case class ProcedureSignature(name: QualifiedName, inputSignature: IndexedSeq[FieldSignature], outputSignature: Option[IndexedSeq[FieldSignature]], deprecationInfo: Option[String], @@ -34,20 +33,20 @@ case class ProcedureSignature(name: QualifiedProcedureName, def isVoid = outputSignature.isEmpty } -case class UserDefinedFunctionSignature(name: QualifiedProcedureName, - inputSignature: IndexedSeq[FieldSignature], - outputFields: IndexedSeq[FieldSignature], - deprecationInfo: Option[String], - accessMode: ProcedureAccessMode) - -object QualifiedProcedureName { - def apply(unresolved: UnresolvedCall): QualifiedProcedureName = - QualifiedProcedureName(unresolved.procedureNamespace.parts, unresolved.procedureName.name) - def apply(unresolved: FunctionInvocation): QualifiedProcedureName = - QualifiedProcedureName(unresolved.namespace.parts, unresolved.functionName.name) +case class UserDefinedFunctionSignature(name: QualifiedName, + inputSignature: IndexedSeq[FieldSignature], + outputField: FieldSignature, + deprecationInfo: Option[String], + accessMode: ProcedureAccessMode) + +object QualifiedName { + def apply(unresolved: UnresolvedCall): QualifiedName = + QualifiedName(unresolved.procedureNamespace.parts, unresolved.procedureName.name) + def apply(unresolved: FunctionInvocation): QualifiedName = + QualifiedName(unresolved.namespace.parts, unresolved.functionName.name) } -case class QualifiedProcedureName(namespace: Seq[String], name: String) { +case class QualifiedName(namespace: Seq[String], name: String) { override def toString = (namespace :+ name).mkString(".") } diff --git a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContext.scala b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContext.scala index 87d014e57f22c..5d6a7be00ef03 100644 --- a/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContext.scala +++ b/community/cypher/cypher-compiler-3.1/src/main/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContext.scala @@ -156,13 +156,20 @@ trait QueryContext extends TokenContext { def lockRelationships(relIds: Long*) - def callReadOnlyProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] + def callReadOnlyProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] - def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] + def callReadWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] - def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] + def callSchemaWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] - def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] + def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] + + def callReadOnlyFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef + + def callReadWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef + def callSchemaWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef + + def callDbmsFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef // Check if a runtime value is a node, relationship, path or some such value returned from // other query context values by calling down to the underlying database diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/RewriteProcedureCallsTest.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/RewriteProcedureCallsTest.scala index e1a29611d6f12..9d35788555e29 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/RewriteProcedureCallsTest.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/RewriteProcedureCallsTest.scala @@ -29,13 +29,13 @@ class RewriteProcedureCallsTest extends CypherFunSuite with AstConstructionTestS val ns = Namespace(List("my", "proc"))(pos) val name = ProcedureName("foo")(pos) - val qualifiedName = QualifiedProcedureName(ns.parts, name.name) + val qualifiedName = QualifiedName(ns.parts, name.name) val signatureInputs = IndexedSeq(FieldSignature("a", CTInteger)) val signatureOutputs = Some(IndexedSeq(FieldSignature("x", CTInteger), FieldSignature("y", CTList(CTNode)))) val signature = ProcedureSignature(qualifiedName, signatureInputs, signatureOutputs, None, ProcedureReadOnlyAccess(Array.empty[String])) - val procLookup: (QualifiedProcedureName) => ProcedureSignature = _ => signature - val fcnLookup: (QualifiedProcedureName) => Option[UserDefinedFunctionSignature] = _ => None + val procLookup: (QualifiedName) => ProcedureSignature = _ => signature + val fcnLookup: (QualifiedName) => Option[UserDefinedFunctionSignature] = _ => None test("should resolve standalone procedure calls") { val unresolved = UnresolvedCall(ns, name, None, None)(pos) diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/CallClauseTest.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/CallClauseTest.scala index 65960bd77d722..85a44c46afc25 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/CallClauseTest.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/CallClauseTest.scala @@ -19,7 +19,7 @@ */ package org.neo4j.cypher.internal.compiler.v3_1.ast -import org.neo4j.cypher.internal.compiler.v3_1.spi.{FieldSignature, ProcedureReadOnlyAccess, ProcedureSignature, QualifiedProcedureName} +import org.neo4j.cypher.internal.compiler.v3_1.spi._ import org.neo4j.cypher.internal.frontend.v3_1.ast._ import org.neo4j.cypher.internal.frontend.v3_1.symbols._ import org.neo4j.cypher.internal.frontend.v3_1.test_helpers.CypherFunSuite @@ -29,7 +29,7 @@ class CallClauseTest extends CypherFunSuite with AstConstructionTestSupport { val ns = Namespace(List("my", "proc"))(pos) val name = ProcedureName("foo")(pos) - val qualifiedName = QualifiedProcedureName(ns.parts, name.name) + val qualifiedName = QualifiedName(ns.parts, name.name) test("should resolve CALL my.proc.foo") { val unresolved = UnresolvedCall(ns, name, None, None)(pos) @@ -51,7 +51,7 @@ class CallClauseTest extends CypherFunSuite with AstConstructionTestSupport { )(pos) ) - QualifiedProcedureName(unresolved) should equal(resolved.qualifiedName) + QualifiedName(unresolved) should equal(resolved.qualifiedName) resolved.callResultTypes should equal(Seq("x" -> CTInteger, "y" -> CTList(CTNode))) resolved.callResultIndices should equal(Seq(0 -> "x", 1 -> "y")) } @@ -76,7 +76,7 @@ class CallClauseTest extends CypherFunSuite with AstConstructionTestSupport { )(pos) ) - QualifiedProcedureName(unresolved) should equal(resolved.qualifiedName) + QualifiedName(unresolved) should equal(resolved.qualifiedName) resolved.callResultTypes should equal(Seq.empty) resolved.callResultIndices should equal(Seq.empty) } @@ -101,7 +101,7 @@ class CallClauseTest extends CypherFunSuite with AstConstructionTestSupport { )(pos) ) - QualifiedProcedureName(unresolved) should equal(resolved.qualifiedName) + QualifiedName(unresolved) should equal(resolved.qualifiedName) resolved.callResultTypes should equal(Seq("x" -> CTInteger, "y" -> CTList(CTNode))) resolved.callResultIndices should equal(Seq(0 -> "x", 1 -> "y")) } @@ -126,7 +126,7 @@ class CallClauseTest extends CypherFunSuite with AstConstructionTestSupport { )(pos) ) - QualifiedProcedureName(unresolved) should equal(resolved.qualifiedName) + QualifiedName(unresolved) should equal(resolved.qualifiedName) resolved.callResultTypes should equal(Seq("x" -> CTInteger, "y" -> CTList(CTNode))) resolved.callResultIndices should equal(Seq(0 -> "x", 1 -> "y")) } @@ -151,7 +151,7 @@ class CallClauseTest extends CypherFunSuite with AstConstructionTestSupport { )(pos) ) - QualifiedProcedureName(unresolved) should equal(resolved.qualifiedName) + QualifiedName(unresolved) should equal(resolved.qualifiedName) resolved.callResultTypes should equal(Seq.empty) resolved.callResultIndices should equal(Seq.empty) } diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/plannerQuery/StatementConvertersTest.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/plannerQuery/StatementConvertersTest.scala index 8878ad90aafa6..45f6d550c1995 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/plannerQuery/StatementConvertersTest.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/ast/convert/plannerQuery/StatementConvertersTest.scala @@ -19,10 +19,9 @@ */ package org.neo4j.cypher.internal.compiler.v3_1.ast.convert.plannerQuery -import org.neo4j.cypher.internal.compiler.v3_1.executionplan.LazyReadOnlyCallMode import org.neo4j.cypher.internal.compiler.v3_1.planner.logical.plans._ import org.neo4j.cypher.internal.compiler.v3_1.planner.{LogicalPlanningTestSupport, _} -import org.neo4j.cypher.internal.compiler.v3_1.spi.{FieldSignature, ProcedureReadOnlyAccess, ProcedureSignature, QualifiedProcedureName} +import org.neo4j.cypher.internal.compiler.v3_1.spi._ import org.neo4j.cypher.internal.frontend.v3_1.SemanticDirection.{BOTH, INCOMING, OUTGOING} import org.neo4j.cypher.internal.frontend.v3_1.ast._ import org.neo4j.cypher.internal.frontend.v3_1.symbols._ @@ -879,7 +878,7 @@ class StatementConvertersTest extends CypherFunSuite with LogicalPlanningTestSup test("CALL foo() YIELD all RETURN all") { val signature = ProcedureSignature( - QualifiedProcedureName(Seq.empty, "foo"), + QualifiedName(Seq.empty, "foo"), inputSignature = IndexedSeq.empty, deprecationInfo = None, outputSignature = Some(IndexedSeq(FieldSignature("all", CTInteger))), diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureCallExecutionPlanTest.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureCallExecutionPlanTest.scala index 9281b2a4819c9..03ff3d4aca4b2 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureCallExecutionPlanTest.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/executionplan/procs/ProcedureCallExecutionPlanTest.scala @@ -82,7 +82,7 @@ class ProcedureCallExecutionPlanTest extends CypherFunSuite { def string(s: String): Expression = StringLiteral(s)(pos) private val readSignature = ProcedureSignature( - QualifiedProcedureName(IndexedSeq.empty, "foo"), + QualifiedName(IndexedSeq.empty, "foo"), IndexedSeq(FieldSignature("a", symbols.CTInteger)), Some(IndexedSeq(FieldSignature("b", symbols.CTInteger))), None, @@ -90,7 +90,7 @@ class ProcedureCallExecutionPlanTest extends CypherFunSuite { ) private val writeSignature = ProcedureSignature( - QualifiedProcedureName(Seq.empty, "foo"), + QualifiedName(Seq.empty, "foo"), IndexedSeq(FieldSignature("a", symbols.CTInteger)), Some(IndexedSeq(FieldSignature("b", symbols.CTInteger))), None, @@ -116,6 +116,6 @@ class ProcedureCallExecutionPlanTest extends CypherFunSuite { } when(ctx.transactionalContext).thenReturn(mock[QueryTransactionalContext]) - when(ctx.callReadOnlyProcedure(any[QualifiedProcedureName], any[Seq[Any]], any[Array[String]])).thenAnswer(procedureResult) - when(ctx.callReadWriteProcedure(any[QualifiedProcedureName], any[Seq[Any]], any[Array[String]])).thenAnswer(procedureResult) + when(ctx.callReadOnlyProcedure(any[QualifiedName], any[Seq[Any]], any[Array[String]])).thenAnswer(procedureResult) + when(ctx.callReadWriteProcedure(any[QualifiedName], any[Seq[Any]], any[Array[String]])).thenAnswer(procedureResult) } diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipeTest.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipeTest.scala index 8384c360e0f5b..216402f2cd8ed 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipeTest.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/pipes/ProcedureCallPipeTest.scala @@ -32,7 +32,7 @@ class ProcedureCallPipeTest with PipeTestSupport with AstConstructionTestSupport { - val procedureName = QualifiedProcedureName(List.empty, "foo") + val procedureName = QualifiedName(List.empty, "foo") val emptyStringArray = Array.empty[String] test("should execute read-only procedure calls") { @@ -110,21 +110,21 @@ class ProcedureCallPipeTest }.toIterator - class FakeQueryContext(procedureName: QualifiedProcedureName, result: Seq[Any] => Iterator[Array[AnyRef]], + class FakeQueryContext(procedureName: QualifiedName, result: Seq[Any] => Iterator[Array[AnyRef]], expectedAccessMode: ProcedureAccessMode) extends QueryContext with QueryContextAdaptation { override def isGraphKernelResultValue(v: Any): Boolean = false - override def callReadOnlyProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = { + override def callReadOnlyProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { expectedAccessMode should equal(ProcedureReadOnlyAccess(emptyStringArray)) doIt(name, args, allowed) } - override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = { + override def callReadWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = { expectedAccessMode should equal(ProcedureReadWriteAccess(emptyStringArray)) doIt(name, args, allowed) } - private def doIt(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = { + private def doIt(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = { name should equal(procedureName) args.length should be(1) result(args) diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport.scala index fc2ce870415c6..816cc33c9275a 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport.scala @@ -187,15 +187,15 @@ trait LogicalPlanningTestSupport extends CypherTestSupport with AstConstructionT nonIndexedLabelWarningThreshold = 10000 ) - def buildPlannerQuery(query: String, lookup: Option[QualifiedProcedureName => ProcedureSignature] = None) = { + def buildPlannerQuery(query: String, lookup: Option[QualifiedName => ProcedureSignature] = None) = { val queries: Seq[PlannerQuery] = buildPlannerUnionQuery(query, lookup).queries queries.head } - def buildPlannerUnionQuery(query: String, procLookup: Option[QualifiedProcedureName => ProcedureSignature] = None, - fcnLookup: Option[QualifiedProcedureName => Option[UserDefinedFunctionSignature]] = None) = { + def buildPlannerUnionQuery(query: String, procLookup: Option[QualifiedName => ProcedureSignature] = None, + fcnLookup: Option[QualifiedName => Option[UserDefinedFunctionSignature]] = None) = { val signature = ProcedureSignature( - QualifiedProcedureName(Seq.empty, "foo"), + QualifiedName(Seq.empty, "foo"), inputSignature = IndexedSeq.empty, deprecationInfo = None, outputSignature = Some(IndexedSeq(FieldSignature("all", CTInteger))), diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport2.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport2.scala index 1be85bb4f20ad..86356d427e45e 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport2.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/planner/LogicalPlanningTestSupport2.scala @@ -154,9 +154,9 @@ trait LogicalPlanningTestSupport2 extends CypherTestSupport with AstConstruction override def hasPropertyExistenceConstraint(labelName: String, propertyKey: String): Boolean = ??? - override def procedureSignature(name: QualifiedProcedureName): ProcedureSignature = ??? + override def procedureSignature(name: QualifiedName): ProcedureSignature = ??? - override def functionSignature(name: QualifiedProcedureName): Option[UserDefinedFunctionSignature] = ??? + override def functionSignature(name: QualifiedName): Option[UserDefinedFunctionSignature] = ??? } def planFor(queryString: String): SemanticPlan = { diff --git a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContextAdaptation.scala b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContextAdaptation.scala index 8399de9b47ad1..b9c588943603c 100644 --- a/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContextAdaptation.scala +++ b/community/cypher/cypher-compiler-3.1/src/test/scala/org/neo4j/cypher/internal/compiler/v3_1/spi/QueryContextAdaptation.scala @@ -122,13 +122,22 @@ trait QueryContextAdaptation { override def lockingUniqueIndexSeek(index: IndexDescriptor, value: Any): Option[Node] = ??? - override def callReadOnlyProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): scala.Iterator[Array[AnyRef]] = ??? + override def callReadOnlyProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): scala.Iterator[Array[AnyRef]] = ??? - override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): scala.Iterator[Array[AnyRef]] = ??? + override def callReadWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): scala.Iterator[Array[AnyRef]] = ??? - override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = ??? + override def callSchemaWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = ??? - override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = ??? + override def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = ??? + + override def callReadOnlyFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef = ??? + + override def callDbmsFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef = ??? + + override def callSchemaWriteFunction(name: QualifiedName, args: Seq[Any], + allowed: Array[String]): AnyRef = ??? + + override def callReadWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]): AnyRef = ??? override def getOrCreateFromSchemaState[K, V](key: K, creator: => V): V = ??? diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/ExceptionTranslatingQueryContextFor3_1.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/ExceptionTranslatingQueryContextFor3_1.scala index 3c5cffc13feb2..9bfd17933ad28 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/ExceptionTranslatingQueryContextFor3_1.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/compatibility/ExceptionTranslatingQueryContextFor3_1.scala @@ -129,18 +129,30 @@ class ExceptionTranslatingQueryContextFor3_1(val inner: QueryContext) extends Qu override def dropRelationshipPropertyExistenceConstraint(relTypeId: Int, propertyKeyId: Int) = translateException(inner.dropRelationshipPropertyExistenceConstraint(relTypeId, propertyKeyId)) - override def callReadOnlyProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = + override def callReadOnlyProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = translateIterator(inner.callReadOnlyProcedure(name, args, allowed)) - override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = + override def callReadWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = translateIterator(inner.callReadWriteProcedure(name, args, allowed)) - override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = + override def callSchemaWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = translateIterator(inner.callSchemaWriteProcedure(name, args, allowed)) - override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = + override def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]): Iterator[Array[AnyRef]] = translateIterator(inner.callDbmsProcedure(name, args, allowed)) + override def callReadOnlyFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + translateException(inner.callReadOnlyFunction(name, args, allowed)) + + override def callReadWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + translateException(inner.callReadWriteFunction(name, args, allowed)) + + override def callSchemaWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + translateException(inner.callSchemaWriteFunction(name, args, allowed)) + + override def callDbmsFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = + translateException(inner.callDbmsFunction(name, args, allowed)) + override def isGraphKernelResultValue(v: Any): Boolean = translateException(inner.isGraphKernelResultValue(v)) diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/ExceptionTranslatingPlanContext.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/ExceptionTranslatingPlanContext.scala index 9513768e2b1cd..effaa611a555f 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/ExceptionTranslatingPlanContext.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/ExceptionTranslatingPlanContext.scala @@ -48,10 +48,10 @@ class ExceptionTranslatingPlanContext(inner: PlanContext) extends PlanContext wi () => translateException(innerTxProvider()) } - override def procedureSignature(name: QualifiedProcedureName): ProcedureSignature = + override def procedureSignature(name: QualifiedName): ProcedureSignature = translateException(inner.procedureSignature(name)) - override def functionSignature(name: QualifiedProcedureName): Option[UserDefinedFunctionSignature] = + override def functionSignature(name: QualifiedName): Option[UserDefinedFunctionSignature] = translateException(inner.functionSignature(name)) override def hasIndexRule(labelName: String): Boolean = diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundPlanContext.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundPlanContext.scala index fdb9fa7b4c007..413a566413987 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundPlanContext.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundPlanContext.scala @@ -36,7 +36,7 @@ import org.neo4j.kernel.api.exceptions.schema.SchemaKernelException import org.neo4j.kernel.api.index.{IndexDescriptor, InternalIndexState} import org.neo4j.kernel.api.proc import org.neo4j.kernel.api.proc.Neo4jTypes.AnyType -import org.neo4j.kernel.api.proc.{ProcedureSignature => KernelProcedureSignature, Mode, QualifiedName, Neo4jTypes} +import org.neo4j.kernel.api.proc.{Neo4jTypes, ProcedureSignature => KernelProcedureSignature, QualifiedName => KernelQualifiedName} import org.neo4j.kernel.impl.proc.Neo4jValue import scala.collection.JavaConverters._ @@ -131,8 +131,8 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapperv3_1) val txIdProvider = LastCommittedTxIdProvider(tc.graph) - override def procedureSignature(name: QualifiedProcedureName) = { - val kn = new QualifiedName(name.namespace.asJava, name.name) + override def procedureSignature(name: QualifiedName) = { + val kn = new KernelQualifiedName(name.namespace.asJava, name.name) val ks = tc.statement.readOperations().procedureGet(kn) val input = ks.inputSignature().asScala.map(s => FieldSignature(s.name(), asCypherType(s.neo4jType()), asOption(s.defaultValue()).map(asCypherValue))).toIndexedSeq val output = if (ks.isVoid) None else Some(ks.outputSignature().asScala.map(s => FieldSignature(s.name(), asCypherType(s.neo4jType()))).toIndexedSeq) @@ -142,8 +142,22 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapperv3_1) ProcedureSignature(name, input, output, deprecationInfo, mode) } - //TODE wire down to kernel - override def functionSignature(name: QualifiedProcedureName): Option[UserDefinedFunctionSignature] = None + override def functionSignature(name: QualifiedName): Option[UserDefinedFunctionSignature] = { + val kn = new KernelQualifiedName(name.namespace.asJava, name.name) + val maybeFunction = tc.statement.readOperations().functionGet(kn) + if (maybeFunction.isPresent) { + val ks = maybeFunction.get + val input = ks.inputSignature().asScala + .map(s => FieldSignature(s.name(), asCypherType(s.neo4jType()), asOption(s.defaultValue()).map(asCypherValue))) + .toIndexedSeq + val output = FieldSignature(ks.outputSignature().name(), asCypherType(ks.outputSignature().neo4jType())) + val deprecationInfo = asOption(ks.deprecated()) + val mode = asCypherProcMode(ks.mode(), ks.allowed()) + + Some(UserDefinedFunctionSignature(name, input, output, deprecationInfo, mode)) + } + else None + } private def asOption[T](optional: Optional[T]): Option[T] = if (optional.isPresent) Some(optional.get()) else None diff --git a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundQueryContext.scala b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundQueryContext.scala index 77ec37e6b9e7c..4786f58594ba0 100644 --- a/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundQueryContext.scala +++ b/community/cypher/cypher/src/main/scala/org/neo4j/cypher/internal/spi/v3_1/TransactionBoundQueryContext.scala @@ -51,7 +51,7 @@ import org.neo4j.kernel.api.constraints.{NodePropertyExistenceConstraint, Relati import org.neo4j.kernel.api.exceptions.ProcedureException import org.neo4j.kernel.api.exceptions.schema.{AlreadyConstrainedException, AlreadyIndexedException} import org.neo4j.kernel.api.index.{IndexDescriptor, InternalIndexState} -import org.neo4j.kernel.api.proc.QualifiedName +import org.neo4j.kernel.api.proc.{QualifiedName => KernelQualifiedName} import org.neo4j.kernel.api.security.{AccessMode, AuthSubject} import org.neo4j.kernel.impl.core.NodeManager import org.neo4j.kernel.impl.locking.ResourceTypes @@ -585,7 +585,7 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional pathFinder.findAllPaths(left, right).iterator().asScala } - override def callReadOnlyProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = { + override def callReadOnlyProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { val revertable = transactionalContext.accessMode match { case a: AuthSubject if a.allowsProcedureWith(allowed) => Some(transactionalContext.restrictCurrentTransaction(AccessMode.Static.OVERRIDE_READ)) @@ -594,7 +594,7 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional callProcedure(name, args, transactionalContext.statement.readOperations().procedureCallRead, revertable.foreach(_.close)) } - override def callReadWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = { + override def callReadWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { val revertable = transactionalContext.accessMode match { case a: AuthSubject if a.allowsProcedureWith(allowed) => Some(transactionalContext.restrictCurrentTransaction(AccessMode.Static.OVERRIDE_WRITE)) @@ -604,8 +604,7 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional revertable.foreach(_.close)) } - - override def callSchemaWriteProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = { + override def callSchemaWriteProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { val revertable = transactionalContext.accessMode match { case a: AuthSubject if a.allowsProcedureWith(allowed) => Some(transactionalContext.restrictCurrentTransaction(AccessMode.Static.OVERRIDE_SCHEMA)) @@ -614,14 +613,14 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional callProcedure(name, args, transactionalContext.statement.schemaWriteOperations().procedureCallSchema, revertable.foreach(_.close)) } - override def callDbmsProcedure(name: QualifiedProcedureName, args: Seq[Any], allowed: Array[String]) = { - callProcedure(name, args, transactionalContext.dbmsOperations.procedureCallDbms(_, _), ()) + override def callDbmsProcedure(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { + callProcedure(name, args, transactionalContext.dbmsOperations.procedureCallDbms, ()) } - private def callProcedure(name: QualifiedProcedureName, args: Seq[Any], - call: (QualifiedName, Array[AnyRef]) => RawIterator[Array[AnyRef], ProcedureException], + private def callProcedure(name: QualifiedName, args: Seq[Any], + call: (KernelQualifiedName, Array[AnyRef]) => RawIterator[Array[AnyRef], ProcedureException], onClose: => Unit) = { - val kn = new QualifiedName(name.namespace.asJava, name.name) + val kn = new KernelQualifiedName(name.namespace.asJava, name.name) val toArray = args.map(_.asInstanceOf[AnyRef]).toArray val read: RawIterator[Array[AnyRef], ProcedureException] = call(kn, toArray) new scala.Iterator[Array[AnyRef]] { @@ -637,6 +636,46 @@ final class TransactionBoundQueryContext(val transactionalContext: Transactional } } + override def callReadOnlyFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { + val revertable = transactionalContext.accessMode match { + case a: AuthSubject if a.allowsProcedureWith(allowed) => + Some(transactionalContext.restrictCurrentTransaction(AccessMode.Static.OVERRIDE_READ)) + case _ => None + } + callFunction(name, args, transactionalContext.statement.readOperations().functionCallRead, revertable.foreach(_.close)) + } + + override def callReadWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { + val revertable = transactionalContext.accessMode match { + case a: AuthSubject if a.allowsProcedureWith(allowed) => + Some(transactionalContext.restrictCurrentTransaction(AccessMode.Static.OVERRIDE_WRITE)) + case _ => None + } + callFunction(name, args, transactionalContext.statement.dataWriteOperations().functionCallWrite, + revertable.foreach(_.close)) + } + + override def callSchemaWriteFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { + val revertable = transactionalContext.accessMode match { + case a: AuthSubject if a.allowsProcedureWith(allowed) => + Some(transactionalContext.restrictCurrentTransaction(AccessMode.Static.OVERRIDE_SCHEMA)) + case _ => None + } + callFunction(name, args, transactionalContext.statement.schemaWriteOperations().functionCallSchema, revertable.foreach(_.close)) + } + + override def callDbmsFunction(name: QualifiedName, args: Seq[Any], allowed: Array[String]) = { + callFunction(name, args, transactionalContext.dbmsOperations.functionCallDbms, ()) + } + + private def callFunction(name: QualifiedName, args: Seq[Any], + call: (KernelQualifiedName, Array[AnyRef]) => AnyRef, + onClose: => Unit) = { + val kn = new KernelQualifiedName(name.namespace.asJava, name.name) + val toArray = args.map(_.asInstanceOf[AnyRef]).toArray + call(kn, toArray) + } + override def isGraphKernelResultValue(v: Any): Boolean = internal.isGraphKernelResultValue(v) private def buildPathFinder(depth: Int, expander: expressions.Expander, pathPredicate: KernelPredicate[Path], diff --git a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ProfilerAcceptanceTest.scala b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ProfilerAcceptanceTest.scala index e4b18a8f1daf9..defaae6af493e 100644 --- a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ProfilerAcceptanceTest.scala +++ b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/ProfilerAcceptanceTest.scala @@ -23,7 +23,7 @@ import org.neo4j.cypher.internal.compiler.v3_1 import org.neo4j.cypher.internal.compiler.v3_1.executionplan.InternalExecutionResult import org.neo4j.cypher.internal.compiler.v3_1.planDescription.InternalPlanDescription.Arguments.{DbHits, EstimatedRows, Rows, Signature} import org.neo4j.cypher.internal.compiler.v3_1.planDescription.{Argument, InternalPlanDescription} -import org.neo4j.cypher.internal.compiler.v3_1.spi.{GraphStatistics, QualifiedProcedureName} +import org.neo4j.cypher.internal.compiler.v3_1.spi.{GraphStatistics, QualifiedName} import org.neo4j.cypher.internal.compiler.v3_1.test_helpers.CreateTempFileTestSupport import org.neo4j.cypher.internal.frontend.v3_1.helpers.StringHelper.RichString import org.neo4j.cypher.internal.frontend.v3_1.symbols._ @@ -67,7 +67,7 @@ class ProfilerAcceptanceTest extends ExecutionEngineFunSuite with CreateTempFile assertDbHits(1)(result)("ProcedureCall") assertRows(2)(result)("ProcedureCall") getPlanDescriptions(result, Seq("ProcedureCall")).foreach { plan => - val Signature(QualifiedProcedureName(namespaces, procName), _, returnSignature) = plan.arguments.collectFirst { + val Signature(QualifiedName(namespaces, procName), _, returnSignature) = plan.arguments.collectFirst { case x: Signature => x }.getOrElse(fail("expected a procedure signature")) @@ -87,7 +87,7 @@ class ProfilerAcceptanceTest extends ExecutionEngineFunSuite with CreateTempFile assertDbHits(1)(result)("ProcedureCall") assertRows(2)(result)("ProcedureCall") getPlanDescriptions(result, Seq("ProcedureCall")).foreach { plan => - val Signature(QualifiedProcedureName(namespaces, procName), _, returnSignature) = plan.arguments.collectFirst { + val Signature(QualifiedName(namespaces, procName), _, returnSignature) = plan.arguments.collectFirst { case x: Signature => x }.getOrElse(fail("expected a procedure signature")) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/DataWriteOperations.java b/community/kernel/src/main/java/org/neo4j/kernel/api/DataWriteOperations.java index 26ce12347bd2d..3b540d3bed94d 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/DataWriteOperations.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/DataWriteOperations.java @@ -151,4 +151,6 @@ void relationshipRemoveFromLegacyIndex( String indexName, long relationship ) /** Invoke a read/write procedure by name */ RawIterator procedureCallWrite( QualifiedName name, Object[] input ) throws ProcedureException; + + Object functionCallWrite( QualifiedName name, Object[] input ) throws ProcedureException; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/ReadOperations.java b/community/kernel/src/main/java/org/neo4j/kernel/api/ReadOperations.java index 3b6668eafe513..8e4fb2eb0fe70 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/ReadOperations.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/ReadOperations.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -44,8 +45,9 @@ import org.neo4j.kernel.api.exceptions.schema.SchemaRuleNotFoundException; import org.neo4j.kernel.api.index.IndexDescriptor; import org.neo4j.kernel.api.index.InternalIndexState; -import org.neo4j.kernel.api.proc.QualifiedName; +import org.neo4j.kernel.api.proc.FunctionSignature; import org.neo4j.kernel.api.proc.ProcedureSignature; +import org.neo4j.kernel.api.proc.QualifiedName; import org.neo4j.kernel.impl.api.RelationshipVisitor; import org.neo4j.kernel.impl.api.store.RelationshipIterator; import org.neo4j.register.Register.DoubleLongRegister; @@ -555,9 +557,15 @@ DoubleLongRegister indexSample( IndexDescriptor index, DoubleLongRegister target /** Fetch a procedure given its signature. */ ProcedureSignature procedureGet( QualifiedName name ) throws ProcedureException; + /** Fetch a function given its signature, or empty if no such function exists*/ + Optional functionGet( QualifiedName name ); + /** Fetch all registered procedures */ Set proceduresGetAll(); /** Invoke a read-only procedure by name */ RawIterator procedureCallRead( QualifiedName name, Object[] input ) throws ProcedureException; + + /** Invoke a read-only procedure by name */ + Object functionCallRead( QualifiedName name, Object[] input ) throws ProcedureException; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/SchemaWriteOperations.java b/community/kernel/src/main/java/org/neo4j/kernel/api/SchemaWriteOperations.java index d8de9284c8225..a522c03cc91cd 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/SchemaWriteOperations.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/SchemaWriteOperations.java @@ -67,4 +67,6 @@ RelationshipPropertyExistenceConstraint relationshipPropertyExistenceConstraintC /** Invoke a schema procedure by name */ RawIterator procedureCallSchema( QualifiedName name, Object[] input ) throws ProcedureException; + + Object functionCallSchema( QualifiedName name, Object[] input ) throws ProcedureException; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/dbms/DbmsOperations.java b/community/kernel/src/main/java/org/neo4j/kernel/api/dbms/DbmsOperations.java index 15d2fab9362a6..438d9e4289bea 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/dbms/DbmsOperations.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/dbms/DbmsOperations.java @@ -38,6 +38,8 @@ public interface DbmsOperations /** Invoke a DBMS procedure by name */ RawIterator procedureCallDbms( QualifiedName name, Object[] input ) throws ProcedureException; + Object functionCallDbms( QualifiedName name, Object[] input ) + throws ProcedureException; interface Factory { diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/proc/FunctionSignature.java b/community/kernel/src/main/java/org/neo4j/kernel/api/proc/FunctionSignature.java index 87d98514249cc..0ac20eb640673 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/proc/FunctionSignature.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/proc/FunctionSignature.java @@ -30,47 +30,35 @@ import static java.util.Collections.unmodifiableList; /** - * This describes the signature of a procedure, made up of its namespace, name, and input/output description. - * Procedure uniqueness is currently *only* on the namespace/name level - no procedure overloading allowed (yet). + * This describes the signature of a function, made up of its namespace, name, and input/output description. + * Function uniqueness is currently *only* on the namespace/name level - no function overloading allowed (yet). */ public class FunctionSignature { private final QualifiedName name; private final List inputSignature; - private final List outputSignature; + private final FieldSignature outputSignature; private final Mode mode; + private final String[] allowed; private final Optional deprecated; private final Optional description; - /** - * The procedure mode affects how the procedure will execute, and which capabilities - * it requires. - */ - public enum Mode - { - /** This procedure will only perform read operations against the graph */ - READ_ONLY, - /** This procedure may perform both read and write operations against the graph */ - READ_WRITE, - /** This procedure will perform operations against the schema */ - SCHEMA_WRITE, - /** This procedure will perform system operations - i.e. not against the graph */ - DBMS - } public FunctionSignature( QualifiedName name, List inputSignature, - List outputSignature, + FieldSignature outputSignature, Mode mode, Optional deprecated, + String[] allowed, Optional description ) { this.name = name; this.inputSignature = unmodifiableList( inputSignature ); - this.outputSignature = unmodifiableList( outputSignature ); + this.outputSignature = outputSignature; this.mode = mode; this.deprecated = deprecated; this.description = description; + this.allowed = allowed; } public QualifiedName name() @@ -93,7 +81,7 @@ public List inputSignature() return inputSignature; } - public List outputSignature() + public FieldSignature outputSignature() { return outputSignature; } @@ -103,6 +91,8 @@ public Optional description() return description; } + public String[] allowed() { return allowed; } + @Override public boolean equals( Object o ) { @@ -127,7 +117,7 @@ public int hashCode() public String toString() { String strInSig = inputSignature == null ? "..." : Iterables.toString( inputSignature, ", " ); - String strOutSig = outputSignature == null ? "..." : Iterables.toString( outputSignature, ", " ); + String strOutSig = outputSignature == null ? "..." : outputSignature.toString(); return String.format( "%s(%s) :: (%s)", name, strInSig, strOutSig ); } @@ -135,8 +125,9 @@ public static class Builder { private final QualifiedName name; private final List inputSignature = new LinkedList<>(); - private List outputSignature = new LinkedList<>(); + private FieldSignature outputSignature; private Mode mode = Mode.READ_ONLY; + private String[] allowed = new String[0]; private Optional deprecated = Optional.empty(); private Optional description = Optional.empty(); @@ -173,19 +164,19 @@ public Builder in( String name, AnyType type ) /** Define an output field */ public Builder out( String name, AnyType type ) { - outputSignature.add( new FieldSignature( name, type ) ); + outputSignature = new FieldSignature( name, type ); return this; } - public Builder out( List fields ) + public Builder allowed( String[] allowed ) { - outputSignature = fields; + this.allowed = allowed; return this; } public FunctionSignature build() { - return new FunctionSignature(name, inputSignature, outputSignature, mode, deprecated, description ); + return new FunctionSignature(name, inputSignature, outputSignature, mode, deprecated, allowed, description ); } } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java index 65aeff104cf12..20f2063c7260c 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/OperationsFacade.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; @@ -72,6 +73,7 @@ import org.neo4j.kernel.api.index.InternalIndexState; import org.neo4j.kernel.api.proc.BasicContext; import org.neo4j.kernel.api.proc.Context; +import org.neo4j.kernel.api.proc.FunctionSignature; import org.neo4j.kernel.api.proc.ProcedureSignature; import org.neo4j.kernel.api.proc.QualifiedName; import org.neo4j.kernel.api.properties.DefinedProperty; @@ -548,6 +550,19 @@ public ProcedureSignature procedureGet( QualifiedName name ) throws ProcedureExc return procedures.procedure( name ); } + @Override + public Optional functionGet( QualifiedName name ) + { + statement.assertOpen(); + return procedures.function( name ); + } + + @Override + public Object functionCallRead( QualifiedName name, Object[] input ) throws ProcedureException + { + return callFunction( name, input, AccessMode.Static.READ ); + } + @Override public RawIterator procedureCallRead( QualifiedName name, Object[] input ) throws ProcedureException { @@ -1073,6 +1088,13 @@ public RawIterator procedureCallWrite( QualifiedNa // FIXME: should this be AccessMode.Static.WRITE instead? return callProcedure( name, input, AccessMode.Static.FULL ); } + + @Override + public Object functionCallWrite( QualifiedName name, Object[] input ) throws ProcedureException + { + // FIXME: should this be AccessMode.Static.WRITE instead? + return callFunction( name, input, AccessMode.Static.FULL ); + } // // @@ -1143,6 +1165,12 @@ public RawIterator procedureCallSchema( QualifiedN return callProcedure( name, input, AccessMode.Static.FULL ); } + @Override + public Object functionCallSchema( QualifiedName name, Object[] input ) throws ProcedureException + { + return callFunction( name, input, AccessMode.Static.FULL ); + } + // // @@ -1492,5 +1520,21 @@ private RawIterator callProcedure( } } + private Object callFunction( + QualifiedName name, Object[] input, AccessMode mode ) + throws ProcedureException + { + statement.assertOpen(); + + try ( KernelTransaction.Revertable revertable = tx.overrideWith( mode ) ) + { + BasicContext ctx = new BasicContext(); + ctx.put( Context.KERNEL_TRANSACTION, tx ); + ctx.put( Context.THREAD, Thread.currentThread() ); + return procedures.callFunction( ctx, name, input ); + } + } + + // } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/dbms/NonTransactionalDbmsOperations.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/dbms/NonTransactionalDbmsOperations.java index 47d7f64ab0ce4..8d6deb09f8b0a 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/dbms/NonTransactionalDbmsOperations.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/dbms/NonTransactionalDbmsOperations.java @@ -55,6 +55,20 @@ public RawIterator procedureCallDbms( QualifiedName return procedures.callProcedure( ctx, name, input ); } + @Override + public Object functionCallDbms( QualifiedName name, + Object[] input ) throws ProcedureException + { + BasicContext ctx = new BasicContext(); + ctx.put( Context.KERNEL_TRANSACTION, transaction ); + if ( transaction.mode() instanceof AuthSubject ) + { + AuthSubject subject = (AuthSubject) transaction.mode(); + ctx.put( Context.AUTH_SUBJECT, subject ); + } + return procedures.callFunction( ctx, name, input ); + } + public static class Factory implements DbmsOperations.Factory { private final Procedures procedures; diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureRegistry.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureRegistry.java index 7f9b48bba0dda..dd15bf6c7fc46 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureRegistry.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/ProcedureRegistry.java @@ -19,10 +19,12 @@ */ package org.neo4j.kernel.impl.proc; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -33,9 +35,9 @@ import org.neo4j.kernel.api.proc.CallableProcedure; import org.neo4j.kernel.api.proc.Context; import org.neo4j.kernel.api.proc.FieldSignature; -import org.neo4j.kernel.api.proc.QualifiedName; -import org.neo4j.kernel.api.proc.ProcedureSignature; import org.neo4j.kernel.api.proc.FunctionSignature; +import org.neo4j.kernel.api.proc.ProcedureSignature; +import org.neo4j.kernel.api.proc.QualifiedName; public class ProcedureRegistry { @@ -87,7 +89,7 @@ public void register( CallableFunction function, boolean overrideCurrentImplemen String descriptiveName = signature.toString(); validateSignature( descriptiveName, signature.inputSignature(), "input" ); - validateSignature( descriptiveName, signature.outputSignature(), "output" ); + validateSignature( descriptiveName, Collections.singletonList(signature.outputSignature()), "output" ); CallableFunction oldImplementation = functions.get( name ); if ( oldImplementation == null ) @@ -133,14 +135,14 @@ public ProcedureSignature procedure( QualifiedName name ) throws ProcedureExcept return proc.signature(); } - public FunctionSignature function(QualifiedName name ) throws ProcedureException + public Optional function( QualifiedName name ) { CallableFunction func = functions.get( name ); if ( func == null ) { - throw noSuchFunction( name ); + return Optional.empty(); } - return func.signature(); + return Optional.of( func.signature() ); } public RawIterator callProcedure( Context ctx, QualifiedName name, Object[] input ) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/Procedures.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/Procedures.java index ab3cf83ed42ac..ecf53a27203a1 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/Procedures.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/proc/Procedures.java @@ -20,6 +20,7 @@ package org.neo4j.kernel.impl.proc; import java.io.File; +import java.util.Optional; import java.util.Set; import org.neo4j.collection.RawIterator; @@ -29,9 +30,9 @@ import org.neo4j.kernel.api.proc.CallableFunction; import org.neo4j.kernel.api.proc.CallableProcedure; import org.neo4j.kernel.api.proc.Context; -import org.neo4j.kernel.api.proc.QualifiedName; -import org.neo4j.kernel.api.proc.ProcedureSignature; import org.neo4j.kernel.api.proc.FunctionSignature; +import org.neo4j.kernel.api.proc.ProcedureSignature; +import org.neo4j.kernel.api.proc.QualifiedName; import org.neo4j.kernel.builtinprocs.SpecialBuiltInProcedures; import org.neo4j.kernel.lifecycle.LifecycleAdapter; import org.neo4j.logging.Log; @@ -148,7 +149,7 @@ public ProcedureSignature procedure( QualifiedName name ) throws ProcedureExcept return registry.procedure( name ); } - public FunctionSignature function( QualifiedName name ) throws ProcedureException + public Optional function( QualifiedName name ) { return registry.function( name ); } diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/FunctionSignatureTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/FunctionSignatureTest.java index 930a56dc2e5d2..44e2ecdaf02aa 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/FunctionSignatureTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/proc/FunctionSignatureTest.java @@ -47,16 +47,6 @@ public void inputSignatureShouldNotBeModifiable() throws Throwable signature.inputSignature().add( new FieldSignature( "b", Neo4jTypes.NTAny ) ); } - @Test - public void outputSignatureShouldNotBeModifiable() throws Throwable - { - // Expect - exception.expect( UnsupportedOperationException.class ); - - // When - signature.outputSignature().add( new FieldSignature( "b", Neo4jTypes.NTAny ) ); - } - @Test public void toStringShouldMatchCypherSyntax() throws Throwable {