From d43bab3cdcd03db21fce58699b36931e6fb4953b Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 6 Feb 2025 17:09:02 +0100 Subject: [PATCH 01/29] temp --- drift/example/example.dart | 14 +- drift/lib/sentry_drift.dart | 1 + drift/lib/src/sentry_query_executor.dart | 195 ------------------ drift/lib/src/sentry_query_interceptor.dart | 97 +++++++++ drift/lib/src/sentry_span_helper.dart | 72 +++---- .../lib/src/sentry_transaction_executor.dart | 167 --------------- drift/pubspec.yaml | 2 +- 7 files changed, 134 insertions(+), 414 deletions(-) delete mode 100644 drift/lib/src/sentry_query_executor.dart create mode 100644 drift/lib/src/sentry_query_interceptor.dart delete mode 100644 drift/lib/src/sentry_transaction_executor.dart diff --git a/drift/example/example.dart b/drift/example/example.dart index bfe8b6d8e6..92689ea1ca 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -15,6 +15,10 @@ Future main() async { options.dsn = dsn; options.tracesSampleRate = 1.0; options.debug = true; + options.spotlight = Spotlight(enabled: true); + options.beforeSendTransaction = (transaction) { + return transaction; + }; }, appRunner: runApp, // Init your App. ); @@ -22,18 +26,20 @@ Future main() async { Future runApp() async { final tr = Sentry.startTransaction('drift', 'op', bindToScope: true); - final executor = SentryQueryExecutor( - () => NativeDatabase.memory(), - databaseName: 'your_db_name', - ); + final executor = NativeDatabase.memory() + .interceptWith(SentryQueryInterceptor(databaseName: 'your_db_name')); + final db = AppDatabase(executor); + // await db.transaction(() async { await db.into(db.todoItems).insert( TodoItemsCompanion.insert( + id: Value(0), title: 'This is a test thing', content: 'test', ), ); + // }); final items = await db.select(db.todoItems).get(); print(items); diff --git a/drift/lib/sentry_drift.dart b/drift/lib/sentry_drift.dart index d2df194d04..eb874235d2 100644 --- a/drift/lib/sentry_drift.dart +++ b/drift/lib/sentry_drift.dart @@ -1,3 +1,4 @@ library; export 'src/sentry_query_executor.dart'; +export 'src/sentry_query_interceptor.dart'; diff --git a/drift/lib/src/sentry_query_executor.dart b/drift/lib/src/sentry_query_executor.dart deleted file mode 100644 index 95784670c8..0000000000 --- a/drift/lib/src/sentry_query_executor.dart +++ /dev/null @@ -1,195 +0,0 @@ -import 'dart:async'; - -import 'package:drift/drift.dart'; -import 'package:meta/meta.dart'; -import 'package:sentry/sentry.dart'; - -import 'sentry_span_helper.dart'; -import 'sentry_transaction_executor.dart'; -import 'version.dart'; - -/// Signature of a function that opens a database connection when instructed to. -typedef DatabaseOpener = FutureOr Function(); - -/// The Sentry Query Executor. -/// -/// If the constructor parameter queryExecutor is null, [LazyDatabase] will be -/// used as a default. -@experimental -class SentryQueryExecutor extends QueryExecutor { - Hub _hub; - - final _spanHelper = SentrySpanHelper( - // ignore: invalid_use_of_internal_member - SentryTraceOrigins.autoDbDriftQueryExecutor, - ); - - final QueryExecutor _executor; - - final String _dbName; - - @internal - // ignore: public_member_api_docs - static const dbNameKey = 'db.name'; - - @internal - // ignore: public_member_api_docs - static const dbOp = 'db'; - - @internal - // ignore: public_member_api_docs - static const dbSystemKey = 'db.system'; - - @internal - // ignore: public_member_api_docs - static const dbSystem = 'sqlite'; - - bool _isOpen = false; - - /// Declares a [SentryQueryExecutor] that will run [opener] when the database is - /// first requested to be opened. You must specify the same [dialect] as the - /// underlying database has - SentryQueryExecutor( - DatabaseOpener opener, { - @internal Hub? hub, - @internal QueryExecutor? queryExecutor, - required String databaseName, - }) : _hub = hub ?? HubAdapter(), - _dbName = databaseName, - _executor = queryExecutor ?? LazyDatabase(opener) { - // ignore: invalid_use_of_internal_member - final options = _hub.options; - options.sdk.addIntegration('SentryDriftTracing'); - options.sdk.addPackage(packageName, sdkVersion); - _spanHelper.setHub(_hub); - } - - /// @nodoc - @internal - void setHub(Hub hub) { - _hub = hub; - _spanHelper.setHub(hub); - } - - @override - TransactionExecutor beginTransaction() { - final transactionExecutor = _executor.beginTransaction(); - final sentryTransactionExecutor = SentryTransactionExecutor( - transactionExecutor, - _hub, - dbName: _dbName, - ); - sentryTransactionExecutor.beginTransaction(); - return sentryTransactionExecutor; - } - - @override - Future runBatched(BatchedStatements statements) { - return _spanHelper.asyncWrapInSpan( - statements.toString(), - () async { - return await _executor.runBatched(statements); - }, - dbName: _dbName, - ); - } - - @override - Future ensureOpen(QueryExecutorUser user) { - if (_isOpen) { - return Future.value(true); - } - return _spanHelper.asyncWrapInSpan( - 'Open DB: $_dbName', - () async { - final res = await _executor.ensureOpen(user); - _isOpen = true; - return res; - }, - dbName: _dbName, - ); - } - - @override - Future runCustom(String statement, [List? args]) { - return _spanHelper.asyncWrapInSpan( - statement, - () async { - return await _executor.runCustom(statement, args); - }, - dbName: _dbName, - ); - } - - @override - Future runDelete(String statement, List args) { - return _spanHelper.asyncWrapInSpan( - statement, - () async { - return await _executor.runDelete(statement, args); - }, - dbName: _dbName, - ); - } - - @override - Future runInsert(String statement, List args) { - return _spanHelper.asyncWrapInSpan( - statement, - () async { - return await _executor.runInsert(statement, args); - }, - dbName: _dbName, - ); - } - - @override - Future>> runSelect( - String statement, - List args, - ) { - return _spanHelper.asyncWrapInSpan( - statement, - () async { - return await _executor.runSelect(statement, args); - }, - dbName: _dbName, - ); - } - - @override - Future runUpdate(String statement, List args) { - return _spanHelper.asyncWrapInSpan( - statement, - () async { - return await _executor.runUpdate(statement, args); - }, - dbName: _dbName, - ); - } - - @override - // ignore: override_on_non_overriding_member, public_member_api_docs - QueryExecutor beginExclusive() { - final dynamic uncheckedExecutor = _executor; - try { - return uncheckedExecutor.beginExclusive() as QueryExecutor; - } on NoSuchMethodError catch (_) { - throw Exception('This method is not supported in Drift versions <2.19.0'); - } - } - - @override - Future close() { - return _spanHelper.asyncWrapInSpan( - 'Close DB: $_dbName', - () async { - return await _executor.close(); - }, - dbName: _dbName, - ); - } - - @override - SqlDialect get dialect => _executor.dialect; -} diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart new file mode 100644 index 0000000000..cef08786f3 --- /dev/null +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -0,0 +1,97 @@ +import 'dart:async'; + +import 'package:drift/drift.dart'; +import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; + +import 'sentry_span_helper.dart'; + +/// Doc +class SentryQueryInterceptor extends QueryInterceptor { + final String _dbName; + final Hub _hub; + + final _spanHelper = NewSentrySpanHelper( + // ignore: invalid_use_of_internal_member + SentryTraceOrigins.autoDbDriftQueryExecutor, + ); + + /// @nodoc + SentryQueryInterceptor({required String databaseName, @internal Hub? hub}) + : _dbName = databaseName, + _hub = hub ?? HubAdapter(); + + Future _run( + String description, + FutureOr Function() operation, + ) async { + return await _spanHelper.asyncWrapInSpan( + description, + () async => operation(), + dbName: _dbName, + ); + } + + @override + TransactionExecutor beginTransaction(QueryExecutor parent) { + return _spanHelper.beginTransaction( + () => super.beginTransaction(parent), + dbName: _dbName, + ); + } + + @override + Future commitTransaction(TransactionExecutor inner) { + return _spanHelper.finishTransaction(() => super.commitTransaction(inner)); + } + + @override + Future rollbackTransaction(TransactionExecutor inner) { + return _spanHelper.abortTransaction(() => super.rollbackTransaction(inner)); + } + + @override + Future runBatched( + QueryExecutor executor, BatchedStatements statements) { + return _run( + 'batch with $statements', () => executor.runBatched(statements)); + } + + @override + Future runInsert( + QueryExecutor executor, String statement, List args) { + print('run insert'); + return _run( + '$statement with $args', + () => executor.runInsert(statement, args), + ); + } + + @override + Future runUpdate( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runUpdate(statement, args)); + } + + @override + Future runDelete( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runDelete(statement, args)); + } + + @override + Future runCustom( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runCustom(statement, args)); + } + + @override + Future>> runSelect( + QueryExecutor executor, String statement, List args) { + return _run( + '$statement with $args', () => executor.runSelect(statement, args)); + } +} diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index 762bd24d08..c23b29ab07 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -1,25 +1,19 @@ import 'package:meta/meta.dart'; - import 'package:sentry/sentry.dart'; import 'sentry_query_executor.dart'; -/// @nodoc -@internal class SentrySpanHelper { /// @nodoc - Hub _hub = HubAdapter(); + final Hub _hub; /// @nodoc final String _origin; - /// @nodoc - SentrySpanHelper(this._origin); + ISentrySpan? _parentSpan; /// @nodoc - void setHub(Hub hub) { - _hub = hub; - } + SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter(); /// @nodoc @internal @@ -29,10 +23,7 @@ class SentrySpanHelper { String? dbName, bool useTransactionSpan = false, }) async { - ISentrySpan? currentSpan = _hub.getSpan(); - if (useTransactionSpan) { - currentSpan = transactionSpan; - } + final currentSpan = _parentSpan ?? _hub.getSpan(); final span = currentSpan?.startChild( SentryQueryExecutor.dbOp, description: description, @@ -65,91 +56,78 @@ class SentrySpanHelper { } } - /// This span is used for the database transaction. - @internal - ISentrySpan? transactionSpan; - /// @nodoc @internal T beginTransaction( - String description, T Function() execute, { String? dbName, }) { - final currentSpan = _hub.getSpan(); - final span = currentSpan?.startChild( + final scopeSpan = _hub.getSpan(); + _parentSpan = scopeSpan?.startChild( SentryQueryExecutor.dbOp, - description: description, + description: 'Begin transaction', ); // ignore: invalid_use_of_internal_member - span?.origin = _origin; + _parentSpan?.origin = _origin; - span?.setData( + _parentSpan?.setData( SentryQueryExecutor.dbSystemKey, SentryQueryExecutor.dbSystem, ); if (dbName != null) { - span?.setData(SentryQueryExecutor.dbNameKey, dbName); + _parentSpan?.setData(SentryQueryExecutor.dbNameKey, dbName); } try { final result = execute(); - span?.status = SpanStatus.unknown(); + _parentSpan?.status = SpanStatus.unknown(); return result; } catch (exception) { - span?.throwable = exception; - span?.status = SpanStatus.internalError(); + _parentSpan?.throwable = exception; + _parentSpan?.status = SpanStatus.internalError(); rethrow; - } finally { - transactionSpan = span; } } /// @nodoc @internal - Future finishTransaction( - Future Function() execute, { - String? dbName, - }) async { + Future finishTransaction(Future Function() execute) async { try { final result = await execute(); - transactionSpan?.status = SpanStatus.ok(); + _parentSpan?.status = SpanStatus.ok(); return result; } catch (exception) { - transactionSpan?.throwable = exception; - transactionSpan?.status = SpanStatus.internalError(); + _parentSpan?.throwable = exception; + _parentSpan?.status = SpanStatus.internalError(); rethrow; } finally { - await transactionSpan?.finish(); - transactionSpan = null; + await _parentSpan?.finish(); + _parentSpan = null; } } /// @nodoc @internal - Future abortTransaction( - Future Function() execute, { - String? dbName, - }) async { + Future abortTransaction(Future Function() execute) async { try { final result = await execute(); - transactionSpan?.status = SpanStatus.aborted(); + _parentSpan?.status = SpanStatus.aborted(); return result; } catch (exception) { - transactionSpan?.throwable = exception; - transactionSpan?.status = SpanStatus.internalError(); + _parentSpan?.throwable = exception; + _parentSpan?.status = SpanStatus.internalError(); rethrow; } finally { - await transactionSpan?.finish(); - transactionSpan = null; + await _parentSpan?.finish(); + _parentSpan = null; } } } diff --git a/drift/lib/src/sentry_transaction_executor.dart b/drift/lib/src/sentry_transaction_executor.dart deleted file mode 100644 index dba05cd8cc..0000000000 --- a/drift/lib/src/sentry_transaction_executor.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'package:drift/backends.dart'; -import 'package:meta/meta.dart'; -import 'package:sentry/sentry.dart'; - -import 'sentry_span_helper.dart'; - -/// @nodoc -@internal -class SentryTransactionExecutor extends TransactionExecutor { - final TransactionExecutor _executor; - - final Hub _hub; - - final _spanHelper = SentrySpanHelper( - // ignore: invalid_use_of_internal_member - SentryTraceOrigins.autoDbDriftTransactionExecutor, - ); - - final String? _dbName; - - bool _isOpen = false; - - final _withinTransactionDescription = 'Within transaction: '; - - /// @nodoc - SentryTransactionExecutor(this._executor, Hub hub, {@internal String? dbName}) - : _hub = hub, - _dbName = dbName { - _spanHelper.setHub(_hub); - } - - @override - TransactionExecutor beginTransaction() { - return _spanHelper.beginTransaction( - 'transaction', - () { - return _executor.beginTransaction(); - }, - dbName: _dbName, - ); - } - - @override - Future rollback() { - return _spanHelper.abortTransaction(() async { - return await _executor.rollback(); - }); - } - - @override - Future send() { - return _spanHelper.finishTransaction(() async { - return await _executor.send(); - }); - } - - @override - SqlDialect get dialect => _executor.dialect; - - @override - Future ensureOpen(QueryExecutorUser user) { - if (_isOpen) { - return Future.value(true); - } - return _spanHelper.asyncWrapInSpan( - 'Open transaction', - () async { - final res = await _executor.ensureOpen(user); - _isOpen = true; - return res; - }, - dbName: _dbName, - ); - } - - @override - Future runBatched(BatchedStatements statements) { - return _spanHelper.asyncWrapInSpan( - 'batch', - () async { - return await _executor.runBatched(statements); - }, - dbName: _dbName, - ); - } - - @override - Future runCustom(String statement, [List? args]) { - return _spanHelper.asyncWrapInSpan( - _spanDescriptionForOperations(statement), - () async { - return _executor.runCustom(statement, args); - }, - dbName: _dbName, - useTransactionSpan: true, - ); - } - - @override - Future runDelete(String statement, List args) { - return _spanHelper.asyncWrapInSpan( - _spanDescriptionForOperations(statement), - () async { - return _executor.runDelete(statement, args); - }, - dbName: _dbName, - useTransactionSpan: true, - ); - } - - @override - Future runInsert(String statement, List args) { - return _spanHelper.asyncWrapInSpan( - _spanDescriptionForOperations(statement), - () async { - return _executor.runInsert(statement, args); - }, - dbName: _dbName, - useTransactionSpan: true, - ); - } - - @override - Future>> runSelect( - String statement, - List args, - ) { - return _spanHelper.asyncWrapInSpan( - _spanDescriptionForOperations(statement), - () async { - return _executor.runSelect(statement, args); - }, - dbName: _dbName, - useTransactionSpan: true, - ); - } - - @override - // ignore: override_on_non_overriding_member, public_member_api_docs - QueryExecutor beginExclusive() { - final dynamic uncheckedExecutor = _executor; - try { - return uncheckedExecutor.beginExclusive() as QueryExecutor; - } on NoSuchMethodError catch (_) { - throw Exception('This method is not supported in Drift versions <2.19.0'); - } - } - - @override - Future runUpdate(String statement, List args) { - return _spanHelper.asyncWrapInSpan( - _spanDescriptionForOperations(statement), - () async { - return _executor.runUpdate(statement, args); - }, - dbName: _dbName, - useTransactionSpan: true, - ); - } - - @override - bool get supportsNestedTransactions => _executor.supportsNestedTransactions; - - String _spanDescriptionForOperations(String operation) { - return '$_withinTransactionDescription$operation'; - } -} diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index 2d0c84ab80..4acb87201b 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -19,7 +19,7 @@ platforms: dependencies: sentry: 8.13.0 meta: ^1.3.0 - drift: ^2.13.0 + drift: ^2.24.0 dev_dependencies: lints: '>=2.0.0' From 0c645547fa45855968deb75135aded0c2ecea251 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 7 Feb 2025 11:57:17 +0100 Subject: [PATCH 02/29] update --- drift/example/example.dart | 18 +++++------ drift/lib/sentry_drift.dart | 1 - drift/lib/src/constants.dart | 5 ++++ drift/lib/src/sentry_query_interceptor.dart | 9 +++--- drift/lib/src/sentry_span_helper.dart | 33 +++++++-------------- 5 files changed, 29 insertions(+), 37 deletions(-) create mode 100644 drift/lib/src/constants.dart diff --git a/drift/example/example.dart b/drift/example/example.dart index 92689ea1ca..4d34f81d45 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -31,15 +31,15 @@ Future runApp() async { final db = AppDatabase(executor); - // await db.transaction(() async { - await db.into(db.todoItems).insert( - TodoItemsCompanion.insert( - id: Value(0), - title: 'This is a test thing', - content: 'test', - ), - ); - // }); + await db.transaction(() async { + await db.into(db.todoItems).insert( + TodoItemsCompanion.insert( + id: Value(0), + title: 'This is a test thing', + content: 'test', + ), + ); + }); final items = await db.select(db.todoItems).get(); print(items); diff --git a/drift/lib/sentry_drift.dart b/drift/lib/sentry_drift.dart index eb874235d2..8f160e44d3 100644 --- a/drift/lib/sentry_drift.dart +++ b/drift/lib/sentry_drift.dart @@ -1,4 +1,3 @@ library; -export 'src/sentry_query_executor.dart'; export 'src/sentry_query_interceptor.dart'; diff --git a/drift/lib/src/constants.dart b/drift/lib/src/constants.dart new file mode 100644 index 0000000000..6c2ce6ec41 --- /dev/null +++ b/drift/lib/src/constants.dart @@ -0,0 +1,5 @@ +const String dbSystemKey = 'db.system'; +const String dbSystem = 'db.sqlite'; +const String dbNameKey = 'db.name'; +const String dbSqlQueryOp = 'db.sql.query'; +const String dbSqlTransactionOp = 'db.sql.transaction'; diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index cef08786f3..156515068b 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -11,7 +11,7 @@ class SentryQueryInterceptor extends QueryInterceptor { final String _dbName; final Hub _hub; - final _spanHelper = NewSentrySpanHelper( + final _spanHelper = SentrySpanHelper( // ignore: invalid_use_of_internal_member SentryTraceOrigins.autoDbDriftQueryExecutor, ); @@ -23,11 +23,12 @@ class SentryQueryInterceptor extends QueryInterceptor { Future _run( String description, - FutureOr Function() operation, - ) async { + FutureOr Function() execute, { + String? operation, + }) async { return await _spanHelper.asyncWrapInSpan( description, - () async => operation(), + () async => execute(), dbName: _dbName, ); } diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index c23b29ab07..f6602faa15 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -1,31 +1,24 @@ import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; -import 'sentry_query_executor.dart'; +import 'constants.dart' as constants; +@internal class SentrySpanHelper { - /// @nodoc final Hub _hub; - - /// @nodoc final String _origin; - ISentrySpan? _parentSpan; - /// @nodoc SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter(); - /// @nodoc - @internal Future asyncWrapInSpan( String description, Future Function() execute, { String? dbName, - bool useTransactionSpan = false, }) async { final currentSpan = _parentSpan ?? _hub.getSpan(); final span = currentSpan?.startChild( - SentryQueryExecutor.dbOp, + constants.dbSqlQueryOp, description: description, ); @@ -33,12 +26,12 @@ class SentrySpanHelper { span?.origin = _origin; span?.setData( - SentryQueryExecutor.dbSystemKey, - SentryQueryExecutor.dbSystem, + constants.dbSystemKey, + constants.dbSystem, ); if (dbName != null) { - span?.setData(SentryQueryExecutor.dbNameKey, dbName); + span?.setData(constants.dbNameKey, dbName); } try { @@ -56,15 +49,13 @@ class SentrySpanHelper { } } - /// @nodoc - @internal T beginTransaction( T Function() execute, { String? dbName, }) { final scopeSpan = _hub.getSpan(); _parentSpan = scopeSpan?.startChild( - SentryQueryExecutor.dbOp, + constants.dbSqlTransactionOp, description: 'Begin transaction', ); @@ -72,12 +63,12 @@ class SentrySpanHelper { _parentSpan?.origin = _origin; _parentSpan?.setData( - SentryQueryExecutor.dbSystemKey, - SentryQueryExecutor.dbSystem, + constants.dbSystemKey, + constants.dbSystem, ); if (dbName != null) { - _parentSpan?.setData(SentryQueryExecutor.dbNameKey, dbName); + _parentSpan?.setData(constants.dbNameKey, dbName); } try { @@ -93,8 +84,6 @@ class SentrySpanHelper { } } - /// @nodoc - @internal Future finishTransaction(Future Function() execute) async { try { final result = await execute(); @@ -112,8 +101,6 @@ class SentrySpanHelper { } } - /// @nodoc - @internal Future abortTransaction(Future Function() execute) async { try { final result = await execute(); From 9870881539f470430a41cee6518937b97ba9ecbd Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 10 Feb 2025 16:16:49 +0100 Subject: [PATCH 03/29] update --- dart/lib/src/sentry_trace_origins.dart | 4 +- drift/analysis_options.yaml | 1 - drift/example/database.g.dart | 162 ++++++ drift/example/example.dart | 46 +- drift/lib/src/constants.dart | 8 + drift/lib/src/sentry_query_interceptor.dart | 121 +++-- drift/lib/src/sentry_span_helper.dart | 5 +- drift/test/mocks/mocks.mocks.dart | 482 ++++++------------ ...abase_test.dart => sentry_drift_test.dart} | 277 +++++----- drift/test/test_database.g.dart | 162 ++++++ 10 files changed, 767 insertions(+), 501 deletions(-) rename drift/test/{sentry_database_test.dart => sentry_drift_test.dart} (69%) diff --git a/dart/lib/src/sentry_trace_origins.dart b/dart/lib/src/sentry_trace_origins.dart index 4377fa2c03..23359bf9f2 100644 --- a/dart/lib/src/sentry_trace_origins.dart +++ b/dart/lib/src/sentry_trace_origins.dart @@ -24,9 +24,7 @@ class SentryTraceOrigins { static const autoDbHiveBoxBase = 'auto.db.hive.box_base'; static const autoDbHiveLazyBox = 'auto.db.hive.lazy_box'; static const autoDbHiveBoxCollection = 'auto.db.hive.box_collection'; - static const autoDbDriftQueryExecutor = 'auto.db.drift.query.executor'; - static const autoDbDriftTransactionExecutor = - 'auto.db.drift.transaction.executor'; + static const autoDbDriftQueryInterceptor = 'auto.db.drift.query.interceptor'; static const autoUiTimeToDisplay = 'auto.ui.time_to_display'; static const manualUiTimeToDisplay = 'manual.ui.time_to_display'; } diff --git a/drift/analysis_options.yaml b/drift/analysis_options.yaml index c5f7c0d066..7119dc352d 100644 --- a/drift/analysis_options.yaml +++ b/drift/analysis_options.yaml @@ -24,7 +24,6 @@ analyzer: linter: rules: - prefer_final_locals - - public_member_api_docs - prefer_single_quotes - prefer_relative_imports - unnecessary_brace_in_string_interps diff --git a/drift/example/database.g.dart b/drift/example/database.g.dart index 1f9d234456..3f01770182 100644 --- a/drift/example/database.g.dart +++ b/drift/example/database.g.dart @@ -160,6 +160,15 @@ class TodoItem extends DataClass implements Insertable { content: content ?? this.content, category: category.present ? category.value : this.category, ); + TodoItem copyWithCompanion(TodoItemsCompanion data) { + return TodoItem( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + ); + } + @override String toString() { return (StringBuffer('TodoItem(') @@ -260,6 +269,7 @@ class TodoItemsCompanion extends UpdateCompanion { abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $TodoItemsTable todoItems = $TodoItemsTable(this); @override Iterable> get allTables => @@ -267,3 +277,155 @@ abstract class _$AppDatabase extends GeneratedDatabase { @override List get allSchemaEntities => [todoItems]; } + +typedef $$TodoItemsTableCreateCompanionBuilder = TodoItemsCompanion Function({ + Value id, + required String title, + required String content, + Value category, +}); +typedef $$TodoItemsTableUpdateCompanionBuilder = TodoItemsCompanion Function({ + Value id, + Value title, + Value content, + Value category, +}); + +class $$TodoItemsTableFilterComposer + extends Composer<_$AppDatabase, $TodoItemsTable> { + $$TodoItemsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnFilters(column)); + + ColumnFilters get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnFilters(column)); + + ColumnFilters get category => $composableBuilder( + column: $table.category, builder: (column) => ColumnFilters(column)); +} + +class $$TodoItemsTableOrderingComposer + extends Composer<_$AppDatabase, $TodoItemsTable> { + $$TodoItemsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get category => $composableBuilder( + column: $table.category, builder: (column) => ColumnOrderings(column)); +} + +class $$TodoItemsTableAnnotationComposer + extends Composer<_$AppDatabase, $TodoItemsTable> { + $$TodoItemsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + GeneratedColumn get category => + $composableBuilder(column: $table.category, builder: (column) => column); +} + +class $$TodoItemsTableTableManager extends RootTableManager< + _$AppDatabase, + $TodoItemsTable, + TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableAnnotationComposer, + $$TodoItemsTableCreateCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder, + (TodoItem, BaseReferences<_$AppDatabase, $TodoItemsTable, TodoItem>), + TodoItem, + PrefetchHooks Function()> { + $$TodoItemsTableTableManager(_$AppDatabase db, $TodoItemsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TodoItemsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TodoItemsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TodoItemsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + Value content = const Value.absent(), + Value category = const Value.absent(), + }) => + TodoItemsCompanion( + id: id, + title: title, + content: content, + category: category, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + required String title, + required String content, + Value category = const Value.absent(), + }) => + TodoItemsCompanion.insert( + id: id, + title: title, + content: content, + category: category, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$TodoItemsTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $TodoItemsTable, + TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableAnnotationComposer, + $$TodoItemsTableCreateCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder, + (TodoItem, BaseReferences<_$AppDatabase, $TodoItemsTable, TodoItem>), + TodoItem, + PrefetchHooks Function()>; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$TodoItemsTableTableManager get todoItems => + $$TodoItemsTableTableManager(_db, _db.todoItems); +} diff --git a/drift/example/example.dart b/drift/example/example.dart index 4d34f81d45..eeba987b75 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -34,13 +34,57 @@ Future runApp() async { await db.transaction(() async { await db.into(db.todoItems).insert( TodoItemsCompanion.insert( - id: Value(0), title: 'This is a test thing', content: 'test', ), ); + + await db.batch((batch) { + // functions in a batch don't have to be awaited - just + // await the whole batch afterwards. + batch.insertAll(db.todoItems, [ + TodoItemsCompanion.insert( + title: 'First entry', + content: 'My content', + ), + TodoItemsCompanion.insert( + title: 'Another entry', + content: 'More content', + ), + // ... + ]); + }); }); + // await db.batch((batch) async { + // batch.insertAll(db.todoItems, [ + // TodoItemsCompanion.insert( + // title: 'This is a test thing inside a batch #1', + // content: 'test', + // ), + // ]); + // await batch.into(db.todoItems).insert( + // TodoItemsCompanion.insert( + // title: 'This is a test thing inside a batch #1', + // content: 'test', + // ), + // ); + // + // await db.into(db.todoItems).insert( + // TodoItemsCompanion.insert( + // title: 'This is a test thing inside a batch #2', + // content: 'test', + // ), + // ); + // + // await db.into(db.todoItems).insert( + // TodoItemsCompanion.insert( + // title: 'This is a test thing inside a batch #3', + // content: 'test', + // ), + // ); + // }); + final items = await db.select(db.todoItems).get(); print(items); diff --git a/drift/lib/src/constants.dart b/drift/lib/src/constants.dart index 6c2ce6ec41..b8a96049d1 100644 --- a/drift/lib/src/constants.dart +++ b/drift/lib/src/constants.dart @@ -3,3 +3,11 @@ const String dbSystem = 'db.sqlite'; const String dbNameKey = 'db.name'; const String dbSqlQueryOp = 'db.sql.query'; const String dbSqlTransactionOp = 'db.sql.transaction'; +const String dbSqlBatchOp = 'db.sql.batch'; +const String dbOpenOp = 'db.open'; +const String dbCloseOp = 'db.close'; +const String integrationName = 'SentryDriftTracing'; +const String dbTransactionDesc = 'Transaction'; +const String dbBatchDesc = 'Batch'; +String dbOpenDesc({required String dbName}) => 'Open database $dbName'; +String dbCloseDesc({required String dbName}) => 'Close database $dbName'; diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index 156515068b..10e184a6eb 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -4,33 +4,56 @@ import 'package:drift/drift.dart'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; +import 'constants.dart' as constants; import 'sentry_span_helper.dart'; +import 'version.dart'; -/// Doc +/// A Sentry query interceptor that wraps database operations in performance monitoring spans. +/// +/// This interceptor tracks all database operations executed through a Drift database connection, +/// including transactions, batches, and individual CRUD operations. Each operation is captured +/// as a Sentry span with relevant context. class SentryQueryInterceptor extends QueryInterceptor { final String _dbName; - final Hub _hub; + late final SentrySpanHelper _spanHelper; + bool _isDbOpen = false; - final _spanHelper = SentrySpanHelper( - // ignore: invalid_use_of_internal_member - SentryTraceOrigins.autoDbDriftQueryExecutor, - ); - - /// @nodoc SentryQueryInterceptor({required String databaseName, @internal Hub? hub}) - : _dbName = databaseName, - _hub = hub ?? HubAdapter(); + : _dbName = databaseName { + _spanHelper = SentrySpanHelper( + // ignore: invalid_use_of_internal_member + SentryTraceOrigins.autoDbDriftQueryInterceptor, + hub: hub, + ); + // ignore: invalid_use_of_internal_member + final options = hub?.options; + options?.sdk.addIntegration(constants.integrationName); + options?.sdk.addPackage(packageName, sdkVersion); + } Future _run( String description, FutureOr Function() execute, { String? operation, - }) async { - return await _spanHelper.asyncWrapInSpan( - description, - () async => execute(), - dbName: _dbName, - ); + }) async => + _spanHelper.asyncWrapInSpan( + description, + () async => execute(), + dbName: _dbName, + operation: operation, + ); + + @override + Future ensureOpen(QueryExecutor executor, QueryExecutorUser user) { + if (!_isDbOpen) { + _isDbOpen = true; + return _run( + constants.dbOpenDesc(dbName: _dbName), + () => super.ensureOpen(executor, user), + operation: constants.dbOpenOp, + ); + } + return super.ensureOpen(executor, user); } @override @@ -41,6 +64,25 @@ class SentryQueryInterceptor extends QueryInterceptor { ); } + @override + Future close(QueryExecutor inner) { + return _run( + constants.dbCloseDesc(dbName: _dbName), + () => super.close(inner), + operation: constants.dbCloseOp, + ); + } + + @override + Future runBatched( + QueryExecutor executor, BatchedStatements statements) { + return _run( + constants.dbBatchDesc, + () => super.runBatched(executor, statements), + operation: constants.dbSqlBatchOp, + ); + } + @override Future commitTransaction(TransactionExecutor inner) { return _spanHelper.finishTransaction(() => super.commitTransaction(inner)); @@ -51,48 +93,51 @@ class SentryQueryInterceptor extends QueryInterceptor { return _spanHelper.abortTransaction(() => super.rollbackTransaction(inner)); } - @override - Future runBatched( - QueryExecutor executor, BatchedStatements statements) { - return _run( - 'batch with $statements', () => executor.runBatched(statements)); - } - @override Future runInsert( - QueryExecutor executor, String statement, List args) { - print('run insert'); + QueryExecutor executor, + String statement, + List args, + ) { return _run( - '$statement with $args', + statement, () => executor.runInsert(statement, args), ); } @override Future runUpdate( - QueryExecutor executor, String statement, List args) { - return _run( - '$statement with $args', () => executor.runUpdate(statement, args)); + QueryExecutor executor, + String statement, + List args, + ) { + return _run(statement, () => executor.runUpdate(statement, args)); } @override Future runDelete( - QueryExecutor executor, String statement, List args) { - return _run( - '$statement with $args', () => executor.runDelete(statement, args)); + QueryExecutor executor, + String statement, + List args, + ) { + return _run(statement, () => executor.runDelete(statement, args)); } @override Future runCustom( - QueryExecutor executor, String statement, List args) { - return _run( - '$statement with $args', () => executor.runCustom(statement, args)); + QueryExecutor executor, + String statement, + List args, + ) { + return _run(statement, () => executor.runCustom(statement, args)); } @override Future>> runSelect( - QueryExecutor executor, String statement, List args) { - return _run( - '$statement with $args', () => executor.runSelect(statement, args)); + QueryExecutor executor, + String statement, + List args, + ) { + return _run(statement, () => executor.runSelect(statement, args)); } } diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index f6602faa15..9130777364 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -15,10 +15,11 @@ class SentrySpanHelper { String description, Future Function() execute, { String? dbName, + String? operation, }) async { final currentSpan = _parentSpan ?? _hub.getSpan(); final span = currentSpan?.startChild( - constants.dbSqlQueryOp, + operation ?? constants.dbSqlQueryOp, description: description, ); @@ -56,7 +57,7 @@ class SentrySpanHelper { final scopeSpan = _hub.getSpan(); _parentSpan = scopeSpan?.startChild( constants.dbSqlTransactionOp, - description: 'Begin transaction', + description: constants.dbTransactionDesc, ); // ignore: invalid_use_of_internal_member diff --git a/drift/test/mocks/mocks.mocks.dart b/drift/test/mocks/mocks.mocks.dart index f6a88c359a..df849a3cc4 100644 --- a/drift/test/mocks/mocks.mocks.dart +++ b/drift/test/mocks/mocks.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.2 from annotations +// Mocks generated by Mockito 5.4.5 from annotations // in sentry_drift/test/mocks/mocks.dart. // Do not manually edit this file. @@ -15,82 +15,50 @@ import 'package:sentry/src/profiling.dart' as _i4; // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeSentryOptions_0 extends _i1.SmartFake implements _i2.SentryOptions { - _FakeSentryOptions_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeSentryOptions_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeSentryId_1 extends _i1.SmartFake implements _i2.SentryId { - _FakeSentryId_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeSentryId_1(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeScope_2 extends _i1.SmartFake implements _i2.Scope { - _FakeScope_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeScope_2(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeHub_3 extends _i1.SmartFake implements _i2.Hub { - _FakeHub_3( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeHub_3(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeISentrySpan_4 extends _i1.SmartFake implements _i2.ISentrySpan { - _FakeISentrySpan_4( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeISentrySpan_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeQueryExecutor_5 extends _i1.SmartFake implements _i3.QueryExecutor { - _FakeQueryExecutor_5( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeQueryExecutor_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } class _FakeTransactionExecutor_6 extends _i1.SmartFake implements _i3.TransactionExecutor { - _FakeTransactionExecutor_6( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); + _FakeTransactionExecutor_6(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); } /// A class which mocks [Hub]. @@ -111,35 +79,25 @@ class MockHub extends _i1.Mock implements _i2.Hub { ) as _i2.SentryOptions); @override - bool get isEnabled => (super.noSuchMethod( - Invocation.getter(#isEnabled), - returnValue: false, - ) as bool); + bool get isEnabled => + (super.noSuchMethod(Invocation.getter(#isEnabled), returnValue: false) + as bool); @override _i2.SentryId get lastEventId => (super.noSuchMethod( Invocation.getter(#lastEventId), - returnValue: _FakeSentryId_1( - this, - Invocation.getter(#lastEventId), - ), + returnValue: _FakeSentryId_1(this, Invocation.getter(#lastEventId)), ) as _i2.SentryId); @override _i2.Scope get scope => (super.noSuchMethod( Invocation.getter(#scope), - returnValue: _FakeScope_2( - this, - Invocation.getter(#scope), - ), + returnValue: _FakeScope_2(this, Invocation.getter(#scope)), ) as _i2.Scope); @override set profilerFactory(_i4.SentryProfilerFactory? value) => super.noSuchMethod( - Invocation.setter( - #profilerFactory, - value, - ), + Invocation.setter(#profilerFactory, value), returnValueForMissingStub: null, ); @@ -154,24 +112,18 @@ class MockHub extends _i1.Mock implements _i2.Hub { Invocation.method( #captureEvent, [event], - { - #stackTrace: stackTrace, - #hint: hint, - #withScope: withScope, - }, - ), - returnValue: _i5.Future<_i2.SentryId>.value(_FakeSentryId_1( - this, - Invocation.method( - #captureEvent, - [event], - { - #stackTrace: stackTrace, - #hint: hint, - #withScope: withScope, - }, + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureEvent, + [event], + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), ), - )), + ), ) as _i5.Future<_i2.SentryId>); @override @@ -185,24 +137,18 @@ class MockHub extends _i1.Mock implements _i2.Hub { Invocation.method( #captureException, [throwable], - { - #stackTrace: stackTrace, - #hint: hint, - #withScope: withScope, - }, - ), - returnValue: _i5.Future<_i2.SentryId>.value(_FakeSentryId_1( - this, - Invocation.method( - #captureException, - [throwable], - { - #stackTrace: stackTrace, - #hint: hint, - #withScope: withScope, - }, + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureException, + [throwable], + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), ), - )), + ), ) as _i5.Future<_i2.SentryId>); @override @@ -226,88 +172,87 @@ class MockHub extends _i1.Mock implements _i2.Hub { #withScope: withScope, }, ), - returnValue: _i5.Future<_i2.SentryId>.value(_FakeSentryId_1( - this, - Invocation.method( - #captureMessage, - [message], - { - #level: level, - #template: template, - #params: params, - #hint: hint, - #withScope: withScope, - }, + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureMessage, + [message], + { + #level: level, + #template: template, + #params: params, + #hint: hint, + #withScope: withScope, + }, + ), ), - )), + ), ) as _i5.Future<_i2.SentryId>); @override _i5.Future captureUserFeedback(_i2.SentryUserFeedback? userFeedback) => (super.noSuchMethod( - Invocation.method( - #captureUserFeedback, - [userFeedback], - ), + Invocation.method(#captureUserFeedback, [userFeedback]), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override - _i5.Future addBreadcrumb( - _i2.Breadcrumb? crumb, { + _i5.Future<_i2.SentryId> captureFeedback( + _i2.SentryFeedback? feedback, { _i2.Hint? hint, + _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( Invocation.method( - #addBreadcrumb, - [crumb], - {#hint: hint}, + #captureFeedback, + [feedback], + {#hint: hint, #withScope: withScope}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureFeedback, + [feedback], + {#hint: hint, #withScope: withScope}, + ), + ), ), + ) as _i5.Future<_i2.SentryId>); + + @override + _i5.Future addBreadcrumb(_i2.Breadcrumb? crumb, {_i2.Hint? hint}) => + (super.noSuchMethod( + Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override void bindClient(_i2.SentryClient? client) => super.noSuchMethod( - Invocation.method( - #bindClient, - [client], - ), + Invocation.method(#bindClient, [client]), returnValueForMissingStub: null, ); @override _i2.Hub clone() => (super.noSuchMethod( - Invocation.method( - #clone, - [], - ), - returnValue: _FakeHub_3( - this, - Invocation.method( - #clone, - [], - ), - ), + Invocation.method(#clone, []), + returnValue: _FakeHub_3(this, Invocation.method(#clone, [])), ) as _i2.Hub); @override _i5.Future close() => (super.noSuchMethod( - Invocation.method( - #close, - [], - ), + Invocation.method(#close, []), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.FutureOr configureScope(_i2.ScopeCallback? callback) => - (super.noSuchMethod(Invocation.method( - #configureScope, - [callback], - )) as _i5.FutureOr); + (super.noSuchMethod(Invocation.method(#configureScope, [callback])) + as _i5.FutureOr); @override _i2.ISentrySpan startTransaction( @@ -325,10 +270,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { (super.noSuchMethod( Invocation.method( #startTransaction, - [ - name, - operation, - ], + [name, operation], { #description: description, #startTimestamp: startTimestamp, @@ -344,10 +286,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { this, Invocation.method( #startTransaction, - [ - name, - operation, - ], + [name, operation], { #description: description, #startTimestamp: startTimestamp, @@ -416,14 +355,16 @@ class MockHub extends _i1.Mock implements _i2.Hub { [transaction], {#traceContext: traceContext}, ), - returnValue: _i5.Future<_i2.SentryId>.value(_FakeSentryId_1( - this, - Invocation.method( - #captureTransaction, - [transaction], - {#traceContext: traceContext}, + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureTransaction, + [transaction], + {#traceContext: traceContext}, + ), ), - )), + ), ) as _i5.Future<_i2.SentryId>); @override @@ -433,14 +374,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { String? transaction, ) => super.noSuchMethod( - Invocation.method( - #setSpanContext, - [ - throwable, - span, - transaction, - ], - ), + Invocation.method(#setSpanContext, [throwable, span, transaction]), returnValueForMissingStub: null, ); } @@ -456,11 +390,9 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { @override _i6.DatabaseOpener get opener => (super.noSuchMethod( Invocation.getter(#opener), - returnValue: () => - _i5.Future<_i3.QueryExecutor>.value(_FakeQueryExecutor_5( - this, - Invocation.getter(#opener), - )), + returnValue: () => _i5.Future<_i3.QueryExecutor>.value( + _FakeQueryExecutor_5(this, Invocation.getter(#opener)), + ), ) as _i6.DatabaseOpener); @override @@ -470,87 +402,57 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { ) as _i3.SqlDialect); @override - _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( - Invocation.method( - #beginTransaction, - [], + _i3.QueryExecutor beginExclusive() => (super.noSuchMethod( + Invocation.method(#beginExclusive, []), + returnValue: _FakeQueryExecutor_5( + this, + Invocation.method(#beginExclusive, []), ), + ) as _i3.QueryExecutor); + + @override + _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( + Invocation.method(#beginTransaction, []), returnValue: _FakeTransactionExecutor_6( this, - Invocation.method( - #beginTransaction, - [], - ), + Invocation.method(#beginTransaction, []), ), ) as _i3.TransactionExecutor); @override _i5.Future ensureOpen(_i3.QueryExecutorUser? user) => (super.noSuchMethod( - Invocation.method( - #ensureOpen, - [user], - ), + Invocation.method(#ensureOpen, [user]), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future runBatched(_i3.BatchedStatements? statements) => (super.noSuchMethod( - Invocation.method( - #runBatched, - [statements], - ), + Invocation.method(#runBatched, [statements]), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override - _i5.Future runCustom( - String? statement, [ - List? args, - ]) => + _i5.Future runCustom(String? statement, [List? args]) => (super.noSuchMethod( - Invocation.method( - #runCustom, - [ - statement, - args, - ], - ), + Invocation.method(#runCustom, [statement, args]), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override - _i5.Future runDelete( - String? statement, - List? args, - ) => + _i5.Future runDelete(String? statement, List? args) => (super.noSuchMethod( - Invocation.method( - #runDelete, - [ - statement, - args, - ], - ), + Invocation.method(#runDelete, [statement, args]), returnValue: _i5.Future.value(0), ) as _i5.Future); @override - _i5.Future runInsert( - String? statement, - List? args, - ) => + _i5.Future runInsert(String? statement, List? args) => (super.noSuchMethod( - Invocation.method( - #runInsert, - [ - statement, - args, - ], - ), + Invocation.method(#runInsert, [statement, args]), returnValue: _i5.Future.value(0), ) as _i5.Future); @@ -560,39 +462,22 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { List? args, ) => (super.noSuchMethod( - Invocation.method( - #runSelect, - [ - statement, - args, - ], - ), + Invocation.method(#runSelect, [statement, args]), returnValue: _i5.Future>>.value( - >[]), + >[], + ), ) as _i5.Future>>); @override - _i5.Future runUpdate( - String? statement, - List? args, - ) => + _i5.Future runUpdate(String? statement, List? args) => (super.noSuchMethod( - Invocation.method( - #runUpdate, - [ - statement, - args, - ], - ), + Invocation.method(#runUpdate, [statement, args]), returnValue: _i5.Future.value(0), ) as _i5.Future); @override _i5.Future close() => (super.noSuchMethod( - Invocation.method( - #close, - [], - ), + Invocation.method(#close, []), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @@ -621,20 +506,14 @@ class MockTransactionExecutor extends _i1.Mock @override _i5.Future send() => (super.noSuchMethod( - Invocation.method( - #send, - [], - ), + Invocation.method(#send, []), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future rollback() => (super.noSuchMethod( - Invocation.method( - #rollback, - [], - ), + Invocation.method(#rollback, []), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @@ -642,10 +521,7 @@ class MockTransactionExecutor extends _i1.Mock @override _i5.Future ensureOpen(_i3.QueryExecutorUser? user) => (super.noSuchMethod( - Invocation.method( - #ensureOpen, - [user], - ), + Invocation.method(#ensureOpen, [user]), returnValue: _i5.Future.value(false), ) as _i5.Future); @@ -655,78 +531,37 @@ class MockTransactionExecutor extends _i1.Mock List? args, ) => (super.noSuchMethod( - Invocation.method( - #runSelect, - [ - statement, - args, - ], - ), + Invocation.method(#runSelect, [statement, args]), returnValue: _i5.Future>>.value( - >[]), + >[], + ), ) as _i5.Future>>); @override - _i5.Future runInsert( - String? statement, - List? args, - ) => + _i5.Future runInsert(String? statement, List? args) => (super.noSuchMethod( - Invocation.method( - #runInsert, - [ - statement, - args, - ], - ), + Invocation.method(#runInsert, [statement, args]), returnValue: _i5.Future.value(0), ) as _i5.Future); @override - _i5.Future runUpdate( - String? statement, - List? args, - ) => + _i5.Future runUpdate(String? statement, List? args) => (super.noSuchMethod( - Invocation.method( - #runUpdate, - [ - statement, - args, - ], - ), + Invocation.method(#runUpdate, [statement, args]), returnValue: _i5.Future.value(0), ) as _i5.Future); @override - _i5.Future runDelete( - String? statement, - List? args, - ) => + _i5.Future runDelete(String? statement, List? args) => (super.noSuchMethod( - Invocation.method( - #runDelete, - [ - statement, - args, - ], - ), + Invocation.method(#runDelete, [statement, args]), returnValue: _i5.Future.value(0), ) as _i5.Future); @override - _i5.Future runCustom( - String? statement, [ - List? args, - ]) => + _i5.Future runCustom(String? statement, [List? args]) => (super.noSuchMethod( - Invocation.method( - #runCustom, - [ - statement, - args, - ], - ), + Invocation.method(#runCustom, [statement, args]), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @@ -734,35 +569,32 @@ class MockTransactionExecutor extends _i1.Mock @override _i5.Future runBatched(_i3.BatchedStatements? statements) => (super.noSuchMethod( - Invocation.method( - #runBatched, - [statements], - ), + Invocation.method(#runBatched, [statements]), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( - Invocation.method( - #beginTransaction, - [], - ), + Invocation.method(#beginTransaction, []), returnValue: _FakeTransactionExecutor_6( this, - Invocation.method( - #beginTransaction, - [], - ), + Invocation.method(#beginTransaction, []), ), ) as _i3.TransactionExecutor); @override - _i5.Future close() => (super.noSuchMethod( - Invocation.method( - #close, - [], + _i3.QueryExecutor beginExclusive() => (super.noSuchMethod( + Invocation.method(#beginExclusive, []), + returnValue: _FakeQueryExecutor_5( + this, + Invocation.method(#beginExclusive, []), ), + ) as _i3.QueryExecutor); + + @override + _i5.Future close() => (super.noSuchMethod( + Invocation.method(#close, []), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); diff --git a/drift/test/sentry_database_test.dart b/drift/test/sentry_drift_test.dart similarity index 69% rename from drift/test/sentry_database_test.dart rename to drift/test/sentry_drift_test.dart index 4a3d4daa3c..79120dbd16 100644 --- a/drift/test/sentry_database_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -8,8 +8,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry_drift/src/sentry_query_executor.dart'; -import 'package:sentry_drift/src/sentry_transaction_executor.dart'; +import 'package:sentry_drift/sentry_drift.dart'; +import 'package:sentry_drift/src/constants.dart' as constants; import 'package:sentry_drift/src/version.dart'; import 'package:sqlite3/open.dart'; @@ -27,28 +27,24 @@ void main() { 'UPDATE "todo_items" SET "title" = ?, "body" = ? WHERE "title" = ?;'; final expectedSelectStatement = 'SELECT * FROM todo_items'; final expectedDeleteStatement = 'DELETE FROM "todo_items";'; - final expectedCloseStatement = 'Close DB: ${Fixture.dbName}'; - final expectedOpenStatement = 'Open DB: ${Fixture.dbName}'; - final expectedTransactionStatement = 'transaction'; - final withinTransactionDescription = 'Within transaction: '; void verifySpan( String description, SentrySpan? span, { - String origin = SentryTraceOrigins.autoDbDriftQueryExecutor, + String? operation, SpanStatus? status, }) { status ??= SpanStatus.ok(); - expect(span?.context.operation, SentryQueryExecutor.dbOp); + expect(span?.context.operation, operation ?? constants.dbSqlQueryOp); expect(span?.context.description, description); expect(span?.status, status); - expect(span?.origin, origin); + expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); expect( - span?.data[SentryQueryExecutor.dbSystemKey], - SentryQueryExecutor.dbSystem, + span?.data[constants.dbSystemKey], + constants.dbSystem, ); expect( - span?.data[SentryQueryExecutor.dbNameKey], + span?.data[constants.dbNameKey], Fixture.dbName, ); } @@ -57,18 +53,19 @@ void main() { String description, Exception exception, SentrySpan? span, { - String origin = SentryTraceOrigins.autoDbDriftQueryExecutor, + String? operation, + SpanStatus? status, }) { - expect(span?.context.operation, SentryQueryExecutor.dbOp); + expect(span?.context.operation, operation ?? constants.dbSqlQueryOp); expect(span?.context.description, description); - expect(span?.status, SpanStatus.internalError()); - expect(span?.origin, origin); + expect(span?.status, status ?? SpanStatus.internalError()); + expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); expect( - span?.data[SentryQueryExecutor.dbSystemKey], - SentryQueryExecutor.dbSystem, + span?.data[constants.dbSystemKey], + constants.dbSystem, ); expect( - span?.data[SentryQueryExecutor.dbNameKey], + span?.data[constants.dbNameKey], Fixture.dbName, ); @@ -93,6 +90,21 @@ void main() { } } + Future insertIntoBatch(AppDatabase sut) { + return sut.batch((batch) { + batch.insertAll(sut.todoItems, [ + TodoItemsCompanion.insert( + title: 'todo: finish drift setup #1', + content: 'We can now write queries and define our own tables.', + ), + TodoItemsCompanion.insert( + title: 'todo: finish drift setup #2', + content: 'We can now write queries and define our own tables.', + ), + ]); + }); + } + Future updateRow(AppDatabase sut, {bool withError = false}) { if (withError) { return (sut.update(sut.todoItems) @@ -140,7 +152,9 @@ void main() { final openSpansCount = fixture.tracer.children .where( - (element) => element.context.description == expectedOpenStatement, + (element) => + element.context.description == + constants.dbOpenDesc(dbName: Fixture.dbName), ) .length; @@ -203,23 +217,20 @@ void main() { final insertSpanCount = fixture.tracer.children .where( - (element) => - element.context.description == - '$withinTransactionDescription$expectedInsertStatement', + (element) => element.context.description == expectedInsertStatement, ) .length; expect(insertSpanCount, 2); verifySpan( - '$withinTransactionDescription$expectedInsertStatement', + expectedInsertStatement, fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, ); verifySpan( - expectedTransactionStatement, - fixture.getCreatedSpanByDescription(expectedTransactionStatement), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, + constants.dbTransactionDesc, + fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), + operation: constants.dbSqlTransactionOp, ); }); @@ -233,23 +244,20 @@ void main() { final updateSpanCount = fixture.tracer.children .where( - (element) => - element.context.description == - '$withinTransactionDescription$expectedUpdateStatement', + (element) => element.context.description == expectedUpdateStatement, ) .length; expect(updateSpanCount, 1); verifySpan( - '$withinTransactionDescription$expectedUpdateStatement', + expectedUpdateStatement, fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, ); verifySpan( - expectedTransactionStatement, - fixture.getCreatedSpanByDescription(expectedTransactionStatement), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, + constants.dbTransactionDesc, + fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), + operation: constants.dbSqlTransactionOp, ); }); @@ -263,23 +271,20 @@ void main() { final deleteSpanCount = fixture.tracer.children .where( - (element) => - element.context.description == - '$withinTransactionDescription$expectedDeleteStatement', + (element) => element.context.description == expectedDeleteStatement, ) .length; expect(deleteSpanCount, 1); verifySpan( - '$withinTransactionDescription$expectedDeleteStatement', + expectedDeleteStatement, fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, ); verifySpan( - expectedTransactionStatement, - fixture.getCreatedSpanByDescription(expectedTransactionStatement), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, + constants.dbTransactionDesc, + fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), + operation: constants.dbSqlTransactionOp, ); }); @@ -293,23 +298,20 @@ void main() { final customSpanCount = fixture.tracer.children .where( - (element) => - element.context.description == - '$withinTransactionDescription$expectedSelectStatement', + (element) => element.context.description == expectedSelectStatement, ) .length; expect(customSpanCount, 1); verifySpan( - '$withinTransactionDescription$expectedSelectStatement', + expectedSelectStatement, fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, ); verifySpan( - expectedTransactionStatement, - fixture.getCreatedSpanByDescription(expectedTransactionStatement), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, + constants.dbTransactionDesc, + fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), + operation: constants.dbSqlTransactionOp, ); }); @@ -331,25 +333,22 @@ void main() { final abortedSpan = spans.first; verifySpan( - expectedTransactionStatement, + constants.dbTransactionDesc, abortedSpan, - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, status: SpanStatus.aborted(), + operation: constants.dbSqlTransactionOp, ); }); test('batch adds span', () async { final sut = fixture.sut; - await sut.batch((batch) async { - await insertRow(sut); - await insertRow(sut); - }); + await insertIntoBatch(sut); verifySpan( - 'batch', + constants.dbBatchDesc, fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, + operation: constants.dbSqlBatchOp, ); }); @@ -359,8 +358,9 @@ void main() { await sut.close(); verifySpan( - 'Close DB: ${Fixture.dbName}', + constants.dbCloseDesc(dbName: Fixture.dbName), fixture.getCreatedSpan(), + operation: constants.dbCloseOp, ); }); @@ -372,8 +372,11 @@ void main() { await sut.select(sut.todoItems).get(); verifySpan( - expectedOpenStatement, - fixture.getCreatedSpanByDescription(expectedOpenStatement), + constants.dbOpenDesc(dbName: Fixture.dbName), + fixture.getCreatedSpanByDescription( + constants.dbOpenDesc(dbName: Fixture.dbName), + ), + operation: constants.dbOpenOp, ); }); }); @@ -424,15 +427,19 @@ void main() { when(fixture.hub.getSpan()).thenReturn(fixture.tracer); when(fixture.mockLazyDatabase.ensureOpen(any)) .thenAnswer((_) => Future.value(true)); - - await fixture.setUp(injectMock: true); + when(fixture.mockLazyDatabase.dialect).thenReturn(SqlDialect.sqlite); }); tearDown(() async { - await fixture.tearDown(); + // catch errors because we purposefully throw a close in one of the tests + try { + await fixture.tearDown(); + } catch (_) {} }); test('throwing runInsert throws error span', () async { + await fixture.setUp(injectMock: true); + when(fixture.mockLazyDatabase.runInsert(any, any)) .thenThrow(fixture.exception); @@ -450,8 +457,12 @@ void main() { }); test('throwing runUpdate throws error span', () async { + await fixture.setUp(injectMock: true); + when(fixture.mockLazyDatabase.runUpdate(any, any)) .thenThrow(fixture.exception); + when(fixture.mockLazyDatabase.ensureOpen(any)) + .thenAnswer((_) => Future.value(true)); try { await updateRow(fixture.sut); @@ -467,6 +478,8 @@ void main() { }); test('throwing runCustom throws error span', () async { + await fixture.setUp(injectMock: true); + when(fixture.mockLazyDatabase.runCustom(any, any)) .thenThrow(fixture.exception); @@ -487,20 +500,12 @@ void main() { final mockTransactionExecutor = MockTransactionExecutor(); when(mockTransactionExecutor.beginTransaction()) .thenThrow(fixture.exception); + when(mockTransactionExecutor.ensureOpen(any)) + .thenAnswer((_) => Future.value(true)); - try { - // We need to move it inside the try/catch becaue SentryTransactionExecutor - // starts beginTransaction() directly after init - final SentryTransactionExecutor transactionExecutor = - SentryTransactionExecutor( - mockTransactionExecutor, - fixture.hub, - dbName: Fixture.dbName, - ); - - when(fixture.mockLazyDatabase.beginTransaction()) - .thenReturn(transactionExecutor); + await fixture.setUp(customExecutor: mockTransactionExecutor); + try { await fixture.sut.transaction(() async { await insertRow(fixture.sut); }); @@ -508,47 +513,41 @@ void main() { expect(exception, fixture.exception); } - verifyErrorSpan( - expectedTransactionStatement, - fixture.exception, - fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, - ); + verifyErrorSpan(constants.dbTransactionDesc, fixture.exception, + fixture.getCreatedSpan(), + operation: constants.dbSqlTransactionOp); }); - test('throwing batch throws error span', () async { + test('throwing batch throws error span in transaction', () async { + await fixture.setUp(injectMock: true); + final mockTransactionExecutor = MockTransactionExecutor(); - when(mockTransactionExecutor.beginTransaction()) + when(mockTransactionExecutor.ensureOpen(any)) + .thenAnswer((_) => Future.value(true)); + when(fixture.mockLazyDatabase.beginTransaction()) + .thenReturn(mockTransactionExecutor); + when(mockTransactionExecutor.runBatched(any)) .thenThrow(fixture.exception); - // We need to move it inside the try/catch becaue SentryTransactionExecutor - // starts beginTransaction() directly after init - final SentryTransactionExecutor transactionExecutor = - SentryTransactionExecutor( - mockTransactionExecutor, - fixture.hub, - dbName: Fixture.dbName, + await expectLater( + () async => await insertIntoBatch(fixture.sut), + throwsException, ); - when(fixture.mockLazyDatabase.beginTransaction()) - .thenReturn(transactionExecutor); - - when(fixture.mockLazyDatabase.runInsert(any, any)) - .thenAnswer((realInvocation) => Future.value(1)); - - try { - await fixture.sut.batch((batch) async { - await insertRow(fixture.sut); - }); - } catch (exception) { - expect(exception, fixture.exception); - } - + // errored batch verifyErrorSpan( - expectedTransactionStatement, + constants.dbBatchDesc, fixture.exception, - fixture.getCreatedSpan(), - origin: SentryTraceOrigins.autoDbDriftTransactionExecutor, + fixture.getCreatedSpanByDescription(constants.dbBatchDesc), + operation: constants.dbSqlBatchOp, + ); + + // aborted transaction + verifySpan( + constants.dbTransactionDesc, + fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), + operation: constants.dbSqlTransactionOp, + status: SpanStatus.aborted(), ); }); @@ -557,6 +556,8 @@ void main() { when(fixture.mockLazyDatabase.runInsert(any, any)) .thenAnswer((_) => Future.value(1)); + await fixture.setUp(injectMock: true); + try { await insertRow(fixture.sut); await fixture.sut.close(); @@ -565,15 +566,17 @@ void main() { } verifyErrorSpan( - expectedCloseStatement, + constants.dbCloseDesc(dbName: Fixture.dbName), fixture.exception, - fixture.getCreatedSpan(), + fixture.getCreatedSpanByDescription( + constants.dbCloseDesc(dbName: Fixture.dbName)), + operation: constants.dbCloseOp, ); - - when(fixture.mockLazyDatabase.close()).thenAnswer((_) => Future.value()); }); test('throwing ensureOpen throws error span', () async { + await fixture.setUp(injectMock: true); + when(fixture.mockLazyDatabase.ensureOpen(any)) .thenThrow(fixture.exception); @@ -584,13 +587,17 @@ void main() { } verifyErrorSpan( - expectedOpenStatement, + constants.dbOpenDesc(dbName: Fixture.dbName), fixture.exception, - fixture.getCreatedSpanByDescription(expectedOpenStatement), + fixture.getCreatedSpanByDescription( + constants.dbOpenDesc(dbName: Fixture.dbName)), + operation: constants.dbOpenOp, ); }); test('throwing runDelete throws error span', () async { + await fixture.setUp(injectMock: true); + when(fixture.mockLazyDatabase.runDelete(any, any)) .thenThrow(fixture.exception); @@ -626,7 +633,7 @@ void main() { test('adds integration', () { expect( - fixture.options.sdk.integrations.contains('SentryDriftTracing'), + fixture.options.sdk.integrations.contains(constants.integrationName), true, ); }); @@ -653,8 +660,14 @@ class Fixture { late AppDatabase sut; final mockLazyDatabase = MockLazyDatabase(); - Future setUp({bool injectMock = false}) async { - sut = AppDatabase(openConnection(injectMock: injectMock)); + Future setUp({ + bool injectMock = false, + QueryExecutor? customExecutor, + }) async { + sut = AppDatabase( + openConnection(injectMock: injectMock, customExecutor: customExecutor), + ); + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; } Future tearDown() async { @@ -670,19 +683,21 @@ class Fixture { .firstWhere((element) => element.context.description == description); } - SentryQueryExecutor openConnection({bool injectMock = false}) { - if (injectMock) { - final executor = - SentryQueryExecutor(() => mockLazyDatabase, databaseName: dbName); - executor.setHub(hub); - return executor; + QueryExecutor openConnection({ + bool injectMock = false, + QueryExecutor? customExecutor, + }) { + if (customExecutor != null) { + return customExecutor.interceptWith( + SentryQueryInterceptor(databaseName: dbName, hub: hub), + ); + } else if (injectMock) { + return mockLazyDatabase.interceptWith( + SentryQueryInterceptor(databaseName: dbName, hub: hub), + ); } else { - return SentryQueryExecutor( - () { - return NativeDatabase.memory(); - }, - hub: hub, - databaseName: dbName, + return NativeDatabase.memory().interceptWith( + SentryQueryInterceptor(databaseName: dbName, hub: hub), ); } } diff --git a/drift/test/test_database.g.dart b/drift/test/test_database.g.dart index 65430026da..45207ed14f 100644 --- a/drift/test/test_database.g.dart +++ b/drift/test/test_database.g.dart @@ -160,6 +160,15 @@ class TodoItem extends DataClass implements Insertable { content: content ?? this.content, category: category.present ? category.value : this.category, ); + TodoItem copyWithCompanion(TodoItemsCompanion data) { + return TodoItem( + id: data.id.present ? data.id.value : this.id, + title: data.title.present ? data.title.value : this.title, + content: data.content.present ? data.content.value : this.content, + category: data.category.present ? data.category.value : this.category, + ); + } + @override String toString() { return (StringBuffer('TodoItem(') @@ -260,6 +269,7 @@ class TodoItemsCompanion extends UpdateCompanion { abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); + $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $TodoItemsTable todoItems = $TodoItemsTable(this); @override Iterable> get allTables => @@ -267,3 +277,155 @@ abstract class _$AppDatabase extends GeneratedDatabase { @override List get allSchemaEntities => [todoItems]; } + +typedef $$TodoItemsTableCreateCompanionBuilder = TodoItemsCompanion Function({ + Value id, + required String title, + required String content, + Value category, +}); +typedef $$TodoItemsTableUpdateCompanionBuilder = TodoItemsCompanion Function({ + Value id, + Value title, + Value content, + Value category, +}); + +class $$TodoItemsTableFilterComposer + extends Composer<_$AppDatabase, $TodoItemsTable> { + $$TodoItemsTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnFilters(column)); + + ColumnFilters get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnFilters(column)); + + ColumnFilters get category => $composableBuilder( + column: $table.category, builder: (column) => ColumnFilters(column)); +} + +class $$TodoItemsTableOrderingComposer + extends Composer<_$AppDatabase, $TodoItemsTable> { + $$TodoItemsTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get title => $composableBuilder( + column: $table.title, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get content => $composableBuilder( + column: $table.content, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get category => $composableBuilder( + column: $table.category, builder: (column) => ColumnOrderings(column)); +} + +class $$TodoItemsTableAnnotationComposer + extends Composer<_$AppDatabase, $TodoItemsTable> { + $$TodoItemsTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get title => + $composableBuilder(column: $table.title, builder: (column) => column); + + GeneratedColumn get content => + $composableBuilder(column: $table.content, builder: (column) => column); + + GeneratedColumn get category => + $composableBuilder(column: $table.category, builder: (column) => column); +} + +class $$TodoItemsTableTableManager extends RootTableManager< + _$AppDatabase, + $TodoItemsTable, + TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableAnnotationComposer, + $$TodoItemsTableCreateCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder, + (TodoItem, BaseReferences<_$AppDatabase, $TodoItemsTable, TodoItem>), + TodoItem, + PrefetchHooks Function()> { + $$TodoItemsTableTableManager(_$AppDatabase db, $TodoItemsTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TodoItemsTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TodoItemsTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TodoItemsTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value title = const Value.absent(), + Value content = const Value.absent(), + Value category = const Value.absent(), + }) => + TodoItemsCompanion( + id: id, + title: title, + content: content, + category: category, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + required String title, + required String content, + Value category = const Value.absent(), + }) => + TodoItemsCompanion.insert( + id: id, + title: title, + content: content, + category: category, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$TodoItemsTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $TodoItemsTable, + TodoItem, + $$TodoItemsTableFilterComposer, + $$TodoItemsTableOrderingComposer, + $$TodoItemsTableAnnotationComposer, + $$TodoItemsTableCreateCompanionBuilder, + $$TodoItemsTableUpdateCompanionBuilder, + (TodoItem, BaseReferences<_$AppDatabase, $TodoItemsTable, TodoItem>), + TodoItem, + PrefetchHooks Function()>; + +class $AppDatabaseManager { + final _$AppDatabase _db; + $AppDatabaseManager(this._db); + $$TodoItemsTableTableManager get todoItems => + $$TodoItemsTableTableManager(_db, _db.todoItems); +} From 21e2bc92517dae63ba7c78698612bceba04c6917 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 10 Feb 2025 16:21:25 +0100 Subject: [PATCH 04/29] update docs --- drift/lib/src/sentry_query_interceptor.dart | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index 10e184a6eb..ea9c56f170 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -31,7 +31,12 @@ class SentryQueryInterceptor extends QueryInterceptor { options?.sdk.addPackage(packageName, sdkVersion); } - Future _run( + /// Wraps database operations in Sentry spans. + /// + /// This handles most CRUD operations but excludes transaction lifecycle methods + /// (begin/commit/rollback), which require maintaining an ongoing transaction span + /// across multiple operations. Those are handled separately via [SentrySpanHelper]. + Future _instrumentOperation( String description, FutureOr Function() execute, { String? operation, @@ -47,7 +52,7 @@ class SentryQueryInterceptor extends QueryInterceptor { Future ensureOpen(QueryExecutor executor, QueryExecutorUser user) { if (!_isDbOpen) { _isDbOpen = true; - return _run( + return _instrumentOperation( constants.dbOpenDesc(dbName: _dbName), () => super.ensureOpen(executor, user), operation: constants.dbOpenOp, @@ -66,7 +71,7 @@ class SentryQueryInterceptor extends QueryInterceptor { @override Future close(QueryExecutor inner) { - return _run( + return _instrumentOperation( constants.dbCloseDesc(dbName: _dbName), () => super.close(inner), operation: constants.dbCloseOp, @@ -76,7 +81,7 @@ class SentryQueryInterceptor extends QueryInterceptor { @override Future runBatched( QueryExecutor executor, BatchedStatements statements) { - return _run( + return _instrumentOperation( constants.dbBatchDesc, () => super.runBatched(executor, statements), operation: constants.dbSqlBatchOp, @@ -99,7 +104,7 @@ class SentryQueryInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run( + return _instrumentOperation( statement, () => executor.runInsert(statement, args), ); @@ -111,7 +116,8 @@ class SentryQueryInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run(statement, () => executor.runUpdate(statement, args)); + return _instrumentOperation( + statement, () => executor.runUpdate(statement, args)); } @override @@ -120,7 +126,8 @@ class SentryQueryInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run(statement, () => executor.runDelete(statement, args)); + return _instrumentOperation( + statement, () => executor.runDelete(statement, args)); } @override @@ -129,7 +136,8 @@ class SentryQueryInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run(statement, () => executor.runCustom(statement, args)); + return _instrumentOperation( + statement, () => executor.runCustom(statement, args)); } @override @@ -138,6 +146,7 @@ class SentryQueryInterceptor extends QueryInterceptor { String statement, List args, ) { - return _run(statement, () => executor.runSelect(statement, args)); + return _instrumentOperation( + statement, () => executor.runSelect(statement, args)); } } From 93d28b20da6a34e1a62b7f31dd9f805280cc3a5b Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 12 Feb 2025 15:28:46 +0100 Subject: [PATCH 05/29] update --- dart/lib/sentry.dart | 70 ++++++------ dart/lib/src/constants.dart | 29 +++++ dart/lib/src/sentry_span_operations.dart | 8 -- drift/example/example.dart | 54 +++------- drift/lib/src/constants.dart | 12 --- drift/lib/src/sentry_query_interceptor.dart | 42 +++++--- drift/lib/src/sentry_span_helper.dart | 24 ++--- drift/test/sentry_drift_test.dart | 111 +++++++++++--------- 8 files changed, 178 insertions(+), 172 deletions(-) create mode 100644 dart/lib/src/constants.dart delete mode 100644 dart/lib/src/sentry_span_operations.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index f9ce19842b..dc6652bd5f 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -5,60 +5,60 @@ /// A pure Dart client for Sentry.io crash reporting. library; -export 'src/run_zoned_guarded_integration.dart'; +// ignore: invalid_export_of_internal_element +export 'src/constants.dart'; +export 'src/event_processor.dart'; +export 'src/exception_cause.dart'; +// exception extraction +export 'src/exception_cause_extractor.dart'; +export 'src/exception_stacktrace_extractor.dart'; +export 'src/exception_type_identifier.dart'; +export 'src/hint.dart'; +export 'src/http_client/sentry_http_client.dart'; +export 'src/http_client/sentry_http_client_error.dart'; export 'src/hub.dart'; // useful for tests export 'src/hub_adapter.dart'; -export 'src/platform_checker.dart'; +export 'src/integration.dart'; export 'src/noop_isolate_error_integration.dart' if (dart.library.io) 'src/isolate_error_integration.dart'; +export 'src/performance_collector.dart'; +export 'src/platform_checker.dart'; export 'src/protocol.dart'; +// feedback +export 'src/protocol/sentry_feedback.dart'; +// proxy +export 'src/protocol/sentry_proxy.dart'; +export 'src/run_zoned_guarded_integration.dart'; export 'src/scope.dart'; export 'src/scope_observer.dart'; export 'src/sentry.dart'; +export 'src/sentry_attachment/sentry_attachment.dart'; +export 'src/sentry_baggage.dart'; +export 'src/sentry_client.dart'; export 'src/sentry_envelope.dart'; export 'src/sentry_envelope_item.dart'; -export 'src/sentry_client.dart'; export 'src/sentry_options.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_trace_origins.dart'; +export 'src/sentry_user_feedback.dart'; +// constants +export 'src/span_data_convention.dart'; +// spotlight debugging +export 'src/spotlight.dart'; // useful for integrations export 'src/throwable_mechanism.dart'; -export 'src/transport/transport.dart'; -export 'src/integration.dart'; -export 'src/event_processor.dart'; -export 'src/http_client/sentry_http_client.dart'; -export 'src/http_client/sentry_http_client_error.dart'; -export 'src/sentry_attachment/sentry_attachment.dart'; -export 'src/sentry_user_feedback.dart'; -export 'src/utils/tracing_utils.dart'; -export 'src/performance_collector.dart'; // tracing export 'src/tracing.dart'; -export 'src/hint.dart'; +export 'src/transport/transport.dart'; export 'src/type_check_hint.dart'; -export 'src/sentry_baggage.dart'; -// exception extraction -export 'src/exception_cause_extractor.dart'; -export 'src/exception_cause.dart'; -export 'src/exception_stacktrace_extractor.dart'; -export 'src/exception_type_identifier.dart'; -// URL -// ignore: invalid_export_of_internal_element -export 'src/utils/http_sanitizer.dart'; // ignore: invalid_export_of_internal_element -export 'src/utils/url_details.dart'; +export 'src/utils.dart'; // ignore: invalid_export_of_internal_element export 'src/utils/http_header_utils.dart'; +// URL // ignore: invalid_export_of_internal_element -export 'src/sentry_trace_origins.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_span_operations.dart'; +export 'src/utils/http_sanitizer.dart'; +export 'src/utils/tracing_utils.dart'; // ignore: invalid_export_of_internal_element -export 'src/utils.dart'; -// spotlight debugging -export 'src/spotlight.dart'; -// proxy -export 'src/protocol/sentry_proxy.dart'; -// feedback -export 'src/protocol/sentry_feedback.dart'; -// constants -export 'src/span_data_convention.dart'; +export 'src/utils/url_details.dart'; diff --git a/dart/lib/src/constants.dart b/dart/lib/src/constants.dart new file mode 100644 index 0000000000..31c9dc99b0 --- /dev/null +++ b/dart/lib/src/constants.dart @@ -0,0 +1,29 @@ +import 'package:meta/meta.dart'; + +@internal +class SentrySpanOperations { + static const String uiLoad = 'ui.load'; + static const String uiTimeToInitialDisplay = 'ui.load.initial_display'; + static const String uiTimeToFullDisplay = 'ui.load.full_display'; + static const String dbSqlQuery = 'db.sql.query'; + static const String dbSqlTransaction = 'db.sql.transaction'; + static const String dbSqlBatch = 'db.sql.batch'; + static const String dbOpen = 'db.open'; + static const String dbClose = 'db.close'; +} + +@internal +class SentrySpanData { + static const String dbSystemKey = 'db.system'; + static const String dbNameKey = 'db.name'; + + static const String dbSystemSqlite = 'db.sqlite'; +} + +@internal +class SentrySpanDescriptions { + static const String dbTransaction = 'Transaction'; + static const String dbBatch = 'Batch'; + static String dbOpen({required String dbName}) => 'Open database $dbName'; + static String dbClose({required String dbName}) => 'Close database $dbName'; +} diff --git a/dart/lib/src/sentry_span_operations.dart b/dart/lib/src/sentry_span_operations.dart deleted file mode 100644 index 27b1d22496..0000000000 --- a/dart/lib/src/sentry_span_operations.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:meta/meta.dart'; - -@internal -class SentrySpanOperations { - static const String uiLoad = 'ui.load'; - static const String uiTimeToInitialDisplay = 'ui.load.initial_display'; - static const String uiTimeToFullDisplay = 'ui.load.full_display'; -} diff --git a/drift/example/example.dart b/drift/example/example.dart index eeba987b75..9750931f86 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -15,10 +15,6 @@ Future main() async { options.dsn = dsn; options.tracesSampleRate = 1.0; options.debug = true; - options.spotlight = Spotlight(enabled: true); - options.beforeSendTransaction = (transaction) { - return transaction; - }; }, appRunner: runApp, // Init your App. ); @@ -26,13 +22,16 @@ Future main() async { Future runApp() async { final tr = Sentry.startTransaction('drift', 'op', bindToScope: true); - final executor = NativeDatabase.memory() - .interceptWith(SentryQueryInterceptor(databaseName: 'your_db_name')); + final executor = NativeDatabase.memory().interceptWith( + SentryQueryInterceptor(databaseName: 'your_db_name'), + ); final db = AppDatabase(executor); await db.transaction(() async { - await db.into(db.todoItems).insert( + await db + .into(db.todoItems) + .insert( TodoItemsCompanion.insert( title: 'This is a test thing', content: 'test', @@ -40,50 +39,21 @@ Future runApp() async { ); await db.batch((batch) { - // functions in a batch don't have to be awaited - just - // await the whole batch afterwards. batch.insertAll(db.todoItems, [ - TodoItemsCompanion.insert( - title: 'First entry', - content: 'My content', - ), + TodoItemsCompanion.insert(title: 'First entry', content: 'My content'), TodoItemsCompanion.insert( title: 'Another entry', content: 'More content', ), - // ... ]); }); }); - // await db.batch((batch) async { - // batch.insertAll(db.todoItems, [ - // TodoItemsCompanion.insert( - // title: 'This is a test thing inside a batch #1', - // content: 'test', - // ), - // ]); - // await batch.into(db.todoItems).insert( - // TodoItemsCompanion.insert( - // title: 'This is a test thing inside a batch #1', - // content: 'test', - // ), - // ); - // - // await db.into(db.todoItems).insert( - // TodoItemsCompanion.insert( - // title: 'This is a test thing inside a batch #2', - // content: 'test', - // ), - // ); - // - // await db.into(db.todoItems).insert( - // TodoItemsCompanion.insert( - // title: 'This is a test thing inside a batch #3', - // content: 'test', - // ), - // ); - // }); + await db.batch((batch) async { + batch.insertAll(db.todoItems, [ + TodoItemsCompanion.insert(title: 'This is a test', content: 'test'), + ]); + }); final items = await db.select(db.todoItems).get(); print(items); diff --git a/drift/lib/src/constants.dart b/drift/lib/src/constants.dart index b8a96049d1..178d98f8ac 100644 --- a/drift/lib/src/constants.dart +++ b/drift/lib/src/constants.dart @@ -1,13 +1 @@ -const String dbSystemKey = 'db.system'; -const String dbSystem = 'db.sqlite'; -const String dbNameKey = 'db.name'; -const String dbSqlQueryOp = 'db.sql.query'; -const String dbSqlTransactionOp = 'db.sql.transaction'; -const String dbSqlBatchOp = 'db.sql.batch'; -const String dbOpenOp = 'db.open'; -const String dbCloseOp = 'db.close'; const String integrationName = 'SentryDriftTracing'; -const String dbTransactionDesc = 'Transaction'; -const String dbBatchDesc = 'Batch'; -String dbOpenDesc({required String dbName}) => 'Open database $dbName'; -String dbCloseDesc({required String dbName}) => 'Close database $dbName'; diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index ea9c56f170..c795cff195 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -1,10 +1,12 @@ +// ignore_for_file: invalid_use_of_internal_member + import 'dart:async'; import 'package:drift/drift.dart'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; -import 'constants.dart' as constants; +import 'constants.dart' as drift_constants; import 'sentry_span_helper.dart'; import 'version.dart'; @@ -21,13 +23,11 @@ class SentryQueryInterceptor extends QueryInterceptor { SentryQueryInterceptor({required String databaseName, @internal Hub? hub}) : _dbName = databaseName { _spanHelper = SentrySpanHelper( - // ignore: invalid_use_of_internal_member SentryTraceOrigins.autoDbDriftQueryInterceptor, hub: hub, ); - // ignore: invalid_use_of_internal_member final options = hub?.options; - options?.sdk.addIntegration(constants.integrationName); + options?.sdk.addIntegration(drift_constants.integrationName); options?.sdk.addPackage(packageName, sdkVersion); } @@ -53,9 +53,9 @@ class SentryQueryInterceptor extends QueryInterceptor { if (!_isDbOpen) { _isDbOpen = true; return _instrumentOperation( - constants.dbOpenDesc(dbName: _dbName), + SentrySpanDescriptions.dbOpen(dbName: _dbName), () => super.ensureOpen(executor, user), - operation: constants.dbOpenOp, + operation: SentrySpanOperations.dbOpen, ); } return super.ensureOpen(executor, user); @@ -72,19 +72,23 @@ class SentryQueryInterceptor extends QueryInterceptor { @override Future close(QueryExecutor inner) { return _instrumentOperation( - constants.dbCloseDesc(dbName: _dbName), + SentrySpanDescriptions.dbClose(dbName: _dbName), () => super.close(inner), - operation: constants.dbCloseOp, + operation: SentrySpanOperations.dbClose, ); } @override Future runBatched( - QueryExecutor executor, BatchedStatements statements) { + QueryExecutor executor, + BatchedStatements statements, + ) { + final description = + '${SentrySpanDescriptions.dbBatch} ${statements.statements}'; return _instrumentOperation( - constants.dbBatchDesc, + description, () => super.runBatched(executor, statements), - operation: constants.dbSqlBatchOp, + operation: SentrySpanOperations.dbSqlBatch, ); } @@ -117,7 +121,9 @@ class SentryQueryInterceptor extends QueryInterceptor { List args, ) { return _instrumentOperation( - statement, () => executor.runUpdate(statement, args)); + statement, + () => executor.runUpdate(statement, args), + ); } @override @@ -127,7 +133,9 @@ class SentryQueryInterceptor extends QueryInterceptor { List args, ) { return _instrumentOperation( - statement, () => executor.runDelete(statement, args)); + statement, + () => executor.runDelete(statement, args), + ); } @override @@ -137,7 +145,9 @@ class SentryQueryInterceptor extends QueryInterceptor { List args, ) { return _instrumentOperation( - statement, () => executor.runCustom(statement, args)); + statement, + () => executor.runCustom(statement, args), + ); } @override @@ -147,6 +157,8 @@ class SentryQueryInterceptor extends QueryInterceptor { List args, ) { return _instrumentOperation( - statement, () => executor.runSelect(statement, args)); + statement, + () => executor.runSelect(statement, args), + ); } } diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index 9130777364..f20bfd910a 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -1,8 +1,8 @@ +// ignore_for_file: invalid_use_of_internal_member + import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; -import 'constants.dart' as constants; - @internal class SentrySpanHelper { final Hub _hub; @@ -19,20 +19,19 @@ class SentrySpanHelper { }) async { final currentSpan = _parentSpan ?? _hub.getSpan(); final span = currentSpan?.startChild( - operation ?? constants.dbSqlQueryOp, + operation ?? SentrySpanOperations.dbSqlQuery, description: description, ); - // ignore: invalid_use_of_internal_member span?.origin = _origin; span?.setData( - constants.dbSystemKey, - constants.dbSystem, + SentrySpanData.dbSystemKey, + SentrySpanData.dbSystemSqlite, ); if (dbName != null) { - span?.setData(constants.dbNameKey, dbName); + span?.setData(SentrySpanData.dbNameKey, dbName); } try { @@ -56,20 +55,19 @@ class SentrySpanHelper { }) { final scopeSpan = _hub.getSpan(); _parentSpan = scopeSpan?.startChild( - constants.dbSqlTransactionOp, - description: constants.dbTransactionDesc, + SentrySpanOperations.dbSqlTransaction, + description: SentrySpanDescriptions.dbTransaction, ); - // ignore: invalid_use_of_internal_member _parentSpan?.origin = _origin; _parentSpan?.setData( - constants.dbSystemKey, - constants.dbSystem, + SentrySpanData.dbSystemKey, + SentrySpanData.dbSystemSqlite, ); if (dbName != null) { - _parentSpan?.setData(constants.dbNameKey, dbName); + _parentSpan?.setData(SentrySpanData.dbNameKey, dbName); } try { diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 79120dbd16..e1b7c0c435 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -9,7 +9,7 @@ import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_tracer.dart'; import 'package:sentry_drift/sentry_drift.dart'; -import 'package:sentry_drift/src/constants.dart' as constants; +import 'package:sentry_drift/src/constants.dart' as drift_constants; import 'package:sentry_drift/src/version.dart'; import 'package:sqlite3/open.dart'; @@ -35,16 +35,19 @@ void main() { SpanStatus? status, }) { status ??= SpanStatus.ok(); - expect(span?.context.operation, operation ?? constants.dbSqlQueryOp); + expect( + span?.context.operation, + operation ?? SentrySpanOperations.dbSqlQuery, + ); expect(span?.context.description, description); expect(span?.status, status); expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); expect( - span?.data[constants.dbSystemKey], - constants.dbSystem, + span?.data[SentrySpanData.dbSystemKey], + SentrySpanData.dbSystemSqlite, ); expect( - span?.data[constants.dbNameKey], + span?.data[SentrySpanData.dbNameKey], Fixture.dbName, ); } @@ -56,16 +59,19 @@ void main() { String? operation, SpanStatus? status, }) { - expect(span?.context.operation, operation ?? constants.dbSqlQueryOp); + expect( + span?.context.operation, + operation ?? SentrySpanOperations.dbSqlQuery, + ); expect(span?.context.description, description); expect(span?.status, status ?? SpanStatus.internalError()); expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); expect( - span?.data[constants.dbSystemKey], - constants.dbSystem, + span?.data[SentrySpanData.dbSystemKey], + SentrySpanData.dbSystemSqlite, ); expect( - span?.data[constants.dbNameKey], + span?.data[SentrySpanData.dbNameKey], Fixture.dbName, ); @@ -154,7 +160,7 @@ void main() { .where( (element) => element.context.description == - constants.dbOpenDesc(dbName: Fixture.dbName), + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), ) .length; @@ -228,9 +234,10 @@ void main() { ); verifySpan( - constants.dbTransactionDesc, - fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), - operation: constants.dbSqlTransactionOp, + SentrySpanDescriptions.dbTransaction, + fixture + .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), + operation: SentrySpanOperations.dbSqlTransaction, ); }); @@ -255,9 +262,10 @@ void main() { ); verifySpan( - constants.dbTransactionDesc, - fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), - operation: constants.dbSqlTransactionOp, + SentrySpanDescriptions.dbTransaction, + fixture + .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), + operation: SentrySpanOperations.dbSqlTransaction, ); }); @@ -282,9 +290,10 @@ void main() { ); verifySpan( - constants.dbTransactionDesc, - fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), - operation: constants.dbSqlTransactionOp, + SentrySpanDescriptions.dbTransaction, + fixture + .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), + operation: SentrySpanOperations.dbSqlTransaction, ); }); @@ -309,9 +318,10 @@ void main() { ); verifySpan( - constants.dbTransactionDesc, - fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), - operation: constants.dbSqlTransactionOp, + SentrySpanDescriptions.dbTransaction, + fixture + .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), + operation: SentrySpanOperations.dbSqlTransaction, ); }); @@ -333,10 +343,10 @@ void main() { final abortedSpan = spans.first; verifySpan( - constants.dbTransactionDesc, + SentrySpanDescriptions.dbTransaction, abortedSpan, status: SpanStatus.aborted(), - operation: constants.dbSqlTransactionOp, + operation: SentrySpanOperations.dbSqlTransaction, ); }); @@ -346,9 +356,9 @@ void main() { await insertIntoBatch(sut); verifySpan( - constants.dbBatchDesc, + SentrySpanDescriptions.dbBatch, fixture.getCreatedSpan(), - operation: constants.dbSqlBatchOp, + operation: SentrySpanOperations.dbSqlBatch, ); }); @@ -358,9 +368,9 @@ void main() { await sut.close(); verifySpan( - constants.dbCloseDesc(dbName: Fixture.dbName), + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), fixture.getCreatedSpan(), - operation: constants.dbCloseOp, + operation: SentrySpanOperations.dbClose, ); }); @@ -372,11 +382,11 @@ void main() { await sut.select(sut.todoItems).get(); verifySpan( - constants.dbOpenDesc(dbName: Fixture.dbName), + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), fixture.getCreatedSpanByDescription( - constants.dbOpenDesc(dbName: Fixture.dbName), + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), ), - operation: constants.dbOpenOp, + operation: SentrySpanOperations.dbOpen, ); }); }); @@ -513,9 +523,12 @@ void main() { expect(exception, fixture.exception); } - verifyErrorSpan(constants.dbTransactionDesc, fixture.exception, - fixture.getCreatedSpan(), - operation: constants.dbSqlTransactionOp); + verifyErrorSpan( + SentrySpanDescriptions.dbTransaction, + fixture.exception, + fixture.getCreatedSpan(), + operation: SentrySpanOperations.dbSqlTransaction, + ); }); test('throwing batch throws error span in transaction', () async { @@ -536,17 +549,18 @@ void main() { // errored batch verifyErrorSpan( - constants.dbBatchDesc, + SentrySpanDescriptions.dbBatch, fixture.exception, - fixture.getCreatedSpanByDescription(constants.dbBatchDesc), - operation: constants.dbSqlBatchOp, + fixture.getCreatedSpanByDescription(SentrySpanDescriptions.dbBatch), + operation: SentrySpanOperations.dbSqlBatch, ); // aborted transaction verifySpan( - constants.dbTransactionDesc, - fixture.getCreatedSpanByDescription(constants.dbTransactionDesc), - operation: constants.dbSqlTransactionOp, + SentrySpanDescriptions.dbTransaction, + fixture + .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), + operation: SentrySpanOperations.dbSqlTransaction, status: SpanStatus.aborted(), ); }); @@ -566,11 +580,12 @@ void main() { } verifyErrorSpan( - constants.dbCloseDesc(dbName: Fixture.dbName), + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), fixture.exception, fixture.getCreatedSpanByDescription( - constants.dbCloseDesc(dbName: Fixture.dbName)), - operation: constants.dbCloseOp, + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), + ), + operation: SentrySpanOperations.dbClose, ); }); @@ -587,11 +602,12 @@ void main() { } verifyErrorSpan( - constants.dbOpenDesc(dbName: Fixture.dbName), + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), fixture.exception, fixture.getCreatedSpanByDescription( - constants.dbOpenDesc(dbName: Fixture.dbName)), - operation: constants.dbOpenOp, + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), + ), + operation: SentrySpanOperations.dbOpen, ); }); @@ -633,7 +649,8 @@ void main() { test('adds integration', () { expect( - fixture.options.sdk.integrations.contains(constants.integrationName), + fixture.options.sdk.integrations + .contains(drift_constants.integrationName), true, ); }); From 83dab13f2f416f76f6b55dd2845a9d81d83e7f78 Mon Sep 17 00:00:00 2001 From: Giancarlo Buenaflor Date: Wed, 12 Feb 2025 15:35:04 +0100 Subject: [PATCH 06/29] Update sentry.dart --- dart/lib/sentry.dart | 70 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index dc6652bd5f..ee9a93f568 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -5,60 +5,60 @@ /// A pure Dart client for Sentry.io crash reporting. library; -// ignore: invalid_export_of_internal_element -export 'src/constants.dart'; -export 'src/event_processor.dart'; -export 'src/exception_cause.dart'; -// exception extraction -export 'src/exception_cause_extractor.dart'; -export 'src/exception_stacktrace_extractor.dart'; -export 'src/exception_type_identifier.dart'; -export 'src/hint.dart'; -export 'src/http_client/sentry_http_client.dart'; -export 'src/http_client/sentry_http_client_error.dart'; +export 'src/run_zoned_guarded_integration.dart'; export 'src/hub.dart'; // useful for tests export 'src/hub_adapter.dart'; -export 'src/integration.dart'; +export 'src/platform_checker.dart'; export 'src/noop_isolate_error_integration.dart' if (dart.library.io) 'src/isolate_error_integration.dart'; -export 'src/performance_collector.dart'; -export 'src/platform_checker.dart'; export 'src/protocol.dart'; -// feedback -export 'src/protocol/sentry_feedback.dart'; -// proxy -export 'src/protocol/sentry_proxy.dart'; -export 'src/run_zoned_guarded_integration.dart'; export 'src/scope.dart'; export 'src/scope_observer.dart'; export 'src/sentry.dart'; -export 'src/sentry_attachment/sentry_attachment.dart'; -export 'src/sentry_baggage.dart'; -export 'src/sentry_client.dart'; export 'src/sentry_envelope.dart'; export 'src/sentry_envelope_item.dart'; +export 'src/sentry_client.dart'; export 'src/sentry_options.dart'; -// ignore: invalid_export_of_internal_element -export 'src/sentry_trace_origins.dart'; -export 'src/sentry_user_feedback.dart'; -// constants -export 'src/span_data_convention.dart'; -// spotlight debugging -export 'src/spotlight.dart'; // useful for integrations export 'src/throwable_mechanism.dart'; +export 'src/transport/transport.dart'; +export 'src/integration.dart'; +export 'src/event_processor.dart'; +export 'src/http_client/sentry_http_client.dart'; +export 'src/http_client/sentry_http_client_error.dart'; +export 'src/sentry_attachment/sentry_attachment.dart'; +export 'src/sentry_user_feedback.dart'; +export 'src/utils/tracing_utils.dart'; +export 'src/performance_collector.dart'; // tracing export 'src/tracing.dart'; -export 'src/transport/transport.dart'; +export 'src/hint.dart'; export 'src/type_check_hint.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils.dart'; -// ignore: invalid_export_of_internal_element -export 'src/utils/http_header_utils.dart'; +export 'src/sentry_baggage.dart'; +// exception extraction +export 'src/exception_cause_extractor.dart'; +export 'src/exception_cause.dart'; +export 'src/exception_stacktrace_extractor.dart'; +export 'src/exception_type_identifier.dart'; // URL // ignore: invalid_export_of_internal_element export 'src/utils/http_sanitizer.dart'; -export 'src/utils/tracing_utils.dart'; // ignore: invalid_export_of_internal_element export 'src/utils/url_details.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/http_header_utils.dart'; +// ignore: invalid_export_of_internal_element +export 'src/sentry_trace_origins.dart'; +// ignore: invalid_export_of_internal_element +export 'src/constants.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils.dart'; +// spotlight debugging +export 'src/spotlight.dart'; +// proxy +export 'src/protocol/sentry_proxy.dart'; +// feedback +export 'src/protocol/sentry_feedback.dart'; +// constants +export 'src/span_data_convention.dart'; From 14774ebd4f07117c9a73d0d34f13f55d5427bef9 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Wed, 12 Feb 2025 15:36:25 +0100 Subject: [PATCH 07/29] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ef58a89a..2a06edb793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Remove deprecated loggers ([#2685](https://github.com/getsentry/sentry-dart/pull/2685)) - Remove user segment ([#2687](https://github.com/getsentry/sentry-dart/pull/2687)) - Remove `options.autoAppStart` and `setAppStartEnd` ([#2680](https://github.com/getsentry/sentry-dart/pull/2680)) +- Bump Drift min version to `2.24.0` and use `QueryInterceptor` instead of `QueryExecutor` ([#2679](https://github.com/getsentry/sentry-dart/pull/2679)) ### Dependencies From 7bff992c723275e6fd5c325b05774d91f9a06483 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 13 Feb 2025 12:39:11 +0100 Subject: [PATCH 08/29] update --- flutter/example/lib/main.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 87977f7113..db9f4f49d6 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:drift/drift.dart' show ApplyInterceptor; import 'package:feedback/feedback.dart' as feedback; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -655,10 +656,8 @@ class MainScaffold extends StatelessWidget { bindToScope: true, ); - final executor = SentryQueryExecutor( - () async => inMemoryExecutor(), - databaseName: 'sentry_in_memory_db', - ); + final executor = inMemoryExecutor().interceptWith( + SentryQueryInterceptor(databaseName: 'sentry_in_memory_db')); final db = AppDatabase(executor); From 485f2eb5d3b10722197d6f13c3019d4ff6e648f4 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 13 Feb 2025 15:09:07 +0100 Subject: [PATCH 09/29] fist tests --- dart/lib/src/constants.dart | 5 ++++- drift/lib/src/sentry_query_interceptor.dart | 2 +- drift/test/sentry_drift_test.dart | 9 +++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/dart/lib/src/constants.dart b/dart/lib/src/constants.dart index 31c9dc99b0..fb24c12bd2 100644 --- a/dart/lib/src/constants.dart +++ b/dart/lib/src/constants.dart @@ -23,7 +23,10 @@ class SentrySpanData { @internal class SentrySpanDescriptions { static const String dbTransaction = 'Transaction'; - static const String dbBatch = 'Batch'; + static String dbBatch({required List statements}) { + return 'Batch $statements'; + } + static String dbOpen({required String dbName}) => 'Open database $dbName'; static String dbClose({required String dbName}) => 'Close database $dbName'; } diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index c795cff195..bf60b9f5a6 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -84,7 +84,7 @@ class SentryQueryInterceptor extends QueryInterceptor { BatchedStatements statements, ) { final description = - '${SentrySpanDescriptions.dbBatch} ${statements.statements}'; + SentrySpanDescriptions.dbBatch(statements: statements.statements); return _instrumentOperation( description, () => super.runBatched(executor, statements), diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index e1b7c0c435..5c4f9be15a 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -356,7 +356,7 @@ void main() { await insertIntoBatch(sut); verifySpan( - SentrySpanDescriptions.dbBatch, + SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), fixture.getCreatedSpan(), operation: SentrySpanOperations.dbSqlBatch, ); @@ -549,13 +549,14 @@ void main() { // errored batch verifyErrorSpan( - SentrySpanDescriptions.dbBatch, + SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), fixture.exception, - fixture.getCreatedSpanByDescription(SentrySpanDescriptions.dbBatch), + fixture.getCreatedSpanByDescription(SentrySpanDescriptions.dbBatch( + statements: [expectedInsertStatement])), operation: SentrySpanOperations.dbSqlBatch, ); - // aborted transaction + // // aborted transaction verifySpan( SentrySpanDescriptions.dbTransaction, fixture From 4c044962b3b8633d782e0adb27ecd7c044e7a7f4 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 13 Feb 2025 15:09:35 +0100 Subject: [PATCH 10/29] formatting --- dart/lib/src/constants.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dart/lib/src/constants.dart b/dart/lib/src/constants.dart index fb24c12bd2..caa7641db0 100644 --- a/dart/lib/src/constants.dart +++ b/dart/lib/src/constants.dart @@ -23,10 +23,8 @@ class SentrySpanData { @internal class SentrySpanDescriptions { static const String dbTransaction = 'Transaction'; - static String dbBatch({required List statements}) { - return 'Batch $statements'; - } - + static String dbBatch({required List statements}) => + 'Batch $statements'; static String dbOpen({required String dbName}) => 'Open database $dbName'; static String dbClose({required String dbName}) => 'Close database $dbName'; } From 7a37b9d330ee29794ab353b9b339d2ca5e53e612 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 13 Feb 2025 15:10:20 +0100 Subject: [PATCH 11/29] fix analyze --- drift/test/sentry_drift_test.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 5c4f9be15a..3b2f43c126 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -551,8 +551,11 @@ void main() { verifyErrorSpan( SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), fixture.exception, - fixture.getCreatedSpanByDescription(SentrySpanDescriptions.dbBatch( - statements: [expectedInsertStatement])), + fixture.getCreatedSpanByDescription( + SentrySpanDescriptions.dbBatch( + statements: [expectedInsertStatement], + ), + ), operation: SentrySpanOperations.dbSqlBatch, ); From fa7f0a06be24367a121454cad985143cfa11e34c Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Thu, 13 Feb 2025 15:15:41 +0100 Subject: [PATCH 12/29] fix analyze --- drift/example/example.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drift/example/example.dart b/drift/example/example.dart index 9750931f86..b7d7d80951 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -29,9 +29,7 @@ Future runApp() async { final db = AppDatabase(executor); await db.transaction(() async { - await db - .into(db.todoItems) - .insert( + await db.into(db.todoItems).insert( TodoItemsCompanion.insert( title: 'This is a test thing', content: 'test', From c7c6e0ce6bf8b107396db8ed533581c7450182e7 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 11:30:24 +0100 Subject: [PATCH 13/29] formatting and change integration name from to --- drift/example/example.dart | 4 +++- drift/lib/src/constants.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drift/example/example.dart b/drift/example/example.dart index b7d7d80951..9750931f86 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -29,7 +29,9 @@ Future runApp() async { final db = AppDatabase(executor); await db.transaction(() async { - await db.into(db.todoItems).insert( + await db + .into(db.todoItems) + .insert( TodoItemsCompanion.insert( title: 'This is a test thing', content: 'test', diff --git a/drift/lib/src/constants.dart b/drift/lib/src/constants.dart index 178d98f8ac..c1be0a71d6 100644 --- a/drift/lib/src/constants.dart +++ b/drift/lib/src/constants.dart @@ -1 +1 @@ -const String integrationName = 'SentryDriftTracing'; +const String integrationName = 'sentryDriftTracing'; From b3b1d734b45c62225e55e451f9b9638b47ae2712 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 11:36:43 +0100 Subject: [PATCH 14/29] improve ensureOpen instrumentation --- drift/lib/src/sentry_query_interceptor.dart | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index bf60b9f5a6..98b6e51e2a 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -50,15 +50,18 @@ class SentryQueryInterceptor extends QueryInterceptor { @override Future ensureOpen(QueryExecutor executor, QueryExecutorUser user) { - if (!_isDbOpen) { - _isDbOpen = true; - return _instrumentOperation( - SentrySpanDescriptions.dbOpen(dbName: _dbName), - () => super.ensureOpen(executor, user), - operation: SentrySpanOperations.dbOpen, - ); + if (_isDbOpen) { + return ensureOpen(executor, user); } - return super.ensureOpen(executor, user); + return _instrumentOperation( + SentrySpanDescriptions.dbOpen(dbName: _dbName), + () async { + final result = await super.ensureOpen(executor, user); + _isDbOpen = true; + return result; + }, + operation: SentrySpanOperations.dbOpen, + ); } @override From 28e8693ad87bb0088fcbaeec704d7599020343f0 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 12:03:19 +0100 Subject: [PATCH 15/29] fix hub not using hubadapter as fallback --- drift/lib/src/sentry_query_interceptor.dart | 9 +++--- drift/test/sentry_drift_test.dart | 33 +++++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index 98b6e51e2a..4686a776c7 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -22,13 +22,14 @@ class SentryQueryInterceptor extends QueryInterceptor { SentryQueryInterceptor({required String databaseName, @internal Hub? hub}) : _dbName = databaseName { + hub = hub ?? HubAdapter(); _spanHelper = SentrySpanHelper( SentryTraceOrigins.autoDbDriftQueryInterceptor, hub: hub, ); - final options = hub?.options; - options?.sdk.addIntegration(drift_constants.integrationName); - options?.sdk.addPackage(packageName, sdkVersion); + final options = hub.options; + options.sdk.addIntegration(drift_constants.integrationName); + options.sdk.addPackage(packageName, sdkVersion); } /// Wraps database operations in Sentry spans. @@ -51,7 +52,7 @@ class SentryQueryInterceptor extends QueryInterceptor { @override Future ensureOpen(QueryExecutor executor, QueryExecutorUser user) { if (_isDbOpen) { - return ensureOpen(executor, user); + return super.ensureOpen(executor, user); } return _instrumentOperation( SentrySpanDescriptions.dbOpen(dbName: _dbName), diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 3b2f43c126..59ef666f94 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -8,8 +8,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_tracer.dart'; -import 'package:sentry_drift/sentry_drift.dart'; import 'package:sentry_drift/src/constants.dart' as drift_constants; +import 'package:sentry_drift/src/sentry_query_interceptor.dart'; import 'package:sentry_drift/src/version.dart'; import 'package:sqlite3/open.dart'; @@ -637,14 +637,12 @@ void main() { group('integrations', () { late Fixture fixture; + late Hub hub; setUp(() async { fixture = Fixture(); - - when(fixture.hub.options).thenReturn(fixture.options); - when(fixture.hub.getSpan()).thenReturn(fixture.tracer); - - await fixture.setUp(); + await fixture.setUp(useRealHub: true); + hub = HubAdapter(); }); tearDown(() async { @@ -653,15 +651,14 @@ void main() { test('adds integration', () { expect( - fixture.options.sdk.integrations - .contains(drift_constants.integrationName), + hub.options.sdk.integrations.contains(drift_constants.integrationName), true, ); }); test('adds package', () { expect( - fixture.options.sdk.packages.any( + hub.options.sdk.packages.any( (element) => element.name == packageName && element.version == sdkVersion, ), @@ -683,10 +680,15 @@ class Fixture { Future setUp({ bool injectMock = false, + bool useRealHub = false, QueryExecutor? customExecutor, }) async { sut = AppDatabase( - openConnection(injectMock: injectMock, customExecutor: customExecutor), + openConnection( + useRealHub: useRealHub, + injectMock: injectMock, + customExecutor: customExecutor, + ), ); driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; } @@ -705,20 +707,25 @@ class Fixture { } QueryExecutor openConnection({ + bool useRealHub = false, bool injectMock = false, QueryExecutor? customExecutor, }) { + Hub? _hub = hub; + if (useRealHub) { + _hub = null; + } if (customExecutor != null) { return customExecutor.interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: hub), + SentryQueryInterceptor(databaseName: dbName, hub: _hub), ); } else if (injectMock) { return mockLazyDatabase.interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: hub), + SentryQueryInterceptor(databaseName: dbName, hub: _hub), ); } else { return NativeDatabase.memory().interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: hub), + SentryQueryInterceptor(databaseName: dbName, hub: _hub), ); } } From c6e6e48257c3c9f76300368f2fee25c26e3fb3b2 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 12:11:33 +0100 Subject: [PATCH 16/29] update parent span --- drift/lib/src/sentry_span_helper.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index f20bfd910a..c339b805d5 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -9,7 +9,9 @@ class SentrySpanHelper { final String _origin; ISentrySpan? _parentSpan; - SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter(); + SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter() { + _parentSpan = hub?.getSpan(); + } Future asyncWrapInSpan( String description, @@ -17,8 +19,7 @@ class SentrySpanHelper { String? dbName, String? operation, }) async { - final currentSpan = _parentSpan ?? _hub.getSpan(); - final span = currentSpan?.startChild( + final span = _parentSpan?.startChild( operation ?? SentrySpanOperations.dbSqlQuery, description: description, ); From 2af04c522445abe182f77bfea2971ab57aa81685 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 14:07:02 +0100 Subject: [PATCH 17/29] use stack to handle parent span --- drift/example/example.dart | 12 +++++ drift/lib/src/sentry_query_interceptor.dart | 1 + drift/lib/src/sentry_span_helper.dart | 58 ++++++++++++--------- drift/test/sentry_drift_test.dart | 30 +++++++++++ 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/drift/example/example.dart b/drift/example/example.dart index 9750931f86..ddf0023873 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -15,6 +15,7 @@ Future main() async { options.dsn = dsn; options.tracesSampleRate = 1.0; options.debug = true; + options.spotlight = Spotlight(enabled: true); }, appRunner: runApp, // Init your App. ); @@ -38,6 +39,17 @@ Future runApp() async { ), ); + await db.transaction(() async { + await db + .into(db.todoItems) + .insert( + TodoItemsCompanion.insert( + title: 'This is a test thing in the tx', + content: 'test', + ), + ); + }); + await db.batch((batch) { batch.insertAll(db.todoItems, [ TodoItemsCompanion.insert(title: 'First entry', content: 'My content'), diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index 4686a776c7..a2fb8dbc86 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -67,6 +67,7 @@ class SentryQueryInterceptor extends QueryInterceptor { @override TransactionExecutor beginTransaction(QueryExecutor parent) { + print('begin transaction'); return _spanHelper.beginTransaction( () => super.beginTransaction(parent), dbName: _dbName, diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index c339b805d5..32d9008397 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -1,5 +1,7 @@ // ignore_for_file: invalid_use_of_internal_member +import 'dart:collection'; + import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; @@ -7,10 +9,10 @@ import 'package:sentry/sentry.dart'; class SentrySpanHelper { final Hub _hub; final String _origin; - ISentrySpan? _parentSpan; + final ListQueue _spanStack = ListQueue(); SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter() { - _parentSpan = hub?.getSpan(); + _spanStack.add(hub?.getSpan()); } Future asyncWrapInSpan( @@ -19,7 +21,8 @@ class SentrySpanHelper { String? dbName, String? operation, }) async { - final span = _parentSpan?.startChild( + final parentSpan = _spanStack.last; + final span = parentSpan?.startChild( operation ?? SentrySpanOperations.dbSqlQuery, description: description, ); @@ -54,67 +57,74 @@ class SentrySpanHelper { T Function() execute, { String? dbName, }) { - final scopeSpan = _hub.getSpan(); - _parentSpan = scopeSpan?.startChild( - SentrySpanOperations.dbSqlTransaction, - description: SentrySpanDescriptions.dbTransaction, - ); + final currentParent = _spanStack.last; + final newParent = currentParent?.startChild( + SentrySpanOperations.dbSqlTransaction, + description: SentrySpanDescriptions.dbTransaction, + ) ?? + _hub.getSpan()?.startChild( + SentrySpanOperations.dbSqlTransaction, + description: SentrySpanDescriptions.dbTransaction, + ); - _parentSpan?.origin = _origin; + _spanStack.add(newParent); - _parentSpan?.setData( + newParent?.origin = _origin; + + newParent?.setData( SentrySpanData.dbSystemKey, SentrySpanData.dbSystemSqlite, ); if (dbName != null) { - _parentSpan?.setData(SentrySpanData.dbNameKey, dbName); + newParent?.setData(SentrySpanData.dbNameKey, dbName); } try { final result = execute(); - _parentSpan?.status = SpanStatus.unknown(); + newParent?.status = SpanStatus.unknown(); return result; } catch (exception) { - _parentSpan?.throwable = exception; - _parentSpan?.status = SpanStatus.internalError(); + newParent?.throwable = exception; + newParent?.status = SpanStatus.internalError(); rethrow; } } Future finishTransaction(Future Function() execute) async { + final parentSpan = _spanStack.removeLast(); try { final result = await execute(); - _parentSpan?.status = SpanStatus.ok(); + parentSpan?.status = SpanStatus.ok(); return result; } catch (exception) { - _parentSpan?.throwable = exception; - _parentSpan?.status = SpanStatus.internalError(); + parentSpan?.throwable = exception; + parentSpan?.status = SpanStatus.internalError(); rethrow; } finally { - await _parentSpan?.finish(); - _parentSpan = null; + await parentSpan?.finish(); } } Future abortTransaction(Future Function() execute) async { + final parentSpan = _spanStack.removeLast(); + try { final result = await execute(); - _parentSpan?.status = SpanStatus.aborted(); + parentSpan?.status = SpanStatus.aborted(); return result; } catch (exception) { - _parentSpan?.throwable = exception; - _parentSpan?.status = SpanStatus.internalError(); + parentSpan?.throwable = exception; + parentSpan?.status = SpanStatus.internalError(); rethrow; } finally { - await _parentSpan?.finish(); - _parentSpan = null; + await parentSpan?.finish(); } } } diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 59ef666f94..9bb70ca301 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -350,6 +350,36 @@ void main() { ); }); + test('nested transaction adds spans', () async { + final sut = fixture.sut; + + await sut.transaction(() async { + await sut.into(sut.todoItems).insert( + TodoItemsCompanion.insert( + title: 'first transaction insert', + content: 'test', + ), + ); + await sut.transaction(() async { + await fixture.sut.delete(fixture.sut.todoItems).go(); + }); + }); + + // 5 spans = 1 db open + 2 tx + 1 insert + 1 delete + expect(fixture.tracer.children.length, 5); + + final outerTxSpan = fixture.tracer.children[1]; + final insertSpan = fixture.tracer.children[2]; + final innerTxSpan = fixture.tracer.children[3]; + final deleteSpan = fixture.tracer.children[4]; + + // Verify parent relationships + expect(outerTxSpan.context.parentSpanId, fixture.tracer.context.spanId); + expect(insertSpan.context.parentSpanId, outerTxSpan.context.spanId); + expect(innerTxSpan.context.parentSpanId, outerTxSpan.context.spanId); + expect(deleteSpan.context.parentSpanId, innerTxSpan.context.spanId); + }); + test('batch adds span', () async { final sut = fixture.sut; From 95bbec90ba6e509eeed589ff2c38580b532c8a7f Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 14:09:31 +0100 Subject: [PATCH 18/29] formatting --- drift/lib/src/sentry_span_helper.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index 32d9008397..4ef3504684 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -57,8 +57,8 @@ class SentrySpanHelper { T Function() execute, { String? dbName, }) { - final currentParent = _spanStack.last; - final newParent = currentParent?.startChild( + final parentSpan = _spanStack.last; + final newParent = parentSpan?.startChild( SentrySpanOperations.dbSqlTransaction, description: SentrySpanDescriptions.dbTransaction, ) ?? @@ -95,6 +95,7 @@ class SentrySpanHelper { Future finishTransaction(Future Function() execute) async { final parentSpan = _spanStack.removeLast(); + try { final result = await execute(); parentSpan?.status = SpanStatus.ok(); From 52202703b2e75b88754da9eb6737f92d6d2b620d Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 14:11:35 +0100 Subject: [PATCH 19/29] formatting --- drift/example/example.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drift/example/example.dart b/drift/example/example.dart index ddf0023873..a24297dd47 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -30,9 +30,7 @@ Future runApp() async { final db = AppDatabase(executor); await db.transaction(() async { - await db - .into(db.todoItems) - .insert( + await db.into(db.todoItems).insert( TodoItemsCompanion.insert( title: 'This is a test thing', content: 'test', @@ -40,9 +38,7 @@ Future runApp() async { ); await db.transaction(() async { - await db - .into(db.todoItems) - .insert( + await db.into(db.todoItems).insert( TodoItemsCompanion.insert( title: 'This is a test thing in the tx', content: 'test', From 0a19ad60078dd1e67661d61069a896379da32f9b Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 14:17:13 +0100 Subject: [PATCH 20/29] fix analyze --- drift/test/sentry_drift_test.dart | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 9bb70ca301..f1b2b712d9 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -139,8 +139,8 @@ void main() { setUp(() async { fixture = Fixture(); - when(fixture.hub.options).thenReturn(fixture.options); - when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.mockHub.options).thenReturn(fixture.options); + when(fixture.mockHub.getSpan()).thenReturn(fixture.tracer); await fixture.setUp(); }); @@ -427,8 +427,8 @@ void main() { setUp(() async { fixture = Fixture(); - when(fixture.hub.options).thenReturn(fixture.options); - when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.mockHub.options).thenReturn(fixture.options); + when(fixture.mockHub.getSpan()).thenReturn(fixture.tracer); await fixture.setUp(); }); @@ -463,8 +463,8 @@ void main() { setUp(() async { fixture = Fixture(); - when(fixture.hub.options).thenReturn(fixture.options); - when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.mockHub.options).thenReturn(fixture.options); + when(fixture.mockHub.getSpan()).thenReturn(fixture.tracer); when(fixture.mockLazyDatabase.ensureOpen(any)) .thenAnswer((_) => Future.value(true)); when(fixture.mockLazyDatabase.dialect).thenReturn(SqlDialect.sqlite); @@ -700,11 +700,11 @@ void main() { class Fixture { final options = defaultTestOptions(); - final hub = MockHub(); + final mockHub = MockHub(); static final dbName = 'people-drift-impl'; final exception = Exception('fixture-exception'); final _context = SentryTransactionContext('name', 'operation'); - late final tracer = SentryTracer(_context, hub); + late final tracer = SentryTracer(_context, mockHub); late AppDatabase sut; final mockLazyDatabase = MockLazyDatabase(); @@ -741,21 +741,18 @@ class Fixture { bool injectMock = false, QueryExecutor? customExecutor, }) { - Hub? _hub = hub; - if (useRealHub) { - _hub = null; - } + final hub = useRealHub ? null : mockHub; if (customExecutor != null) { return customExecutor.interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: _hub), + SentryQueryInterceptor(databaseName: dbName, hub: hub), ); } else if (injectMock) { return mockLazyDatabase.interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: _hub), + SentryQueryInterceptor(databaseName: dbName, hub: hub), ); } else { return NativeDatabase.memory().interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: _hub), + SentryQueryInterceptor(databaseName: dbName, hub: hub), ); } } From 758ab6e24cbbd064be105cb3ce6bead30ec59215 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 15:54:03 +0100 Subject: [PATCH 21/29] add disabled tracing test --- drift/test/sentry_drift_test.dart | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index f1b2b712d9..2175b313cf 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -437,6 +437,18 @@ void main() { await fixture.tearDown(); }); + test('disabled tracing does not create spans', () async { + HubAdapter().options.tracesSampleRate = null; + HubAdapter().options.tracesSampler = null; + await fixture.setUp(useRealHub: true); + final sut = fixture.sut; + expect(fixture.tracer.children, isEmpty); + + await insertRow(sut); + + expect(fixture.tracer.children, isEmpty); + }); + test('does not add open span if db is not used', () async { fixture.sut; @@ -736,6 +748,21 @@ class Fixture { .firstWhere((element) => element.context.description == description); } + AppDatabase getSut({ + bool injectMock = false, + bool useRealHub = false, + QueryExecutor? customExecutor, + }) { + return AppDatabase( + openConnection( + useRealHub: useRealHub, + injectMock: injectMock, + customExecutor: customExecutor, + ), + ); + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + } + QueryExecutor openConnection({ bool useRealHub = false, bool injectMock = false, From b1316345910d237b39f708ec810e557b49324906 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Fri, 14 Feb 2025 16:11:12 +0100 Subject: [PATCH 22/29] unused code --- drift/test/sentry_drift_test.dart | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 2175b313cf..31b6ec605c 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -748,21 +748,6 @@ class Fixture { .firstWhere((element) => element.context.description == description); } - AppDatabase getSut({ - bool injectMock = false, - bool useRealHub = false, - QueryExecutor? customExecutor, - }) { - return AppDatabase( - openConnection( - useRealHub: useRealHub, - injectMock: injectMock, - customExecutor: customExecutor, - ), - ); - driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; - } - QueryExecutor openConnection({ bool useRealHub = false, bool injectMock = false, From a2d1d7682e95cdfcdff9a612422b63c36802e783 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 14:05:48 +0100 Subject: [PATCH 23/29] temp --- drift/lib/src/sentry_span_helper.dart | 57 ++++++++++++++++++--------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index 4ef3504684..f2c92ce3cf 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -9,11 +9,14 @@ import 'package:sentry/sentry.dart'; class SentrySpanHelper { final Hub _hub; final String _origin; - final ListQueue _spanStack = ListQueue(); - SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter() { - _spanStack.add(hub?.getSpan()); - } + /// Represents a stack of Drift transaction spans. + /// These are used to allow nested spans if the user nests Drift transactions. + /// If the transaction stack is empty, the spans are attached to the + /// active span in the Hub's scope. + final ListQueue _transactionStack = ListQueue(); + + SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter(); Future asyncWrapInSpan( String description, @@ -21,7 +24,12 @@ class SentrySpanHelper { String? dbName, String? operation, }) async { - final parentSpan = _spanStack.last; + final parentSpan = _transactionStack.lastOrNull ?? _hub.getSpan(); + if (parentSpan == null) { + _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not start span for the Drift operation: $description'); + return execute(); + } + final span = parentSpan?.startChild( operation ?? SentrySpanOperations.dbSqlQuery, description: description, @@ -57,8 +65,13 @@ class SentrySpanHelper { T Function() execute, { String? dbName, }) { - final parentSpan = _spanStack.last; - final newParent = parentSpan?.startChild( + final parentSpan = _transactionStack.lastOrNull ?? _hub.getSpan(); + if (parentSpan == null) { + _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not start span for Drift operation: Begin Transaction'); + return execute(); + } + + final newParent = parentSpan.startChild( SentrySpanOperations.dbSqlTransaction, description: SentrySpanDescriptions.dbTransaction, ) ?? @@ -67,7 +80,7 @@ class SentrySpanHelper { description: SentrySpanDescriptions.dbTransaction, ); - _spanStack.add(newParent); + _transactionStack.add(newParent); newParent?.origin = _origin; @@ -94,38 +107,46 @@ class SentrySpanHelper { } Future finishTransaction(Future Function() execute) async { - final parentSpan = _spanStack.removeLast(); + final parentSpan = _transactionStack.removeLast(); + if (parentSpan == null) { + _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not finish span for Drift operation: Finish Transaction'); + return execute(); + } try { final result = await execute(); - parentSpan?.status = SpanStatus.ok(); + parentSpan.status = SpanStatus.ok(); return result; } catch (exception) { - parentSpan?.throwable = exception; - parentSpan?.status = SpanStatus.internalError(); + parentSpan.throwable = exception; + parentSpan.status = SpanStatus.internalError(); rethrow; } finally { - await parentSpan?.finish(); + await parentSpan.finish(); } } Future abortTransaction(Future Function() execute) async { - final parentSpan = _spanStack.removeLast(); + final parentSpan = _transactionStack.removeLast(); + if (parentSpan == null) { + _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not finish span for Drift operation: Abort Transaction'); + return Future.value(); + } try { final result = await execute(); - parentSpan?.status = SpanStatus.aborted(); + parentSpan.status = SpanStatus.aborted(); return result; } catch (exception) { - parentSpan?.throwable = exception; - parentSpan?.status = SpanStatus.internalError(); + parentSpan.throwable = exception; + parentSpan.status = SpanStatus.internalError(); rethrow; } finally { - await parentSpan?.finish(); + await parentSpan.finish(); } } } From 05827f94809bcff83be6a3e602469c26c08fa4a1 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 15:55:48 +0100 Subject: [PATCH 24/29] update --- drift/lib/src/constants.dart | 1 + drift/lib/src/sentry_query_interceptor.dart | 4 +- drift/lib/src/sentry_span_helper.dart | 78 +- drift/test/mocks/mocks.dart | 2 +- drift/test/mocks/mocks.mocks.dart | 628 +++++++------- drift/test/sentry_drift_test.dart | 897 ++++++++++---------- 6 files changed, 844 insertions(+), 766 deletions(-) diff --git a/drift/lib/src/constants.dart b/drift/lib/src/constants.dart index c1be0a71d6..c096dbc0af 100644 --- a/drift/lib/src/constants.dart +++ b/drift/lib/src/constants.dart @@ -1 +1,2 @@ const String integrationName = 'sentryDriftTracing'; +const String loggerName = 'sentry_drift'; diff --git a/drift/lib/src/sentry_query_interceptor.dart b/drift/lib/src/sentry_query_interceptor.dart index a2fb8dbc86..89bcabdf65 100644 --- a/drift/lib/src/sentry_query_interceptor.dart +++ b/drift/lib/src/sentry_query_interceptor.dart @@ -20,6 +20,9 @@ class SentryQueryInterceptor extends QueryInterceptor { late final SentrySpanHelper _spanHelper; bool _isDbOpen = false; + @visibleForTesting + SentrySpanHelper get spanHelper => _spanHelper; + SentryQueryInterceptor({required String databaseName, @internal Hub? hub}) : _dbName = databaseName { hub = hub ?? HubAdapter(); @@ -67,7 +70,6 @@ class SentryQueryInterceptor extends QueryInterceptor { @override TransactionExecutor beginTransaction(QueryExecutor parent) { - print('begin transaction'); return _spanHelper.beginTransaction( () => super.beginTransaction(parent), dbName: _dbName, diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index f2c92ce3cf..c4e8cb5185 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -5,6 +5,8 @@ import 'dart:collection'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; +import 'constants.dart'; + @internal class SentrySpanHelper { final Hub _hub; @@ -16,6 +18,9 @@ class SentrySpanHelper { /// active span in the Hub's scope. final ListQueue _transactionStack = ListQueue(); + @visibleForTesting + ListQueue get transactionStack => _transactionStack; + SentrySpanHelper(this._origin, {Hub? hub}) : _hub = hub ?? HubAdapter(); Future asyncWrapInSpan( @@ -26,38 +31,42 @@ class SentrySpanHelper { }) async { final parentSpan = _transactionStack.lastOrNull ?? _hub.getSpan(); if (parentSpan == null) { - _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not start span for the Drift operation: $description'); + _hub.options.logger( + SentryLevel.warning, + 'Active Sentry transaction does not exist, could not start span for the Drift operation: $description', + logger: loggerName, + ); return execute(); } - final span = parentSpan?.startChild( + final span = parentSpan.startChild( operation ?? SentrySpanOperations.dbSqlQuery, description: description, ); - span?.origin = _origin; + span.origin = _origin; - span?.setData( + span.setData( SentrySpanData.dbSystemKey, SentrySpanData.dbSystemSqlite, ); if (dbName != null) { - span?.setData(SentrySpanData.dbNameKey, dbName); + span.setData(SentrySpanData.dbNameKey, dbName); } try { final result = await execute(); - span?.status = SpanStatus.ok(); + span.status = SpanStatus.ok(); return result; } catch (exception) { - span?.throwable = exception; - span?.status = SpanStatus.internalError(); + span.throwable = exception; + span.status = SpanStatus.internalError(); rethrow; } finally { - await span?.finish(); + await span.finish(); } } @@ -67,51 +76,57 @@ class SentrySpanHelper { }) { final parentSpan = _transactionStack.lastOrNull ?? _hub.getSpan(); if (parentSpan == null) { - _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not start span for Drift operation: Begin Transaction'); + _hub.options.logger( + SentryLevel.warning, + 'Active Sentry transaction does not exist, could not start span for Drift operation: Begin Transaction', + logger: loggerName, + ); return execute(); } final newParent = parentSpan.startChild( - SentrySpanOperations.dbSqlTransaction, - description: SentrySpanDescriptions.dbTransaction, - ) ?? - _hub.getSpan()?.startChild( - SentrySpanOperations.dbSqlTransaction, - description: SentrySpanDescriptions.dbTransaction, - ); - - _transactionStack.add(newParent); + SentrySpanOperations.dbSqlTransaction, + description: SentrySpanDescriptions.dbTransaction, + ); - newParent?.origin = _origin; + newParent.origin = _origin; - newParent?.setData( + newParent.setData( SentrySpanData.dbSystemKey, SentrySpanData.dbSystemSqlite, ); if (dbName != null) { - newParent?.setData(SentrySpanData.dbNameKey, dbName); + newParent.setData(SentrySpanData.dbNameKey, dbName); } try { final result = execute(); - newParent?.status = SpanStatus.unknown(); + newParent.status = SpanStatus.unknown(); + + // Only add to the stack if no error occurred + _transactionStack.add(newParent); return result; } catch (exception) { - newParent?.throwable = exception; - newParent?.status = SpanStatus.internalError(); + newParent.throwable = exception; + newParent.status = SpanStatus.internalError(); rethrow; } } Future finishTransaction(Future Function() execute) async { - final parentSpan = _transactionStack.removeLast(); + final parentSpan = _transactionStack.lastOrNull; if (parentSpan == null) { - _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not finish span for Drift operation: Finish Transaction'); + _hub.options.logger( + SentryLevel.warning, + 'Active Sentry transaction does not exist, could not finish span for Drift operation: Finish Transaction', + logger: loggerName, + ); return execute(); } + _transactionStack.removeLast(); try { final result = await execute(); @@ -129,11 +144,16 @@ class SentrySpanHelper { } Future abortTransaction(Future Function() execute) async { - final parentSpan = _transactionStack.removeLast(); + final parentSpan = _transactionStack.lastOrNull; if (parentSpan == null) { - _hub.options.logger(SentryLevel.info, 'Drift: active Sentry transaction does not exist, could not finish span for Drift operation: Abort Transaction'); + _hub.options.logger( + SentryLevel.warning, + 'Active Sentry transaction does not exist, could not finish span for Drift operation: Abort Transaction', + logger: loggerName, + ); return Future.value(); } + _transactionStack.removeLast(); try { final result = await execute(); diff --git a/drift/test/mocks/mocks.dart b/drift/test/mocks/mocks.dart index 4842f97455..98470693f6 100644 --- a/drift/test/mocks/mocks.dart +++ b/drift/test/mocks/mocks.dart @@ -5,6 +5,6 @@ import 'package:sentry/sentry.dart'; @GenerateMocks([ Hub, LazyDatabase, - TransactionExecutor, + QueryExecutor, ]) void main() {} diff --git a/drift/test/mocks/mocks.mocks.dart b/drift/test/mocks/mocks.mocks.dart index 25dcb269e5..ae034e47b1 100644 --- a/drift/test/mocks/mocks.mocks.dart +++ b/drift/test/mocks/mocks.mocks.dart @@ -27,38 +27,38 @@ import 'package:sentry/src/profiling.dart' as _i4; class _FakeSentryOptions_0 extends _i1.SmartFake implements _i2.SentryOptions { _FakeSentryOptions_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeSentryId_1 extends _i1.SmartFake implements _i2.SentryId { _FakeSentryId_1(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeScope_2 extends _i1.SmartFake implements _i2.Scope { _FakeScope_2(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeHub_3 extends _i1.SmartFake implements _i2.Hub { _FakeHub_3(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeISentrySpan_4 extends _i1.SmartFake implements _i2.ISentrySpan { _FakeISentrySpan_4(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeQueryExecutor_5 extends _i1.SmartFake implements _i3.QueryExecutor { _FakeQueryExecutor_5(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeTransactionExecutor_6 extends _i1.SmartFake implements _i3.TransactionExecutor { _FakeTransactionExecutor_6(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } /// A class which mocks [Hub]. @@ -70,13 +70,15 @@ class MockHub extends _i1.Mock implements _i2.Hub { } @override - _i2.SentryOptions get options => (super.noSuchMethod( - Invocation.getter(#options), - returnValue: _FakeSentryOptions_0( - this, - Invocation.getter(#options), - ), - ) as _i2.SentryOptions); + _i2.SentryOptions get options => + (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeSentryOptions_0( + this, + Invocation.getter(#options), + ), + ) + as _i2.SentryOptions); @override bool get isEnabled => @@ -84,22 +86,26 @@ class MockHub extends _i1.Mock implements _i2.Hub { as bool); @override - _i2.SentryId get lastEventId => (super.noSuchMethod( - Invocation.getter(#lastEventId), - returnValue: _FakeSentryId_1(this, Invocation.getter(#lastEventId)), - ) as _i2.SentryId); + _i2.SentryId get lastEventId => + (super.noSuchMethod( + Invocation.getter(#lastEventId), + returnValue: _FakeSentryId_1(this, Invocation.getter(#lastEventId)), + ) + as _i2.SentryId); @override - _i2.Scope get scope => (super.noSuchMethod( - Invocation.getter(#scope), - returnValue: _FakeScope_2(this, Invocation.getter(#scope)), - ) as _i2.Scope); + _i2.Scope get scope => + (super.noSuchMethod( + Invocation.getter(#scope), + returnValue: _FakeScope_2(this, Invocation.getter(#scope)), + ) + as _i2.Scope); @override set profilerFactory(_i4.SentryProfilerFactory? value) => super.noSuchMethod( - Invocation.setter(#profilerFactory, value), - returnValueForMissingStub: null, - ); + Invocation.setter(#profilerFactory, value), + returnValueForMissingStub: null, + ); @override _i5.Future<_i2.SentryId> captureEvent( @@ -109,22 +115,23 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( - Invocation.method( - #captureEvent, - [event], - {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, - ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, Invocation.method( #captureEvent, [event], {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, ), - ), - ), - ) as _i5.Future<_i2.SentryId>); + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureEvent, + [event], + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), + ), + ), + ) + as _i5.Future<_i2.SentryId>); @override _i5.Future<_i2.SentryId> captureException( @@ -134,22 +141,23 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( - Invocation.method( - #captureException, - [throwable], - {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, - ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, Invocation.method( #captureException, [throwable], {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, ), - ), - ), - ) as _i5.Future<_i2.SentryId>); + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureException, + [throwable], + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), + ), + ), + ) + as _i5.Future<_i2.SentryId>); @override _i5.Future<_i2.SentryId> captureMessage( @@ -161,20 +169,6 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( - Invocation.method( - #captureMessage, - [message], - { - #level: level, - #template: template, - #params: params, - #hint: hint, - #withScope: withScope, - }, - ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, Invocation.method( #captureMessage, [message], @@ -186,17 +180,33 @@ class MockHub extends _i1.Mock implements _i2.Hub { #withScope: withScope, }, ), - ), - ), - ) as _i5.Future<_i2.SentryId>); + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureMessage, + [message], + { + #level: level, + #template: template, + #params: params, + #hint: hint, + #withScope: withScope, + }, + ), + ), + ), + ) + as _i5.Future<_i2.SentryId>); @override _i5.Future captureUserFeedback(_i2.SentryUserFeedback? userFeedback) => (super.noSuchMethod( - Invocation.method(#captureUserFeedback, [userFeedback]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + Invocation.method(#captureUserFeedback, [userFeedback]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override _i5.Future<_i2.SentryId> captureFeedback( @@ -205,49 +215,55 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( - Invocation.method( - #captureFeedback, - [feedback], - {#hint: hint, #withScope: withScope}, - ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, Invocation.method( #captureFeedback, [feedback], {#hint: hint, #withScope: withScope}, ), - ), - ), - ) as _i5.Future<_i2.SentryId>); + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureFeedback, + [feedback], + {#hint: hint, #withScope: withScope}, + ), + ), + ), + ) + as _i5.Future<_i2.SentryId>); @override _i5.Future addBreadcrumb(_i2.Breadcrumb? crumb, {_i2.Hint? hint}) => (super.noSuchMethod( - Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override void bindClient(_i2.SentryClient? client) => super.noSuchMethod( - Invocation.method(#bindClient, [client]), - returnValueForMissingStub: null, - ); + Invocation.method(#bindClient, [client]), + returnValueForMissingStub: null, + ); @override - _i2.Hub clone() => (super.noSuchMethod( - Invocation.method(#clone, []), - returnValue: _FakeHub_3(this, Invocation.method(#clone, [])), - ) as _i2.Hub); + _i2.Hub clone() => + (super.noSuchMethod( + Invocation.method(#clone, []), + returnValue: _FakeHub_3(this, Invocation.method(#clone, [])), + ) + as _i2.Hub); @override - _i5.Future close() => (super.noSuchMethod( - Invocation.method(#close, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + _i5.Future close() => + (super.noSuchMethod( + Invocation.method(#close, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override _i5.FutureOr configureScope(_i2.ScopeCallback? callback) => @@ -268,38 +284,39 @@ class MockHub extends _i1.Mock implements _i2.Hub { Map? customSamplingContext, }) => (super.noSuchMethod( - Invocation.method( - #startTransaction, - [name, operation], - { - #description: description, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - #customSamplingContext: customSamplingContext, - }, - ), - returnValue: _FakeISentrySpan_4( - this, - Invocation.method( - #startTransaction, - [name, operation], - { - #description: description, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - #customSamplingContext: customSamplingContext, - }, - ), - ), - ) as _i2.ISentrySpan); + Invocation.method( + #startTransaction, + [name, operation], + { + #description: description, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + #customSamplingContext: customSamplingContext, + }, + ), + returnValue: _FakeISentrySpan_4( + this, + Invocation.method( + #startTransaction, + [name, operation], + { + #description: description, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + #customSamplingContext: customSamplingContext, + }, + ), + ), + ) + as _i2.ISentrySpan); @override _i2.ISentrySpan startTransactionWithContext( @@ -313,36 +330,37 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.OnTransactionFinish? onFinish, }) => (super.noSuchMethod( - Invocation.method( - #startTransactionWithContext, - [transactionContext], - { - #customSamplingContext: customSamplingContext, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - }, - ), - returnValue: _FakeISentrySpan_4( - this, - Invocation.method( - #startTransactionWithContext, - [transactionContext], - { - #customSamplingContext: customSamplingContext, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - }, - ), - ), - ) as _i2.ISentrySpan); + Invocation.method( + #startTransactionWithContext, + [transactionContext], + { + #customSamplingContext: customSamplingContext, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + }, + ), + returnValue: _FakeISentrySpan_4( + this, + Invocation.method( + #startTransactionWithContext, + [transactionContext], + { + #customSamplingContext: customSamplingContext, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + }, + ), + ), + ) + as _i2.ISentrySpan); @override _i5.Future<_i2.SentryId> captureTransaction( @@ -351,33 +369,33 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.Hint? hint, }) => (super.noSuchMethod( - Invocation.method( - #captureTransaction, - [transaction], - {#traceContext: traceContext, #hint: hint}, - ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, Invocation.method( #captureTransaction, [transaction], {#traceContext: traceContext, #hint: hint}, ), - ), - ), - ) as _i5.Future<_i2.SentryId>); + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, + Invocation.method( + #captureTransaction, + [transaction], + {#traceContext: traceContext, #hint: hint}, + ), + ), + ), + ) + as _i5.Future<_i2.SentryId>); @override void setSpanContext( dynamic throwable, _i2.ISentrySpan? span, String? transaction, - ) => - super.noSuchMethod( - Invocation.method(#setSpanContext, [throwable, span, transaction]), - returnValueForMissingStub: null, - ); + ) => super.noSuchMethod( + Invocation.method(#setSpanContext, [throwable, span, transaction]), + returnValueForMissingStub: null, + ); } /// A class which mocks [LazyDatabase]. @@ -389,73 +407,87 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { } @override - _i6.DatabaseOpener get opener => (super.noSuchMethod( - Invocation.getter(#opener), - returnValue: () => _i5.Future<_i3.QueryExecutor>.value( - _FakeQueryExecutor_5(this, Invocation.getter(#opener)), - ), - ) as _i6.DatabaseOpener); + _i6.DatabaseOpener get opener => + (super.noSuchMethod( + Invocation.getter(#opener), + returnValue: + () => _i5.Future<_i3.QueryExecutor>.value( + _FakeQueryExecutor_5(this, Invocation.getter(#opener)), + ), + ) + as _i6.DatabaseOpener); @override - _i3.SqlDialect get dialect => (super.noSuchMethod( - Invocation.getter(#dialect), - returnValue: _i3.SqlDialect.sqlite, - ) as _i3.SqlDialect); + _i3.SqlDialect get dialect => + (super.noSuchMethod( + Invocation.getter(#dialect), + returnValue: _i3.SqlDialect.sqlite, + ) + as _i3.SqlDialect); @override - _i3.QueryExecutor beginExclusive() => (super.noSuchMethod( - Invocation.method(#beginExclusive, []), - returnValue: _FakeQueryExecutor_5( - this, - Invocation.method(#beginExclusive, []), - ), - ) as _i3.QueryExecutor); + _i3.QueryExecutor beginExclusive() => + (super.noSuchMethod( + Invocation.method(#beginExclusive, []), + returnValue: _FakeQueryExecutor_5( + this, + Invocation.method(#beginExclusive, []), + ), + ) + as _i3.QueryExecutor); @override - _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( - Invocation.method(#beginTransaction, []), - returnValue: _FakeTransactionExecutor_6( - this, - Invocation.method(#beginTransaction, []), - ), - ) as _i3.TransactionExecutor); + _i3.TransactionExecutor beginTransaction() => + (super.noSuchMethod( + Invocation.method(#beginTransaction, []), + returnValue: _FakeTransactionExecutor_6( + this, + Invocation.method(#beginTransaction, []), + ), + ) + as _i3.TransactionExecutor); @override _i5.Future ensureOpen(_i3.QueryExecutorUser? user) => (super.noSuchMethod( - Invocation.method(#ensureOpen, [user]), - returnValue: _i5.Future.value(false), - ) as _i5.Future); + Invocation.method(#ensureOpen, [user]), + returnValue: _i5.Future.value(false), + ) + as _i5.Future); @override _i5.Future runBatched(_i3.BatchedStatements? statements) => (super.noSuchMethod( - Invocation.method(#runBatched, [statements]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + Invocation.method(#runBatched, [statements]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override _i5.Future runCustom(String? statement, [List? args]) => (super.noSuchMethod( - Invocation.method(#runCustom, [statement, args]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + Invocation.method(#runCustom, [statement, args]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override _i5.Future runDelete(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runDelete, [statement, args]), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + Invocation.method(#runDelete, [statement, args]), + returnValue: _i5.Future.value(0), + ) + as _i5.Future); @override _i5.Future runInsert(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runInsert, [statement, args]), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + Invocation.method(#runInsert, [statement, args]), + returnValue: _i5.Future.value(0), + ) + as _i5.Future); @override _i5.Future>> runSelect( @@ -463,68 +495,54 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { List? args, ) => (super.noSuchMethod( - Invocation.method(#runSelect, [statement, args]), - returnValue: _i5.Future>>.value( - >[], - ), - ) as _i5.Future>>); + Invocation.method(#runSelect, [statement, args]), + returnValue: _i5.Future>>.value( + >[], + ), + ) + as _i5.Future>>); @override _i5.Future runUpdate(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runUpdate, [statement, args]), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + Invocation.method(#runUpdate, [statement, args]), + returnValue: _i5.Future.value(0), + ) + as _i5.Future); @override - _i5.Future close() => (super.noSuchMethod( - Invocation.method(#close, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + _i5.Future close() => + (super.noSuchMethod( + Invocation.method(#close, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); } -/// A class which mocks [TransactionExecutor]. +/// A class which mocks [QueryExecutor]. /// /// See the documentation for Mockito's code generation for more information. -class MockTransactionExecutor extends _i1.Mock - implements _i3.TransactionExecutor { - MockTransactionExecutor() { +class MockQueryExecutor extends _i1.Mock implements _i3.QueryExecutor { + MockQueryExecutor() { _i1.throwOnMissingStub(this); } @override - bool get supportsNestedTransactions => (super.noSuchMethod( - Invocation.getter(#supportsNestedTransactions), - returnValue: false, - ) as bool); - - @override - _i3.SqlDialect get dialect => (super.noSuchMethod( - Invocation.getter(#dialect), - returnValue: _i3.SqlDialect.sqlite, - ) as _i3.SqlDialect); - - @override - _i5.Future send() => (super.noSuchMethod( - Invocation.method(#send, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); - - @override - _i5.Future rollback() => (super.noSuchMethod( - Invocation.method(#rollback, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + _i3.SqlDialect get dialect => + (super.noSuchMethod( + Invocation.getter(#dialect), + returnValue: _i3.SqlDialect.sqlite, + ) + as _i3.SqlDialect); @override _i5.Future ensureOpen(_i3.QueryExecutorUser? user) => (super.noSuchMethod( - Invocation.method(#ensureOpen, [user]), - returnValue: _i5.Future.value(false), - ) as _i5.Future); + Invocation.method(#ensureOpen, [user]), + returnValue: _i5.Future.value(false), + ) + as _i5.Future); @override _i5.Future>> runSelect( @@ -532,71 +550,83 @@ class MockTransactionExecutor extends _i1.Mock List? args, ) => (super.noSuchMethod( - Invocation.method(#runSelect, [statement, args]), - returnValue: _i5.Future>>.value( - >[], - ), - ) as _i5.Future>>); + Invocation.method(#runSelect, [statement, args]), + returnValue: _i5.Future>>.value( + >[], + ), + ) + as _i5.Future>>); @override _i5.Future runInsert(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runInsert, [statement, args]), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + Invocation.method(#runInsert, [statement, args]), + returnValue: _i5.Future.value(0), + ) + as _i5.Future); @override _i5.Future runUpdate(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runUpdate, [statement, args]), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + Invocation.method(#runUpdate, [statement, args]), + returnValue: _i5.Future.value(0), + ) + as _i5.Future); @override _i5.Future runDelete(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runDelete, [statement, args]), - returnValue: _i5.Future.value(0), - ) as _i5.Future); + Invocation.method(#runDelete, [statement, args]), + returnValue: _i5.Future.value(0), + ) + as _i5.Future); @override _i5.Future runCustom(String? statement, [List? args]) => (super.noSuchMethod( - Invocation.method(#runCustom, [statement, args]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + Invocation.method(#runCustom, [statement, args]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override _i5.Future runBatched(_i3.BatchedStatements? statements) => (super.noSuchMethod( - Invocation.method(#runBatched, [statements]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + Invocation.method(#runBatched, [statements]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); @override - _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( - Invocation.method(#beginTransaction, []), - returnValue: _FakeTransactionExecutor_6( - this, - Invocation.method(#beginTransaction, []), - ), - ) as _i3.TransactionExecutor); + _i3.TransactionExecutor beginTransaction() => + (super.noSuchMethod( + Invocation.method(#beginTransaction, []), + returnValue: _FakeTransactionExecutor_6( + this, + Invocation.method(#beginTransaction, []), + ), + ) + as _i3.TransactionExecutor); @override - _i3.QueryExecutor beginExclusive() => (super.noSuchMethod( - Invocation.method(#beginExclusive, []), - returnValue: _FakeQueryExecutor_5( - this, - Invocation.method(#beginExclusive, []), - ), - ) as _i3.QueryExecutor); + _i3.QueryExecutor beginExclusive() => + (super.noSuchMethod( + Invocation.method(#beginExclusive, []), + returnValue: _FakeQueryExecutor_5( + this, + Invocation.method(#beginExclusive, []), + ), + ) + as _i3.QueryExecutor); @override - _i5.Future close() => (super.noSuchMethod( - Invocation.method(#close, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) as _i5.Future); + _i5.Future close() => + (super.noSuchMethod( + Invocation.method(#close, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); } diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 31b6ec605c..9e2bc594b0 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -133,574 +133,641 @@ void main() { } } - group('adds span', () { + SentryTracer startTransaction() { + return Sentry.startTransaction('drift', 'test op', bindToScope: true) + as SentryTracer; + } + + group('open operations', () { late Fixture fixture; setUp(() async { fixture = Fixture(); - - when(fixture.mockHub.options).thenReturn(fixture.options); - when(fixture.mockHub.getSpan()).thenReturn(fixture.tracer); - - await fixture.setUp(); - }); - - tearDown(() async { - await fixture.tearDown(); + await Sentry.init( + (options) {}, + options: fixture.options, + ); }); - test('open span is only added once', () async { - final sut = fixture.sut; - - await insertRow(sut); - await insertRow(sut); - await insertRow(sut); + test('successful adds span only once', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final openSpansCount = fixture.tracer.children - .where( - (element) => - element.context.description == - SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), - ) - .length; + final tx = startTransaction(); + await insertRow(db); + await insertRow(db); + await insertRow(db); - expect(openSpansCount, 1); - }); - - test('insert adds span', () async { - final sut = fixture.sut; - - await insertRow(sut); + final openSpans = tx.children.where( + (element) => + element.context.description == + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), + ); + expect(openSpans.length, 1); verifySpan( - expectedInsertStatement, - fixture.getCreatedSpan(), + operation: SentrySpanOperations.dbOpen, + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), + openSpans.first, ); }); - test('update adds span', () async { - final sut = fixture.sut; + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenThrow(exception); + when(queryExecutor.runInsert(any, any)) + .thenAnswer((_) => Future.value(1)); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - await insertRow(sut); - await updateRow(sut); + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); - verifySpan( - expectedUpdateStatement, - fixture.getCreatedSpan(), - ); - }); - - test('custom adds span', () async { - final sut = fixture.sut; + final tx = startTransaction(); + try { + await insertRow(db); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } - await sut.customStatement('SELECT * FROM todo_items'); + final openSpans = tx.children.where( + (element) => + element.context.description == + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), + ); - verifySpan( - expectedSelectStatement, - fixture.getCreatedSpan(), + expect(openSpans.length, 1); + verifyErrorSpan( + operation: SentrySpanOperations.dbOpen, + SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), + exception, + openSpans.first, ); }); + }); - test('delete adds span', () async { - final sut = fixture.sut; - - await insertRow(sut); - await fixture.sut.delete(fixture.sut.todoItems).go(); + group('close operations', () { + late Fixture fixture; - verifySpan( - expectedDeleteStatement, - fixture.getCreatedSpan(), + setUp(() async { + fixture = Fixture(); + await Sentry.init( + (options) {}, + options: defaultTestOptions()..tracesSampleRate = 1.0, ); }); - test('transaction adds insert spans', () async { - final sut = fixture.sut; - - await sut.transaction(() async { - await insertRow(sut); - await insertRow(sut); - }); + test('successful adds close only once', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final insertSpanCount = fixture.tracer.children - .where( - (element) => element.context.description == expectedInsertStatement, - ) - .length; - expect(insertSpanCount, 2); + final tx = startTransaction(); + await insertRow(db); + await db.close(); - verifySpan( - expectedInsertStatement, - fixture.getCreatedSpan(), + final closeSpans = tx.children.where( + (element) => + element.context.description == + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), ); + expect(closeSpans.length, 1); verifySpan( - SentrySpanDescriptions.dbTransaction, - fixture - .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), - operation: SentrySpanOperations.dbSqlTransaction, + operation: SentrySpanOperations.dbClose, + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), + closeSpans.first, ); }); - test('transaction adds update spans', () async { - final sut = fixture.sut; + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.runInsert(any, any)) + .thenAnswer((_) => Future.value(1)); + when(queryExecutor.close()).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - await sut.transaction(() async { - await insertRow(sut); - await updateRow(sut); - }); + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); - final updateSpanCount = fixture.tracer.children - .where( - (element) => element.context.description == expectedUpdateStatement, - ) - .length; - expect(updateSpanCount, 1); + final tx = startTransaction(); + try { + await insertRow(db); + await db.close(); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } - verifySpan( - expectedUpdateStatement, - fixture.getCreatedSpan(), + final closeSpans = tx.children.where( + (element) => + element.context.description == + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), ); - verifySpan( - SentrySpanDescriptions.dbTransaction, - fixture - .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), - operation: SentrySpanOperations.dbSqlTransaction, + expect(closeSpans.length, 1); + verifyErrorSpan( + SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), + exception, + closeSpans.first, + operation: SentrySpanOperations.dbClose, ); }); + }); - test('transaction adds delete spans', () async { - final sut = fixture.sut; + group('insert operations', () { + late Fixture fixture; - await sut.transaction(() async { - await insertRow(sut); - await fixture.sut.delete(fixture.sut.todoItems).go(); - }); + setUp(() async { + fixture = Fixture(); + await fixture.sentryInit(); + }); - final deleteSpanCount = fixture.tracer.children - .where( - (element) => element.context.description == expectedDeleteStatement, - ) - .length; - expect(deleteSpanCount, 1); + test('successful adds span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - verifySpan( - expectedDeleteStatement, - fixture.getCreatedSpan(), - ); + final tx = startTransaction(); + await insertRow(db); verifySpan( - SentrySpanDescriptions.dbTransaction, - fixture - .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), - operation: SentrySpanOperations.dbSqlTransaction, + expectedInsertStatement, + tx.children.last, ); }); - test('transaction adds custom spans', () async { - final sut = fixture.sut; + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.runInsert(any, any)).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - await sut.transaction(() async { - await insertRow(sut); - await sut.customStatement('SELECT * FROM todo_items'); - }); + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); - final customSpanCount = fixture.tracer.children - .where( - (element) => element.context.description == expectedSelectStatement, - ) - .length; - expect(customSpanCount, 1); - - verifySpan( - expectedSelectStatement, - fixture.getCreatedSpan(), - ); + final tx = startTransaction(); + try { + await insertRow(db); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } - verifySpan( - SentrySpanDescriptions.dbTransaction, - fixture - .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), - operation: SentrySpanOperations.dbSqlTransaction, + verifyErrorSpan( + expectedInsertStatement, + exception, + tx.children.last, ); }); + }); - test('transaction rollback adds span', () async { - final sut = fixture.sut; + group('update operations', () { + late Fixture fixture; - await insertRow(sut); - await insertRow(sut); + setUp(() async { + fixture = Fixture(); + await fixture.sentryInit(); + }); - try { - await sut.transaction(() async { - await insertRow(sut, withError: true); - }); - } catch (_) {} + test('successful adds span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final spans = fixture.tracer.children - .where((child) => child.status == SpanStatus.aborted()); - expect(spans.length, 1); - final abortedSpan = spans.first; + final tx = startTransaction(); + await insertRow(db); + await updateRow(db); verifySpan( - SentrySpanDescriptions.dbTransaction, - abortedSpan, - status: SpanStatus.aborted(), - operation: SentrySpanOperations.dbSqlTransaction, + expectedUpdateStatement, + tx.children.last, ); }); - test('nested transaction adds spans', () async { - final sut = fixture.sut; - - await sut.transaction(() async { - await sut.into(sut.todoItems).insert( - TodoItemsCompanion.insert( - title: 'first transaction insert', - content: 'test', - ), - ); - await sut.transaction(() async { - await fixture.sut.delete(fixture.sut.todoItems).go(); - }); - }); + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.runInsert(any, any)) + .thenAnswer((_) => Future.value(1)); + when(queryExecutor.runUpdate(any, any)).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - // 5 spans = 1 db open + 2 tx + 1 insert + 1 delete - expect(fixture.tracer.children.length, 5); + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); - final outerTxSpan = fixture.tracer.children[1]; - final insertSpan = fixture.tracer.children[2]; - final innerTxSpan = fixture.tracer.children[3]; - final deleteSpan = fixture.tracer.children[4]; + final tx = startTransaction(); + try { + await insertRow(db); + await updateRow(db); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } - // Verify parent relationships - expect(outerTxSpan.context.parentSpanId, fixture.tracer.context.spanId); - expect(insertSpan.context.parentSpanId, outerTxSpan.context.spanId); - expect(innerTxSpan.context.parentSpanId, outerTxSpan.context.spanId); - expect(deleteSpan.context.parentSpanId, innerTxSpan.context.spanId); + verifyErrorSpan( + expectedUpdateStatement, + exception, + tx.children.last, + ); }); + }); - test('batch adds span', () async { - final sut = fixture.sut; - - await insertIntoBatch(sut); + group('delete operations', () { + late Fixture fixture; - verifySpan( - SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), - fixture.getCreatedSpan(), - operation: SentrySpanOperations.dbSqlBatch, - ); + setUp(() async { + fixture = Fixture(); + await fixture.sentryInit(); }); - test('close adds span', () async { - final sut = fixture.sut; + test('successful adds span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - await sut.close(); + final tx = startTransaction(); + await insertRow(db); + await db.delete(db.todoItems).go(); verifySpan( - SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), - fixture.getCreatedSpan(), - operation: SentrySpanOperations.dbClose, + expectedDeleteStatement, + tx.children.last, ); }); - test('open adds span', () async { - final sut = fixture.sut; + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.runInsert(any, any)) + .thenAnswer((_) => Future.value(1)); + when(queryExecutor.runDelete(any, any)).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - // SentryDriftDatabase is by default lazily opened by default so it won't - // create a span until it is actually used. - await sut.select(sut.todoItems).get(); + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); - verifySpan( - SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), - fixture.getCreatedSpanByDescription( - SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), - ), - operation: SentrySpanOperations.dbOpen, + final tx = startTransaction(); + try { + await insertRow(db); + await db.delete(db.todoItems).go(); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } + + verifyErrorSpan( + expectedDeleteStatement, + exception, + tx.children.last, ); }); }); - group('does not add span', () { + group('custom query operations', () { late Fixture fixture; setUp(() async { fixture = Fixture(); - - when(fixture.mockHub.options).thenReturn(fixture.options); - when(fixture.mockHub.getSpan()).thenReturn(fixture.tracer); - - await fixture.setUp(); + await fixture.sentryInit(); }); - tearDown(() async { - await fixture.tearDown(); - }); + test('successful adds span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - test('disabled tracing does not create spans', () async { - HubAdapter().options.tracesSampleRate = null; - HubAdapter().options.tracesSampler = null; - await fixture.setUp(useRealHub: true); - final sut = fixture.sut; - expect(fixture.tracer.children, isEmpty); + final tx = startTransaction(); + await db.customStatement(expectedSelectStatement); - await insertRow(sut); - - expect(fixture.tracer.children, isEmpty); + verifySpan( + expectedSelectStatement, + tx.children.last, + ); }); - test('does not add open span if db is not used', () async { - fixture.sut; - - expect(fixture.tracer.children.isEmpty, true); - }); + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.runCustom(any, any)).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - test('batch does not add span for failed operations', () async { - final sut = fixture.sut; + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); + final tx = startTransaction(); try { - await sut.batch((batch) async { - await insertRow(sut, withError: true); - await insertRow(sut); - }); - } catch (_) {} + await db.customStatement(expectedSelectStatement); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } - expect(fixture.tracer.children.isEmpty, true); + verifyErrorSpan( + expectedSelectStatement, + exception, + tx.children.last, + ); }); }); - group('adds error span', () { + group('transaction operations', () { late Fixture fixture; setUp(() async { fixture = Fixture(); - - when(fixture.mockHub.options).thenReturn(fixture.options); - when(fixture.mockHub.getSpan()).thenReturn(fixture.tracer); - when(fixture.mockLazyDatabase.ensureOpen(any)) - .thenAnswer((_) => Future.value(true)); - when(fixture.mockLazyDatabase.dialect).thenReturn(SqlDialect.sqlite); + await fixture.sentryInit(); }); - tearDown(() async { - // catch errors because we purposefully throw a close in one of the tests - try { - await fixture.tearDown(); - } catch (_) {} + // already tests nesting + test('commit successful adds spans', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); + + final tx = startTransaction(); + await db.transaction(() async { + await db.into(db.todoItems).insert( + TodoItemsCompanion.insert( + title: 'first transaction insert', + content: 'test', + ), + ); + await db.transaction(() async { + await db.delete(db.todoItems).go(); + }); + }); + + // 5 spans = 1 db open + 2 tx + 1 insert + 1 delete + expect(tx.children.length, 5); + + final outerTxSpan = tx.children[1]; + final insertSpan = tx.children[2]; + final innerTxSpan = tx.children[3]; + final deleteSpan = tx.children[4]; + + // Verify parent relationships + expect(outerTxSpan.context.parentSpanId, tx.context.spanId); + expect(insertSpan.context.parentSpanId, outerTxSpan.context.spanId); + expect(innerTxSpan.context.parentSpanId, outerTxSpan.context.spanId); + expect(deleteSpan.context.parentSpanId, innerTxSpan.context.spanId); }); - test('throwing runInsert throws error span', () async { - await fixture.setUp(injectMock: true); + test('successful commit adds insert spans', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - when(fixture.mockLazyDatabase.runInsert(any, any)) - .thenThrow(fixture.exception); + final tx = startTransaction(); + await db.transaction(() async { + await insertRow(db); + await insertRow(db); + }); - try { - await insertRow(fixture.sut); - } catch (exception) { - expect(exception, fixture.exception); - } + final insertSpanCount = tx.children + .where( + (element) => element.context.description == expectedInsertStatement, + ) + .length; + expect(insertSpanCount, 2); - verifyErrorSpan( + verifySpan( expectedInsertStatement, - fixture.exception, - fixture.getCreatedSpan(), + tx.children.last, + ); + + verifySpan( + SentrySpanDescriptions.dbTransaction, + tx.children[1], + operation: SentrySpanOperations.dbSqlTransaction, ); }); - test('throwing runUpdate throws error span', () async { - await fixture.setUp(injectMock: true); + test('successful commit adds update spans', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - when(fixture.mockLazyDatabase.runUpdate(any, any)) - .thenThrow(fixture.exception); - when(fixture.mockLazyDatabase.ensureOpen(any)) - .thenAnswer((_) => Future.value(true)); + final tx = startTransaction(); + await db.transaction(() async { + await insertRow(db); + await updateRow(db); + }); - try { - await updateRow(fixture.sut); - } catch (exception) { - expect(exception, fixture.exception); - } + final insertSpanCount = tx.children + .where( + (element) => element.context.description == expectedInsertStatement, + ) + .length; + expect(insertSpanCount, 1); - verifyErrorSpan( + final updateSpanCount = tx.children + .where( + (element) => element.context.description == expectedInsertStatement, + ) + .length; + expect(updateSpanCount, 1); + + verifySpan( expectedUpdateStatement, - fixture.exception, - fixture.getCreatedSpan(), + tx.children.last, + ); + + verifySpan( + SentrySpanDescriptions.dbTransaction, + tx.children[1], + operation: SentrySpanOperations.dbSqlTransaction, ); }); - test('throwing runCustom throws error span', () async { - await fixture.setUp(injectMock: true); + test('successful commit adds delete spans', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - when(fixture.mockLazyDatabase.runCustom(any, any)) - .thenThrow(fixture.exception); + final tx = startTransaction(); + await db.transaction(() async { + await insertRow(db); + await db.delete(db.todoItems).go(); + }); - try { - await fixture.sut.customStatement('SELECT * FROM todo_items'); - } catch (exception) { - expect(exception, fixture.exception); - } + final insertSpanCount = tx.children + .where( + (element) => element.context.description == expectedInsertStatement, + ) + .length; + expect(insertSpanCount, 1); - verifyErrorSpan( - expectedSelectStatement, - fixture.exception, - fixture.getCreatedSpan(), + final deleteSpanCount = tx.children + .where( + (element) => element.context.description == expectedDeleteStatement, + ) + .length; + expect(deleteSpanCount, 1); + + verifySpan( + expectedDeleteStatement, + tx.children.last, + ); + + verifySpan( + SentrySpanDescriptions.dbTransaction, + tx.children[1], + operation: SentrySpanOperations.dbSqlTransaction, ); }); - test('throwing transaction throws error span', () async { - final mockTransactionExecutor = MockTransactionExecutor(); - when(mockTransactionExecutor.beginTransaction()) - .thenThrow(fixture.exception); - when(mockTransactionExecutor.ensureOpen(any)) - .thenAnswer((_) => Future.value(true)); + test('successful commit adds custom query spans', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - await fixture.setUp(customExecutor: mockTransactionExecutor); + final tx = startTransaction(); + await db.transaction(() async { + await db.customStatement(expectedSelectStatement); + }); - try { - await fixture.sut.transaction(() async { - await insertRow(fixture.sut); - }); - } catch (exception) { - expect(exception, fixture.exception); - } + final customSpanCount = tx.children + .where( + (element) => element.context.description == expectedSelectStatement, + ) + .length; + expect(customSpanCount, 1); - verifyErrorSpan( + verifySpan( + expectedSelectStatement, + tx.children.last, + ); + + verifySpan( SentrySpanDescriptions.dbTransaction, - fixture.exception, - fixture.getCreatedSpan(), + tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, ); }); - test('throwing batch throws error span in transaction', () async { - await fixture.setUp(injectMock: true); - - final mockTransactionExecutor = MockTransactionExecutor(); - when(mockTransactionExecutor.ensureOpen(any)) - .thenAnswer((_) => Future.value(true)); - when(fixture.mockLazyDatabase.beginTransaction()) - .thenReturn(mockTransactionExecutor); - when(mockTransactionExecutor.runBatched(any)) - .thenThrow(fixture.exception); + test('successful commit adds batch spans', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - await expectLater( - () async => await insertIntoBatch(fixture.sut), - throwsException, - ); + final tx = startTransaction(); + await db.transaction(() async { + await insertIntoBatch(db); + }); - // errored batch - verifyErrorSpan( + verifySpan( SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), - fixture.exception, - fixture.getCreatedSpanByDescription( - SentrySpanDescriptions.dbBatch( - statements: [expectedInsertStatement], - ), - ), + tx.children.last, operation: SentrySpanOperations.dbSqlBatch, ); + }); + + test('batch creates transaction span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); + + final tx = startTransaction(); + await insertIntoBatch(db); - // // aborted transaction verifySpan( SentrySpanDescriptions.dbTransaction, - fixture - .getCreatedSpanByDescription(SentrySpanDescriptions.dbTransaction), + tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, - status: SpanStatus.aborted(), + ); + + verifySpan( + SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), + tx.children.last, + operation: SentrySpanOperations.dbSqlBatch, ); }); - test('throwing close throws error span', () async { - when(fixture.mockLazyDatabase.close()).thenThrow(fixture.exception); - when(fixture.mockLazyDatabase.runInsert(any, any)) - .thenAnswer((_) => Future.value(1)); + test('rollback case adds aborted span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - await fixture.setUp(injectMock: true); + final tx = startTransaction(); + await insertRow(db); + await insertRow(db); try { - await insertRow(fixture.sut); - await fixture.sut.close(); - } catch (exception) { - expect(exception, fixture.exception); - } + await db.transaction(() async { + await insertRow(db, withError: true); + }); + } catch (_) {} - verifyErrorSpan( - SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), - fixture.exception, - fixture.getCreatedSpanByDescription( - SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), - ), - operation: SentrySpanOperations.dbClose, + final spans = + tx.children.where((child) => child.status == SpanStatus.aborted()); + expect(spans.length, 1); + final abortedSpan = spans.first; + + expect(sut.spanHelper.transactionStack, isEmpty); + verifySpan( + SentrySpanDescriptions.dbTransaction, + abortedSpan, + status: SpanStatus.aborted(), + operation: SentrySpanOperations.dbSqlTransaction, ); }); - test('throwing ensureOpen throws error span', () async { - await fixture.setUp(injectMock: true); - - when(fixture.mockLazyDatabase.ensureOpen(any)) - .thenThrow(fixture.exception); + test('batch does not add span for failed operations', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); + final tx = startTransaction(); try { - await fixture.sut.select(fixture.sut.todoItems).get(); - } catch (exception) { - expect(exception, fixture.exception); - } + await db.batch((batch) async { + await insertRow(db, withError: true); + await insertRow(db); + }); + } catch (_) {} - verifyErrorSpan( - SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), - fixture.exception, - fixture.getCreatedSpanByDescription( - SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), - ), - operation: SentrySpanOperations.dbOpen, - ); + expect(tx.children.isEmpty, true); }); - test('throwing runDelete throws error span', () async { - await fixture.setUp(injectMock: true); + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.beginTransaction()).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); - when(fixture.mockLazyDatabase.runDelete(any, any)) - .thenThrow(fixture.exception); + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); + final tx = startTransaction(); try { - await fixture.sut.delete(fixture.sut.todoItems).go(); - } catch (exception) { - expect(exception, fixture.exception); + await db.transaction(() async { + await insertRow(db); + }); + } catch (e) { + // making sure the thrown exception doesn't fail the test } + // when beginTransaction errored, we don't add it to the stack + expect(sut.spanHelper.transactionStack, isEmpty); verifyErrorSpan( - expectedDeleteStatement, - fixture.exception, - fixture.getCreatedSpan(), + operation: SentrySpanOperations.dbSqlTransaction, + SentrySpanDescriptions.dbTransaction, + exception, + tx.children.last, ); }); }); group('integrations', () { late Fixture fixture; - late Hub hub; setUp(() async { fixture = Fixture(); - await fixture.setUp(useRealHub: true); - hub = HubAdapter(); - }); + await fixture.sentryInit(); - tearDown(() async { - await fixture.tearDown(); + // init the interceptor so the integrations are added + fixture.getSut(); }); test('adds integration', () { expect( - hub.options.sdk.integrations.contains(drift_constants.integrationName), + fixture.options.sdk.integrations + .contains(drift_constants.integrationName), true, ); }); test('adds package', () { expect( - hub.options.sdk.packages.any( + fixture.options.sdk.packages.any( (element) => element.name == packageName && element.version == sdkVersion, ), @@ -711,61 +778,19 @@ void main() { } class Fixture { - final options = defaultTestOptions(); - final mockHub = MockHub(); - static final dbName = 'people-drift-impl'; - final exception = Exception('fixture-exception'); - final _context = SentryTransactionContext('name', 'operation'); - late final tracer = SentryTracer(_context, mockHub); - late AppDatabase sut; - final mockLazyDatabase = MockLazyDatabase(); - - Future setUp({ - bool injectMock = false, - bool useRealHub = false, - QueryExecutor? customExecutor, - }) async { - sut = AppDatabase( - openConnection( - useRealHub: useRealHub, - injectMock: injectMock, - customExecutor: customExecutor, - ), - ); - driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; - } - - Future tearDown() async { - await sut.close(); - } + static final dbName = 'test_db_name'; + final options = defaultTestOptions()..tracesSampleRate = 1.0; - SentrySpan? getCreatedSpan() { - return tracer.children.last; - } - - SentrySpan? getCreatedSpanByDescription(String description) { - return tracer.children - .firstWhere((element) => element.context.description == description); + Future sentryInit() { + return Sentry.init( + (options) {}, + options: options, + ); } - QueryExecutor openConnection({ - bool useRealHub = false, - bool injectMock = false, - QueryExecutor? customExecutor, - }) { - final hub = useRealHub ? null : mockHub; - if (customExecutor != null) { - return customExecutor.interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: hub), - ); - } else if (injectMock) { - return mockLazyDatabase.interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: hub), - ); - } else { - return NativeDatabase.memory().interceptWith( - SentryQueryInterceptor(databaseName: dbName, hub: hub), - ); - } + SentryQueryInterceptor getSut({Hub? hub}) { + hub = hub ?? HubAdapter(); + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + return SentryQueryInterceptor(databaseName: dbName, hub: hub); } } From 64285d35887a03a66d2ee2f570c3bfe944b135bd Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 16:02:57 +0100 Subject: [PATCH 25/29] add test --- drift/test/mocks/mocks.mocks.dart | 601 ++++++++++++++---------------- drift/test/sentry_drift_test.dart | 20 +- 2 files changed, 292 insertions(+), 329 deletions(-) diff --git a/drift/test/mocks/mocks.mocks.dart b/drift/test/mocks/mocks.mocks.dart index ae034e47b1..2001b49951 100644 --- a/drift/test/mocks/mocks.mocks.dart +++ b/drift/test/mocks/mocks.mocks.dart @@ -27,38 +27,38 @@ import 'package:sentry/src/profiling.dart' as _i4; class _FakeSentryOptions_0 extends _i1.SmartFake implements _i2.SentryOptions { _FakeSentryOptions_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeSentryId_1 extends _i1.SmartFake implements _i2.SentryId { _FakeSentryId_1(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeScope_2 extends _i1.SmartFake implements _i2.Scope { _FakeScope_2(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeHub_3 extends _i1.SmartFake implements _i2.Hub { _FakeHub_3(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeISentrySpan_4 extends _i1.SmartFake implements _i2.ISentrySpan { _FakeISentrySpan_4(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeQueryExecutor_5 extends _i1.SmartFake implements _i3.QueryExecutor { _FakeQueryExecutor_5(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } class _FakeTransactionExecutor_6 extends _i1.SmartFake implements _i3.TransactionExecutor { _FakeTransactionExecutor_6(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); + : super(parent, parentInvocation); } /// A class which mocks [Hub]. @@ -70,15 +70,13 @@ class MockHub extends _i1.Mock implements _i2.Hub { } @override - _i2.SentryOptions get options => - (super.noSuchMethod( - Invocation.getter(#options), - returnValue: _FakeSentryOptions_0( - this, - Invocation.getter(#options), - ), - ) - as _i2.SentryOptions); + _i2.SentryOptions get options => (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeSentryOptions_0( + this, + Invocation.getter(#options), + ), + ) as _i2.SentryOptions); @override bool get isEnabled => @@ -86,26 +84,22 @@ class MockHub extends _i1.Mock implements _i2.Hub { as bool); @override - _i2.SentryId get lastEventId => - (super.noSuchMethod( - Invocation.getter(#lastEventId), - returnValue: _FakeSentryId_1(this, Invocation.getter(#lastEventId)), - ) - as _i2.SentryId); + _i2.SentryId get lastEventId => (super.noSuchMethod( + Invocation.getter(#lastEventId), + returnValue: _FakeSentryId_1(this, Invocation.getter(#lastEventId)), + ) as _i2.SentryId); @override - _i2.Scope get scope => - (super.noSuchMethod( - Invocation.getter(#scope), - returnValue: _FakeScope_2(this, Invocation.getter(#scope)), - ) - as _i2.Scope); + _i2.Scope get scope => (super.noSuchMethod( + Invocation.getter(#scope), + returnValue: _FakeScope_2(this, Invocation.getter(#scope)), + ) as _i2.Scope); @override set profilerFactory(_i4.SentryProfilerFactory? value) => super.noSuchMethod( - Invocation.setter(#profilerFactory, value), - returnValueForMissingStub: null, - ); + Invocation.setter(#profilerFactory, value), + returnValueForMissingStub: null, + ); @override _i5.Future<_i2.SentryId> captureEvent( @@ -115,23 +109,22 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( + Invocation.method( + #captureEvent, + [event], + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, Invocation.method( #captureEvent, [event], {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, - Invocation.method( - #captureEvent, - [event], - {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, - ), - ), - ), - ) - as _i5.Future<_i2.SentryId>); + ), + ), + ) as _i5.Future<_i2.SentryId>); @override _i5.Future<_i2.SentryId> captureException( @@ -141,23 +134,22 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( + Invocation.method( + #captureException, + [throwable], + {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, Invocation.method( #captureException, [throwable], {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, - Invocation.method( - #captureException, - [throwable], - {#stackTrace: stackTrace, #hint: hint, #withScope: withScope}, - ), - ), - ), - ) - as _i5.Future<_i2.SentryId>); + ), + ), + ) as _i5.Future<_i2.SentryId>); @override _i5.Future<_i2.SentryId> captureMessage( @@ -169,6 +161,20 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( + Invocation.method( + #captureMessage, + [message], + { + #level: level, + #template: template, + #params: params, + #hint: hint, + #withScope: withScope, + }, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, Invocation.method( #captureMessage, [message], @@ -180,33 +186,17 @@ class MockHub extends _i1.Mock implements _i2.Hub { #withScope: withScope, }, ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, - Invocation.method( - #captureMessage, - [message], - { - #level: level, - #template: template, - #params: params, - #hint: hint, - #withScope: withScope, - }, - ), - ), - ), - ) - as _i5.Future<_i2.SentryId>); + ), + ), + ) as _i5.Future<_i2.SentryId>); @override _i5.Future captureUserFeedback(_i2.SentryUserFeedback? userFeedback) => (super.noSuchMethod( - Invocation.method(#captureUserFeedback, [userFeedback]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + Invocation.method(#captureUserFeedback, [userFeedback]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future<_i2.SentryId> captureFeedback( @@ -215,55 +205,49 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.ScopeCallback? withScope, }) => (super.noSuchMethod( + Invocation.method( + #captureFeedback, + [feedback], + {#hint: hint, #withScope: withScope}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, Invocation.method( #captureFeedback, [feedback], {#hint: hint, #withScope: withScope}, ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, - Invocation.method( - #captureFeedback, - [feedback], - {#hint: hint, #withScope: withScope}, - ), - ), - ), - ) - as _i5.Future<_i2.SentryId>); + ), + ), + ) as _i5.Future<_i2.SentryId>); @override _i5.Future addBreadcrumb(_i2.Breadcrumb? crumb, {_i2.Hint? hint}) => (super.noSuchMethod( - Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override void bindClient(_i2.SentryClient? client) => super.noSuchMethod( - Invocation.method(#bindClient, [client]), - returnValueForMissingStub: null, - ); + Invocation.method(#bindClient, [client]), + returnValueForMissingStub: null, + ); @override - _i2.Hub clone() => - (super.noSuchMethod( - Invocation.method(#clone, []), - returnValue: _FakeHub_3(this, Invocation.method(#clone, [])), - ) - as _i2.Hub); + _i2.Hub clone() => (super.noSuchMethod( + Invocation.method(#clone, []), + returnValue: _FakeHub_3(this, Invocation.method(#clone, [])), + ) as _i2.Hub); @override - _i5.Future close() => - (super.noSuchMethod( - Invocation.method(#close, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + _i5.Future close() => (super.noSuchMethod( + Invocation.method(#close, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.FutureOr configureScope(_i2.ScopeCallback? callback) => @@ -284,39 +268,38 @@ class MockHub extends _i1.Mock implements _i2.Hub { Map? customSamplingContext, }) => (super.noSuchMethod( - Invocation.method( - #startTransaction, - [name, operation], - { - #description: description, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - #customSamplingContext: customSamplingContext, - }, - ), - returnValue: _FakeISentrySpan_4( - this, - Invocation.method( - #startTransaction, - [name, operation], - { - #description: description, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - #customSamplingContext: customSamplingContext, - }, - ), - ), - ) - as _i2.ISentrySpan); + Invocation.method( + #startTransaction, + [name, operation], + { + #description: description, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + #customSamplingContext: customSamplingContext, + }, + ), + returnValue: _FakeISentrySpan_4( + this, + Invocation.method( + #startTransaction, + [name, operation], + { + #description: description, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + #customSamplingContext: customSamplingContext, + }, + ), + ), + ) as _i2.ISentrySpan); @override _i2.ISentrySpan startTransactionWithContext( @@ -330,37 +313,36 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.OnTransactionFinish? onFinish, }) => (super.noSuchMethod( - Invocation.method( - #startTransactionWithContext, - [transactionContext], - { - #customSamplingContext: customSamplingContext, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - }, - ), - returnValue: _FakeISentrySpan_4( - this, - Invocation.method( - #startTransactionWithContext, - [transactionContext], - { - #customSamplingContext: customSamplingContext, - #startTimestamp: startTimestamp, - #bindToScope: bindToScope, - #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter, - #trimEnd: trimEnd, - #onFinish: onFinish, - }, - ), - ), - ) - as _i2.ISentrySpan); + Invocation.method( + #startTransactionWithContext, + [transactionContext], + { + #customSamplingContext: customSamplingContext, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + }, + ), + returnValue: _FakeISentrySpan_4( + this, + Invocation.method( + #startTransactionWithContext, + [transactionContext], + { + #customSamplingContext: customSamplingContext, + #startTimestamp: startTimestamp, + #bindToScope: bindToScope, + #waitForChildren: waitForChildren, + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, + #onFinish: onFinish, + }, + ), + ), + ) as _i2.ISentrySpan); @override _i5.Future<_i2.SentryId> captureTransaction( @@ -369,33 +351,33 @@ class MockHub extends _i1.Mock implements _i2.Hub { _i2.Hint? hint, }) => (super.noSuchMethod( + Invocation.method( + #captureTransaction, + [transaction], + {#traceContext: traceContext, #hint: hint}, + ), + returnValue: _i5.Future<_i2.SentryId>.value( + _FakeSentryId_1( + this, Invocation.method( #captureTransaction, [transaction], {#traceContext: traceContext, #hint: hint}, ), - returnValue: _i5.Future<_i2.SentryId>.value( - _FakeSentryId_1( - this, - Invocation.method( - #captureTransaction, - [transaction], - {#traceContext: traceContext, #hint: hint}, - ), - ), - ), - ) - as _i5.Future<_i2.SentryId>); + ), + ), + ) as _i5.Future<_i2.SentryId>); @override void setSpanContext( dynamic throwable, _i2.ISentrySpan? span, String? transaction, - ) => super.noSuchMethod( - Invocation.method(#setSpanContext, [throwable, span, transaction]), - returnValueForMissingStub: null, - ); + ) => + super.noSuchMethod( + Invocation.method(#setSpanContext, [throwable, span, transaction]), + returnValueForMissingStub: null, + ); } /// A class which mocks [LazyDatabase]. @@ -407,87 +389,73 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { } @override - _i6.DatabaseOpener get opener => - (super.noSuchMethod( - Invocation.getter(#opener), - returnValue: - () => _i5.Future<_i3.QueryExecutor>.value( - _FakeQueryExecutor_5(this, Invocation.getter(#opener)), - ), - ) - as _i6.DatabaseOpener); + _i6.DatabaseOpener get opener => (super.noSuchMethod( + Invocation.getter(#opener), + returnValue: () => _i5.Future<_i3.QueryExecutor>.value( + _FakeQueryExecutor_5(this, Invocation.getter(#opener)), + ), + ) as _i6.DatabaseOpener); @override - _i3.SqlDialect get dialect => - (super.noSuchMethod( - Invocation.getter(#dialect), - returnValue: _i3.SqlDialect.sqlite, - ) - as _i3.SqlDialect); + _i3.SqlDialect get dialect => (super.noSuchMethod( + Invocation.getter(#dialect), + returnValue: _i3.SqlDialect.sqlite, + ) as _i3.SqlDialect); @override - _i3.QueryExecutor beginExclusive() => - (super.noSuchMethod( - Invocation.method(#beginExclusive, []), - returnValue: _FakeQueryExecutor_5( - this, - Invocation.method(#beginExclusive, []), - ), - ) - as _i3.QueryExecutor); + _i3.QueryExecutor beginExclusive() => (super.noSuchMethod( + Invocation.method(#beginExclusive, []), + returnValue: _FakeQueryExecutor_5( + this, + Invocation.method(#beginExclusive, []), + ), + ) as _i3.QueryExecutor); @override - _i3.TransactionExecutor beginTransaction() => - (super.noSuchMethod( - Invocation.method(#beginTransaction, []), - returnValue: _FakeTransactionExecutor_6( - this, - Invocation.method(#beginTransaction, []), - ), - ) - as _i3.TransactionExecutor); + _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( + Invocation.method(#beginTransaction, []), + returnValue: _FakeTransactionExecutor_6( + this, + Invocation.method(#beginTransaction, []), + ), + ) as _i3.TransactionExecutor); @override _i5.Future ensureOpen(_i3.QueryExecutorUser? user) => (super.noSuchMethod( - Invocation.method(#ensureOpen, [user]), - returnValue: _i5.Future.value(false), - ) - as _i5.Future); + Invocation.method(#ensureOpen, [user]), + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override _i5.Future runBatched(_i3.BatchedStatements? statements) => (super.noSuchMethod( - Invocation.method(#runBatched, [statements]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + Invocation.method(#runBatched, [statements]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future runCustom(String? statement, [List? args]) => (super.noSuchMethod( - Invocation.method(#runCustom, [statement, args]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + Invocation.method(#runCustom, [statement, args]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future runDelete(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runDelete, [statement, args]), - returnValue: _i5.Future.value(0), - ) - as _i5.Future); + Invocation.method(#runDelete, [statement, args]), + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override _i5.Future runInsert(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runInsert, [statement, args]), - returnValue: _i5.Future.value(0), - ) - as _i5.Future); + Invocation.method(#runInsert, [statement, args]), + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override _i5.Future>> runSelect( @@ -495,29 +463,25 @@ class MockLazyDatabase extends _i1.Mock implements _i6.LazyDatabase { List? args, ) => (super.noSuchMethod( - Invocation.method(#runSelect, [statement, args]), - returnValue: _i5.Future>>.value( - >[], - ), - ) - as _i5.Future>>); + Invocation.method(#runSelect, [statement, args]), + returnValue: _i5.Future>>.value( + >[], + ), + ) as _i5.Future>>); @override _i5.Future runUpdate(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runUpdate, [statement, args]), - returnValue: _i5.Future.value(0), - ) - as _i5.Future); + Invocation.method(#runUpdate, [statement, args]), + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override - _i5.Future close() => - (super.noSuchMethod( - Invocation.method(#close, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + _i5.Future close() => (super.noSuchMethod( + Invocation.method(#close, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [QueryExecutor]. @@ -529,20 +493,17 @@ class MockQueryExecutor extends _i1.Mock implements _i3.QueryExecutor { } @override - _i3.SqlDialect get dialect => - (super.noSuchMethod( - Invocation.getter(#dialect), - returnValue: _i3.SqlDialect.sqlite, - ) - as _i3.SqlDialect); + _i3.SqlDialect get dialect => (super.noSuchMethod( + Invocation.getter(#dialect), + returnValue: _i3.SqlDialect.sqlite, + ) as _i3.SqlDialect); @override _i5.Future ensureOpen(_i3.QueryExecutorUser? user) => (super.noSuchMethod( - Invocation.method(#ensureOpen, [user]), - returnValue: _i5.Future.value(false), - ) - as _i5.Future); + Invocation.method(#ensureOpen, [user]), + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override _i5.Future>> runSelect( @@ -550,83 +511,71 @@ class MockQueryExecutor extends _i1.Mock implements _i3.QueryExecutor { List? args, ) => (super.noSuchMethod( - Invocation.method(#runSelect, [statement, args]), - returnValue: _i5.Future>>.value( - >[], - ), - ) - as _i5.Future>>); + Invocation.method(#runSelect, [statement, args]), + returnValue: _i5.Future>>.value( + >[], + ), + ) as _i5.Future>>); @override _i5.Future runInsert(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runInsert, [statement, args]), - returnValue: _i5.Future.value(0), - ) - as _i5.Future); + Invocation.method(#runInsert, [statement, args]), + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override _i5.Future runUpdate(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runUpdate, [statement, args]), - returnValue: _i5.Future.value(0), - ) - as _i5.Future); + Invocation.method(#runUpdate, [statement, args]), + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override _i5.Future runDelete(String? statement, List? args) => (super.noSuchMethod( - Invocation.method(#runDelete, [statement, args]), - returnValue: _i5.Future.value(0), - ) - as _i5.Future); + Invocation.method(#runDelete, [statement, args]), + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override _i5.Future runCustom(String? statement, [List? args]) => (super.noSuchMethod( - Invocation.method(#runCustom, [statement, args]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + Invocation.method(#runCustom, [statement, args]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override _i5.Future runBatched(_i3.BatchedStatements? statements) => (super.noSuchMethod( - Invocation.method(#runBatched, [statements]), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + Invocation.method(#runBatched, [statements]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override - _i3.TransactionExecutor beginTransaction() => - (super.noSuchMethod( - Invocation.method(#beginTransaction, []), - returnValue: _FakeTransactionExecutor_6( - this, - Invocation.method(#beginTransaction, []), - ), - ) - as _i3.TransactionExecutor); + _i3.TransactionExecutor beginTransaction() => (super.noSuchMethod( + Invocation.method(#beginTransaction, []), + returnValue: _FakeTransactionExecutor_6( + this, + Invocation.method(#beginTransaction, []), + ), + ) as _i3.TransactionExecutor); @override - _i3.QueryExecutor beginExclusive() => - (super.noSuchMethod( - Invocation.method(#beginExclusive, []), - returnValue: _FakeQueryExecutor_5( - this, - Invocation.method(#beginExclusive, []), - ), - ) - as _i3.QueryExecutor); + _i3.QueryExecutor beginExclusive() => (super.noSuchMethod( + Invocation.method(#beginExclusive, []), + returnValue: _FakeQueryExecutor_5( + this, + Invocation.method(#beginExclusive, []), + ), + ) as _i3.QueryExecutor); @override - _i5.Future close() => - (super.noSuchMethod( - Invocation.method(#close, []), - returnValue: _i5.Future.value(), - returnValueForMissingStub: _i5.Future.value(), - ) - as _i5.Future); + _i5.Future close() => (super.noSuchMethod( + Invocation.method(#close, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 9e2bc594b0..ab4912d865 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -78,16 +78,16 @@ void main() { expect(span?.throwable, exception); } - Future insertRow(AppDatabase sut, {bool withError = false}) { + Future insertRow(AppDatabase db, {bool withError = false}) { if (withError) { - return sut.into(sut.todoItems).insert( + return db.into(db.todoItems).insert( TodoItemsCompanion.insert( title: '', content: '', ), ); } else { - return sut.into(sut.todoItems).insert( + return db.into(db.todoItems).insert( TodoItemsCompanion.insert( title: 'todo: finish drift setup', content: 'We can now write queries and define our own tables.', @@ -475,6 +475,20 @@ void main() { await fixture.sentryInit(); }); + test('without transaction, spans are added to active scope span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); + + final tx = startTransaction(); + await insertRow(db); + + expect(tx.children.length, 2); + + final insertSpan = tx.children.last; + expect(insertSpan.context.parentSpanId, tx.context.spanId); + expect(sut.spanHelper.transactionStack, isEmpty); + }); + // already tests nesting test('commit successful adds spans', () async { final sut = fixture.getSut(); From 9d7649295bfe1975a71a56f0217ddd079e4f96f9 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 16:03:33 +0100 Subject: [PATCH 26/29] remove spotlight --- drift/example/example.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/drift/example/example.dart b/drift/example/example.dart index a24297dd47..02a4b09998 100644 --- a/drift/example/example.dart +++ b/drift/example/example.dart @@ -15,7 +15,6 @@ Future main() async { options.dsn = dsn; options.tracesSampleRate = 1.0; options.debug = true; - options.spotlight = Spotlight(enabled: true); }, appRunner: runApp, // Init your App. ); From 6e6b02de972b36f0b279940086f27ecb68096904 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 16:07:52 +0100 Subject: [PATCH 27/29] cleanup test file --- drift/test/sentry_drift_test.dart | 441 +++++++++++++----------------- 1 file changed, 195 insertions(+), 246 deletions(-) diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index ab4912d865..50b1867c29 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -28,135 +28,25 @@ void main() { final expectedSelectStatement = 'SELECT * FROM todo_items'; final expectedDeleteStatement = 'DELETE FROM "todo_items";'; - void verifySpan( - String description, - SentrySpan? span, { - String? operation, - SpanStatus? status, - }) { - status ??= SpanStatus.ok(); - expect( - span?.context.operation, - operation ?? SentrySpanOperations.dbSqlQuery, - ); - expect(span?.context.description, description); - expect(span?.status, status); - expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); - expect( - span?.data[SentrySpanData.dbSystemKey], - SentrySpanData.dbSystemSqlite, - ); - expect( - span?.data[SentrySpanData.dbNameKey], - Fixture.dbName, - ); - } + late Fixture fixture; - void verifyErrorSpan( - String description, - Exception exception, - SentrySpan? span, { - String? operation, - SpanStatus? status, - }) { - expect( - span?.context.operation, - operation ?? SentrySpanOperations.dbSqlQuery, - ); - expect(span?.context.description, description); - expect(span?.status, status ?? SpanStatus.internalError()); - expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); - expect( - span?.data[SentrySpanData.dbSystemKey], - SentrySpanData.dbSystemSqlite, - ); - expect( - span?.data[SentrySpanData.dbNameKey], - Fixture.dbName, + setUp(() async { + fixture = Fixture(); + await Sentry.init( + (options) {}, + options: fixture.options, ); - - expect(span?.throwable, exception); - } - - Future insertRow(AppDatabase db, {bool withError = false}) { - if (withError) { - return db.into(db.todoItems).insert( - TodoItemsCompanion.insert( - title: '', - content: '', - ), - ); - } else { - return db.into(db.todoItems).insert( - TodoItemsCompanion.insert( - title: 'todo: finish drift setup', - content: 'We can now write queries and define our own tables.', - ), - ); - } - } - - Future insertIntoBatch(AppDatabase sut) { - return sut.batch((batch) { - batch.insertAll(sut.todoItems, [ - TodoItemsCompanion.insert( - title: 'todo: finish drift setup #1', - content: 'We can now write queries and define our own tables.', - ), - TodoItemsCompanion.insert( - title: 'todo: finish drift setup #2', - content: 'We can now write queries and define our own tables.', - ), - ]); - }); - } - - Future updateRow(AppDatabase sut, {bool withError = false}) { - if (withError) { - return (sut.update(sut.todoItems) - ..where((tbl) => tbl.title.equals('doesnt exist'))) - .write( - TodoItemsCompanion( - title: Value('after update'), - content: Value('We can now write queries and define our own tables.'), - ), - ); - } else { - return (sut.update(sut.todoItems) - ..where((tbl) => tbl.title.equals('todo: finish drift setup'))) - .write( - TodoItemsCompanion( - title: Value('after update'), - content: Value('We can now write queries and define our own tables.'), - ), - ); - } - } - - SentryTracer startTransaction() { - return Sentry.startTransaction('drift', 'test op', bindToScope: true) - as SentryTracer; - } + }); group('open operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await Sentry.init( - (options) {}, - options: fixture.options, - ); - }); - test('successful adds span only once', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); - await insertRow(db); - await insertRow(db); + final tx = _startTransaction(); + await _insertRow(db); + await _insertRow(db); + await _insertRow(db); final openSpans = tx.children.where( (element) => @@ -165,7 +55,7 @@ void main() { ); expect(openSpans.length, 1); - verifySpan( + _verifySpan( operation: SentrySpanOperations.dbOpen, SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), openSpans.first, @@ -183,9 +73,9 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { - await insertRow(db); + await _insertRow(db); } catch (e) { // making sure the thrown exception doesn't fail the test } @@ -197,7 +87,7 @@ void main() { ); expect(openSpans.length, 1); - verifyErrorSpan( + _verifyErrorSpan( operation: SentrySpanOperations.dbOpen, SentrySpanDescriptions.dbOpen(dbName: Fixture.dbName), exception, @@ -207,22 +97,12 @@ void main() { }); group('close operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await Sentry.init( - (options) {}, - options: defaultTestOptions()..tracesSampleRate = 1.0, - ); - }); - test('successful adds close only once', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); + final tx = _startTransaction(); + await _insertRow(db); await db.close(); final closeSpans = tx.children.where( @@ -232,7 +112,7 @@ void main() { ); expect(closeSpans.length, 1); - verifySpan( + _verifySpan( operation: SentrySpanOperations.dbClose, SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), closeSpans.first, @@ -251,9 +131,9 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { - await insertRow(db); + await _insertRow(db); await db.close(); } catch (e) { // making sure the thrown exception doesn't fail the test @@ -266,7 +146,7 @@ void main() { ); expect(closeSpans.length, 1); - verifyErrorSpan( + _verifyErrorSpan( SentrySpanDescriptions.dbClose(dbName: Fixture.dbName), exception, closeSpans.first, @@ -276,21 +156,14 @@ void main() { }); group('insert operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await fixture.sentryInit(); - }); - test('successful adds span', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); + final tx = _startTransaction(); + await _insertRow(db); - verifySpan( + _verifySpan( expectedInsertStatement, tx.children.last, ); @@ -306,14 +179,14 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { - await insertRow(db); + await _insertRow(db); } catch (e) { // making sure the thrown exception doesn't fail the test } - verifyErrorSpan( + _verifyErrorSpan( expectedInsertStatement, exception, tx.children.last, @@ -322,22 +195,15 @@ void main() { }); group('update operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await fixture.sentryInit(); - }); - test('successful adds span', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); - await updateRow(db); + final tx = _startTransaction(); + await _insertRow(db); + await _updateRow(db); - verifySpan( + _verifySpan( expectedUpdateStatement, tx.children.last, ); @@ -355,15 +221,15 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { - await insertRow(db); - await updateRow(db); + await _insertRow(db); + await _updateRow(db); } catch (e) { // making sure the thrown exception doesn't fail the test } - verifyErrorSpan( + _verifyErrorSpan( expectedUpdateStatement, exception, tx.children.last, @@ -372,22 +238,15 @@ void main() { }); group('delete operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await fixture.sentryInit(); - }); - test('successful adds span', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); + final tx = _startTransaction(); + await _insertRow(db); await db.delete(db.todoItems).go(); - verifySpan( + _verifySpan( expectedDeleteStatement, tx.children.last, ); @@ -405,15 +264,15 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { - await insertRow(db); + await _insertRow(db); await db.delete(db.todoItems).go(); } catch (e) { // making sure the thrown exception doesn't fail the test } - verifyErrorSpan( + _verifyErrorSpan( expectedDeleteStatement, exception, tx.children.last, @@ -422,21 +281,14 @@ void main() { }); group('custom query operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await fixture.sentryInit(); - }); - test('successful adds span', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.customStatement(expectedSelectStatement); - verifySpan( + _verifySpan( expectedSelectStatement, tx.children.last, ); @@ -452,14 +304,14 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { await db.customStatement(expectedSelectStatement); } catch (e) { // making sure the thrown exception doesn't fail the test } - verifyErrorSpan( + _verifyErrorSpan( expectedSelectStatement, exception, tx.children.last, @@ -468,19 +320,12 @@ void main() { }); group('transaction operations', () { - late Fixture fixture; - - setUp(() async { - fixture = Fixture(); - await fixture.sentryInit(); - }); - test('without transaction, spans are added to active scope span', () async { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); + final tx = _startTransaction(); + await _insertRow(db); expect(tx.children.length, 2); @@ -494,7 +339,7 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.transaction(() async { await db.into(db.todoItems).insert( TodoItemsCompanion.insert( @@ -526,10 +371,10 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.transaction(() async { - await insertRow(db); - await insertRow(db); + await _insertRow(db); + await _insertRow(db); }); final insertSpanCount = tx.children @@ -539,12 +384,12 @@ void main() { .length; expect(insertSpanCount, 2); - verifySpan( + _verifySpan( expectedInsertStatement, tx.children.last, ); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbTransaction, tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, @@ -555,10 +400,10 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.transaction(() async { - await insertRow(db); - await updateRow(db); + await _insertRow(db); + await _updateRow(db); }); final insertSpanCount = tx.children @@ -575,12 +420,12 @@ void main() { .length; expect(updateSpanCount, 1); - verifySpan( + _verifySpan( expectedUpdateStatement, tx.children.last, ); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbTransaction, tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, @@ -591,9 +436,9 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.transaction(() async { - await insertRow(db); + await _insertRow(db); await db.delete(db.todoItems).go(); }); @@ -611,12 +456,12 @@ void main() { .length; expect(deleteSpanCount, 1); - verifySpan( + _verifySpan( expectedDeleteStatement, tx.children.last, ); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbTransaction, tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, @@ -627,7 +472,7 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.transaction(() async { await db.customStatement(expectedSelectStatement); }); @@ -639,12 +484,12 @@ void main() { .length; expect(customSpanCount, 1); - verifySpan( + _verifySpan( expectedSelectStatement, tx.children.last, ); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbTransaction, tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, @@ -655,12 +500,12 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); await db.transaction(() async { - await insertIntoBatch(db); + await _insertIntoBatch(db); }); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), tx.children.last, operation: SentrySpanOperations.dbSqlBatch, @@ -671,16 +516,16 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertIntoBatch(db); + final tx = _startTransaction(); + await _insertIntoBatch(db); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbTransaction, tx.children[1], operation: SentrySpanOperations.dbSqlTransaction, ); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbBatch(statements: [expectedInsertStatement]), tx.children.last, operation: SentrySpanOperations.dbSqlBatch, @@ -691,13 +536,13 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); - await insertRow(db); - await insertRow(db); + final tx = _startTransaction(); + await _insertRow(db); + await _insertRow(db); try { await db.transaction(() async { - await insertRow(db, withError: true); + await _insertRow(db, withError: true); }); } catch (_) {} @@ -707,7 +552,7 @@ void main() { final abortedSpan = spans.first; expect(sut.spanHelper.transactionStack, isEmpty); - verifySpan( + _verifySpan( SentrySpanDescriptions.dbTransaction, abortedSpan, status: SpanStatus.aborted(), @@ -719,11 +564,11 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { await db.batch((batch) async { - await insertRow(db, withError: true); - await insertRow(db); + await _insertRow(db, withError: true); + await _insertRow(db); }); } catch (_) {} @@ -740,10 +585,10 @@ void main() { final sut = fixture.getSut(); final db = AppDatabase(queryExecutor.interceptWith(sut)); - final tx = startTransaction(); + final tx = _startTransaction(); try { await db.transaction(() async { - await insertRow(db); + await _insertRow(db); }); } catch (e) { // making sure the thrown exception doesn't fail the test @@ -751,7 +596,7 @@ void main() { // when beginTransaction errored, we don't add it to the stack expect(sut.spanHelper.transactionStack, isEmpty); - verifyErrorSpan( + _verifyErrorSpan( operation: SentrySpanOperations.dbSqlTransaction, SentrySpanDescriptions.dbTransaction, exception, @@ -761,12 +606,7 @@ void main() { }); group('integrations', () { - late Fixture fixture; - setUp(() async { - fixture = Fixture(); - await fixture.sentryInit(); - // init the interceptor so the integrations are added fixture.getSut(); }); @@ -802,9 +642,118 @@ class Fixture { ); } - SentryQueryInterceptor getSut({Hub? hub}) { - hub = hub ?? HubAdapter(); + SentryQueryInterceptor getSut() { driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; - return SentryQueryInterceptor(databaseName: dbName, hub: hub); + return SentryQueryInterceptor(databaseName: dbName); + } +} + +void _verifySpan( + String description, + SentrySpan? span, { + String? operation, + SpanStatus? status, +}) { + status ??= SpanStatus.ok(); + expect( + span?.context.operation, + operation ?? SentrySpanOperations.dbSqlQuery, + ); + expect(span?.context.description, description); + expect(span?.status, status); + expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); + expect( + span?.data[SentrySpanData.dbSystemKey], + SentrySpanData.dbSystemSqlite, + ); + expect( + span?.data[SentrySpanData.dbNameKey], + Fixture.dbName, + ); +} + +void _verifyErrorSpan( + String description, + Exception exception, + SentrySpan? span, { + String? operation, + SpanStatus? status, +}) { + expect( + span?.context.operation, + operation ?? SentrySpanOperations.dbSqlQuery, + ); + expect(span?.context.description, description); + expect(span?.status, status ?? SpanStatus.internalError()); + expect(span?.origin, SentryTraceOrigins.autoDbDriftQueryInterceptor); + expect( + span?.data[SentrySpanData.dbSystemKey], + SentrySpanData.dbSystemSqlite, + ); + expect( + span?.data[SentrySpanData.dbNameKey], + Fixture.dbName, + ); + + expect(span?.throwable, exception); +} + +Future _insertRow(AppDatabase db, {bool withError = false}) { + if (withError) { + return db.into(db.todoItems).insert( + TodoItemsCompanion.insert( + title: '', + content: '', + ), + ); + } else { + return db.into(db.todoItems).insert( + TodoItemsCompanion.insert( + title: 'todo: finish drift setup', + content: 'We can now write queries and define our own tables.', + ), + ); + } +} + +Future _insertIntoBatch(AppDatabase sut) { + return sut.batch((batch) { + batch.insertAll(sut.todoItems, [ + TodoItemsCompanion.insert( + title: 'todo: finish drift setup #1', + content: 'We can now write queries and define our own tables.', + ), + TodoItemsCompanion.insert( + title: 'todo: finish drift setup #2', + content: 'We can now write queries and define our own tables.', + ), + ]); + }); +} + +Future _updateRow(AppDatabase sut, {bool withError = false}) { + if (withError) { + return (sut.update(sut.todoItems) + ..where((tbl) => tbl.title.equals('doesnt exist'))) + .write( + TodoItemsCompanion( + title: Value('after update'), + content: Value('We can now write queries and define our own tables.'), + ), + ); + } else { + return (sut.update(sut.todoItems) + ..where((tbl) => tbl.title.equals('todo: finish drift setup'))) + .write( + TodoItemsCompanion( + title: Value('after update'), + content: Value('We can now write queries and define our own tables.'), + ), + ); } } + +SentryTracer _startTransaction() { + return Sentry.startTransaction('drift', 'test op', bindToScope: true) + as SentryTracer; +} From 1646e4d77320ab19e3bcba62a2fd48cda1be9124 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 16:40:20 +0100 Subject: [PATCH 28/29] add select query test: --- drift/test/sentry_drift_test.dart | 45 ++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/drift/test/sentry_drift_test.dart b/drift/test/sentry_drift_test.dart index 50b1867c29..fbf42c43de 100644 --- a/drift/test/sentry_drift_test.dart +++ b/drift/test/sentry_drift_test.dart @@ -25,7 +25,7 @@ void main() { 'INSERT INTO "todo_items" ("title", "body") VALUES (?, ?)'; final expectedUpdateStatement = 'UPDATE "todo_items" SET "title" = ?, "body" = ? WHERE "title" = ?;'; - final expectedSelectStatement = 'SELECT * FROM todo_items'; + final expectedSelectStatement = 'SELECT * FROM "todo_items";'; final expectedDeleteStatement = 'DELETE FROM "todo_items";'; late Fixture fixture; @@ -280,6 +280,49 @@ void main() { }); }); + group('select operations', () { + test('successful adds span', () async { + final sut = fixture.getSut(); + final db = AppDatabase(NativeDatabase.memory().interceptWith(sut)); + + final tx = _startTransaction(); + await _insertRow(db); + await db.select(db.todoItems).get(); + + _verifySpan( + expectedSelectStatement, + tx.children.last, + ); + }); + + test('error case adds error span', () async { + final exception = Exception('test'); + final queryExecutor = MockQueryExecutor(); + when(queryExecutor.ensureOpen(any)).thenAnswer((_) => Future.value(true)); + when(queryExecutor.runInsert(any, any)) + .thenAnswer((_) => Future.value(1)); + when(queryExecutor.runSelect(any, any)).thenThrow(exception); + when(queryExecutor.dialect).thenReturn(SqlDialect.sqlite); + + final sut = fixture.getSut(); + final db = AppDatabase(queryExecutor.interceptWith(sut)); + + final tx = _startTransaction(); + try { + await _insertRow(db); + await db.select(db.todoItems).get(); + } catch (e) { + // making sure the thrown exception doesn't fail the test + } + + _verifyErrorSpan( + expectedSelectStatement, + exception, + tx.children.last, + ); + }); + }); + group('custom query operations', () { test('successful adds span', () async { final sut = fixture.getSut(); From 8b59210cb797034b13a90c3fea543a96f90ccdd8 Mon Sep 17 00:00:00 2001 From: GIancarlo Buenaflor Date: Mon, 17 Feb 2025 16:51:19 +0100 Subject: [PATCH 29/29] removing stack in finally block --- drift/lib/src/sentry_span_helper.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drift/lib/src/sentry_span_helper.dart b/drift/lib/src/sentry_span_helper.dart index c4e8cb5185..f9a04e0ed7 100644 --- a/drift/lib/src/sentry_span_helper.dart +++ b/drift/lib/src/sentry_span_helper.dart @@ -126,7 +126,6 @@ class SentrySpanHelper { ); return execute(); } - _transactionStack.removeLast(); try { final result = await execute(); @@ -140,6 +139,7 @@ class SentrySpanHelper { rethrow; } finally { await parentSpan.finish(); + _transactionStack.removeLast(); } } @@ -153,7 +153,6 @@ class SentrySpanHelper { ); return Future.value(); } - _transactionStack.removeLast(); try { final result = await execute(); @@ -167,6 +166,7 @@ class SentrySpanHelper { rethrow; } finally { await parentSpan.finish(); + _transactionStack.removeLast(); } } }