Skip to content

Commit

Permalink
Merge pull request #194 from odroe/transaction-primitives
Browse files Browse the repository at this point in the history
Transaction primitives
  • Loading branch information
Seven Du authored Apr 5, 2023
2 parents fe1a7ee + 5159319 commit 6c90883
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 5 deletions.
64 changes: 63 additions & 1 deletion docs/docs/transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,66 @@ final post = prisma.$transaction((prisma) async {
return post;
});
```
```

## Transaction API

Interaction transactions cannot achieve atomic transaction interaction, so we expose the following methods in the client:

- `$startTransaction()` - Starts a transaction
- `$commitTransaction()` - Commits a transaction
- `$rollbackTransaction()` - Rolls back a transaction

### Start a transaction

To start a transaction, use the `$startTransaction()` method:

```dart
final transaction = await prisma.$startTransaction();
```

### Commit a transaction

To commit a transaction, use the `$commitTransaction()` method:

```dart
await transaction.$commitTransaction();
```

### Rollback a transaction

To rollback a transaction, use the `$rollbackTransaction()` method:

```dart
await transaction.$rollbackTransaction();
```

### Example

The following example shows how to use the transaction API:

```dart
final transaction = await prisma.$startTransaction();
try {
// Create a post
final post = await transaction.post.create({
data: PostCreateInput(
title: 'Interactive transactions',
content: 'This is an interactive transaction',
),
});
// Update user posts count
await transaction.user.update({
where: UserWhereUniqueInput(id: post.authorId),
data: UserUpdateInput(
posts: UserPostsInput(increment: 1),
),
});
await transaction.$commitTransaction();
} catch (e) {
await transaction.$rollbackTransaction();
}
```
103 changes: 99 additions & 4 deletions lib/src/client/prisma_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';

import '../../engine_core.dart';
import '../../logger.dart';
import '../exceptions.dart';
import '../graphql/arg.dart';
import '../graphql/field.dart';
import 'prisma_raw_codec.dart';
Expand All @@ -17,6 +18,9 @@ abstract class BasePrismaClient<Client extends BasePrismaClient<Client>> {
static final Finalizer<Engine> finalizer =
Finalizer<Engine>((engine) => engine.stop());

/// Finalizer is registered.
static bool _finalizerRegistered = false;

/// Create a new instance of [BasePrismaClient].
BasePrismaClient(
Engine engine, {
Expand All @@ -25,7 +29,10 @@ abstract class BasePrismaClient<Client extends BasePrismaClient<Client>> {
}) : _transaction = transaction,
_headers = headers,
_engine = engine {
// finalizer.attach(this, engine, detach: this);
if (_finalizerRegistered != true) {
_finalizerRegistered = true;
finalizer.attach(this, engine, detach: this);
}
}

/// The prisma engine.
Expand All @@ -45,17 +52,105 @@ abstract class BasePrismaClient<Client extends BasePrismaClient<Client>> {

/// Connect to the prisma engine.
Future<void> $connect() {
finalizer.attach(this, _engine, detach: this);

return _engine.start();
}

/// Disconnect from the prisma engine.
Future<void> $disconnect() async {
if (_transaction != null) {
return;
}

await _engine.stop();
finalizer.detach(this);
}

/// Start a new transaction.
Future<Client> $startTransaction({
TransactionHeaders? headers,
Duration timeout = const Duration(seconds: 5),
Duration maxWait = const Duration(seconds: 2),
TransactionIsolationLevel? isolationLevel,
}) async {
// If the client is a transaction, use it.
if (_transaction != null) {
return this as Client;
}

// Request a new transaction.
final TransactionInfo transactionInfo = await _engine.startTransaction(
headers: headers,
timeout: timeout,
maxWait: maxWait,
isolationLevel: isolationLevel,
);

return copyWith(
headers: QueryEngineRequestHeaders(
transactionId: transactionInfo.id,
traceparent: headers?.traceparent,
),
transaction: transactionInfo,
);
}

/// Commit the current transaction.
Future<void> $commitTransaction() async {
_validateTransactionState();

// Commit the transaction.
await _engine.commitTransaction(
info: _transaction!,
headers: _headers,
);

// Set the transaction to committed.
_transaction!['isCommitted'] = true;
}

/// Rollback the current transaction.
Future<void> $rollbackTransaction() async {
_validateTransactionState();

// Rollback the transaction.
await _engine.rollbackTransaction(
info: _transaction!,
headers: _headers,
);

// Set the transaction to rolled back.
_transaction!['isRolledBack'] = true;
}

/// Validate transaction state.
void _validateTransactionState() {
// If the client is not a transaction, do nothing.
if (_transaction == null) {
throw PrismaException(
message: 'Cannot execute a query outside of a transaction.',
engine: _engine,
);
}

// If the client is committed, throw an error.
if (_transaction!['isCommitted'] == true) {
throw PrismaException(
message:
'Cannot execute a query in a transaction that is already committed.',
engine: _engine,
);
}

// If the client is rolled back, throw an error.
if (_transaction!['isRolledBack'] == true) {
throw PrismaException(
message:
'Cannot execute a query in a transaction that is already rolled back.',
engine: _engine,
);
}
}

/// Interactive transactions.
///
/// Sometimes you need more control over what queries execute within a
Expand All @@ -79,7 +174,7 @@ abstract class BasePrismaClient<Client extends BasePrismaClient<Client>> {
TransactionIsolationLevel? isolationLevel,
}) async {
// If the client is a transaction, use it.
if (_headers?.transactionId != null || _transaction != null) {
if (_transaction != null) {
return callback(this as Client);
}

Expand Down

1 comment on commit 6c90883

@vercel
Copy link

@vercel vercel bot commented on 6c90883 Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.