Skip to content

Add support for readTransaction in sqflite #1819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jan 17, 2024
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