Skip to content

Commit d1780aa

Browse files
GQL errors (#474)
Ports #472 to other python, js, go.
1 parent 5e71077 commit d1780aa

File tree

3 files changed

+251
-19
lines changed

3 files changed

+251
-19
lines changed

go-manual/modules/ROOT/pages/query-simple.adoc

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,18 @@ For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_ke
189189

190190
A query run may fail for a number of reasons.
191191
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).
192-
An error will be raised if the operation keeps failing after the configured link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j/config#Config.MaxTransactionRetryTime[maximum retry time].
193192

194-
All link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[errors coming from the server] are of type link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#Neo4jError[`Neo4jError`].
195-
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.
193+
An error is raised if the operation keeps failing after a number of attempts.
194+
195+
All errors coming from the server are of type link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#Neo4jError[`Neo4jError`].
196+
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.
196197

197198
.Basic error handling
198-
[source, go]
199+
[source, go, role=nocollapse]
199200
----
200-
res, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN", nil,
201+
_, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN", nil,
201202
neo4j.EagerResultTransformer,
202-
neo4j.ExecuteQueryWithDatabase("<database-name>"))
203+
neo4j.ExecuteQueryWithDatabase("neo4j"))
203204
if err != nil {
204205
var neo4jErr *neo4j.Neo4jError
205206
if errors.As(err, &neo4jErr) {
@@ -218,6 +219,87 @@ Error message: Invalid input '': expected an expression, '*', 'ALL' or 'DISTINCT
218219
*/
219220
----
220221

222+
Exception objects also expose errors as GQL-status objects.
223+
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.
224+
225+
The actual _cause_ that triggered an exception is sometimes found in the optional GQL-status object `.GqlCause`, which is itself a `Neo4jError`.
226+
You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught.
227+
In the example below, the exception's GQL status code is `42001`, but the actual source of the error has status code `42I06`.
228+
229+
.Usage of `Neo4jError` with GQL-related methods
230+
[source, go]
231+
----
232+
_, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN p", nil,
233+
neo4j.EagerResultTransformer,
234+
neo4j.ExecuteQueryWithDatabase("nekko4j"))
235+
if err != nil {
236+
var neo4jErr *neo4j.Neo4jError
237+
if errors.As(err, &neo4jErr) {
238+
// Error is of type Neo4jError
239+
fmt.Println("Error GQL status code:", neo4jErr.GqlStatus)
240+
fmt.Println("Error GQL status description:", neo4jErr.GqlStatusDescription)
241+
fmt.Println("Error GQL classification:", neo4jErr.GqlClassification)
242+
if neo4jErr.GqlDiagnosticRecord != nil {
243+
fmt.Printf("Error GQL diagnostic record: %+v\n", neo4jErr.GqlDiagnosticRecord)
244+
}
245+
if neo4jErr.GqlCause != nil {
246+
fmt.Println("Error GQL cause:", neo4jErr.GqlCause.Error())
247+
}
248+
} else {
249+
fmt.Println("Non-Neo4j error:", err.Error())
250+
}
251+
}
252+
/*
253+
Error GQL status code: 42001
254+
Error GQL status description: error: syntax error or access rule violation - invalid syntax
255+
Error GQL classification: CLIENT_ERROR
256+
Error GQL diagnostic record: map[CURRENT_SCHEMA:/ OPERATION: OPERATION_CODE:0 _classification:CLIENT_ERROR _position:map[column:24 line:1 offset:23]]
257+
Error GQL cause: Neo4jError: Neo.DatabaseError.General.UnknownError (42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.)
258+
*/
259+
----
260+
261+
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.
262+
263+
.Distinguishing between different error codes
264+
[source, go]
265+
----
266+
_, err := neo4j.ExecuteQuery(ctx, driver, "MATCH (p:Person) RETURN", nil,
267+
neo4j.EagerResultTransformer,
268+
neo4j.ExecuteQueryWithDatabase("neo4j"))
269+
if err != nil {
270+
var neo4jErr *neo4j.Neo4jError
271+
if errors.As(err, &neo4jErr) {
272+
if hasGqlStatus(neo4jErr, '42001') {
273+
# Neo.ClientError.Statement.SyntaxError
274+
# special handling of syntax error in query
275+
fmt.Println(neo4jErr.Error())
276+
} else if hasGqlStatus(neo4jErr, '42NFF') {
277+
# Neo.ClientError.Security.Forbidden
278+
# special handling of user not having CREATE permissions
279+
fmt.Println(neo4jErr.Error())
280+
} else {
281+
# handling of all other exceptions
282+
fmt.Println(neo4jErr.Error())
283+
}
284+
}
285+
}
286+
----
287+
288+
[NOTE]
289+
====
290+
The GQL status code `50N42` is returned when an error does not have a GQL-status object.
291+
This can happen if the driver is connected to an older Neo4j server.
292+
Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one.
293+
====
294+
295+
[TIP]
296+
====
297+
Transient server errors can be retried without need to alter the original request.
298+
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.
299+
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running.
300+
====
301+
302+
=======
221303
222304
== Query configuration
223305

javascript-manual/modules/ROOT/pages/query-simple.adoc

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,21 @@ For those rare use cases, see xref:query-advanced#_dynamic_values_in_property_ke
147147
== Error handling
148148

149149
A query run may fail for a number of reasons.
150+
150151
When using `driver.execute_query()`, the driver automatically retries to run a failed query if the failure is deemed to be transient (for example due to temporary server unavailability).
151-
An error will be raised if the operation keeps failing after the configured link:https://neo4j.com/docs/api/javascript-driver/{javascript-driver-version}/class/lib6/types.js~Config.html#instance-member-maxTransactionRetryTime[maximum retry time].
152+
An error will be raised if the operation keeps failing after the configured link:https://neo4j.com/docs/api/javascript-driver/current/class/lib6/types.js~Config.html#instance-member-maxTransactionRetryTime[maximum retry time].
152153

153-
All link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[errors coming from the server] are subclasses of link:https://neo4j.com/docs/api/javascript-driver/{javascript-driver-version}/class/lib6/error.js~Neo4jError.html[`Neo4jError`].
154+
All link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[errors coming from the server] are subclasses of link:https://neo4j.com/docs/api/javascript-driver/current/class/lib6/error.js~Neo4jError.html[`Neo4jError`].
154155
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.
155156

156157
.Basic error handling
157-
[source, javascript]
158+
[source, javascript, role=nocollapse]
158159
----
159-
try {
160-
let err = await driver.executeQuery(
161-
'MATCH (p:Person) RETURN ',
162-
{},
163-
{ database: '<database-name>' }
164-
)
160+
let err = await driver.executeQuery(
161+
'MATCH (p:Person) RETURN ',
162+
{},
163+
{ database: 'neo4j' }
164+
)
165165
} catch (err) {
166166
console.log('Neo4j error code:', err.code)
167167
console.log('Error message:', err.message)
@@ -174,6 +174,87 @@ Error message: Neo4jError: Invalid input '': expected an expression, '*', 'ALL'
174174
*/
175175
----
176176

177+
Error objects also expose errors as GQL-status objects.
178+
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.
179+
180+
The actual _cause_ that triggered an error is sometimes found in the optional GQL-status object `.cause`, which is itself a `Neo4jError`.
181+
You might need to recursively traverse the cause chain before reaching the root cause of the error you caught.
182+
In the example below, the error's GQL status code is `42001`, but the actual source of the error has status code `42I06`.
183+
184+
.Usage of `Neo4jError` with GQL-related methods
185+
[source, javascript]
186+
----
187+
let err = await driver.executeQuery(
188+
'MATCH (p:Person) RETURN ',
189+
{},
190+
{ database: 'neo4j' }
191+
)
192+
} catch (err) {
193+
console.log('Error GQL status:', err.gqlStatus)
194+
console.log('Error GQL status description:', err.gqlStatusDescription)
195+
console.log('Error GQL classification:', err.classification)
196+
console.log('Error GQL cause:', err.cause.message)
197+
console.log('Error GQL diagnostic record:', err.diagnosticRecord)
198+
}
199+
/*
200+
Error GQL status: 42001
201+
Error GQL status description: error: syntax error or access rule violation - invalid syntax
202+
Error GQL classification: CLIENT_ERROR
203+
Error GQL cause: GQLError: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.
204+
Error GQL diagnostic record: {
205+
OPERATION: '',
206+
OPERATION_CODE: '0',
207+
CURRENT_SCHEMA: '/',
208+
_classification: 'CLIENT_ERROR',
209+
_position: {
210+
line: Integer { low: 1, high: 0 },
211+
column: Integer { low: 25, high: 0 },
212+
offset: Integer { low: 24, high: 0 }
213+
}
214+
}
215+
*/
216+
----
217+
218+
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.
219+
220+
.Distinguishing between different error codes
221+
[source, javascript]
222+
----
223+
let err = await driver.executeQuery(
224+
'MATCH (p:Person) RETURN ',
225+
{},
226+
{ database: 'neo4j' }
227+
)
228+
} catch (err) {
229+
if hasGqlStatus(err, '42001') {
230+
# Neo.ClientError.Statement.SyntaxError
231+
# special handling of syntax error in query
232+
console.log(err.message)
233+
} else if hasGqlStatus(err, '42NFF') {
234+
# Neo.ClientError.Security.Forbidden
235+
# special handling of user not having CREATE permissions
236+
console.log(err.message)
237+
} else {
238+
# handling of all other errors
239+
console.log(err.message)
240+
}
241+
}
242+
----
243+
244+
[NOTE]
245+
====
246+
The GQL status code `50N42` is returned when an error does not have a GQL-status object.
247+
This can happen if the driver is connected to an older Neo4j server.
248+
Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one.
249+
====
250+
251+
[TIP]
252+
====
253+
Transient server errors can be retried without need to alter the original request.
254+
You can discover whether an error is transient via the method `Neo4jError.isRetryable()`, which gives insights into whether a further attempt might be successful.
255+
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running.
256+
====
257+
177258

