diff --git a/.craft.yml b/.craft.yml index 04d1af925d..45554efb25 100644 --- a/.craft.yml +++ b/.craft.yml @@ -24,3 +24,5 @@ targets: pub:sentry_sqflite: pub:sentry_drift: pub:sentry_hive: + # Initial release on pub.dev needed first before uncommenting + # pub:sentry_isar: diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 5f61c73903..3d6e4229fd 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -13,6 +13,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/diagrams.yml b/.github/workflows/diagrams.yml index b4f403b78c..a534112e8b 100644 --- a/.github/workflows/diagrams.yml +++ b/.github/workflows/diagrams.yml @@ -47,6 +47,10 @@ jobs: working-directory: ./hive run: lakos . -i "{test/**,example/**}" | dot -Tsvg -o class-diagram.svg + - name: isar + working-directory: ./isar + run: lakos . -i "{test/**,example/**}" | dot -Tsvg -o class-diagram.svg + # Source: https://stackoverflow.com/a/58035262 - name: Extract branch name shell: bash diff --git a/.github/workflows/dio.yml b/.github/workflows/dio.yml index 064c175698..ec7a54eedc 100644 --- a/.github/workflows/dio.yml +++ b/.github/workflows/dio.yml @@ -13,6 +13,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index cc4da1177e..b5e2c630aa 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -12,6 +12,8 @@ on: - "dio/**" - "file/**" - "sqflite/**" + - "hive/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/e2e_dart.yml b/.github/workflows/e2e_dart.yml index 9deb9a3eaf..730d015d8e 100644 --- a/.github/workflows/e2e_dart.yml +++ b/.github/workflows/e2e_dart.yml @@ -14,6 +14,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} diff --git a/.github/workflows/file.yml b/.github/workflows/file.yml index 7fed334c62..56e3750fc9 100644 --- a/.github/workflows/file.yml +++ b/.github/workflows/file.yml @@ -13,6 +13,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 7b7a4c8f27..77e02e4fc7 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -13,6 +13,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index 582f4e36ac..4e710d19fa 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -12,6 +12,8 @@ on: - "dio/**" - "file/**" - "sqflite/**" + - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/isar.yml b/.github/workflows/isar.yml new file mode 100644 index 0000000000..2484fa56bf --- /dev/null +++ b/.github/workflows/isar.yml @@ -0,0 +1,116 @@ +name: sentry-isar +on: + push: + branches: + - main + - release/** + pull_request: + paths-ignore: + - "**/*.md" + - "logging/**" + - "flutter/**" + - "dio/**" + - "file/**" + - "sqflite/**" + - "hive/**" + - "drift/**" + +jobs: + cancel-previous-workflow: + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@01ce38bf961b4e243a6342cbade0dbc8ba3f0432 # pin@0.12.0 + with: + access_token: ${{ github.token }} + + build: + name: ${{ matrix.target }} | ${{ matrix.os }} | ${{ matrix.sdk }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + target: ["ios", "android", "macos", "linux", "windows"] + sdk: ["stable", "beta"] + exclude: + - os: ubuntu-latest + target: ios + - os: ubuntu-latest + target: macos + - os: ubuntu-latest + target: windows + - os: windows-latest + target: ios + - os: windows-latest + target: android + - os: windows-latest + target: macos + - os: windows-latest + target: linux + # macos-latest is taking hours due to limited resources + - os: macos-latest + target: android + - os: macos-latest + target: linux + - os: macos-latest + target: windows + # Bad CPU type in executable + - os: macos-latest + sdk: beta + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v3 + if: ${{ matrix.target == 'android' }} + with: + java-version: "11" + distribution: "adopt" + + # Install required dependencies for Flutter on Linux on Ubuntu + - name: "Setup Linux" + run: | + sudo apt update + sudo apt install -y cmake dbus libblkid-dev libgtk-3-dev liblzma-dev ninja-build pkg-config xvfb + sudo apt install -y network-manager upower + if: matrix.os == 'ubuntu-latest' && matrix.target == 'linux' + + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa # pin@v2.10.0 + with: + channel: ${{ matrix.sdk }} + + - run: flutter upgrade + + - name: Pub Get + run: | + cd isar + flutter pub get + + - name: Test VM with coverage + run: | + cd isar + flutter test -j 1 --coverage --test-randomize-ordering-seed=random + + - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # pin@v3 + if: runner.os == 'Linux' && matrix.sdk == 'stable' && matrix.target == 'linux' + with: + name: sentry_isar + file: ./isar/coverage/lcov.info + functionalities: "search" # remove after https://github.com/codecov/codecov-action/issues/600 + + - uses: VeryGoodOpenSource/very_good_coverage@e5c91bc7ce9843e87c800b3bcafdfb86fbe28491 # pin@v2.1.0 + if: runner.os == 'Linux' && matrix.sdk == 'stable' && matrix.target == 'linux' + with: + path: "./isar/coverage/lcov.info" + min_coverage: 55 + + analyze: + uses: ./.github/workflows/analyze.yml + with: + package: isar + sdk: flutter diff --git a/.github/workflows/logging.yml b/.github/workflows/logging.yml index b489d1be86..d4b6d9898c 100644 --- a/.github/workflows/logging.yml +++ b/.github/workflows/logging.yml @@ -13,6 +13,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/min_version_test.yml b/.github/workflows/min_version_test.yml index c40c015b16..3d227a1447 100644 --- a/.github/workflows/min_version_test.yml +++ b/.github/workflows/min_version_test.yml @@ -11,6 +11,7 @@ on: - "sqflite/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.github/workflows/sqflite.yml b/.github/workflows/sqflite.yml index 7943eb0e92..48bcf380b2 100644 --- a/.github/workflows/sqflite.yml +++ b/.github/workflows/sqflite.yml @@ -13,6 +13,7 @@ on: - "file/**" - "hive/**" - "drift/**" + - "isar/**" jobs: cancel-previous-workflow: diff --git a/.gitignore b/.gitignore index 60bf635a0c..1f40d0ebb1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ flutter/coverage/* sqflite/coverage/* drift/coverage/* hive/coverage/* +isar/coverage/* pubspec.lock Podfile.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index facd194bf6..d9051b9e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,12 @@ - Add `ConnectivityIntegration` for web ([#1765](https://github.com/getsentry/sentry-dart/pull/1765)) - We only get the info if online/offline on web platform. The added breadcrumb is set to either `wifi` or `none`. +- APM for isar ([#1726](https://github.com/getsentry/sentry-dart/pull/1726)) ## 7.14.0 +- Add option to opt out of fatal level for automatically collected errors ([#1738](https://github.com/getsentry/sentry-dart/pull/1738)) + ### Fixes - Add debug_meta to all events ([#1756](https://github.com/getsentry/sentry-dart/pull/1756)) diff --git a/README.md b/README.md index 1a6d03bc41..6159941c4c 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,11 @@ Sentry SDK for Dart and Flutter | sentry_sqflite | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-sqflite/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-sqflite) | [![pub package](https://img.shields.io/pub/v/sentry_sqflite.svg)](https://pub.dev/packages/sentry_sqflite) | [![likes](https://img.shields.io/pub/likes/sentry_sqflite)](https://pub.dev/packages/sentry_sqflite/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_sqflite)](https://pub.dev/packages/sentry_sqflite/score) | [![pub points](https://img.shields.io/pub/points/sentry_sqflite)](https://pub.dev/packages/sentry_sqflite/score) | sentry_drift | [![build](https://github.com/getsentry/sentry-dart/actions/workflows/drift.yml/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-drift) | [![pub package](https://img.shields.io/pub/v/sentry_drift.svg)](https://pub.dev/packages/sentry_drift) | [![likes](https://img.shields.io/pub/likes/sentry_drift)](https://pub.dev/packages/sentry_drift/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_drift)](https://pub.dev/packages/sentry_drift/score) | [![pub points](https://img.shields.io/pub/points/sentry_drift)](https://pub.dev/packages/sentry_drift/score) | sentry_hive | [![build](https://github.com/getsentry/sentry-dart/actions/workflows/hive.yml/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-hive) | [![pub package](https://img.shields.io/pub/v/sentry_hive.svg)](https://pub.dev/packages/sentry_hive) | [![likes](https://img.shields.io/pub/likes/sentry_hive)](https://pub.dev/packages/sentry_hive/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_hive)](https://pub.dev/packages/sentry_hive/score) | [![pub points](https://img.shields.io/pub/points/sentry_hive)](https://pub.dev/packages/sentry_hive/score) +| sentry_isar | [![build](https://github.com/getsentry/sentry-dart/actions/workflows/isar.yml/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-isar) | [![pub package](https://img.shields.io/pub/v/sentry_isar.svg)](https://pub.dev/packages/sentry_isar) | [![likes](https://img.shields.io/pub/likes/sentry_isar)](https://pub.dev/packages/sentry_isar/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_isar)](https://pub.dev/packages/sentry_isar/score) | [![pub points](https://img.shields.io/pub/points/sentry_isar)](https://pub.dev/packages/sentry_isar/score) ##### Usage -For detailed usage, check out the inner [dart](https://github.com/getsentry/sentry-dart/tree/main/dart), [flutter](https://github.com/getsentry/sentry-dart/tree/main/flutter), [logging](https://github.com/getsentry/sentry-dart/tree/main/logging), [dio](https://github.com/getsentry/sentry-dart/tree/main/dio), [file](https://github.com/getsentry/sentry-dart/tree/main/file), [sqflite](https://github.com/getsentry/sentry-dart/tree/main/sqflite), [drift](https://github.com/getsentry/sentry-dart/tree/main/drift) and [hive](https://github.com/getsentry/sentry-dart/tree/main/hive) `README's` or our `Resources` section below. +For detailed usage, check out the inner [dart](https://github.com/getsentry/sentry-dart/tree/main/dart), [flutter](https://github.com/getsentry/sentry-dart/tree/main/flutter), [logging](https://github.com/getsentry/sentry-dart/tree/main/logging), [dio](https://github.com/getsentry/sentry-dart/tree/main/dio), [file](https://github.com/getsentry/sentry-dart/tree/main/file), [sqflite](https://github.com/getsentry/sentry-dart/tree/main/sqflite), [drift](https://github.com/getsentry/sentry-dart/tree/main/drift), [hive](https://github.com/getsentry/sentry-dart/tree/main/hive) and [isar](https://github.com/getsentry/sentry-dart/tree/main/isar) `README's` or our `Resources` section below. #### Blog posts diff --git a/dart/lib/src/sentry_trace_origins.dart b/dart/lib/src/sentry_trace_origins.dart index 903348aa19..5a7c49b339 100644 --- a/dart/lib/src/sentry_trace_origins.dart +++ b/dart/lib/src/sentry_trace_origins.dart @@ -18,6 +18,8 @@ class SentryTraceOrigins { 'auto.db.sqflite.database_executor'; static const autoDbSqfliteDatabaseFactory = 'auto.db.sqflite.database_factory'; + static const autoDbIsar = 'auto.db.isar'; + static const autoDbIsarCollection = 'auto.db.isar.collection'; static const autoDbHive = 'auto.db.hive'; static const autoDbHiveBoxBase = 'auto.db.hive.box_base'; static const autoDbHiveLazyBox = 'auto.db.hive.lazy_box'; diff --git a/flutter/example/lib/isar/user.dart b/flutter/example/lib/isar/user.dart new file mode 100644 index 0000000000..f255d2389d --- /dev/null +++ b/flutter/example/lib/isar/user.dart @@ -0,0 +1,12 @@ +import 'package:isar/isar.dart'; + +part 'user.g.dart'; + +@collection +class User { + Id id = Isar.autoIncrement; + + String? name; + + int? age; +} diff --git a/flutter/example/lib/isar/user.g.dart b/flutter/example/lib/isar/user.g.dart new file mode 100644 index 0000000000..c4d7ef985f --- /dev/null +++ b/flutter/example/lib/isar/user.g.dart @@ -0,0 +1,553 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetUserCollection on Isar { + IsarCollection get users => this.collection(); +} + +final UserSchema = CollectionSchema( + name: r'User', + id: BigInt.parse("-7838171048429979076").toInt(), + properties: { + r'age': PropertySchema( + id: BigInt.parse("0").toInt(), + name: r'age', + type: IsarType.long, + ), + r'name': PropertySchema( + id: BigInt.parse("1").toInt(), + name: r'name', + type: IsarType.string, + ) + }, + estimateSize: _userEstimateSize, + serialize: _userSerialize, + deserialize: _userDeserialize, + deserializeProp: _userDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _userGetId, + getLinks: _userGetLinks, + attach: _userAttach, + version: '3.1.0', +); + +int _userEstimateSize( + User object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.name; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _userSerialize( + User object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.age); + writer.writeString(offsets[1], object.name); +} + +User _userDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = User(); + object.age = reader.readLongOrNull(offsets[0]); + object.id = id; + object.name = reader.readStringOrNull(offsets[1]); + return object; +} + +P _userDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLongOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _userGetId(User object) { + return object.id; +} + +List> _userGetLinks(User object) { + return []; +} + +void _userAttach(IsarCollection col, Id id, User object) { + object.id = id; +} + +extension UserQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension UserQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension UserQueryFilter on QueryBuilder { + QueryBuilder ageIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'age', + )); + }); + } + + QueryBuilder ageIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'age', + )); + }); + } + + QueryBuilder ageEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'age', + value: value, + )); + }); + } + + QueryBuilder ageGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'age', + value: value, + )); + }); + } + + QueryBuilder ageLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'age', + value: value, + )); + }); + } + + QueryBuilder ageBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'age', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder nameIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'name', + )); + }); + } + + QueryBuilder nameIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'name', + )); + }); + } + + QueryBuilder nameEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } +} + +extension UserQueryObject on QueryBuilder {} + +extension UserQueryLinks on QueryBuilder {} + +extension UserQuerySortBy on QueryBuilder { + QueryBuilder sortByAge() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.asc); + }); + } + + QueryBuilder sortByAgeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension UserQuerySortThenBy on QueryBuilder { + QueryBuilder thenByAge() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.asc); + }); + } + + QueryBuilder thenByAgeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension UserQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByAge() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'age'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } +} + +extension UserQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder ageProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'age'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } +} diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 4e39b96db2..4cb5b97942 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -10,9 +10,9 @@ import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sentry_drift/sentry_drift.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_isar/sentry_isar.dart'; import 'package:sentry_sqflite/sentry_sqflite.dart'; import 'package:sqflite/sqflite.dart'; - // import 'package:sqflite_common_ffi/sqflite_ffi.dart'; // import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -21,6 +21,7 @@ import 'package:provider/provider.dart'; import 'auto_close_screen.dart'; import 'drift/database.dart'; import 'drift/connection/connection.dart'; +import 'isar/user.dart'; import 'user_feedback_dialog.dart'; import 'package:dio/dio.dart'; import 'package:sentry_dio/sentry_dio.dart'; @@ -212,6 +213,13 @@ class MainScaffold extends StatelessWidget { 'Executes CRUD operations on an in-memory with Hive and sends the created transaction to Sentry.', buttonTitle: 'hive', ), + if (!UniversalPlatform.isWeb) + TooltipButton( + onPressed: isarTest, + text: + 'Executes CRUD operations on an in-memory with Isart and sends the created transaction to Sentry.', + buttonTitle: 'isar', + ), TooltipButton( onPressed: sqfliteTest, text: @@ -528,11 +536,38 @@ class MainScaffold extends StatelessWidget { ); } - Future hiveTest() async { - if (kIsWeb) { - return; - } + Future isarTest() async { + final tr = Sentry.startTransaction( + 'isarTest', + 'db', + bindToScope: true, + ); + + final dir = await getApplicationDocumentsDirectory(); + final isar = await SentryIsar.open( + [UserSchema], + directory: dir.path, + ); + + final newUser = User() + ..name = 'Joe Dirt' + ..age = 36; + + await isar.writeTxn(() async { + await isar.users.put(newUser); // insert & update + }); + + final existingUser = await isar.users.get(newUser.id); // get + + await isar.writeTxn(() async { + await isar.users.delete(existingUser!.id); // delete + }); + + await tr.finish(status: const SpanStatus.ok()); + } + + Future hiveTest() async { final tr = Sentry.startTransaction( 'hiveTest', 'db', diff --git a/flutter/example/pubspec.yaml b/flutter/example/pubspec.yaml index cdbbbd0e0d..0f279e8e8f 100644 --- a/flutter/example/pubspec.yaml +++ b/flutter/example/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: sentry_file: sentry_hive: sentry_drift: + sentry_isar: universal_platform: ^1.0.0 feedback: ^2.0.0 provider: ^6.0.0 @@ -26,6 +27,7 @@ dependencies: sqflite: any # This gets constrained by `sentry_sqflite` logging: any # This gets constrained by `sentry_logging` drift: any # This gets constrained by `sentry_drift` + isar: any # This gets constrained by `sentry_isar` package_info_plus: ^4.0.0 path_provider: ^2.0.0 #sqflite_common_ffi: ^2.0.0 @@ -42,6 +44,7 @@ dev_dependencies: flutter_test: sdk: flutter test: ^1.21.1 + build_runner: any flutter: uses-material-design: true diff --git a/flutter/example/pubspec_overrides.yaml b/flutter/example/pubspec_overrides.yaml index 4a3bb3c8cc..a392cc626d 100644 --- a/flutter/example/pubspec_overrides.yaml +++ b/flutter/example/pubspec_overrides.yaml @@ -15,3 +15,5 @@ dependency_overrides: path: ../../hive sentry_drift: path: ../../drift + sentry_isar: + path: ../../isar diff --git a/isar/.gitignore b/isar/.gitignore new file mode 100644 index 0000000000..fdd96ceb43 --- /dev/null +++ b/isar/.gitignore @@ -0,0 +1,17 @@ +# Omit committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Downloaded on demand by tests for correct architecture. +libisar.dylib diff --git a/isar/.metadata b/isar/.metadata new file mode 100644 index 0000000000..eea17bc4a0 --- /dev/null +++ b/isar/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "2f708eb8396e362e280fac22cf171c2cb467343c" + channel: "stable" + +project_type: package diff --git a/isar/CHANGELOG.md b/isar/CHANGELOG.md new file mode 120000 index 0000000000..04c99a55ca --- /dev/null +++ b/isar/CHANGELOG.md @@ -0,0 +1 @@ +../CHANGELOG.md \ No newline at end of file diff --git a/isar/LICENSE b/isar/LICENSE new file mode 100644 index 0000000000..2a6964d84d --- /dev/null +++ b/isar/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/isar/README.md b/isar/README.md new file mode 100644 index 0000000000..5405c8e43e --- /dev/null +++ b/isar/README.md @@ -0,0 +1,83 @@ +

