Skip to content

Commit

Permalink
Add support for readTransaction in sqflite (#1819)
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase authored Jan 17, 2024
1 parent 24b6e60 commit 552c543
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
- Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800))
- Starting with Flutter 3.16, Sentry adds the [`appFlavor`](https://api.flutter.dev/flutter/services/appFlavor-constant.html) to the `flutter_context` ([#1799](https://github.com/getsentry/sentry-dart/pull/1799))
- Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805))

- Add support for `readTransaction` in `sqflite` ([#1819](https://github.com/getsentry/sentry-dart/pull/1819))

### Dependencies

- Bump Android SDK from v7.0.0 to v7.1.0 ([#1788](https://github.com/getsentry/sentry-dart/pull/1788))
Expand Down
93 changes: 90 additions & 3 deletions sqflite/lib/src/sentry_database.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'package:sentry/sentry.dart';
import 'package:sqflite/sqflite.dart';
Expand Down Expand Up @@ -32,7 +34,10 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
// ignore: public_member_api_docs
static const dbSqlQueryOp = 'db.sql.query';

static const _dbSqlOp = 'db.sql.transaction';
static const _dbSqlTransactionOp = 'db.sql.transaction';

static const _dbSqlReadTransactionOp = 'db.sql.read_transaction';

@internal
// ignore: public_member_api_docs
static const dbSystemKey = 'db.system';
Expand Down Expand Up @@ -143,7 +148,7 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
final currentSpan = _hub.getSpan();
final description = 'Transaction DB: ${_database.path}';
final span = currentSpan?.startChild(
_dbSqlOp,
_dbSqlTransactionOp,
description: description,
);
// ignore: invalid_use_of_internal_member
Expand All @@ -152,7 +157,7 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {

var breadcrumb = Breadcrumb(
message: description,
category: _dbSqlOp,
category: _dbSqlTransactionOp,
data: {},
type: 'query',
);
Expand Down Expand Up @@ -196,4 +201,86 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
}
});
}

@override
// ignore: override_on_non_overriding_member, public_member_api_docs
Future<T> readTransaction<T>(Future<T> Function(Transaction txn) action) {
return Future<T>(() async {
final currentSpan = _hub.getSpan();
final description = 'Transaction DB: ${_database.path}';
final span = currentSpan?.startChild(
_dbSqlReadTransactionOp,
description: description,
);
// ignore: invalid_use_of_internal_member
span?.origin = SentryTraceOrigins.autoDbSqfliteDatabase;
setDatabaseAttributeData(span, dbName);

var breadcrumb = Breadcrumb(
message: description,
category: _dbSqlReadTransactionOp,
data: {},
type: 'query',
);
setDatabaseAttributeOnBreadcrumb(breadcrumb, dbName);

Future<T> newAction(Transaction txn) async {
final executor = SentryDatabaseExecutor(
txn,
parentSpan: span,
hub: _hub,
dbName: dbName,
);
final sentrySqfliteTransaction =
SentrySqfliteTransaction(executor, hub: _hub, dbName: dbName);

return await action(sentrySqfliteTransaction);
}

try {
final futureOrResult = _resolvedReadTransaction(newAction);
T result;

if (futureOrResult is Future<T>) {
result = await futureOrResult;
} else {
result = futureOrResult;
}

span?.status = SpanStatus.ok();
breadcrumb.data?['status'] = 'ok';

return result;
} catch (exception) {
span?.throwable = exception;
span?.status = SpanStatus.internalError();
breadcrumb.data?['status'] = 'internal_error';
breadcrumb = breadcrumb.copyWith(
level: SentryLevel.warning,
);

rethrow;
} finally {
await span?.finish();

// ignore: invalid_use_of_internal_member
await _hub.scope.addBreadcrumb(breadcrumb);
}
});
}

FutureOr<T> _resolvedReadTransaction<T>(
Future<T> Function(Transaction txn) action,
) async {
try {
// ignore: return_of_invalid_type
final result = await (_database as dynamic).readTransaction(action);
// Await and cast, as directly returning the future resulted in a runtime error.
return result as T;
} on NoSuchMethodError catch (_) {
// The `readTransaction` does not exists on sqflite version < 2.5.0+2.
// Fallback to transaction instead.
return _database.transaction(action);
}
}
}
42 changes: 42 additions & 0 deletions sqflite/test/sentry_database_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ void main() {
await db.close();
});

test('creates readTransaction span', () async {
final db = await fixture.getSut();

await db.readTransaction((txn) async {
expect(txn is SentrySqfliteTransaction, true);
});
final span = fixture.tracer.children.last;
expect(span.context.operation, 'db.sql.read_transaction');
expect(span.context.description, 'Transaction DB: $inMemoryDatabasePath');
expect(span.status, SpanStatus.ok());
expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem);
expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath);
expect(
span.origin,
// ignore: invalid_use_of_internal_member
SentryTraceOrigins.autoDbSqfliteDatabase,
);

await db.close();
});

test('creates transaction breadcrumb', () async {
final db = await fixture.getSut();

Expand All @@ -128,6 +149,27 @@ void main() {
await db.close();
});

test('creates readTransaction breadcrumb', () async {
final db = await fixture.getSut();

await db.readTransaction((txn) async {
expect(txn is SentrySqfliteTransaction, true);
});

final breadcrumb = fixture.hub.scope.breadcrumbs.first;
expect(breadcrumb.message, 'Transaction DB: $inMemoryDatabasePath');
expect(breadcrumb.category, 'db.sql.read_transaction');
expect(breadcrumb.data?['status'], 'ok');
expect(
breadcrumb.data?[SentryDatabase.dbSystemKey],
SentryDatabase.dbSystem,
);
expect(breadcrumb.data?[SentryDatabase.dbNameKey], inMemoryDatabasePath);
expect(breadcrumb.type, 'query');

await db.close();
});

test('creates transaction children run by the transaction', () async {
final db = await fixture.getSut();

Expand Down

0 comments on commit 552c543

Please sign in to comment.