178259
== Query configuration
179260

python-manual/modules/ROOT/pages/query-simple.adoc

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,25 @@ There can be circumstances where your query structure prevents the usage of para
155155
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].
156156

157157

158+
159+
[#error-handling]
158160
== Error handling
159161

160162
A query run may fail for a number of reasons, with different link:https://neo4j.com/docs/api/python-driver/current/api.html#errors[exceptions] being raised.
161163
When using `driver.execute_query()`, the driver automatically retries to run a failed query if the failure is deemed to be transient (for example due to temporary server unavailability).
162-
An error will be raised if the operation keeps failing after the configured link:https://neo4j.com/docs/api/python-driver/current/api.html#max-transaction-retry-time[maximum retry time].
163164

164-
All link:https://neo4j.com/docs/status-codes/current/errors/all-errors/[exceptions coming from the server] are subclasses of link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.exceptions.Neo4jError[`Neo4jError`].
165+
An exception will be raised if the operation keeps failing after a number of attempts.
166+
167+
All exceptions coming from the server are subclasses of link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.exceptions.Neo4jError[`Neo4jError`].
165168
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.
166169

167170
.Basic error handling
168-
[source, python]
171+
[source, python, role=nocollapse]
169172
----
170173
# from neo4j.exceptions import Neo4jError
171174
172175
try:
173-
driver.execute_query('MATCH (p:Person) RETURN', database_='<database-name>')
176+
driver.execute_query('MATCH (p:Person) RETURN', database_='neo4j')
174177
except Neo4jError as e:
175178
print('Neo4j error code:', e.code)
176179
print('Exception message:', e.message)
@@ -182,6 +185,72 @@ Exception message: Invalid input '': expected an expression, '*', 'ALL' or 'DIST
182185
'''
183186
----
184187

188+
Exception objects also expose errors as GQL-status objects.
189+
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.
190+
191+
The actual _cause_ that triggered an exception is sometimes found in the optional GQL-status object `\\__cause__`, which is itself a `Neo4jError`.
192+
You might need to recursively traverse the cause chain before reaching the root cause of the exception you caught.
193+
In the example below, the exception's GQL status code is `42001`, but the actual source of the error has status code `42I06`.
194+
195+
.Usage of `Neo4jError` with GQL-related methods
196+
[source, python]
197+
----
198+
# from neo4j.exceptions import Neo4jError
199+
200+
try:
201+
driver.execute_query('MATCH (p:Person) RETURN', database_='neo4j')
202+
except Neo4jError as e:
203+
print('Exception GQL status:', e.gql_status)
204+
print('Exception GQL status description:', e.gql_status_description)
205+
print('Exception GQL classification:', e.gql_classification)
206+
print('Exception GQL cause:', e.__cause__)
207+
print('Exception GQL diagnostic record:', e.diagnostic_record)
208+
'''
209+
Exception GQL status: 42001
210+
Exception GQL status description: error: syntax error or access rule violation - invalid syntax
211+
Exception GQL classification: GqlErrorClassification.CLIENT_ERROR
212+
Exception GQL cause: {gql_status: 42I06} {gql_status_description: error: syntax error or access rule violation - invalid input. Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.} {message: 42I06: Invalid input '', expected: an expression, '*', 'ALL' or 'DISTINCT'.} {diagnostic_record: {'_classification': 'CLIENT_ERROR', '_position': {'line': 1, 'column': 24, 'offset': 23}, 'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'}} {raw_classification: CLIENT_ERROR}
213+
Exception GQL diagnostic record: {'_classification': 'CLIENT_ERROR', '_position': {'line': 1, 'column': 24, 'offset': 23}, 'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'}
214+
'''
215+
----
216+
217+
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.
218+
219+
.Distinguishing between different error codes
220+
[source, python]
221+
----
222+
# from neo4j.exceptions import Neo4jError
223+
224+
try:
225+
driver.execute_query('MATCH (p:Person) RETURN', database_='neo4j')
226+
except Neo4jError as e:
227+
if e.find_by_gql_status('42001'):
228+
# Neo.ClientError.Statement.SyntaxError
229+
# special handling of syntax error in query
230+
print(e.message)
231+
elif e.find_by_gql_status('42NFF'):
232+
# Neo.ClientError.Security.Forbidden
233+
# special handling of user not having CREATE permissions
234+
print(e.message)
235+
else:
236+
# handling of all other exceptions
237+
print(e.message)
238+
----
239+
240+
[NOTE]
241+
====
242+
The GQL status code `50N42` is returned when an exception does not have a GQL-status object.
243+
This can happen if the driver is connected to an older Neo4j server.
244+
Don't rely on this status code, as future Neo4j server versions might change it with a more appropriate one.
245+
====
246+
247+
[TIP]
248+
====
249+
Transient server errors can be retried without need to alter the original request.
250+
You can discover whether an error is transient via the method `Neo4jError.is_retryable()`, which gives insights into whether a further attempt might be successful.
251+
This is particular useful when running queries in xref:transactions#explicit-transactions[explicit transactions], to know if a failed query is worth re-running.
252+
====
253+
185254

186255
== Query configuration
187256

0 commit comments

Comments
 (0)