+ + + +
+

+ +Sentry integration for `isar` package +=========== + +| package | build | pub | likes | popularity | pub points | +| ------- | ------- | ------- | ------- | ------- | ------- | +| sentry_isar | [![build](https://github.com/getsentry/sentry-dart/actions/workflows/isar.yml/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-isar) | [![pub package](https://img.shields.io/pub/v/sentry_isar.svg)](https://pub.dev/packages/sentry_isar) | [![likes](https://img.shields.io/pub/likes/sentry_isar)](https://pub.dev/packages/sentry_isar/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_isar)](https://pub.dev/packages/sentry_isar/score) | [![pub points](https://img.shields.io/pub/points/sentry_isar)](https://pub.dev/packages/sentry_isar/score) + +Integration for the [`isar`](https://pub.dev/packages/isar) package. + +#### Usage + +- Sign up for a Sentry.io account and get a DSN at https://sentry.io. + +- Follow the installing instructions on [pub.dev](https://pub.dev/packages/sentry/install). + +- Initialize the Sentry SDK using the DSN issued by Sentry.io. + +- Call... + +```dart +import 'package:path_provider/path_provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_isar/sentry_isar.dart'; + +import 'user.dart'; + +Future main() async { + await SentryFlutter.init( + (options) { + options.dsn = 'https://example@sentry.io/add-your-dsn-here'; + options.tracesSampleRate = 1.0; + }, + // Init your App. + appRunner: () => runApp(MyApp()), + ); +} + +Future runApp() async { + final tr = Sentry.startTransaction( + 'isarTest', + 'db', + bindToScope: true, + ); + + final dir = await getApplicationDocumentsDirectory(); + + final isar = await SentryIsar.open( + [UserSchema], + directory: dir.path, + ); + + final newUser = User() + ..name = 'Joe Dirt' + ..age = 36; + + await isar.writeTxn(() async { + await isar.users.put(newUser); // insert & update + }); + + final existingUser = await isar.users.get(newUser.id); // get + + await isar.writeTxn(() async { + await isar.users.delete(existingUser!.id); // delete + }); + + await tr.finish(status: const SpanStatus.ok()); +} +``` + +#### Resources + +* [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dart/) +* [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks) +* [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) +* [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry) +* [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) diff --git a/isar/analysis_options.yaml b/isar/analysis_options.yaml new file mode 100644 index 0000000000..92c8931384 --- /dev/null +++ b/isar/analysis_options.yaml @@ -0,0 +1,33 @@ +include: package:lints/recommended.yaml + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: error + # treat missing returns as a warning (not a hint) + missing_return: error + # allow having TODOs in the code + todo: ignore + # allow self-reference to deprecated members (we do this because otherwise we have + # to annotate every member in every test, assert, etc, when we deprecate something) + deprecated_member_use_from_same_package: warning + # ignore sentry/path on pubspec as we change it on deployment + invalid_dependency: ignore + unnecessary_import: ignore + exclude: + - example/** + +linter: + rules: + - prefer_final_locals + - public_member_api_docs + - prefer_single_quotes + - prefer_relative_imports + - unnecessary_brace_in_string_interps + - implementation_imports + - require_trailing_commas + - unawaited_futures diff --git a/isar/dartdoc_options.yaml b/isar/dartdoc_options.yaml new file mode 120000 index 0000000000..7cbb8c0d74 --- /dev/null +++ b/isar/dartdoc_options.yaml @@ -0,0 +1 @@ +../dart/dartdoc_options.yaml \ No newline at end of file diff --git a/isar/example/main.dart b/isar/example/main.dart new file mode 100644 index 0000000000..7c9972b50d --- /dev/null +++ b/isar/example/main.dart @@ -0,0 +1,47 @@ +import 'package:path_provider/path_provider.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_isar/sentry_isar.dart'; + +import 'user.dart'; + +Future main() async { + // ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io + const dsn = + 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562'; + + await SentryFlutter.init( + (options) { + options.dsn = dsn; + options.tracesSampleRate = 1.0; + options.debug = true; + }, + appRunner: runApp, // Init your App. + ); +} + +Future runApp() async { + final tr = Sentry.startTransaction('isar', 'db', bindToScope: true); + + final dir = await getApplicationDocumentsDirectory(); + + final isar = await SentryIsar.open( + [UserSchema], + directory: dir.path, + ); + + final newUser = User() + ..name = 'Joe Dirt' + ..age = 36; + + await isar.writeTxn(() async { + await isar.users.put(newUser); // insert & update + }); + + final existingUser = await isar.users.get(newUser.id); // get + + await isar.writeTxn(() async { + await isar.users.delete(existingUser!.id); // delete + }); + + await tr.finish(status: const SpanStatus.ok()); +} diff --git a/isar/example/user.dart b/isar/example/user.dart new file mode 100644 index 0000000000..f255d2389d --- /dev/null +++ b/isar/example/user.dart @@ -0,0 +1,12 @@ +import 'package:isar/isar.dart'; + +part 'user.g.dart'; + +@collection +class User { + Id id = Isar.autoIncrement; + + String? name; + + int? age; +} diff --git a/isar/example/user.g.dart b/isar/example/user.g.dart new file mode 100644 index 0000000000..370c40860f --- /dev/null +++ b/isar/example/user.g.dart @@ -0,0 +1,553 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetUserCollection on Isar { + IsarCollection get users => this.collection(); +} + +const UserSchema = CollectionSchema( + name: r'User', + id: -7838171048429979076, + properties: { + r'age': PropertySchema( + id: 0, + name: r'age', + type: IsarType.long, + ), + r'name': PropertySchema( + id: 1, + name: r'name', + type: IsarType.string, + ) + }, + estimateSize: _userEstimateSize, + serialize: _userSerialize, + deserialize: _userDeserialize, + deserializeProp: _userDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + getId: _userGetId, + getLinks: _userGetLinks, + attach: _userAttach, + version: '3.1.0+1', +); + +int _userEstimateSize( + User object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.name; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _userSerialize( + User object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.age); + writer.writeString(offsets[1], object.name); +} + +User _userDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = User(); + object.age = reader.readLongOrNull(offsets[0]); + object.id = id; + object.name = reader.readStringOrNull(offsets[1]); + return object; +} + +P _userDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLongOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _userGetId(User object) { + return object.id; +} + +List> _userGetLinks(User object) { + return []; +} + +void _userAttach(IsarCollection col, Id id, User object) { + object.id = id; +} + +extension UserQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension UserQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } +} + +extension UserQueryFilter on QueryBuilder { + QueryBuilder ageIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'age', + )); + }); + } + + QueryBuilder ageIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'age', + )); + }); + } + + QueryBuilder ageEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'age', + value: value, + )); + }); + } + + QueryBuilder ageGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'age', + value: value, + )); + }); + } + + QueryBuilder ageLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'age', + value: value, + )); + }); + } + + QueryBuilder ageBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'age', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder nameIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'name', + )); + }); + } + + QueryBuilder nameIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'name', + )); + }); + } + + QueryBuilder nameEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } +} + +extension UserQueryObject on QueryBuilder {} + +extension UserQueryLinks on QueryBuilder {} + +extension UserQuerySortBy on QueryBuilder { + QueryBuilder sortByAge() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.asc); + }); + } + + QueryBuilder sortByAgeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension UserQuerySortThenBy on QueryBuilder { + QueryBuilder thenByAge() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.asc); + }); + } + + QueryBuilder thenByAgeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'age', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension UserQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByAge() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'age'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } +} + +extension UserQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder ageProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'age'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } +} diff --git a/isar/lib/sentry_isar.dart b/isar/lib/sentry_isar.dart new file mode 100644 index 0000000000..75e63c47ea --- /dev/null +++ b/isar/lib/sentry_isar.dart @@ -0,0 +1,4 @@ +library sentry_isar; + +export 'src/sentry_isar.dart'; +export 'src/sentry_isar_collection.dart'; diff --git a/isar/lib/src/sentry_isar.dart b/isar/lib/src/sentry_isar.dart new file mode 100644 index 0000000000..e6e174a8eb --- /dev/null +++ b/isar/lib/src/sentry_isar.dart @@ -0,0 +1,253 @@ +import 'package:isar/isar.dart'; +import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; +import 'version.dart'; + +import 'sentry_isar_collection.dart'; +import 'sentry_span_helper.dart'; + +/// A sentry wrapper around the Isar Database +@experimental +class SentryIsar implements Isar { + @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 = 'isar'; + + @internal + // ignore: public_member_api_docs + static const dbNameKey = 'db.name'; + + @internal + // ignore: public_member_api_docs + static const dbCollectionKey = 'db.collection'; + + final Isar _isar; + final Hub _hub; + final _spanHelper = SentrySpanHelper( + // ignore: invalid_use_of_internal_member + SentryTraceOrigins.autoDbIsar, + ); + + /// ctor of SentryIsar + SentryIsar(this._isar, this._hub) { + _spanHelper.setHub(_hub); + + // ignore: invalid_use_of_internal_member + final options = _hub.options; + options.sdk.addIntegration('SentryIsarTracing'); + options.sdk.addPackage(packageName, sdkVersion); + } + + /// Open a new Isar instance, wrapped by SentryIsar + static Future open( + List> schemas, { + required String directory, + String name = Isar.defaultName, + int maxSizeMiB = Isar.defaultMaxSizeMiB, + bool relaxedDurability = true, + CompactCondition? compactOnLaunch, + bool inspector = true, + Hub? hub, + }) async { + final spanHelper = SentrySpanHelper( + // ignore: invalid_use_of_internal_member + SentryTraceOrigins.autoDbIsar, + ); + final hubToUse = hub ?? HubAdapter(); + spanHelper.setHub(hubToUse); + + final isar = await spanHelper.asyncWrapInSpan( + 'open', + () async { + return await Isar.open( + schemas, + directory: directory, + name: name, + maxSizeMiB: maxSizeMiB, + relaxedDurability: relaxedDurability, + compactOnLaunch: compactOnLaunch, + inspector: inspector, + ); + }, + dbName: name, + ); + + return SentryIsar(isar, hubToUse); + } + + /// Open a new Isar instance, wrapped by SentryIsar + static Isar openSync( + List> schemas, { + required String directory, + String name = Isar.defaultName, + int maxSizeMiB = Isar.defaultMaxSizeMiB, + bool relaxedDurability = true, + CompactCondition? compactOnLaunch, + bool inspector = true, + Hub? hub, + }) { + final isar = Isar.openSync( + schemas, + directory: directory, + name: name, + maxSizeMiB: maxSizeMiB, + relaxedDurability: relaxedDurability, + compactOnLaunch: compactOnLaunch, + inspector: inspector, + ); + return SentryIsar(isar, hub ?? HubAdapter()); + } + + @override + void attachCollections(Map> collections) { + _isar.attachCollections(collections); + } + + @override + Future clear() { + return _spanHelper.asyncWrapInSpan( + 'clear', + () { + return _isar.clear(); + }, + dbName: name, + ); + } + + @override + void clearSync() { + _isar.clearSync(); + } + + @override + Future close({bool deleteFromDisk = false}) { + return _spanHelper.asyncWrapInSpan( + 'close', + () { + return _isar.close(deleteFromDisk: deleteFromDisk); + }, + dbName: name, + ); + } + + @override + IsarCollection collection() { + return SentryIsarCollection(_isar.collection(), _hub, name); + } + + @override + Future copyToFile(String targetPath) { + return _spanHelper.asyncWrapInSpan( + 'copyToFile', + () { + return _isar.copyToFile(targetPath); + }, + dbName: name, + ); + } + + @override + String? get directory => _isar.directory; + + @override + IsarCollection? getCollectionByNameInternal(String name) { + final collection = _isar.getCollectionByNameInternal(name); + if (collection != null) { + return SentryIsarCollection(collection, _hub, name); + } else { + return null; + } + } + + @override + Future getSize({ + bool includeIndexes = false, + bool includeLinks = false, + }) { + return _spanHelper.asyncWrapInSpan( + 'getSize', + () { + return _isar.getSize( + includeIndexes: includeIndexes, + includeLinks: includeLinks, + ); + }, + dbName: name, + ); + } + + @override + int getSizeSync({bool includeIndexes = false, bool includeLinks = false}) { + return _isar.getSizeSync( + includeIndexes: includeIndexes, + includeLinks: includeLinks, + ); + } + + @override + bool get isOpen => _isar.isOpen; + + @override + String get name => _isar.name; + + @override + String? get path => _isar.path; + + @override + void requireOpen() { + _isar.requireOpen(); + } + + @override + Future txn(Future Function() callback) { + return _spanHelper.asyncWrapInSpan( + 'txn', + () { + return _isar.txn(callback); + }, + dbName: name, + ); + } + + @override + T txnSync(T Function() callback) { + return _isar.txnSync(callback); + } + + @override + @visibleForTesting + @experimental + Future verify() { + return _spanHelper.asyncWrapInSpan( + 'verify', + () { + // ignore: invalid_use_of_visible_for_testing_member + return _isar.verify(); + }, + dbName: name, + ); + } + + @override + Future writeTxn(Future Function() callback, {bool silent = false}) { + return _spanHelper.asyncWrapInSpan( + 'writeTxn', + () { + return _isar.writeTxn(callback, silent: silent); + }, + dbName: name, + ); + } + + @override + T writeTxnSync(T Function() callback, {bool silent = false}) { + return _isar.writeTxnSync(callback, silent: silent); + } +} diff --git a/isar/lib/src/sentry_isar_collection.dart b/isar/lib/src/sentry_isar_collection.dart new file mode 100644 index 0000000000..3f85242bbe --- /dev/null +++ b/isar/lib/src/sentry_isar_collection.dart @@ -0,0 +1,433 @@ +import 'dart:typed_data'; +import 'package:isar/isar.dart'; +import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; + +import 'sentry_span_helper.dart'; + +/// Sentry wrapper around IsarCollection +@experimental +class SentryIsarCollection implements IsarCollection { + final IsarCollection _isarCollection; + final Hub _hub; + final String _dbName; + + final _spanHelper = SentrySpanHelper( + // ignore: invalid_use_of_internal_member + SentryTraceOrigins.autoDbIsarCollection, + ); + + /// ctor of SentryIsarCollection + SentryIsarCollection(this._isarCollection, this._hub, this._dbName) { + _spanHelper.setHub(_hub); + } + + @override + Query buildQuery({ + List whereClauses = const [], + bool whereDistinct = false, + Sort whereSort = Sort.asc, + FilterOperation? filter, + List sortBy = const [], + List distinctBy = const [], + int? offset, + int? limit, + String? property, + }) { + return _isarCollection.buildQuery( + whereClauses: whereClauses, + whereDistinct: whereDistinct, + whereSort: whereSort, + filter: filter, + sortBy: sortBy, + distinctBy: distinctBy, + offset: offset, + limit: limit, + property: property, + ); + } + + @override + Future clear() { + return _spanHelper.asyncWrapInSpan( + 'clear', + () { + return _isarCollection.clear(); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + void clearSync() { + _isarCollection.clearSync(); + } + + @override + Future count() { + return _spanHelper.asyncWrapInSpan( + 'count', + () { + return _isarCollection.count(); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + int countSync() { + return _isarCollection.countSync(); + } + + @override + Future delete(Id id) { + return _spanHelper.asyncWrapInSpan( + 'delete', + () { + return _isarCollection.delete(id); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future deleteAll(List ids) { + return _spanHelper.asyncWrapInSpan( + 'deleteAll', + () { + return _isarCollection.deleteAll(ids); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future deleteAllByIndex(String indexName, List keys) { + return _spanHelper.asyncWrapInSpan( + 'deleteAllByIndex', + () { + return _isarCollection.deleteAllByIndex(indexName, keys); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + int deleteAllByIndexSync(String indexName, List keys) { + return _isarCollection.deleteAllByIndexSync(indexName, keys); + } + + @override + int deleteAllSync(List ids) { + return _isarCollection.deleteAllSync(ids); + } + + @override + Future deleteByIndex(String indexName, IndexKey key) { + return _spanHelper.asyncWrapInSpan( + 'deleteByIndex', + () { + return _isarCollection.deleteByIndex(indexName, key); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + bool deleteByIndexSync(String indexName, IndexKey key) { + return _isarCollection.deleteByIndexSync(indexName, key); + } + + @override + bool deleteSync(Id id) { + return _isarCollection.deleteSync(id); + } + + @override + QueryBuilder filter() { + return _isarCollection.filter(); + } + + @override + Future get(Id id) { + return _spanHelper.asyncWrapInSpan( + 'get', + () { + return _isarCollection.get(id); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future> getAll(List ids) { + return _spanHelper.asyncWrapInSpan( + 'getAll', + () { + return _isarCollection.getAll(ids); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future> getAllByIndex(String indexName, List keys) { + return _spanHelper.asyncWrapInSpan( + 'getAllByIndex', + () { + return _isarCollection.getAllByIndex(indexName, keys); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + List getAllByIndexSync(String indexName, List keys) { + return _isarCollection.getAllByIndexSync(indexName, keys); + } + + @override + List getAllSync(List ids) { + return _isarCollection.getAllSync(ids); + } + + @override + Future getByIndex(String indexName, IndexKey key) { + return _spanHelper.asyncWrapInSpan( + 'getByIndex', + () { + return _isarCollection.getByIndex(indexName, key); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + OBJ? getByIndexSync(String indexName, IndexKey key) { + return _isarCollection.getByIndexSync(indexName, key); + } + + @override + Future getSize({ + bool includeIndexes = false, + bool includeLinks = false, + }) { + return _spanHelper.asyncWrapInSpan( + 'getSize', + () { + return _isarCollection.getSize( + includeIndexes: includeIndexes, + includeLinks: includeLinks, + ); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + int getSizeSync({bool includeIndexes = false, bool includeLinks = false}) { + return _isarCollection.getSizeSync( + includeIndexes: includeIndexes, + includeLinks: includeLinks, + ); + } + + @override + OBJ? getSync(Id id) { + return _isarCollection.getSync(id); + } + + @override + Future importJson(List> json) { + return _spanHelper.asyncWrapInSpan( + 'importJson', + () { + return _isarCollection.importJson(json); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future importJsonRaw(Uint8List jsonBytes) { + return _spanHelper.asyncWrapInSpan( + 'importJsonRaw', + () { + return _isarCollection.importJsonRaw(jsonBytes); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + void importJsonRawSync(Uint8List jsonBytes) { + _isarCollection.importJsonRawSync(jsonBytes); + } + + @override + void importJsonSync(List> json) { + _isarCollection.importJsonSync(json); + } + + @override + Isar get isar => _isarCollection.isar; + + @override + String get name => _isarCollection.name; + + @override + Future put(OBJ object) { + return _spanHelper.asyncWrapInSpan( + 'put', + () { + return _isarCollection.put(object); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future> putAll(List objects) { + return _spanHelper.asyncWrapInSpan( + 'putAll', + () { + return _isarCollection.putAll(objects); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Future> putAllByIndex(String indexName, List objects) { + return _spanHelper.asyncWrapInSpan( + 'putAllByIndex', + () { + return _isarCollection.putAllByIndex(indexName, objects); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + List putAllByIndexSync( + String indexName, + List objects, { + bool saveLinks = true, + }) { + return _isarCollection.putAllByIndexSync( + indexName, + objects, + saveLinks: saveLinks, + ); + } + + @override + List putAllSync(List objects, {bool saveLinks = true}) { + return _isarCollection.putAllSync(objects, saveLinks: saveLinks); + } + + @override + Future putByIndex(String indexName, OBJ object) { + return _spanHelper.asyncWrapInSpan( + 'putByIndex', + () { + return _isarCollection.putByIndex(indexName, object); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Id putByIndexSync(String indexName, OBJ object, {bool saveLinks = true}) { + return _isarCollection.putByIndexSync( + indexName, + object, + saveLinks: saveLinks, + ); + } + + @override + Id putSync(OBJ object, {bool saveLinks = true}) { + return _isarCollection.putSync(object, saveLinks: saveLinks); + } + + @override + CollectionSchema get schema => _isarCollection.schema; + + @override + @visibleForTesting + @experimental + Future verify(List objects) { + return _spanHelper.asyncWrapInSpan( + 'verify', + () { + // ignore: invalid_use_of_visible_for_testing_member + return _isarCollection.verify(objects); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + @visibleForTesting + @experimental + Future verifyLink( + String linkName, + List sourceIds, + List targetIds, + ) { + return _spanHelper.asyncWrapInSpan( + 'verifyLink', + () { + // ignore: invalid_use_of_visible_for_testing_member + return _isarCollection.verifyLink(linkName, sourceIds, targetIds); + }, + dbName: _dbName, + collectionName: name, + ); + } + + @override + Stream watchLazy({bool fireImmediately = false}) { + return _isarCollection.watchLazy(fireImmediately: fireImmediately); + } + + @override + Stream watchObject(Id id, {bool fireImmediately = false}) { + return _isarCollection.watchObject(id, fireImmediately: fireImmediately); + } + + @override + Stream watchObjectLazy(Id id, {bool fireImmediately = false}) { + return _isarCollection.watchObjectLazy( + id, + fireImmediately: fireImmediately, + ); + } + + @override + QueryBuilder where({ + bool distinct = false, + Sort sort = Sort.asc, + }) { + return _isarCollection.where(distinct: distinct, sort: sort); + } +} diff --git a/isar/lib/src/sentry_span_helper.dart b/isar/lib/src/sentry_span_helper.dart new file mode 100644 index 0000000000..69fc6e5c39 --- /dev/null +++ b/isar/lib/src/sentry_span_helper.dart @@ -0,0 +1,65 @@ +// ignore_for_file: invalid_internal_annotation + +import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; +import 'sentry_isar.dart'; + +/// @nodoc +@internal +class SentrySpanHelper { + /// @nodoc + Hub _hub = HubAdapter(); + + /// @nodoc + final String _origin; + + /// @nodoc + SentrySpanHelper(this._origin); + + /// @nodoc + void setHub(Hub hub) { + _hub = hub; + } + + /// @nodoc + @internal + Future asyncWrapInSpan( + String description, + Future Function() execute, { + String? dbName, + String? collectionName, + }) async { + final currentSpan = _hub.getSpan(); + final span = currentSpan?.startChild( + SentryIsar.dbOp, + description: description, + ); + + // ignore: invalid_use_of_internal_member + span?.origin = _origin; + + span?.setData(SentryIsar.dbSystemKey, SentryIsar.dbSystem); + + if (dbName != null) { + span?.setData(SentryIsar.dbNameKey, dbName); + } + + if (collectionName != null) { + span?.setData(SentryIsar.dbCollectionKey, collectionName); + } + + try { + final result = await execute(); + span?.status = SpanStatus.ok(); + + return result; + } catch (exception) { + span?.throwable = exception; + span?.status = SpanStatus.internalError(); + + rethrow; + } finally { + await span?.finish(); + } + } +} diff --git a/isar/lib/src/version.dart b/isar/lib/src/version.dart new file mode 100644 index 0000000000..7c1a7d4031 --- /dev/null +++ b/isar/lib/src/version.dart @@ -0,0 +1,5 @@ +/// The SDK version reported to Sentry.io in the submitted events. +const String sdkVersion = '7.13.1'; + +/// The package name reported to Sentry.io in the submitted events. +const String packageName = 'pub:sentry_isar'; diff --git a/isar/pubspec.yaml b/isar/pubspec.yaml new file mode 100644 index 0000000000..43c0907472 --- /dev/null +++ b/isar/pubspec.yaml @@ -0,0 +1,28 @@ +name: sentry_isar +description: An integration which adds support for performance tracing for the isar package. +version: 7.13.1 +homepage: https://docs.sentry.io/platforms/flutter/ +repository: https://github.com/getsentry/sentry-dart +issue_tracker: https://github.com/getsentry/sentry-dart/issues + +environment: + sdk: '>=2.17.0 <4.0.0' + flutter: '>=3.3.0' + +dependencies: + isar: ^3.1.0 + isar_flutter_libs: ^3.1.0 # contains Isar Core + sentry: 7.12.0 + meta: ^1.3.0 + path: ^1.8.3 + +dev_dependencies: + isar_generator: ^3.1.0 + build_runner: ^2.4.2 + lints: ^3.0.0 + flutter_test: + sdk: flutter + coverage: ^1.3.0 + mockito: ^5.1.0 + yaml: ^3.1.0 # needed for version match (code and pubspec) + path_provider: ^2.1.1 diff --git a/isar/pubspec_overrides.yaml b/isar/pubspec_overrides.yaml new file mode 100644 index 0000000000..16e71d16f0 --- /dev/null +++ b/isar/pubspec_overrides.yaml @@ -0,0 +1,3 @@ +dependency_overrides: + sentry: + path: ../dart diff --git a/isar/test/mocks/mocks.dart b/isar/test/mocks/mocks.dart new file mode 100644 index 0000000000..55f59b6de3 --- /dev/null +++ b/isar/test/mocks/mocks.dart @@ -0,0 +1,10 @@ +import 'package:isar/isar.dart'; +import 'package:mockito/annotations.dart'; +import 'package:sentry/sentry.dart'; + +@GenerateMocks([ + Hub, + Isar, + IsarCollection, +]) +void main() {} diff --git a/isar/test/mocks/mocks.mocks.dart b/isar/test/mocks/mocks.mocks.dart new file mode 100644 index 0000000000..4f7adfed86 --- /dev/null +++ b/isar/test/mocks/mocks.mocks.dart @@ -0,0 +1,1340 @@ +// Mocks generated by Mockito 5.4.2 from annotations +// in sentry_isar/test/mocks/mocks.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes, invalid_use_of_internal_member +import 'dart:async' as _i3; +import 'dart:typed_data' as _i7; + +import 'package:isar/isar.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i6; +import 'package:sentry/sentry.dart' as _i2; +import 'package:sentry/src/profiling.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// 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, + ); +} + +class _FakeSentryId_1 extends _i1.SmartFake implements _i2.SentryId { + _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, + ); +} + +class _FakeHub_3 extends _i1.SmartFake implements _i2.Hub { + _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, + ); +} + +class _FakeFuture_5 extends _i1.SmartFake implements _i3.Future { + _FakeFuture_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIsarCollection_6 extends _i1.SmartFake + implements _i4.IsarCollection { + _FakeIsarCollection_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIsar_7 extends _i1.SmartFake implements _i4.Isar { + _FakeIsar_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCollectionSchema_8 extends _i1.SmartFake + implements _i4.CollectionSchema { + _FakeCollectionSchema_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeQueryBuilder_9 extends _i1.SmartFake + implements _i4.QueryBuilder { + _FakeQueryBuilder_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeQuery_10 extends _i1.SmartFake implements _i4.Query { + _FakeQuery_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [Hub]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHub extends _i1.Mock implements _i2.Hub { + MockHub() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.SentryOptions get options => (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeSentryOptions_0( + this, + Invocation.getter(#options), + ), + ) as _i2.SentryOptions); + + @override + 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), + ), + ) as _i2.SentryId); + + @override + _i2.Scope get scope => (super.noSuchMethod( + Invocation.getter(#scope), + returnValue: _FakeScope_2( + this, + Invocation.getter(#scope), + ), + ) as _i2.Scope); + + @override + set profilerFactory(_i5.SentryProfilerFactory? value) => super.noSuchMethod( + Invocation.setter( + #profilerFactory, + value, + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future<_i2.SentryId> captureEvent( + _i2.SentryEvent? event, { + dynamic stackTrace, + _i2.Hint? hint, + _i2.ScopeCallback? withScope, + }) => + (super.noSuchMethod( + Invocation.method( + #captureEvent, + [event], + { + #stackTrace: stackTrace, + #hint: hint, + #withScope: withScope, + }, + ), + returnValue: _i3.Future<_i2.SentryId>.value(_FakeSentryId_1( + this, + Invocation.method( + #captureEvent, + [event], + { + #stackTrace: stackTrace, + #hint: hint, + #withScope: withScope, + }, + ), + )), + ) as _i3.Future<_i2.SentryId>); + + @override + _i3.Future<_i2.SentryId> captureException( + dynamic throwable, { + dynamic stackTrace, + _i2.Hint? hint, + _i2.ScopeCallback? withScope, + }) => + (super.noSuchMethod( + Invocation.method( + #captureException, + [throwable], + { + #stackTrace: stackTrace, + #hint: hint, + #withScope: withScope, + }, + ), + returnValue: _i3.Future<_i2.SentryId>.value(_FakeSentryId_1( + this, + Invocation.method( + #captureException, + [throwable], + { + #stackTrace: stackTrace, + #hint: hint, + #withScope: withScope, + }, + ), + )), + ) as _i3.Future<_i2.SentryId>); + + @override + _i3.Future<_i2.SentryId> captureMessage( + String? message, { + _i2.SentryLevel? level, + String? template, + List? params, + _i2.Hint? hint, + _i2.ScopeCallback? withScope, + }) => + (super.noSuchMethod( + Invocation.method( + #captureMessage, + [message], + { + #level: level, + #template: template, + #params: params, + #hint: hint, + #withScope: withScope, + }, + ), + returnValue: _i3.Future<_i2.SentryId>.value(_FakeSentryId_1( + this, + Invocation.method( + #captureMessage, + [message], + { + #level: level, + #template: template, + #params: params, + #hint: hint, + #withScope: withScope, + }, + ), + )), + ) as _i3.Future<_i2.SentryId>); + + @override + _i3.Future captureUserFeedback(_i2.SentryUserFeedback? userFeedback) => + (super.noSuchMethod( + Invocation.method( + #captureUserFeedback, + [userFeedback], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future addBreadcrumb( + _i2.Breadcrumb? crumb, { + _i2.Hint? hint, + }) => + (super.noSuchMethod( + Invocation.method( + #addBreadcrumb, + [crumb], + {#hint: hint}, + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + void bindClient(_i2.SentryClient? client) => super.noSuchMethod( + 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); + + @override + _i3.Future close() => (super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.FutureOr configureScope(_i2.ScopeCallback? callback) => + (super.noSuchMethod(Invocation.method( + #configureScope, + [callback], + )) as _i3.FutureOr); + + @override + _i2.ISentrySpan startTransaction( + String? name, + String? operation, { + String? description, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + _i2.OnTransactionFinish? onFinish, + 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); + + @override + _i2.ISentrySpan startTransactionWithContext( + _i2.SentryTransactionContext? transactionContext, { + Map? customSamplingContext, + DateTime? startTimestamp, + bool? bindToScope, + bool? waitForChildren, + Duration? autoFinishAfter, + bool? trimEnd, + _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); + + @override + _i3.Future<_i2.SentryId> captureTransaction( + _i2.SentryTransaction? transaction, { + _i2.SentryTraceContextHeader? traceContext, + }) => + (super.noSuchMethod( + Invocation.method( + #captureTransaction, + [transaction], + {#traceContext: traceContext}, + ), + returnValue: _i3.Future<_i2.SentryId>.value(_FakeSentryId_1( + this, + Invocation.method( + #captureTransaction, + [transaction], + {#traceContext: traceContext}, + ), + )), + ) as _i3.Future<_i2.SentryId>); + + @override + void setSpanContext( + dynamic throwable, + _i2.ISentrySpan? span, + String? transaction, + ) => + super.noSuchMethod( + Invocation.method( + #setSpanContext, + [ + throwable, + span, + transaction, + ], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [Isar]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockIsar extends _i1.Mock implements _i4.Isar { + MockIsar() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: '', + ) as String); + + @override + bool get isOpen => (super.noSuchMethod( + Invocation.getter(#isOpen), + returnValue: false, + ) as bool); + + @override + void requireOpen() => super.noSuchMethod( + Invocation.method( + #requireOpen, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future txn(_i3.Future Function()? callback) => + (super.noSuchMethod( + Invocation.method( + #txn, + [callback], + ), + returnValue: _i6.ifNotNull( + _i6.dummyValueOrNull( + this, + Invocation.method( + #txn, + [callback], + ), + ), + (T v) => _i3.Future.value(v), + ) ?? + _FakeFuture_5( + this, + Invocation.method( + #txn, + [callback], + ), + ), + ) as _i3.Future); + + @override + _i3.Future writeTxn( + _i3.Future Function()? callback, { + bool? silent = false, + }) => + (super.noSuchMethod( + Invocation.method( + #writeTxn, + [callback], + {#silent: silent}, + ), + returnValue: _i6.ifNotNull( + _i6.dummyValueOrNull( + this, + Invocation.method( + #writeTxn, + [callback], + {#silent: silent}, + ), + ), + (T v) => _i3.Future.value(v), + ) ?? + _FakeFuture_5( + this, + Invocation.method( + #writeTxn, + [callback], + {#silent: silent}, + ), + ), + ) as _i3.Future); + + @override + T txnSync(T Function()? callback) => (super.noSuchMethod( + Invocation.method( + #txnSync, + [callback], + ), + returnValue: _i6.dummyValue( + this, + Invocation.method( + #txnSync, + [callback], + ), + ), + ) as T); + + @override + T writeTxnSync( + T Function()? callback, { + bool? silent = false, + }) => + (super.noSuchMethod( + Invocation.method( + #writeTxnSync, + [callback], + {#silent: silent}, + ), + returnValue: _i6.dummyValue( + this, + Invocation.method( + #writeTxnSync, + [callback], + {#silent: silent}, + ), + ), + ) as T); + + @override + void attachCollections(Map>? collections) => + super.noSuchMethod( + Invocation.method( + #attachCollections, + [collections], + ), + returnValueForMissingStub: null, + ); + + @override + _i4.IsarCollection collection() => (super.noSuchMethod( + Invocation.method( + #collection, + [], + ), + returnValue: _FakeIsarCollection_6( + this, + Invocation.method( + #collection, + [], + ), + ), + ) as _i4.IsarCollection); + + @override + _i4.IsarCollection? getCollectionByNameInternal(String? name) => + (super.noSuchMethod(Invocation.method( + #getCollectionByNameInternal, + [name], + )) as _i4.IsarCollection?); + + @override + _i3.Future clear() => (super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + void clearSync() => super.noSuchMethod( + Invocation.method( + #clearSync, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future getSize({ + bool? includeIndexes = false, + bool? includeLinks = false, + }) => + (super.noSuchMethod( + Invocation.method( + #getSize, + [], + { + #includeIndexes: includeIndexes, + #includeLinks: includeLinks, + }, + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int getSizeSync({ + bool? includeIndexes = false, + bool? includeLinks = false, + }) => + (super.noSuchMethod( + Invocation.method( + #getSizeSync, + [], + { + #includeIndexes: includeIndexes, + #includeLinks: includeLinks, + }, + ), + returnValue: 0, + ) as int); + + @override + _i3.Future copyToFile(String? targetPath) => (super.noSuchMethod( + Invocation.method( + #copyToFile, + [targetPath], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future close({bool? deleteFromDisk = false}) => (super.noSuchMethod( + Invocation.method( + #close, + [], + {#deleteFromDisk: deleteFromDisk}, + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); + + @override + _i3.Future verify() => (super.noSuchMethod( + Invocation.method( + #verify, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} + +/// A class which mocks [IsarCollection]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockIsarCollection extends _i1.Mock + implements _i4.IsarCollection { + MockIsarCollection() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Isar get isar => (super.noSuchMethod( + Invocation.getter(#isar), + returnValue: _FakeIsar_7( + this, + Invocation.getter(#isar), + ), + ) as _i4.Isar); + + @override + _i4.CollectionSchema get schema => (super.noSuchMethod( + Invocation.getter(#schema), + returnValue: _FakeCollectionSchema_8( + this, + Invocation.getter(#schema), + ), + ) as _i4.CollectionSchema); + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: '', + ) as String); + + @override + _i3.Future get(int? id) => (super.noSuchMethod( + Invocation.method( + #get, + [id], + ), + returnValue: _i3.Future.value(), + ) as _i3.Future); + + @override + OBJ? getSync(int? id) => (super.noSuchMethod(Invocation.method( + #getSync, + [id], + )) as OBJ?); + + @override + _i3.Future> getAll(List? ids) => (super.noSuchMethod( + Invocation.method( + #getAll, + [ids], + ), + returnValue: _i3.Future>.value([]), + ) as _i3.Future>); + + @override + List getAllSync(List? ids) => (super.noSuchMethod( + Invocation.method( + #getAllSync, + [ids], + ), + returnValue: [], + ) as List); + + @override + _i3.Future getByIndex( + String? indexName, + List? key, + ) => + (super.noSuchMethod( + Invocation.method( + #getByIndex, + [ + indexName, + key, + ], + ), + returnValue: _i3.Future.value(), + ) as _i3.Future); + + @override + OBJ? getByIndexSync( + String? indexName, + List? key, + ) => + (super.noSuchMethod(Invocation.method( + #getByIndexSync, + [ + indexName, + key, + ], + )) as OBJ?); + + @override + _i3.Future> getAllByIndex( + String? indexName, + List>? keys, + ) => + (super.noSuchMethod( + Invocation.method( + #getAllByIndex, + [ + indexName, + keys, + ], + ), + returnValue: _i3.Future>.value([]), + ) as _i3.Future>); + + @override + List getAllByIndexSync( + String? indexName, + List>? keys, + ) => + (super.noSuchMethod( + Invocation.method( + #getAllByIndexSync, + [ + indexName, + keys, + ], + ), + returnValue: [], + ) as List); + + @override + _i3.Future put(OBJ? object) => (super.noSuchMethod( + Invocation.method( + #put, + [object], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int putSync( + OBJ? object, { + bool? saveLinks = true, + }) => + (super.noSuchMethod( + Invocation.method( + #putSync, + [object], + {#saveLinks: saveLinks}, + ), + returnValue: 0, + ) as int); + + @override + _i3.Future> putAll(List? objects) => (super.noSuchMethod( + Invocation.method( + #putAll, + [objects], + ), + returnValue: _i3.Future>.value([]), + ) as _i3.Future>); + + @override + List putAllSync( + List? objects, { + bool? saveLinks = true, + }) => + (super.noSuchMethod( + Invocation.method( + #putAllSync, + [objects], + {#saveLinks: saveLinks}, + ), + returnValue: [], + ) as List); + + @override + _i3.Future putByIndex( + String? indexName, + OBJ? object, + ) => + (super.noSuchMethod( + Invocation.method( + #putByIndex, + [ + indexName, + object, + ], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int putByIndexSync( + String? indexName, + OBJ? object, { + bool? saveLinks = true, + }) => + (super.noSuchMethod( + Invocation.method( + #putByIndexSync, + [ + indexName, + object, + ], + {#saveLinks: saveLinks}, + ), + returnValue: 0, + ) as int); + + @override + _i3.Future> putAllByIndex( + String? indexName, + List? objects, + ) => + (super.noSuchMethod( + Invocation.method( + #putAllByIndex, + [ + indexName, + objects, + ], + ), + returnValue: _i3.Future>.value([]), + ) as _i3.Future>); + + @override + List putAllByIndexSync( + String? indexName, + List? objects, { + bool? saveLinks = true, + }) => + (super.noSuchMethod( + Invocation.method( + #putAllByIndexSync, + [ + indexName, + objects, + ], + {#saveLinks: saveLinks}, + ), + returnValue: [], + ) as List); + + @override + _i3.Future delete(int? id) => (super.noSuchMethod( + Invocation.method( + #delete, + [id], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); + + @override + bool deleteSync(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteSync, + [id], + ), + returnValue: false, + ) as bool); + + @override + _i3.Future deleteAll(List? ids) => (super.noSuchMethod( + Invocation.method( + #deleteAll, + [ids], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int deleteAllSync(List? ids) => (super.noSuchMethod( + Invocation.method( + #deleteAllSync, + [ids], + ), + returnValue: 0, + ) as int); + + @override + _i3.Future deleteByIndex( + String? indexName, + List? key, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteByIndex, + [ + indexName, + key, + ], + ), + returnValue: _i3.Future.value(false), + ) as _i3.Future); + + @override + bool deleteByIndexSync( + String? indexName, + List? key, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteByIndexSync, + [ + indexName, + key, + ], + ), + returnValue: false, + ) as bool); + + @override + _i3.Future deleteAllByIndex( + String? indexName, + List>? keys, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteAllByIndex, + [ + indexName, + keys, + ], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int deleteAllByIndexSync( + String? indexName, + List>? keys, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteAllByIndexSync, + [ + indexName, + keys, + ], + ), + returnValue: 0, + ) as int); + + @override + _i3.Future clear() => (super.noSuchMethod( + Invocation.method( + #clear, + [], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + void clearSync() => super.noSuchMethod( + Invocation.method( + #clearSync, + [], + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future importJsonRaw(_i7.Uint8List? jsonBytes) => + (super.noSuchMethod( + Invocation.method( + #importJsonRaw, + [jsonBytes], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + void importJsonRawSync(_i7.Uint8List? jsonBytes) => super.noSuchMethod( + Invocation.method( + #importJsonRawSync, + [jsonBytes], + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future importJson(List>? json) => + (super.noSuchMethod( + Invocation.method( + #importJson, + [json], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + void importJsonSync(List>? json) => super.noSuchMethod( + Invocation.method( + #importJsonSync, + [json], + ), + returnValueForMissingStub: null, + ); + + @override + _i4.QueryBuilder where({ + bool? distinct = false, + _i4.Sort? sort = _i4.Sort.asc, + }) => + (super.noSuchMethod( + Invocation.method( + #where, + [], + { + #distinct: distinct, + #sort: sort, + }, + ), + returnValue: _FakeQueryBuilder_9( + this, + Invocation.method( + #where, + [], + { + #distinct: distinct, + #sort: sort, + }, + ), + ), + ) as _i4.QueryBuilder); + + @override + _i4.QueryBuilder filter() => + (super.noSuchMethod( + Invocation.method( + #filter, + [], + ), + returnValue: _FakeQueryBuilder_9( + this, + Invocation.method( + #filter, + [], + ), + ), + ) as _i4.QueryBuilder); + + @override + _i4.Query buildQuery({ + List<_i4.WhereClause>? whereClauses = const [], + bool? whereDistinct = false, + _i4.Sort? whereSort = _i4.Sort.asc, + _i4.FilterOperation? filter, + List<_i4.SortProperty>? sortBy = const [], + List<_i4.DistinctProperty>? distinctBy = const [], + int? offset, + int? limit, + String? property, + }) => + (super.noSuchMethod( + Invocation.method( + #buildQuery, + [], + { + #whereClauses: whereClauses, + #whereDistinct: whereDistinct, + #whereSort: whereSort, + #filter: filter, + #sortBy: sortBy, + #distinctBy: distinctBy, + #offset: offset, + #limit: limit, + #property: property, + }, + ), + returnValue: _FakeQuery_10( + this, + Invocation.method( + #buildQuery, + [], + { + #whereClauses: whereClauses, + #whereDistinct: whereDistinct, + #whereSort: whereSort, + #filter: filter, + #sortBy: sortBy, + #distinctBy: distinctBy, + #offset: offset, + #limit: limit, + #property: property, + }, + ), + ), + ) as _i4.Query); + + @override + _i3.Future count() => (super.noSuchMethod( + Invocation.method( + #count, + [], + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int countSync() => (super.noSuchMethod( + Invocation.method( + #countSync, + [], + ), + returnValue: 0, + ) as int); + + @override + _i3.Future getSize({ + bool? includeIndexes = false, + bool? includeLinks = false, + }) => + (super.noSuchMethod( + Invocation.method( + #getSize, + [], + { + #includeIndexes: includeIndexes, + #includeLinks: includeLinks, + }, + ), + returnValue: _i3.Future.value(0), + ) as _i3.Future); + + @override + int getSizeSync({ + bool? includeIndexes = false, + bool? includeLinks = false, + }) => + (super.noSuchMethod( + Invocation.method( + #getSizeSync, + [], + { + #includeIndexes: includeIndexes, + #includeLinks: includeLinks, + }, + ), + returnValue: 0, + ) as int); + + @override + _i3.Stream watchLazy({bool? fireImmediately = false}) => + (super.noSuchMethod( + Invocation.method( + #watchLazy, + [], + {#fireImmediately: fireImmediately}, + ), + returnValue: _i3.Stream.empty(), + ) as _i3.Stream); + + @override + _i3.Stream watchObject( + int? id, { + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchObject, + [id], + {#fireImmediately: fireImmediately}, + ), + returnValue: _i3.Stream.empty(), + ) as _i3.Stream); + + @override + _i3.Stream watchObjectLazy( + int? id, { + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchObjectLazy, + [id], + {#fireImmediately: fireImmediately}, + ), + returnValue: _i3.Stream.empty(), + ) as _i3.Stream); + + @override + _i3.Future verify(List? objects) => (super.noSuchMethod( + Invocation.method( + #verify, + [objects], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + + @override + _i3.Future verifyLink( + String? linkName, + List? sourceIds, + List? targetIds, + ) => + (super.noSuchMethod( + Invocation.method( + #verifyLink, + [ + linkName, + sourceIds, + targetIds, + ], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} diff --git a/isar/test/person.dart b/isar/test/person.dart new file mode 100644 index 0000000000..385ae69e04 --- /dev/null +++ b/isar/test/person.dart @@ -0,0 +1,11 @@ +import 'package:isar/isar.dart'; + +part 'person.g.dart'; + +@collection +class Person { + Id id = Isar.autoIncrement; + + @Index() + String? name; +} diff --git a/isar/test/person.g.dart b/isar/test/person.g.dart new file mode 100644 index 0000000000..cbd85e6fe5 --- /dev/null +++ b/isar/test/person.g.dart @@ -0,0 +1,518 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetPersonCollection on Isar { + IsarCollection get persons => this.collection(); +} + +const PersonSchema = CollectionSchema( + name: r'Person', + id: 7854610480646705599, + properties: { + r'name': PropertySchema( + id: 0, + name: r'name', + type: IsarType.string, + ) + }, + estimateSize: _personEstimateSize, + serialize: _personSerialize, + deserialize: _personDeserialize, + deserializeProp: _personDeserializeProp, + idName: r'id', + indexes: { + r'name': IndexSchema( + id: 879695947855722453, + name: r'name', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'name', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _personGetId, + getLinks: _personGetLinks, + attach: _personAttach, + version: '3.1.0+1', +); + +int _personEstimateSize( + Person object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.name; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _personSerialize( + Person object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.name); +} + +Person _personDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Person(); + object.id = id; + object.name = reader.readStringOrNull(offsets[0]); + return object; +} + +P _personDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _personGetId(Person object) { + return object.id; +} + +List> _personGetLinks(Person object) { + return []; +} + +void _personAttach(IsarCollection col, Id id, Person object) { + object.id = id; +} + +extension PersonQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension PersonQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder nameIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'name', + value: [null], + )); + }); + } + + QueryBuilder nameIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'name', + lower: [null], + includeLower: false, + upper: [], + )); + }); + } + + QueryBuilder nameEqualTo(String? name) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'name', + value: [name], + )); + }); + } + + QueryBuilder nameNotEqualTo(String? name) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'name', + lower: [], + upper: [name], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'name', + lower: [name], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'name', + lower: [name], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'name', + lower: [], + upper: [name], + includeUpper: false, + )); + } + }); + } +} + +extension PersonQueryFilter on QueryBuilder { + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder nameIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'name', + )); + }); + } + + QueryBuilder nameIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'name', + )); + }); + } + + QueryBuilder nameEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } +} + +extension PersonQueryObject on QueryBuilder {} + +extension PersonQueryLinks on QueryBuilder {} + +extension PersonQuerySortBy on QueryBuilder { + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension PersonQuerySortThenBy on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension PersonQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } +} + +extension PersonQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } +} diff --git a/isar/test/sentry_isar_collection_test.dart b/isar/test/sentry_isar_collection_test.dart new file mode 100644 index 0000000000..c3286a2971 --- /dev/null +++ b/isar/test/sentry_isar_collection_test.dart @@ -0,0 +1,476 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:isar/isar.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry_isar/sentry_isar.dart'; +import 'package:sentry_isar/src/sentry_isar.dart'; + +import 'package:sentry/src/sentry_tracer.dart'; + +import 'mocks/mocks.mocks.dart'; +import 'person.dart'; + +void main() { + void verifySpan( + String description, + SentrySpan? span, + ) { + expect(span?.context.operation, SentryIsar.dbOp); + expect(span?.context.description, description); + expect(span?.status, SpanStatus.ok()); + // ignore: invalid_use_of_internal_member + expect(span?.origin, SentryTraceOrigins.autoDbIsarCollection); + expect(span?.data[SentryIsar.dbNameKey], Fixture.dbName); + expect(span?.data[SentryIsar.dbCollectionKey], 'Person'); + } + + void verifyErrorSpan(String description, SentrySpan? span, Exception error) { + expect(span?.context.operation, SentryIsar.dbOp); + expect(span?.context.description, description); + expect(span?.status, SpanStatus.internalError()); + // ignore: invalid_use_of_internal_member + expect(span?.origin, SentryTraceOrigins.autoDbIsarCollection); + expect(span?.throwable, error); + } + + group('add spans', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('clear adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().clear(); + }); + final span = fixture.getCreatedSpan(); + verifySpan('clear', span); + }); + + test('count adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().count(); + }); + final span = fixture.getCreatedSpan(); + verifySpan('count', span); + }); + + test('delete adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().delete(0); + }); + final span = fixture.getCreatedSpan(); + verifySpan('delete', span); + }); + + test('deleteAll adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().deleteAll([0]); + }); + final span = fixture.getCreatedSpan(); + verifySpan('deleteAll', span); + }); + + test('deleteAllByIndex adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putByIndex('name', Person()..name = 'Joe'); + await fixture.getSut().deleteAllByIndex('name', []); + }); + final span = fixture.getCreatedSpan(); + verifySpan('deleteAllByIndex', span); + }); + + test('deleteByIndex adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putByIndex('name', Person()..name = 'Joe'); + await fixture.getSut().deleteByIndex('name', []); + }); + final span = fixture.getCreatedSpan(); + verifySpan('deleteByIndex', span); + }); + + test('get adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().get(1); + }); + final span = fixture.getCreatedSpan(); + verifySpan('get', span); + }); + + test('getAll adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getAll([1]); + }); + final span = fixture.getCreatedSpan(); + verifySpan('getAll', span); + }); + + test('getAllByIndex adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getAllByIndex('name', []); + }); + final span = fixture.getCreatedSpan(); + verifySpan('getAllByIndex', span); + }); + + test('getByIndex adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getByIndex('name', []); + }); + final span = fixture.getCreatedSpan(); + verifySpan('getByIndex', span); + }); + + test('getSize adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().getSize(); + }); + final span = fixture.getCreatedSpan(); + verifySpan('getSize', span); + }); + + test('importJson adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().importJson([]); + }); + final span = fixture.getCreatedSpan(); + verifySpan('importJson', span); + }); + + test('importJsonRaw adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + final query = fixture.getSut().buildQuery(); + Uint8List jsonRaw = Uint8List.fromList([]); + await query.exportJsonRaw((raw) { + jsonRaw = Uint8List.fromList(raw); + }); + await fixture.getSut().importJsonRaw(jsonRaw); + }); + final span = fixture.getCreatedSpan(); + verifySpan('importJsonRaw', span); + }); + + test('put adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().put(Person()); + }); + final span = fixture.getCreatedSpan(); + verifySpan('put', span); + }); + + test('putAll adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putAll([Person()]); + }); + final span = fixture.getCreatedSpan(); + verifySpan('putAll', span); + }); + + test('putAllByIndex adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putAllByIndex('name', [Person()]); + }); + final span = fixture.getCreatedSpan(); + verifySpan('putAllByIndex', span); + }); + + test('putByIndex adds span', () async { + await fixture.sentryIsar.writeTxn(() async { + await fixture.getSut().putByIndex('name', Person()); + }); + final span = fixture.getCreatedSpan(); + verifySpan('putByIndex', span); + }); + }); + + group('add error spans', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.isarCollection.name).thenReturn(Fixture.dbCollection); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('throwing clear adds error span', () async { + when(fixture.isarCollection.clear()).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).clear(); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('clear', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing count adds error span', () async { + when(fixture.isarCollection.count()).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).count(); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('count', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing delete adds error span', () async { + when(fixture.isarCollection.delete(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).delete(0); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('delete', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing deleteAll adds error span', () async { + when(fixture.isarCollection.deleteAll(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).deleteAll([0]); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('deleteAll', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing deleteAllByIndex adds error span', () async { + when(fixture.isarCollection.deleteAllByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).deleteAllByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'deleteAllByIndex', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing deleteByIndex adds error span', () async { + when(fixture.isarCollection.deleteByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).deleteByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'deleteByIndex', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing get adds error span', () async { + when(fixture.isarCollection.get(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).get(1); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('get', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing getAll adds error span', () async { + when(fixture.isarCollection.getAll(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getAll([1]); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('getAll', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing getAllByIndex adds error span', () async { + when(fixture.isarCollection.getAllByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getAllByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'getAllByIndex', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing getByIndex adds error span', () async { + when(fixture.isarCollection.getByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getByIndex('name', []); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'getByIndex', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing getSize adds error span', () async { + when(fixture.isarCollection.getSize()).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).getSize(); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('getSize', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing importJson adds error span', () async { + when(fixture.isarCollection.importJson(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).importJson([]); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'importJson', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing importJsonRaw adds error span', () async { + when(fixture.isarCollection.importJsonRaw(any)) + .thenThrow(fixture.exception); + try { + await fixture + .getSut(injectMock: true) + .importJsonRaw(Uint8List.fromList([])); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'importJsonRaw', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing put adds error span', () async { + when(fixture.isarCollection.put(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).put(Person()); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('put', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing putAll adds error span', () async { + when(fixture.isarCollection.putAll(any)).thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).putAll([Person()]); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('putAll', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing putAllByIndex adds error span', () async { + when(fixture.isarCollection.putAllByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture + .getSut(injectMock: true) + .putAllByIndex('name', [Person()]); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'putAllByIndex', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing putByIndex adds error span', () async { + when(fixture.isarCollection.putByIndex(any, any)) + .thenThrow(fixture.exception); + try { + await fixture.getSut(injectMock: true).putByIndex('name', Person()); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'putByIndex', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + }); +} + +class Fixture { + final options = SentryOptions(); + final hub = MockHub(); + final isarCollection = MockIsarCollection(); + + static final dbName = 'people-isar'; + static final dbCollection = 'Person'; + final exception = Exception('fixture-exception'); + + final _context = SentryTransactionContext('name', 'operation'); + late final tracer = SentryTracer(_context, hub); + late Isar sentryIsar; + + Future setUp() async { + // Make sure to use flutter test -j 1 to avoid tests running in parallel. This would break the automatic download. + await Isar.initializeIsarCore(download: true); + sentryIsar = await SentryIsar.open( + [PersonSchema], + directory: Directory.systemTemp.path, + name: dbName, + hub: hub, + ); + } + + Future tearDown() async { + try { + // ignore: invalid_use_of_protected_member + sentryIsar.requireOpen(); + await sentryIsar.close(); + } catch (_) { + // Don't close multiple times + } + } + + IsarCollection getSut({bool injectMock = false}) { + if (injectMock) { + return SentryIsarCollection(isarCollection, hub, sentryIsar.name); + } else { + return sentryIsar.collection(); + } + } + + SentrySpan? getCreatedSpan() { + return tracer.children.last; + } +} diff --git a/isar/test/sentry_isar_test.dart b/isar/test/sentry_isar_test.dart new file mode 100644 index 0000000000..70cc23c832 --- /dev/null +++ b/isar/test/sentry_isar_test.dart @@ -0,0 +1,277 @@ +@TestOn('vm') + +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:isar/isar.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:sentry_isar/src/sentry_isar.dart'; + +import 'package:sentry/src/sentry_tracer.dart'; +import 'package:sentry_isar/src/version.dart'; + +import 'mocks/mocks.mocks.dart'; +import 'person.dart'; + +void main() { + void verifySpan( + String description, + SentrySpan? span, { + bool checkName = false, + }) { + expect(span?.context.operation, SentryIsar.dbOp); + expect(span?.context.description, description); + expect(span?.status, SpanStatus.ok()); + // ignore: invalid_use_of_internal_member + expect(span?.origin, SentryTraceOrigins.autoDbIsar); + if (checkName) { + expect(span?.data[SentryIsar.dbNameKey], Fixture.dbName); + } + } + + void verifyErrorSpan(String description, SentrySpan? span, Exception error) { + expect(span?.context.operation, SentryIsar.dbOp); + expect(span?.context.description, description); + expect(span?.status, SpanStatus.internalError()); + // ignore: invalid_use_of_internal_member + expect(span?.origin, SentryTraceOrigins.autoDbIsar); + expect(span?.throwable, error); + } + + group('add spans', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('open adds span', () async { + final span = fixture.getCreatedSpan(); + verifySpan('open', span, checkName: true); + }); + + test('clear adds span', () async { + await fixture.sut.writeTxn(() async { + await fixture.sut.clear(); + }); + final span = fixture.getCreatedSpan(); + verifySpan('clear', span, checkName: true); + }); + + test('close adds span', () async { + await fixture.sut.close(); + final span = fixture.getCreatedSpan(); + verifySpan('close', span, checkName: true); + }); + + test('copyToFile adds span', () async { + await fixture.sut.copyToFile(fixture.copyPath); + final span = fixture.getCreatedSpan(); + verifySpan('copyToFile', span, checkName: true); + }); + + test('getSize adds span', () async { + await fixture.sut.getSize(); + final span = fixture.getCreatedSpan(); + verifySpan('getSize', span, checkName: true); + }); + + test('txn adds span', () async { + await fixture.sut.txn(() async {}); + final span = fixture.getCreatedSpan(); + verifySpan('txn', span, checkName: true); + }); + + test('writeTxn adds span', () async { + await fixture.sut.writeTxn(() async {}); + final span = fixture.getCreatedSpan(); + verifySpan('writeTxn', span, checkName: true); + }); + }); + + group('add error spans', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + when(fixture.isar.close()).thenAnswer((_) async { + return true; + }); + when(fixture.isar.name).thenReturn(Fixture.dbName); + + await fixture.setUp(injectMock: true); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('throwing close adds error span', () async { + when(fixture.isar.close()).thenThrow(fixture.exception); + try { + await fixture.sut.close(); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('close', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing clear adds error span', () async { + when(fixture.isar.clear()).thenThrow(fixture.exception); + try { + await fixture.sut.clear(); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('clear', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing copyToFile adds error span', () async { + when(fixture.isar.copyToFile(any)).thenThrow(fixture.exception); + try { + await fixture.sut.copyToFile(fixture.copyPath); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan( + 'copyToFile', + fixture.getCreatedSpan(), + fixture.exception, + ); + }); + + test('throwing getSize adds error span', () async { + when(fixture.isar.getSize()).thenThrow(fixture.exception); + try { + await fixture.sut.getSize(); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('getSize', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing txn adds error span', () async { + param() async {} + when(fixture.isar.txn(param)).thenThrow(fixture.exception); + try { + await fixture.sut.txn(param); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('txn', fixture.getCreatedSpan(), fixture.exception); + }); + + test('throwing writeTxn adds error span', () async { + param() async {} + when(fixture.isar.writeTxn(param)).thenThrow(fixture.exception); + try { + await fixture.sut.writeTxn(param); + } catch (error) { + expect(error, fixture.exception); + } + verifyErrorSpan('writeTxn', fixture.getCreatedSpan(), fixture.exception); + }); + }); + + group('integrations', () { + late Fixture fixture; + + setUp(() async { + fixture = Fixture(); + + when(fixture.hub.options).thenReturn(fixture.options); + when(fixture.hub.getSpan()).thenReturn(fixture.tracer); + + await fixture.setUp(); + }); + + tearDown(() async { + await fixture.tearDown(); + }); + + test('adds integration', () { + expect( + fixture.options.sdk.integrations.contains('SentryIsarTracing'), + true, + ); + }); + + test('adds package', () { + expect( + fixture.options.sdk.packages.any( + (element) => + element.name == packageName && element.version == sdkVersion, + ), + true, + ); + }); + }); +} + +class Fixture { + final options = SentryOptions(); + final hub = MockHub(); + final isar = MockIsar(); + + static final dbName = 'people-isar'; + final exception = Exception('fixture-exception'); + final copyPath = '${Directory.systemTemp.path}/copy'; + + final _context = SentryTransactionContext('name', 'operation'); + late final tracer = SentryTracer(_context, hub); + late Isar sut; + + Future setUp({bool injectMock = false}) async { + if (injectMock) { + sut = SentryIsar(isar, hub); + } else { + // Make sure to use flutter test -j 1 to avoid tests running in parallel. This would break the automatic download. + await Isar.initializeIsarCore(download: true); + sut = await SentryIsar.open( + [PersonSchema], + directory: Directory.systemTemp.path, + name: dbName, + hub: hub, + ); + } + await deleteCopyPath(); + } + + Future tearDown() async { + try { + // ignore: invalid_use_of_protected_member + sut.requireOpen(); + await sut.close(); + } catch (_) { + // Don't close multiple times + } + } + + Isar getSut() { + return sut; + } + + SentrySpan? getCreatedSpan() { + return tracer.children.last; + } + + Future deleteCopyPath() async { + final file = File(copyPath); + if (await file.exists()) { + await file.delete(recursive: true); + } + } +} diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index d3e8fae081..dedd3e1b66 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -10,7 +10,7 @@ NEW_VERSION="${2}" echo "Current version: ${OLD_VERSION}" echo "Bumping version: ${NEW_VERSION}" -for pkg in {dart,flutter,logging,dio,file,sqflite,drift,hive}; do +for pkg in {dart,flutter,logging,dio,file,sqflite,drift,hive,isar}; do # Bump version in pubspec.yaml perl -pi -e "s/^version: .*/version: $NEW_VERSION/" $pkg/pubspec.yaml # Bump sentry dependency version in pubspec.yaml