Skip to content

GQL errors #474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions go-manual/modules/ROOT/pages/data-types.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,18 @@ The type `Point2D` can be used to represent either a 2D Cartesian point or a 2D
----
// A 2D Cartesian Point
cartesian2d := neo4j.Point2D{
X: 1.23,
Y: 4.56,
SpatialRefId: 7203,
X: 1.23,
Y: 4.56,
SpatialRefId: 7203,
}
fmt.Println(cartesian2d)
// Point{srId=7203, x=1.230000, y=4.560000}

// A 2D WGS84 Point
wgs842d := neo4j.Point2D{
X: 1.23,
Y: 4.56,
SpatialRefId: 9157,
X: 1.23,
Y: 4.56,
SpatialRefId: 9157,
}
fmt.Println(wgs842d)
// Point{srId=9157, x=1.230000, y=4.560000}
Expand Down Expand Up @@ -392,17 +392,6 @@ func addFriend(ctx context.Context, driver neo4j.DriverWithContext, name string,
For full documentation, see link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/dbtype#Path[API documentation -> Path].


== Exceptions

For the most part, the driver simply forwards any error the server may raise.
For a list of errors the server can return, see the link:{neo4j-docs-base-uri}/status-codes/{page-version}[Status code] page.

Some server errors are marked as safe to retry without need to alter the original request.
Examples of such errors are deadlocks, memory issues, or connectivity issues.
When an error is raised, the function link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#IsRetryable[`neo4j.IsRetryable(error)`] gives insights into whether a further attempt might be successful.
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query should be run again. Note that xref:transactions#managed-transactions[managed transactions] already implement a retry mechanism, so you don't need to implement your own.


ifndef::backend-pdf[]
[discrete.glossary]
== Glossary
Expand Down
115 changes: 115 additions & 0 deletions go-manual/modules/ROOT/pages/query-simple.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,121 @@ There can be circumstances where your query structure prevents the usage of para
For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_keys_relationship_types_and_labels[Dynamic values in property keys, relationship types, and labels].


[#error-handling]
== Error handling

A query run may fail for a number of reasons.
When using `ExecuteQuery()`, the driver automatically retries to run a failed query if the failure is deemed to be transient (for example due to temporary server unavailability).
An error is raised if the operation keeps failing after a number of attempts.

All errors coming from the server are of type link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#Neo4jError[`Neo4jError`].
You can use an exception's code to stably identify a specific error; error messages are instead not stable markers, and should not be relied upon.

.Basic error handling
[source, go, role=nocollapse]
----
_, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN", nil,
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
var neo4jErr *neo4j.Neo4jError
if errors.As(err, &neo4jErr) {
// error is of type Neo4jError
fmt.Println("Neo4j error code:", neo4jErr.Code)
fmt.Println("Error message:", neo4jErr.Msg)
} else {
fmt.Printf("Non-Neo4j error: %s\n", err.Error())
}
}
/*
Neo4j error code: Neo.ClientError.Statement.SyntaxError
Error message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 24 (offset: 23))
"MATCH (p:Person) RETURN"
^
*/
----

Exception objects also expose errors as GQL-status objects.
The main difference between link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[Neo4j error codes] and link:https://neo4j.com/docs/status-codes/current/errors/gql-errors/[GQL error codes] is that the GQL ones are more granular: a single Neo4j error code might be broken in several, more specific GQL error codes.

The actual _cause_ that triggered an exception is sometimes found in the optional GQL-status object `.GqlCause`, which is itself a `Neo4jError`.
You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught.
In the example below, the exception's GQL status code is `42001`, but the actual source of the error has status code `42I06`.

.Usage of `Neo4jError` with GQL-related methods
[source, go]
----
_, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN p", nil,
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("nekko4j"))
if err != nil {
var neo4jErr *neo4j.Neo4jError
if errors.As(err, &neo4jErr) {
// Error is of type Neo4jError
fmt.Println("Error GQL status code:", neo4jErr.GqlStatus)
fmt.Println("Error GQL status description:", neo4jErr.GqlStatusDescription)
fmt.Println("Error GQL classification:", neo4jErr.GqlClassification)
if neo4jErr.GqlDiagnosticRecord != nil {
fmt.Printf("Error GQL diagnostic record: %+v\n", neo4jErr.GqlDiagnosticRecord)
}
if neo4jErr.GqlCause != nil {
fmt.Println("Error GQL cause:", neo4jErr.GqlCause.Error())
}
} else {
fmt.Println("Non-Neo4j error:", err.Error())
}
}
/*
Error GQL status code: 42001
Error GQL status description: error: syntax error or access rule violation - invalid syntax
Error GQL classification: CLIENT_ERROR
Error GQL diagnostic record: map[CURRENT_SCHEMA:/ OPERATION: OPERATION_CODE:0 _classification:CLIENT_ERROR _position:map[column:24 line:1 offset:23]]
Error GQL cause: Neo4jError: Neo.DatabaseError.General.UnknownError (42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.)
*/
----

