Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Integrate new TCK and support procedures and some exceptions
- Loading branch information
1 parent
61bbc68
commit e50a6c7
Showing
8 changed files
with
615 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
enterprise/cypher/acceptance-spec-suite/src/test/scala/cypher/features/Neo4jAdapter.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* Copyright (c) 2002-2017 "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 Affero 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 Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package cypher.features | ||
|
||
import cypher.features.Neo4jExceptionToExecutionFailed._ | ||
import org.neo4j.cypher.internal.javacompat.GraphDatabaseCypherService | ||
import org.neo4j.graphdb.{Result => Neo4jResult} | ||
import org.neo4j.kernel.impl.factory.GraphDatabaseFacade | ||
import org.neo4j.test.TestGraphDatabaseFactory | ||
import org.opencypher.tools.tck.api._ | ||
import org.opencypher.tools.tck.values.CypherValue | ||
|
||
import scala.collection.JavaConverters._ | ||
import scala.util.{Failure, Success, Try} | ||
|
||
object Neo4jAdapter { | ||
def apply(): Neo4jAdapter = { | ||
val service: GraphDatabaseCypherService = new GraphDatabaseCypherService(new TestGraphDatabaseFactory().newImpermanentDatabase()) | ||
new Neo4jAdapter(service) | ||
} | ||
} | ||
|
||
class Neo4jAdapter(service: GraphDatabaseCypherService) extends Graph with Neo4jProcedureAdapter { | ||
protected val instance: GraphDatabaseFacade = service.getGraphDatabaseService | ||
|
||
override def cypher(query: String, params: Map[String, CypherValue], meta: QueryType): Result = { | ||
val neo4jParams = params.mapValues(v => TCKValueToNeo4jValue(v)).asJava | ||
|
||
val tx = instance.beginTx() | ||
val result = { | ||
val neo4jResult = Try(instance.execute(query, neo4jParams)) match { | ||
case Success(r) => r | ||
case Failure(exception) => | ||
tx.failure() | ||
tx.close() // TODO: better solution? | ||
return convert(exception) | ||
} | ||
val convertedResult: Result = Try(convertResult(neo4jResult)) match { | ||
case Success(r) => | ||
tx.success() | ||
r | ||
case Failure(exception) => | ||
tx.failure() | ||
tx.close() // TODO: better solution? | ||
return convert(exception) | ||
} | ||
tx.close() | ||
convertedResult | ||
} | ||
result | ||
} | ||
|
||
def convertResult(result: Neo4jResult): Result = { | ||
val header = result.columns().asScala.toList | ||
val rows: List[Map[String, String]] = result.asScala.map { row => | ||
row.asScala.map { case (k, v) => (k, Neo4jValueToString(v)) }.toMap | ||
}.toList | ||
StringRecords(header, rows) | ||
} | ||
|
||
override def close(): Unit = { | ||
instance.shutdown() | ||
} | ||
|
||
} | ||
|
||
case class Neo4jExecutionException( | ||
query: String, | ||
params: Map[String, CypherValue], | ||
meta: QueryType, msg: String) | ||
extends Exception(s"Error when executing $meta query $query with params $params: $msg") | ||
|
||
case class Neo4jValueConversionException( | ||
query: String, | ||
params: Map[String, CypherValue], | ||
meta: QueryType, msg: String | ||
) | ||
extends Exception(s"Error when converting result values for $meta query $query with params $params: $msg") |
207 changes: 207 additions & 0 deletions
207
...cceptance-spec-suite/src/test/scala/cypher/features/Neo4jExceptionToExecutionFailed.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
/* | ||
* Copyright (c) 2002-2017 "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 Affero 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 Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package cypher.features | ||
|
||
import org.opencypher.tools.tck.api.ExecutionFailed | ||
import org.opencypher.tools.tck.constants.TCKErrorDetails._ | ||
|
||
object Neo4jExceptionToExecutionFailed { | ||
|
||
// Error types | ||
val typeError = "TypeError" | ||
val syntaxError = "SyntaxError" | ||
val unsupportedError = "UnsupportedError" | ||
|
||
// Phases | ||
val runtime = "runtime" | ||
val compile = "compile time" | ||
val unsupportedPhase = "unsupportedPhase" | ||
|
||
def convert(neo4jException: Throwable): ExecutionFailed = { | ||
//(errorType: String, phase: String, detail: String) | ||
val msg = neo4jException.getMessage.toLowerCase | ||
val errorType = if (msg.contains("type")) { | ||
typeError | ||
} else if (msg.contains("invalid")) { | ||
syntaxError | ||
} else { | ||
unsupportedError | ||
} | ||
val exceptionName = neo4jException.getClass.getSimpleName | ||
val phase = if (exceptionName.contains("Execution")) { | ||
runtime | ||
} else { | ||
compile | ||
} | ||
|
||
val detail = { | ||
if (msg.matches("Type mismatch: expected a map but was .+")) | ||
PROPERTY_ACCESS_ON_NON_MAP | ||
else if (msg.matches("Expected .+ to be a ((java.lang.String)|(org.neo4j.values.storable.TextValue)), but it was a .+")) | ||
MAP_ELEMENT_ACCESS_BY_NON_STRING | ||
else if (msg.matches("Expected .+ to be a ((java.lang.Number)|(org.neo4j.values.storable.NumberValue)), but it was a .+")) | ||
LIST_ELEMENT_ACCESS_BY_NON_INTEGER | ||
else if (msg.matches(".+ is not a collection or a map. Element access is only possible by performing a collection lookup using an integer index, or by performing a map lookup using a string key .+")) | ||
INVALID_ELEMENT_ACCESS | ||
else if (msg.matches(s"\nElement access is only possible by performing a collection lookup using an integer index,\nor by performing a map lookup using a string key .+")) | ||
INVALID_ELEMENT_ACCESS | ||
else if (msg.matches(".+ can not create a new node due to conflicts with( both)? existing( and missing)? unique nodes.*")) | ||
"CreateBlockedByConstraint" | ||
else if (msg.matches("Node\\(\\d+\\) already exists with label `.+` and property `.+` = .+")) | ||
"CreateBlockedByConstraint" | ||
else if (msg.matches("Cannot delete node\\<\\d+\\>, because it still has relationships. To delete this node, you must first delete its relationships.")) | ||
DELETE_CONNECTED_NODE | ||
else if (msg.matches("Don't know how to compare that\\..+")) | ||
"IncomparableValues" | ||
else if (msg.matches("Cannot perform .+ on mixed types\\..+")) | ||
"IncomparableValues" | ||
else if (msg.matches("Invalid input '.+' is not a valid argument, must be a number in the range 0.0 to 1.0")) | ||
NUMBER_OUT_OF_RANGE | ||
else if (msg.matches("step argument to range\\(\\) cannot be zero")) | ||
NUMBER_OUT_OF_RANGE | ||
else if (msg.matches("Expected a (.+), got: (.*)")) | ||
INVALID_ARGUMENT_VALUE | ||
else if (msg.matches("The expression .+ should have been a node or a relationship, but got .+")) | ||
REQUIRES_DIRECTED_RELATIONSHIP | ||
else if (msg.matches("((Node)|(Relationship)) with id \\d+ has been deleted in this transaction")) | ||
DELETED_ENTITY_ACCESS | ||
else | ||
"unsupportedDetail" | ||
} | ||
println(neo4jException.getMessage) | ||
println(neo4jException.getClass) | ||
ExecutionFailed(errorType, phase, detail) | ||
|
||
} | ||
|
||
/* | ||
private def compileTimeError(msg: String, typ: String, phase: String, detail: String): Boolean = { | ||
var r = true | ||
if (msg.matches("Invalid input '-(\\d)+' is not a valid value, must be a positive integer[\\s.\\S]+")) | ||
detail should equal(NEGATIVE_INTEGER_ARGUMENT) | ||
else if (msg.matches("Invalid input '.+' is not a valid value, must be a positive integer[\\s.\\S]+")) | ||
detail should equal(INVALID_ARGUMENT_TYPE) | ||
else if (msg.matches("Can't use aggregate functions inside of aggregate functions\\.")) | ||
detail should equal(NESTED_AGGREGATION) | ||
else if (msg.matches("Can't create node `(\\w+)` with labels or properties here. The variable is already declared in this context")) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches("Can't create node `(\\w+)` with labels or properties here. It already exists in this context")) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches("Can't create `\\w+` with properties or labels here. The variable is already declared in this context")) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches("Can't create `\\w+` with properties or labels here. It already exists in this context")) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches("Can't create `(\\w+)` with labels or properties here. It already exists in this context")) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches(semanticError("\\w+ already declared"))) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches(semanticError("Only directed relationships are supported in ((CREATE)|(MERGE))"))) | ||
detail should equal(REQUIRES_DIRECTED_RELATIONSHIP) | ||
else if (msg.matches(s"${DOTALL}Type mismatch: expected .+ but was .+")) | ||
detail should equal(INVALID_ARGUMENT_TYPE) | ||
else if (msg.matches(semanticError("Variable `.+` not defined"))) | ||
detail should equal(UNDEFINED_VARIABLE) | ||
else if (msg.matches(semanticError(".+ not defined"))) | ||
detail should equal(UNDEFINED_VARIABLE) | ||
else if (msg.matches(semanticError("Type mismatch: .+ already defined with conflicting type .+ \\(expected .+\\)"))) | ||
detail should equal(VARIABLE_TYPE_CONFLICT) | ||
else if (msg.matches(semanticError("Cannot use the same relationship variable '.+' for multiple patterns"))) | ||
detail should equal(RELATIONSHIP_UNIQUENESS_VIOLATION) | ||
else if (msg.matches(semanticError("Cannot use the same relationship identifier '.+' for multiple patterns"))) | ||
detail should equal(RELATIONSHIP_UNIQUENESS_VIOLATION) | ||
else if (msg.matches(semanticError("Variable length relationships cannot be used in ((CREATE)|(MERGE))"))) | ||
detail should equal(CREATING_VAR_LENGTH) | ||
else if (msg.matches(semanticError("Parameter maps cannot be used in ((MATCH)|(MERGE)) patterns \\(use a literal map instead, eg. \"\\{id: \\{param\\}\\.id\\}\"\\)"))) | ||
detail should equal(INVALID_PARAMETER_USE) | ||
else if (msg.matches(semanticError("Variable `.+` already declared"))) | ||
detail should equal(VARIABLE_ALREADY_BOUND) | ||
else if (msg.matches(semanticError("MATCH cannot follow OPTIONAL MATCH \\(perhaps use a WITH clause between them\\)"))) | ||
detail should equal(INVALID_CLAUSE_COMPOSITION) | ||
else if (msg.matches(semanticError("Invalid combination of UNION and UNION ALL"))) | ||
detail should equal(INVALID_CLAUSE_COMPOSITION) | ||
else if (msg.matches(semanticError("floating point number is too large"))) | ||
detail should equal(FLOATING_POINT_OVERFLOW) | ||
else if (msg.matches(semanticError("Argument to exists\\(\\.\\.\\.\\) is not a property or pattern"))) | ||
detail should equal(INVALID_ARGUMENT_EXPRESSION) | ||
else if (msg.startsWith("Invalid input '—':")) | ||
detail should equal(INVALID_UNICODE_CHARACTER) | ||
else if (msg.matches(semanticError("Can't use aggregating expressions inside of expressions executing over lists"))) | ||
detail should equal(INVALID_AGGREGATION) | ||
else if (msg.matches(semanticError("Can't use aggregating expressions inside of expressions executing over collections"))) | ||
detail should equal(INVALID_AGGREGATION) | ||
else if (msg.matches(semanticError("It is not allowed to refer to variables in ((SKIP)|(LIMIT))"))) | ||
detail should equal(NON_CONSTANT_EXPRESSION) | ||
else if (msg.matches(semanticError("It is not allowed to refer to identifiers in ((SKIP)|(LIMIT))"))) | ||
detail should equal(NON_CONSTANT_EXPRESSION) | ||
else if (msg.matches("Can't use non-deterministic \\(random\\) functions inside of aggregate functions\\.")) | ||
detail should equal(NON_CONSTANT_EXPRESSION) | ||
else if (msg.matches(semanticError("A single relationship type must be specified for ((CREATE)|(MERGE))\\")) || | ||
msg.matches(semanticError("Exactly one relationship type must be specified for ((CREATE)|(MERGE))\\. " + | ||
"Did you forget to prefix your relationship type with a \\'\\:\\'\\?"))) | ||
detail should equal(NO_SINGLE_RELATIONSHIP_TYPE) | ||
else if (msg.matches(s"${DOTALL}Invalid input '.*': expected an identifier character, whitespace, '\\|', a length specification, a property map or '\\]' \\(line \\d+, column \\d+ \\(offset: \\d+\\)\\).*")) | ||
detail should equal(INVALID_RELATIONSHIP_PATTERN) | ||
else if (msg.matches(s"${DOTALL}Invalid input '.*': expected whitespace, RangeLiteral, a property map or '\\]' \\(line \\d+, column \\d+ \\(offset: \\d+\\)\\).*")) | ||
detail should equal(INVALID_RELATIONSHIP_PATTERN) | ||
else if (msg.matches(semanticError("invalid literal number"))) | ||
detail should equal(INVALID_NUMBER_LITERAL) | ||
else if (msg.matches(semanticError("Unknown function '.+'"))) | ||
detail should equal(UNKNOWN_FUNCTION) | ||
else if (msg.matches(semanticError("Invalid input '.+': expected four hexadecimal digits specifying a unicode character"))) | ||
detail should equal(INVALID_UNICODE_LITERAL) | ||
else if (msg.matches("Cannot merge ((relationship)|(node)) using null property value for .+")) | ||
detail should equal(MERGE_READ_OWN_WRITES) | ||
else if (msg.matches(semanticError("Invalid use of aggregating function count\\(\\.\\.\\.\\) in this context"))) | ||
detail should equal(INVALID_AGGREGATION) | ||
else if (msg.matches(semanticError("Cannot use aggregation in ORDER BY if there are no aggregate expressions in the preceding ((RETURN)|(WITH))"))) | ||
detail should equal(INVALID_AGGREGATION) | ||
else if (msg.matches(semanticError("Expression in WITH must be aliased \\(use AS\\)"))) | ||
detail should equal(NO_EXPRESSION_ALIAS) | ||
else if (msg.matches(semanticError("All sub queries in an UNION must have the same column names"))) | ||
detail should equal(DIFFERENT_COLUMNS_IN_UNION) | ||
else if (msg.matches(semanticError("DELETE doesn't support removing labels from a node. Try REMOVE."))) | ||
detail should equal(INVALID_DELETE) | ||
else if (msg.matches("Property values can only be of primitive types or arrays thereof")) | ||
detail should equal(INVALID_PROPERTY_TYPE) | ||
else if (msg.matches(semanticError("Multiple result columns with the same name are not supported"))) | ||
detail should equal(COLUMN_NAME_CONFLICT) | ||
else if (msg.matches(semanticError("RETURN \\* is not allowed when there are no variables in scope"))) | ||
detail should equal(NO_VARIABLES_IN_SCOPE) | ||
else if (msg.matches(semanticError("RETURN \\* is not allowed when there are no identifiers in scope"))) | ||
detail should equal(NO_VARIABLES_IN_SCOPE) | ||
else if (msg.matches(semanticError("Procedure call does not provide the required number of arguments.+"))) | ||
detail should equal("InvalidNumberOfArguments") | ||
else if (msg.matches("Expected a parameter named .+")) | ||
detail should equal("MissingParameter") | ||
else if (msg.startsWith("Procedure call cannot take an aggregating function as argument, please add a 'WITH' to your statement.")) | ||
detail should equal("InvalidAggregation") | ||
else if (msg.startsWith("Procedure call inside a query does not support passing arguments implicitly (pass explicitly after procedure name instead)")) | ||
detail should equal("InvalidArgumentPassingMode") | ||
else if (msg.matches("There is no procedure with the name `.+` registered for this database instance. Please ensure you've spelled the procedure name correctly and that the procedure is properly deployed.")) | ||
detail should equal("ProcedureNotFound") | ||
else r = false | ||
r | ||
} | ||
*/ | ||
} |
Oops, something went wrong.