From 3448e9488103601316a12d4f038d72c4df41d373 Mon Sep 17 00:00:00 2001 From: Antonio Barcelos Date: Mon, 30 Oct 2023 10:38:09 +0100 Subject: [PATCH] Introduce explicity resource management to Transaction This is a TC39 [proposal](https://github.com/tc39/proposal-explicit-resource-management) which is already implemented in Typescript 5.2, core-js, babel and other polyfill tools. This feature enables the user begin a transaction with the `await using` keywords and then do not have to close the resource afterwards, since this resources will be closed after leaving the block which were created at. Transaction will be closed using with the same behaviour as `Transaction.close` calls. So, the transaction will be rollback if still open. _Note: For better usage in cluster environments, you should use `executeRead` and `executeWrite` for handling retries._ Usage example: ```typescript await using tx = session.beginTransaction() await tx.run('CREATE (p:Person { name:$name }) RETURN p', { name }).summary() await tx.commit() ``` --- packages/core/src/transaction.ts | 6 +++ .../neo4j-driver-deno/lib/core/transaction.ts | 6 +++ packages/neo4j-driver-deno/test/neo4j.test.ts | 41 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/packages/core/src/transaction.ts b/packages/core/src/transaction.ts index fbc7a7a0f..fb7a7ba69 100644 --- a/packages/core/src/transaction.ts +++ b/packages/core/src/transaction.ts @@ -291,6 +291,12 @@ class Transaction { } } + // eslint-disable-next-line + // @ts-ignore + [Symbol.asyncDispose] (): Promise { + return this.close() + } + _onErrorCallback (error: Error): Promise { // error will be "acknowledged" by sending a RESET message // database will then forget about this transaction and cleanup all corresponding resources diff --git a/packages/neo4j-driver-deno/lib/core/transaction.ts b/packages/neo4j-driver-deno/lib/core/transaction.ts index 17df98d16..967fe06ec 100644 --- a/packages/neo4j-driver-deno/lib/core/transaction.ts +++ b/packages/neo4j-driver-deno/lib/core/transaction.ts @@ -291,6 +291,12 @@ class Transaction { } } + // eslint-disable-next-line + // @ts-ignore + [Symbol.asyncDispose] (): Promise { + return this.close() + } + _onErrorCallback (error: Error): Promise { // error will be "acknowledged" by sending a RESET message // database will then forget about this transaction and cleanup all corresponding resources diff --git a/packages/neo4j-driver-deno/test/neo4j.test.ts b/packages/neo4j-driver-deno/test/neo4j.test.ts index 2bc4f4fff..ff25b7e1e 100644 --- a/packages/neo4j-driver-deno/test/neo4j.test.ts +++ b/packages/neo4j-driver-deno/test/neo4j.test.ts @@ -17,6 +17,8 @@ * limitations under the License. */ import neo4j from '../lib/mod.ts' +//@ts-ignore +import { assertEquals } from "https://deno.land/std@0.182.0/testing/asserts.ts"; const env = Deno.env.toObject() @@ -42,3 +44,42 @@ Deno.test('driver.session should be able to use explicity resource management', await session.executeRead(tx => "RETURN 1") }) + + +// Deno will fail with resource leaks +Deno.test('session.beginTransaction should rollback the transaction if not committed', async () => { + await using driver = neo4j.driver(uri, authToken) + await using session = driver.session() + const name = "Must Be Conor" + + + { + await using tx = session.beginTransaction() + await tx.run('CREATE (p:Person { name:$name }) RETURN p', { name }).summary() + } + + const { records } = await driver.executeQuery('MATCH (p:Person { name:$name }) RETURN p', { name }) + assertEquals(records.length, 0) +}) + + +// Deno will fail with resource leaks +Deno.test('session.beginTransaction should noop if resource committed', async () => { + await using driver = neo4j.driver(uri, authToken) + try { + await using session = driver.session() + const name = "Must Be Conor" + + { + await using tx = session.beginTransaction() + await tx.run('CREATE (p:Person { name:$name }) RETURN p', { name }).summary() + await tx.commit() + } + + const { records } = await driver.executeQuery('MATCH (p:Person { name:$name }) RETURN p', { name }) + assertEquals(records.length, 1) + } finally { + // cleaning up + await driver.executeQuery('MATCH (p:Person { name:$name }) DELETE(p)', { name }) + } +})