From 168b893e2a3a4a2109310622d546ca3562abee64 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 28 Feb 2023 15:01:37 +0100 Subject: [PATCH 01/15] Database Notification Improvements Cypher are improving the notifications that are produced by query execution by making it configurable what level of notifications can be returned and adding additional information to notifications. --- packages/core/src/index.ts | 18 ++- packages/core/src/result-summary.ts | 151 +++++++++++++++++- packages/core/test/result-summary.test.ts | 131 ++++++++++++++- packages/neo4j-driver-lite/src/index.ts | 18 ++- .../neo4j-driver-lite/test/unit/index.test.ts | 18 +++ packages/neo4j-driver/src/index.js | 12 +- .../neo4j-driver/test/types/index.test.ts | 28 +++- packages/neo4j-driver/types/index.d.ts | 16 +- 8 files changed, 374 insertions(+), 18 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 3ab6adccc..ca0373a13 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -64,7 +64,11 @@ import ResultSummary, { Plan, ProfiledPlan, QueryStatistics, - Stats + Stats, + NotificationSeverityLevel, + NotificationCategory, + notificationCategory, + notificationSeverityLevel } from './result-summary' import Result, { QueryResult, ResultObserver } from './result' import EagerResult from './result-eager' @@ -154,7 +158,9 @@ const forExport = { auth, bookmarkManager, routing, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export { @@ -217,7 +223,9 @@ export { auth, bookmarkManager, routing, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export type { @@ -232,7 +240,9 @@ export type { SessionConfig, QueryConfig, RoutingControl, - ResultTransformer + ResultTransformer, + NotificationCategory, + NotificationSeverityLevel } export default forExport diff --git a/packages/core/src/result-summary.ts b/packages/core/src/result-summary.ts index 1431fefa2..ed2ec6708 100644 --- a/packages/core/src/result-summary.ts +++ b/packages/core/src/result-summary.ts @@ -419,6 +419,43 @@ interface NotificationPosition { column?: number } +type NotificationSeverityLevel = 'WARNING' | 'INFORMATION' | 'UNKNOWN' +/** + * @typedef {'WARNING' | 'INFORMATION' | 'UNKNOWN'} NotificationSeverityLevel + */ +/** + * Constants that represents the Severity level in the {@link Notification} + */ +const notificationSeverityLevel: { [key in NotificationSeverityLevel]: key } = { + WARNING: 'WARNING', + INFORMATION: 'INFORMATION', + UNKNOWN: 'UNKNOWN' +} + +Object.freeze(notificationSeverityLevel) +const severityLevels = Object.values(notificationSeverityLevel) + +type NotificationCategory = 'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | +'DEPRECATION' | 'GENERIC' | 'UNKNOWN' +/** + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'RUNTIME' | 'UNKNOWN'} NotificationCategory + */ +/** + * Constants that represents the Category in the {@link Notification} + */ +const notificationCategory: { [key in NotificationCategory]: key } = { + HINT: 'HINT', + UNRECOGNIZED: 'UNRECOGNIZED', + UNSUPPORTED: 'UNSUPPORTED', + PERFORMANCE: 'PERFORMANCE', + DEPRECATION: 'DEPRECATION', + GENERIC: 'GENERIC', + UNKNOWN: 'UNKNOWN' +} + +Object.freeze(notificationCategory) +const categories = Object.values(notificationCategory) + /** * Class for Cypher notifications * @access public @@ -429,6 +466,10 @@ class Notification { description: string severity: string position: NotificationPosition | {} + severityLevel: NotificationSeverityLevel + category: NotificationCategory + rawSeverityLevel: string + rawCategory?: string /** * Create a Notification instance @@ -436,11 +477,113 @@ class Notification { * @param {Object} notification - Object with notification data */ constructor (notification: any) { + /** + * The code + * @type {string} + * @public + */ this.code = notification.code + /** + * The title + * @type {string} + * @public + */ this.title = notification.title + /** + * The description + * @type {string} + * @public + */ this.description = notification.description + /** + * The raw severity + * + * Use {@link Notification#rawSeverityLevel} for the raw value or {@link Notification#severityLevel} for an enumerated value. + * + * @type {string} + * @public + * @deprecated This property will be removed in 6.0. + */ this.severity = notification.severity + /** + * The position which the notification had occur. + * + * @type {NotificationPosition} + * @public + */ this.position = Notification._constructPosition(notification.position) + + /** + * The severity level + * + * @type {NotificationSeverityLevel} + * @public + * @example + * const { summary } = await session.run("RETURN 1") + * + * for (const notification of summary.notifications) { + * switch(notification.severityLevel) { + * case neo4j.notificationSeverityLevel.INFORMATION: // or simply 'INFORMATION' + * console.info(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationSeverityLevel.WARNING: // or simply 'WARNING' + * console.warn(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationSeverityLevel.UNKNOWN: // or simply 'UNKNOWN' + * default: + * // the raw info came from the server could be found at notification.rawSeverityLevel + * console.log(`${notification.title} - ${notification.description}`) + * break + * } + * } + */ + this.severityLevel = severityLevels.includes(notification.severity) + ? notification.severity + : notificationSeverityLevel.UNKNOWN + + /** + * The severity level returned by the server without any validation. + * + * @type {string} + * @public + */ + this.rawSeverityLevel = notification.severity + + /** + * The category + * + * @type {NotificationCategory} + * @public + * @example + * const { summary } = await session.run("RETURN 1") + * + * for (const notification of summary.notifications) { + * switch(notification.category) { + * case neo4j.notificationCategory.QUERY: // or simply 'QUERY' + * console.info(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationCategory.PERFORMANCE: // or simply 'PERFORMANCE' + * console.warn(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationCategory.UNKNOWN: // or simply 'UNKNOWN' + * default: + * // the raw info came from the server could be found at notification.rawCategory + * console.log(`${notification.title} - ${notification.description}`) + * break + * } + * } + */ + this.category = categories.includes(notification.category) + ? notification.category + : notificationCategory.UNKNOWN + + /** + * The category returned by the server without any validation. + * + * @type {string|undefined} + * @public + */ + this.rawCategory = notification.category } static _constructPosition (pos: NotificationPosition): NotificationPosition { @@ -540,10 +683,14 @@ export { Plan, ProfiledPlan, QueryStatistics, - Stats + Stats, + notificationSeverityLevel, + notificationCategory } export type { - NotificationPosition + NotificationPosition, + NotificationSeverityLevel, + NotificationCategory } export default ResultSummary diff --git a/packages/core/test/result-summary.test.ts b/packages/core/test/result-summary.test.ts index cc3ea7c44..20b6fd77a 100644 --- a/packages/core/test/result-summary.test.ts +++ b/packages/core/test/result-summary.test.ts @@ -17,7 +17,14 @@ * limitations under the License. */ -import { ServerInfo } from '../src/result-summary' +import { + ServerInfo, + Notification, + NotificationSeverityLevel, + NotificationCategory, + notificationSeverityLevel, + notificationCategory +} from '../src/result-summary' describe('ServerInfo', () => { it.each([ @@ -48,3 +55,125 @@ describe('ServerInfo', () => { } ) }) + +describe('Notification', () => { + describe('.severityLevel', () => { + it.each(getValidSeverityLevels())('should fill severityLevel with the rawSeverityLevel equals to %s', rawSeverityLevel => { + const rawNotification = { + severity: rawSeverityLevel + } + + const notification = new Notification(rawNotification) + + expect(notification.severityLevel).toBe(rawSeverityLevel) + expect(notification.rawSeverityLevel).toBe(rawSeverityLevel) + }) + + it.each([ + 'UNKNOWN', + null, + undefined, + 'I_AM_NOT_OKAY', + 'information' + ])('should fill severityLevel UNKNOWN if the rawSeverityLevel equals to %s', rawSeverityLevel => { + const rawNotification = { + severity: rawSeverityLevel + } + + const notification = new Notification(rawNotification) + + expect(notification.severityLevel).toBe('UNKNOWN') + expect(notification.rawSeverityLevel).toBe(rawSeverityLevel) + }) + }) + + describe('.category', () => { + it.each(getValidCategories())('should fill category with the rawCategory equals to %s', rawCategory => { + const rawNotification = { + category: rawCategory + } + + const notification = new Notification(rawNotification) + + expect(notification.category).toBe(rawCategory) + expect(notification.rawCategory).toBe(rawCategory) + }) + + it.each([ + 'UNKNOWN', + undefined, + null, + 'DUNNO', + 'deprecation' + ])('should fill category with UNKNOWN the rawCategory equals to %s', rawCategory => { + const rawNotification = { + category: rawCategory + } + + const notification = new Notification(rawNotification) + + expect(notification.category).toBe('UNKNOWN') + expect(notification.rawCategory).toBe(rawCategory) + }) + }) +}) + +describe('notificationSeverityLevel', () => { + it('should have keys equals to values', () => { + for (const [key, value] of Object.entries(notificationSeverityLevel)) { + expect(key).toEqual(value) + } + }) + + it('should have values assignable to NotificationSeverityLevel', () => { + for (const [, value] of Object.entries(notificationSeverityLevel)) { + const assignableValue: NotificationSeverityLevel = value + expect(assignableValue).toBeDefined() + } + }) + + it.each(getValidSeverityLevels())('should have %s as key', (severity) => { + const keys = Object.keys(notificationSeverityLevel) + expect(keys.includes(severity)).toBe(true) + }) +}) + +describe('notificationCategory', () => { + it('should have keys equals to values', () => { + for (const [key, value] of Object.entries(notificationCategory)) { + expect(key).toEqual(value) + } + }) + + it('should values be assignable to NotificationCategory', () => { + for (const [, value] of Object.entries(notificationCategory)) { + const assignableValue: NotificationCategory = value + expect(assignableValue).toBeDefined() + } + }) + + it.each(getValidCategories())('should have %s as key', (category) => { + const keys = Object.keys(notificationCategory) + expect(keys.includes(category)).toBe(true) + }) +}) + +function getValidSeverityLevels (): NotificationSeverityLevel[] { + return [ + 'WARNING', + 'INFORMATION', + 'UNKNOWN' + ] +} + +function getValidCategories (): NotificationCategory[] { + return [ + 'HINT', + 'UNRECOGNIZED', + 'UNSUPPORTED', + 'PERFORMANCE', + 'DEPRECATION', + 'GENERIC', + 'UNKNOWN' + ] +} diff --git a/packages/neo4j-driver-lite/src/index.ts b/packages/neo4j-driver-lite/src/index.ts index 56442d200..8082ea3a7 100644 --- a/packages/neo4j-driver-lite/src/index.ts +++ b/packages/neo4j-driver-lite/src/index.ts @@ -84,7 +84,11 @@ import { RoutingControl, routing, resultTransformers, - ResultTransformer + ResultTransformer, + notificationCategory, + notificationSeverityLevel, + NotificationSeverityLevel, + NotificationCategory } from 'neo4j-driver-core' import { DirectConnectionProvider, @@ -497,7 +501,9 @@ const forExport = { ConnectionProvider, Connection, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export { @@ -559,7 +565,9 @@ export { ConnectionProvider, Connection, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export type { QueryResult, @@ -575,6 +583,8 @@ export type { SessionConfig, QueryConfig, RoutingControl, - ResultTransformer + ResultTransformer, + NotificationCategory, + NotificationSeverityLevel } export default forExport diff --git a/packages/neo4j-driver-lite/test/unit/index.test.ts b/packages/neo4j-driver-lite/test/unit/index.test.ts index ebb9bffd3..723d818f2 100644 --- a/packages/neo4j-driver-lite/test/unit/index.test.ts +++ b/packages/neo4j-driver-lite/test/unit/index.test.ts @@ -409,4 +409,22 @@ describe('index', () => { expect(neo4j.routing.WRITERS).toBeDefined() expect(neo4j.routing.READERS).toBeDefined() }) + + it('should export notificationSeverityLevel', () => { + expect(neo4j.notificationSeverityLevel).toBeDefined() + expect(neo4j.notificationSeverityLevel.WARNING).toBeDefined() + expect(neo4j.notificationSeverityLevel.INFORMATION).toBeDefined() + expect(neo4j.notificationSeverityLevel.UNKNOWN).toBeDefined() + }) + + it('should export notificationCategory', () => { + expect(neo4j.notificationCategory).toBeDefined() + expect(neo4j.notificationCategory.HINT).toBeDefined() + expect(neo4j.notificationCategory.UNRECOGNIZED).toBeDefined() + expect(neo4j.notificationCategory.UNSUPPORTED).toBeDefined() + expect(neo4j.notificationCategory.PERFORMANCE).toBeDefined() + expect(neo4j.notificationCategory.DEPRECATION).toBeDefined() + expect(neo4j.notificationCategory.GENERIC).toBeDefined() + expect(neo4j.notificationCategory.UNKNOWN).toBeDefined() + }) }) diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 3ccd05733..53edfbec1 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -69,7 +69,9 @@ import { ManagedTransaction, bookmarkManager, routing, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } from 'neo4j-driver-core' import { DirectConnectionProvider, @@ -484,7 +486,9 @@ const forExport = { LocalDateTime, DateTime, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export { @@ -547,6 +551,8 @@ export { LocalDateTime, DateTime, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export default forExport diff --git a/packages/neo4j-driver/test/types/index.test.ts b/packages/neo4j-driver/test/types/index.test.ts index 2fd1f50a9..f27af76f9 100644 --- a/packages/neo4j-driver/test/types/index.test.ts +++ b/packages/neo4j-driver/test/types/index.test.ts @@ -36,7 +36,11 @@ import { isPathSegment, isRelationship, isUnboundRelationship, - RoutingControl + RoutingControl, + notificationSeverityLevel, + NotificationSeverityLevel, + notificationCategory, + NotificationCategory } from '../../types/index' import Driver from '../../types/driver' @@ -111,3 +115,25 @@ const neo4jIsPath: boolean = isPath({}) const neo4jIsPathSegment: boolean = isPathSegment({}) const neo4jIsRelationship: boolean = isRelationship({}) const neo4jIsUnboundRelationship: boolean = isUnboundRelationship({}) + +const unknownSeverityString: string = notificationSeverityLevel.UNKNOWN +const warningSeverityString: string = notificationSeverityLevel.WARNING +const informationSeverityString: string = notificationSeverityLevel.INFORMATION +const unknownSeverity: NotificationSeverityLevel = notificationSeverityLevel.UNKNOWN +const warningSeverity: NotificationSeverityLevel = notificationSeverityLevel.WARNING +const informationSeverity: NotificationSeverityLevel = notificationSeverityLevel.INFORMATION + +const hintCategoryString: string = notificationCategory.HINT +const deprecationCategoryString: string = notificationCategory.DEPRECATION +const performanceCategoryString: string = notificationCategory.PERFORMANCE +const genericCategoryString: string = notificationCategory.GENERIC +const unrecognizedCategoryString: string = notificationCategory.UNRECOGNIZED +const unsupportedCategoryString: string = notificationCategory.UNSUPPORTED +const unknownCategoryString: string = notificationCategory.UNKNOWN +const hintCategory: NotificationCategory = notificationCategory.HINT +const deprecationCategory: NotificationCategory = notificationCategory.DEPRECATION +const performanceCategory: NotificationCategory = notificationCategory.PERFORMANCE +const genericCategory: NotificationCategory = notificationCategory.GENERIC +const unrecognizedCategory: NotificationCategory = notificationCategory.UNRECOGNIZED +const unsupportedCategory: NotificationCategory = notificationCategory.UNSUPPORTED +const unknownCategory: NotificationCategory = notificationCategory.UNKNOWN diff --git a/packages/neo4j-driver/types/index.d.ts b/packages/neo4j-driver/types/index.d.ts index badb51bcd..3921b5c82 100644 --- a/packages/neo4j-driver/types/index.d.ts +++ b/packages/neo4j-driver/types/index.d.ts @@ -74,7 +74,11 @@ import { RoutingControl, routing, resultTransformers, - ResultTransformer + ResultTransformer, + notificationCategory, + notificationSeverityLevel, + NotificationCategory, + NotificationSeverityLevel } from 'neo4j-driver-core' import { AuthToken, @@ -252,6 +256,8 @@ declare const forExport: { isUnboundRelationship: typeof isUnboundRelationship bookmarkManager: typeof bookmarkManager resultTransformers: typeof resultTransformers + notificationCategory: typeof notificationCategory + notificationSeverityLevel: typeof notificationSeverityLevel } export { @@ -321,7 +327,9 @@ export { isRelationship, isUnboundRelationship, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export type { @@ -330,7 +338,9 @@ export type { SessionConfig, QueryConfig, RoutingControl, - ResultTransformer + ResultTransformer, + NotificationCategory, + NotificationSeverityLevel } export default forExport From 6b2aa25f3a9ff290218ff34f55bc6695b7141f40 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 28 Feb 2023 16:58:48 +0100 Subject: [PATCH 02/15] Add notification config base --- packages/core/src/notification-config.ts | 82 +++++++++++++ .../core/test/notification-config.test.ts | 114 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 packages/core/src/notification-config.ts create mode 100644 packages/core/test/notification-config.test.ts diff --git a/packages/core/src/notification-config.ts b/packages/core/src/notification-config.ts new file mode 100644 index 000000000..991c944be --- /dev/null +++ b/packages/core/src/notification-config.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + NotificationCategory, + NotificationSeverityLevel +} from './result-summary' + +type ExcludeUnknown = Exclude +type DISABLED = 'DISABLED' +type EnumRecord = { [key in T]: key } + +type NotificationsMinimumSeverityLevel = ExcludeUnknown | DISABLED +type NotificationsCategory = ExcludeUnknown + +const notificationsMinimumSeverityLevel: EnumRecord = { + DISABLED: 'DISABLED', + WARNING: 'WARNING', + INFORMATION: 'INFORMATION' +} + +Object.freeze(notificationsMinimumSeverityLevel) + +const notificationsCategory: EnumRecord = { + HINT: 'HINT', + UNRECOGNIZED: 'UNRECOGNIZED', + UNSUPPORTED: 'UNSUPPORTED', + PERFORMANCE: 'PERFORMANCE', + DEPRECATION: 'DEPRECATION', + GENERIC: 'GENERIC' +} + +Object.freeze(notificationsCategory) + +class NotificationConfig { + minimumSeverityLevel?: NotificationsMinimumSeverityLevel + disabledCategories?: NotificationsCategory[] +} + +function notificationsDisabled (): NotificationConfig { + return { + minimumSeverityLevel: notificationsMinimumSeverityLevel.DISABLED + } +} + +function notifications ( + minimumSeverityLevel?: NotificationsMinimumSeverityLevel, + disabledCategories?: NotificationsCategory[] +): NotificationConfig { + return { + minimumSeverityLevel, + disabledCategories + } +} + +export { + notificationsMinimumSeverityLevel, + notificationsCategory, + notificationsDisabled, + notifications +} + +export type { + NotificationsMinimumSeverityLevel, + NotificationsCategory, + NotificationConfig +} diff --git a/packages/core/test/notification-config.test.ts b/packages/core/test/notification-config.test.ts new file mode 100644 index 000000000..9dc84c7ab --- /dev/null +++ b/packages/core/test/notification-config.test.ts @@ -0,0 +1,114 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + NotificationsCategory, + notificationsCategory, + NotificationsMinimumSeverityLevel, + notificationsMinimumSeverityLevel, + notificationsDisabled, + notifications, + NotificationConfig +} from '../src/notification-config' + +describe('notificationsDisabled()', () => { + it('should return disable notification config', () => { + expect(notificationsDisabled()).toEqual({ + minimumSeverityLevel: 'DISABLED' + }) + }) +}) + +describe('notifications()', () => { + it.each(notificationFixture())('called with %o and %o', (minimumSeverityLevel, disabledCategories, expectedConfig) => { + expect(notifications(minimumSeverityLevel, disabledCategories)) + .toEqual(expectedConfig) + }) + + function notificationFixture (): Array<[ + NotificationsMinimumSeverityLevel?, + NotificationsCategory[]?, + NotificationConfig? + ]> { + return [ + [undefined, undefined, {}], + ['DISABLED', undefined, { minimumSeverityLevel: 'DISABLED' }], + [undefined, ['DEPRECATION', 'PERFORMANCE'], { disabledCategories: ['DEPRECATION', 'PERFORMANCE'] }], + ['WARNING', ['UNRECOGNIZED', 'GENERIC'], { minimumSeverityLevel: 'WARNING', disabledCategories: ['UNRECOGNIZED', 'GENERIC'] }] + ] + } +}) + +describe('notificationsMinimumSeverityLevel', () => { + it('should have keys equals to values', () => { + for (const [key, value] of Object.entries(notificationsMinimumSeverityLevel)) { + expect(key).toEqual(value) + } + }) + + it('should values be assignable to NotificationsMinimumSeverityLevel', () => { + for (const [, value] of Object.entries(notificationsMinimumSeverityLevel)) { + const assignableValue: NotificationsMinimumSeverityLevel = value + expect(assignableValue).toBeDefined() + } + }) + + it.each(getValidNotificationsSeverityLevels())('should have %s as key', (minimumSeverityLevel) => { + const keys = Object.keys(notificationsMinimumSeverityLevel) + expect(keys.includes(minimumSeverityLevel)).toBe(true) + }) +}) + +describe('notificationsCategory', () => { + it('should have keys equals to values', () => { + for (const [key, value] of Object.entries(notificationsCategory)) { + expect(key).toEqual(value) + } + }) + + it('should values be assignable to NotificationsCategory', () => { + for (const [, value] of Object.entries(notificationsCategory)) { + const assignableValue: NotificationsCategory = value + expect(assignableValue).toBeDefined() + } + }) + + it.each(getValidNotificationsCategories())('should have %s as key', (category) => { + const keys = Object.keys(notificationsCategory) + expect(keys.includes(category)).toBe(true) + }) +}) + +function getValidNotificationsSeverityLevels (): NotificationsMinimumSeverityLevel[] { + return [ + 'DISABLED', + 'INFORMATION', + 'WARNING' + ] +} + +function getValidNotificationsCategories (): NotificationsCategory[] { + return [ + 'HINT', + 'DEPRECATION', + 'GENERIC', + 'PERFORMANCE', + 'UNRECOGNIZED', + 'UNSUPPORTED' + ] +} From aa9dceb67a70e44dbb4fa6da9621750d2d0a5e46 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Tue, 28 Feb 2023 17:01:02 +0100 Subject: [PATCH 03/15] sync deno --- packages/neo4j-driver-deno/lib/core/index.ts | 18 ++- .../lib/core/notification-config.ts | 82 ++++++++++ .../lib/core/result-summary.ts | 151 +++++++++++++++++- packages/neo4j-driver-deno/lib/mod.ts | 18 ++- 4 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 packages/neo4j-driver-deno/lib/core/notification-config.ts diff --git a/packages/neo4j-driver-deno/lib/core/index.ts b/packages/neo4j-driver-deno/lib/core/index.ts index 0e15fcdc7..6e1f9c7d5 100644 --- a/packages/neo4j-driver-deno/lib/core/index.ts +++ b/packages/neo4j-driver-deno/lib/core/index.ts @@ -64,7 +64,11 @@ import ResultSummary, { Plan, ProfiledPlan, QueryStatistics, - Stats + Stats, + NotificationSeverityLevel, + NotificationCategory, + notificationCategory, + notificationSeverityLevel } from './result-summary.ts' import Result, { QueryResult, ResultObserver } from './result.ts' import EagerResult from './result-eager.ts' @@ -154,7 +158,9 @@ const forExport = { auth, bookmarkManager, routing, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export { @@ -217,7 +223,9 @@ export { auth, bookmarkManager, routing, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export type { @@ -232,7 +240,9 @@ export type { SessionConfig, QueryConfig, RoutingControl, - ResultTransformer + ResultTransformer, + NotificationCategory, + NotificationSeverityLevel } export default forExport diff --git a/packages/neo4j-driver-deno/lib/core/notification-config.ts b/packages/neo4j-driver-deno/lib/core/notification-config.ts new file mode 100644 index 000000000..bb0e51ebe --- /dev/null +++ b/packages/neo4j-driver-deno/lib/core/notification-config.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + NotificationCategory, + NotificationSeverityLevel +} from './result-summary.ts' + +type ExcludeUnknown = Exclude +type DISABLED = 'DISABLED' +type EnumRecord = { [key in T]: key } + +type NotificationsMinimumSeverityLevel = ExcludeUnknown | DISABLED +type NotificationsCategory = ExcludeUnknown + +const notificationsMinimumSeverityLevel: EnumRecord = { + DISABLED: 'DISABLED', + WARNING: 'WARNING', + INFORMATION: 'INFORMATION' +} + +Object.freeze(notificationsMinimumSeverityLevel) + +const notificationsCategory: EnumRecord = { + HINT: 'HINT', + UNRECOGNIZED: 'UNRECOGNIZED', + UNSUPPORTED: 'UNSUPPORTED', + PERFORMANCE: 'PERFORMANCE', + DEPRECATION: 'DEPRECATION', + GENERIC: 'GENERIC' +} + +Object.freeze(notificationsCategory) + +class NotificationConfig { + minimumSeverityLevel?: NotificationsMinimumSeverityLevel + disabledCategories?: NotificationsCategory[] +} + +function notificationsDisabled (): NotificationConfig { + return { + minimumSeverityLevel: notificationsMinimumSeverityLevel.DISABLED + } +} + +function notifications ( + minimumSeverityLevel?: NotificationsMinimumSeverityLevel, + disabledCategories?: NotificationsCategory[] +): NotificationConfig { + return { + minimumSeverityLevel, + disabledCategories + } +} + +export { + notificationsMinimumSeverityLevel, + notificationsCategory, + notificationsDisabled, + notifications +} + +export type { + NotificationsMinimumSeverityLevel, + NotificationsCategory, + NotificationConfig +} diff --git a/packages/neo4j-driver-deno/lib/core/result-summary.ts b/packages/neo4j-driver-deno/lib/core/result-summary.ts index 771075922..700f83631 100644 --- a/packages/neo4j-driver-deno/lib/core/result-summary.ts +++ b/packages/neo4j-driver-deno/lib/core/result-summary.ts @@ -419,6 +419,43 @@ interface NotificationPosition { column?: number } +type NotificationSeverityLevel = 'WARNING' | 'INFORMATION' | 'UNKNOWN' +/** + * @typedef {'WARNING' | 'INFORMATION' | 'UNKNOWN'} NotificationSeverityLevel + */ +/** + * Constants that represents the Severity level in the {@link Notification} + */ +const notificationSeverityLevel: { [key in NotificationSeverityLevel]: key } = { + WARNING: 'WARNING', + INFORMATION: 'INFORMATION', + UNKNOWN: 'UNKNOWN' +} + +Object.freeze(notificationSeverityLevel) +const severityLevels = Object.values(notificationSeverityLevel) + +type NotificationCategory = 'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | +'DEPRECATION' | 'GENERIC' | 'UNKNOWN' +/** + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'RUNTIME' | 'UNKNOWN'} NotificationCategory + */ +/** + * Constants that represents the Category in the {@link Notification} + */ +const notificationCategory: { [key in NotificationCategory]: key } = { + HINT: 'HINT', + UNRECOGNIZED: 'UNRECOGNIZED', + UNSUPPORTED: 'UNSUPPORTED', + PERFORMANCE: 'PERFORMANCE', + DEPRECATION: 'DEPRECATION', + GENERIC: 'GENERIC', + UNKNOWN: 'UNKNOWN' +} + +Object.freeze(notificationCategory) +const categories = Object.values(notificationCategory) + /** * Class for Cypher notifications * @access public @@ -429,6 +466,10 @@ class Notification { description: string severity: string position: NotificationPosition | {} + severityLevel: NotificationSeverityLevel + category: NotificationCategory + rawSeverityLevel: string + rawCategory?: string /** * Create a Notification instance @@ -436,11 +477,113 @@ class Notification { * @param {Object} notification - Object with notification data */ constructor (notification: any) { + /** + * The code + * @type {string} + * @public + */ this.code = notification.code + /** + * The title + * @type {string} + * @public + */ this.title = notification.title + /** + * The description + * @type {string} + * @public + */ this.description = notification.description + /** + * The raw severity + * + * Use {@link Notification#rawSeverityLevel} for the raw value or {@link Notification#severityLevel} for an enumerated value. + * + * @type {string} + * @public + * @deprecated This property will be removed in 6.0. + */ this.severity = notification.severity + /** + * The position which the notification had occur. + * + * @type {NotificationPosition} + * @public + */ this.position = Notification._constructPosition(notification.position) + + /** + * The severity level + * + * @type {NotificationSeverityLevel} + * @public + * @example + * const { summary } = await session.run("RETURN 1") + * + * for (const notification of summary.notifications) { + * switch(notification.severityLevel) { + * case neo4j.notificationSeverityLevel.INFORMATION: // or simply 'INFORMATION' + * console.info(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationSeverityLevel.WARNING: // or simply 'WARNING' + * console.warn(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationSeverityLevel.UNKNOWN: // or simply 'UNKNOWN' + * default: + * // the raw info came from the server could be found at notification.rawSeverityLevel + * console.log(`${notification.title} - ${notification.description}`) + * break + * } + * } + */ + this.severityLevel = severityLevels.includes(notification.severity) + ? notification.severity + : notificationSeverityLevel.UNKNOWN + + /** + * The severity level returned by the server without any validation. + * + * @type {string} + * @public + */ + this.rawSeverityLevel = notification.severity + + /** + * The category + * + * @type {NotificationCategory} + * @public + * @example + * const { summary } = await session.run("RETURN 1") + * + * for (const notification of summary.notifications) { + * switch(notification.category) { + * case neo4j.notificationCategory.QUERY: // or simply 'QUERY' + * console.info(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationCategory.PERFORMANCE: // or simply 'PERFORMANCE' + * console.warn(`${notification.title} - ${notification.description}`) + * break + * case neo4j.notificationCategory.UNKNOWN: // or simply 'UNKNOWN' + * default: + * // the raw info came from the server could be found at notification.rawCategory + * console.log(`${notification.title} - ${notification.description}`) + * break + * } + * } + */ + this.category = categories.includes(notification.category) + ? notification.category + : notificationCategory.UNKNOWN + + /** + * The category returned by the server without any validation. + * + * @type {string|undefined} + * @public + */ + this.rawCategory = notification.category } static _constructPosition (pos: NotificationPosition): NotificationPosition { @@ -540,10 +683,14 @@ export { Plan, ProfiledPlan, QueryStatistics, - Stats + Stats, + notificationSeverityLevel, + notificationCategory } export type { - NotificationPosition + NotificationPosition, + NotificationSeverityLevel, + NotificationCategory } export default ResultSummary diff --git a/packages/neo4j-driver-deno/lib/mod.ts b/packages/neo4j-driver-deno/lib/mod.ts index 9065e7f72..df08648d2 100644 --- a/packages/neo4j-driver-deno/lib/mod.ts +++ b/packages/neo4j-driver-deno/lib/mod.ts @@ -84,7 +84,11 @@ import { RoutingControl, routing, resultTransformers, - ResultTransformer + ResultTransformer, + notificationCategory, + notificationSeverityLevel, + NotificationSeverityLevel, + NotificationCategory } from './core/index.ts' // @deno-types=./bolt-connection/types/index.d.ts import { @@ -498,7 +502,9 @@ const forExport = { ConnectionProvider, Connection, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export { @@ -560,7 +566,9 @@ export { ConnectionProvider, Connection, bookmarkManager, - resultTransformers + resultTransformers, + notificationCategory, + notificationSeverityLevel } export type { QueryResult, @@ -576,6 +584,8 @@ export type { SessionConfig, QueryConfig, RoutingControl, - ResultTransformer + ResultTransformer, + NotificationCategory, + NotificationSeverityLevel } export default forExport From 288f66eb7b733c37ae849034e1ffc7ac9e6e94c1 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Wed, 1 Mar 2023 19:08:45 +0100 Subject: [PATCH 04/15] Improve notification-config --- packages/core/src/notification-config.ts | 75 +++++++++++++++---- packages/core/src/result-summary.ts | 2 +- .../core/test/notification-config.test.ts | 12 +-- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/packages/core/src/notification-config.ts b/packages/core/src/notification-config.ts index 991c944be..2c9d25ce2 100644 --- a/packages/core/src/notification-config.ts +++ b/packages/core/src/notification-config.ts @@ -22,20 +22,30 @@ import { } from './result-summary' type ExcludeUnknown = Exclude -type DISABLED = 'DISABLED' +type OFF = 'OFF' type EnumRecord = { [key in T]: key } -type NotificationsMinimumSeverityLevel = ExcludeUnknown | DISABLED -type NotificationsCategory = ExcludeUnknown - +type NotificationsMinimumSeverityLevel = ExcludeUnknown | OFF +/** + * @typedef {'WARNING' | 'INFORMATION' | 'DISABLED'} NotificationsMinimumSeverityLevel + */ +/** + * Constants that represents the minimum Severity level in the {@link NotificationConfig} + */ const notificationsMinimumSeverityLevel: EnumRecord = { - DISABLED: 'DISABLED', + OFF: 'OFF', WARNING: 'WARNING', INFORMATION: 'INFORMATION' } - Object.freeze(notificationsMinimumSeverityLevel) +type NotificationsCategory = ExcludeUnknown +/** + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' } NotificationsCategory + */ +/** + * Constants that represents the disabled categories in the {@link NotificationConfig} + */ const notificationsCategory: EnumRecord = { HINT: 'HINT', UNRECOGNIZED: 'UNRECOGNIZED', @@ -44,20 +54,59 @@ const notificationsCategory: EnumRecord = { DEPRECATION: 'DEPRECATION', GENERIC: 'GENERIC' } - Object.freeze(notificationsCategory) +/** + * The notification config object used + * + * @interface + */ class NotificationConfig { minimumSeverityLevel?: NotificationsMinimumSeverityLevel disabledCategories?: NotificationsCategory[] + + /** + * @constructor + * @private + */ + constructor () { + /** + * The minimum level of all notifications to receive. + * + * @public + * @type {?NotificationsMinimumSeverityLevel} + */ + this.minimumSeverityLevel = undefined + + /** + * Categories the user would like to opt-out of receiving. + * @type {?NotificationsCategory[]} + */ + this.disabledCategories = undefined + + throw new Error('Not implemented') + } } -function notificationsDisabled (): NotificationConfig { +/** + * Creates a {@link NotificationConfig} for disabling the notifications. + * + * @returns {NotificationConfig} Notification configuration with disabled. + */ +function notificationsOff (): NotificationConfig { return { - minimumSeverityLevel: notificationsMinimumSeverityLevel.DISABLED + minimumSeverityLevel: notificationsMinimumSeverityLevel.OFF } } +/** + * Creates a {@link NotificationConfig} with {@link NotificationConfig#minimumSeverityLevel} + * and {@link NotificationConfig#disabledCategories}. + * + * @param {NotificationsMinimumSeverityLevel} [minimumSeverityLevel=undefined] The minimum level of all notifications to receive. + * @param {NotificationsCategory[]} [disabledCategories=undefined] Categories the user would like to opt-out of receiving. + * @returns {NotificationConfig} + */ function notifications ( minimumSeverityLevel?: NotificationsMinimumSeverityLevel, disabledCategories?: NotificationsCategory[] @@ -71,12 +120,12 @@ function notifications ( export { notificationsMinimumSeverityLevel, notificationsCategory, - notificationsDisabled, - notifications + notificationsOff, + notifications, + NotificationConfig } export type { NotificationsMinimumSeverityLevel, - NotificationsCategory, - NotificationConfig + NotificationsCategory } diff --git a/packages/core/src/result-summary.ts b/packages/core/src/result-summary.ts index ed2ec6708..674990ec3 100644 --- a/packages/core/src/result-summary.ts +++ b/packages/core/src/result-summary.ts @@ -438,7 +438,7 @@ const severityLevels = Object.values(notificationSeverityLevel) type NotificationCategory = 'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' | 'UNKNOWN' /** - * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'RUNTIME' | 'UNKNOWN'} NotificationCategory + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' | 'UNKNOWN'} NotificationCategory */ /** * Constants that represents the Category in the {@link Notification} diff --git a/packages/core/test/notification-config.test.ts b/packages/core/test/notification-config.test.ts index 9dc84c7ab..289c0e15c 100644 --- a/packages/core/test/notification-config.test.ts +++ b/packages/core/test/notification-config.test.ts @@ -21,15 +21,15 @@ import { notificationsCategory, NotificationsMinimumSeverityLevel, notificationsMinimumSeverityLevel, - notificationsDisabled, + notificationsOff, notifications, NotificationConfig } from '../src/notification-config' -describe('notificationsDisabled()', () => { +describe('notificationsOff()', () => { it('should return disable notification config', () => { - expect(notificationsDisabled()).toEqual({ - minimumSeverityLevel: 'DISABLED' + expect(notificationsOff()).toEqual({ + minimumSeverityLevel: 'OFF' }) }) }) @@ -47,7 +47,7 @@ describe('notifications()', () => { ]> { return [ [undefined, undefined, {}], - ['DISABLED', undefined, { minimumSeverityLevel: 'DISABLED' }], + ['OFF', undefined, { minimumSeverityLevel: 'OFF' }], [undefined, ['DEPRECATION', 'PERFORMANCE'], { disabledCategories: ['DEPRECATION', 'PERFORMANCE'] }], ['WARNING', ['UNRECOGNIZED', 'GENERIC'], { minimumSeverityLevel: 'WARNING', disabledCategories: ['UNRECOGNIZED', 'GENERIC'] }] ] @@ -96,7 +96,7 @@ describe('notificationsCategory', () => { function getValidNotificationsSeverityLevels (): NotificationsMinimumSeverityLevel[] { return [ - 'DISABLED', + 'OFF', 'INFORMATION', 'WARNING' ] From 1261bc22ce3fa480b754a43debeae3e8a9aa30d1 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 2 Mar 2023 12:45:36 +0100 Subject: [PATCH 05/15] Revamp filters --- packages/core/src/index.ts | 19 ++- packages/core/src/notification-config.ts | 131 ------------------ packages/core/src/notification-filter.ts | 101 ++++++++++++++ .../core/test/notification-config.test.ts | 114 --------------- .../core/test/notification-filter.test.ts | 83 +++++++++++ packages/neo4j-driver-deno/lib/core/index.ts | 19 ++- .../lib/core/notification-config.ts | 82 ----------- .../lib/core/notification-filter.ts | 101 ++++++++++++++ .../lib/core/result-summary.ts | 2 +- packages/neo4j-driver-deno/lib/mod.ts | 20 ++- packages/neo4j-driver-lite/src/index.ts | 20 ++- .../neo4j-driver-lite/test/unit/index.test.ts | 17 +++ packages/neo4j-driver/src/index.js | 12 +- .../neo4j-driver/test/types/index.test.ts | 26 +++- packages/neo4j-driver/types/index.d.ts | 18 ++- 15 files changed, 416 insertions(+), 349 deletions(-) delete mode 100644 packages/core/src/notification-config.ts create mode 100644 packages/core/src/notification-filter.ts delete mode 100644 packages/core/test/notification-config.test.ts create mode 100644 packages/core/test/notification-filter.test.ts delete mode 100644 packages/neo4j-driver-deno/lib/core/notification-config.ts create mode 100644 packages/neo4j-driver-deno/lib/core/notification-filter.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ca0373a13..5532f83f0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -70,6 +70,12 @@ import ResultSummary, { notificationCategory, notificationSeverityLevel } from './result-summary' +import NotificationFilter, { + notificationFilterDisabledCategory, + NotificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel, + NotificationFilterMinimumSeverityLevel +} from './notification-filter' import Result, { QueryResult, ResultObserver } from './result' import EagerResult from './result-eager' import ConnectionProvider from './connection-provider' @@ -160,7 +166,9 @@ const forExport = { routing, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export { @@ -225,7 +233,9 @@ export { routing, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export type { @@ -242,7 +252,10 @@ export type { RoutingControl, ResultTransformer, NotificationCategory, - NotificationSeverityLevel + NotificationSeverityLevel, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel } export default forExport diff --git a/packages/core/src/notification-config.ts b/packages/core/src/notification-config.ts deleted file mode 100644 index 2c9d25ce2..000000000 --- a/packages/core/src/notification-config.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - NotificationCategory, - NotificationSeverityLevel -} from './result-summary' - -type ExcludeUnknown = Exclude -type OFF = 'OFF' -type EnumRecord = { [key in T]: key } - -type NotificationsMinimumSeverityLevel = ExcludeUnknown | OFF -/** - * @typedef {'WARNING' | 'INFORMATION' | 'DISABLED'} NotificationsMinimumSeverityLevel - */ -/** - * Constants that represents the minimum Severity level in the {@link NotificationConfig} - */ -const notificationsMinimumSeverityLevel: EnumRecord = { - OFF: 'OFF', - WARNING: 'WARNING', - INFORMATION: 'INFORMATION' -} -Object.freeze(notificationsMinimumSeverityLevel) - -type NotificationsCategory = ExcludeUnknown -/** - * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' } NotificationsCategory - */ -/** - * Constants that represents the disabled categories in the {@link NotificationConfig} - */ -const notificationsCategory: EnumRecord = { - HINT: 'HINT', - UNRECOGNIZED: 'UNRECOGNIZED', - UNSUPPORTED: 'UNSUPPORTED', - PERFORMANCE: 'PERFORMANCE', - DEPRECATION: 'DEPRECATION', - GENERIC: 'GENERIC' -} -Object.freeze(notificationsCategory) - -/** - * The notification config object used - * - * @interface - */ -class NotificationConfig { - minimumSeverityLevel?: NotificationsMinimumSeverityLevel - disabledCategories?: NotificationsCategory[] - - /** - * @constructor - * @private - */ - constructor () { - /** - * The minimum level of all notifications to receive. - * - * @public - * @type {?NotificationsMinimumSeverityLevel} - */ - this.minimumSeverityLevel = undefined - - /** - * Categories the user would like to opt-out of receiving. - * @type {?NotificationsCategory[]} - */ - this.disabledCategories = undefined - - throw new Error('Not implemented') - } -} - -/** - * Creates a {@link NotificationConfig} for disabling the notifications. - * - * @returns {NotificationConfig} Notification configuration with disabled. - */ -function notificationsOff (): NotificationConfig { - return { - minimumSeverityLevel: notificationsMinimumSeverityLevel.OFF - } -} - -/** - * Creates a {@link NotificationConfig} with {@link NotificationConfig#minimumSeverityLevel} - * and {@link NotificationConfig#disabledCategories}. - * - * @param {NotificationsMinimumSeverityLevel} [minimumSeverityLevel=undefined] The minimum level of all notifications to receive. - * @param {NotificationsCategory[]} [disabledCategories=undefined] Categories the user would like to opt-out of receiving. - * @returns {NotificationConfig} - */ -function notifications ( - minimumSeverityLevel?: NotificationsMinimumSeverityLevel, - disabledCategories?: NotificationsCategory[] -): NotificationConfig { - return { - minimumSeverityLevel, - disabledCategories - } -} - -export { - notificationsMinimumSeverityLevel, - notificationsCategory, - notificationsOff, - notifications, - NotificationConfig -} - -export type { - NotificationsMinimumSeverityLevel, - NotificationsCategory -} diff --git a/packages/core/src/notification-filter.ts b/packages/core/src/notification-filter.ts new file mode 100644 index 000000000..91ffeb177 --- /dev/null +++ b/packages/core/src/notification-filter.ts @@ -0,0 +1,101 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + NotificationCategory, + NotificationSeverityLevel +} from './result-summary' + +type ExcludeUnknown = Exclude +type OFF = 'OFF' +type EnumRecord = { [key in T]: key } + +type NotificationFilterMinimumSeverityLevel = ExcludeUnknown | OFF +/** + * @typedef {'WARNING' | 'INFORMATION' | 'DISABLED'} NotificationFilterMinimumSeverityLevel + */ +/** + * Constants that represents the minimum Severity level in the {@link NotificationFilter} + */ +const notificationFilterMinimumSeverityLevel: EnumRecord = { + OFF: 'OFF', + WARNING: 'WARNING', + INFORMATION: 'INFORMATION' +} +Object.freeze(notificationFilterMinimumSeverityLevel) + +type NotificationFilterDisabledCategory = ExcludeUnknown +/** + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' } NotificationFilterDisabledCategory + */ +/** + * Constants that represents the disabled categories in the {@link NotificationFilter} + */ +const notificationFilterDisabledCategory: EnumRecord = { + HINT: 'HINT', + UNRECOGNIZED: 'UNRECOGNIZED', + UNSUPPORTED: 'UNSUPPORTED', + PERFORMANCE: 'PERFORMANCE', + DEPRECATION: 'DEPRECATION', + GENERIC: 'GENERIC' +} +Object.freeze(notificationFilterDisabledCategory) + +/** + * The notification config object used + * + * @interface + */ +class NotificationFilter { + minimumSeverityLevel?: NotificationFilterMinimumSeverityLevel + disabledCategories?: NotificationFilterDisabledCategory[] + + /** + * @constructor + * @private + */ + constructor () { + /** + * The minimum level of all notifications to receive. + * + * @public + * @type {?NotificationFilterMinimumSeverityLevel} + */ + this.minimumSeverityLevel = undefined + + /** + * Categories the user would like to opt-out of receiving. + * @type {?NotificationFilterDisabledCategory[]} + */ + this.disabledCategories = undefined + + throw new Error('Not implemented') + } +} + +export default NotificationFilter + +export { + notificationFilterMinimumSeverityLevel, + notificationFilterDisabledCategory +} + +export type { + NotificationFilterMinimumSeverityLevel, + NotificationFilterDisabledCategory +} diff --git a/packages/core/test/notification-config.test.ts b/packages/core/test/notification-config.test.ts deleted file mode 100644 index 289c0e15c..000000000 --- a/packages/core/test/notification-config.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - NotificationsCategory, - notificationsCategory, - NotificationsMinimumSeverityLevel, - notificationsMinimumSeverityLevel, - notificationsOff, - notifications, - NotificationConfig -} from '../src/notification-config' - -describe('notificationsOff()', () => { - it('should return disable notification config', () => { - expect(notificationsOff()).toEqual({ - minimumSeverityLevel: 'OFF' - }) - }) -}) - -describe('notifications()', () => { - it.each(notificationFixture())('called with %o and %o', (minimumSeverityLevel, disabledCategories, expectedConfig) => { - expect(notifications(minimumSeverityLevel, disabledCategories)) - .toEqual(expectedConfig) - }) - - function notificationFixture (): Array<[ - NotificationsMinimumSeverityLevel?, - NotificationsCategory[]?, - NotificationConfig? - ]> { - return [ - [undefined, undefined, {}], - ['OFF', undefined, { minimumSeverityLevel: 'OFF' }], - [undefined, ['DEPRECATION', 'PERFORMANCE'], { disabledCategories: ['DEPRECATION', 'PERFORMANCE'] }], - ['WARNING', ['UNRECOGNIZED', 'GENERIC'], { minimumSeverityLevel: 'WARNING', disabledCategories: ['UNRECOGNIZED', 'GENERIC'] }] - ] - } -}) - -describe('notificationsMinimumSeverityLevel', () => { - it('should have keys equals to values', () => { - for (const [key, value] of Object.entries(notificationsMinimumSeverityLevel)) { - expect(key).toEqual(value) - } - }) - - it('should values be assignable to NotificationsMinimumSeverityLevel', () => { - for (const [, value] of Object.entries(notificationsMinimumSeverityLevel)) { - const assignableValue: NotificationsMinimumSeverityLevel = value - expect(assignableValue).toBeDefined() - } - }) - - it.each(getValidNotificationsSeverityLevels())('should have %s as key', (minimumSeverityLevel) => { - const keys = Object.keys(notificationsMinimumSeverityLevel) - expect(keys.includes(minimumSeverityLevel)).toBe(true) - }) -}) - -describe('notificationsCategory', () => { - it('should have keys equals to values', () => { - for (const [key, value] of Object.entries(notificationsCategory)) { - expect(key).toEqual(value) - } - }) - - it('should values be assignable to NotificationsCategory', () => { - for (const [, value] of Object.entries(notificationsCategory)) { - const assignableValue: NotificationsCategory = value - expect(assignableValue).toBeDefined() - } - }) - - it.each(getValidNotificationsCategories())('should have %s as key', (category) => { - const keys = Object.keys(notificationsCategory) - expect(keys.includes(category)).toBe(true) - }) -}) - -function getValidNotificationsSeverityLevels (): NotificationsMinimumSeverityLevel[] { - return [ - 'OFF', - 'INFORMATION', - 'WARNING' - ] -} - -function getValidNotificationsCategories (): NotificationsCategory[] { - return [ - 'HINT', - 'DEPRECATION', - 'GENERIC', - 'PERFORMANCE', - 'UNRECOGNIZED', - 'UNSUPPORTED' - ] -} diff --git a/packages/core/test/notification-filter.test.ts b/packages/core/test/notification-filter.test.ts new file mode 100644 index 000000000..a904e1f2d --- /dev/null +++ b/packages/core/test/notification-filter.test.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + NotificationFilterDisabledCategory, + notificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel, + notificationFilterMinimumSeverityLevel +} from '../src/notification-filter' + +describe('notificationFilterMinimumSeverityLevel', () => { + it('should have keys equals to values', () => { + for (const [key, value] of Object.entries(notificationFilterMinimumSeverityLevel)) { + expect(key).toEqual(value) + } + }) + + it('should values be assignable to NotificationFilterMinimumSeverityLevel', () => { + for (const [, value] of Object.entries(notificationFilterMinimumSeverityLevel)) { + const assignableValue: NotificationFilterMinimumSeverityLevel = value + expect(assignableValue).toBeDefined() + } + }) + + it.each(getValidNotificationsSeverityLevels())('should have %s as key', (minimumSeverityLevel) => { + const keys = Object.keys(notificationFilterMinimumSeverityLevel) + expect(keys.includes(minimumSeverityLevel)).toBe(true) + }) +}) + +describe('notificationFilterDisabledCategory', () => { + it('should have keys equals to values', () => { + for (const [key, value] of Object.entries(notificationFilterDisabledCategory)) { + expect(key).toEqual(value) + } + }) + + it('should values be assignable to NotificationFilterDisabledCategory', () => { + for (const [, value] of Object.entries(notificationFilterDisabledCategory)) { + const assignableValue: NotificationFilterDisabledCategory = value + expect(assignableValue).toBeDefined() + } + }) + + it.each(getValidNotificationsCategories())('should have %s as key', (category) => { + const keys = Object.keys(notificationFilterDisabledCategory) + expect(keys.includes(category)).toBe(true) + }) +}) + +function getValidNotificationsSeverityLevels (): NotificationFilterMinimumSeverityLevel[] { + return [ + 'OFF', + 'INFORMATION', + 'WARNING' + ] +} + +function getValidNotificationsCategories (): NotificationFilterDisabledCategory[] { + return [ + 'HINT', + 'DEPRECATION', + 'GENERIC', + 'PERFORMANCE', + 'UNRECOGNIZED', + 'UNSUPPORTED' + ] +} diff --git a/packages/neo4j-driver-deno/lib/core/index.ts b/packages/neo4j-driver-deno/lib/core/index.ts index 6e1f9c7d5..12352d6fd 100644 --- a/packages/neo4j-driver-deno/lib/core/index.ts +++ b/packages/neo4j-driver-deno/lib/core/index.ts @@ -70,6 +70,12 @@ import ResultSummary, { notificationCategory, notificationSeverityLevel } from './result-summary.ts' +import NotificationFilter, { + notificationFilterDisabledCategory, + NotificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel, + NotificationFilterMinimumSeverityLevel +} from './notification-filter.ts' import Result, { QueryResult, ResultObserver } from './result.ts' import EagerResult from './result-eager.ts' import ConnectionProvider from './connection-provider.ts' @@ -160,7 +166,9 @@ const forExport = { routing, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export { @@ -225,7 +233,9 @@ export { routing, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export type { @@ -242,7 +252,10 @@ export type { RoutingControl, ResultTransformer, NotificationCategory, - NotificationSeverityLevel + NotificationSeverityLevel, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel } export default forExport diff --git a/packages/neo4j-driver-deno/lib/core/notification-config.ts b/packages/neo4j-driver-deno/lib/core/notification-config.ts deleted file mode 100644 index bb0e51ebe..000000000 --- a/packages/neo4j-driver-deno/lib/core/notification-config.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - NotificationCategory, - NotificationSeverityLevel -} from './result-summary.ts' - -type ExcludeUnknown = Exclude -type DISABLED = 'DISABLED' -type EnumRecord = { [key in T]: key } - -type NotificationsMinimumSeverityLevel = ExcludeUnknown | DISABLED -type NotificationsCategory = ExcludeUnknown - -const notificationsMinimumSeverityLevel: EnumRecord = { - DISABLED: 'DISABLED', - WARNING: 'WARNING', - INFORMATION: 'INFORMATION' -} - -Object.freeze(notificationsMinimumSeverityLevel) - -const notificationsCategory: EnumRecord = { - HINT: 'HINT', - UNRECOGNIZED: 'UNRECOGNIZED', - UNSUPPORTED: 'UNSUPPORTED', - PERFORMANCE: 'PERFORMANCE', - DEPRECATION: 'DEPRECATION', - GENERIC: 'GENERIC' -} - -Object.freeze(notificationsCategory) - -class NotificationConfig { - minimumSeverityLevel?: NotificationsMinimumSeverityLevel - disabledCategories?: NotificationsCategory[] -} - -function notificationsDisabled (): NotificationConfig { - return { - minimumSeverityLevel: notificationsMinimumSeverityLevel.DISABLED - } -} - -function notifications ( - minimumSeverityLevel?: NotificationsMinimumSeverityLevel, - disabledCategories?: NotificationsCategory[] -): NotificationConfig { - return { - minimumSeverityLevel, - disabledCategories - } -} - -export { - notificationsMinimumSeverityLevel, - notificationsCategory, - notificationsDisabled, - notifications -} - -export type { - NotificationsMinimumSeverityLevel, - NotificationsCategory, - NotificationConfig -} diff --git a/packages/neo4j-driver-deno/lib/core/notification-filter.ts b/packages/neo4j-driver-deno/lib/core/notification-filter.ts new file mode 100644 index 000000000..c60b42d0c --- /dev/null +++ b/packages/neo4j-driver-deno/lib/core/notification-filter.ts @@ -0,0 +1,101 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + NotificationCategory, + NotificationSeverityLevel +} from './result-summary.ts' + +type ExcludeUnknown = Exclude +type OFF = 'OFF' +type EnumRecord = { [key in T]: key } + +type NotificationFilterMinimumSeverityLevel = ExcludeUnknown | OFF +/** + * @typedef {'WARNING' | 'INFORMATION' | 'DISABLED'} NotificationFilterMinimumSeverityLevel + */ +/** + * Constants that represents the minimum Severity level in the {@link NotificationFilter} + */ +const notificationFilterMinimumSeverityLevel: EnumRecord = { + OFF: 'OFF', + WARNING: 'WARNING', + INFORMATION: 'INFORMATION' +} +Object.freeze(notificationFilterMinimumSeverityLevel) + +type NotificationFilterDisabledCategory = ExcludeUnknown +/** + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' } NotificationFilterDisabledCategory + */ +/** + * Constants that represents the disabled categories in the {@link NotificationFilter} + */ +const notificationFilterDisabledCategory: EnumRecord = { + HINT: 'HINT', + UNRECOGNIZED: 'UNRECOGNIZED', + UNSUPPORTED: 'UNSUPPORTED', + PERFORMANCE: 'PERFORMANCE', + DEPRECATION: 'DEPRECATION', + GENERIC: 'GENERIC' +} +Object.freeze(notificationFilterDisabledCategory) + +/** + * The notification config object used + * + * @interface + */ +class NotificationFilter { + minimumSeverityLevel?: NotificationFilterMinimumSeverityLevel + disabledCategories?: NotificationFilterDisabledCategory[] + + /** + * @constructor + * @private + */ + constructor () { + /** + * The minimum level of all notifications to receive. + * + * @public + * @type {?NotificationFilterMinimumSeverityLevel} + */ + this.minimumSeverityLevel = undefined + + /** + * Categories the user would like to opt-out of receiving. + * @type {?NotificationFilterDisabledCategory[]} + */ + this.disabledCategories = undefined + + throw new Error('Not implemented') + } +} + +export default NotificationFilter + +export { + notificationFilterMinimumSeverityLevel, + notificationFilterDisabledCategory +} + +export type { + NotificationFilterMinimumSeverityLevel, + NotificationFilterDisabledCategory +} diff --git a/packages/neo4j-driver-deno/lib/core/result-summary.ts b/packages/neo4j-driver-deno/lib/core/result-summary.ts index 700f83631..c16803598 100644 --- a/packages/neo4j-driver-deno/lib/core/result-summary.ts +++ b/packages/neo4j-driver-deno/lib/core/result-summary.ts @@ -438,7 +438,7 @@ const severityLevels = Object.values(notificationSeverityLevel) type NotificationCategory = 'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' | 'UNKNOWN' /** - * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'RUNTIME' | 'UNKNOWN'} NotificationCategory + * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'DEPRECATION' | 'GENERIC' | 'UNKNOWN'} NotificationCategory */ /** * Constants that represents the Category in the {@link Notification} diff --git a/packages/neo4j-driver-deno/lib/mod.ts b/packages/neo4j-driver-deno/lib/mod.ts index df08648d2..eaab180b7 100644 --- a/packages/neo4j-driver-deno/lib/mod.ts +++ b/packages/neo4j-driver-deno/lib/mod.ts @@ -88,7 +88,12 @@ import { notificationCategory, notificationSeverityLevel, NotificationSeverityLevel, - NotificationCategory + NotificationCategory, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } from './core/index.ts' // @deno-types=./bolt-connection/types/index.d.ts import { @@ -504,7 +509,9 @@ const forExport = { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export { @@ -568,7 +575,9 @@ export { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export type { QueryResult, @@ -586,6 +595,9 @@ export type { RoutingControl, ResultTransformer, NotificationCategory, - NotificationSeverityLevel + NotificationSeverityLevel, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel } export default forExport diff --git a/packages/neo4j-driver-lite/src/index.ts b/packages/neo4j-driver-lite/src/index.ts index 8082ea3a7..089f7c2c8 100644 --- a/packages/neo4j-driver-lite/src/index.ts +++ b/packages/neo4j-driver-lite/src/index.ts @@ -88,7 +88,12 @@ import { notificationCategory, notificationSeverityLevel, NotificationSeverityLevel, - NotificationCategory + NotificationCategory, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } from 'neo4j-driver-core' import { DirectConnectionProvider, @@ -503,7 +508,9 @@ const forExport = { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export { @@ -567,7 +574,9 @@ export { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export type { QueryResult, @@ -585,6 +594,9 @@ export type { RoutingControl, ResultTransformer, NotificationCategory, - NotificationSeverityLevel + NotificationSeverityLevel, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel } export default forExport diff --git a/packages/neo4j-driver-lite/test/unit/index.test.ts b/packages/neo4j-driver-lite/test/unit/index.test.ts index 723d818f2..a5ebbc4aa 100644 --- a/packages/neo4j-driver-lite/test/unit/index.test.ts +++ b/packages/neo4j-driver-lite/test/unit/index.test.ts @@ -427,4 +427,21 @@ describe('index', () => { expect(neo4j.notificationCategory.GENERIC).toBeDefined() expect(neo4j.notificationCategory.UNKNOWN).toBeDefined() }) + + it('should export notificationFilterMinimumSeverityLevel', () => { + expect(neo4j.notificationFilterMinimumSeverityLevel).toBeDefined() + expect(neo4j.notificationFilterMinimumSeverityLevel.WARNING).toBeDefined() + expect(neo4j.notificationFilterMinimumSeverityLevel.INFORMATION).toBeDefined() + expect(neo4j.notificationFilterMinimumSeverityLevel.OFF).toBeDefined() + }) + + it('should export notificationFilterDisabledCategory', () => { + expect(neo4j.notificationFilterDisabledCategory).toBeDefined() + expect(neo4j.notificationFilterDisabledCategory.HINT).toBeDefined() + expect(neo4j.notificationFilterDisabledCategory.UNRECOGNIZED).toBeDefined() + expect(neo4j.notificationFilterDisabledCategory.UNSUPPORTED).toBeDefined() + expect(neo4j.notificationFilterDisabledCategory.PERFORMANCE).toBeDefined() + expect(neo4j.notificationFilterDisabledCategory.DEPRECATION).toBeDefined() + expect(neo4j.notificationFilterDisabledCategory.GENERIC).toBeDefined() + }) }) diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 53edfbec1..820d678ec 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -71,7 +71,9 @@ import { routing, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } from 'neo4j-driver-core' import { DirectConnectionProvider, @@ -488,7 +490,9 @@ const forExport = { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export { @@ -553,6 +557,8 @@ export { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export default forExport diff --git a/packages/neo4j-driver/test/types/index.test.ts b/packages/neo4j-driver/test/types/index.test.ts index f27af76f9..b7b611ce4 100644 --- a/packages/neo4j-driver/test/types/index.test.ts +++ b/packages/neo4j-driver/test/types/index.test.ts @@ -40,7 +40,11 @@ import { notificationSeverityLevel, NotificationSeverityLevel, notificationCategory, - NotificationCategory + NotificationCategory, + notificationFilterMinimumSeverityLevel, + NotificationFilterMinimumSeverityLevel, + NotificationFilterDisabledCategory, + notificationFilterDisabledCategory } from '../../types/index' import Driver from '../../types/driver' @@ -137,3 +141,23 @@ const genericCategory: NotificationCategory = notificationCategory.GENERIC const unrecognizedCategory: NotificationCategory = notificationCategory.UNRECOGNIZED const unsupportedCategory: NotificationCategory = notificationCategory.UNSUPPORTED const unknownCategory: NotificationCategory = notificationCategory.UNKNOWN + +const offNotificationFilterMinimumSeverityLevelString: string = notificationFilterMinimumSeverityLevel.OFF +const warningNotificationFilterMinimumSeverityLevelString: string = notificationFilterMinimumSeverityLevel.WARNING +const infoNotificationFilterMinimumSeverityLevelString: string = notificationFilterMinimumSeverityLevel.INFORMATION +const offNotificationFilterMinimumSeverityLevel: NotificationFilterMinimumSeverityLevel = notificationFilterMinimumSeverityLevel.OFF +const warningNotificationFilterMinimumSeverityLevel: NotificationFilterMinimumSeverityLevel = notificationFilterMinimumSeverityLevel.WARNING +const infoNotificationFilterMinimumSeverityLevel: NotificationFilterMinimumSeverityLevel = notificationFilterMinimumSeverityLevel.INFORMATION + +const hintDisabledCategoryString: string = notificationFilterDisabledCategory.HINT +const deprecationDisabledCategoryString: string = notificationFilterDisabledCategory.DEPRECATION +const performanceDisabledCategoryString: string = notificationFilterDisabledCategory.PERFORMANCE +const genericDisabledCategoryString: string = notificationFilterDisabledCategory.GENERIC +const unrecognizedDisabledCategoryString: string = notificationFilterDisabledCategory.UNRECOGNIZED +const unsupportedDisabledCategoryString: string = notificationFilterDisabledCategory.UNSUPPORTED +const hintDisabledCategory: NotificationFilterDisabledCategory = notificationFilterDisabledCategory.HINT +const deprecationDisabledCategory: NotificationFilterDisabledCategory = notificationFilterDisabledCategory.DEPRECATION +const performanceDisabledCategory: NotificationFilterDisabledCategory = notificationFilterDisabledCategory.PERFORMANCE +const genericDisabledCategory: NotificationFilterDisabledCategory = notificationFilterDisabledCategory.GENERIC +const unrecognizedDisabledCategory: NotificationFilterDisabledCategory = notificationFilterDisabledCategory.UNRECOGNIZED +const unsupportedDisabledCategory: NotificationFilterDisabledCategory = notificationFilterDisabledCategory.UNSUPPORTED diff --git a/packages/neo4j-driver/types/index.d.ts b/packages/neo4j-driver/types/index.d.ts index 3921b5c82..e0dfa1865 100644 --- a/packages/neo4j-driver/types/index.d.ts +++ b/packages/neo4j-driver/types/index.d.ts @@ -78,7 +78,12 @@ import { notificationCategory, notificationSeverityLevel, NotificationCategory, - NotificationSeverityLevel + NotificationSeverityLevel, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } from 'neo4j-driver-core' import { AuthToken, @@ -258,6 +263,8 @@ declare const forExport: { resultTransformers: typeof resultTransformers notificationCategory: typeof notificationCategory notificationSeverityLevel: typeof notificationSeverityLevel + notificationFilterDisabledCategory: typeof notificationFilterDisabledCategory + notificationFilterMinimumSeverityLevel: typeof notificationFilterMinimumSeverityLevel } export { @@ -329,7 +336,9 @@ export { bookmarkManager, resultTransformers, notificationCategory, - notificationSeverityLevel + notificationSeverityLevel, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel } export type { @@ -340,7 +349,10 @@ export type { RoutingControl, ResultTransformer, NotificationCategory, - NotificationSeverityLevel + NotificationSeverityLevel, + NotificationFilter, + NotificationFilterDisabledCategory, + NotificationFilterMinimumSeverityLevel } export default forExport From 01c02133b5cba3edf35219ae7f686922382c695a Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 2 Mar 2023 14:13:55 +0100 Subject: [PATCH 06/15] Add filter configuration to `Driver.session` --- packages/core/src/driver.ts | 80 ++++++++++++++++++- packages/core/src/notification-filter.ts | 7 +- packages/core/src/session.ts | 14 +++- packages/core/src/transaction-promise.ts | 24 +++--- packages/core/src/transaction.ts | 28 ++++--- packages/core/test/driver.test.ts | 27 ++++++- packages/core/test/session.test.ts | 52 +++++++++++- packages/core/test/transaction.test.ts | 37 +++++++-- .../utils/notification-filters.fixtures.ts | 47 +++++++++++ packages/neo4j-driver/src/driver.js | 6 +- 10 files changed, 283 insertions(+), 39 deletions(-) create mode 100644 packages/core/test/utils/notification-filters.fixtures.ts diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 11129d090..5c916bb19 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -46,6 +46,7 @@ import EagerResult from './result-eager' import resultTransformers, { ResultTransformer } from './result-transformers' import QueryExecutor from './internal/query-executor' import { newError } from './error' +import NotificationFilter from './notification-filter' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour @@ -94,6 +95,7 @@ type CreateSession = (args: { fetchSize: number impersonatedUser?: string bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -117,6 +119,7 @@ class SessionConfig { impersonatedUser?: string fetchSize?: number bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter /** * @constructor @@ -235,6 +238,70 @@ class SessionConfig { * @since 5.0 */ this.bookmarkManager = undefined + + /** + * Configure filter for {@link Notification} objects returned in {@link ResultSummary#notifications}. + * + * This configuration enables filter notifications by: + * + * * the minimum severity level ({@link NotificationFilterMinimumSeverityLevel}) + * * disabling notification categories ({@link NotificationFilterDisabledCategory}) + * + * + * Disabling notifications can be done by defining the minimum severity level to 'OFF'. + * Default values can be use by omitting the configuration. + * + * @example + * // enabling warning notification, but disabling `HINT` and `DEPRECATION` notifications. + * const session = driver.session({ + * database: 'neo4j', + * notificationFilter: { + * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING, // or 'WARNING + * disabledCategories: [ + * neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT' + * neo4j.notificationFilterDisabledCategory.DEPRECATION // or 'DEPRECATION' + * ] + * } + * }) + * + * @example + * // disabling notifications for a session + * const session = driver.session({ + * database: 'neo4j', + * notificationFilter: { + * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.OFF // or 'OFF' + * } + * }) + * + * @example + * // using default values configured in the driver + * const sessionWithDefaultValues = driver.session({ database: 'neo4j' }) + * // or driver.session({ database: 'neo4j', notificationFilter: undefined }) + * + * // using default minimum severity level, but disabling 'HINT' and 'UNRECOGNIZED' + * // notification categories + * const sessionWithDefaultSeverityLevel = driver.session({ + * database: 'neo4j', + * notificationFilters: { + * disabledCategories: [ + * neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT' + * neo4j.notificationFilterDisabledCategory.UNRECOGNIZED // or 'UNRECOGNIZED' + * ] + * } + * }) + * + * // using default disabled categories, but configuring minimum severity level to 'WARNING' + * const sessionWithDefaultSeverityLevel = driver.session({ + * database: 'neo4j', + * notificationFilters: { + * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING // or 'WARNING' + * } + * }) + * + * @type {NotificationFilter|undefined} + * @since 5.7 + */ + this.notificationFilter = undefined } } @@ -627,7 +694,8 @@ class Driver { database = '', impersonatedUser, fetchSize, - bookmarkManager + bookmarkManager, + notificationFilter }: SessionConfig = {}): Session { return this._newSession({ defaultAccessMode, @@ -637,7 +705,8 @@ class Driver { impersonatedUser, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion fetchSize: validateFetchSizeValue(fetchSize, this._config.fetchSize!), - bookmarkManager + bookmarkManager, + notificationFilter }) } @@ -675,7 +744,8 @@ class Driver { reactive, impersonatedUser, fetchSize, - bookmarkManager + bookmarkManager, + notificationFilter }: { defaultAccessMode: SessionMode bookmarkOrBookmarks?: string | string[] @@ -684,6 +754,7 @@ class Driver { impersonatedUser?: string fetchSize: number bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() @@ -700,7 +771,8 @@ class Driver { reactive, impersonatedUser, fetchSize, - bookmarkManager + bookmarkManager, + notificationFilter }) } diff --git a/packages/core/src/notification-filter.ts b/packages/core/src/notification-filter.ts index 91ffeb177..cd5e21b9e 100644 --- a/packages/core/src/notification-filter.ts +++ b/packages/core/src/notification-filter.ts @@ -27,7 +27,7 @@ type EnumRecord = { [key in T]: key } type NotificationFilterMinimumSeverityLevel = ExcludeUnknown | OFF /** - * @typedef {'WARNING' | 'INFORMATION' | 'DISABLED'} NotificationFilterMinimumSeverityLevel + * @typedef {'WARNING' | 'INFORMATION' | 'OFF'} NotificationFilterMinimumSeverityLevel */ /** * Constants that represents the minimum Severity level in the {@link NotificationFilter} @@ -57,7 +57,10 @@ const notificationFilterDisabledCategory: EnumRecord any | undefined | Promise | Promise type TransactionWork = (tx: Transaction) => Promise | T @@ -72,6 +73,7 @@ class Session { private readonly _highRecordWatermark: number private readonly _results: Result[] private readonly _bookmarkManager?: BookmarkManager + private readonly _notificationFilter?: NotificationFilter /** * @constructor * @protected @@ -84,6 +86,7 @@ class Session { * @param {boolean} args.reactive - Whether this session should create reactive streams * @param {number} args.fetchSize - Defines how many records is pulled in each pulling batch * @param {string} args.impersonatedUser - The username which the user wants to impersonate for the duration of the session. + * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. */ constructor ({ mode, @@ -94,7 +97,8 @@ class Session { reactive, fetchSize, impersonatedUser, - bookmarkManager + bookmarkManager, + notificationFilter }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -105,6 +109,7 @@ class Session { fetchSize: number impersonatedUser?: string bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }) { this._mode = mode this._database = database @@ -142,6 +147,7 @@ class Session { this._highRecordWatermark = calculatedWatermaks.high this._results = [] this._bookmarkManager = bookmarkManager + this._notificationFilter = notificationFilter } /** @@ -181,7 +187,8 @@ class Session { reactive: this._reactive, fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, - highRecordWatermark: this._highRecordWatermark + highRecordWatermark: this._highRecordWatermark, + notificationFilter: this._notificationFilter }) }) this._results.push(result) @@ -298,7 +305,8 @@ class Session { reactive: this._reactive, fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, - highRecordWatermark: this._highRecordWatermark + highRecordWatermark: this._highRecordWatermark, + notificationFilter: this._notificationFilter }) tx._begin(() => this._bookmarks(), txConfig) return tx diff --git a/packages/core/src/transaction-promise.ts b/packages/core/src/transaction-promise.ts index 04798195c..30bab3d65 100644 --- a/packages/core/src/transaction-promise.ts +++ b/packages/core/src/transaction-promise.ts @@ -26,6 +26,7 @@ import { import { Bookmarks } from './internal/bookmarks' import { TxConfig } from './internal/tx-config' +import NotificationFilter from './notification-filter' /** * Represents a {@link Promise} object and a {@link Transaction} object. @@ -47,14 +48,16 @@ class TransactionPromise extends Transaction implements Promise { /** * @constructor - * @param {ConnectionHolder} connectionHolder - the connection holder to get connection from. - * @param {function()} onClose - Function to be called when transaction is committed or rolled back. - * @param {function(bookmarks: Bookmarks)} onBookmarks callback invoked when new bookmark is produced. - * @param {function()} onConnection - Function to be called when a connection is obtained to ensure the connection + * @param {object} args + * @param {ConnectionHolder} args.connectionHolder - the connection holder to get connection from. + * @param {function()} args.onClose - Function to be called when transaction is committed or rolled back. + * @param {function(bookmarks: Bookmarks)} args.onBookmarks callback invoked when new bookmark is produced. + * @param {function()} args.onConnection - Function to be called when a connection is obtained to ensure the connection * is not yet released. - * @param {boolean} reactive whether this transaction generates reactive streams - * @param {number} fetchSize - the record fetch size in each pulling batch. - * @param {string} impersonatedUser - The name of the user which should be impersonated for the duration of the session. + * @param {boolean} args.reactive whether this transaction generates reactive streams + * @param {number} args.fetchSize - the record fetch size in each pulling batch. + * @param {string} args.impersonatedUser - The name of the user which should be impersonated for the duration of the session. + * @param {NotificationFilter} args.notificationFilter - The notification filter used for this transaction. */ constructor ({ connectionHolder, @@ -65,7 +68,8 @@ class TransactionPromise extends Transaction implements Promise { fetchSize, impersonatedUser, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }: { connectionHolder: ConnectionHolder onClose: () => void @@ -76,6 +80,7 @@ class TransactionPromise extends Transaction implements Promise { impersonatedUser?: string highRecordWatermark: number lowRecordWatermark: number + notificationFilter?: NotificationFilter }) { super({ connectionHolder, @@ -86,7 +91,8 @@ class TransactionPromise extends Transaction implements Promise { fetchSize, impersonatedUser, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }) } diff --git a/packages/core/src/transaction.ts b/packages/core/src/transaction.ts index 3bee6d480..ca0c95884 100644 --- a/packages/core/src/transaction.ts +++ b/packages/core/src/transaction.ts @@ -38,6 +38,7 @@ import { newError } from './error' import Result from './result' import { Query } from './types' import { Dict } from './record' +import NotificationFilter from './notification-filter' /** * Represents a transaction in the Neo4j database. @@ -61,19 +62,22 @@ class Transaction { private _bookmarks: Bookmarks private readonly _activePromise: Promise private _acceptActive: () => void + private readonly _notificationFilter?: NotificationFilter /** * @constructor - * @param {ConnectionHolder} connectionHolder - the connection holder to get connection from. - * @param {function()} onClose - Function to be called when transaction is committed or rolled back. - * @param {function(bookmarks: Bookmarks)} onBookmarks callback invoked when new bookmark is produced. - * @param {function()} onConnection - Function to be called when a connection is obtained to ensure the conneciton + * @param {object} args + * @param {ConnectionHolder} args.connectionHolder - the connection holder to get connection from. + * @param {function()} args.onClose - Function to be called when transaction is committed or rolled back. + * @param {function(bookmarks: Bookmarks)} args.onBookmarks callback invoked when new bookmark is produced. + * @param {function()} args.onConnection - Function to be called when a connection is obtained to ensure the conneciton * is not yet released. - * @param {boolean} reactive whether this transaction generates reactive streams - * @param {number} fetchSize - the record fetch size in each pulling batch. - * @param {string} impersonatedUser - The name of the user which should be impersonated for the duration of the session. - * @param {number} highRecordWatermark - The high watermark for the record buffer. - * @param {number} lowRecordWatermark - The low watermark for the record buffer. + * @param {boolean} args.reactive whether this transaction generates reactive streams + * @param {number} args.fetchSize - the record fetch size in each pulling batch. + * @param {string} args.impersonatedUser - The name of the user which should be impersonated for the duration of the session. + * @param {number} args.highRecordWatermark - The high watermark for the record buffer. + * @param {number} args.lowRecordWatermark - The low watermark for the record buffer. + * @param {NotificationFilter} args.notificationFilter - The notification filter used for this transaction. */ constructor ({ connectionHolder, @@ -84,7 +88,8 @@ class Transaction { fetchSize, impersonatedUser, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }: { connectionHolder: ConnectionHolder onClose: () => void @@ -95,6 +100,7 @@ class Transaction { impersonatedUser?: string highRecordWatermark: number lowRecordWatermark: number + notificationFilter?: NotificationFilter }) { this._connectionHolder = connectionHolder this._reactive = reactive @@ -110,6 +116,7 @@ class Transaction { this._lowRecordWatermak = lowRecordWatermark this._highRecordWatermark = highRecordWatermark this._bookmarks = Bookmarks.empty() + this._notificationFilter = notificationFilter this._acceptActive = () => { } // satisfy DenoJS this._activePromise = new Promise((resolve, reject) => { this._acceptActive = resolve @@ -138,6 +145,7 @@ class Transaction { mode: this._connectionHolder.mode(), database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, + notificationFilter: this._notificationFilter, beforeError: (error: Error) => { if (events != null) { events.onError(error) diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index d78148a04..1d5f0e5ed 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -17,7 +17,7 @@ * limitations under the License. */ /* eslint-disable @typescript-eslint/promise-function-async */ -import { bookmarkManager, ConnectionProvider, EagerResult, newError, Result, ResultSummary, ServerInfo, Session } from '../src' +import { bookmarkManager, ConnectionProvider, EagerResult, newError, NotificationFilter, Result, ResultSummary, ServerInfo, Session } from '../src' import Driver, { QueryConfig, READ, routing } from '../src/driver' import { Bookmarks } from '../src/internal/bookmarks' import { Logger } from '../src/internal/logger' @@ -26,6 +26,7 @@ import { ConfiguredCustomResolver } from '../src/internal/resolver' import { LogLevel } from '../src/types' import resultTransformers from '../src/result-transformers' import Record, { Dict } from '../src/record' +import { validNotificationFilters } from './utils/notification-filters.fixtures' describe('Driver', () => { let driver: Driver | null @@ -165,6 +166,30 @@ describe('Driver', () => { } }) }) + + describe('when set config.notificationFilters', () => { + it.each( + validNotificationFilters() + )('should send valid "notificationFilters" to the session', async (notificationFilter?: NotificationFilter) => { + const driver = new Driver( + META_INFO, + { ...CONFIG }, + mockCreateConnectonProvider(connectionProvider), + createSession + ) + + const session = driver.session({ notificationFilter }) + + try { + expect(createSession).toBeCalledWith(expect.objectContaining({ + notificationFilter + })) + } finally { + await session.close() + await driver.close() + } + }) + }) }) it.each([ diff --git a/packages/core/test/session.test.ts b/packages/core/test/session.test.ts index 58795e4f4..2e2002c16 100644 --- a/packages/core/test/session.test.ts +++ b/packages/core/test/session.test.ts @@ -16,11 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ConnectionProvider, Session, Connection, TransactionPromise, Transaction, BookmarkManager, bookmarkManager } from '../src' +import { ConnectionProvider, Session, Connection, TransactionPromise, Transaction, BookmarkManager, bookmarkManager, NotificationFilter } from '../src' import { bookmarks } from '../src/internal' import { ACCESS_MODE_READ, FETCH_ALL } from '../src/internal/constants' import ManagedTransaction from '../src/transaction-managed' import FakeConnection from './utils/connection.fake' +import { validNotificationFilters } from './utils/notification-filters.fixtures' describe('session', () => { const systemBookmarks = ['sys:bm01', 'sys:bm02'] @@ -486,6 +487,27 @@ describe('session', () => { expect(updateBookmarksSpy).not.toBeCalled() }) + + it.each( + validNotificationFilters() + )('should call run query with notificationFilters', async (notificationFilter?: NotificationFilter) => { + const connection = mockBeginWithSuccess(newFakeConnection()) + + const { session } = setupSession({ + connection, + beginTx: false, + database: 'neo4j', + notificationFilter + }) + + await session.beginTransaction() + + expect(connection.seenBeginTransaction[0][0]).toEqual( + expect.objectContaining({ + notificationFilter + }) + ) + }) }) describe.each([ @@ -792,6 +814,27 @@ describe('session', () => { expect(updateBookmarksSpy).not.toBeCalled() }) + + it.each( + validNotificationFilters() + )('should call run query with notificationFilters', async (notificationFilter?: NotificationFilter) => { + const connection = newFakeConnection() + + const { session } = setupSession({ + connection, + beginTx: false, + database: 'neo4j', + notificationFilter + }) + + await session.run('query') + + expect(connection.seenProtocolOptions[0]).toEqual( + expect.objectContaining({ + notificationFilter + }) + ) + }) }) }) @@ -843,7 +886,8 @@ function setupSession ({ fetchSize = 1000, database = '', lastBookmarks = bookmarks.Bookmarks.empty(), - bookmarkManager + bookmarkManager, + notificationFilter }: { connection: Connection beginTx?: boolean @@ -851,6 +895,7 @@ function setupSession ({ lastBookmarks?: bookmarks.Bookmarks database?: string bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }): { session: Session, connectionProvider: ConnectionProvider } { const connectionProvider = new ConnectionProvider() connectionProvider.acquireConnection = jest.fn(async () => await Promise.resolve(connection)) @@ -864,7 +909,8 @@ function setupSession ({ config: {}, reactive: false, bookmarks: lastBookmarks, - bookmarkManager + bookmarkManager, + notificationFilter }) if (beginTx) { diff --git a/packages/core/test/transaction.test.ts b/packages/core/test/transaction.test.ts index d61459ff3..f3cdc8fa9 100644 --- a/packages/core/test/transaction.test.ts +++ b/packages/core/test/transaction.test.ts @@ -17,11 +17,12 @@ * limitations under the License. */ -import { ConnectionProvider, newError, Transaction, TransactionPromise } from '../src' +import { ConnectionProvider, newError, NotificationFilter, Transaction, TransactionPromise } from '../src' import { Bookmarks } from '../src/internal/bookmarks' import { ConnectionHolder } from '../src/internal/connection-holder' import { TxConfig } from '../src/internal/tx-config' import FakeConnection from './utils/connection.fake' +import { validNotificationFilters } from './utils/notification-filters.fixtures' testTx('Transaction', newRegularTransaction) @@ -375,6 +376,25 @@ function testTx (transactionName: string, newTransaction: expect(connection.seenBeginTransaction.length).toEqual(1) expect(connection.seenQueries.length).toEqual(1) }) + + it.each( + validNotificationFilters() + )('should call not run query with notificationFilters', async (notificationFilter?: NotificationFilter) => { + const connection = newFakeConnection() + const tx = newTransaction({ + connection, + notificationFilter + }) + + tx._begin(async () => Bookmarks.empty(), TxConfig.empty()) + + await tx.run('RETURN 1') + expect(connection.seenProtocolOptions[0]).not.toEqual( + expect.objectContaining({ + notificationFilter + }) + ) + }) }) describe('.close()', () => { @@ -467,6 +487,7 @@ type TransactionFactory = (_: { fetchSize?: number highRecordWatermark?: number lowRecordWatermark?: number + notificationFilter?: NotificationFilter }) => T function newTransactionPromise ({ @@ -474,13 +495,15 @@ function newTransactionPromise ({ fetchSize = 1000, highRecordWatermark = 700, lowRecordWatermark = 300, - errorResolvingConnection = undefined + errorResolvingConnection = undefined, + notificationFilter }: { connection?: FakeConnection fetchSize?: number highRecordWatermark?: number lowRecordWatermark?: number errorResolvingConnection?: Error + notificationFilter?: NotificationFilter }): TransactionPromise { const connectionProvider = new ConnectionProvider() // @ts-expect-error @@ -504,7 +527,8 @@ function newTransactionPromise ({ fetchSize, impersonatedUser: '', highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }) return transaction @@ -514,12 +538,14 @@ function newRegularTransaction ({ connection, fetchSize = 1000, highRecordWatermark = 700, - lowRecordWatermark = 300 + lowRecordWatermark = 300, + notificationFilter }: { connection: FakeConnection fetchSize?: number highRecordWatermark?: number lowRecordWatermark?: number + notificationFilter?: NotificationFilter }): Transaction { const connectionProvider = new ConnectionProvider() connectionProvider.acquireConnection = async () => await Promise.resolve(connection) @@ -537,7 +563,8 @@ function newRegularTransaction ({ fetchSize, impersonatedUser: '', highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }) return transaction diff --git a/packages/core/test/utils/notification-filters.fixtures.ts b/packages/core/test/utils/notification-filters.fixtures.ts new file mode 100644 index 000000000..1c0f89cbc --- /dev/null +++ b/packages/core/test/utils/notification-filters.fixtures.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NotificationFilter, notificationFilterDisabledCategory, notificationFilterMinimumSeverityLevel } from '../../src' + +export function validNotificationFilters (): Array { + return [ + undefined, + { + minimumSeverityLevel: 'OFF' + }, + { + minimumSeverityLevel: notificationFilterMinimumSeverityLevel.INFORMATION + }, + { + disabledCategories: [] + }, + { + disabledCategories: ['DEPRECATION'] + }, + { + disabledCategories: [notificationFilterDisabledCategory.GENERIC, notificationFilterDisabledCategory.PERFORMANCE] + }, + { + disabledCategories: [notificationFilterDisabledCategory.GENERIC, 'PERFORMANCE'] + }, + { + minimumSeverityLevel: notificationFilterMinimumSeverityLevel.INFORMATION, + disabledCategories: [notificationFilterDisabledCategory.GENERIC, notificationFilterDisabledCategory.PERFORMANCE] + } + ] +} diff --git a/packages/neo4j-driver/src/driver.js b/packages/neo4j-driver/src/driver.js index 491c43f02..f91638af4 100644 --- a/packages/neo4j-driver/src/driver.js +++ b/packages/neo4j-driver/src/driver.js @@ -58,7 +58,8 @@ class Driver extends CoreDriver { database = '', fetchSize, impersonatedUser, - bookmarkManager + bookmarkManager, + notificationFilter } = {}) { return new RxSession({ session: this._newSession({ @@ -68,7 +69,8 @@ class Driver extends CoreDriver { impersonatedUser, reactive: false, fetchSize: validateFetchSizeValue(fetchSize, this._config.fetchSize), - bookmarkManager + bookmarkManager, + notificationFilter }), config: this._config }) From 907cddfe9507e9e171b780e2af0ecb2767bd89b0 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 2 Mar 2023 14:15:27 +0100 Subject: [PATCH 07/15] Sync denojs --- packages/neo4j-driver-deno/lib/core/driver.ts | 80 ++++++++++++++++++- .../lib/core/notification-filter.ts | 7 +- .../neo4j-driver-deno/lib/core/session.ts | 14 +++- .../lib/core/transaction-promise.ts | 24 +++--- .../neo4j-driver-deno/lib/core/transaction.ts | 28 ++++--- 5 files changed, 125 insertions(+), 28 deletions(-) diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index d79341a71..9e85c6c57 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -46,6 +46,7 @@ import EagerResult from './result-eager.ts' import resultTransformers, { ResultTransformer } from './result-transformers.ts' import QueryExecutor from './internal/query-executor.ts' import { newError } from './error.ts' +import NotificationFilter from './notification-filter.ts' const DEFAULT_MAX_CONNECTION_LIFETIME: number = 60 * 60 * 1000 // 1 hour @@ -94,6 +95,7 @@ type CreateSession = (args: { fetchSize: number impersonatedUser?: string bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }) => Session type CreateQueryExecutor = (createSession: (config: { database?: string, bookmarkManager?: BookmarkManager }) => Session) => QueryExecutor @@ -117,6 +119,7 @@ class SessionConfig { impersonatedUser?: string fetchSize?: number bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter /** * @constructor @@ -235,6 +238,70 @@ class SessionConfig { * @since 5.0 */ this.bookmarkManager = undefined + + /** + * Configure filter for {@link Notification} objects returned in {@link ResultSummary#notifications}. + * + * This configuration enables filter notifications by: + * + * * the minimum severity level ({@link NotificationFilterMinimumSeverityLevel}) + * * disabling notification categories ({@link NotificationFilterDisabledCategory}) + * + * + * Disabling notifications can be done by defining the minimum severity level to 'OFF'. + * Default values can be use by omitting the configuration. + * + * @example + * // enabling warning notification, but disabling `HINT` and `DEPRECATION` notifications. + * const session = driver.session({ + * database: 'neo4j', + * notificationFilter: { + * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING, // or 'WARNING + * disabledCategories: [ + * neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT' + * neo4j.notificationFilterDisabledCategory.DEPRECATION // or 'DEPRECATION' + * ] + * } + * }) + * + * @example + * // disabling notifications for a session + * const session = driver.session({ + * database: 'neo4j', + * notificationFilter: { + * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.OFF // or 'OFF' + * } + * }) + * + * @example + * // using default values configured in the driver + * const sessionWithDefaultValues = driver.session({ database: 'neo4j' }) + * // or driver.session({ database: 'neo4j', notificationFilter: undefined }) + * + * // using default minimum severity level, but disabling 'HINT' and 'UNRECOGNIZED' + * // notification categories + * const sessionWithDefaultSeverityLevel = driver.session({ + * database: 'neo4j', + * notificationFilters: { + * disabledCategories: [ + * neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT' + * neo4j.notificationFilterDisabledCategory.UNRECOGNIZED // or 'UNRECOGNIZED' + * ] + * } + * }) + * + * // using default disabled categories, but configuring minimum severity level to 'WARNING' + * const sessionWithDefaultSeverityLevel = driver.session({ + * database: 'neo4j', + * notificationFilters: { + * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING // or 'WARNING' + * } + * }) + * + * @type {NotificationFilter|undefined} + * @since 5.7 + */ + this.notificationFilter = undefined } } @@ -627,7 +694,8 @@ class Driver { database = '', impersonatedUser, fetchSize, - bookmarkManager + bookmarkManager, + notificationFilter }: SessionConfig = {}): Session { return this._newSession({ defaultAccessMode, @@ -637,7 +705,8 @@ class Driver { impersonatedUser, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion fetchSize: validateFetchSizeValue(fetchSize, this._config.fetchSize!), - bookmarkManager + bookmarkManager, + notificationFilter }) } @@ -675,7 +744,8 @@ class Driver { reactive, impersonatedUser, fetchSize, - bookmarkManager + bookmarkManager, + notificationFilter }: { defaultAccessMode: SessionMode bookmarkOrBookmarks?: string | string[] @@ -684,6 +754,7 @@ class Driver { impersonatedUser?: string fetchSize: number bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }): Session { const sessionMode = Session._validateSessionMode(defaultAccessMode) const connectionProvider = this._getOrCreateConnectionProvider() @@ -700,7 +771,8 @@ class Driver { reactive, impersonatedUser, fetchSize, - bookmarkManager + bookmarkManager, + notificationFilter }) } diff --git a/packages/neo4j-driver-deno/lib/core/notification-filter.ts b/packages/neo4j-driver-deno/lib/core/notification-filter.ts index c60b42d0c..6f02d926c 100644 --- a/packages/neo4j-driver-deno/lib/core/notification-filter.ts +++ b/packages/neo4j-driver-deno/lib/core/notification-filter.ts @@ -27,7 +27,7 @@ type EnumRecord = { [key in T]: key } type NotificationFilterMinimumSeverityLevel = ExcludeUnknown | OFF /** - * @typedef {'WARNING' | 'INFORMATION' | 'DISABLED'} NotificationFilterMinimumSeverityLevel + * @typedef {'WARNING' | 'INFORMATION' | 'OFF'} NotificationFilterMinimumSeverityLevel */ /** * Constants that represents the minimum Severity level in the {@link NotificationFilter} @@ -57,7 +57,10 @@ const notificationFilterDisabledCategory: EnumRecord any | undefined | Promise | Promise type TransactionWork = (tx: Transaction) => Promise | T @@ -72,6 +73,7 @@ class Session { private readonly _highRecordWatermark: number private readonly _results: Result[] private readonly _bookmarkManager?: BookmarkManager + private readonly _notificationFilter?: NotificationFilter /** * @constructor * @protected @@ -84,6 +86,7 @@ class Session { * @param {boolean} args.reactive - Whether this session should create reactive streams * @param {number} args.fetchSize - Defines how many records is pulled in each pulling batch * @param {string} args.impersonatedUser - The username which the user wants to impersonate for the duration of the session. + * @param {NotificationFilter} args.notificationFilter - The notification filter used for this session. */ constructor ({ mode, @@ -94,7 +97,8 @@ class Session { reactive, fetchSize, impersonatedUser, - bookmarkManager + bookmarkManager, + notificationFilter }: { mode: SessionMode connectionProvider: ConnectionProvider @@ -105,6 +109,7 @@ class Session { fetchSize: number impersonatedUser?: string bookmarkManager?: BookmarkManager + notificationFilter?: NotificationFilter }) { this._mode = mode this._database = database @@ -142,6 +147,7 @@ class Session { this._highRecordWatermark = calculatedWatermaks.high this._results = [] this._bookmarkManager = bookmarkManager + this._notificationFilter = notificationFilter } /** @@ -181,7 +187,8 @@ class Session { reactive: this._reactive, fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, - highRecordWatermark: this._highRecordWatermark + highRecordWatermark: this._highRecordWatermark, + notificationFilter: this._notificationFilter }) }) this._results.push(result) @@ -298,7 +305,8 @@ class Session { reactive: this._reactive, fetchSize: this._fetchSize, lowRecordWatermark: this._lowRecordWatermark, - highRecordWatermark: this._highRecordWatermark + highRecordWatermark: this._highRecordWatermark, + notificationFilter: this._notificationFilter }) tx._begin(() => this._bookmarks(), txConfig) return tx diff --git a/packages/neo4j-driver-deno/lib/core/transaction-promise.ts b/packages/neo4j-driver-deno/lib/core/transaction-promise.ts index 157588735..b5a72a403 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction-promise.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction-promise.ts @@ -26,6 +26,7 @@ import { import { Bookmarks } from './internal/bookmarks.ts' import { TxConfig } from './internal/tx-config.ts' +import NotificationFilter from './notification-filter.ts' /** * Represents a {@link Promise} object and a {@link Transaction} object. @@ -47,14 +48,16 @@ class TransactionPromise extends Transaction implements Promise { /** * @constructor - * @param {ConnectionHolder} connectionHolder - the connection holder to get connection from. - * @param {function()} onClose - Function to be called when transaction is committed or rolled back. - * @param {function(bookmarks: Bookmarks)} onBookmarks callback invoked when new bookmark is produced. - * @param {function()} onConnection - Function to be called when a connection is obtained to ensure the connection + * @param {object} args + * @param {ConnectionHolder} args.connectionHolder - the connection holder to get connection from. + * @param {function()} args.onClose - Function to be called when transaction is committed or rolled back. + * @param {function(bookmarks: Bookmarks)} args.onBookmarks callback invoked when new bookmark is produced. + * @param {function()} args.onConnection - Function to be called when a connection is obtained to ensure the connection * is not yet released. - * @param {boolean} reactive whether this transaction generates reactive streams - * @param {number} fetchSize - the record fetch size in each pulling batch. - * @param {string} impersonatedUser - The name of the user which should be impersonated for the duration of the session. + * @param {boolean} args.reactive whether this transaction generates reactive streams + * @param {number} args.fetchSize - the record fetch size in each pulling batch. + * @param {string} args.impersonatedUser - The name of the user which should be impersonated for the duration of the session. + * @param {NotificationFilter} args.notificationFilter - The notification filter used for this transaction. */ constructor ({ connectionHolder, @@ -65,7 +68,8 @@ class TransactionPromise extends Transaction implements Promise { fetchSize, impersonatedUser, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }: { connectionHolder: ConnectionHolder onClose: () => void @@ -76,6 +80,7 @@ class TransactionPromise extends Transaction implements Promise { impersonatedUser?: string highRecordWatermark: number lowRecordWatermark: number + notificationFilter?: NotificationFilter }) { super({ connectionHolder, @@ -86,7 +91,8 @@ class TransactionPromise extends Transaction implements Promise { fetchSize, impersonatedUser, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }) } diff --git a/packages/neo4j-driver-deno/lib/core/transaction.ts b/packages/neo4j-driver-deno/lib/core/transaction.ts index 5e4b98b96..91c786de2 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction.ts @@ -38,6 +38,7 @@ import { newError } from './error.ts' import Result from './result.ts' import { Query } from './types.ts' import { Dict } from './record.ts' +import NotificationFilter from './notification-filter.ts' /** * Represents a transaction in the Neo4j database. @@ -61,19 +62,22 @@ class Transaction { private _bookmarks: Bookmarks private readonly _activePromise: Promise private _acceptActive: () => void + private readonly _notificationFilter?: NotificationFilter /** * @constructor - * @param {ConnectionHolder} connectionHolder - the connection holder to get connection from. - * @param {function()} onClose - Function to be called when transaction is committed or rolled back. - * @param {function(bookmarks: Bookmarks)} onBookmarks callback invoked when new bookmark is produced. - * @param {function()} onConnection - Function to be called when a connection is obtained to ensure the conneciton + * @param {object} args + * @param {ConnectionHolder} args.connectionHolder - the connection holder to get connection from. + * @param {function()} args.onClose - Function to be called when transaction is committed or rolled back. + * @param {function(bookmarks: Bookmarks)} args.onBookmarks callback invoked when new bookmark is produced. + * @param {function()} args.onConnection - Function to be called when a connection is obtained to ensure the conneciton * is not yet released. - * @param {boolean} reactive whether this transaction generates reactive streams - * @param {number} fetchSize - the record fetch size in each pulling batch. - * @param {string} impersonatedUser - The name of the user which should be impersonated for the duration of the session. - * @param {number} highRecordWatermark - The high watermark for the record buffer. - * @param {number} lowRecordWatermark - The low watermark for the record buffer. + * @param {boolean} args.reactive whether this transaction generates reactive streams + * @param {number} args.fetchSize - the record fetch size in each pulling batch. + * @param {string} args.impersonatedUser - The name of the user which should be impersonated for the duration of the session. + * @param {number} args.highRecordWatermark - The high watermark for the record buffer. + * @param {number} args.lowRecordWatermark - The low watermark for the record buffer. + * @param {NotificationFilter} args.notificationFilter - The notification filter used for this transaction. */ constructor ({ connectionHolder, @@ -84,7 +88,8 @@ class Transaction { fetchSize, impersonatedUser, highRecordWatermark, - lowRecordWatermark + lowRecordWatermark, + notificationFilter }: { connectionHolder: ConnectionHolder onClose: () => void @@ -95,6 +100,7 @@ class Transaction { impersonatedUser?: string highRecordWatermark: number lowRecordWatermark: number + notificationFilter?: NotificationFilter }) { this._connectionHolder = connectionHolder this._reactive = reactive @@ -110,6 +116,7 @@ class Transaction { this._lowRecordWatermak = lowRecordWatermark this._highRecordWatermark = highRecordWatermark this._bookmarks = Bookmarks.empty() + this._notificationFilter = notificationFilter this._acceptActive = () => { } // satisfy DenoJS this._activePromise = new Promise((resolve, reject) => { this._acceptActive = resolve @@ -138,6 +145,7 @@ class Transaction { mode: this._connectionHolder.mode(), database: this._connectionHolder.database(), impersonatedUser: this._impersonatedUser, + notificationFilter: this._notificationFilter, beforeError: (error: Error) => { if (events != null) { events.onError(error) From 866baa4ec5435111d434c13de79e36a77d73dbab Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 2 Mar 2023 14:21:48 +0100 Subject: [PATCH 08/15] Add notificationFilter to Driver constructor --- packages/core/src/driver.ts | 1 + packages/core/test/driver.test.ts | 27 +++++++++++++++++++ packages/neo4j-driver-deno/lib/core/driver.ts | 1 + 3 files changed, 29 insertions(+) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index 5c916bb19..bf9f6eedf 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -105,6 +105,7 @@ interface DriverConfig { trust?: TrustStrategy fetchSize?: number logging?: LoggingConfig + notificationFilter?: NotificationFilter } /** diff --git a/packages/core/test/driver.test.ts b/packages/core/test/driver.test.ts index 1d5f0e5ed..44de0c37c 100644 --- a/packages/core/test/driver.test.ts +++ b/packages/core/test/driver.test.ts @@ -536,6 +536,33 @@ describe('Driver', () => { }) }) + describe('constructor', () => { + describe('when set config.notificationFilters', () => { + it.each( + validNotificationFilters() + )('should send valid "notificationFilters" to the connection provider', async (notificationFilter?: NotificationFilter) => { + const createConnectionProviderMock = jest.fn(mockCreateConnectonProvider(connectionProvider)) + const driver = new Driver( + META_INFO, + { notificationFilter }, + createConnectionProviderMock, + createSession + ) + + driver._getOrCreateConnectionProvider() + + expect(createConnectionProviderMock).toHaveBeenCalledWith( + expect.any(Number), + expect.objectContaining({ notificationFilter }), + expect.any(Object), + expect.any(Object) + ) + + await driver.close() + }) + }) + }) + function mockCreateConnectonProvider (connectionProvider: ConnectionProvider) { return ( id: number, diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 9e85c6c57..3b69802d0 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -105,6 +105,7 @@ interface DriverConfig { trust?: TrustStrategy fetchSize?: number logging?: LoggingConfig + notificationFilter?: NotificationFilter } /** From 5bd99a3dcc7035d440f2244e87eac71b8d2e6d58 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 2 Mar 2023 15:17:20 +0100 Subject: [PATCH 09/15] Add to neo4j.driver factory --- packages/neo4j-driver-lite/src/index.ts | 6 +++++- packages/neo4j-driver/src/index.js | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/neo4j-driver-lite/src/index.ts b/packages/neo4j-driver-lite/src/index.ts index 089f7c2c8..e64639855 100644 --- a/packages/neo4j-driver-lite/src/index.ts +++ b/packages/neo4j-driver-lite/src/index.ts @@ -237,7 +237,11 @@ const { * return ['127.0.0.1:8888', 'fallback.db.com:7687']; * }, * - * // Optionally override the default user agent name. + * // Configure filter for Notification objects returned in ResultSummary#notifications. + * // See SessionConfig#notificationFilter for usage instructions. + * notificationFilter: undefined, + * + * // Optionally override the default user agent name. * userAgent: USER_AGENT * } * diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js index 820d678ec..954bff531 100644 --- a/packages/neo4j-driver/src/index.js +++ b/packages/neo4j-driver/src/index.js @@ -212,7 +212,11 @@ const { * return ['127.0.0.1:8888', 'fallback.db.com:7687']; * }, * - * // Optionally override the default user agent name. + * // Configure filter for Notification objects returned in ResultSummary#notifications. + * // See SessionConfig#notificationFilter for usage instructions. + * notificationFilter: undefined, + * + * // Optionally override the default user agent name. * userAgent: USER_AGENT * } * From 18130dd4cfe6fda1ce8d7b2032583242a2d8766c Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 2 Mar 2023 15:18:04 +0100 Subject: [PATCH 10/15] sync deno --- packages/neo4j-driver-deno/lib/mod.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/neo4j-driver-deno/lib/mod.ts b/packages/neo4j-driver-deno/lib/mod.ts index eaab180b7..633060a56 100644 --- a/packages/neo4j-driver-deno/lib/mod.ts +++ b/packages/neo4j-driver-deno/lib/mod.ts @@ -238,7 +238,11 @@ const { * return ['127.0.0.1:8888', 'fallback.db.com:7687']; * }, * - * // Optionally override the default user agent name. + * // Configure filter for Notification objects returned in ResultSummary#notifications. + * // See SessionConfig#notificationFilter for usage instructions. + * notificationFilter: undefined, + * + * // Optionally override the default user agent name. * userAgent: USER_AGENT * } * From a7297856450599fafecfef840c1372afa21f5d14 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Wed, 15 Mar 2023 15:58:04 +0100 Subject: [PATCH 11/15] Raise exception when the protocol doesn't support notificationFilter Also fix naming through the code --- .../src/bolt/bolt-protocol-util.js | 24 +++- .../src/bolt/bolt-protocol-v1.js | 16 ++- .../src/bolt/bolt-protocol-v3.js | 13 +- .../src/bolt/bolt-protocol-v4x0.js | 8 +- .../src/bolt/bolt-protocol-v4x1.js | 6 +- .../src/bolt/bolt-protocol-v4x3.js | 17 ++- .../src/bolt/bolt-protocol-v4x4.js | 9 ++ .../src/bolt/bolt-protocol-v5x0.js | 17 ++- .../src/bolt/bolt-protocol-v5x1.js | 17 ++- .../test/bolt/behaviour/index.js | 20 ++++ .../bolt/behaviour/notification-filter.js | 112 ++++++++++++++++++ .../test/bolt/bolt-protocol-v1.test.js | 11 ++ .../test/bolt/bolt-protocol-v2.test.js | 11 ++ .../test/bolt/bolt-protocol-v3.test.js | 11 ++ .../test/bolt/bolt-protocol-v4x0.test.js | 11 ++ .../test/bolt/bolt-protocol-v4x1.test.js | 11 ++ .../test/bolt/bolt-protocol-v4x2.test.js | 11 ++ .../test/bolt/bolt-protocol-v4x3.test.js | 11 ++ .../test/bolt/bolt-protocol-v4x4.test.js | 11 ++ .../test/bolt/bolt-protocol-v5x0.test.js | 11 ++ .../test/bolt/bolt-protocol-v5x1.test.js | 11 ++ packages/core/src/driver.ts | 4 +- packages/core/test/session.test.ts | 4 +- packages/core/test/transaction.test.ts | 2 +- .../bolt/bolt-protocol-util.js | 24 +++- .../bolt-connection/bolt/bolt-protocol-v1.js | 16 ++- .../bolt-connection/bolt/bolt-protocol-v3.js | 13 +- .../bolt/bolt-protocol-v4x0.js | 8 +- .../bolt/bolt-protocol-v4x1.js | 6 +- .../bolt/bolt-protocol-v4x3.js | 17 ++- .../bolt/bolt-protocol-v4x4.js | 9 ++ .../bolt/bolt-protocol-v5x0.js | 17 ++- .../bolt/bolt-protocol-v5x1.js | 17 ++- packages/neo4j-driver-deno/lib/core/driver.ts | 4 +- 34 files changed, 451 insertions(+), 59 deletions(-) create mode 100644 packages/bolt-connection/test/bolt/behaviour/index.js create mode 100644 packages/bolt-connection/test/bolt/behaviour/notification-filter.js diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-util.js b/packages/bolt-connection/src/bolt/bolt-protocol-util.js index 8a6ef2e4d..0073752ec 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-util.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-util.js @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { newError } from 'neo4j-driver-core' +import { newError, json } from 'neo4j-driver-core' // eslint-disable-next-line no-unused-vars import { ResultStreamObserver } from './stream-observers' @@ -79,4 +79,24 @@ function assertImpersonatedUserIsEmpty (impersonatedUser, onProtocolError = () = } } -export { assertDatabaseIsEmpty, assertTxConfigIsEmpty, assertImpersonatedUserIsEmpty } +/** + * Asserts that the passed-in notificationFilter is empty + * @param {NotificationFilter} notificationFilter + * @param {function (err:Error)} onProtocolError Called when it does have notificationFilter user set + * @param {any} observer + */ +function assertNotificationFilterIsEmpty (notificationFilter, onProtocolError = () => {}, observer) { + if (notificationFilter !== undefined) { + const error = newError( + 'Driver is connected to a database that does not support user notification filters. ' + + 'Please upgrade to Neo4j 5.7.0 or later in order to use this functionality. ' + + `Trying to set notifications to ${json.stringify(notificationFilter)}.` + ) + // unsupported API was used, consider this a fatal error for the current connection + onProtocolError(error.message) + observer.onError(error) + throw error + } +} + +export { assertDatabaseIsEmpty, assertTxConfigIsEmpty, assertImpersonatedUserIsEmpty, assertNotificationFilterIsEmpty } diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js index 24d00c5fa..35c8b5dfa 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v1.js @@ -19,7 +19,8 @@ import { assertDatabaseIsEmpty, assertTxConfigIsEmpty, - assertImpersonatedUserIsEmpty + assertImpersonatedUserIsEmpty, + assertNotificationFilterIsEmpty } from './bolt-protocol-util' // eslint-disable-next-line no-unused-vars import { Chunker } from '../channel' @@ -169,16 +170,20 @@ export default class BoltProtocol { * @param {Object} param * @param {string} param.userAgent the user agent. * @param {Object} param.authToken the authentication token. + * @param {NotificationFilter} param.notificationFilter the notification filter. * @param {function(err: Error)} param.onError the callback to invoke on error. * @param {function()} param.onComplete the callback to invoke on completion. * @returns {StreamObserver} the stream observer that monitors the corresponding server response. */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write(RequestMessage.init(userAgent, authToken), observer, true) return observer @@ -254,6 +259,7 @@ export default class BoltProtocol { * @param {string} param.database the target database name. * @param {string} param.mode the access mode. * @param {string} param.impersonatedUser the impersonated user + * @param {NotificationFilter} param.notificationFilter the notification filter. * @param {function(err: Error)} param.beforeError the callback to invoke before handling the error. * @param {function(err: Error)} param.afterError the callback to invoke after handling the error. * @param {function()} param.beforeComplete the callback to invoke before handling the completion. @@ -266,6 +272,7 @@ export default class BoltProtocol { database, mode, impersonatedUser, + notificationFilter, beforeError, afterError, beforeComplete, @@ -280,6 +287,7 @@ export default class BoltProtocol { database, mode, impersonatedUser, + notificationFilter, beforeError, afterError, beforeComplete, @@ -362,6 +370,7 @@ export default class BoltProtocol { * @param {TxConfig} param.txConfig the transaction configuration. * @param {string} param.database the target database name. * @param {string} param.impersonatedUser the impersonated user + * @param {NotificationFilter} param.notificationFilter the notification filter. * @param {string} param.mode the access mode. * @param {function(keys: string[])} param.beforeKeys the callback to invoke before handling the keys. * @param {function(keys: string[])} param.afterKeys the callback to invoke after handling the keys. @@ -381,6 +390,7 @@ export default class BoltProtocol { database, mode, impersonatedUser, + notificationFilter, beforeKeys, afterKeys, beforeError, @@ -410,6 +420,8 @@ export default class BoltProtocol { assertDatabaseIsEmpty(database, this._onProtocolError, observer) // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write(RequestMessage.run(query, parameters), observer, false) this.write(RequestMessage.pullAll(), observer, flush) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js index 523f6b229..783e35080 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v3.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v3.js @@ -18,7 +18,7 @@ */ import BoltProtocolV2 from './bolt-protocol-v2' import RequestMessage from './request-message' -import { assertDatabaseIsEmpty, assertImpersonatedUserIsEmpty } from './bolt-protocol-util' +import { assertDatabaseIsEmpty, assertImpersonatedUserIsEmpty, assertNotificationFilterIsEmpty } from './bolt-protocol-util' import { StreamObserver, LoginObserver, @@ -69,12 +69,15 @@ export default class BoltProtocol extends BoltProtocolV2 { return metadata } - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write(RequestMessage.hello(userAgent, authToken), observer, true) return observer @@ -89,6 +92,7 @@ export default class BoltProtocol extends BoltProtocolV2 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeError, afterError, @@ -108,6 +112,8 @@ export default class BoltProtocol extends BoltProtocolV2 { assertDatabaseIsEmpty(database, this._onProtocolError, observer) // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write( RequestMessage.begin({ bookmarks, txConfig, mode }), @@ -166,6 +172,7 @@ export default class BoltProtocol extends BoltProtocolV2 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeKeys, afterKeys, @@ -194,6 +201,8 @@ export default class BoltProtocol extends BoltProtocolV2 { assertDatabaseIsEmpty(database, this._onProtocolError, observer) // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write( RequestMessage.runWithMetadata(query, parameters, { diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js index 2dfc1f541..cb4438ec0 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x0.js @@ -18,7 +18,7 @@ */ import BoltProtocolV3 from './bolt-protocol-v3' import RequestMessage from './request-message' -import { assertImpersonatedUserIsEmpty } from './bolt-protocol-util' +import { assertImpersonatedUserIsEmpty, assertNotificationFilterIsEmpty } from './bolt-protocol-util' import { ResultStreamObserver, ProcedureRouteObserver @@ -56,6 +56,7 @@ export default class BoltProtocol extends BoltProtocolV3 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeError, afterError, @@ -73,6 +74,8 @@ export default class BoltProtocol extends BoltProtocolV3 { // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write( RequestMessage.begin({ bookmarks, txConfig, database, mode }), @@ -91,6 +94,7 @@ export default class BoltProtocol extends BoltProtocolV3 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeKeys, afterKeys, @@ -123,6 +127,8 @@ export default class BoltProtocol extends BoltProtocolV3 { // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) const flushRun = reactive this.write( diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js index 7c79f6e35..d507241d2 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x1.js @@ -20,6 +20,7 @@ import BoltProtocolV4 from './bolt-protocol-v4x0' import RequestMessage from './request-message' import { LoginObserver } from './stream-observers' import { internal } from 'neo4j-driver-core' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util' import transformersFactories from './bolt-protocol-v4x1.transformer' import Transformer from './transformer' @@ -72,12 +73,15 @@ export default class BoltProtocol extends BoltProtocolV4 { return this._transformer } - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello(userAgent, authToken, this._serversideRouting), observer, diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js index 2efedb1af..291c348a8 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x3.js @@ -19,6 +19,7 @@ import BoltProtocolV42 from './bolt-protocol-v4x2' import RequestMessage from './request-message' import { LoginObserver, RouteObserver } from './stream-observers' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util' import transformersFactories from './bolt-protocol-v4x3.transformer' import utcTransformersFactories from './bolt-protocol-v5x0.utc.transformer' @@ -80,14 +81,15 @@ export default class BoltProtocol extends BoltProtocolV42 { /** * Initialize a connection with the server * - * @param {Object} param0 The params - * @param {string} param0.userAgent The user agent - * @param {any} param0.authToken The auth token - * @param {function(error)} param0.onError On error callback - * @param {function(onComplte)} param0.onComplete On complete callback + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filter. + * @param {function(error)} args.onError On error callback + * @param {function(onComplte)} args.onComplete On complete callback * @returns {LoginObserver} The Login observer */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => { @@ -98,6 +100,9 @@ export default class BoltProtocol extends BoltProtocolV42 { } }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello(userAgent, authToken, this._serversideRouting, ['utc']), observer, diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js b/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js index bd2c94228..2e8e99c9d 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v4x4.js @@ -21,6 +21,7 @@ import BoltProtocolV43 from './bolt-protocol-v4x3' import { internal } from 'neo4j-driver-core' import RequestMessage from './request-message' import { RouteObserver, ResultStreamObserver } from './stream-observers' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util' import transformersFactories from './bolt-protocol-v4x4.transformer' import utcTransformersFactories from './bolt-protocol-v5x0.utc.transformer' @@ -87,6 +88,7 @@ export default class BoltProtocol extends BoltProtocolV43 { database, mode, impersonatedUser, + notificationFilter, beforeKeys, afterKeys, beforeError, @@ -116,6 +118,9 @@ export default class BoltProtocol extends BoltProtocolV43 { lowRecordWatermark }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + const flushRun = reactive this.write( RequestMessage.runWithMetadata(query, parameters, { @@ -142,6 +147,7 @@ export default class BoltProtocol extends BoltProtocolV43 { database, mode, impersonatedUser, + notificationFilter, beforeError, afterError, beforeComplete, @@ -156,6 +162,9 @@ export default class BoltProtocol extends BoltProtocolV43 { }) observer.prepareToHandleSingleResponse() + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.begin({ bookmarks, txConfig, database, mode, impersonatedUser }), observer, diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js index d4834f4b7..85000baa5 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x0.js @@ -18,6 +18,7 @@ */ import BoltProtocolV44 from './bolt-protocol-v4x4' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util' import transformersFactories from './bolt-protocol-v5x0.transformer' import Transformer from './transformer' import RequestMessage from './request-message' @@ -44,19 +45,23 @@ export default class BoltProtocol extends BoltProtocolV44 { /** * Initialize a connection with the server * - * @param {Object} param0 The params - * @param {string} param0.userAgent The user agent - * @param {any} param0.authToken The auth token - * @param {function(error)} param0.onError On error callback - * @param {function(onComplte)} param0.onComplete On complete callback + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filter. + * @param {function(error)} args.onError On error callback + * @param {function(onComplte)} args.onComplete On complete callback * @returns {LoginObserver} The Login observer */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello(userAgent, authToken, this._serversideRouting), observer, diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js index 1a4861623..7fef848d4 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x1.js @@ -18,6 +18,7 @@ */ import BoltProtocolV5x0 from './bolt-protocol-v5x0' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util' import transformersFactories from './bolt-protocol-v5x1.transformer' import Transformer from './transformer' import RequestMessage from './request-message' @@ -48,14 +49,15 @@ export default class BoltProtocol extends BoltProtocolV5x0 { /** * Initialize a connection with the server * - * @param {Object} param0 The params - * @param {string} param0.userAgent The user agent - * @param {any} param0.authToken The auth token - * @param {function(error)} param0.onError On error callback - * @param {function(onComplte)} param0.onComplete On complete callback + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filters. + * @param {function(error)} args.onError On error callback + * @param {function(onComplete)} args.onComplete On complete callback * @returns {LoginObserver} The Login observer */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const state = {} const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), @@ -65,6 +67,9 @@ export default class BoltProtocol extends BoltProtocolV5x0 { } }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello5x1(userAgent, this._serversideRouting), observer, diff --git a/packages/bolt-connection/test/bolt/behaviour/index.js b/packages/bolt-connection/test/bolt/behaviour/index.js new file mode 100644 index 000000000..6149bfb61 --- /dev/null +++ b/packages/bolt-connection/test/bolt/behaviour/index.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * as notificationFilterBehaviour from './notification-filter' diff --git a/packages/bolt-connection/test/bolt/behaviour/notification-filter.js b/packages/bolt-connection/test/bolt/behaviour/notification-filter.js new file mode 100644 index 000000000..6d7fd8748 --- /dev/null +++ b/packages/bolt-connection/test/bolt/behaviour/notification-filter.js @@ -0,0 +1,112 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import utils from '../../test-utils' + +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + */ +export function shouldNotSupportNotificationFilterOnInitialize (createProtocol) { + describe('initialize', () => { + function verifyInitialize (notificationFilter) { + verifyNotificationFilterNotSupportedError( + createProtocol, + notificationFilter, + protocol => protocol.initialize({ notificationFilter })) + } + + it.each( + notificationFilters() + )('should throw error when notificationFilters is set (%o)', (notificationFilter) => { + verifyInitialize({ + notificationFilter + }) + }) + }) +} + +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + */ +export function shouldNotSupportNotificationFilterOnBeginTransaction (createProtocol) { + describe('beginTransaction', () => { + function verifyBeginTransaction (notificationFilter) { + verifyNotificationFilterNotSupportedError( + createProtocol, + notificationFilter, + protocol => protocol.beginTransaction({ notificationFilter })) + } + + it.each( + notificationFilters() + )('should throw error when notificationFilters is set', (notificationFilter) => { + verifyBeginTransaction(notificationFilter) + }) + }) +} + +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + */ +export function shouldNotSupportNotificationFilterOnRun (createProtocol) { + describe('beginTransaction', () => { + function verifyRun (notificationFilter) { + verifyNotificationFilterNotSupportedError( + createProtocol, + notificationFilter, + protocol => protocol.run('query', {}, { notificationFilter })) + } + + it.each( + notificationFilters() + )('should throw error when notificationFilters is set', (notificationFilter) => { + verifyRun(notificationFilter) + }) + }) +} + +/** + * + * @returns {Array} Return the list of notification features used in test + */ +function notificationFilters () { + return [ + {}, + { minimumSeverityLevel: 'OFF' }, + { minimumSeverityLevel: 'INFORMATION' }, + { disabledCategories: [] }, + { disabledCategories: ['UNRECOGNIZED'] } + ] +} + +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + * @param {string[]} notificationFilter The notification filters. + * @param {function(protocol: BoltProtocolV1)} fn + */ +function verifyNotificationFilterNotSupportedError (createProtocol, notificationFilter, fn) { + const recorder = new utils.MessageRecordingConnection() + const protocol = createProtocol(recorder) + + expect(() => fn(protocol)).toThrowError( + 'Driver is connected to a database that does not support user notification filters. ' + + 'Please upgrade to Neo4j 5.7.0 or later in order to use this functionality. ' + + `Trying to set notifications to ${JSON.stringify(notificationFilter)}.` + ) +} diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js index 1ec6c840e..cae56c8d6 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v1.test.js @@ -38,6 +38,7 @@ import utils from '../test-utils' import { LoginObserver } from '../../src/bolt/stream-observers' import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' +import { notificationFilterBehaviour } from './behaviour' const WRITE = 'WRITE' @@ -635,4 +636,14 @@ describe('#unit BoltProtocolV1', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV1(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js index 70843b424..651fc4c95 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v2.test.js @@ -37,6 +37,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' +import { notificationFilterBehaviour } from './behaviour' describe('#unit BoltProtocolV2', () => { beforeEach(() => { @@ -501,4 +502,14 @@ describe('#unit BoltProtocolV2', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV2(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js index 40adfb621..8902ffe9e 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v3.test.js @@ -43,6 +43,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' +import { notificationFilterBehaviour } from './behaviour' const { bookmarks: { Bookmarks }, @@ -706,6 +707,16 @@ describe('#unit BoltProtocolV3', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV3(recorder, null, false, undefined, undefined, () => {}) + } }) class SpiedBoltProtocolV3 extends BoltProtocolV3 { diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js index b84e78fc8..e37f96c74 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x0.test.js @@ -43,6 +43,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' +import { notificationFilterBehaviour } from './behaviour' const WRITE = 'WRITE' @@ -598,6 +599,16 @@ describe('#unit BoltProtocolV4x0', () => { expect(protocol.supportsReAuth).toBe(false) }) }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV4x0(recorder, null, false, undefined, undefined, () => {}) + } }) class SpiedBoltProtocolV4x0 extends BoltProtocolV4x0 { diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js index 7b1d05e26..c921f6a17 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x1.test.js @@ -37,6 +37,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' +import { notificationFilterBehaviour } from './behaviour' const { txConfig: { TxConfig }, @@ -498,4 +499,14 @@ describe('#unit BoltProtocolV4x1', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV4x1(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js index 9df65f4cd..6e16b1dee 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x2.test.js @@ -37,6 +37,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' +import { notificationFilterBehaviour } from './behaviour' const { txConfig: { TxConfig }, @@ -497,4 +498,14 @@ describe('#unit BoltProtocolV4x2', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV4x2(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js index 9bcce10d4..698c8dd19 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x3.test.js @@ -40,6 +40,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' import fc from 'fast-check' +import { notificationFilterBehaviour } from './behaviour' const WRITE = 'WRITE' @@ -1130,4 +1131,14 @@ describe('#unit BoltProtocolV4x3', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV4x3(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js index f541c869e..8c4ed5209 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v4x4.test.js @@ -40,6 +40,7 @@ import { import { alloc } from '../../src/channel' import { structure } from '../../src/packstream' import fc from 'fast-check' +import { notificationFilterBehaviour } from './behaviour' const WRITE = 'WRITE' @@ -1163,4 +1164,14 @@ describe('#unit BoltProtocolV4x4', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV4x4(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js index 72d603215..3fcdeb935 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x0.test.js @@ -40,6 +40,7 @@ import { } from 'neo4j-driver-core' import { alloc } from '../../src/channel' +import { notificationFilterBehaviour } from './behaviour' const WRITE = 'WRITE' @@ -1062,4 +1063,14 @@ describe('#unit BoltProtocolV5x0', () => { expect(() => fn(protocol)).toThrowError(message) } }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV5x0(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js index afce3f0a3..8c472ba42 100644 --- a/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x1.test.js @@ -40,6 +40,7 @@ import { } from 'neo4j-driver-core' import { alloc } from '../../src/channel' +import { notificationFilterBehaviour } from './behaviour' const WRITE = 'WRITE' @@ -1089,4 +1090,14 @@ describe('#unit BoltProtocolV5x1', () => { expect(unpacked).toEqual(struct) }) }) + + describe('Bolt v5.2', () => { + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldNotSupportNotificationFilterOnRun(newProtocol) + }) + + function newProtocol (recorder) { + return new BoltProtocolV5x1(recorder, null, false, undefined, undefined, () => {}) + } }) diff --git a/packages/core/src/driver.ts b/packages/core/src/driver.ts index bf9f6eedf..a3fcb2dc4 100644 --- a/packages/core/src/driver.ts +++ b/packages/core/src/driver.ts @@ -283,7 +283,7 @@ class SessionConfig { * // notification categories * const sessionWithDefaultSeverityLevel = driver.session({ * database: 'neo4j', - * notificationFilters: { + * notificationFilter: { * disabledCategories: [ * neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT' * neo4j.notificationFilterDisabledCategory.UNRECOGNIZED // or 'UNRECOGNIZED' @@ -294,7 +294,7 @@ class SessionConfig { * // using default disabled categories, but configuring minimum severity level to 'WARNING' * const sessionWithDefaultSeverityLevel = driver.session({ * database: 'neo4j', - * notificationFilters: { + * notificationFilter: { * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING // or 'WARNING' * } * }) diff --git a/packages/core/test/session.test.ts b/packages/core/test/session.test.ts index 2e2002c16..d554468ec 100644 --- a/packages/core/test/session.test.ts +++ b/packages/core/test/session.test.ts @@ -490,7 +490,7 @@ describe('session', () => { it.each( validNotificationFilters() - )('should call run query with notificationFilters', async (notificationFilter?: NotificationFilter) => { + )('should call run query with notificationFilter', async (notificationFilter?: NotificationFilter) => { const connection = mockBeginWithSuccess(newFakeConnection()) const { session } = setupSession({ @@ -817,7 +817,7 @@ describe('session', () => { it.each( validNotificationFilters() - )('should call run query with notificationFilters', async (notificationFilter?: NotificationFilter) => { + )('should call run query with notificationFilter', async (notificationFilter?: NotificationFilter) => { const connection = newFakeConnection() const { session } = setupSession({ diff --git a/packages/core/test/transaction.test.ts b/packages/core/test/transaction.test.ts index f3cdc8fa9..0c8a23bda 100644 --- a/packages/core/test/transaction.test.ts +++ b/packages/core/test/transaction.test.ts @@ -379,7 +379,7 @@ function testTx (transactionName: string, newTransaction: it.each( validNotificationFilters() - )('should call not run query with notificationFilters', async (notificationFilter?: NotificationFilter) => { + )('should call not run query with notificationFilter', async (notificationFilter?: NotificationFilter) => { const connection = newFakeConnection() const tx = newTransaction({ connection, diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-util.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-util.js index 21293dd7a..6fe79efd0 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-util.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-util.js @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { newError } from '../../core/index.ts' +import { newError, json } from '../../core/index.ts' // eslint-disable-next-line no-unused-vars import { ResultStreamObserver } from './stream-observers.js' @@ -79,4 +79,24 @@ function assertImpersonatedUserIsEmpty (impersonatedUser, onProtocolError = () = } } -export { assertDatabaseIsEmpty, assertTxConfigIsEmpty, assertImpersonatedUserIsEmpty } +/** + * Asserts that the passed-in notificationFilter is empty + * @param {NotificationFilter} notificationFilter + * @param {function (err:Error)} onProtocolError Called when it does have notificationFilter user set + * @param {any} observer + */ +function assertNotificationFilterIsEmpty (notificationFilter, onProtocolError = () => {}, observer) { + if (notificationFilter !== undefined) { + const error = newError( + 'Driver is connected to a database that does not support user notification filters. ' + + 'Please upgrade to Neo4j 5.7.0 or later in order to use this functionality. ' + + `Trying to set notifications to ${json.stringify(notificationFilter)}.` + ) + // unsupported API was used, consider this a fatal error for the current connection + onProtocolError(error.message) + observer.onError(error) + throw error + } +} + +export { assertDatabaseIsEmpty, assertTxConfigIsEmpty, assertImpersonatedUserIsEmpty, assertNotificationFilterIsEmpty } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js index 7874be285..bafc8d74e 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v1.js @@ -19,7 +19,8 @@ import { assertDatabaseIsEmpty, assertTxConfigIsEmpty, - assertImpersonatedUserIsEmpty + assertImpersonatedUserIsEmpty, + assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' // eslint-disable-next-line no-unused-vars import { Chunker } from '../channel/index.js' @@ -169,16 +170,20 @@ export default class BoltProtocol { * @param {Object} param * @param {string} param.userAgent the user agent. * @param {Object} param.authToken the authentication token. + * @param {NotificationFilter} param.notificationFilter the notification filter. * @param {function(err: Error)} param.onError the callback to invoke on error. * @param {function()} param.onComplete the callback to invoke on completion. * @returns {StreamObserver} the stream observer that monitors the corresponding server response. */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write(RequestMessage.init(userAgent, authToken), observer, true) return observer @@ -254,6 +259,7 @@ export default class BoltProtocol { * @param {string} param.database the target database name. * @param {string} param.mode the access mode. * @param {string} param.impersonatedUser the impersonated user + * @param {NotificationFilter} param.notificationFilter the notification filter. * @param {function(err: Error)} param.beforeError the callback to invoke before handling the error. * @param {function(err: Error)} param.afterError the callback to invoke after handling the error. * @param {function()} param.beforeComplete the callback to invoke before handling the completion. @@ -266,6 +272,7 @@ export default class BoltProtocol { database, mode, impersonatedUser, + notificationFilter, beforeError, afterError, beforeComplete, @@ -280,6 +287,7 @@ export default class BoltProtocol { database, mode, impersonatedUser, + notificationFilter, beforeError, afterError, beforeComplete, @@ -362,6 +370,7 @@ export default class BoltProtocol { * @param {TxConfig} param.txConfig the transaction configuration. * @param {string} param.database the target database name. * @param {string} param.impersonatedUser the impersonated user + * @param {NotificationFilter} param.notificationFilter the notification filter. * @param {string} param.mode the access mode. * @param {function(keys: string[])} param.beforeKeys the callback to invoke before handling the keys. * @param {function(keys: string[])} param.afterKeys the callback to invoke after handling the keys. @@ -381,6 +390,7 @@ export default class BoltProtocol { database, mode, impersonatedUser, + notificationFilter, beforeKeys, afterKeys, beforeError, @@ -410,6 +420,8 @@ export default class BoltProtocol { assertDatabaseIsEmpty(database, this._onProtocolError, observer) // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write(RequestMessage.run(query, parameters), observer, false) this.write(RequestMessage.pullAll(), observer, flush) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js index f9826485c..42b9d6788 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v3.js @@ -18,7 +18,7 @@ */ import BoltProtocolV2 from './bolt-protocol-v2.js' import RequestMessage from './request-message.js' -import { assertDatabaseIsEmpty, assertImpersonatedUserIsEmpty } from './bolt-protocol-util.js' +import { assertDatabaseIsEmpty, assertImpersonatedUserIsEmpty, assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import { StreamObserver, LoginObserver, @@ -69,12 +69,15 @@ export default class BoltProtocol extends BoltProtocolV2 { return metadata } - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write(RequestMessage.hello(userAgent, authToken), observer, true) return observer @@ -89,6 +92,7 @@ export default class BoltProtocol extends BoltProtocolV2 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeError, afterError, @@ -108,6 +112,8 @@ export default class BoltProtocol extends BoltProtocolV2 { assertDatabaseIsEmpty(database, this._onProtocolError, observer) // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write( RequestMessage.begin({ bookmarks, txConfig, mode }), @@ -166,6 +172,7 @@ export default class BoltProtocol extends BoltProtocolV2 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeKeys, afterKeys, @@ -194,6 +201,8 @@ export default class BoltProtocol extends BoltProtocolV2 { assertDatabaseIsEmpty(database, this._onProtocolError, observer) // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write( RequestMessage.runWithMetadata(query, parameters, { diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js index a9eb5c856..884c09615 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x0.js @@ -18,7 +18,7 @@ */ import BoltProtocolV3 from './bolt-protocol-v3.js' import RequestMessage from './request-message.js' -import { assertImpersonatedUserIsEmpty } from './bolt-protocol-util.js' +import { assertImpersonatedUserIsEmpty, assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import { ResultStreamObserver, ProcedureRouteObserver @@ -56,6 +56,7 @@ export default class BoltProtocol extends BoltProtocolV3 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeError, afterError, @@ -73,6 +74,8 @@ export default class BoltProtocol extends BoltProtocolV3 { // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) this.write( RequestMessage.begin({ bookmarks, txConfig, database, mode }), @@ -91,6 +94,7 @@ export default class BoltProtocol extends BoltProtocolV3 { txConfig, database, impersonatedUser, + notificationFilter, mode, beforeKeys, afterKeys, @@ -123,6 +127,8 @@ export default class BoltProtocol extends BoltProtocolV3 { // passing impersonated user on this protocol version throws an error assertImpersonatedUserIsEmpty(impersonatedUser, this._onProtocolError, observer) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) const flushRun = reactive this.write( diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js index d3e813602..8ab31730e 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x1.js @@ -20,6 +20,7 @@ import BoltProtocolV4 from './bolt-protocol-v4x0.js' import RequestMessage from './request-message.js' import { LoginObserver } from './stream-observers.js' import { internal } from '../../core/index.ts' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import transformersFactories from './bolt-protocol-v4x1.transformer.js' import Transformer from './transformer.js' @@ -72,12 +73,15 @@ export default class BoltProtocol extends BoltProtocolV4 { return this._transformer } - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello(userAgent, authToken, this._serversideRouting), observer, diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js index 68d50bad7..dcee97cb5 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x3.js @@ -19,6 +19,7 @@ import BoltProtocolV42 from './bolt-protocol-v4x2.js' import RequestMessage from './request-message.js' import { LoginObserver, RouteObserver } from './stream-observers.js' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import transformersFactories from './bolt-protocol-v4x3.transformer.js' import utcTransformersFactories from './bolt-protocol-v5x0.utc.transformer.js' @@ -80,14 +81,15 @@ export default class BoltProtocol extends BoltProtocolV42 { /** * Initialize a connection with the server * - * @param {Object} param0 The params - * @param {string} param0.userAgent The user agent - * @param {any} param0.authToken The auth token - * @param {function(error)} param0.onError On error callback - * @param {function(onComplte)} param0.onComplete On complete callback + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filter. + * @param {function(error)} args.onError On error callback + * @param {function(onComplte)} args.onComplete On complete callback * @returns {LoginObserver} The Login observer */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => { @@ -98,6 +100,9 @@ export default class BoltProtocol extends BoltProtocolV42 { } }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello(userAgent, authToken, this._serversideRouting, ['utc']), observer, diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js index 22d95f52b..774b21348 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v4x4.js @@ -21,6 +21,7 @@ import BoltProtocolV43 from './bolt-protocol-v4x3.js' import { internal } from '../../core/index.ts' import RequestMessage from './request-message.js' import { RouteObserver, ResultStreamObserver } from './stream-observers.js' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import transformersFactories from './bolt-protocol-v4x4.transformer.js' import utcTransformersFactories from './bolt-protocol-v5x0.utc.transformer.js' @@ -87,6 +88,7 @@ export default class BoltProtocol extends BoltProtocolV43 { database, mode, impersonatedUser, + notificationFilter, beforeKeys, afterKeys, beforeError, @@ -116,6 +118,9 @@ export default class BoltProtocol extends BoltProtocolV43 { lowRecordWatermark }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + const flushRun = reactive this.write( RequestMessage.runWithMetadata(query, parameters, { @@ -142,6 +147,7 @@ export default class BoltProtocol extends BoltProtocolV43 { database, mode, impersonatedUser, + notificationFilter, beforeError, afterError, beforeComplete, @@ -156,6 +162,9 @@ export default class BoltProtocol extends BoltProtocolV43 { }) observer.prepareToHandleSingleResponse() + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.begin({ bookmarks, txConfig, database, mode, impersonatedUser }), observer, diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js index d29f73044..cf4330563 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x0.js @@ -18,6 +18,7 @@ */ import BoltProtocolV44 from './bolt-protocol-v4x4.js' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import transformersFactories from './bolt-protocol-v5x0.transformer.js' import Transformer from './transformer.js' import RequestMessage from './request-message.js' @@ -44,19 +45,23 @@ export default class BoltProtocol extends BoltProtocolV44 { /** * Initialize a connection with the server * - * @param {Object} param0 The params - * @param {string} param0.userAgent The user agent - * @param {any} param0.authToken The auth token - * @param {function(error)} param0.onError On error callback - * @param {function(onComplte)} param0.onComplete On complete callback + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filter. + * @param {function(error)} args.onError On error callback + * @param {function(onComplte)} args.onComplete On complete callback * @returns {LoginObserver} The Login observer */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), onCompleted: metadata => this._onLoginCompleted(metadata, authToken, onComplete) }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello(userAgent, authToken, this._serversideRouting), observer, diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js index 4dda70917..4c6dba634 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x1.js @@ -18,6 +18,7 @@ */ import BoltProtocolV5x0 from './bolt-protocol-v5x0.js' +import { assertNotificationFilterIsEmpty } from './bolt-protocol-util.js' import transformersFactories from './bolt-protocol-v5x1.transformer.js' import Transformer from './transformer.js' import RequestMessage from './request-message.js' @@ -48,14 +49,15 @@ export default class BoltProtocol extends BoltProtocolV5x0 { /** * Initialize a connection with the server * - * @param {Object} param0 The params - * @param {string} param0.userAgent The user agent - * @param {any} param0.authToken The auth token - * @param {function(error)} param0.onError On error callback - * @param {function(onComplte)} param0.onComplete On complete callback + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filters. + * @param {function(error)} args.onError On error callback + * @param {function(onComplete)} args.onComplete On complete callback * @returns {LoginObserver} The Login observer */ - initialize ({ userAgent, authToken, onError, onComplete } = {}) { + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { const state = {} const observer = new LoginObserver({ onError: error => this._onLoginError(error, onError), @@ -65,6 +67,9 @@ export default class BoltProtocol extends BoltProtocolV5x0 { } }) + // passing notification filter on this protocol version throws an error + assertNotificationFilterIsEmpty(notificationFilter, this._onProtocolError, observer) + this.write( RequestMessage.hello5x1(userAgent, this._serversideRouting), observer, diff --git a/packages/neo4j-driver-deno/lib/core/driver.ts b/packages/neo4j-driver-deno/lib/core/driver.ts index 3b69802d0..aa61ee18e 100644 --- a/packages/neo4j-driver-deno/lib/core/driver.ts +++ b/packages/neo4j-driver-deno/lib/core/driver.ts @@ -283,7 +283,7 @@ class SessionConfig { * // notification categories * const sessionWithDefaultSeverityLevel = driver.session({ * database: 'neo4j', - * notificationFilters: { + * notificationFilter: { * disabledCategories: [ * neo4j.notificationFilterDisabledCategory.HINT, // or 'HINT' * neo4j.notificationFilterDisabledCategory.UNRECOGNIZED // or 'UNRECOGNIZED' @@ -294,7 +294,7 @@ class SessionConfig { * // using default disabled categories, but configuring minimum severity level to 'WARNING' * const sessionWithDefaultSeverityLevel = driver.session({ * database: 'neo4j', - * notificationFilters: { + * notificationFilter: { * minimumSeverityLevel: neo4j.notificationFilterMinimumSeverityLevel.WARNING // or 'WARNING' * } * }) From d79b7e82d7856145efad5427853804ba74a6b07f Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Wed, 15 Mar 2023 19:31:02 +0100 Subject: [PATCH 12/15] Add notificationFilter to the bolt-messages in Bolt 5.2 --- .../src/bolt/bolt-protocol-v5x2.js | 172 +++ .../bolt/bolt-protocol-v5x2.transformer.js | 24 + packages/bolt-connection/src/bolt/create.js | 11 + .../bolt-connection/src/bolt/handshake.js | 2 +- .../src/bolt/request-message.js | 52 +- .../src/connection/connection-channel.js | 4 + .../bolt-protocol-v5x2.test.js.snap | 61 + .../bolt/behaviour/notification-filter.js | 183 ++- .../test/bolt/bolt-protocol-v5x2.test.js | 1103 +++++++++++++++++ .../bolt-connection/test/bolt/index.test.js | 11 +- .../test/bolt/request-message.test.js | 120 ++ .../connection/connection-channel.test.js | 27 + packages/core/src/internal/constants.ts | 4 +- .../bolt/bolt-protocol-v5x2.js | 172 +++ .../bolt/bolt-protocol-v5x2.transformer.js | 24 + .../lib/bolt-connection/bolt/create.js | 11 + .../lib/bolt-connection/bolt/handshake.js | 2 +- .../bolt-connection/bolt/request-message.js | 52 +- .../connection/connection-channel.js | 4 + .../lib/core/internal/constants.ts | 4 +- 20 files changed, 2014 insertions(+), 29 deletions(-) create mode 100644 packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js create mode 100644 packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js create mode 100644 packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x2.test.js.snap create mode 100644 packages/bolt-connection/test/bolt/bolt-protocol-v5x2.test.js create mode 100644 packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js create mode 100644 packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js new file mode 100644 index 000000000..4d49b5b42 --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.js @@ -0,0 +1,172 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import BoltProtocolV5x1 from './bolt-protocol-v5x1' + +import transformersFactories from './bolt-protocol-v5x2.transformer' +import Transformer from './transformer' +import RequestMessage from './request-message' +import { LoginObserver, ResultStreamObserver } from './stream-observers' + +import { internal } from 'neo4j-driver-core' + +const { + constants: { BOLT_PROTOCOL_V5_2, FETCH_ALL } +} = internal + +export default class BoltProtocol extends BoltProtocolV5x1 { + get version () { + return BOLT_PROTOCOL_V5_2 + } + + get transformer () { + if (this._transformer === undefined) { + this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log))) + } + return this._transformer + } + + get supportsReAuth () { + return true + } + + /** + * Initialize a connection with the server + * + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filters. + * @param {function(error)} args.onError On error callback + * @param {function(onComplete)} args.onComplete On complete callback + * @returns {LoginObserver} The Login observer + */ + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { + const state = {} + const observer = new LoginObserver({ + onError: error => this._onLoginError(error, onError), + onCompleted: metadata => { + state.metadata = metadata + return this._onLoginCompleted(metadata) + } + }) + + this.write( + RequestMessage.hello5x2(userAgent, notificationFilter, this._serversideRouting), + observer, + false + ) + + return this.logon({ + authToken, + onComplete: metadata => onComplete({ ...metadata, ...state.metadata }), + onError, + flush: true + }) + } + + beginTransaction ({ + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter, + beforeError, + afterError, + beforeComplete, + afterComplete + } = {}) { + const observer = new ResultStreamObserver({ + server: this._server, + beforeError, + afterError, + beforeComplete, + afterComplete + }) + observer.prepareToHandleSingleResponse() + + this.write( + RequestMessage.begin({ bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter }), + observer, + true + ) + + return observer + } + + run ( + query, + parameters, + { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter, + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + flush = true, + reactive = false, + fetchSize = FETCH_ALL, + highRecordWatermark = Number.MAX_VALUE, + lowRecordWatermark = Number.MAX_VALUE + } = {} + ) { + const observer = new ResultStreamObserver({ + server: this._server, + reactive: reactive, + fetchSize: fetchSize, + moreFunction: this._requestMore.bind(this), + discardFunction: this._requestDiscard.bind(this), + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + highRecordWatermark, + lowRecordWatermark + }) + + const flushRun = reactive + this.write( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter + }), + observer, + flushRun && flush + ) + + if (!reactive) { + this.write(RequestMessage.pull({ n: fetchSize }), observer, flush) + } + + return observer + } +} diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js new file mode 100644 index 000000000..0923aef4d --- /dev/null +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import v5x0 from './bolt-protocol-v5x0.transformer' + +export default { + ...v5x0 +} diff --git a/packages/bolt-connection/src/bolt/create.js b/packages/bolt-connection/src/bolt/create.js index 374266fda..21539cb54 100644 --- a/packages/bolt-connection/src/bolt/create.js +++ b/packages/bolt-connection/src/bolt/create.js @@ -28,6 +28,7 @@ import BoltProtocolV4x3 from './bolt-protocol-v4x3' import BoltProtocolV4x4 from './bolt-protocol-v4x4' import BoltProtocolV5x0 from './bolt-protocol-v5x0' import BoltProtocolV5x1 from './bolt-protocol-v5x1' +import BoltProtocolV5x2 from './bolt-protocol-v5x2' // eslint-disable-next-line no-unused-vars import { Chunker, Dechunker } from '../channel' import ResponseHandler from './response-handler' @@ -202,6 +203,16 @@ function createProtocol ( onProtocolError, serversideRouting ) + case 5.2: + return new BoltProtocolV5x2( + server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting + ) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/bolt-connection/src/bolt/handshake.js b/packages/bolt-connection/src/bolt/handshake.js index 09eb4beff..76067aba4 100644 --- a/packages/bolt-connection/src/bolt/handshake.js +++ b/packages/bolt-connection/src/bolt/handshake.js @@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer) { */ function newHandshakeBuffer () { return createHandshakeMessage([ - [version(5, 1), version(5, 0)], + [version(5, 2), version(5, 0)], [version(4, 4), version(4, 2)], version(4, 1), version(3, 0) diff --git a/packages/bolt-connection/src/bolt/request-message.js b/packages/bolt-connection/src/bolt/request-message.js index 446a7828d..b60486962 100644 --- a/packages/bolt-connection/src/bolt/request-message.js +++ b/packages/bolt-connection/src/bolt/request-message.js @@ -160,6 +160,37 @@ export default class RequestMessage { ) } + /** + * Create a new HELLO message. + * @param {string} userAgent the user agent. + * @param {NotificationFilter} notificationFilter the notification filter configured + * @param {Object} routing server side routing, set to routing context to turn on server side routing (> 4.1) + * @return {RequestMessage} new HELLO message. + */ + static hello5x2 (userAgent, notificationFilter = null, routing = null) { + const metadata = { user_agent: userAgent } + + if (notificationFilter) { + if (notificationFilter.minimumSeverityLevel) { + metadata.notifications_minimum_severity = notificationFilter.minimumSeverityLevel + } + + if (notificationFilter.disabledCategories) { + metadata.notifications_disabled_categories = notificationFilter.disabledCategories + } + } + + if (routing) { + metadata.routing = routing + } + + return new RequestMessage( + HELLO, + [metadata], + () => `HELLO ${json.stringify(metadata)}` + ) + } + /** * Create a new LOGON message. * @@ -194,10 +225,11 @@ export default class RequestMessage { * @param {string} database the database name. * @param {string} mode the access mode. * @param {string} impersonatedUser the impersonated user. + * @param {NotificationFilter} notificationFilter the notification filter * @return {RequestMessage} new BEGIN message. */ - static begin ({ bookmarks, txConfig, database, mode, impersonatedUser } = {}) { - const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser) + static begin ({ bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter } = {}) { + const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter) return new RequestMessage( BEGIN, [metadata], @@ -235,9 +267,9 @@ export default class RequestMessage { static runWithMetadata ( query, parameters, - { bookmarks, txConfig, database, mode, impersonatedUser } = {} + { bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter } = {} ) { - const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser) + const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter) return new RequestMessage( RUN, [query, parameters, metadata], @@ -348,9 +380,10 @@ export default class RequestMessage { * @param {string} database the database name. * @param {string} mode the access mode. * @param {string} impersonatedUser the impersonated user mode. + * @param {notificationFilter} notificationFilter the notification filter * @return {Object} a metadata object. */ -function buildTxMetadata (bookmarks, txConfig, database, mode, impersonatedUser) { +function buildTxMetadata (bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter) { const metadata = {} if (!bookmarks.isEmpty()) { metadata.bookmarks = bookmarks.values() @@ -370,6 +403,15 @@ function buildTxMetadata (bookmarks, txConfig, database, mode, impersonatedUser) if (mode === ACCESS_MODE_READ) { metadata.mode = READ_MODE } + if (notificationFilter) { + if (notificationFilter.minimumSeverityLevel) { + metadata.notifications_minimum_severity = notificationFilter.minimumSeverityLevel + } + + if (notificationFilter.disabledCategories) { + metadata.notifications_disabled_categories = notificationFilter.disabledCategories + } + } return metadata } diff --git a/packages/bolt-connection/src/connection/connection-channel.js b/packages/bolt-connection/src/connection/connection-channel.js index 390eedb7e..8b270519f 100644 --- a/packages/bolt-connection/src/connection/connection-channel.js +++ b/packages/bolt-connection/src/connection/connection-channel.js @@ -86,6 +86,7 @@ export function createChannelConnection ( config.disableLosslessIntegers, serversideRouting, chunker, + config.notificationFilter, createProtocol ) @@ -119,6 +120,7 @@ export default class ChannelConnection extends Connection { disableLosslessIntegers = false, serversideRouting = null, chunker, // to be removed, + notificationFilter, protocolSupplier ) { super(errorHandler) @@ -134,6 +136,7 @@ export default class ChannelConnection extends Connection { this._chunker = chunker this._log = createConnectionLogger(this, log) this._serversideRouting = serversideRouting + this._notificationFilter = notificationFilter // connection from the database, returned in response for HELLO message and might not be available this._dbConnectionId = null @@ -187,6 +190,7 @@ export default class ChannelConnection extends Connection { this._protocol.initialize({ userAgent, authToken, + notificationFilter: this._notificationFilter, onError: err => reject(err), onComplete: metadata => { if (metadata) { diff --git a/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x2.test.js.snap b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x2.test.js.snap new file mode 100644 index 000000000..75f545b99 --- /dev/null +++ b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v5x2.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#unit BoltProtocolV5x2 .packable() should pack not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:\\"b\\"})"`; + +exports[`#unit BoltProtocolV5x2 .packable() should pack not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`; + +exports[`#unit BoltProtocolV5x2 .packable() should pack not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:\\"c\\"}]->(f)"`; + +exports[`#unit BoltProtocolV5x2 .packable() should pack not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:\\"c\\"}]->"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Date with more fields) 1`] = `"Wrong struct size for Date, expected 1 but was 2"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (DateTimeWithZoneId with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (DateTimeWithZoneId with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Duration with less fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Duration with more fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (LocalDateTime with less fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 1"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (LocalDateTime with more fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 3"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (LocalTime with less fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 0"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (LocalTime with more fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 2"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Node with less fields) 1`] = `"Wrong struct size for Node, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Node with more fields) 1`] = `"Wrong struct size for Node, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Path with less fields) 1`] = `"Wrong struct size for Path, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Path with more fields) 1`] = `"Wrong struct size for Path, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Point with less fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 2"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Point with more fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 4"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Point3D with less fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Point3D with more fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 5"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Relationship with less fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 5"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Relationship with more fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 9"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Time with less fields) 1`] = `"Wrong struct size for Time, expected 2 but was 1"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (Time with more fileds) 1`] = `"Wrong struct size for Time, expected 2 but was 3"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (UnboundRelationship with less fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 3"`; + +exports[`#unit BoltProtocolV5x2 .unpack() should not unpack with wrong size (UnboundRelationship with more fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 5"`; diff --git a/packages/bolt-connection/test/bolt/behaviour/notification-filter.js b/packages/bolt-connection/test/bolt/behaviour/notification-filter.js index 6d7fd8748..5bc88de85 100644 --- a/packages/bolt-connection/test/bolt/behaviour/notification-filter.js +++ b/packages/bolt-connection/test/bolt/behaviour/notification-filter.js @@ -17,8 +17,23 @@ * limitations under the License. */ +import { + internal, + notificationFilterDisabledCategory, + notificationFilterMinimumSeverityLevel +} from 'neo4j-driver-core' +import RequestMessage from '../../../src/bolt/request-message' +import { LoginObserver } from '../../../src/bolt/stream-observers' + import utils from '../../test-utils' +const WRITE = 'WRITE' + +const { + txConfig: { TxConfig }, + bookmarks: { Bookmarks } +} = internal + /** * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory */ @@ -32,8 +47,8 @@ export function shouldNotSupportNotificationFilterOnInitialize (createProtocol) } it.each( - notificationFilters() - )('should throw error when notificationFilters is set (%o)', (notificationFilter) => { + notificationFilterSetFixture() + )('should throw error when notificationsFilter=%o is set (%o)', (notificationFilter) => { verifyInitialize({ notificationFilter }) @@ -54,8 +69,8 @@ export function shouldNotSupportNotificationFilterOnBeginTransaction (createProt } it.each( - notificationFilters() - )('should throw error when notificationFilters is set', (notificationFilter) => { + notificationFilterSetFixture() + )('should throw error when notificationsFilter=%o is set', (notificationFilter) => { verifyBeginTransaction(notificationFilter) }) }) @@ -74,24 +89,170 @@ export function shouldNotSupportNotificationFilterOnRun (createProtocol) { } it.each( - notificationFilters() - )('should throw error when notificationFilters is set', (notificationFilter) => { + notificationFilterSetFixture() + )('should throw error when notificationsFilter=%o is set', (notificationFilter) => { verifyRun(notificationFilter) }) }) } +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + */ +export function shouldSupportNotificationFilterOnInitialize (createProtocol) { + it.each( + notificationFilterFixture() + )('should send notificationsFilter=%o on initialize', (notificationFilter) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = createProtocol(recorder) + utils.spyProtocolWrite(protocol) + const userAgent = 'js-driver-123' + const authToken = { type: 'none' } + + const observer = protocol.initialize({ userAgent, authToken, notificationFilter }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.hello5x2(userAgent, notificationFilter) + ) + expect(protocol.messages[1]).toBeMessage( + RequestMessage.logon(authToken) + ) + + verifyObserversAndFlushes(protocol, observer) + }) + + function verifyObserversAndFlushes (protocol, observer) { + expect(protocol.observers.length).toBe(2) + + // hello observer + const helloObserver = protocol.observers[0] + expect(helloObserver).toBeInstanceOf(LoginObserver) + expect(helloObserver).not.toBe(observer) + + // login observer + const loginObserver = protocol.observers[1] + expect(loginObserver).toBeInstanceOf(LoginObserver) + expect(loginObserver).toBe(observer) + + expect(protocol.flushes).toEqual([false, true]) + } +} + +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + */ +export function shouldSupportNotificationFilterOnBeginTransaction (createProtocol) { + it.each( + notificationFilterFixture() + )('should send notificationsFilter=%o on begin a transaction', (notificationFilter) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = createProtocol(recorder) + utils.spyProtocolWrite(protocol) + + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + notificationFilter, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE, notificationFilter }) + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) +} + +/** + * @param {function(recorder:MessageRecorder):BoltProtocolV1} createProtocol The protocol factory + */ +export function shouldSupportNotificationFilterOnRun (createProtocol) { + it.each( + notificationFilterFixture() + )('should send notificationsFilter=%o on run', (notificationFilter) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = createProtocol(recorder) + utils.spyProtocolWrite(protocol) + + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + notificationFilter + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + notificationFilter + }) + ) + + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) +} + +export function notificationFilterFixture () { + return [ + undefined, + null, + ...notificationFilterSetFixture() + ] +} + /** * * @returns {Array} Return the list of notification features used in test */ -function notificationFilters () { +function notificationFilterSetFixture () { + const minimumSeverityLevelSet = Object.values(notificationFilterMinimumSeverityLevel) + const disabledCategories = Object.values(notificationFilterDisabledCategory) + const disabledCategoriesSet = [...disabledCategories.keys()] + .map(length => disabledCategories.slice(0, length + 1)) return [ {}, - { minimumSeverityLevel: 'OFF' }, - { minimumSeverityLevel: 'INFORMATION' }, - { disabledCategories: [] }, - { disabledCategories: ['UNRECOGNIZED'] } + ...minimumSeverityLevelSet.map(minimumSeverityLevel => ({ minimumSeverityLevel })), + ...disabledCategoriesSet.map(disabledCategories => ({ disabledCategories })), + ...minimumSeverityLevelSet.flatMap( + minimumSeverityLevel => disabledCategories.map( + disabledCategories => ({ minimumSeverityLevel, disabledCategories }))) ] } diff --git a/packages/bolt-connection/test/bolt/bolt-protocol-v5x2.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v5x2.test.js new file mode 100644 index 000000000..d4a1e10da --- /dev/null +++ b/packages/bolt-connection/test/bolt/bolt-protocol-v5x2.test.js @@ -0,0 +1,1103 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import BoltProtocolV5x2 from '../../src/bolt/bolt-protocol-v5x2' +import RequestMessage from '../../src/bolt/request-message' +import { v2, structure } from '../../src/packstream' +import utils from '../test-utils' +import { LoginObserver, RouteObserver } from '../../src/bolt/stream-observers' +import fc from 'fast-check' +import { + Date, + DateTime, + Duration, + LocalDateTime, + LocalTime, + Path, + PathSegment, + Point, + Relationship, + Time, + UnboundRelationship, + Node, + internal +} from 'neo4j-driver-core' + +import { alloc } from '../../src/channel' +import { notificationFilterBehaviour } from './behaviour' + +const WRITE = 'WRITE' + +const { + txConfig: { TxConfig }, + bookmarks: { Bookmarks }, + logger: { Logger }, + temporalUtil +} = internal + +describe('#unit BoltProtocolV5x2', () => { + beforeEach(() => { + expect.extend(utils.matchers) + }) + + it('should request routing information', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, [], { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should request routing information sending bookmarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + const routingContext = { someContextParam: 'value' } + const listOfBookmarks = ['a', 'b', 'c'] + const bookmarks = new Bookmarks(listOfBookmarks) + const databaseName = 'name' + + const observer = protocol.requestRoutingInformation({ + routingContext, + databaseName, + sessionContext: { bookmarks } + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.routeV4x4(routingContext, listOfBookmarks, { databaseName, impersonatedUser: null }) + ) + expect(protocol.observers).toEqual([observer]) + expect(observer).toEqual(expect.any(RouteObserver)) + expect(protocol.flushes).toEqual([true]) + }) + + it('should run a query', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should run a with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + + const observer = protocol.run(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(2) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + ) + expect(protocol.messages[1]).toBeMessage(RequestMessage.pull()) + expect(protocol.observers).toEqual([observer, observer]) + expect(protocol.flushes).toEqual([false, true]) + }) + + it('should begin a transaction', () => { + const database = 'testdb' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should begin a transaction with impersonated user', () => { + const database = 'testdb' + const impersonatedUser = 'the impostor' + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + database, + mode: WRITE, + impersonatedUser + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, database, mode: WRITE, impersonatedUser }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should return correct bolt version number', () => { + const protocol = new BoltProtocolV5x2(null, null, false) + + expect(protocol.version).toBe(5.2) + }) + + it('should update metadata', () => { + const metadata = { t_first: 1, t_last: 2, db_hits: 3, some_other_key: 4 } + const protocol = new BoltProtocolV5x2(null, null, false) + + const transformedMetadata = protocol.transformMetadata(metadata) + + expect(transformedMetadata).toEqual({ + result_available_after: 1, + result_consumed_after: 2, + db_hits: 3, + some_other_key: 4 + }) + }) + + it('should initialize connection', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const clientName = 'js-driver/1.2.3' + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.initialize({ userAgent: clientName, authToken }) + + protocol.verifyMessageCount(2) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.hello5x1(clientName) + ) + expect(protocol.messages[1]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers.length).toBe(2) + + // hello observer + const helloObserver = protocol.observers[0] + expect(helloObserver).toBeInstanceOf(LoginObserver) + expect(helloObserver).not.toBe(observer) + + // login observer + const loginObserver = protocol.observers[1] + expect(loginObserver).toBeInstanceOf(LoginObserver) + expect(loginObserver).toBe(observer) + + expect(protocol.flushes).toEqual([false, true]) + }) + + it.each( + [true, false] + )('should logon to the server [flush=%s]', (flush) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const authToken = { username: 'neo4j', password: 'secret' } + + const observer = protocol.logon({ authToken, flush }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.logon(authToken) + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([flush]) + }) + + it.each( + [true, false] + )('should logoff from the server [flush=%s]', (flush) => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.logoff({ flush }) + + protocol.verifyMessageCount(1) + + expect(protocol.messages[0]).toBeMessage( + RequestMessage.logoff() + ) + + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([flush]) + }) + + it('should begin a transaction', () => { + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx2' + ]) + const txConfig = new TxConfig({ + timeout: 5000, + metadata: { x: 1, y: 'something' } + }) + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.beginTransaction({ + bookmarks, + txConfig, + mode: WRITE + }) + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage( + RequestMessage.begin({ bookmarks, txConfig, mode: WRITE }) + ) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should commit', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.commitTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.commit()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should rollback', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + utils.spyProtocolWrite(protocol) + + const observer = protocol.rollbackTransaction() + + protocol.verifyMessageCount(1) + expect(protocol.messages[0]).toBeMessage(RequestMessage.rollback()) + expect(protocol.observers).toEqual([observer]) + expect(protocol.flushes).toEqual([true]) + }) + + it('should support logoff', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = new BoltProtocolV5x2(recorder, null, false) + + expect(protocol.supportsReAuth).toBe(true) + }) + + describe('unpacker configuration', () => { + test.each([ + [false, false], + [false, true], + [true, false], + [true, true] + ])( + 'should create unpacker with disableLosslessIntegers=%p and useBigInt=%p', + (disableLosslessIntegers, useBigInt) => { + const protocol = new BoltProtocolV5x2(null, null, { + disableLosslessIntegers, + useBigInt + }) + expect(protocol._unpacker._disableLosslessIntegers).toBe( + disableLosslessIntegers + ) + expect(protocol._unpacker._useBigInt).toBe(useBigInt) + } + ) + }) + + describe('notificationFilter', () => { + notificationFilterBehaviour.shouldSupportNotificationFilterOnInitialize(newProtocol) + notificationFilterBehaviour.shouldSupportNotificationFilterOnBeginTransaction(newProtocol) + notificationFilterBehaviour.shouldSupportNotificationFilterOnRun(newProtocol) + }) + + describe('watermarks', () => { + it('.run() should configure watermarks', () => { + const recorder = new utils.MessageRecordingConnection() + const protocol = utils.spyProtocolWrite( + new BoltProtocolV5x2(recorder, null, false) + ) + + const query = 'RETURN $x, $y' + const parameters = { x: 'x', y: 'y' } + const observer = protocol.run(query, parameters, { + bookmarks: Bookmarks.empty(), + txConfig: TxConfig.empty(), + lowRecordWatermark: 100, + highRecordWatermark: 200 + }) + + expect(observer._lowRecordWatermark).toEqual(100) + expect(observer._highRecordWatermark).toEqual(200) + }) + }) + + describe('packstream', () => { + it('should configure v2 packer', () => { + const protocol = new BoltProtocolV5x2(null, null, false) + expect(protocol.packer()).toBeInstanceOf(v2.Packer) + }) + + it('should configure v2 unpacker', () => { + const protocol = new BoltProtocolV5x2(null, null, false) + expect(protocol.unpacker()).toBeInstanceOf(v2.Unpacker) + }) + }) + + describe('.packable()', () => { + it.each([ + ['Node', new Node(1, ['a'], { a: 'b' }, 'c')], + ['Relationship', new Relationship(1, 2, 3, 'a', { b: 'c' }, 'd', 'e', 'f')], + ['UnboundRelationship', new UnboundRelationship(1, 'a', { b: 'c' }, '1')], + ['Path', new Path(new Node(1, [], {}), new Node(2, [], {}), [])] + ])('should pack not pack graph types (%s)', (_, graphType) => { + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + null, + false + ) + + const packable = protocol.packable(graphType) + + expect(packable).toThrowErrorMatchingSnapshot() + }) + + it.each([ + ['Duration', new Duration(1, 1, 1, 1)], + ['LocalTime', new LocalTime(1, 1, 1, 1)], + ['Time', new Time(1, 1, 1, 1, 1)], + ['Date', new Date(1, 1, 1)], + ['LocalDateTime', new LocalDateTime(1, 1, 1, 1, 1, 1, 1)], + [ + 'DateTimeWithZoneOffset', + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 120 * 60) + ], + [ + 'DateTimeWithZoneOffset / 1978', + new DateTime(1978, 12, 16, 10, 5, 59, 128000987, -150 * 60) + ], + [ + 'DateTimeWithZoneId / Berlin 2:30 CET', + new DateTime(2022, 10, 30, 2, 30, 0, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Berlin 2:30 CEST', + new DateTime(2022, 10, 30, 2, 30, 0, 183_000_000, 1 * 60 * 60, 'Europe/Berlin') + ], + ['Point2D', new Point(1, 1, 1)], + ['Point3D', new Point(1, 1, 1, 1)] + ])('should pack spatial types and temporal types (%s)', (_, object) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked).toEqual(object) + }) + + it.each([ + [ + 'DateTimeWithZoneId / Australia', + new DateTime(2022, 6, 15, 15, 21, 18, 183_000_000, undefined, 'Australia/Eucla') + ], + [ + 'DateTimeWithZoneId', + new DateTime(2022, 6, 22, 15, 21, 18, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just before turn CEST', + new DateTime(2022, 3, 27, 1, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 before turn CEST', + new DateTime(2022, 3, 27, 0, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just after turn CEST', + new DateTime(2022, 3, 27, 3, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 after turn CEST', + new DateTime(2022, 3, 27, 4, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just before turn CET', + new DateTime(2022, 10, 30, 2, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 before turn CET', + new DateTime(2022, 10, 30, 1, 59, 59, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just after turn CET', + new DateTime(2022, 10, 30, 3, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Europe just 1 after turn CET', + new DateTime(2022, 10, 30, 4, 0, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just before turn summer time', + new DateTime(2018, 11, 4, 11, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 before turn summer time', + new DateTime(2018, 11, 4, 10, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just after turn summer time', + new DateTime(2018, 11, 5, 1, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 after turn summer time', + new DateTime(2018, 11, 5, 2, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just before turn winter time', + new DateTime(2019, 2, 17, 11, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 before turn winter time', + new DateTime(2019, 2, 17, 10, 59, 59, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just after turn winter time', + new DateTime(2019, 2, 18, 0, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Sao Paulo just 1 after turn winter time', + new DateTime(2019, 2, 18, 1, 0, 0, 183_000_000, undefined, 'America/Sao_Paulo') + ], + [ + 'DateTimeWithZoneId / Istanbul', + new DateTime(1978, 12, 16, 12, 35, 59, 128000987, undefined, 'Europe/Istanbul') + ], + [ + 'DateTimeWithZoneId / Istanbul', + new DateTime(2020, 6, 15, 4, 30, 0, 183_000_000, undefined, 'Pacific/Honolulu') + ], + [ + 'DateWithWithZoneId / Berlin before common era', + new DateTime(-2020, 6, 15, 4, 30, 0, 183_000_000, undefined, 'Europe/Berlin') + ], + [ + 'DateWithWithZoneId / Max Date', + new DateTime(99_999, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Kiritimati') + ], + [ + 'DateWithWithZoneId / Min Date', + new DateTime(-99_999, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Samoa') + ], + [ + 'DateWithWithZoneId / Ambiguous date between 00 and 99', + new DateTime(50, 12, 31, 23, 59, 59, 999_999_999, undefined, 'Pacific/Samoa') + ] + ])('should pack and unpack DateTimeWithZoneId and without offset (%s)', (_, object) => { + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + expect(loggerFunction) + .toBeCalledWith('warn', + 'DateTime objects without "timeZoneOffsetSeconds" property ' + + 'are prune to bugs related to ambiguous times. For instance, ' + + '2022-10-30T2:30:00[Europe/Berlin] could be GMT+1 or GMT+2.') + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + const unpackedDateTimeWithoutOffset = new DateTime( + unpacked.year, + unpacked.month, + unpacked.day, + unpacked.hour, + unpacked.minute, + unpacked.second, + unpacked.nanosecond, + undefined, + unpacked.timeZoneId + ) + + expect(unpackedDateTimeWithoutOffset).toEqual(object) + }) + + it('should pack and unpack DateTimeWithOffset', () => { + fc.assert( + fc.property( + fc.date({ + min: temporalUtil.newDate(utils.MIN_UTC_IN_MS + utils.ONE_DAY_IN_MS), + max: temporalUtil.newDate(utils.MAX_UTC_IN_MS - utils.ONE_DAY_IN_MS) + }), + fc.integer({ min: 0, max: 999_999 }), + utils.arbitraryTimeZoneId(), + (date, nanoseconds, timeZoneId) => { + const object = new DateTime( + date.getUTCFullYear(), + date.getUTCMonth() + 1, + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds() * 1_000_000 + nanoseconds, + undefined, + timeZoneId + ) + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + expect(loggerFunction) + .toBeCalledWith('warn', + 'DateTime objects without "timeZoneOffsetSeconds" property ' + + 'are prune to bugs related to ambiguous times. For instance, ' + + '2022-10-30T2:30:00[Europe/Berlin] could be GMT+1 or GMT+2.') + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + const unpackedDateTimeWithoutOffset = new DateTime( + unpacked.year, + unpacked.month, + unpacked.day, + unpacked.hour, + unpacked.minute, + unpacked.second, + unpacked.nanosecond, + undefined, + unpacked.timeZoneId + ) + + expect(unpackedDateTimeWithoutOffset).toEqual(object) + }) + ) + }) + + it('should pack and unpack DateTimeWithZoneIdAndNoOffset', () => { + fc.assert( + fc.property(fc.date(), date => { + const object = DateTime.fromStandardDate(date) + const buffer = alloc(256) + const loggerFunction = jest.fn() + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + }, + undefined, + new Logger('debug', loggerFunction) + ) + + const packable = protocol.packable(object) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + + expect(unpacked.timeZoneOffsetSeconds).toBeDefined() + + expect(unpacked).toEqual(object) + }) + ) + }) + }) + + describe('.unpack()', () => { + it.each([ + [ + 'Node', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }, 'elementId']), + new Node(1, ['a'], { c: 'd' }, 'elementId') + ], + [ + 'Relationship', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }, 'elementId', 'node1', 'node2']), + new Relationship(1, 2, 3, '4', { 5: 6 }, 'elementId', 'node1', 'node2') + ], + [ + 'UnboundRelationship', + new structure.Structure(0x72, [1, '2', { 3: 4 }, 'elementId']), + new UnboundRelationship(1, '2', { 3: 4 }, 'elementId') + ], + [ + 'Path', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }, 'node1']), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }, 'node2']), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }, 'node3']) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'reltype1', { 4: '5' }, 'rel1', 'node1', 'node2']), + new structure.Structure(0x52, [5, 4, 2, 'reltype2', { 6: 7 }, 'rel2', 'node2', 'node3']) + ], + [1, 1, 2, 2] + ] + ), + new Path( + new Node(1, ['2'], { 3: '4' }, 'node1'), + new Node(2, ['3'], { 4: '5' }, 'node3'), + [ + new PathSegment( + new Node(1, ['2'], { 3: '4' }, 'node1'), + new Relationship(3, 1, 4, 'reltype1', { 4: '5' }, 'rel1', 'node1', 'node2'), + new Node(4, ['5'], { 6: 7 }, 'node2') + ), + new PathSegment( + new Node(4, ['5'], { 6: 7 }, 'node2'), + new Relationship(5, 4, 2, 'reltype2', { 6: 7 }, 'rel2', 'node2', 'node3'), + new Node(2, ['3'], { 4: '5' }, 'node3') + ) + ] + ) + ] + ])('should unpack graph types (%s)', (_, struct, graphObject) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + false + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(graphObject) + }) + + it.each([ + [ + 'Node with less fields', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }]) + ], + [ + 'Node with more fields', + new structure.Structure(0x4e, [1, ['a'], { c: 'd' }, '1', 'b']) + ], + [ + 'Relationship with less fields', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }]) + ], + [ + 'Relationship with more fields', + new structure.Structure(0x52, [1, 2, 3, '4', { 5: 6 }, '1', '2', '3', '4']) + ], + [ + 'UnboundRelationship with less fields', + new structure.Structure(0x72, [1, '2', { 3: 4 }]) + ], + [ + 'UnboundRelationship with more fields', + new structure.Structure(0x72, [1, '2', { 3: 4 }, '1', '2']) + ], + [ + 'Path with less fields', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }]), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }]), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }]) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'rel1', { 4: '5' }]), + new structure.Structure(0x52, [5, 4, 2, 'rel2', { 6: 7 }]) + ] + ] + ) + ], + [ + 'Path with more fields', + new structure.Structure( + 0x50, + [ + [ + new structure.Structure(0x4e, [1, ['2'], { 3: '4' }]), + new structure.Structure(0x4e, [4, ['5'], { 6: 7 }]), + new structure.Structure(0x4e, [2, ['3'], { 4: '5' }]) + ], + [ + new structure.Structure(0x52, [3, 1, 4, 'rel1', { 4: '5' }]), + new structure.Structure(0x52, [5, 4, 2, 'rel2', { 6: 7 }]) + ], + [1, 1, 2, 2], + 'a' + ] + ) + ], + [ + 'Point with less fields', + new structure.Structure(0x58, [1, 2]) + ], + [ + 'Point with more fields', + new structure.Structure(0x58, [1, 2, 3, 4]) + ], + [ + 'Point3D with less fields', + new structure.Structure(0x59, [1, 2, 3]) + ], + + [ + 'Point3D with more fields', + new structure.Structure(0x59, [1, 2, 3, 4, 6]) + ], + [ + 'Duration with less fields', + new structure.Structure(0x45, [1, 2, 3]) + ], + [ + 'Duration with more fields', + new structure.Structure(0x45, [1, 2, 3, 4, 5]) + ], + [ + 'LocalTime with less fields', + new structure.Structure(0x74, []) + ], + [ + 'LocalTime with more fields', + new structure.Structure(0x74, [1, 2]) + ], + [ + 'Time with less fields', + new structure.Structure(0x54, [1]) + ], + [ + 'Time with more fileds', + new structure.Structure(0x54, [1, 2, 3]) + ], + [ + 'Date with less fields', + new structure.Structure(0x44, []) + ], + [ + 'Date with more fields', + new structure.Structure(0x44, [1, 2]) + ], + [ + 'LocalDateTime with less fields', + new structure.Structure(0x64, [1]) + ], + [ + 'LocalDateTime with more fields', + new structure.Structure(0x64, [1, 2, 3]) + ], + [ + 'DateTimeWithZoneOffset with less fields', + new structure.Structure(0x49, [1, 2]) + ], + [ + 'DateTimeWithZoneOffset with more fields', + new structure.Structure(0x49, [1, 2, 3, 4]) + ], + [ + 'DateTimeWithZoneId with less fields', + new structure.Structure(0x69, [1, 2]) + ], + [ + 'DateTimeWithZoneId with more fields', + new structure.Structure(0x69, [1, 2, 'America/Sao Paulo', 'Brasil']) + ] + ])('should not unpack with wrong size (%s)', (_, struct) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + false + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(() => unpacked instanceof structure.Structure).toThrowErrorMatchingSnapshot() + }) + + it.each([ + [ + 'Point', + new structure.Structure(0x58, [1, 2, 3]), + new Point(1, 2, 3) + ], + [ + 'Point3D', + new structure.Structure(0x59, [1, 2, 3, 4]), + new Point(1, 2, 3, 4) + ], + [ + 'Duration', + new structure.Structure(0x45, [1, 2, 3, 4]), + new Duration(1, 2, 3, 4) + ], + [ + 'LocalTime', + new structure.Structure(0x74, [1]), + new LocalTime(0, 0, 0, 1) + ], + [ + 'Time', + new structure.Structure(0x54, [1, 2]), + new Time(0, 0, 0, 1, 2) + ], + [ + 'Date', + new structure.Structure(0x44, [1]), + new Date(1970, 1, 2) + ], + [ + 'LocalDateTime', + new structure.Structure(0x64, [1, 2]), + new LocalDateTime(1970, 1, 1, 0, 0, 1, 2) + ], + [ + 'DateTimeWithZoneOffset', + new structure.Structure(0x49, [ + 1655212878, 183_000_000, 120 * 60 + ]), + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 120 * 60) + ], + [ + 'DateTimeWithZoneOffset / 1978', + new structure.Structure(0x49, [ + 282659759, 128000987, -150 * 60 + ]), + new DateTime(1978, 12, 16, 10, 5, 59, 128000987, -150 * 60) + ], + [ + 'DateTimeWithZoneId', + new structure.Structure(0x69, [ + 1655212878, 183_000_000, 'Europe/Berlin' + ]), + new DateTime(2022, 6, 14, 15, 21, 18, 183_000_000, 2 * 60 * 60, 'Europe/Berlin') + ], + [ + 'DateTimeWithZoneId / Australia', + new structure.Structure(0x69, [ + 1655212878, 183_000_000, 'Australia/Eucla' + ]), + new DateTime(2022, 6, 14, 22, 6, 18, 183_000_000, 8 * 60 * 60 + 45 * 60, 'Australia/Eucla') + ], + [ + 'DateTimeWithZoneId / Honolulu', + new structure.Structure(0x69, [ + 1592231400, 183_000_000, 'Pacific/Honolulu' + ]), + new DateTime(2020, 6, 15, 4, 30, 0, 183_000_000, -10 * 60 * 60, 'Pacific/Honolulu') + ] + ])('should unpack spatial types and temporal types (%s)', (_, struct, object) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(object) + }) + + it.each([ + [ + 'DateTimeWithZoneOffset/0x46', + new structure.Structure(0x46, [1, 2, 3]) + ], + [ + 'DateTimeWithZoneId/0x66', + new structure.Structure(0x66, [1, 2, 'America/Sao_Paulo']) + ] + ])('should unpack deprecated temporal types as unknown structs (%s)', (_, struct) => { + const buffer = alloc(256) + const protocol = new BoltProtocolV5x2( + new utils.MessageRecordingConnection(), + buffer, + { + disableLosslessIntegers: true + } + ) + + const packable = protocol.packable(struct) + + expect(packable).not.toThrow() + + buffer.reset() + + const unpacked = protocol.unpack(buffer) + expect(unpacked).toEqual(struct) + }) + }) + + function newProtocol (recorder) { + return new BoltProtocolV5x2(recorder, null, false, undefined, undefined, () => {}) + } +}) diff --git a/packages/bolt-connection/test/bolt/index.test.js b/packages/bolt-connection/test/bolt/index.test.js index 49273b1ca..1d550e7c8 100644 --- a/packages/bolt-connection/test/bolt/index.test.js +++ b/packages/bolt-connection/test/bolt/index.test.js @@ -31,6 +31,8 @@ import BoltProtocolV4x2 from '../../src/bolt/bolt-protocol-v4x2' import BoltProtocolV4x3 from '../../src/bolt/bolt-protocol-v4x3' import BoltProtocolV4x4 from '../../src/bolt/bolt-protocol-v4x4' import BoltProtocolV5x0 from '../../src/bolt/bolt-protocol-v5x0' +import BoltProtocolV5x1 from '../../src/bolt/bolt-protocol-v5x1' +import BoltProtocolV5x2 from '../../src/bolt/bolt-protocol-v5x2' const { logger: { Logger } @@ -44,17 +46,17 @@ describe('#unit Bolt', () => { const writtenBuffer = channel.written[0] const boltMagicPreamble = '60 60 b0 17' - const protocolVersion5x1to5x0 = '00 01 01 05' + const protocolVersion5x2to5x0 = '00 02 02 05' const protocolVersion4x4to4x2 = '00 02 04 04' const protocolVersion4x1 = '00 00 01 04' const protocolVersion3 = '00 00 00 03' expect(writtenBuffer.toHex()).toEqual( - `${boltMagicPreamble} ${protocolVersion5x1to5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}` + `${boltMagicPreamble} ${protocolVersion5x2to5x0} ${protocolVersion4x4to4x2} ${protocolVersion4x1} ${protocolVersion3}` ) }) - it('should handle a successful handshake without reaining buffer', done => { + it('should handle a successful handshake without remaining buffer', done => { const { channel, handshakePromise } = subject() const expectedProtocolVersion = 4.3 @@ -360,7 +362,8 @@ describe('#unit Bolt', () => { v(4.3, BoltProtocolV4x3), v(4.4, BoltProtocolV4x4), v(5.0, BoltProtocolV5x0), - v(5.1, BoltProtocolV5x0) + v(5.1, BoltProtocolV5x1), + v(5.2, BoltProtocolV5x2) ] availableProtocols.forEach(lambda) diff --git a/packages/bolt-connection/test/bolt/request-message.test.js b/packages/bolt-connection/test/bolt/request-message.test.js index f0f66dd68..7f86f9d09 100644 --- a/packages/bolt-connection/test/bolt/request-message.test.js +++ b/packages/bolt-connection/test/bolt/request-message.test.js @@ -19,6 +19,7 @@ import RequestMessage from '../../src/bolt/request-message' import { internal, int, json } from 'neo4j-driver-core' +import { notificationFilterBehaviour } from './behaviour' const { bookmarks: { Bookmarks }, @@ -429,4 +430,123 @@ describe('#unit RequestMessage', () => { }) }) }) + + describe('BoltV5.2', () => { + it.each( + notificationFilterFixtures() + )('should create HELLO message where notificationFilters=%o', (notificationFilter, expectedNotificationFilter) => { + const userAgent = 'my-driver/1.0.2' + const message = RequestMessage.hello5x2(userAgent, notificationFilter) + + const expectedFields = { user_agent: userAgent, ...expectedNotificationFilter } + + expect(message.signature).toEqual(0x01) + expect(message.fields).toEqual([ + expectedFields + ]) + expect(message.toString()).toEqual( + `HELLO ${json.stringify(expectedFields)}` + ) + }) + + it.each( + notificationFilterFixtures() + )('should create BEGIN message where notificationFilters=%o', (notificationFilter, expectedNotificationFilter) => { + ;[READ, WRITE].forEach(mode => { + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx10' + ]) + const impersonatedUser = 'the impostor' + const txConfig = new TxConfig({ timeout: 42, metadata: { key: 42 } }) + + const message = RequestMessage.begin({ bookmarks, txConfig, mode, impersonatedUser, notificationFilter }) + + const expectedMode = {} + if (mode === READ) { + expectedMode.mode = 'r' + } + const expectedMetadata = { + bookmarks: bookmarks.values(), + tx_timeout: int(42), + tx_metadata: { key: 42 }, + imp_user: impersonatedUser, + ...expectedMode, + ...expectedNotificationFilter + } + + expect(message.signature).toEqual(0x11) + expect(message.fields).toEqual([expectedMetadata]) + expect(message.toString()).toEqual( + `BEGIN ${json.stringify(expectedMetadata)}` + ) + }) + }) + + it.each( + notificationFilterFixtures() + )('should create RUN message where notificationFilters=%o', (notificationFilter, expectedNotificationFilter) => { + ;[READ, WRITE].forEach(mode => { + const query = 'RETURN $x' + const parameters = { x: 42 } + const bookmarks = new Bookmarks([ + 'neo4j:bookmark:v1:tx1', + 'neo4j:bookmark:v1:tx10', + 'neo4j:bookmark:v1:tx100' + ]) + const txConfig = new TxConfig({ + timeout: 999, + metadata: { a: 'a', b: 'b' } + }) + const impersonatedUser = 'the impostor' + + const message = RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + mode, + impersonatedUser, + notificationFilter + }) + + const expectedMode = {} + if (mode === READ) { + expectedMode.mode = 'r' + } + + const expectedMetadata = { + bookmarks: bookmarks.values(), + tx_timeout: int(999), + tx_metadata: { a: 'a', b: 'b' }, + imp_user: impersonatedUser, + ...expectedMode, + ...expectedNotificationFilter + } + + expect(message.signature).toEqual(0x10) + expect(message.fields).toEqual([query, parameters, expectedMetadata]) + expect(message.toString()).toEqual( + `RUN ${query} ${json.stringify(parameters)} ${json.stringify( + expectedMetadata + )}` + ) + }) + }) + + function notificationFilterFixtures () { + return notificationFilterBehaviour.notificationFilterFixture() + .map(notificationFilter => { + const expectedNotificationFilter = {} + if (notificationFilter) { + if (notificationFilter.minimumSeverityLevel) { + expectedNotificationFilter.notifications_minimum_severity = notificationFilter.minimumSeverityLevel + } + + if (notificationFilter.disabledCategories) { + expectedNotificationFilter.notifications_disabled_categories = notificationFilter.disabledCategories + } + } + return [notificationFilter, expectedNotificationFilter] + }) + } + }) }) diff --git a/packages/bolt-connection/test/connection/connection-channel.test.js b/packages/bolt-connection/test/connection/connection-channel.test.js index 21696bd27..18200eea5 100644 --- a/packages/bolt-connection/test/connection/connection-channel.test.js +++ b/packages/bolt-connection/test/connection/connection-channel.test.js @@ -19,6 +19,7 @@ import ChannelConnection from '../../src/connection/connection-channel' import { int, internal, newError } from 'neo4j-driver-core' +import { notificationFilterBehaviour } from '../bolt/behaviour' const { serverAddress: { ServerAddress }, @@ -124,6 +125,30 @@ describe('ChannelConnection', () => { ) } ) + + it.each( + notificationFilterBehaviour.notificationFilterFixture() + )( + 'should send notificationFilter=%o to initialize ', + async (notificationFilter) => { + const channel = { + setupReceiveTimeout: jest.fn().mockName('setupReceiveTimeout') + } + const protocol = { + initialize: jest.fn(observer => + observer.onComplete({}) + ) + } + const protocolSupplier = () => protocol + const connection = spyOnConnectionChannel({ channel, protocolSupplier, notificationFilter }) + + await connection.connect('userAgent', {}) + + const call = protocol.initialize.mock.calls[0][0] + + expect(call.notificationFilter).toBe(notificationFilter) + } + ) }) describe('._handleFatalError()', () => { @@ -522,6 +547,7 @@ describe('ChannelConnection', () => { disableLosslessIntegers, serversideRouting, chuncker, + notificationFilter, protocolSupplier }) { address = address || ServerAddress.fromUrl('bolt://localhost') @@ -534,6 +560,7 @@ describe('ChannelConnection', () => { disableLosslessIntegers, serversideRouting, chuncker, + notificationFilter, protocolSupplier ) } diff --git a/packages/core/src/internal/constants.ts b/packages/core/src/internal/constants.ts index 1b9519387..d42d1da1c 100644 --- a/packages/core/src/internal/constants.ts +++ b/packages/core/src/internal/constants.ts @@ -35,6 +35,7 @@ const BOLT_PROTOCOL_V4_3: number = 4.3 const BOLT_PROTOCOL_V4_4: number = 4.4 const BOLT_PROTOCOL_V5_0: number = 5.0 const BOLT_PROTOCOL_V5_1: number = 5.1 +const BOLT_PROTOCOL_V5_2: number = 5.2 export { FETCH_ALL, @@ -52,5 +53,6 @@ export { BOLT_PROTOCOL_V4_3, BOLT_PROTOCOL_V4_4, BOLT_PROTOCOL_V5_0, - BOLT_PROTOCOL_V5_1 + BOLT_PROTOCOL_V5_1, + BOLT_PROTOCOL_V5_2 } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js new file mode 100644 index 000000000..9a77a3a10 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.js @@ -0,0 +1,172 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import BoltProtocolV5x1 from './bolt-protocol-v5x1.js' + +import transformersFactories from './bolt-protocol-v5x2.transformer.js' +import Transformer from './transformer.js' +import RequestMessage from './request-message.js' +import { LoginObserver, ResultStreamObserver } from './stream-observers.js' + +import { internal } from '../../core/index.ts' + +const { + constants: { BOLT_PROTOCOL_V5_2, FETCH_ALL } +} = internal + +export default class BoltProtocol extends BoltProtocolV5x1 { + get version () { + return BOLT_PROTOCOL_V5_2 + } + + get transformer () { + if (this._transformer === undefined) { + this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log))) + } + return this._transformer + } + + get supportsReAuth () { + return true + } + + /** + * Initialize a connection with the server + * + * @param {Object} args The params + * @param {string} args.userAgent The user agent + * @param {any} args.authToken The auth token + * @param {NotificationFilter} args.notificationFilter The notification filters. + * @param {function(error)} args.onError On error callback + * @param {function(onComplete)} args.onComplete On complete callback + * @returns {LoginObserver} The Login observer + */ + initialize ({ userAgent, authToken, notificationFilter, onError, onComplete } = {}) { + const state = {} + const observer = new LoginObserver({ + onError: error => this._onLoginError(error, onError), + onCompleted: metadata => { + state.metadata = metadata + return this._onLoginCompleted(metadata) + } + }) + + this.write( + RequestMessage.hello5x2(userAgent, notificationFilter, this._serversideRouting), + observer, + false + ) + + return this.logon({ + authToken, + onComplete: metadata => onComplete({ ...metadata, ...state.metadata }), + onError, + flush: true + }) + } + + beginTransaction ({ + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter, + beforeError, + afterError, + beforeComplete, + afterComplete + } = {}) { + const observer = new ResultStreamObserver({ + server: this._server, + beforeError, + afterError, + beforeComplete, + afterComplete + }) + observer.prepareToHandleSingleResponse() + + this.write( + RequestMessage.begin({ bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter }), + observer, + true + ) + + return observer + } + + run ( + query, + parameters, + { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter, + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + flush = true, + reactive = false, + fetchSize = FETCH_ALL, + highRecordWatermark = Number.MAX_VALUE, + lowRecordWatermark = Number.MAX_VALUE + } = {} + ) { + const observer = new ResultStreamObserver({ + server: this._server, + reactive: reactive, + fetchSize: fetchSize, + moreFunction: this._requestMore.bind(this), + discardFunction: this._requestDiscard.bind(this), + beforeKeys, + afterKeys, + beforeError, + afterError, + beforeComplete, + afterComplete, + highRecordWatermark, + lowRecordWatermark + }) + + const flushRun = reactive + this.write( + RequestMessage.runWithMetadata(query, parameters, { + bookmarks, + txConfig, + database, + mode, + impersonatedUser, + notificationFilter + }), + observer, + flushRun && flush + ) + + if (!reactive) { + this.write(RequestMessage.pull({ n: fetchSize }), observer, flush) + } + + return observer + } +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js new file mode 100644 index 000000000..b8583e846 --- /dev/null +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import v5x0 from './bolt-protocol-v5x0.transformer.js' + +export default { + ...v5x0 +} diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js index 45c33eef2..95cbbbb8a 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js @@ -28,6 +28,7 @@ import BoltProtocolV4x3 from './bolt-protocol-v4x3.js' import BoltProtocolV4x4 from './bolt-protocol-v4x4.js' import BoltProtocolV5x0 from './bolt-protocol-v5x0.js' import BoltProtocolV5x1 from './bolt-protocol-v5x1.js' +import BoltProtocolV5x2 from './bolt-protocol-v5x2.js' // eslint-disable-next-line no-unused-vars import { Chunker, Dechunker } from '../channel/index.js' import ResponseHandler from './response-handler.js' @@ -202,6 +203,16 @@ function createProtocol ( onProtocolError, serversideRouting ) + case 5.2: + return new BoltProtocolV5x2( + server, + chunker, + packingConfig, + createResponseHandler, + log, + onProtocolError, + serversideRouting + ) default: throw newError('Unknown Bolt protocol version: ' + version) } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js index f8c0de714..ef7ff76f1 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js @@ -76,7 +76,7 @@ function parseNegotiatedResponse (buffer) { */ function newHandshakeBuffer () { return createHandshakeMessage([ - [version(5, 1), version(5, 0)], + [version(5, 2), version(5, 0)], [version(4, 4), version(4, 2)], version(4, 1), version(3, 0) diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js index fc9d6a07d..574cdee80 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/request-message.js @@ -160,6 +160,37 @@ export default class RequestMessage { ) } + /** + * Create a new HELLO message. + * @param {string} userAgent the user agent. + * @param {NotificationFilter} notificationFilter the notification filter configured + * @param {Object} routing server side routing, set to routing context to turn on server side routing (> 4.1) + * @return {RequestMessage} new HELLO message. + */ + static hello5x2 (userAgent, notificationFilter = null, routing = null) { + const metadata = { user_agent: userAgent } + + if (notificationFilter) { + if (notificationFilter.minimumSeverityLevel) { + metadata.notifications_minimum_severity = notificationFilter.minimumSeverityLevel + } + + if (notificationFilter.disabledCategories) { + metadata.notifications_disabled_categories = notificationFilter.disabledCategories + } + } + + if (routing) { + metadata.routing = routing + } + + return new RequestMessage( + HELLO, + [metadata], + () => `HELLO ${json.stringify(metadata)}` + ) + } + /** * Create a new LOGON message. * @@ -194,10 +225,11 @@ export default class RequestMessage { * @param {string} database the database name. * @param {string} mode the access mode. * @param {string} impersonatedUser the impersonated user. + * @param {NotificationFilter} notificationFilter the notification filter * @return {RequestMessage} new BEGIN message. */ - static begin ({ bookmarks, txConfig, database, mode, impersonatedUser } = {}) { - const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser) + static begin ({ bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter } = {}) { + const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter) return new RequestMessage( BEGIN, [metadata], @@ -235,9 +267,9 @@ export default class RequestMessage { static runWithMetadata ( query, parameters, - { bookmarks, txConfig, database, mode, impersonatedUser } = {} + { bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter } = {} ) { - const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser) + const metadata = buildTxMetadata(bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter) return new RequestMessage( RUN, [query, parameters, metadata], @@ -348,9 +380,10 @@ export default class RequestMessage { * @param {string} database the database name. * @param {string} mode the access mode. * @param {string} impersonatedUser the impersonated user mode. + * @param {notificationFilter} notificationFilter the notification filter * @return {Object} a metadata object. */ -function buildTxMetadata (bookmarks, txConfig, database, mode, impersonatedUser) { +function buildTxMetadata (bookmarks, txConfig, database, mode, impersonatedUser, notificationFilter) { const metadata = {} if (!bookmarks.isEmpty()) { metadata.bookmarks = bookmarks.values() @@ -370,6 +403,15 @@ function buildTxMetadata (bookmarks, txConfig, database, mode, impersonatedUser) if (mode === ACCESS_MODE_READ) { metadata.mode = READ_MODE } + if (notificationFilter) { + if (notificationFilter.minimumSeverityLevel) { + metadata.notifications_minimum_severity = notificationFilter.minimumSeverityLevel + } + + if (notificationFilter.disabledCategories) { + metadata.notifications_disabled_categories = notificationFilter.disabledCategories + } + } return metadata } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js index b676085a0..79dd0e4b9 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/connection/connection-channel.js @@ -86,6 +86,7 @@ export function createChannelConnection ( config.disableLosslessIntegers, serversideRouting, chunker, + config.notificationFilter, createProtocol ) @@ -119,6 +120,7 @@ export default class ChannelConnection extends Connection { disableLosslessIntegers = false, serversideRouting = null, chunker, // to be removed, + notificationFilter, protocolSupplier ) { super(errorHandler) @@ -134,6 +136,7 @@ export default class ChannelConnection extends Connection { this._chunker = chunker this._log = createConnectionLogger(this, log) this._serversideRouting = serversideRouting + this._notificationFilter = notificationFilter // connection from the database, returned in response for HELLO message and might not be available this._dbConnectionId = null @@ -187,6 +190,7 @@ export default class ChannelConnection extends Connection { this._protocol.initialize({ userAgent, authToken, + notificationFilter: this._notificationFilter, onError: err => reject(err), onComplete: metadata => { if (metadata) { diff --git a/packages/neo4j-driver-deno/lib/core/internal/constants.ts b/packages/neo4j-driver-deno/lib/core/internal/constants.ts index 1b9519387..d42d1da1c 100644 --- a/packages/neo4j-driver-deno/lib/core/internal/constants.ts +++ b/packages/neo4j-driver-deno/lib/core/internal/constants.ts @@ -35,6 +35,7 @@ const BOLT_PROTOCOL_V4_3: number = 4.3 const BOLT_PROTOCOL_V4_4: number = 4.4 const BOLT_PROTOCOL_V5_0: number = 5.0 const BOLT_PROTOCOL_V5_1: number = 5.1 +const BOLT_PROTOCOL_V5_2: number = 5.2 export { FETCH_ALL, @@ -52,5 +53,6 @@ export { BOLT_PROTOCOL_V4_3, BOLT_PROTOCOL_V4_4, BOLT_PROTOCOL_V5_0, - BOLT_PROTOCOL_V5_1 + BOLT_PROTOCOL_V5_1, + BOLT_PROTOCOL_V5_2 } From 0010a93305567f8998f0b0c1dc1efcefe2dcc944 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Thu, 16 Mar 2023 11:43:35 +0100 Subject: [PATCH 13/15] Add support for testkit --- packages/testkit-backend/src/feature/common.js | 3 +++ .../testkit-backend/src/request-handlers-rx.js | 10 +++++++++- .../testkit-backend/src/request-handlers.js | 17 ++++++++++++++++- packages/testkit-backend/src/summary-binder.js | 1 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/testkit-backend/src/feature/common.js b/packages/testkit-backend/src/feature/common.js index bd270cf3e..99181a219 100644 --- a/packages/testkit-backend/src/feature/common.js +++ b/packages/testkit-backend/src/feature/common.js @@ -17,11 +17,14 @@ const features = [ 'Feature:Bolt:4.4', 'Feature:Bolt:5.0', 'Feature:Bolt:5.1', + 'Feature:Bolt:5.2', 'Feature:Bolt:Patch:UTC', 'Feature:API:ConnectionAcquisitionTimeout', 'Feature:API:Driver.ExecuteQuery', + 'Feature:API:Driver:NotificationsConfig', 'Feature:API:Driver:GetServerInfo', 'Feature:API:Driver.VerifyConnectivity', + 'Feature:API:Session:NotificationsConfig', 'Optimization:EagerTransactionBegin', 'Optimization:ImplicitDefaultArguments', 'Optimization:MinimalBookmarksSet', diff --git a/packages/testkit-backend/src/request-handlers-rx.js b/packages/testkit-backend/src/request-handlers-rx.js index b48ff1e28..1d0762856 100644 --- a/packages/testkit-backend/src/request-handlers-rx.js +++ b/packages/testkit-backend/src/request-handlers-rx.js @@ -46,6 +46,13 @@ export function NewSession (neo4j, context, data, wire) { return } } + let notificationFilter + if ('notificationsMinSeverity' in data || 'notificationsDisabledCategories' in data) { + notificationFilter = { + minimumSeverityLevel: data.notificationsMinSeverity, + disabledCategories: data.notificationsDisabledCategories + } + } const driver = context.getDriver(driverId) const session = driver.rxSession({ defaultAccessMode: accessMode, @@ -53,7 +60,8 @@ export function NewSession (neo4j, context, data, wire) { database, fetchSize, impersonatedUser, - bookmarkManager + bookmarkManager, + notificationFilter }) const id = context.addSession(session) wire.writeResponse(responses.Session({ id })) diff --git a/packages/testkit-backend/src/request-handlers.js b/packages/testkit-backend/src/request-handlers.js index 82d6f2e30..295033053 100644 --- a/packages/testkit-backend/src/request-handlers.js +++ b/packages/testkit-backend/src/request-handlers.js @@ -39,6 +39,7 @@ export function NewDriver (neo4j, context, data, wire) { authToken.parameters ) } + const resolver = resolverRegistered ? address => new Promise((resolve, reject) => { @@ -83,6 +84,12 @@ export function NewDriver (neo4j, context, data, wire) { if ('maxTxRetryTimeMs' in data) { config.maxTransactionRetryTime = data.maxTxRetryTimeMs } + if ('notificationsMinSeverity' in data || 'notificationsDisabledCategories' in data) { + config.notificationFilter = { + minimumSeverityLevel: data.notificationsMinSeverity, + disabledCategories: data.notificationsDisabledCategories + } + } let driver try { driver = neo4j.driver(uri, parsedAuthToken, config) @@ -126,6 +133,13 @@ export function NewSession (neo4j, context, data, wire) { return } } + let notificationFilter + if ('notificationsMinSeverity' in data || 'notificationsDisabledCategories' in data) { + notificationFilter = { + minimumSeverityLevel: data.notificationsMinSeverity, + disabledCategories: data.notificationsDisabledCategories + } + } const driver = context.getDriver(driverId) const session = driver.session({ defaultAccessMode: accessMode, @@ -133,7 +147,8 @@ export function NewSession (neo4j, context, data, wire) { database, fetchSize, impersonatedUser, - bookmarkManager + bookmarkManager, + notificationFilter }) const id = context.addSession(session) wire.writeResponse(responses.Session({ id })) diff --git a/packages/testkit-backend/src/summary-binder.js b/packages/testkit-backend/src/summary-binder.js index d7221a3ea..62af6c1d0 100644 --- a/packages/testkit-backend/src/summary-binder.js +++ b/packages/testkit-backend/src/summary-binder.js @@ -42,6 +42,7 @@ function mapProfile (profile, child = false, binder) { function mapNotification (notification) { return { ...notification, + rawCategory: notification.rawCategory || '', position: Object.keys(notification.position).length !== 0 ? notification.position : undefined } } From caed0596b4b4cadfe0d562cfd2fe4b465627ea37 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Fri, 17 Mar 2023 10:07:02 +0100 Subject: [PATCH 14/15] Fix 5.2 transformer inehitance chain. --- .../src/bolt/bolt-protocol-v5x2.transformer.js | 4 ++-- .../bolt-connection/bolt/bolt-protocol-v5x2.transformer.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js index 0923aef4d..e3649215d 100644 --- a/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js +++ b/packages/bolt-connection/src/bolt/bolt-protocol-v5x2.transformer.js @@ -17,8 +17,8 @@ * limitations under the License. */ -import v5x0 from './bolt-protocol-v5x0.transformer' +import v5x1 from './bolt-protocol-v5x1.transformer' export default { - ...v5x0 + ...v5x1 } diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js index b8583e846..9cbb0c2e2 100644 --- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js +++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v5x2.transformer.js @@ -17,8 +17,8 @@ * limitations under the License. */ -import v5x0 from './bolt-protocol-v5x0.transformer.js' +import v5x1 from './bolt-protocol-v5x1.transformer.js' export default { - ...v5x0 + ...v5x1 } From b0b4a4d5d6e48869d2210fbe81ed59b5abc829fd Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Fri, 17 Mar 2023 10:53:17 +0100 Subject: [PATCH 15/15] Polyfill flatMap for generate tests fixtures --- .../test/bolt/behaviour/notification-filter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/bolt-connection/test/bolt/behaviour/notification-filter.js b/packages/bolt-connection/test/bolt/behaviour/notification-filter.js index 5bc88de85..22e9659d8 100644 --- a/packages/bolt-connection/test/bolt/behaviour/notification-filter.js +++ b/packages/bolt-connection/test/bolt/behaviour/notification-filter.js @@ -246,6 +246,14 @@ function notificationFilterSetFixture () { const disabledCategories = Object.values(notificationFilterDisabledCategory) const disabledCategoriesSet = [...disabledCategories.keys()] .map(length => disabledCategories.slice(0, length + 1)) + + /** Polyfill flatMap for Node10 tests */ + if (!minimumSeverityLevelSet.flatMap) { + minimumSeverityLevelSet.flatMap = function (callback, thisArg) { + return minimumSeverityLevelSet.concat.apply([], minimumSeverityLevelSet.map(callback, thisArg)) + } + } + return [ {}, ...minimumSeverityLevelSet.map(minimumSeverityLevel => ({ minimumSeverityLevel })),