diff --git a/go-manual/modules/ROOT/pages/data-types.adoc b/go-manual/modules/ROOT/pages/data-types.adoc index 5ab5d863..3ff4bfde 100644 --- a/go-manual/modules/ROOT/pages/data-types.adoc +++ b/go-manual/modules/ROOT/pages/data-types.adoc @@ -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} @@ -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 diff --git a/go-manual/modules/ROOT/pages/query-simple.adoc b/go-manual/modules/ROOT/pages/query-simple.adoc index 459e4edd..18830a01 100644 --- a/go-manual/modules/ROOT/pages/query-simple.adoc +++ b/go-manual/modules/ROOT/pages/query-simple.adoc @@ -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()`]. diff --git a/javascript-manual/modules/ROOT/pages/data-types.adoc b/javascript-manual/modules/ROOT/pages/data-types.adoc index ae9af760..2f6e9532 100644 --- a/javascript-manual/modules/ROOT/pages/data-types.adoc +++ b/javascript-manual/modules/ROOT/pages/data-types.adoc @@ -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 diff --git a/javascript-manual/modules/ROOT/pages/query-simple.adoc b/javascript-manual/modules/ROOT/pages/query-simple.adoc index d78f4c1a..8da430f9 100644 --- a/javascript-manual/modules/ROOT/pages/query-simple.adoc +++ b/javascript-manual/modules/ROOT/pages/query-simple.adoc @@ -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 diff --git a/python-manual/modules/ROOT/pages/data-types.adoc b/python-manual/modules/ROOT/pages/data-types.adoc index d7d5409f..c36871b7 100644 --- a/python-manual/modules/ROOT/pages/data-types.adoc +++ b/python-manual/modules/ROOT/pages/data-types.adoc @@ -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 diff --git a/python-manual/modules/ROOT/pages/query-simple.adoc b/python-manual/modules/ROOT/pages/query-simple.adoc index 02f65ccd..1bfe57a4 100644 --- a/python-manual/modules/ROOT/pages/query-simple.adoc +++ b/python-manual/modules/ROOT/pages/query-simple.adoc @@ -159,21 +159,100 @@ 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 -Because `.execute_query()` can potentially raise a number of different exceptions, the best way to handle errors is to catch all exceptions in a single `try/except` block: +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. +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). +An exception will be raised if the operation keeps failing after a number of attempts. + +All exceptions coming from the server are subclasses of link:https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.exceptions.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, python, role=nocollapse] +---- +# from neo4j.exceptions import Neo4jError + +try: + driver.execute_query('MATCH (p:Person) RETURN', database_='neo4j') +except Neo4jError as e: + print('Neo4j error code:', e.code) + print('Exception message:', e.message) +''' +Neo4j error code: Neo.ClientError.Statement.SyntaxError +Exception 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 `\\__cause__`, 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, python] ---- +# from neo4j.exceptions import Neo4jError + try: - driver.execute_query(...) -except Exception as e: - ... # handle exception + driver.execute_query('MATCH (p:Person) RETURN', database_='neo4j') +except Neo4jError as e: + print('Exception GQL status:', e.gql_status) + print('Exception GQL status description:', e.gql_status_description) + print('Exception GQL classification:', e.gql_classification) + print('Exception GQL cause:', e.__cause__) + print('Exception GQL diagnostic record:', e.diagnostic_record) +''' +Exception GQL status: 42001 +Exception GQL status description: error: syntax error or access rule violation - invalid syntax +Exception GQL classification: GqlErrorClassification.CLIENT_ERROR +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} +Exception GQL diagnostic record: {'_classification': 'CLIENT_ERROR', '_position': {'line': 1, 'column': 24, 'offset': 23}, 'OPERATION': '', 'OPERATION_CODE': '0', 'CURRENT_SCHEMA': '/'} +''' ---- +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, python] +---- +# from neo4j.exceptions import Neo4jError + +try: + driver.execute_query('MATCH (p:Person) RETURN', database_='neo4j') +except Neo4jError as e: + if e.find_by_gql_status('42001'): + # Neo.ClientError.Statement.SyntaxError + # special handling of syntax error in query + print(e.message) + elif e.find_by_gql_status('42NFF'): + # Neo.ClientError.Security.Forbidden + # special handling of user not having CREATE permissions + print(e.message) + else: + # handling of all other exceptions + print(e.message) +---- + +[NOTE] +==== +The GQL status code `50N42` is returned when an exception 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.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. +==== == Query configuration @@ -266,6 +345,7 @@ For more information, see xref:transformers.adoc[Manipulate query results]. [source, python] ---- from neo4j import GraphDatabase +from neo4j.exceptions import Neo4jError URI = "{neo4j-database-uri}" @@ -316,7 +396,7 @@ with GraphDatabase.driver(URI, auth=AUTH) as driver: time=summary.result_available_after )) - except Exception as e: + except Neo4jError as e: print(e) # further logging/processing ----