Skip to content

Commit

Permalink
GQL notifications (#438)
Browse files Browse the repository at this point in the history
Introduced across drivers between 5.21 and 5.23, likely stable as of
5.25.
  • Loading branch information
stefano-ottolenghi authored Sep 13, 2024
1 parent e82d539 commit ea1c326
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 40 deletions.
97 changes: 86 additions & 11 deletions go-manual/modules/ROOT/pages/result-summary.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,21 @@ For more information and examples, see link:{neo4j-docs-base-uri}/cypher-manual/

== Notifications

The method `ResultSummary.Notifications()` returns a list of link:{neo4j-docs-base-uri}/status-codes/current/notifications[notifications coming from the server], if any were raised by the execution of the query.
These include recommendations for performance improvements, warnings about the usage of deprecated features, and other hints about sub-optimal usage of Neo4j.
Each notification comes as a link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#Notification[`Notification`] object.
After executing a query, the server can return link:{neo4j-docs-base-uri}/status-codes/current/notifications/[notifications] alongside the query result.
Notifications contain recommendations for performance improvements, warnings about the usage of deprecated features, and other hints about sub-optimal usage of Neo4j.

[TIP]
For driver version >= 5.25 and server version >= 5.23, two forms of notifications are available (_Neo4j status codes_ and _GQL status codes_).
For earlier versions, only _Neo4j status codes_ are available. +
GQL status codes are planned to supersede Neo4j status codes.

.An unbounded shortest path raises a performance notification
[.tabbed-example]
=====
[.include-with-neo4j-status-code]
======
The method `Summary.Notifications()` returns a list of link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#Notification[`Notification`] objects.

[source, go, role=nocollapse]
----
result, _ := neo4j.ExecuteQuery(ctx, driver, `
Expand All @@ -184,16 +194,81 @@ Category: PERFORMANCE
*/
----

======
[.include-with-GQL-status-code]
======

With version >= 5.25, the method `Summary.GqlStatusObjects()` returns a list of link:https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v5/neo4j#GqlStatusObject[`GqlStatusObject`]s.
These are GQL-compliant status objects.

Some (but not all) `GqlStatusObjects` are notifications, whereas some report an _outcome_ status: `00000` for "success", `02000` for "no data", and `00001` for "omitted result".
`Summary.GqlStatusObjects()` always contains at least one entry, containing the outcome status.

[source, go, role=nocollapse]
----
result, _ := neo4j.ExecuteQuery(ctx, driver, `
MATCH p=shortestPath((:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'}))
RETURN p
`, nil,
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
for _, status := range result.Summary.GqlStatusObjects() {
fmt.Println("GQLSTATUS:", status.GqlStatus())
fmt.Println("Description:", status.StatusDescription())
// Not all statuses are notifications.
fmt.Println("Is notification:", status.IsNotification())
// Notification and thus vendor-specific fields.
// These fields are only meaningful for notifications.
if status.IsNotification() == true {
fmt.Println("Position (offset, line, column):", status.Position().Offset(), status.Position().Line(), status.Position().Column())
fmt.Println("Classification:", status.Classification())
fmt.Println("Unparsed classification:", status.RawClassification())
fmt.Println("Severity:", status.Severity())
fmt.Println("Unparsed severity:", status.RawSeverity())
}
// Any raw extra information provided by the server
fmt.Println("Diagnostic record:", status.DiagnosticRecord())
fmt.Println(strings.Repeat("=", 80))
}
/*
GQLSTATUS: 02000
Description: note: no data
Is notification: false
Diagnostic record: map[CURRENT_SCHEMA:/ OPERATION: OPERATION_CODE:0]
================================================================================
GQLSTATUS: 03N91
Description: info: unbounded variable length pattern. The provided pattern `(:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'})` is unbounded. Shortest path with an unbounded pattern may result in long execution times. Use an upper limit (e.g. `[*..5]`) on the number of node hops in your pattern.
Is notification: true
Position (offset, line, column): 26 2 26
Classification: PERFORMANCE
Unparsed classification: PERFORMANCE
Severity: INFORMATION
Unparsed severity: INFORMATION
Diagnostic record: map[CURRENT_SCHEMA:/ OPERATION: OPERATION_CODE:0 _classification:PERFORMANCE _position:map[column:26 line:2 offset:26] _severity:INFORMATION _status_parameters:map[pat:(:Person {name: 'Alice'})-[*]->(:Person {name: 'Bob'})]]
================================================================================
*/
----

======
=====


[role=label--new-5.7]
=== Filter notifications

By default, the server analyses each query for all categories and severity of notifications.
Starting from version 5.7, you can use the parameters `NotificationsMinSeverity` and/or `NotificationsDisabledCategories` to restrict the severity or category of notifications that you are interested into.
You may disable notifications altogether by setting the minimum severity to `OFF`.
You can use those parameters either when creating a driver instance, or when creating a session.

Starting from version 5.7, you can use the parameters `NotificationsMinSeverity` and/or `NotificationsDisabledCategories`/`NotificationsDisabledClassifications` to restrict the severity and/or category/classification of notifications that you are interested into.
There is a slight performance gain in restricting the amount of notifications the server is allowed to raise.

The severity filter applies to both Neo4j and GQL notifications.
Category and classification filters exist separately only due to the discrepancy in lexicon between GQL and Neo4j; both filters affect either form of notification though, so you should use only one of them.
You can use any of those parameters either when creating a `Driver` instance, or when creating a session.

You can disable notifications altogether by setting the minimum severity to `"OFF"`.

.Allow only `Warning` notifications, but not of `Hint` or `Generic` category
[source, go]
----
Expand All @@ -207,15 +282,15 @@ driverNot, _ := neo4j.NewDriverWithContext(
dbUri,
neo4j.BasicAuth(dbUser, dbPassword, ""),
func (conf *config.Config) {
conf.NotificationsDisabledCategories = notifications.DisableCategories(notifications.Hint, notifications.Generic)
conf.NotificationsMinSeverity = notifications.WarningLevel
conf.NotificationsMinSeverity = notifications.WarningLevel // or "OFF" to disable entirely
conf.NotificationsDisabledClassifications = notifications.DisableClassifications(notifications.Hint, notifications.Generic) // filters categories as well
})
// At session level
sessionNot := driver.NewSession(ctx, neo4j.SessionConfig{
NotificationsMinSeverity: notifications.WarningLevel,
NotificationsDisabledCategories: notifications.DisableCategories(notifications.Hint, notifications.Generic),
NotificationsMinSeverity: notifications.WarningLevel, // or "OFF" to disable entirely
NotificationsDisabledClassifications: notifications.DisableClassifications(notifications.Hint, notifications.Generic), // filters categories as well
DatabaseName: "neo4j", // always provide the database name
})
----
98 changes: 90 additions & 8 deletions java-manual/modules/ROOT/pages/result-summary.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,21 @@ For more information and examples, see link:{neo4j-docs-base-uri}/cypher-manual/

== Notifications

The method `ResultSummary.notifications()` returns a list of link:{neo4j-docs-base-uri}/status-codes/current/notifications[notifications coming from the server], if any were raised by the execution of the query.
These include recommendations for performance improvements, warnings about the usage of deprecated features, and other hints about sub-optimal usage of Neo4j.
Each notification comes as a link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/summary/Notification.html[`Notification`] object.
After executing a query, the server can return link:{neo4j-docs-base-uri}/status-codes/current/notifications/[notifications] alongside the query result.
Notifications contain recommendations for performance improvements, warnings about the usage of deprecated features, and other hints about sub-optimal usage of Neo4j.

[TIP]
For driver version >= 5.25 and server version >= 5.23, two forms of notifications are available (_Neo4j status codes_ and _GQL status codes_).
For earlier versions, only _Neo4j status codes_ are available. +
GQL status codes are planned to supersede Neo4j status codes.

.An unbounded shortest path raises a performance notification
[.tabbed-example]
=====
[.include-with-neo4j-status-code]
======
The method `ResultSummary.notifications()` returns a list of link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/summary/Notification.html[`Notification`] objects.

[source, java, role=nocollapse]
----
var result = driver.executableQuery("""
Expand All @@ -161,7 +171,6 @@ var result = driver.executableQuery("""
.execute();
var notifications = result.summary().notifications();
System.out.println(notifications);
/*
[
code=Neo.ClientNotification.Statement.UnboundedVariableLengthPattern,
Expand All @@ -177,17 +186,88 @@ System.out.println(notifications);
*/
----

======
[.include-with-GQL-status-code]
======

With version >= 5.25, the method `ResultSummary.gqlStatusObjects()` returns an ordered set of GQL-compliant status objects.

The set can contain both link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/summary/Notification.html[`Notification`] objects and link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/summary/GqlStatusObject.html[`GqlStatusObject`] objects.
The latter encodes the query's _outcome_ status: `00000` for "success", `02000` for "no data", and `00001` for "omitted result".
The set always contains at least one entry, containing the outcome status.

[source, java, role=nocollapse]
----
var result = driver.executableQuery("""
MATCH p=shortestPath((:Person {name: $start})-[*]->(:Person {name: $end}))
RETURN p
""")
.withParameters(Map.of("start", "Alice", "end", "Bob"))
.withConfig(QueryConfig.builder().withDatabase("neo4j").build())
.execute();
var statuses = result.summary().gqlStatusObjects();
System.out.println(statuses);
/*
[
InternalGqlStatusObject{gqlStatus='02000', statusDescription='note: no data', diagnosticRecord={OPERATION_CODE="0", OPERATION="", CURRENT_SCHEMA="/"}},
code=Neo.ClientNotification.Statement.UnboundedVariableLengthPattern, title=The provided pattern is unbounded, consider adding an upper limit to the number of node hops., description=Using shortest path with an unbounded pattern will likely result in long execution times. It is recommended to use an upper limit to the number of node hops in your pattern., severityLevel=InternalNotificationSeverity[type=INFORMATION, level=800], rawSeverityLevel=INFORMATION, classification=PERFORMANCE, rawClassification=PERFORMANCE, position={offset=21, line=1, column=22}
]
*/
----

======
=====


[role=label--new-5.7]
=== Filter notifications

By default, the server analyses each query for all categories and severity of notifications.
Starting from version 5.7, you can use the configuration method `.withNotificationConfig(link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/NotificationConfig.html[NotificationConfig])` to restrict the severity or category of notifications that you are interested into, or disable them altogether. There is a slight performance gain in restricting the amount of notifications the server is allowed to raise.
Starting from version 5.22, you can use the configuration methods link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Config.ConfigBuilder.html#withMinimumNotificationSeverity(org.neo4j.driver.NotificationSeverity)[`.withMinimumNotificationSeverity()`] and link:https://neo4j.com/docs/api/java-driver/5.24/org.neo4j.driver/org/neo4j/driver/Config.ConfigBuilder.html#withDisabledNotificationClassifications(java.util.Set)[`.withDisabledNotificationClassification()`] to tweak the severity and/or category/classification of notifications that you are interested into, or to disable them altogether.
There is a slight performance gain in restricting the amount of notifications the server is allowed to raise.

The severity filter applies to both Neo4j and GQL notifications.
The category filter acts on both categories and classifications.

You can call the methods both on a `Config` object when creating a `Driver` instance, and on a `SessionConfig` object when creating a session.

The `NotificationConfig` interface provides the methods `.enableMinimumSeverity(link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/NotificationSeverity.html[NotificationSeverity])`, `.disableCategories(Set<link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/NotificationCategory.html[NotificationCategory]>)`, and `.disableAllConfig()` to set the configuration.
.Allow only `WARNING` notifications, but not of `HINT` or `GENERIC` classifications
[source, java]
----
// import java.util.Set
// import org.neo4j.driver.Config;
// import org.neo4j.driver.NotificationClassification;
// import org.neo4j.driver.NotificationConfig;
// import org.neo4j.driver.NotificationSeverity;
// import org.neo4j.driver.SessionConfig;
// at `Driver` level
var driver = GraphDatabase.driver(
dbUri, AuthTokens.basic(dbUser, dbPassword),
Config.builder()
.withMinimumNotificationSeverity(NotificationSeverity.WARNING) // NotificationSeverity.OFF to disable entirely
.withDisabledNotificationClassifications(Set.of(NotificationClassification.PERFORMANCE, NotificationClassification.GENERIC)) // filters categories as well
.build()
);
// at `Session` level
var session = driver.session(
SessionConfig.builder()
.withDatabase("neo4j")
.withMinimumNotificationSeverity(NotificationSeverity.WARNING) // NotificationSeverity.OFF to disable entirely
.withDisabledNotificationClassifications(Set.of(NotificationClassification.PERFORMANCE, NotificationClassification.GENERIC)) // filters categories as well
.build()
);
----

.Notifications filtering on versions earlier than 5.22
[%collapsible]
====
For versions earlier than 5.22, notification filtering is done via the configuration method link:https://neo4j.com/docs/api/java-driver/current/org.neo4j.driver/org/neo4j/driver/Config.ConfigBuilder.html#withNotificationConfig(org.neo4j.driver.NotificationConfig)[`.withNotificationConfig()`] (versions 5.7+).
You can call `.withNotificationConfig()` both on a `Config` object when creating a `Driver` instance, and on a `SessionConfig` object when creating a session.
The `NotificationConfig` interface provides the methods `.enableMinimumSeverity()`, `.disableCategories()`, and `.disableAllConfig()` to set the configuration.
.Allow only `WARNING` (and over) notifications, but not of `HINT` or `GENERIC` category
.Allow only `WARNING` notifications, but not of `HINT` or `GENERIC` category
[source, java]
----
// import java.util.Set
Expand Down Expand Up @@ -241,3 +321,5 @@ var session = driver.session(
.build()
);
----
====
Loading

0 comments on commit ea1c326

Please sign in to comment.