Skip to content

Commit

Permalink
Better error message for calling procedures
Browse files Browse the repository at this point in the history
When the static types are wrong we should provide a nice error message.
  • Loading branch information
pontusmelke committed Feb 11, 2016
1 parent 7ef4d25 commit 9543296
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 8 deletions.
Expand Up @@ -20,7 +20,7 @@
package org.neo4j.cypher.internal.compiler.v3_0.executionplan.procs package org.neo4j.cypher.internal.compiler.v3_0.executionplan.procs


import org.neo4j.cypher.internal.compiler.v3_0.executionplan.{ExecutablePlanBuilder, ExecutionPlan, PlanFingerprint, PlanFingerprintReference, SCHEMA_WRITE} import org.neo4j.cypher.internal.compiler.v3_0.executionplan.{ExecutablePlanBuilder, ExecutionPlan, PlanFingerprint, PlanFingerprintReference, SCHEMA_WRITE}
import org.neo4j.cypher.internal.compiler.v3_0.spi.{FieldSignature, PlanContext, ProcedureName, QueryContext} import org.neo4j.cypher.internal.compiler.v3_0.spi.{FieldSignature, PlanContext, ProcedureName, ProcedureSignature, QueryContext}
import org.neo4j.cypher.internal.compiler.v3_0.{CompilationPhaseTracer, PreparedQuery} import org.neo4j.cypher.internal.compiler.v3_0.{CompilationPhaseTracer, PreparedQuery}
import org.neo4j.cypher.internal.frontend.v3_0.ast._ import org.neo4j.cypher.internal.frontend.v3_0.ast._
import org.neo4j.cypher.internal.frontend.v3_0.symbols.TypeSpec import org.neo4j.cypher.internal.frontend.v3_0.symbols.TypeSpec
Expand Down Expand Up @@ -51,7 +51,7 @@ case class DelegatingProcedureExecutablePlanBuilder(delegate: ExecutablePlanBuil
} }
//type check arguments //type check arguments
args.zip(signature.inputSignature).foreach { args.zip(signature.inputSignature).foreach {
case (arg, field) => typeCheck(inputQuery.semanticTable)(arg, field) case (arg, field) => typeCheck(inputQuery.semanticTable)(arg, field, signature)
} }
} }


Expand Down Expand Up @@ -115,13 +115,18 @@ case class DelegatingProcedureExecutablePlanBuilder(delegate: ExecutablePlanBuil
private def typeProp(ctx: QueryContext)(relType: RelTypeName, prop: PropertyKeyName) = private def typeProp(ctx: QueryContext)(relType: RelTypeName, prop: PropertyKeyName) =
(ctx.getOrCreateRelTypeId(relType.name), ctx.getOrCreatePropertyKeyId(prop.name)) (ctx.getOrCreateRelTypeId(relType.name), ctx.getOrCreatePropertyKeyId(prop.name))


private def typeCheck(semanticTable: SemanticTable)(exp: Expression, field: FieldSignature) = { private def typeCheck(semanticTable: SemanticTable)(exp: Expression, field: FieldSignature, proc: ProcedureSignature) = {
val actual = semanticTable.types(exp).actual val actual = semanticTable.types(exp).actual
val expected = field.typ val expected = field.typ
val intersected = actual intersectOrCoerce expected.covariant val intersected = actual intersectOrCoerce expected.covariant
if (intersected == TypeSpec.none) if (intersected == TypeSpec.none)
throw new CypherTypeException( throw new CypherTypeException(
s"""${field.name} expects $expected, but got ${actual.mkString(",", "or")}""") s"""Parameter `${field.name}` for procedure `${proc.name}`
|expects value of type ${semanticTable.types(exp).actual.toShortString} but got value of type ${field.typ}.
|
|Usage: CALL ${proc.name}(${proc.inputSignature.map(s => s"<${s.name}>").mkString(", ")})
|${proc.inputSignature.map(s => s" ${s.name} (type ${s.typ})").mkString("Parameters:\n", "\n","")}
""".stripMargin)
} }
} }


Expand Up @@ -58,6 +58,8 @@ case class ProcedureSignature(name: ProcedureName,
outputSignature: Seq[FieldSignature], outputSignature: Seq[FieldSignature],
mode: ProcedureCallMode = LazyReadOnlyCallMode) mode: ProcedureCallMode = LazyReadOnlyCallMode)


case class ProcedureName(namespace: Seq[String], name: String) case class ProcedureName(namespace: Seq[String], name: String) {
override def toString = s"""${namespace.mkString(".")}.$name"""
}


case class FieldSignature(name: String, typ: CypherType) case class FieldSignature(name: String, typ: CypherType)
Expand Up @@ -90,7 +90,7 @@ public void shouldCallRegisteredProcedure() throws Throwable
} }


@Test @Test
public void shouldNotAllowCallingNonExistantProcedure() throws Throwable public void shouldNotAllowCallingNonExistingProcedure() throws Throwable
{ {
// Expect // Expect
exception.expect( ProcedureException.class ); exception.expect( ProcedureException.class );
Expand All @@ -105,7 +105,7 @@ public void shouldNotAllowCallingNonExistantProcedure() throws Throwable
} }


@Test @Test
public void shouldNotAllowRegisteringConflicingName() throws Throwable public void shouldNotAllowRegisteringConflictingName() throws Throwable
{ {
// Given // Given
procs.register( procedure ); procs.register( procedure );
Expand Down Expand Up @@ -145,7 +145,7 @@ public void shouldNotAllowDuplicateFieldNamesInOutput() throws Throwable
} }


@Test @Test
public void shouldSignalNonExistantProcedure() throws Throwable public void shouldSignalNonExistingProcedure() throws Throwable
{ {
// Expect // Expect
exception.expect( ProcedureException.class ); exception.expect( ProcedureException.class );
Expand Down
Expand Up @@ -80,6 +80,24 @@ public void shouldCallProcedureWithParameterMap() throws Throwable
} }
} }


@Test
public void shouldGiveNiceErrorMessageOnWrongStaticType() throws Throwable
{
//Expect
exception.expect( QueryExecutionException.class );
exception.expectMessage(
"Parameter `name` for procedure `org.neo4j.procedure.simpleArgument`\n" +
"expects value of type String but got value of type Integer.\n\n" +
"Usage: CALL org.neo4j.procedure.simpleArgument(<name>)\n" +
"Parameters:\n" +
" name (type Integer)" );
// When
try ( Transaction ignore = db.beginTx() )
{
Result res = db.execute( "CALL org.neo4j.procedure.simpleArgument('42')");
}
}

@Test @Test
public void shouldCallProcedureWithGenericArgument() throws Throwable public void shouldCallProcedureWithGenericArgument() throws Throwable
{ {
Expand Down

0 comments on commit 9543296

Please sign in to comment.