GQL status codes are particularly helpful when you want your application to behave differently depending on the exact error that was raised by the server.

.Distinguishing between different error codes
[source, go]
----
_, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN", nil,
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
var neo4jErr *neo4j.Neo4jError
if errors.As(err, &neo4jErr) {
if hasGqlStatus(neo4jErr, '42001') {
# Neo.ClientError.Statement.SyntaxError
# special handling of syntax error in query
fmt.Println(neo4jErr.Error())
} else if hasGqlStatus(neo4jErr, '42NFF') {
# Neo.ClientError.Security.Forbidden
# special handling of user not having CREATE permissions
fmt.Println(neo4jErr.Error())
} else {
# handling of all other exceptions
fmt.Println(neo4jErr.Error())
}
}
}
----

[NOTE]
====
The GQL status code `50N42` is returned when an error does not have a GQL-status object.
This can happen if the driver is connected to an older Neo4j server.
Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one.
====

[TIP]
====
Transient server errors can be retried without need to alter the original request.
You can discover whether an error is transient via the function link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#IsRetryable[`neo4j.IsRetryable(error)`], which gives insights into whether a further attempt might be successful.
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running.
====


== Query configuration

You can supply further configuration parameters to alter the default behavior of link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#ExecuteQuery[`ExecuteQuery()`].
Expand Down
11 changes: 0 additions & 11 deletions javascript-manual/modules/ROOT/pages/data-types.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -465,17 +465,6 @@ async function addFriend(driver, name, status, friendName) {
For full documentation, see link:{neo4j-docs-base-uri}/api/javascript-driver/{javascript-driver-version}/class/lib6/graph-types.js~Path.html[API documentation -- Path] and link:{neo4j-docs-base-uri}/api/javascript-driver/{javascript-driver-version}/class/lib6/graph-types.js~PathSegment.html[PathSegment].


== Errors

All errors raised by the driver are of type link:{neo4j-docs-base-uri}/api/javascript-driver/{javascript-driver-version}/class/lib6/error.js~Neo4jError.html[`Neo4jError`].
For a list of errors the server can return, see the link:{neo4j-docs-base-uri}/status-codes/5/[Status code] page.

Some server errors are marked as safe to retry without need to alter the original request.
Examples of such errors are deadlocks, memory issues, or connectivity issues.
All driver's exception types implement the method `.isRetryable()`, which gives insights into whether a further attempt might be successful.
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query should be run again.


ifndef::backend-pdf[]
[discrete.glossary]
== Glossary
Expand Down
110 changes: 101 additions & 9 deletions javascript-manual/modules/ROOT/pages/query-simple.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -143,24 +143,116 @@ There can be circumstances where your query structure prevents the usage of para
For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_keys_relationship_types_and_labels[Dynamic values in property keys, relationship types, and labels].


[#error-handling]
== Error handling

To avoid an error in one query crashing your application, you can wrap queries into `try/catch` blocks.
We avoid proper error handling throughout this manual to make examples lighter to parse, and because appropriate error handling depends on the application.
Here below an example with a `try/catch` block.
A query run may fail for a number of reasons.
When using `driver.executeQuery()`, the driver automatically retries to run a failed query if the failure is deemed to be transient (for example due to temporary server unavailability).
An error is raised if the operation keeps failing after a number of attempts.

All errors coming from the server are subclasses of link:https://neo4j.com/docs/api/javascript-driver/current/class/lib6/error.js~Neo4jError.html[`Neo4jError`].
You can use an error's code to stably identify a specific error; error messages are instead not stable markers, and should not be relied upon.

.Basic error handling
[source, javascript, role=nocollapse]
----
let err = await driver.executeQuery(
'MATCH (p:Person) RETURN ',
{},
{ database: 'neo4j' }
)
} catch (err) {
console.log('Neo4j error code:', err.code)
console.log('Error message:', err.message)
}
/*
Neo4j error code: Neo.ClientError.Statement.SyntaxError
Error message: Neo4jError: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT' (line 1, column 25 (offset: 24))
"MATCH (p:Person) RETURN"
^
*/
----

Error objects also expose errors as GQL-status objects.
The main difference between link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[Neo4j error codes] and link:https://neo4j.com/docs/status-codes/current/errors/gql-errors/[GQL error codes] is that the GQL ones are more granular: a single Neo4j error code might be broken in several, more specific GQL error codes.

The actual _cause_ that triggered an error is sometimes found in the optional GQL-status object `.cause`, which is itself a `Neo4jError`.
You might need to recursively traverse the cause chain before reaching the root cause of the error you caught.
In the example below, the error's GQL status code is `42001`, but the actual source of the error has status code `42I06`.

.Usage of `Neo4jError` with GQL-related methods
[source, javascript]
----
try {
let result = await driver.executeQuery('MATCH (p:Person) RETURN p')
} catch(err) {
console.log(`Error in query\n${err}`)
let err = await driver.executeQuery(
'MATCH (p:Person) RETURN ',
{},
{ database: 'neo4j' }
)
} catch (err) {
console.log('Error GQL status:', err.gqlStatus)
console.log('Error GQL status description:', err.gqlStatusDescription)
console.log('Error GQL classification:', err.classification)
console.log('Error GQL cause:', err.cause.message)
console.log('Error GQL diagnostic record:', err.diagnosticRecord)
}
/*
Error GQL status: 42001
Error GQL status description: error: syntax error or access rule violation - invalid syntax
Error GQL classification: CLIENT_ERROR
Error GQL cause: GQLError: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.
Error GQL diagnostic record: {
OPERATION: '',
OPERATION_CODE: '0',
CURRENT_SCHEMA: '/',
_classification: 'CLIENT_ERROR',
_position: {
line: Integer { low: 1, high: 0 },
column: Integer { low: 25, high: 0 },
offset: Integer { low: 24, high: 0 }
}
}
*/
----

GQL status codes are particularly helpful when you want your application to behave differently depending on the exact error that was raised by the server.

.Distinguishing between different error codes
[source, javascript]
----
let err = await driver.executeQuery(
'MATCH (p:Person) RETURN ',
{},
{ database: 'neo4j' }
)
} catch (err) {
if hasGqlStatus(err, '42001') {
# Neo.ClientError.Statement.SyntaxError
# special handling of syntax error in query
console.log(err.message)
} else if hasGqlStatus(err, '42NFF') {
# Neo.ClientError.Security.Forbidden
# special handling of user not having CREATE permissions
console.log(err.message)
} else {
# handling of all other errors
console.log(err.message)
}
}
----

