Skip to content

Commit 552c543

Browse files
authored
Add support for readTransaction in sqflite (#1819)
1 parent 24b6e60 commit 552c543

File tree

3 files changed

+134
-4
lines changed

3 files changed

+134
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
- Add isar breadcrumbs ([#1800](https://github.com/getsentry/sentry-dart/pull/1800))
1313
- 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))
1414
- Add beforeScreenshotCallback to SentryFlutterOptions ([#1805](https://github.com/getsentry/sentry-dart/pull/1805))
15-
15+
- Add support for `readTransaction` in `sqflite` ([#1819](https://github.com/getsentry/sentry-dart/pull/1819))
16+
1617
### Dependencies
1718

1819
- Bump Android SDK from v7.0.0 to v7.1.0 ([#1788](https://github.com/getsentry/sentry-dart/pull/1788))

sqflite/lib/src/sentry_database.dart

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:meta/meta.dart';
24
import 'package:sentry/sentry.dart';
35
import 'package:sqflite/sqflite.dart';
@@ -32,7 +34,10 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
3234
// ignore: public_member_api_docs
3335
static const dbSqlQueryOp = 'db.sql.query';
3436

35-
static const _dbSqlOp = 'db.sql.transaction';
37+
static const _dbSqlTransactionOp = 'db.sql.transaction';
38+
39+
static const _dbSqlReadTransactionOp = 'db.sql.read_transaction';
40+
3641
@internal
3742
// ignore: public_member_api_docs
3843
static const dbSystemKey = 'db.system';
@@ -143,7 +148,7 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
143148
final currentSpan = _hub.getSpan();
144149
final description = 'Transaction DB: ${_database.path}';
145150
final span = currentSpan?.startChild(
146-
_dbSqlOp,
151+
_dbSqlTransactionOp,
147152
description: description,
148153
);
149154
// ignore: invalid_use_of_internal_member
@@ -152,7 +157,7 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
152157

153158
var breadcrumb = Breadcrumb(
154159
message: description,
155-
category: _dbSqlOp,
160+
category: _dbSqlTransactionOp,
156161
data: {},
157162
type: 'query',
158163
);
@@ -196,4 +201,86 @@ class SentryDatabase extends SentryDatabaseExecutor implements Database {
196201
}
197202
});
198203
}
204+
205+
@override
206+
// ignore: override_on_non_overriding_member, public_member_api_docs
207+
Future<T> readTransaction<T>(Future<T> Function(Transaction txn) action) {
208+
return Future<T>(() async {
209+
final currentSpan = _hub.getSpan();
210+
final description = 'Transaction DB: ${_database.path}';
211+
final span = currentSpan?.startChild(
212+
_dbSqlReadTransactionOp,
213+
description: description,
214+
);
215+
// ignore: invalid_use_of_internal_member
216+
span?.origin = SentryTraceOrigins.autoDbSqfliteDatabase;
217+
setDatabaseAttributeData(span, dbName);
218+
219+
var breadcrumb = Breadcrumb(
220+
message: description,
221+
category: _dbSqlReadTransactionOp,
222+
data: {},
223+
type: 'query',
224+
);
225+
setDatabaseAttributeOnBreadcrumb(breadcrumb, dbName);
226+
227+
Future<T> newAction(Transaction txn) async {
228+
final executor = SentryDatabaseExecutor(
229+
txn,
230+
parentSpan: span,
231+
hub: _hub,
232+
dbName: dbName,
233+
);
234+
final sentrySqfliteTransaction =
235+
SentrySqfliteTransaction(executor, hub: _hub, dbName: dbName);
236+
237+
return await action(sentrySqfliteTransaction);
238+
}
239+
240+
try {
241+
final futureOrResult = _resolvedReadTransaction(newAction);
242+
T result;
243+
244+
if (futureOrResult is Future<T>) {
245+
result = await futureOrResult;
246+
} else {
247+
result = futureOrResult;
248+
}
249+
250+
span?.status = SpanStatus.ok();
251+
breadcrumb.data?['status'] = 'ok';
252+
253+
return result;
254+
} catch (exception) {
255+
span?.throwable = exception;
256+
span?.status = SpanStatus.internalError();
257+
breadcrumb.data?['status'] = 'internal_error';
258+
breadcrumb = breadcrumb.copyWith(
259+
level: SentryLevel.warning,
260+
);
261+
262+
rethrow;
263+
} finally {
264+
await span?.finish();
265+
266+
// ignore: invalid_use_of_internal_member
267+
await _hub.scope.addBreadcrumb(breadcrumb);
268+
}
269+
});
270+
}
271+
272+
FutureOr<T> _resolvedReadTransaction<T>(
273+
Future<T> Function(Transaction txn) action,
274+
) async {
275+
try {
276+
// ignore: return_of_invalid_type
277+
final result = await (_database as dynamic).readTransaction(action);
278+
// Await and cast, as directly returning the future resulted in a runtime error.
279+
return result as T;
280+
} on NoSuchMethodError catch (_) {
281+
// The `readTransaction` does not exists on sqflite version < 2.5.0+2.
282+
// Fallback to transaction instead.
283+
return _database.transaction(action);
284+
}
285+
}
199286
}

sqflite/test/sentry_database_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ void main() {
107107
await db.close();
108108
});
109109

110+
test('creates readTransaction span', () async {
111+
final db = await fixture.getSut();
112+
113+
await db.readTransaction((txn) async {
114+
expect(txn is SentrySqfliteTransaction, true);
115+
});
116+
final span = fixture.tracer.children.last;
117+
expect(span.context.operation, 'db.sql.read_transaction');
118+
expect(span.context.description, 'Transaction DB: $inMemoryDatabasePath');
119+
expect(span.status, SpanStatus.ok());
120+
expect(span.data[SentryDatabase.dbSystemKey], SentryDatabase.dbSystem);
121+
expect(span.data[SentryDatabase.dbNameKey], inMemoryDatabasePath);
122+
expect(
123+
span.origin,
124+
// ignore: invalid_use_of_internal_member
125+
SentryTraceOrigins.autoDbSqfliteDatabase,
126+
);
127+
128+
await db.close();
129+
});
130+
110131
test('creates transaction breadcrumb', () async {
111132
final db = await fixture.getSut();
112133

@@ -128,6 +149,27 @@ void main() {
128149
await db.close();
129150
});
130151

152+
test('creates readTransaction breadcrumb', () async {
153+
final db = await fixture.getSut();
154+
155+
await db.readTransaction((txn) async {
156+
expect(txn is SentrySqfliteTransaction, true);
157+
});
158+
159+
final breadcrumb = fixture.hub.scope.breadcrumbs.first;
160+
expect(breadcrumb.message, 'Transaction DB: $inMemoryDatabasePath');
161+
expect(breadcrumb.category, 'db.sql.read_transaction');
162+
expect(breadcrumb.data?['status'], 'ok');
163+
expect(
164+
breadcrumb.data?[SentryDatabase.dbSystemKey],
165+
SentryDatabase.dbSystem,
166+
);
167+
expect(breadcrumb.data?[SentryDatabase.dbNameKey], inMemoryDatabasePath);
168+
expect(breadcrumb.type, 'query');
169+
170+
await db.close();
171+
});
172+
131173
test('creates transaction children run by the transaction', () async {
132174
final db = await fixture.getSut();
133175

0 commit comments

Comments
 (0)