diff --git a/README.md b/README.md
index b176bd13c..e04bf85c3 100644
--- a/README.md
+++ b/README.md
@@ -1,505 +1,28 @@
# Neo4j Driver for JavaScript
+**This is a preview branch for the new Vector type in the JavaScript driver.**
-This is the official Neo4j driver for JavaScript.
+The 6.0 release of the JavaScript driver will introduce the ability to read and write Vectors to the database.
+These are single type arrays with inner types of int 8/16/32/64 and float 32/64.
+To facilitate ease of use and good integration with AI packages in JavaScript, Vectors will be represented by JavaScript TypedArrays, wrapped inside a new Vector type much like Integers.
-Starting with 5.0, the Neo4j Drivers will be moving to a monthly release cadence. A minor version will be released on the last Friday of each month so as to maintain versioning consistency with the core product (Neo4j DBMS) which has also moved to a monthly cadence.
+A number of pieces of example code can be found in the Vector types examples test file [here](./packages/neo4j-driver/test/vector-examples.test.js)
-As a policy, patch versions will not be released except on rare occasions. Bug fixes and updates will go into the latest minor version and users should upgrade to that. Driver upgrades within a major version will never contain breaking API changes.
+Vector types are usuable from bolt version 6.0 and forward, which no current server currently supports, so this can not be tested against any live Neo4j Database at this time.
-See also: https://neo4j.com/developer/kb/neo4j-supported-versions/
+## Example code
-Resources to get you started:
-
-- [API Documentation](https://neo4j.com/docs/api/javascript-driver/current/)
-- [Neo4j Manual](https://neo4j.com/docs/)
-- [Neo4j Refcard](https://neo4j.com/docs/cypher-refcard/current/)
-
-## What's New in 5.x
-
-- [Changelog](https://github.com/neo4j/neo4j-javascript-driver/wiki/5.0-changelog)
-
-## Including the Driver
-
-### In Node.js application
-
-Stable channel:
-
-```shell
-npm install neo4j-driver
-```
-
-Pre-release channel:
-
-```shell
-npm install neo4j-driver@next
-```
-
-Please note that `@next` only points to pre-releases that are not suitable for production use.
-To get the latest stable release omit `@next` part altogether or use `@latest` instead.
-
-```javascript
-var neo4j = require('neo4j-driver')
-```
-
-Driver instance should be closed when Node.js application exits:
-
-```javascript
-driver.close() // returns a Promise
-```
-
-otherwise application shutdown might hang or it might exit with a non-zero exit code.
-
-### In web browser
-
-We build a special browser version of the driver, which supports connecting to Neo4j over WebSockets.
-It can be included in an HTML page using one of the following tags:
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-This will make a global `neo4j` object available, where you can create a driver instance with `neo4j.driver`:
-
-```javascript
-var driver = neo4j.driver(
- 'neo4j://localhost',
- neo4j.auth.basic('neo4j', 'password')
-)
-```
-
-From `5.4.0`, this version is also exported as ECMA Script Module.
-It can be imported from a module using the following statements:
-
-```javascript
-// Direct reference
-import neo4j from 'lib/browser/neo4j-web.esm.min.js'
-
-// unpkg CDN non-minified , version X.Y.Z where X.Y.Z >= 5.4.0
-import neo4j from 'https://unpkg.com/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.js'
-
-// unpkg CDN minified for production use, version X.Y.Z where X.Y.Z >= 5.4.0
-import neo4j from 'https://unpkg.com/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.min.js'
-
-// jsDelivr CDN non-minified, version X.Y.Z where X.Y.Z >= 5.4.0
-import neo4j from 'https://cdn.jsdelivr.net/npm/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.js'
-
-// jsDelivr CDN minified for production use, version X.Y.Z where X.Y.Z >= 5.4.0
-import neo4j from 'https://cdn.jsdelivr.net/npm/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.min.js'
-
-```
-
-It is not required to explicitly close the driver on a web page. Web browser should gracefully close all open
-WebSockets when the page is unloaded. However, driver instance should be explicitly closed when it's lifetime
-is not the same as the lifetime of the web page:
-
-```javascript
-driver.close() // returns a Promise
-```
-
-## Usage examples
-
-### Constructing a Driver
-
-```javascript
-// Create a driver instance, for the user `neo4j` with password `password`.
-// It should be enough to have a single driver per database per application.
-var driver = neo4j.driver(
- 'neo4j://localhost',
- neo4j.auth.basic('neo4j', 'password')
-)
-
-// Close the driver when application exits.
-// This closes all used network connections.
-await driver.close()
-```
-
-### Acquiring a Session
-
-#### Regular Session
-
-```javascript
-// Create a session to run Cypher statements in.
-// Note: Always make sure to close sessions when you are done using them!
-var session = driver.session()
-```
-
-##### with a Default Access Mode of `READ`
-
-```javascript
-var session = driver.session({ defaultAccessMode: neo4j.session.READ })
-```
-
-##### with Bookmarks
-
-```javascript
-var session = driver.session({
- bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession]
-})
-```
-
-##### against a Database
-
-```javascript
-var session = driver.session({
- database: 'foo',
- defaultAccessMode: neo4j.session.WRITE
-})
-```
-
-#### Reactive Session
-
-```javascript
-// Create a reactive session to run Cypher statements in.
-// Note: Always make sure to close sessions when you are done using them!
-var rxSession = driver.rxSession()
-```
-
-##### with a Default Access Mode of `READ`
-
-```javascript
-var rxSession = driver.rxSession({ defaultAccessMode: neo4j.session.READ })
-```
-
-##### with Bookmarks
-
-```javascript
-var rxSession = driver.rxSession({
- bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession]
-})
-```
-
-##### against a Database
-
-```javascript
-var rxSession = driver.rxSession({
- database: 'foo',
- defaultAccessMode: neo4j.session.WRITE
-})
-```
-
-### Transaction functions
-
-```javascript
-// Transaction functions provide a convenient API with minimal boilerplate and
-// retries on network fluctuations and transient errors. Maximum retry time is
-// configured on the driver level and is 30 seconds by default:
-// Applies both to standard and reactive sessions.
-neo4j.driver('neo4j://localhost', neo4j.auth.basic('neo4j', 'password'), {
- maxTransactionRetryTime: 30000
-})
-```
-
-#### Reading with Async Session
-
-```javascript
-// It is possible to execute read transactions that will benefit from automatic
-// retries on both single instance ('bolt' URI scheme) and Causal Cluster
-// ('neo4j' URI scheme) and will get automatic load balancing in cluster deployments
-var readTxResultPromise = session.executeRead(txc => {
- // used transaction will be committed automatically, no need for explicit commit/rollback
-
- var result = txc.run('MATCH (person:Person) RETURN person.name AS name')
- // at this point it is possible to either return the result or process it and return the
- // result of processing it is also possible to run more statements in the same transaction
- return result
-})
-
-// returned Promise can be later consumed like this:
-readTxResultPromise
- .then(result => {
- console.log(result.records)
- })
- .catch(error => {
- console.log(error)
- })
- .then(() => session.close())
-```
-
-#### Reading with Reactive Session
-
-```javascript
-rxSession
- .executeRead(txc =>
- txc
- .run('MATCH (person:Person) RETURN person.name AS name')
- .records()
- .pipe(map(record => record.get('name')))
- )
- .subscribe({
- next: data => console.log(data),
- complete: () => console.log('completed'),
- error: err => console.log(error)
- })
-```
-
-#### Writing with Async Session
-
-```javascript
-// It is possible to execute write transactions that will benefit from automatic retries
-// on both single instance ('bolt' URI scheme) and Causal Cluster ('neo4j' URI scheme)
-var writeTxResultPromise = session.executeWrite(async txc => {
- // used transaction will be committed automatically, no need for explicit commit/rollback
-
- var result = await txc.run(
- "MERGE (alice:Person {name : 'Alice'}) RETURN alice.name AS name"
- )
- // at this point it is possible to either return the result or process it and return the
- // result of processing it is also possible to run more statements in the same transaction
- return result.records.map(record => record.get('name'))
-})
-
-// returned Promise can be later consumed like this:
-writeTxResultPromise
- .then(namesArray => {
- console.log(namesArray)
- })
- .catch(error => {
- console.log(error)
- })
- .then(() => session.close())
-```
-
-#### Writing with Reactive Session
-
-```javascript
-rxSession
- .executeWrite(txc =>
- txc
- .run("MERGE (alice:Person {name: 'James'}) RETURN alice.name AS name")
- .records()
- .pipe(map(record => record.get('name')))
- )
- .subscribe({
- next: data => console.log(data),
- complete: () => console.log('completed'),
- error: error => console.log(error)
- })
-```
-
-### Consuming Records
-
-#### Consuming Records with Streaming API
-
-```javascript
-// Run a Cypher statement, reading the result in a streaming manner as records arrive:
-session
- .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', {
- nameParam: 'Alice'
- })
- .subscribe({
- onKeys: keys => {
- console.log(keys)
- },
- onNext: record => {
- console.log(record.get('name'))
- },
- onCompleted: () => {
- session.close() // returns a Promise
- },
- onError: error => {
- console.log(error)
- }
- })
-```
-
-Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations:
-
-- zero or one `onKeys`,
-- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case
-- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case.
-
-#### Consuming Records with Promise API
-
-```javascript
-// the Promise way, where the complete result is collected before we act on it:
-session
- .run('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', {
- nameParam: 'James'
- })
- .then(result => {
- result.records.forEach(record => {
- console.log(record.get('name'))
+### Read and Write Vectors
+```Javascript
+ const driver = neo4j.driver(uri, sharedNeo4j.authToken)
+ await driver.executeQuery('CREATE (p:Product) SET p.embeddings = $embeddings', {
+ embeddings: neo4j.vector(Float32Array.from([0, 1, 2, 3])), //Typed arrays can be created from a regular list of Numbers
})
- })
- .catch(error => {
- console.log(error)
- })
- .then(() => session.close())
-```
-
-#### Consuming Records with Reactive API
-
-```javascript
-rxSession
- .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', {
- nameParam: 'Bob'
- })
- .records()
- .pipe(
- map(record => record.get('name')),
- concatWith(rxSession.close())
- )
- .subscribe({
- next: data => console.log(data),
- complete: () => console.log('completed'),
- error: err => console.log(err)
- })
-```
-
-### Explicit Transactions
-
-#### With Async Session
-
-```javascript
-// run statement in a transaction
-const txc = session.beginTransaction()
-try {
- const result1 = await txc.run(
- 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name',
- {
- nameParam: 'Bob'
- }
- )
- result1.records.forEach(r => console.log(r.get('name')))
- console.log('First query completed')
-
- const result2 = await txc.run(
- 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name',
- {
- nameParam: 'Adam'
- }
- )
- result2.records.forEach(r => console.log(r.get('name')))
- console.log('Second query completed')
-
- await txc.commit()
- console.log('committed')
-} catch (error) {
- console.log(error)
- await txc.rollback()
- console.log('rolled back')
-} finally {
- await session.close()
-}
-```
-
-#### With Reactive Session
-
-```javascript
-rxSession
- .beginTransaction()
- .pipe(
- mergeMap(txc =>
- concatWith(
- txc
- .run(
- 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name',
- {
- nameParam: 'Bob'
- }
- )
- .records()
- .pipe(map(r => r.get('name'))),
- of('First query completed'),
- txc
- .run(
- 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name',
- {
- nameParam: 'Adam'
- }
- )
- .records()
- .pipe(map(r => r.get('name'))),
- of('Second query completed'),
- txc.commit(),
- of('committed')
- ).pipe(catchError(err => txc.rollback().pipe(throwError(() => err))))
- )
- )
- .subscribe({
- next: data => console.log(data),
- complete: () => console.log('completed'),
- error: error => console.log(error)
- })
-```
-
-### Numbers and the Integer type
-
-The Neo4j type system uses 64-bit signed integer values. The range of values is between `-(2``64``- 1)` and `(2``63``- 1)`.
-
-However, JavaScript can only safely represent integers between `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`.
-
-In order to support the full Neo4j type system, the driver will not automatically convert to javascript integers.
-Any time the driver receives an integer value from Neo4j, it will be represented with an internal integer type by the driver.
-
-_**Any javascript number value passed as a parameter will be recognized as `Float` type.**_
-
-#### Writing integers
-
-Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j.
-
-To write the `age` as an integer the `neo4j.int` method should be used:
-
-```javascript
-var neo4j = require('neo4j-driver')
-
-session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) })
-```
-
-To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`:
-
-```javascript
-session.run('CREATE (n {age: $myIntParam})', {
- myIntParam: neo4j.int('9223372036854775807')
-})
-```
-
-#### Reading integers
-
-In Neo4j, the type Integer can be larger what can be represented safely as an integer with JavaScript Number.
-
-It is only safe to convert to a JavaScript Number if you know that the number will be in the range `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`.
-
-In order to facilitate working with integers the driver include `neo4j.isInt`, `neo4j.integer.inSafeRange`, `neo4j.integer.toNumber`, and `neo4j.integer.toString`.
-
-```javascript
-var smallInteger = neo4j.int(123)
-if (neo4j.integer.inSafeRange(smallInteger)) {
- var aNumber = smallInteger.toNumber()
-}
-```
-
-If you will be handling integers that is not within the JavaScript safe range of integers, you should convert the value to a string:
-
-```javascript
-var largeInteger = neo4j.int('9223372036854775807')
-if (!neo4j.integer.inSafeRange(largeInteger)) {
- var integerAsString = largeInteger.toString()
-}
-```
-
-#### Enabling native numbers
+ const res = await driver.executeQuery('MATCH (p:Product) RETURN p.embeddings as embeddings')
+
+ let vector = res.records[0].get('embeddings')
-Starting from 1.6 version of the driver it is possible to configure it to only return native numbers instead of custom `Integer` objects.
-The configuration option affects all integers returned by the driver. **Enabling this option can result in a loss of precision and incorrect numeric
-values being returned if the database contains integer numbers outside of the range** `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`.
-To enable potentially lossy integer values use the driver's configuration object:
+ console.log(vector.toTypedArray()) //Float32Array[0, 1, 2, 3]
+ console.log(vector.type) //FLOAT32
-```javascript
-var driver = neo4j.driver(
- 'neo4j://localhost',
- neo4j.auth.basic('neo4j', 'password'),
- { disableLosslessIntegers: true }
-)
+ await driver.close()
```
diff --git a/original_README.md b/original_README.md
new file mode 100644
index 000000000..b176bd13c
--- /dev/null
+++ b/original_README.md
@@ -0,0 +1,505 @@
+# Neo4j Driver for JavaScript
+
+This is the official Neo4j driver for JavaScript.
+
+Starting with 5.0, the Neo4j Drivers will be moving to a monthly release cadence. A minor version will be released on the last Friday of each month so as to maintain versioning consistency with the core product (Neo4j DBMS) which has also moved to a monthly cadence.
+
+As a policy, patch versions will not be released except on rare occasions. Bug fixes and updates will go into the latest minor version and users should upgrade to that. Driver upgrades within a major version will never contain breaking API changes.
+
+See also: https://neo4j.com/developer/kb/neo4j-supported-versions/
+
+Resources to get you started:
+
+- [API Documentation](https://neo4j.com/docs/api/javascript-driver/current/)
+- [Neo4j Manual](https://neo4j.com/docs/)
+- [Neo4j Refcard](https://neo4j.com/docs/cypher-refcard/current/)
+
+## What's New in 5.x
+
+- [Changelog](https://github.com/neo4j/neo4j-javascript-driver/wiki/5.0-changelog)
+
+## Including the Driver
+
+### In Node.js application
+
+Stable channel:
+
+```shell
+npm install neo4j-driver
+```
+
+Pre-release channel:
+
+```shell
+npm install neo4j-driver@next
+```
+
+Please note that `@next` only points to pre-releases that are not suitable for production use.
+To get the latest stable release omit `@next` part altogether or use `@latest` instead.
+
+```javascript
+var neo4j = require('neo4j-driver')
+```
+
+Driver instance should be closed when Node.js application exits:
+
+```javascript
+driver.close() // returns a Promise
+```
+
+otherwise application shutdown might hang or it might exit with a non-zero exit code.
+
+### In web browser
+
+We build a special browser version of the driver, which supports connecting to Neo4j over WebSockets.
+It can be included in an HTML page using one of the following tags:
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+This will make a global `neo4j` object available, where you can create a driver instance with `neo4j.driver`:
+
+```javascript
+var driver = neo4j.driver(
+ 'neo4j://localhost',
+ neo4j.auth.basic('neo4j', 'password')
+)
+```
+
+From `5.4.0`, this version is also exported as ECMA Script Module.
+It can be imported from a module using the following statements:
+
+```javascript
+// Direct reference
+import neo4j from 'lib/browser/neo4j-web.esm.min.js'
+
+// unpkg CDN non-minified , version X.Y.Z where X.Y.Z >= 5.4.0
+import neo4j from 'https://unpkg.com/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.js'
+
+// unpkg CDN minified for production use, version X.Y.Z where X.Y.Z >= 5.4.0
+import neo4j from 'https://unpkg.com/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.min.js'
+
+// jsDelivr CDN non-minified, version X.Y.Z where X.Y.Z >= 5.4.0
+import neo4j from 'https://cdn.jsdelivr.net/npm/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.js'
+
+// jsDelivr CDN minified for production use, version X.Y.Z where X.Y.Z >= 5.4.0
+import neo4j from 'https://cdn.jsdelivr.net/npm/neo4j-driver@X.Y.Z/lib/browser/neo4j-web.esm.min.js'
+
+```
+
+It is not required to explicitly close the driver on a web page. Web browser should gracefully close all open
+WebSockets when the page is unloaded. However, driver instance should be explicitly closed when it's lifetime
+is not the same as the lifetime of the web page:
+
+```javascript
+driver.close() // returns a Promise
+```
+
+## Usage examples
+
+### Constructing a Driver
+
+```javascript
+// Create a driver instance, for the user `neo4j` with password `password`.
+// It should be enough to have a single driver per database per application.
+var driver = neo4j.driver(
+ 'neo4j://localhost',
+ neo4j.auth.basic('neo4j', 'password')
+)
+
+// Close the driver when application exits.
+// This closes all used network connections.
+await driver.close()
+```
+
+### Acquiring a Session
+
+#### Regular Session
+
+```javascript
+// Create a session to run Cypher statements in.
+// Note: Always make sure to close sessions when you are done using them!
+var session = driver.session()
+```
+
+##### with a Default Access Mode of `READ`
+
+```javascript
+var session = driver.session({ defaultAccessMode: neo4j.session.READ })
+```
+
+##### with Bookmarks
+
+```javascript
+var session = driver.session({
+ bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession]
+})
+```
+
+##### against a Database
+
+```javascript
+var session = driver.session({
+ database: 'foo',
+ defaultAccessMode: neo4j.session.WRITE
+})
+```
+
+#### Reactive Session
+
+```javascript
+// Create a reactive session to run Cypher statements in.
+// Note: Always make sure to close sessions when you are done using them!
+var rxSession = driver.rxSession()
+```
+
+##### with a Default Access Mode of `READ`
+
+```javascript
+var rxSession = driver.rxSession({ defaultAccessMode: neo4j.session.READ })
+```
+
+##### with Bookmarks
+
+```javascript
+var rxSession = driver.rxSession({
+ bookmarks: [bookmark1FromPreviousSession, bookmark2FromPreviousSession]
+})
+```
+
+##### against a Database
+
+```javascript
+var rxSession = driver.rxSession({
+ database: 'foo',
+ defaultAccessMode: neo4j.session.WRITE
+})
+```
+
+### Transaction functions
+
+```javascript
+// Transaction functions provide a convenient API with minimal boilerplate and
+// retries on network fluctuations and transient errors. Maximum retry time is
+// configured on the driver level and is 30 seconds by default:
+// Applies both to standard and reactive sessions.
+neo4j.driver('neo4j://localhost', neo4j.auth.basic('neo4j', 'password'), {
+ maxTransactionRetryTime: 30000
+})
+```
+
+#### Reading with Async Session
+
+```javascript
+// It is possible to execute read transactions that will benefit from automatic
+// retries on both single instance ('bolt' URI scheme) and Causal Cluster
+// ('neo4j' URI scheme) and will get automatic load balancing in cluster deployments
+var readTxResultPromise = session.executeRead(txc => {
+ // used transaction will be committed automatically, no need for explicit commit/rollback
+
+ var result = txc.run('MATCH (person:Person) RETURN person.name AS name')
+ // at this point it is possible to either return the result or process it and return the
+ // result of processing it is also possible to run more statements in the same transaction
+ return result
+})
+
+// returned Promise can be later consumed like this:
+readTxResultPromise
+ .then(result => {
+ console.log(result.records)
+ })
+ .catch(error => {
+ console.log(error)
+ })
+ .then(() => session.close())
+```
+
+#### Reading with Reactive Session
+
+```javascript
+rxSession
+ .executeRead(txc =>
+ txc
+ .run('MATCH (person:Person) RETURN person.name AS name')
+ .records()
+ .pipe(map(record => record.get('name')))
+ )
+ .subscribe({
+ next: data => console.log(data),
+ complete: () => console.log('completed'),
+ error: err => console.log(error)
+ })
+```
+
+#### Writing with Async Session
+
+```javascript
+// It is possible to execute write transactions that will benefit from automatic retries
+// on both single instance ('bolt' URI scheme) and Causal Cluster ('neo4j' URI scheme)
+var writeTxResultPromise = session.executeWrite(async txc => {
+ // used transaction will be committed automatically, no need for explicit commit/rollback
+
+ var result = await txc.run(
+ "MERGE (alice:Person {name : 'Alice'}) RETURN alice.name AS name"
+ )
+ // at this point it is possible to either return the result or process it and return the
+ // result of processing it is also possible to run more statements in the same transaction
+ return result.records.map(record => record.get('name'))
+})
+
+// returned Promise can be later consumed like this:
+writeTxResultPromise
+ .then(namesArray => {
+ console.log(namesArray)
+ })
+ .catch(error => {
+ console.log(error)
+ })
+ .then(() => session.close())
+```
+
+#### Writing with Reactive Session
+
+```javascript
+rxSession
+ .executeWrite(txc =>
+ txc
+ .run("MERGE (alice:Person {name: 'James'}) RETURN alice.name AS name")
+ .records()
+ .pipe(map(record => record.get('name')))
+ )
+ .subscribe({
+ next: data => console.log(data),
+ complete: () => console.log('completed'),
+ error: error => console.log(error)
+ })
+```
+
+### Consuming Records
+
+#### Consuming Records with Streaming API
+
+```javascript
+// Run a Cypher statement, reading the result in a streaming manner as records arrive:
+session
+ .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', {
+ nameParam: 'Alice'
+ })
+ .subscribe({
+ onKeys: keys => {
+ console.log(keys)
+ },
+ onNext: record => {
+ console.log(record.get('name'))
+ },
+ onCompleted: () => {
+ session.close() // returns a Promise
+ },
+ onError: error => {
+ console.log(error)
+ }
+ })
+```
+
+Subscriber API allows following combinations of `onKeys`, `onNext`, `onCompleted` and `onError` callback invocations:
+
+- zero or one `onKeys`,
+- zero or more `onNext` followed by `onCompleted` when operation was successful. `onError` will not be invoked in this case
+- zero or more `onNext` followed by `onError` when operation failed. Callback `onError` might be invoked after couple `onNext` invocations because records are streamed lazily by the database. `onCompleted` will not be invoked in this case.
+
+#### Consuming Records with Promise API
+
+```javascript
+// the Promise way, where the complete result is collected before we act on it:
+session
+ .run('MERGE (james:Person {name : $nameParam}) RETURN james.name AS name', {
+ nameParam: 'James'
+ })
+ .then(result => {
+ result.records.forEach(record => {
+ console.log(record.get('name'))
+ })
+ })
+ .catch(error => {
+ console.log(error)
+ })
+ .then(() => session.close())
+```
+
+#### Consuming Records with Reactive API
+
+```javascript
+rxSession
+ .run('MERGE (james:Person {name: $nameParam}) RETURN james.name AS name', {
+ nameParam: 'Bob'
+ })
+ .records()
+ .pipe(
+ map(record => record.get('name')),
+ concatWith(rxSession.close())
+ )
+ .subscribe({
+ next: data => console.log(data),
+ complete: () => console.log('completed'),
+ error: err => console.log(err)
+ })
+```
+
+### Explicit Transactions
+
+#### With Async Session
+
+```javascript
+// run statement in a transaction
+const txc = session.beginTransaction()
+try {
+ const result1 = await txc.run(
+ 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name',
+ {
+ nameParam: 'Bob'
+ }
+ )
+ result1.records.forEach(r => console.log(r.get('name')))
+ console.log('First query completed')
+
+ const result2 = await txc.run(
+ 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name',
+ {
+ nameParam: 'Adam'
+ }
+ )
+ result2.records.forEach(r => console.log(r.get('name')))
+ console.log('Second query completed')
+
+ await txc.commit()
+ console.log('committed')
+} catch (error) {
+ console.log(error)
+ await txc.rollback()
+ console.log('rolled back')
+} finally {
+ await session.close()
+}
+```
+
+#### With Reactive Session
+
+```javascript
+rxSession
+ .beginTransaction()
+ .pipe(
+ mergeMap(txc =>
+ concatWith(
+ txc
+ .run(
+ 'MERGE (bob:Person {name: $nameParam}) RETURN bob.name AS name',
+ {
+ nameParam: 'Bob'
+ }
+ )
+ .records()
+ .pipe(map(r => r.get('name'))),
+ of('First query completed'),
+ txc
+ .run(
+ 'MERGE (adam:Person {name: $nameParam}) RETURN adam.name AS name',
+ {
+ nameParam: 'Adam'
+ }
+ )
+ .records()
+ .pipe(map(r => r.get('name'))),
+ of('Second query completed'),
+ txc.commit(),
+ of('committed')
+ ).pipe(catchError(err => txc.rollback().pipe(throwError(() => err))))
+ )
+ )
+ .subscribe({
+ next: data => console.log(data),
+ complete: () => console.log('completed'),
+ error: error => console.log(error)
+ })
+```
+
+### Numbers and the Integer type
+
+The Neo4j type system uses 64-bit signed integer values. The range of values is between `-(2``64``- 1)` and `(2``63``- 1)`.
+
+However, JavaScript can only safely represent integers between `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`.
+
+In order to support the full Neo4j type system, the driver will not automatically convert to javascript integers.
+Any time the driver receives an integer value from Neo4j, it will be represented with an internal integer type by the driver.
+
+_**Any javascript number value passed as a parameter will be recognized as `Float` type.**_
+
+#### Writing integers
+
+Numbers written directly e.g. `session.run("CREATE (n:Node {age: $age})", {age: 22})` will be of type `Float` in Neo4j.
+
+To write the `age` as an integer the `neo4j.int` method should be used:
+
+```javascript
+var neo4j = require('neo4j-driver')
+
+session.run('CREATE (n {age: $myIntParam})', { myIntParam: neo4j.int(22) })
+```
+
+To write an integer value that are not within the range of `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`, use a string argument to `neo4j.int`:
+
+```javascript
+session.run('CREATE (n {age: $myIntParam})', {
+ myIntParam: neo4j.int('9223372036854775807')
+})
+```
+
+#### Reading integers
+
+In Neo4j, the type Integer can be larger what can be represented safely as an integer with JavaScript Number.
+
+It is only safe to convert to a JavaScript Number if you know that the number will be in the range `Number.MIN_SAFE_INTEGER` `-(2``53``- 1)` and `Number.MAX_SAFE_INTEGER` `(2``53``- 1)`.
+
+In order to facilitate working with integers the driver include `neo4j.isInt`, `neo4j.integer.inSafeRange`, `neo4j.integer.toNumber`, and `neo4j.integer.toString`.
+
+```javascript
+var smallInteger = neo4j.int(123)
+if (neo4j.integer.inSafeRange(smallInteger)) {
+ var aNumber = smallInteger.toNumber()
+}
+```
+
+If you will be handling integers that is not within the JavaScript safe range of integers, you should convert the value to a string:
+
+```javascript
+var largeInteger = neo4j.int('9223372036854775807')
+if (!neo4j.integer.inSafeRange(largeInteger)) {
+ var integerAsString = largeInteger.toString()
+}
+```
+
+#### Enabling native numbers
+
+Starting from 1.6 version of the driver it is possible to configure it to only return native numbers instead of custom `Integer` objects.
+The configuration option affects all integers returned by the driver. **Enabling this option can result in a loss of precision and incorrect numeric
+values being returned if the database contains integer numbers outside of the range** `[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]`.
+To enable potentially lossy integer values use the driver's configuration object:
+
+```javascript
+var driver = neo4j.driver(
+ 'neo4j://localhost',
+ neo4j.auth.basic('neo4j', 'password'),
+ { disableLosslessIntegers: true }
+)
+```
diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v6x0.js b/packages/bolt-connection/src/bolt/bolt-protocol-v6x0.js
new file mode 100644
index 000000000..9db026f1a
--- /dev/null
+++ b/packages/bolt-connection/src/bolt/bolt-protocol-v6x0.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 BoltProtocolV5x8 from './bolt-protocol-v5x8'
+
+import transformersFactories from './bolt-protocol-v6x0.transformer'
+import Transformer from './transformer'
+
+import { internal } from 'neo4j-driver-core'
+
+const {
+ constants: { BOLT_PROTOCOL_V6_0 }
+} = internal
+
+export default class BoltProtocol extends BoltProtocolV5x8 {
+ get version () {
+ return BOLT_PROTOCOL_V6_0
+ }
+
+ get transformer () {
+ if (this._transformer === undefined) {
+ this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log)))
+ }
+ return this._transformer
+ }
+}
diff --git a/packages/bolt-connection/src/bolt/bolt-protocol-v6x0.transformer.js b/packages/bolt-connection/src/bolt/bolt-protocol-v6x0.transformer.js
new file mode 100644
index 000000000..12b239e22
--- /dev/null
+++ b/packages/bolt-connection/src/bolt/bolt-protocol-v6x0.transformer.js
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 v5x8 from './bolt-protocol-v5x8.transformer'
+import { TypeTransformer } from './transformer'
+import { structure } from '../packstream'
+import { Vector, newError } from 'neo4j-driver-core'
+const VECTOR = 0x56
+const FLOAT_32 = 0xc6
+const FLOAT_64 = 0xc1
+const INT_8 = 0xc8
+const INT_16 = 0xc9
+const INT_32 = 0xca
+const INT_64 = 0xcb
+
+function createVectorTransformer () {
+ return new TypeTransformer({
+ signature: VECTOR,
+ isTypeInstance: object => object instanceof Vector,
+ toStructure: vector => {
+ const dataview = new DataView(new ArrayBuffer(vector.typedArray.byteLength))
+ let set
+ let typeMarker
+ switch (vector.type) {
+ case 'INT8':
+ typeMarker = Uint8Array.from([INT_8])
+ set = dataview.setUint8.bind(dataview)
+ break
+ case 'INT16':
+ typeMarker = Uint8Array.from([INT_16])
+ set = dataview.setUint16.bind(dataview)
+ break
+ case 'INT32':
+ typeMarker = Uint8Array.from([INT_32])
+ set = dataview.setUint32.bind(dataview)
+ break
+ case 'INT64':
+ typeMarker = Uint8Array.from([INT_64])
+ set = dataview.setBigInt64.bind(dataview)
+ break
+ case 'FLOAT32':
+ typeMarker = Uint8Array.from([FLOAT_32])
+ set = dataview.setFloat32.bind(dataview)
+ break
+ case 'FLOAT64':
+ typeMarker = Uint8Array.from([FLOAT_64])
+ set = dataview.setFloat64.bind(dataview)
+ break
+ default:
+ throw newError(`Vector is of unsupported type ${vector.type}`)
+ }
+ for (let i = 0; i < vector.typedArray.length; i++) {
+ set(i * vector.typedArray.BYTES_PER_ELEMENT, vector.typedArray[i])
+ }
+ const struct = new structure.Structure(VECTOR, [typeMarker, new Int8Array(dataview.buffer)])
+ return struct
+ },
+ fromStructure: structure => {
+ const isLittleEndian = checkLittleEndian()
+ const typeMarker = structure.fields[0][0]
+ const arrayBuffer = structure.fields[1]
+ const setview = new DataView(new ArrayBuffer(arrayBuffer.byteLength))
+ const getview = new DataView(arrayBuffer.buffer)
+ let get
+ let set
+ let resultArray
+ switch (typeMarker) {
+ case INT_8:
+ return new Vector(Int8Array.from(arrayBuffer))
+ case INT_16:
+ resultArray = new Int16Array(setview.buffer)
+ get = getview.getInt16.bind(getview)
+ set = setview.setInt16.bind(setview)
+ break
+ case INT_32:
+ resultArray = new Int32Array(setview.buffer)
+ get = getview.getInt32.bind(getview)
+ set = setview.setInt32.bind(setview)
+ break
+ case INT_64:
+ resultArray = new BigInt64Array(setview.buffer)
+ get = getview.getBigInt64.bind(getview)
+ set = setview.setBigInt64.bind(setview)
+ break
+ case FLOAT_32:
+ resultArray = new Float32Array(setview.buffer)
+ get = getview.getFloat32.bind(getview)
+ set = setview.setFloat32.bind(setview)
+ break
+ case FLOAT_64:
+ resultArray = new Float64Array(setview.buffer)
+ get = getview.getFloat64.bind(getview)
+ set = setview.setFloat64.bind(setview)
+ break
+ default:
+ throw newError(`Recieved Vector of unknown type ${typeMarker}`)
+ }
+ for (let i = 0; i < arrayBuffer.length; i += resultArray.BYTES_PER_ELEMENT) {
+ set(i, get(i), isLittleEndian)
+ }
+ return new Vector(resultArray)
+ }
+ })
+}
+
+function checkLittleEndian () {
+ const dataview = new DataView(new ArrayBuffer(2))
+ dataview.setInt16(0, 1000, true)
+ const typeArray = new Int16Array(dataview.buffer)
+ return typeArray[0] === 1000
+}
+
+export default {
+ ...v5x8,
+ createVectorTransformer
+}
diff --git a/packages/bolt-connection/src/bolt/create.js b/packages/bolt-connection/src/bolt/create.js
index 1b8f792a5..18891e197 100644
--- a/packages/bolt-connection/src/bolt/create.js
+++ b/packages/bolt-connection/src/bolt/create.js
@@ -33,6 +33,7 @@ import BoltProtocolV5x5 from './bolt-protocol-v5x5'
import BoltProtocolV5x6 from './bolt-protocol-v5x6'
import BoltProtocolV5x7 from './bolt-protocol-v5x7'
import BoltProtocolV5x8 from './bolt-protocol-v5x8'
+import BoltProtocolV6x0 from './bolt-protocol-v6x0'
// eslint-disable-next-line no-unused-vars
import { Chunker, Dechunker } from '../channel'
import ResponseHandler from './response-handler'
@@ -266,6 +267,14 @@ function createProtocol (
log,
onProtocolError,
serversideRouting)
+ case 6.0:
+ return new BoltProtocolV6x0(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 7f1d2a1c4..4354654cf 100644
--- a/packages/bolt-connection/src/bolt/handshake.js
+++ b/packages/bolt-connection/src/bolt/handshake.js
@@ -19,7 +19,7 @@ import { alloc } from '../channel'
import { newError } from 'neo4j-driver-core'
const BOLT_MAGIC_PREAMBLE = 0x6060b017
-const AVAILABLE_BOLT_PROTOCOLS = ['5.8', '5.7', '5.6', '5.4', '5.3', '5.2', '5.1', '5.0', '4.4', '4.3', '4.2', '3.0'] // bolt protocols the client will accept, ordered by preference
+const AVAILABLE_BOLT_PROTOCOLS = ['6.0', '5.8', '5.7', '5.6', '5.4', '5.3', '5.2', '5.1', '5.0', '4.4', '4.3', '4.2', '3.0'] // bolt protocols the client will accept, ordered by preference
const DESIRED_CAPABILITES = 0
function version (major, minor) {
diff --git a/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v6x0.test.js.snap b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v6x0.test.js.snap
new file mode 100644
index 000000000..a56e2c5af
--- /dev/null
+++ b/packages/bolt-connection/test/bolt/__snapshots__/bolt-protocol-v6x0.test.js.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`#unit BoltProtocolV6x0 .packable() should resultant function not pack graph types (Node) 1`] = `"It is not allowed to pass nodes in query parameters, given: (c:a {a:"b"})"`;
+
+exports[`#unit BoltProtocolV6x0 .packable() should resultant function not pack graph types (Path) 1`] = `"It is not allowed to pass paths in query parameters, given: [object Object]"`;
+
+exports[`#unit BoltProtocolV6x0 .packable() should resultant function not pack graph types (Relationship) 1`] = `"It is not allowed to pass relationships in query parameters, given: (e)-[:a {b:"c"}]->(f)"`;
+
+exports[`#unit BoltProtocolV6x0 .packable() should resultant function not pack graph types (UnboundRelationship) 1`] = `"It is not allowed to pass unbound relationships in query parameters, given: -[:a {b:"c"}]->"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Date with less fields) 1`] = `"Wrong struct size for Date, expected 1 but was 0"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Date with more fields) 1`] = `"Wrong struct size for Date, expected 1 but was 2"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (DateTimeWithZoneId with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 2"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (DateTimeWithZoneId with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneId, expected 3 but was 4"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with less fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 2"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (DateTimeWithZoneOffset with more fields) 1`] = `"Wrong struct size for DateTimeWithZoneOffset, expected 3 but was 4"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Duration with less fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 3"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Duration with more fields) 1`] = `"Wrong struct size for Duration, expected 4 but was 5"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (LocalDateTime with less fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 1"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (LocalDateTime with more fields) 1`] = `"Wrong struct size for LocalDateTime, expected 2 but was 3"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (LocalTime with less fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 0"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (LocalTime with more fields) 1`] = `"Wrong struct size for LocalTime, expected 1 but was 2"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Node with less fields) 1`] = `"Wrong struct size for Node, expected 4 but was 3"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Node with more fields) 1`] = `"Wrong struct size for Node, expected 4 but was 5"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Path with less fields) 1`] = `"Wrong struct size for Path, expected 3 but was 2"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Path with more fields) 1`] = `"Wrong struct size for Path, expected 3 but was 4"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Point with less fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 2"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Point with more fields) 1`] = `"Wrong struct size for Point2D, expected 3 but was 4"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Point3D with less fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 3"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Point3D with more fields) 1`] = `"Wrong struct size for Point3D, expected 4 but was 5"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Relationship with less fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 5"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Relationship with more fields) 1`] = `"Wrong struct size for Relationship, expected 8 but was 9"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Time with less fields) 1`] = `"Wrong struct size for Time, expected 2 but was 1"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (Time with more fileds) 1`] = `"Wrong struct size for Time, expected 2 but was 3"`;
+
+exports[`#unit BoltProtocolV6x0 .unpack() should not unpack with wrong size (UnboundRelationship with less fields) 1`] = `"Wrong struct size for UnboundRelationship, expected 4 but was 3"`;
+
+exports[`#unit BoltProtocolV6x0 .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/bolt-protocol-v6x0.test.js b/packages/bolt-connection/test/bolt/bolt-protocol-v6x0.test.js
new file mode 100644
index 000000000..afc7fbf61
--- /dev/null
+++ b/packages/bolt-connection/test/bolt/bolt-protocol-v6x0.test.js
@@ -0,0 +1,1606 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 BoltProtocolV6x0 from '../../src/bolt/bolt-protocol-v6x0'
+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,
+ vector,
+ json
+} from 'neo4j-driver-core'
+
+import { alloc } from '../../src/channel'
+import { notificationFilterBehaviour, telemetryBehaviour } from './behaviour'
+
+const WRITE = 'WRITE'
+
+const {
+ txConfig: { TxConfig },
+ bookmarks: { Bookmarks },
+ logger: { Logger },
+ temporalUtil
+} = internal
+
+describe('#unit BoltProtocolV6x0', () => {
+ beforeEach(() => {
+ expect.extend(utils.matchers)
+ })
+
+ telemetryBehaviour.protocolSupportsTelemetry(newProtocol)
+
+ it('should enrich error metadata', () => {
+ const protocol = new BoltProtocolV6x0()
+ const enrichedData = protocol.enrichErrorMetadata({ neo4j_code: 'hello', diagnostic_record: {} })
+ expect(enrichedData.code).toBe('hello')
+ expect(enrichedData.diagnostic_record.OPERATION).toBe('')
+ expect(enrichedData.diagnostic_record.OPERATION_CODE).toBe('0')
+ expect(enrichedData.diagnostic_record.CURRENT_SCHEMA).toBe('/')
+ })
+
+ it('should request routing information', () => {
+ const recorder = new utils.MessageRecordingConnection()
+ const protocol = new BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(null, null, false)
+
+ expect(protocol.version).toBe(6.0)
+ })
+
+ it('should update metadata', () => {
+ const metadata = { t_first: 1, t_last: 2, db_hits: 3, some_other_key: 4 }
+ const protocol = new BoltProtocolV6x0(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 BoltProtocolV6x0(recorder, null, false)
+ utils.spyProtocolWrite(protocol)
+
+ const clientName = 'js-driver/1.2.3'
+ const boltAgent = {
+ product: 'neo4j-javascript/5.28',
+ platform: 'netbsd 1.1.1; Some arch',
+ languageDetails: 'Node/16.0.1 (v8 1.7.0)'
+ }
+ const authToken = { username: 'neo4j', password: 'secret' }
+
+ const observer = protocol.initialize({ userAgent: clientName, boltAgent, authToken })
+
+ protocol.verifyMessageCount(2)
+ expect(protocol.messages[0]).toBeMessage(
+ RequestMessage.hello5x3(clientName, boltAgent)
+ )
+ 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([
+ 'javascript-driver/6.0.0',
+ '',
+ undefined,
+ null
+ ])('should always use the user agent set by the user', (userAgent) => {
+ const recorder = new utils.MessageRecordingConnection()
+ const protocol = new BoltProtocolV6x0(recorder, null, false)
+ utils.spyProtocolWrite(protocol)
+
+ const boltAgent = {
+ product: 'neo4j-javascript/5.28',
+ platform: 'netbsd 1.1.1; Some arch',
+ languageDetails: 'Node/16.0.1 (v8 1.7.0)'
+ }
+ const authToken = { username: 'neo4j', password: 'secret' }
+
+ const observer = protocol.initialize({ userAgent, boltAgent, authToken })
+
+ protocol.verifyMessageCount(2)
+ expect(protocol.messages[0]).toBeMessage(
+ RequestMessage.hello5x3(userAgent, boltAgent)
+ )
+ 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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(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 BoltProtocolV6x0(null, null, {
+ disableLosslessIntegers,
+ useBigInt
+ })
+ expect(protocol._unpacker._disableLosslessIntegers).toBe(
+ disableLosslessIntegers
+ )
+ expect(protocol._unpacker._useBigInt).toBe(useBigInt)
+ }
+ )
+ })
+
+ describe('notificationFilter', () => {
+ notificationFilterBehaviour.shouldSupportGqlNotificationFilterOnInitialize(newProtocol)
+ notificationFilterBehaviour.shouldSupportGqlNotificationFilterOnBeginTransaction(newProtocol)
+ notificationFilterBehaviour.shouldSupportGqlNotificationFilterOnRun(newProtocol)
+ })
+
+ describe('watermarks', () => {
+ it('.run() should configure watermarks', () => {
+ const recorder = new utils.MessageRecordingConnection()
+ const protocol = utils.spyProtocolWrite(
+ new BoltProtocolV6x0(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 BoltProtocolV6x0(null, null, false)
+ expect(protocol.packer()).toBeInstanceOf(v2.Packer)
+ })
+
+ it('should configure v2 unpacker', () => {
+ const protocol = new BoltProtocolV6x0(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 resultant function not pack graph types (%s)', (_, graphType) => {
+ const protocol = new BoltProtocolV6x0(
+ 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 BoltProtocolV6x0(
+ 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([
+ ['Int8', vector(Int8Array.from([1, 2, 3]))],
+ ['Int16', vector(Int16Array.from([1, 2, 3]))],
+ ['Int32', vector(Int32Array.from([1, 2, 3]))],
+ ['Int64', vector(BigInt64Array.from([BigInt(1), BigInt(2), BigInt(3)]))],
+ ['Float32', vector(Float32Array.from([1, 2, 3]))],
+ ['Float64', vector(Float64Array.from([1, 2, 3]))]
+ ])('should pack vectors (%s)', (_, object) => {
+ const buffer = alloc(256)
+ const protocol = new BoltProtocolV6x0(
+ new utils.MessageRecordingConnection(),
+ buffer,
+ {}
+ )
+
+ const packable = protocol.packable(object)
+
+ expect(packable).not.toThrow()
+
+ buffer.reset()
+
+ const unpacked = protocol.unpack(buffer)
+ expect(json.stringify(unpacked)).toEqual(json.stringify(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 BoltProtocolV6x0(
+ 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 BoltProtocolV6x0(
+ 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 BoltProtocolV6x0(
+ 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 BoltProtocolV6x0(
+ 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 BoltProtocolV6x0(
+ 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')
+ ],
+ [
+ 'DateTimeWithZoneId / Midnight',
+ new structure.Structure(0x69, [
+ 1685397950, 183_000_000, 'Europe/Berlin'
+ ]),
+ new DateTime(2023, 5, 30, 0, 5, 50, 183_000_000, 2 * 60 * 60, 'Europe/Berlin')
+ ]
+ ])('should unpack spatial types and temporal types (%s)', (_, struct, object) => {
+ const buffer = alloc(256)
+ const protocol = new BoltProtocolV6x0(
+ 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 BoltProtocolV6x0(
+ 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)
+ })
+ })
+
+ describe('result metadata enrichment', () => {
+ it('run should configure BoltProtocolV6x0._enrichMetadata as enrichMetadata', () => {
+ 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 BoltProtocolV6x0(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
+ })
+
+ expect(observer._enrichMetadata).toBe(protocol._enrichMetadata)
+ })
+
+ describe('BoltProtocolV6x0._enrichMetadata', () => {
+ const protocol = newProtocol()
+
+ it('should handle empty metadata', () => {
+ const metadata = protocol._enrichMetadata({})
+
+ expect(metadata).toEqual({})
+ })
+
+ it('should handle metadata with random objects', () => {
+ const metadata = protocol._enrichMetadata({
+ a: 1133,
+ b: 345
+ })
+
+ expect(metadata).toEqual({
+ a: 1133,
+ b: 345
+ })
+ })
+
+ it('should handle metadata not change notifications ', () => {
+ const metadata = protocol._enrichMetadata({
+ a: 1133,
+ b: 345,
+ notifications: [
+ {
+ severity: 'WARNING',
+ category: 'HINT'
+ }
+ ]
+ })
+
+ expect(metadata).toEqual({
+ a: 1133,
+ b: 345,
+ notifications: [
+ {
+ severity: 'WARNING',
+ category: 'HINT'
+ }
+ ]
+ })
+ })
+
+ it.each([
+ [null, null],
+ [undefined, undefined],
+ [[], []],
+ [statusesWithDiagnosticRecord(null, null), statusesWithDiagnosticRecord(null, null)],
+ [statusesWithDiagnosticRecord(undefined, undefined), statusesWithDiagnosticRecord({
+ OPERATION: '',
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/'
+ },
+ {
+ OPERATION: '',
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/'
+ })],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A'
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B'
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C'
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' }
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' }
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' },
+ _severity: 'F'
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' },
+ _severity: 'F'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' },
+ _severity: 'F',
+ _classification: 'G'
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' },
+ _severity: 'F',
+ _classification: 'G'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' },
+ _severity: 'F',
+ _classification: 'G',
+ _position: {
+ offset: 1,
+ line: 2,
+ column: 3
+ }
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: 'A',
+ OPERATION_CODE: 'B',
+ CURRENT_SCHEMA: '/C',
+ _status_parameters: { d: 'E' },
+ _severity: 'F',
+ _classification: 'G',
+ _position: {
+ offset: 1,
+ line: 2,
+ column: 3
+ }
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: '/'
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null,
+ _severity: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null,
+ _severity: null
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null,
+ _severity: null,
+ _classification: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null,
+ _severity: null,
+ _classification: null
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null,
+ _severity: null,
+ _classification: null,
+ _position: null
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: null,
+ OPERATION_CODE: null,
+ CURRENT_SCHEMA: null,
+ _status_parameters: null,
+ _severity: null,
+ _classification: null,
+ _position: null
+ })
+ ],
+ [
+ statusesWithDiagnosticRecord({
+ OPERATION: undefined,
+ OPERATION_CODE: undefined,
+ CURRENT_SCHEMA: undefined,
+ _status_parameters: undefined,
+ _severity: undefined,
+ _classification: undefined,
+ _position: undefined
+ }),
+ statusesWithDiagnosticRecord({
+ OPERATION: undefined,
+ OPERATION_CODE: undefined,
+ CURRENT_SCHEMA: undefined,
+ _status_parameters: undefined,
+ _severity: undefined,
+ _classification: undefined,
+ _position: undefined
+ })
+ ],
+ [
+ [{
+ gql_status: '03N33',
+ status_description: 'info: description',
+ neo4j_code: 'Neo.Info.My.Code',
+ title: 'Mitt title',
+ diagnostic_record: {
+ _classification: 'SOME',
+ _severity: 'INFORMATION'
+ }
+ }],
+ [{
+ gql_status: '03N33',
+ status_description: 'info: description',
+ neo4j_code: 'Neo.Info.My.Code',
+ title: 'Mitt title',
+ diagnostic_record: {
+ OPERATION: '',
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/',
+ _classification: 'SOME',
+ _severity: 'INFORMATION'
+ }
+ }]
+ ],
+ [
+ [{
+ gql_status: '03N33',
+ status_description: 'info: description',
+ neo4j_code: 'Neo.Info.My.Code',
+ description: 'description',
+ title: 'Mitt title',
+ diagnostic_record: {
+ _classification: 'SOME',
+ _severity: 'INFORMATION'
+ }
+ }],
+ [{
+ gql_status: '03N33',
+ status_description: 'info: description',
+ neo4j_code: 'Neo.Info.My.Code',
+ title: 'Mitt title',
+ description: 'description',
+ diagnostic_record: {
+ OPERATION: '',
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/',
+ _classification: 'SOME',
+ _severity: 'INFORMATION'
+ }
+ }]
+ ],
+ [
+ [{
+ gql_status: '03N33',
+ status_description: 'info: description',
+ description: 'description'
+ }],
+ [{
+ gql_status: '03N33',
+ status_description: 'info: description',
+ description: 'description',
+ diagnostic_record: {
+ OPERATION: '',
+ OPERATION_CODE: '0',
+ CURRENT_SCHEMA: '/'
+ }
+ }]
+ ]
+ ])('should handle statuses (%o) ', (statuses, expectedStatuses) => {
+ const metadata = protocol._enrichMetadata({
+ a: 1133,
+ b: 345,
+ statuses
+ })
+
+ expect(metadata).toEqual({
+ a: 1133,
+ b: 345,
+ statuses: expectedStatuses
+ })
+ })
+ })
+
+ function statusesWithDiagnosticRecord (...diagnosticRecords) {
+ return diagnosticRecords.map(diagnosticRecord => {
+ return {
+ gql_status: '00000',
+ status_description: 'note: successful completion',
+ diagnostic_record: diagnosticRecord
+ }
+ })
+ }
+ })
+
+ function newProtocol (recorder) {
+ return new BoltProtocolV6x0(recorder, null, false, undefined, undefined, () => {})
+ }
+})
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index a0950cf37..226dc9ad9 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -101,6 +101,7 @@ import * as json from './json'
import resultTransformers, { ResultTransformer } from './result-transformers'
import ClientCertificate, { clientCertificateProviders, ClientCertificateProvider, ClientCertificateProviders, RotatingClientCertificateProvider, resolveCertificateProvider } from './client-certificate'
import * as internal from './internal' // todo: removed afterwards
+import Vector, { VectorType, vector } from './vector'
/**
* Object containing string constants representing predefined {@link Neo4jError} codes.
@@ -263,7 +264,9 @@ export {
notificationFilterDisabledClassification,
notificationFilterMinimumSeverityLevel,
clientCertificateProviders,
- resolveCertificateProvider
+ resolveCertificateProvider,
+ Vector,
+ vector
}
export type {
@@ -294,7 +297,8 @@ export type {
ClientCertificate,
ClientCertificateProvider,
ClientCertificateProviders,
- RotatingClientCertificateProvider
+ RotatingClientCertificateProvider,
+ VectorType
}
export default forExport
diff --git a/packages/core/src/internal/constants.ts b/packages/core/src/internal/constants.ts
index b45034da9..7403677c3 100644
--- a/packages/core/src/internal/constants.ts
+++ b/packages/core/src/internal/constants.ts
@@ -40,6 +40,7 @@ const BOLT_PROTOCOL_V5_5: number = 5.5
const BOLT_PROTOCOL_V5_6: number = 5.6
const BOLT_PROTOCOL_V5_7: number = 5.7
const BOLT_PROTOCOL_V5_8: number = 5.8
+const BOLT_PROTOCOL_V6_0: number = 6.0
const TELEMETRY_APIS = {
MANAGED_TRANSACTION: 0,
@@ -76,5 +77,6 @@ export {
BOLT_PROTOCOL_V5_6,
BOLT_PROTOCOL_V5_7,
BOLT_PROTOCOL_V5_8,
+ BOLT_PROTOCOL_V6_0,
TELEMETRY_APIS
}
diff --git a/packages/core/src/vector.ts b/packages/core/src/vector.ts
new file mode 100644
index 000000000..ad3c121af
--- /dev/null
+++ b/packages/core/src/vector.ts
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 { newError } from './error'
+
+type EnumRecord = { [key in T]: key }
+
+export type VectorType = 'INT8' | 'INT16' | 'INT32' | 'INT64' | 'FLOAT32' | 'FLOAT64'
+/**
+ * @typedef {'INT8' | 'INT16' | 'INT32' | 'INT64' | 'FLOAT32' | 'FLOAT64'} VectorType
+ */
+const vectorTypes: EnumRecord = {
+ INT8: 'INT8',
+ INT16: 'INT16',
+ INT32: 'INT32',
+ INT64: 'INT64',
+ FLOAT32: 'FLOAT32',
+ FLOAT64: 'FLOAT64'
+}
+Object.freeze(vectorTypes)
+
+/**
+ * A wrapper class for JavaScript TypedArrays that makes the driver send them as a Vector type to the database.
+ * @access public
+ * @exports Vector
+ * @class A Integer class for representing a 64 bit two's-complement integer value.
+ * @param {Float32Array | Float64Array | Int8Array | Int16Array | Int32Array | BigInt64Array} typedArray The TypedArray to convert to a vector
+ *
+ * @constructor
+ *
+ */
+export default class Vector {
+ typedArray: K
+ type: VectorType
+ constructor (typedArray: K) {
+ if (typedArray instanceof Int8Array) {
+ this.type = vectorTypes.INT8
+ } else if (typedArray instanceof Int16Array) {
+ this.type = vectorTypes.INT16
+ } else if (typedArray instanceof Int32Array) {
+ this.type = vectorTypes.INT32
+ } else if (typedArray instanceof BigInt64Array) {
+ this.type = vectorTypes.INT64
+ } else if (typedArray instanceof Float32Array) {
+ this.type = vectorTypes.FLOAT32
+ } else if (typedArray instanceof Float64Array) {
+ this.type = vectorTypes.FLOAT64
+ } else {
+ throw newError(`The neo4j Vector class is a wrapper for TypedArrays. got ${typeof typedArray}`)
+ }
+ this.typedArray = typedArray
+ }
+
+ toTypedArray (): K {
+ return this.typedArray
+ }
+}
+
+/**
+ * Cast a TypedArray to a {@link Vector}
+ * @access public
+ * @param {Float32Array | Float64Array | Int8Array | Int16Array | Int32Array | BigInt64Array} typedArray - The value to use.
+ * @return {Vector} - The Neo4j Vector ready to be used as a query parameter
+ */
+export function vector (typedArray: K): Vector {
+ return new Vector(typedArray)
+}
diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v6x0.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v6x0.js
new file mode 100644
index 000000000..be4e7456b
--- /dev/null
+++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v6x0.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 BoltProtocolV5x8 from './bolt-protocol-v5x8.js'
+
+import transformersFactories from './bolt-protocol-v6x0.transformer.js'
+import Transformer from './transformer.js'
+
+import { internal } from '../../core/index.ts'
+
+const {
+ constants: { BOLT_PROTOCOL_V6_0 }
+} = internal
+
+export default class BoltProtocol extends BoltProtocolV5x8 {
+ get version () {
+ return BOLT_PROTOCOL_V6_0
+ }
+
+ get transformer () {
+ if (this._transformer === undefined) {
+ this._transformer = new Transformer(Object.values(transformersFactories).map(create => create(this._config, this._log)))
+ }
+ return this._transformer
+ }
+}
diff --git a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v6x0.transformer.js b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v6x0.transformer.js
new file mode 100644
index 000000000..c03e05a10
--- /dev/null
+++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/bolt-protocol-v6x0.transformer.js
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 v5x8 from './bolt-protocol-v5x8.transformer.js'
+import { TypeTransformer } from './transformer.js'
+import { structure } from '../packstream/index.js'
+import { Vector, newError } from '../../core/index.ts'
+const VECTOR = 0x56
+const FLOAT_32 = 0xc6
+const FLOAT_64 = 0xc1
+const INT_8 = 0xc8
+const INT_16 = 0xc9
+const INT_32 = 0xca
+const INT_64 = 0xcb
+
+function createVectorTransformer () {
+ return new TypeTransformer({
+ signature: VECTOR,
+ isTypeInstance: object => object instanceof Vector,
+ toStructure: vector => {
+ const dataview = new DataView(new ArrayBuffer(vector.typedArray.byteLength))
+ let set
+ let typeMarker
+ switch (vector.type) {
+ case 'INT8':
+ typeMarker = Uint8Array.from([INT_8])
+ set = dataview.setUint8.bind(dataview)
+ break
+ case 'INT16':
+ typeMarker = Uint8Array.from([INT_16])
+ set = dataview.setUint16.bind(dataview)
+ break
+ case 'INT32':
+ typeMarker = Uint8Array.from([INT_32])
+ set = dataview.setUint32.bind(dataview)
+ break
+ case 'INT64':
+ typeMarker = Uint8Array.from([INT_64])
+ set = dataview.setBigInt64.bind(dataview)
+ break
+ case 'FLOAT32':
+ typeMarker = Uint8Array.from([FLOAT_32])
+ set = dataview.setFloat32.bind(dataview)
+ break
+ case 'FLOAT64':
+ typeMarker = Uint8Array.from([FLOAT_64])
+ set = dataview.setFloat64.bind(dataview)
+ break
+ default:
+ throw newError(`Vector is of unsupported type ${vector.type}`)
+ }
+ for (let i = 0; i < vector.typedArray.length; i++) {
+ set(i * vector.typedArray.BYTES_PER_ELEMENT, vector.typedArray[i])
+ }
+ const struct = new structure.Structure(VECTOR, [typeMarker, new Int8Array(dataview.buffer)])
+ return struct
+ },
+ fromStructure: structure => {
+ const isLittleEndian = checkLittleEndian()
+ const typeMarker = structure.fields[0][0]
+ const arrayBuffer = structure.fields[1]
+ const setview = new DataView(new ArrayBuffer(arrayBuffer.byteLength))
+ const getview = new DataView(arrayBuffer.buffer)
+ let get
+ let set
+ let resultArray
+ switch (typeMarker) {
+ case INT_8:
+ return new Vector(Int8Array.from(arrayBuffer))
+ case INT_16:
+ resultArray = new Int16Array(setview.buffer)
+ get = getview.getInt16.bind(getview)
+ set = setview.setInt16.bind(setview)
+ break
+ case INT_32:
+ resultArray = new Int32Array(setview.buffer)
+ get = getview.getInt32.bind(getview)
+ set = setview.setInt32.bind(setview)
+ break
+ case INT_64:
+ resultArray = new BigInt64Array(setview.buffer)
+ get = getview.getBigInt64.bind(getview)
+ set = setview.setBigInt64.bind(setview)
+ break
+ case FLOAT_32:
+ resultArray = new Float32Array(setview.buffer)
+ get = getview.getFloat32.bind(getview)
+ set = setview.setFloat32.bind(setview)
+ break
+ case FLOAT_64:
+ resultArray = new Float64Array(setview.buffer)
+ get = getview.getFloat64.bind(getview)
+ set = setview.setFloat64.bind(setview)
+ break
+ default:
+ throw newError(`Recieved Vector of unknown type ${typeMarker}`)
+ }
+ for (let i = 0; i < arrayBuffer.length; i += resultArray.BYTES_PER_ELEMENT) {
+ set(i, get(i), isLittleEndian)
+ }
+ return new Vector(resultArray)
+ }
+ })
+}
+
+function checkLittleEndian () {
+ const dataview = new DataView(new ArrayBuffer(2))
+ dataview.setInt16(0, 1000, true)
+ const typeArray = new Int16Array(dataview.buffer)
+ return typeArray[0] === 1000
+}
+
+export default {
+ ...v5x8,
+ createVectorTransformer
+}
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 188e8c6bd..cea9b1dd4 100644
--- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js
+++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/create.js
@@ -33,6 +33,7 @@ import BoltProtocolV5x5 from './bolt-protocol-v5x5.js'
import BoltProtocolV5x6 from './bolt-protocol-v5x6.js'
import BoltProtocolV5x7 from './bolt-protocol-v5x7.js'
import BoltProtocolV5x8 from './bolt-protocol-v5x8.js'
+import BoltProtocolV6x0 from './bolt-protocol-v6x0.js'
// eslint-disable-next-line no-unused-vars
import { Chunker, Dechunker } from '../channel/index.js'
import ResponseHandler from './response-handler.js'
@@ -266,6 +267,14 @@ function createProtocol (
log,
onProtocolError,
serversideRouting)
+ case 6.0:
+ return new BoltProtocolV6x0(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 ddf9bccca..adcf1624f 100644
--- a/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js
+++ b/packages/neo4j-driver-deno/lib/bolt-connection/bolt/handshake.js
@@ -19,7 +19,7 @@ import { alloc } from '../channel/index.js'
import { newError } from '../../core/index.ts'
const BOLT_MAGIC_PREAMBLE = 0x6060b017
-const AVAILABLE_BOLT_PROTOCOLS = ['5.8', '5.7', '5.6', '5.4', '5.3', '5.2', '5.1', '5.0', '4.4', '4.3', '4.2', '3.0'] // bolt protocols the client will accept, ordered by preference
+const AVAILABLE_BOLT_PROTOCOLS = ['6.0', '5.8', '5.7', '5.6', '5.4', '5.3', '5.2', '5.1', '5.0', '4.4', '4.3', '4.2', '3.0'] // bolt protocols the client will accept, ordered by preference
const DESIRED_CAPABILITES = 0
function version (major, minor) {
diff --git a/packages/neo4j-driver-deno/lib/core/index.ts b/packages/neo4j-driver-deno/lib/core/index.ts
index 3a341708b..135e19afb 100644
--- a/packages/neo4j-driver-deno/lib/core/index.ts
+++ b/packages/neo4j-driver-deno/lib/core/index.ts
@@ -101,6 +101,7 @@ import * as json from './json.ts'
import resultTransformers, { ResultTransformer } from './result-transformers.ts'
import ClientCertificate, { clientCertificateProviders, ClientCertificateProvider, ClientCertificateProviders, RotatingClientCertificateProvider, resolveCertificateProvider } from './client-certificate.ts'
import * as internal from './internal/index.ts'
+import Vector, { VectorType, vector } from './vector.ts'
/**
* Object containing string constants representing predefined {@link Neo4jError} codes.
@@ -263,7 +264,9 @@ export {
notificationFilterDisabledClassification,
notificationFilterMinimumSeverityLevel,
clientCertificateProviders,
- resolveCertificateProvider
+ resolveCertificateProvider,
+ Vector,
+ vector
}
export type {
@@ -294,7 +297,8 @@ export type {
ClientCertificate,
ClientCertificateProvider,
ClientCertificateProviders,
- RotatingClientCertificateProvider
+ RotatingClientCertificateProvider,
+ VectorType
}
export default forExport
diff --git a/packages/neo4j-driver-deno/lib/core/internal/constants.ts b/packages/neo4j-driver-deno/lib/core/internal/constants.ts
index b45034da9..7403677c3 100644
--- a/packages/neo4j-driver-deno/lib/core/internal/constants.ts
+++ b/packages/neo4j-driver-deno/lib/core/internal/constants.ts
@@ -40,6 +40,7 @@ const BOLT_PROTOCOL_V5_5: number = 5.5
const BOLT_PROTOCOL_V5_6: number = 5.6
const BOLT_PROTOCOL_V5_7: number = 5.7
const BOLT_PROTOCOL_V5_8: number = 5.8
+const BOLT_PROTOCOL_V6_0: number = 6.0
const TELEMETRY_APIS = {
MANAGED_TRANSACTION: 0,
@@ -76,5 +77,6 @@ export {
BOLT_PROTOCOL_V5_6,
BOLT_PROTOCOL_V5_7,
BOLT_PROTOCOL_V5_8,
+ BOLT_PROTOCOL_V6_0,
TELEMETRY_APIS
}
diff --git a/packages/neo4j-driver-deno/lib/core/vector.ts b/packages/neo4j-driver-deno/lib/core/vector.ts
new file mode 100644
index 000000000..4de021d9e
--- /dev/null
+++ b/packages/neo4j-driver-deno/lib/core/vector.ts
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 { newError } from './error.ts'
+
+type EnumRecord = { [key in T]: key }
+
+export type VectorType = 'INT8' | 'INT16' | 'INT32' | 'INT64' | 'FLOAT32' | 'FLOAT64'
+/**
+ * @typedef {'INT8' | 'INT16' | 'INT32' | 'INT64' | 'FLOAT32' | 'FLOAT64'} VectorType
+ */
+const vectorTypes: EnumRecord = {
+ INT8: 'INT8',
+ INT16: 'INT16',
+ INT32: 'INT32',
+ INT64: 'INT64',
+ FLOAT32: 'FLOAT32',
+ FLOAT64: 'FLOAT64'
+}
+Object.freeze(vectorTypes)
+
+/**
+ * A wrapper class for JavaScript TypedArrays that makes the driver send them as a Vector type to the database.
+ * @access public
+ * @exports Vector
+ * @class A Integer class for representing a 64 bit two's-complement integer value.
+ * @param {Float32Array | Float64Array | Int8Array | Int16Array | Int32Array | BigInt64Array} typedArray The TypedArray to convert to a vector
+ *
+ * @constructor
+ *
+ */
+export default class Vector {
+ typedArray: K
+ type: VectorType
+ constructor (typedArray: K) {
+ if (typedArray instanceof Int8Array) {
+ this.type = vectorTypes.INT8
+ } else if (typedArray instanceof Int16Array) {
+ this.type = vectorTypes.INT16
+ } else if (typedArray instanceof Int32Array) {
+ this.type = vectorTypes.INT32
+ } else if (typedArray instanceof BigInt64Array) {
+ this.type = vectorTypes.INT64
+ } else if (typedArray instanceof Float32Array) {
+ this.type = vectorTypes.FLOAT32
+ } else if (typedArray instanceof Float64Array) {
+ this.type = vectorTypes.FLOAT64
+ } else {
+ throw newError(`The neo4j Vector class is a wrapper for TypedArrays. got ${typeof typedArray}`)
+ }
+ this.typedArray = typedArray
+ }
+
+ toTypedArray (): K {
+ return this.typedArray
+ }
+}
+
+/**
+ * Cast a TypedArray to a {@link Vector}
+ * @access public
+ * @param {Float32Array | Float64Array | Int8Array | Int16Array | Int32Array | BigInt64Array} typedArray - The value to use.
+ * @return {Vector} - The Neo4j Vector ready to be used as a query parameter
+ */
+export function vector (typedArray: K): Vector {
+ return new Vector(typedArray)
+}
diff --git a/packages/neo4j-driver/src/index.js b/packages/neo4j-driver/src/index.js
index 911ad9fcd..1330ad597 100644
--- a/packages/neo4j-driver/src/index.js
+++ b/packages/neo4j-driver/src/index.js
@@ -78,7 +78,9 @@ import {
notificationFilterMinimumSeverityLevel,
staticAuthTokenManager,
clientCertificateProviders,
- resolveCertificateProvider
+ resolveCertificateProvider,
+ Vector,
+ vector
} from 'neo4j-driver-core'
import {
DirectConnectionProvider,
@@ -282,7 +284,8 @@ const types = {
LocalDateTime,
LocalTime,
Time,
- Integer
+ Integer,
+ Vector
}
/**
@@ -402,7 +405,8 @@ const forExport = {
notificationSeverityLevel,
notificationFilterDisabledCategory,
notificationFilterMinimumSeverityLevel,
- clientCertificateProviders
+ clientCertificateProviders,
+ vector
}
export {
@@ -474,6 +478,8 @@ export {
notificationFilterDisabledCategory,
notificationFilterDisabledClassification,
notificationFilterMinimumSeverityLevel,
- clientCertificateProviders
+ clientCertificateProviders,
+ vector,
+ Vector
}
export default forExport
diff --git a/packages/neo4j-driver/test/vector-examples.test.js b/packages/neo4j-driver/test/vector-examples.test.js
new file mode 100644
index 000000000..e6d500a0a
--- /dev/null
+++ b/packages/neo4j-driver/test/vector-examples.test.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) "Neo4j"
+ * Neo4j Sweden AB [https://neo4j.com]
+ *
+ * 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 neo4j from '../src'
+import sharedNeo4j from './internal/shared-neo4j'
+
+describe('#integration vector type api suggestion', () => {
+ let driverGlobal
+ let protocolVersion
+ const uri = `bolt://${sharedNeo4j.hostnameWithBoltPort}`
+
+ beforeAll(async () => {
+ driverGlobal = neo4j.driver(uri, sharedNeo4j.authToken)
+ const tmpDriver = neo4j.driver(
+ `bolt://${sharedNeo4j.hostnameWithBoltPort}`,
+ sharedNeo4j.authToken
+ )
+ protocolVersion = await sharedNeo4j.cleanupAndGetProtocolVersion(tmpDriver)
+ await tmpDriver.close()
+ })
+
+ beforeEach(async () => {
+ const driver = driverGlobal
+ const session = driver.session()
+ await session.run('MATCH (n) DETACH DELETE n')
+ await session.close()
+ })
+
+ afterAll(async () => {
+ await driverGlobal.close()
+ })
+
+ it('write and read vectors', async () => {
+ if (protocolVersion >= 6.0) {
+ const driver = driverGlobal
+
+ const bufferWriter = Uint8Array.from([1, 1])
+ await driver.executeQuery('CREATE (p:Product) SET p.vector_from_array = $vector_from_array, p.vector_from_buffer = $vector_from_buffer', {
+ vector_from_array: neo4j.vector(Float32Array.from([1, 2, 3, 4])), // Typed arrays can be created from a regular list of Numbers
+ vector_from_buffer: neo4j.vector(new Uint8Array(bufferWriter.buffer)) // Or from a bytebuffer
+ })
+ const res = await driver.executeQuery('MATCH (p:Product) RETURN p.vector_from_array as arrayVector, p.vector_from_buffer as bufferVector')
+
+ const arrayVec = res.records[0].get('arrayVector').toTypedArray()
+ const bufferVec = res.records[0].get('bufferVector').toTypedArray()
+
+ expect(arrayVec[0]).toBe(1)
+ expect(bufferVec[1]).toBe(1)
+ }
+ })
+})