[NOTE]
====
The GQL status code `50N42` is returned when an error does not have a GQL-status object.
This can happen if the driver is connected to an older Neo4j server.
Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one.
====

[TIP]
The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability).
An exception will be raised if the operation keeps failing after a number of attempts.
====
Transient server errors can be retried without need to alter the original request.
You can discover whether an error is transient via the method `Neo4jError.isRetryable()`, which gives insights into whether a further attempt might be successful.
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running.
====


== Query configuration
Expand Down
12 changes: 0 additions & 12 deletions python-manual/modules/ROOT/pages/data-types.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -421,18 +421,6 @@ with neo4j.GraphDatabase.driver(URI, auth=AUTH) as driver:
----


== Exceptions

The driver can raise a number of different exceptions.
A full list is available in the link:{neo4j-docs-base-uri}/api/python-driver/{python-driver-version}/api.html#errors[API documentation].
For a list of errors the server can return, see the link:{neo4j-docs-base-uri}/status-codes/{page-version}[Status code] page.

Some server errors are marked as safe to retry without need to alter the original request.
Examples of such errors are deadlocks, memory issues, or connectivity issues.
All driver's exception types implement the method `.is_retryable()`, which gives insights into whether a further attempt might be successful.
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running.


ifndef::backend-pdf[]
[discrete.glossary]
== Glossary
Expand Down
Loading