Tests for more platforms (#879)

This commit is contained in:
Simon Leier
2022-11-12 19:23:41 +01:00
committed by GitHub
parent c23aada193
commit 01da127a7c
56 changed files with 761 additions and 474 deletions
+22
View File
@@ -0,0 +1,22 @@
name: "Prepare Build"
description: "Prepares the build for Isar Core"
runs:
using: "composite"
steps:
- name: Install LLVM and Clang
if: runner.os == 'Windows'
uses: KyleMayes/install-llvm-action@v1
with:
version: "11.0"
directory: ${{ runner.temp }}/llvm
- name: Set LIBCLANG_PATH
if: runner.os == 'Windows'
shell: bash
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
# See https://github.com/godot-rust/godot-rust/pull/920
- name: "Workaround Android NDK due to Rust bug"
if: runner.os == 'Linux' || runner.os == 'macOS'
shell: bash
run: >
find -L $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION -name libunwind.a
-execdir sh -c 'echo "INPUT(-lunwind)" > libgcc.a' \;
+2 -2
View File
@@ -3,7 +3,7 @@ name: Docs
on: on:
push: push:
branches: branches:
- main - main
permissions: permissions:
contents: read contents: read
@@ -37,4 +37,4 @@ jobs:
path: docs/docs/.vuepress/dist path: docs/docs/.vuepress/dist
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v1 uses: actions/deploy-pages@v1
+22 -35
View File
@@ -3,20 +3,20 @@ name: Isar release
on: on:
push: push:
tags: tags:
- '*' - "*"
jobs: jobs:
verify_version: verify_version:
name: Verify version matches release name: Verify version matches release
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: dart-lang/setup-dart@v1 - uses: dart-lang/setup-dart@v1
- name: Verify release version - name: Verify release version
run: | run: |
dart pub get dart pub get
dart tool/verify_release_version.dart ${{ github.ref_name }} dart tool/verify_release_version.dart ${{ github.ref_name }}
working-directory: packages/isar working-directory: packages/isar
build_binaries: build_binaries:
name: Build Binaries name: Build Binaries
@@ -54,33 +54,20 @@ jobs:
script: build_windows.sh x64 script: build_windows.sh x64
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install LLVM and Clang - name: Prepare Build
uses: KyleMayes/install-llvm-action@v1 uses: ./.github/actions/prepare-build
if: matrix.os == 'windows-latest' - name: Set env
with: run: echo "ISAR_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
version: "11.0" - name: Build binary
directory: ${{ runner.temp }}/llvm run: bash tool/${{ matrix.script }}
- name: Set LIBCLANG_PATH - name: Upload binary
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV uses: svenstaro/upload-release-action@v2
if: matrix.os == 'windows-latest' with:
# See https://github.com/godot-rust/godot-rust/pull/920 repo_token: ${{ secrets.GITHUB_TOKEN }}
- name: "Workaround Android NDK due to Rust bug" file: ${{ matrix.artifact_name }}
if: matrix.os == 'ubuntu-latest' asset_name: ${{ matrix.artifact_name }}
run: > tag: ${{ github.ref }}
find -L $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION -name libunwind.a
-execdir sh -c 'echo "INPUT(-lunwind)" > libgcc.a' \;
- name: Set env
run: echo "ISAR_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
- name: Build binary
run: bash tool/${{ matrix.script }}
- name: Upload binary
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ matrix.artifact_name }}
asset_name: ${{ matrix.artifact_name }}
tag: ${{ github.ref }}
build_inspector: build_inspector:
name: Build Inspector name: Build Inspector
+181 -71
View File
@@ -3,10 +3,10 @@ name: Dart CI
on: on:
push: push:
branches: branches:
- main - main
pull_request: pull_request:
branches: branches:
- main - main
jobs: jobs:
format: format:
@@ -48,35 +48,65 @@ jobs:
run: flutter analyze run: flutter analyze
test: test:
name: Dart Test
strategy: strategy:
matrix: matrix:
os: [macos-latest, ubuntu-latest] os: [macos-latest, ubuntu-latest]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- run: echo "$OSTYPE"
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Unit tests - name: Run Flutter Unit tests
run: flutter test -j 1
working-directory: packages/isar_test
valgrind:
name: Valgrind
runs-on: ubuntu-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Install valgrind and llvm
run: sudo apt update && sudo apt install -y valgrind libclang-dev
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Valgrind
run: | run: |
flutter pub get dart compile exe integration_test/all_tests.dart
dart tool/generate_long_double_test.dart valgrind \
dart tool/generate_prepare_test.dart --leak-check=full \
flutter pub run build_runner build --error-exitcode=1 \
flutter test -j 1 --show-mismatched-frees=no \
--show-possibly-lost=no \
--errors-for-leak-kinds=definite \
integration_test/all_tests.exe
working-directory: packages/isar_test working-directory: packages/isar_test
coverage: coverage:
name: Code Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
- name: Prepare Unit tests - name: Prepare Build
run: | uses: ./.github/actions/prepare-build
flutter pub get - name: Build Isar Core
dart tool/generate_long_double_test.dart run: sh tool/build.sh
dart tool/generate_prepare_test.dart - name: Prepare Tests
flutter pub run build_runner build run: sh tool/prepare_tests.sh
working-directory: packages/isar_test
- name: Add packages - name: Add packages
run: | run: |
flutter pub add json_annotation flutter pub add json_annotation
@@ -101,6 +131,7 @@ jobs:
files: packages/isar/lcov_isar_test.info files: packages/isar/lcov_isar_test.info
test_generator: test_generator:
name: Generator Test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -111,99 +142,178 @@ jobs:
dart test dart test
working-directory: packages/isar_generator working-directory: packages/isar_generator
drive_ios: integration_test_ios:
strategy: name: Integration Test iOS
matrix:
device:
- "iPhone 8"
- "iPhone 11 Pro Max"
fail-fast: false
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Start simulator - name: Start simulator
uses: futureware-tech/simulator-action@v2 uses: futureware-tech/simulator-action@v2
with: with:
model: ${{ matrix.device }} model: iPhone 13
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core - name: Build Isar Core
run: | run: |
bash tool/build_ios.sh bash tool/build_ios.sh
unzip isar_ios.xcframework.zip -d packages/isar_flutter_libs/ios unzip isar_ios.xcframework.zip -d packages/isar_flutter_libs/ios
- name: Run Flutter Driver tests - name: Prepare Tests
run: | run: sh tool/prepare_tests.sh
flutter pub get - name: Run Flutter Integration tests
dart tool/generate_long_double_test.dart run: flutter test integration_test/integration_test.dart --dart-define STRESS=true
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart
working-directory: packages/isar_test working-directory: packages/isar_test
drive_android: integration_test_android:
name: Integration Test Android
runs-on: macos-latest runs-on: macos-latest
if: ${{ false }}
strategy:
matrix:
api-level: [29] #[21, 29]
target: [default]
fail-fast: false
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-java@v3 - uses: actions/setup-java@v3
with: with:
java-version: '11' java-version: "11"
distribution: 'zulu' distribution: "zulu"
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Setup old NDK
run: |
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;21.4.7075529"
ln -sfn $ANDROID_HOME/ndk/21.4.7075529 $ANDROID_HOME/ndk-bundle
- name: Update ANDROID_NDK_HOME
run: echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/21.4.7075529" >> $GITHUB_ENV
- name: Build Isar Core - name: Build Isar Core
run: | run: |
bash tool/build_android.sh x64 bash tool/build_android.sh x64
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/x86_64
mv libisar_android_x64.so packages/isar_flutter_libs/android/src/main/jniLibs/x86_64/libisar.so mv libisar_android_x64.so packages/isar_flutter_libs/android/src/main/jniLibs/x86_64/libisar.so
- name: Run isar_generator - name: Prepare Tests
run: | run: sh tool/prepare_tests.sh
flutter pub get - name: Run Flutter Integration tests
dart tool/generate_long_double_test.dart continue-on-error: true
flutter pub run build_runner build timeout-minutes: ${{ inputs.timeout_minutes }}
dart tool/generate_all_tests.dart uses: Wandalen/wretry.action@v1.0.25
working-directory: packages/isar_test
- name: Run Flutter Driver tests
uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} action: reactivecircus/android-emulator-runner@v2
target: ${{ matrix.target }} with: |
arch: x86_64 api-level: 29
profile: Nexus 6 arch: x86_64
working-directory: packages/isar_test profile: pixel
script: "flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart" working-directory: packages/isar_test
script: flutter test integration_test/integration_test.dart --dart-define STRESS=true
drive_desktop: firebase_testlab_android:
strategy: name: Firebase Testlab Android
matrix: runs-on: ubuntu-latest
os: [macos-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
- name: Install Linux requirements - name: Prepare Build
run: sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev uses: ./.github/actions/prepare-build
if: matrix.os == 'ubuntu-latest' - name: Build Isar Core arm64
- name: Build Isar Core macOS run: |
if: matrix.os == 'macos-latest' bash tool/build_android.sh arm64
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/arm64-v8a
mv libisar_android_arm64.so packages/isar_flutter_libs/android/src/main/jniLibs/arm64-v8a/libisar.so
- name: Build Isar Core armv7
run: |
bash tool/build_android.sh armv7
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/armeabi-v7a
mv libisar_android_armv7.so packages/isar_flutter_libs/android/src/main/jniLibs/armeabi-v7a/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Build dummy APK
run: flutter build apk integration_test/integration_test.dart
working-directory: packages/isar_test
- name: Build APKs
run: |
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget=integration_test/integration_test.dart
working-directory: packages/isar_test/android
- name: Login to Google Cloud
uses: "google-github-actions/auth@v1"
with:
credentials_json: "${{ secrets.FIREBASE_JSON }}"
- name: Run tests
run: |
gcloud firebase test android run \
--project honeyy \
--type instrumentation \
--device model=hammerhead,version=23 \
--device model=starqlteue,version=26 \
--device model=cheetah,version=33 \
--device model=1610,version=23 \
--device model=griffin,version=24 \
--device model=harpia,version=23 \
--device model=sailfish,version=25 \
--app build/app/outputs/apk/debug/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk\
working-directory: packages/isar_test
integration_test_macos:
name: Integration Test macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: | run: |
bash tool/build_macos.sh bash tool/build_macos.sh
install_name_tool -id @rpath/libisar.dylib libisar_macos.dylib install_name_tool -id @rpath/libisar.dylib libisar_macos.dylib
mv libisar_macos.dylib packages/isar_flutter_libs/macos/libisar.dylib mv libisar_macos.dylib packages/isar_flutter_libs/macos/libisar.dylib
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests - name: Run Flutter Driver tests
run: | run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter config --no-enable-web
flutter config --enable-macos-desktop flutter config --enable-macos-desktop
flutter test -d macos integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_linux:
name: Integration Test Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Install Linux requirements
run: sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev
- name: Setup headless display
uses: pyvista/setup-headless-display-action@v1
- name: Build Isar Core
run: |
bash tool/build_linux.sh x64
mv libisar_linux_x64.so packages/isar_flutter_libs/linux/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-linux-desktop flutter config --enable-linux-desktop
flutter test -d linux integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_windows:
name: Integration Test Windows
runs-on: windows-2019
if: ${{ false }}
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_windows.sh x64
mv isar_windows_x64.dll packages/isar_flutter_libs/windows/libisar.dll
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-windows-desktop flutter config --enable-windows-desktop
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart flutter test -d windows integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test working-directory: packages/isar_test
drive_chrome: drive_chrome:
@@ -220,8 +330,8 @@ jobs:
run: | run: |
flutter pub get flutter pub get
dart tool/generate_long_double_test.dart dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart dart tool/generate_all_tests.dart
flutter pub run build_runner build
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart -d web-server --browser-name chrome flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart -d web-server --browser-name chrome
working-directory: packages/isar_test working-directory: packages/isar_test
+8 -7
View File
@@ -18,13 +18,14 @@ final isar = await Isar.open([RecipeSchema]);
You can use the default config or provide some of the following parameters: You can use the default config or provide some of the following parameters:
| Config | Description | | Config | Description |
| -------| -------------| | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Open multiple instances with distinct names. By default, `"default"` is used. | | `name` | Open multiple instances with distinct names. By default, `"default"` is used. |
| `directory` | The storage location for this instance. By default, `NSDocumentDirectory` is used for iOS and `getDataDirectory` for Android. Not required for web. | | `directory` | The storage location for this instance. By default, `NSDocumentDirectory` is used for iOS and `getDataDirectory` for Android. Not required for web. |
| `relaxedDurability` | Relaxes the durability guarantee to increase write performance. In case of a system crash (not app crash), it is possible to lose the last committed transaction. Corruption is not possible | | `maxSizeMib` | The maximum size of the database file in MiB. Isar uses virtual memory which is not an endless resource so be mindful with the value here. If you open multiple instances they share the available virtual memory so each instance should have a smaller `maxSizeMib` . The default is 2048. |
| `compactOnLaunch` | Conditions to check whether the database should be compacted when the instance is opened. | | `relaxedDurability` | Relaxes the durability guarantee to increase write performance. In case of a system crash (not app crash), it is possible to lose the last committed transaction. Corruption is not possible |
| `inspector` | Enabled the Inspector for debug builds. For profile and release builds this option is ignored. | | `compactOnLaunch` | Conditions to check whether the database should be compacted when the instance is opened. |
| `inspector` | Enabled the Inspector for debug builds. For profile and release builds this option is ignored. |
If an instance is already open, calling `Isar.open()` will yield the existing instance regardless of the specified parameters. That's useful for using Isar in an isolate. If an instance is already open, calling `Isar.open()` will yield the existing instance regardless of the specified parameters. That's useful for using Isar in an isolate.
+1 -1
View File
@@ -185,7 +185,7 @@ final user1 = User()
..age = 25; ..age = 25;
// user does not exist so this is the same as put() // user does not exist so this is the same as put()
await isar.users.putByUsername(user1); await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}] await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User() final user2 = User()
+20 -20
View File
@@ -39,15 +39,15 @@ class Shoe {
Depending on the type of field, there are different conditions available. Depending on the type of field, there are different conditions available.
| Condition | Description | | Condition | Description |
| ----------| ------------| | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `.equalTo(value)` | Matches values that are equal to the specified `value`. | | `.equalTo(value)` | Matches values that are equal to the specified `value`. |
| `.between(lower, upper)` | Matches values that are between `lower` and `upper`. | | `.between(lower, upper)` | Matches values that are between `lower` and `upper`. |
| `.greaterThan(bound)` | Matches values that are greater than `bound`. | | `.greaterThan(bound)` | Matches values that are greater than `bound`. |
| `.lessThan(bound)` | Matches values that are less than `bound`. `null` values will be included by default because `null` is considered smaller than any other value. | | `.lessThan(bound)` | Matches values that are less than `bound`. `null` values will be included by default because `null` is considered smaller than any other value. |
| `.isNull()` | Matches values that are `null`.| | `.isNull()` | Matches values that are `null`. |
| `.isNotNull()` | Matches values that are not `null`.| | `.isNotNull()` | Matches values that are not `null`. |
| `.length()` | List, String and link length queries filter objects based on the number of elements in a list or link. | | `.length()` | List, String and link length queries filter objects based on the number of elements in a list or link. |
Let's assume the database contains four shoes with sizes 39, 40, 46 and one with an un-set (`null`) size. Unless you perform sorting, the values will be returned sorted by id. Let's assume the database contains four shoes with sizes 39, 40, 46 and one with an un-set (`null`) size. Unless you perform sorting, the values will be returned sorted by id.
@@ -71,13 +71,13 @@ isar.shoes.filter()
You can composite predicates using the following logical operators: You can composite predicates using the following logical operators:
| Operator | Description | | Operator | Description |
| ---------- | ----------- | | ---------- | ------------------------------------------------------------------------------------ |
| `.and()` | Evaluates to `true` if both left-hand and right-hand expressions evaluate to `true`. | | `.and()` | Evaluates to `true` if both left-hand and right-hand expressions evaluate to `true`. |
| `.or()` | Evaluates to `true` if either expression evaluates to `true`. | | `.or()` | Evaluates to `true` if either expression evaluates to `true`. |
| `.xor()` | Evaluates to `true` if exactly one expression evaluates to `true`. | | `.xor()` | Evaluates to `true` if exactly one expression evaluates to `true`. |
| `.not()` | Negates the result of the following expression. | | `.not()` | Negates the result of the following expression. |
| `.group()` | Group conditions and allow to specify order of evaluation. | | `.group()` | Group conditions and allow to specify order of evaluation. |
If you want to find all shoes in size 46, you can use the following query: If you want to find all shoes in size 46, you can use the following query:
@@ -152,11 +152,11 @@ A [wildcard string expression](https://en.wikipedia.org/wiki/Wildcard_character)
Sometimes it is necessary to build a query based on some conditions or for different values. Isar has a very powerful tool for building conditional queries: Sometimes it is necessary to build a query based on some conditions or for different values. Isar has a very powerful tool for building conditional queries:
| Modifier | Description | | Modifier | Description |
| --------------------- | ---------------------------------------------------- | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `.optional(cond, qb)` | Extends the query only if the `condition` is `true`. This can be used almost anywhere in a query for example to conditionally sort or limit it. | | `.optional(cond, qb)` | Extends the query only if the `condition` is `true`. This can be used almost anywhere in a query for example to conditionally sort or limit it. |
| `.anyOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **or**. | | `.anyOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **or**. |
| `.allOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **and**. | | `.allOf(list, qb)` | Extends the query for each value in `values` and combines the conditions using logical **and**. |
In this example, we build a method that can find shoes with an optional filter: In this example, we build a method that can find shoes with an optional filter:
@@ -309,7 +309,7 @@ final result = await isar.students.filter()
Link filters evaluate to `true` if at least one linked object matches the conditions. Link filters evaluate to `true` if at least one linked object matches the conditions.
Let's search for all students that have no teachers: Let's search for all students that have no teachers:
```dart ```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll(); final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
``` ```
+4 -2
View File
@@ -4,14 +4,16 @@ import 'dart:async';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pub_app/models/asset.dart'; import 'package:pub_app/models/asset.dart';
import 'package:pub_app/models/package.dart'; import 'package:pub_app/models/package.dart';
import 'package:pub_app/package_manager.dart'; import 'package:pub_app/package_manager.dart';
import 'package:pub_app/repository.dart'; import 'package:pub_app/repository.dart';
import 'package:riverpod/riverpod.dart'; import 'package:riverpod/riverpod.dart';
final isarPod = FutureProvider((ref) { final isarPod = FutureProvider((ref) async {
return Isar.open([PackageSchema, AssetSchema]); final dir = await getApplicationDocumentsDirectory();
return Isar.open([PackageSchema, AssetSchema], directory: dir.path);
}); });
final repositoryPod = Provider((ref) { final repositoryPod = Provider((ref) {
+1 -1
View File
@@ -48,7 +48,7 @@ class PubAppBar extends ConsumerWidget implements PreferredSizeWidget {
darkMode ? Icons.light_mode_rounded : Icons.dark_mode_rounded, darkMode ? Icons.light_mode_rounded : Icons.dark_mode_rounded,
), ),
onPressed: () { onPressed: () {
ref.read(darkModePod.state).state = !darkMode; ref.read(darkModePod.notifier).state = !darkMode;
}, },
), ),
], ],
+5
View File
@@ -3,13 +3,18 @@
### Enhancements ### Enhancements
- Improved performance for all operations - Improved performance for all operations
- Added `maxSizeMiB` option to `Isar.open()` to specify the maximum size of the database file
- Significantly reduced native library size - Significantly reduced native library size
- With the help of the community, the docs have been translated to a range of languages
- Improved API docs
- Added integration tests for more platforms to ensure high quality releases
### Fixes ### Fixes
- Fixed a native port that was not closed correctly in some cases - Fixed a native port that was not closed correctly in some cases
- Added swift version to podspec - Added swift version to podspec
- Fixed crash on Windows - Fixed crash on Windows
- Fixed "IndexNotFound" error
## 3.0.2 ## 3.0.2
+1 -1
View File
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright [yyyy] [name of copyright owner] Copyright 2022 Simon Leier
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
+7 -1
View File
@@ -29,8 +29,10 @@ abstract class Isar {
/// The default Isar instance name. /// The default Isar instance name.
static const String defaultName = 'default'; static const String defaultName = 'default';
/// The default max Isar size.
static const int defaultMaxSizeMiB = 2048;
/// Placeholder for an auto-increment id. /// Placeholder for an auto-increment id.
// ignore: prefer_const_declarations
static const Id autoIncrement = isarAutoIncrementId; static const Id autoIncrement = isarAutoIncrementId;
static final Map<String, Isar> _instances = <String, Isar>{}; static final Map<String, Isar> _instances = <String, Isar>{};
@@ -87,6 +89,7 @@ abstract class Isar {
List<CollectionSchema<dynamic>> schemas, { List<CollectionSchema<dynamic>> schemas, {
String? directory, String? directory,
String name = defaultName, String name = defaultName,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
bool relaxedDurability = true, bool relaxedDurability = true,
CompactCondition? compactOnLaunch, CompactCondition? compactOnLaunch,
bool inspector = true, bool inspector = true,
@@ -105,6 +108,7 @@ abstract class Isar {
schemas: schemas, schemas: schemas,
directory: directory, directory: directory,
name: name, name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability, relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch, compactOnLaunch: compactOnLaunch,
); );
@@ -115,6 +119,7 @@ abstract class Isar {
List<CollectionSchema<dynamic>> schemas, { List<CollectionSchema<dynamic>> schemas, {
String? directory, String? directory,
String name = defaultName, String name = defaultName,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
bool relaxedDurability = true, bool relaxedDurability = true,
CompactCondition? compactOnLaunch, CompactCondition? compactOnLaunch,
bool inspector = true, bool inspector = true,
@@ -133,6 +138,7 @@ abstract class Isar {
schemas: schemas, schemas: schemas,
directory: directory, directory: directory,
name: name, name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability, relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch, compactOnLaunch: compactOnLaunch,
); );
+137 -57
View File
@@ -3,7 +3,11 @@ part of isar;
/// Normal keys consist of a single object, composite keys multiple. /// Normal keys consist of a single object, composite keys multiple.
typedef IndexKey = List<Object?>; typedef IndexKey = List<Object?>;
/// Collections are used to store and retrieve your objects from Isar. /// Use `IsarCollection` instances to find, query, and create new objects of a
/// given type in Isar.
///
/// You can get an instance of `IsarCollection` by calling `isar.get<OBJ>()` or
/// by using the generated `isar.yourCollections` getter.
abstract class IsarCollection<OBJ> { abstract class IsarCollection<OBJ> {
/// The corresponding Isar instance. /// The corresponding Isar instance.
Isar get isar; Isar get isar;
@@ -14,150 +18,227 @@ abstract class IsarCollection<OBJ> {
/// The name of the collection. /// The name of the collection.
String get name => schema.name; String get name => schema.name;
/// {@template col_get}
/// Get a single object by its [id] or `null` if the object does not exist. /// Get a single object by its [id] or `null` if the object does not exist.
/// {@endtemplate}
Future<OBJ?> get(Id id) { Future<OBJ?> get(Id id) {
return getAll([id]).then((List<OBJ?> objects) => objects[0]); return getAll([id]).then((List<OBJ?> objects) => objects[0]);
} }
/// Get a single object by its [id] or `null` if the object does not exist. /// {@macro col_get}
OBJ? getSync(Id id) { OBJ? getSync(Id id) {
return getAllSync([id])[0]; return getAllSync([id])[0];
} }
/// {@template col_get_all}
/// Get a list of objects by their [ids] or `null` if an object does not /// Get a list of objects by their [ids] or `null` if an object does not
/// exist. /// exist.
/// {@endtemplate}
Future<List<OBJ?>> getAll(List<Id> ids); Future<List<OBJ?>> getAll(List<Id> ids);
/// Get a list of objects by their [ids] or `null` if an object does not /// {@macro col_get_all}
/// exist.
List<OBJ?> getAllSync(List<Id> ids); List<OBJ?> getAllSync(List<Id> ids);
/// @nodoc /// {@template col_get_by_index}
@protected /// Get a single object by the unique index [indexName] and [key].
///
/// Returns `null` if the object does not exist.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<OBJ?> getByIndex(String indexName, IndexKey key) { Future<OBJ?> getByIndex(String indexName, IndexKey key) {
return getAllByIndex(indexName, [key]) return getAllByIndex(indexName, [key])
.then((List<OBJ?> objects) => objects[0]); .then((List<OBJ?> objects) => objects[0]);
} }
/// @nodoc /// {@macro col_get_by_index}
@protected @experimental
OBJ? getByIndexSync(String indexName, IndexKey key) { OBJ? getByIndexSync(String indexName, IndexKey key) {
return getAllByIndexSync(indexName, [key])[0]; return getAllByIndexSync(indexName, [key])[0];
} }
/// @nodoc /// {@template col_get_all_by_index}
@protected /// Get a list of objects by the unique index [indexName] and [keys].
///
/// Returns `null` if the object does not exist.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<List<OBJ?>> getAllByIndex(String indexName, List<IndexKey> keys); Future<List<OBJ?>> getAllByIndex(String indexName, List<IndexKey> keys);
/// @nodoc /// {@macro col_get_all_by_index}'
@protected @experimental
List<OBJ?> getAllByIndexSync(String indexName, List<IndexKey> keys); List<OBJ?> getAllByIndexSync(String indexName, List<IndexKey> keys);
/// Insert or update an [object] and returns the assigned id. /// {@template col_put}
/// Insert or update an [object]. Returns the id of the new or updated object.
///
/// If the object has an non-final id property, it will be set to the assigned
/// id. Otherwise you should use the returned id to update the object.
/// {@endtemplate}
Future<Id> put(OBJ object) { Future<Id> put(OBJ object) {
return putAll([object]).then((List<Id> ids) => ids[0]); return putAll([object]).then((List<Id> ids) => ids[0]);
} }
/// Insert or update an [object] and returns the assigned id. /// {@macro col_put}
Id putSync(OBJ object, {bool saveLinks = true}) { Id putSync(OBJ object, {bool saveLinks = true}) {
return putAllSync([object], saveLinks: saveLinks)[0]; return putAllSync([object], saveLinks: saveLinks)[0];
} }
/// Insert or update a list of [objects] and returns the list of assigned ids. /// {@template col_put_all}
/// Insert or update a list of [objects]. Returns the list of ids of the new
/// or updated objects.
///
/// If the objects have an non-final id property, it will be set to the
/// assigned id. Otherwise you should use the returned ids to update the
/// objects.
/// {@endtemplate}
Future<List<Id>> putAll(List<OBJ> objects); Future<List<Id>> putAll(List<OBJ> objects);
/// Insert or update a list of [objects] and returns the list of assigned ids. /// {@macro col_put_all}
List<Id> putAllSync(List<OBJ> objects, {bool saveLinks = true}); List<Id> putAllSync(List<OBJ> objects, {bool saveLinks = true});
/// @nodoc /// {@template col_put_by_index}
@protected /// Insert or update the [object] by the unique index [indexName]. Returns the
/// id of the new or updated object.
///
/// If there is already an object with the same index key, it will be
/// updated and all links will be preserved. Otherwise a new object will be
/// inserted.
///
/// If the object has an non-final id property, it will be set to the assigned
/// id. Otherwise you should use the returned id to update the object.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<Id> putByIndex(String indexName, OBJ object) { Future<Id> putByIndex(String indexName, OBJ object) {
return putAllByIndex(indexName, [object]).then((List<Id> ids) => ids[0]); return putAllByIndex(indexName, [object]).then((List<Id> ids) => ids[0]);
} }
/// @nodoc /// {@macro col_put_by_index}
@protected @experimental
Id putByIndexSync(String indexName, OBJ object, {bool saveLinks = true}) { Id putByIndexSync(String indexName, OBJ object, {bool saveLinks = true}) {
return putAllByIndexSync(indexName, [object])[0]; return putAllByIndexSync(indexName, [object])[0];
} }
/// @nodoc /// {@template col_put_all_by_index}
@protected /// Insert or update a list of [objects] by the unique index [indexName].
/// Returns the list of ids of the new or updated objects.
///
/// If there is already an object with the same index key, it will be
/// updated and all links will be preserved. Otherwise a new object will be
/// inserted.
///
/// If the objects have an non-final id property, it will be set to the
/// assigned id. Otherwise you should use the returned ids to update the
/// objects.
///
/// If possible, you should use the generated type-safe methods instead.
/// {@endtemplate}
@experimental
Future<List<Id>> putAllByIndex(String indexName, List<OBJ> objects); Future<List<Id>> putAllByIndex(String indexName, List<OBJ> objects);
/// @nodoc /// {@macro col_put_all_by_index}
@protected @experimental
List<Id> putAllByIndexSync( List<Id> putAllByIndexSync(
String indexName, String indexName,
List<OBJ> objects, { List<OBJ> objects, {
bool saveLinks = true, bool saveLinks = true,
}); });
/// {@template col_delete}
/// Delete a single object by its [id]. /// Delete a single object by its [id].
/// ///
/// Returns whether the object has been deleted. Isar web always returns /// Returns whether the object has been deleted. Isar web always returns
/// `true`. /// `true`.
/// {@endtemplate}
Future<bool> delete(Id id) { Future<bool> delete(Id id) {
return deleteAll([id]).then((int count) => count == 1); return deleteAll([id]).then((int count) => count == 1);
} }
/// Delete a single object by its [id]. /// {@macro col_delete}
///
/// Returns whether the object has been deleted.
bool deleteSync(Id id) { bool deleteSync(Id id) {
return deleteAllSync([id]) == 1; return deleteAllSync([id]) == 1;
} }
/// {@template col_delete_all}
/// Delete a list of objects by their [ids]. /// Delete a list of objects by their [ids].
/// ///
/// Returns the number of objects that have been deleted. Isar web always /// Returns the number of objects that have been deleted. Isar web always
/// returns `ids.length`. /// returns `ids.length`.
/// {@endtemplate}
Future<int> deleteAll(List<Id> ids); Future<int> deleteAll(List<Id> ids);
/// Delete a list of objects by their [ids]. /// {@macro col_delete_all}
///
/// Returns the number of objects that have been deleted.
int deleteAllSync(List<Id> ids); int deleteAllSync(List<Id> ids);
/// @nodoc /// {@template col_delete_by_index}
@protected /// Delete a single object by the unique index [indexName] and [key].
///
/// Returns whether the object has been deleted. Isar web always returns
/// `true`.
/// {@endtemplate}
@experimental
Future<bool> deleteByIndex(String indexName, IndexKey key) { Future<bool> deleteByIndex(String indexName, IndexKey key) {
return deleteAllByIndex(indexName, [key]).then((int count) => count == 1); return deleteAllByIndex(indexName, [key]).then((int count) => count == 1);
} }
/// @nodoc /// {@macro col_delete_by_index}
@protected @experimental
bool deleteByIndexSync(String indexName, IndexKey key) { bool deleteByIndexSync(String indexName, IndexKey key) {
return deleteAllByIndexSync(indexName, [key]) == 1; return deleteAllByIndexSync(indexName, [key]) == 1;
} }
/// @nodoc /// {@template col_delete_all_by_index}
@protected /// Delete a list of objects by the unique index [indexName] and [keys].
///
/// Returns the number of objects that have been deleted. Isar web always
/// returns `keys.length`.
/// {@endtemplate}
@experimental
Future<int> deleteAllByIndex(String indexName, List<IndexKey> keys); Future<int> deleteAllByIndex(String indexName, List<IndexKey> keys);
/// @nodoc /// {@macro col_delete_all_by_index}
@protected @experimental
int deleteAllByIndexSync(String indexName, List<IndexKey> keys); int deleteAllByIndexSync(String indexName, List<IndexKey> keys);
/// {@template col_clear}
/// Remove all data in this collection and reset the auto increment value. /// Remove all data in this collection and reset the auto increment value.
/// {@endtemplate}
Future<void> clear(); Future<void> clear();
/// Remove all data in this collection and reset the auto increment value. /// {@macro col_clear}
void clearSync(); void clearSync();
/// Import a list of json objects. /// {@template col_import_json_raw}
/// Import a list of json objects encoded as a byte array.
///
/// The json objects must have the same structure as the objects in this
/// collection. Otherwise an exception will be thrown.
/// {@endtemplate}
Future<void> importJsonRaw(Uint8List jsonBytes); Future<void> importJsonRaw(Uint8List jsonBytes);
/// Import a list of json objects. /// {@macro col_import_json_raw}
void importJsonRawSync(Uint8List jsonBytes); void importJsonRawSync(Uint8List jsonBytes);
/// {@template col_import_json}
/// Import a list of json objects. /// Import a list of json objects.
///
/// The json objects must have the same structure as the objects in this
/// collection. Otherwise an exception will be thrown.
/// {@endtemplate}
Future<void> importJson(List<Map<String, dynamic>> json); Future<void> importJson(List<Map<String, dynamic>> json);
/// Import a list of json objects. /// {@macro col_import_json}
void importJsonSync(List<Map<String, dynamic>> json); void importJsonSync(List<Map<String, dynamic>> json);
/// Start building a query using the [QueryBuilder]. /// Start building a query using the [QueryBuilder].
///
/// You can use where clauses to only return [distinct] results. If you want
/// to reverse the order, set [sort] to [Sort.desc].
QueryBuilder<OBJ, OBJ, QWhere> where({ QueryBuilder<OBJ, OBJ, QWhere> where({
bool distinct = false, bool distinct = false,
Sort sort = Sort.asc, Sort sort = Sort.asc,
@@ -172,10 +253,10 @@ abstract class IsarCollection<OBJ> {
/// Start building a query using the [QueryBuilder]. /// Start building a query using the [QueryBuilder].
/// ///
/// Shortcut if you don't want to use indexes /// Shortcut if you don't want to use where clauses.
QueryBuilder<OBJ, OBJ, QFilterCondition> filter() => where().filter(); QueryBuilder<OBJ, OBJ, QFilterCondition> filter() => where().filter();
/// Build a query dynamically. Can be used to build a custom query language. /// Build a query dynamically for example to build a custom query language.
/// ///
/// It is highly discouraged to use this method. Only in very special cases /// It is highly discouraged to use this method. Only in very special cases
/// should it be used. If you open an issue please always mention that you /// should it be used. If you open an issue please always mention that you
@@ -196,28 +277,26 @@ abstract class IsarCollection<OBJ> {
String? property, String? property,
}); });
/// {@template col_count}
/// Returns the total number of objects in this collection. /// Returns the total number of objects in this collection.
/// ///
/// For non-web apps, this method is extremely fast and independent of the /// For non-web apps, this method is extremely fast and independent of the
/// number of objects in the collection. /// number of objects in the collection.
/// {@endtemplate}
Future<int> count(); Future<int> count();
/// Returns the total number of objects in this collection. /// {@macro col_count}
int countSync();
/// {@template col_size}
/// Returns the size of the collection in bytes. Not supported on web.
/// ///
/// For non-web apps, this method is extremely fast and independent of the /// For non-web apps, this method is extremely fast and independent of the
/// number of objects in the collection. /// number of objects in the collection.
int countSync(); /// {@endtemplate}
/// Returns the size of the collection in bytes. Not supported on web.
///
/// This method is extremely fast and independent of the number of objects in
/// the collection.
Future<int> getSize({bool includeIndexes = false, bool includeLinks = false}); Future<int> getSize({bool includeIndexes = false, bool includeLinks = false});
/// Returns the size of the collection in bytes. Not supported on web. /// {@macro col_size}
///
/// This method is extremely fast and independent of the number of objects in
/// the collection.
int getSizeSync({bool includeIndexes = false, bool includeLinks = false}); int getSizeSync({bool includeIndexes = false, bool includeLinks = false});
/// Watch the collection for changes. /// Watch the collection for changes.
@@ -225,13 +304,14 @@ abstract class IsarCollection<OBJ> {
/// If [fireImmediately] is `true`, an event will be fired immediately. /// If [fireImmediately] is `true`, an event will be fired immediately.
Stream<void> watchLazy({bool fireImmediately = false}); Stream<void> watchLazy({bool fireImmediately = false});
/// Watch an object with [id] for changes. /// Watch the object with [id] for changes. If a change occurs, the new object
/// will be returned in the stream.
/// ///
/// Objects that don't exist (yet) can also be watched. If [fireImmediately] /// Objects that don't exist (yet) can also be watched. If [fireImmediately]
/// is `true`, the object will be sent to the consumer immediately. /// is `true`, the object will be sent to the consumer immediately.
Stream<OBJ?> watchObject(Id id, {bool fireImmediately = false}); Stream<OBJ?> watchObject(Id id, {bool fireImmediately = false});
/// Watch an object with [id] for changes. /// Watch the object with [id] for changes.
/// ///
/// If [fireImmediately] is `true`, an event will be fired immediately. /// If [fireImmediately] is `true`, an event will be fired immediately.
Stream<void> watchObjectLazy(Id id, {bool fireImmediately = false}); Stream<void> watchObjectLazy(Id id, {bool fireImmediately = false});
+7 -6
View File
@@ -1,5 +1,5 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: avoid_print, avoid_dynamic_calls // ignore_for_file: avoid_print
part of isar; part of isar;
@@ -243,11 +243,12 @@ abstract class _IsarConnect {
if (objects.isNotEmpty) { if (objects.isNotEmpty) {
dynamic object = objects.first; dynamic object = objects.first;
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
final key = object is List ? int.parse(keys[i]) : keys[i]; if (i == keys.length - 1 && object is Map) {
if (i == keys.length - 1) { object[keys[i]] = cEdit.value;
object[key] = cEdit.value; } else if (object is Map) {
} else { object = object[keys[i]];
object = object[key]; } else if (object is List) {
object = object[int.parse(keys[i])];
} }
} }
try { try {
+16 -17
View File
@@ -1,7 +1,7 @@
part of isar; part of isar;
/// @nodoc /// @nodoc
@protected @sealed
abstract class IsarLinkBase<OBJ> { abstract class IsarLinkBase<OBJ> {
/// Is the containing object managed by Isar? /// Is the containing object managed by Isar?
bool get isAttached; bool get isAttached;
@@ -12,32 +12,33 @@ abstract class IsarLinkBase<OBJ> {
/// Has this link been loaded? /// Has this link been loaded?
bool get isLoaded; bool get isLoaded;
/// {@template link_load}
/// Loads the linked object(s) from the database /// Loads the linked object(s) from the database
/// {@endtemplate}
Future<void> load(); Future<void> load();
/// Loads the linked object(s) from the database /// {@macro link_load}
void loadSync(); void loadSync();
/// {@template link_save}
/// Saves the linked object(s) to the database if there are changes. /// Saves the linked object(s) to the database if there are changes.
/// ///
/// Also puts new objects into the database that have id set to `null` or /// Also puts new objects into the database that have id set to `null` or
/// `Isar.autoIncrement`. /// `Isar.autoIncrement`.
/// {@endtemplate}
Future<void> save(); Future<void> save();
/// Saves the linked object(s) to the database if there are changes. /// {@macro link_save}
///
/// Also puts new objects into the database that have id set to `null` or
/// `Isar.autoIncrement`.
void saveSync(); void saveSync();
/// {@template link_reset}
/// Unlinks all linked object(s). /// Unlinks all linked object(s).
/// ///
/// You can even call this method on links that have not been loaded yet. /// You can even call this method on links that have not been loaded yet.
/// {@endtemplate}
Future<void> reset(); Future<void> reset();
/// Unlinks all linked object(s). /// {@macro link_reset}
///
/// You can even call this method on links that have not been loaded yet.
void resetSync(); void resetSync();
/// @nodoc /// @nodoc
@@ -77,20 +78,19 @@ abstract class IsarLinks<OBJ> implements IsarLinkBase<OBJ>, Set<OBJ> {
@override @override
void loadSync({bool overrideChanges = true}); void loadSync({bool overrideChanges = true});
/// {@template links_update}
/// Creates and removes the specified links in the database. /// Creates and removes the specified links in the database.
/// ///
/// This operation does not alter the state of the local copy of this link /// This operation does not alter the state of the local copy of this link
/// and it can even be used without loading the link. /// and it can even be used without loading the link.
/// {@endtemplate}
Future<void> update({ Future<void> update({
Iterable<OBJ> link = const [], Iterable<OBJ> link = const [],
Iterable<OBJ> unlink = const [], Iterable<OBJ> unlink = const [],
bool reset = false, bool reset = false,
}); });
/// Creates and removes the specified links in the database. /// {@macro links_update}
///
/// This operation does not alter the state of the local copy of this link
/// and it can even be used without loading the link.
void updateSync({ void updateSync({
Iterable<OBJ> link = const [], Iterable<OBJ> link = const [],
Iterable<OBJ> unlink = const [], Iterable<OBJ> unlink = const [],
@@ -100,15 +100,14 @@ abstract class IsarLinks<OBJ> implements IsarLinkBase<OBJ>, Set<OBJ> {
/// Starts a query for linked objects. /// Starts a query for linked objects.
QueryBuilder<OBJ, OBJ, QAfterFilterCondition> filter(); QueryBuilder<OBJ, OBJ, QAfterFilterCondition> filter();
/// {@template links_count}
/// Counts the linked objects in the database. /// Counts the linked objects in the database.
/// ///
/// It does not take the local state into account and can even be used /// It does not take the local state into account and can even be used
/// without loading the link. /// without loading the link.
/// {@endtemplate}
Future<int> count() => filter().count(); Future<int> count() => filter().count();
/// Counts the linked objects in the database. /// {@macro links_count}
///
/// It does not take the local state into account and can even be used
/// without loading the link.
int countSync() => filter().countSync(); int countSync() => filter().countSync();
} }
+1
View File
@@ -2,6 +2,7 @@
part of isar; part of isar;
/// @nodoc
@protected @protected
abstract class IsarReader { abstract class IsarReader {
bool readBool(int offset); bool readBool(int offset);
+1
View File
@@ -2,6 +2,7 @@
part of isar; part of isar;
/// @nodoc
@protected @protected
abstract class IsarWriter { abstract class IsarWriter {
void writeBool(int offset, bool? value); void writeBool(int offset, bool? value);
@@ -1285,6 +1285,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char> name, ffi.Pointer<ffi.Char> name,
ffi.Pointer<ffi.Char> path, ffi.Pointer<ffi.Char> path,
ffi.Pointer<ffi.Char> schema_json, ffi.Pointer<ffi.Char> schema_json,
int max_size_mib,
bool relaxed_durability, bool relaxed_durability,
int compact_min_file_size, int compact_min_file_size,
int compact_min_bytes, int compact_min_bytes,
@@ -1295,6 +1296,7 @@ class IsarCoreBindings {
name, name,
path, path,
schema_json, schema_json,
max_size_mib,
relaxed_durability, relaxed_durability,
compact_min_file_size, compact_min_file_size,
compact_min_bytes, compact_min_bytes,
@@ -1309,6 +1311,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Int64,
ffi.Bool, ffi.Bool,
ffi.Uint32, ffi.Uint32,
ffi.Uint32, ffi.Uint32,
@@ -1319,6 +1322,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
int,
bool, bool,
int, int,
int, int,
@@ -1329,6 +1333,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char> name, ffi.Pointer<ffi.Char> name,
ffi.Pointer<ffi.Char> path, ffi.Pointer<ffi.Char> path,
ffi.Pointer<ffi.Char> schema_json, ffi.Pointer<ffi.Char> schema_json,
int max_size_mib,
bool relaxed_durability, bool relaxed_durability,
int compact_min_file_size, int compact_min_file_size,
int compact_min_bytes, int compact_min_bytes,
@@ -1340,6 +1345,7 @@ class IsarCoreBindings {
name, name,
path, path,
schema_json, schema_json,
max_size_mib,
relaxed_durability, relaxed_durability,
compact_min_file_size, compact_min_file_size,
compact_min_bytes, compact_min_bytes,
@@ -1355,6 +1361,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Int64,
ffi.Bool, ffi.Bool,
ffi.Uint32, ffi.Uint32,
ffi.Uint32, ffi.Uint32,
@@ -1367,6 +1374,7 @@ class IsarCoreBindings {
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>,
int,
bool, bool,
int, int,
int, int,
+6 -2
View File
@@ -79,6 +79,7 @@ Future<Isar> openIsar({
required List<CollectionSchema<dynamic>> schemas, required List<CollectionSchema<dynamic>> schemas,
String? directory, String? directory,
required String name, required String name,
required int maxSizeMiB,
required bool relaxedDurability, required bool relaxedDurability,
CompactCondition? compactOnLaunch, CompactCondition? compactOnLaunch,
}) async { }) async {
@@ -105,6 +106,7 @@ Future<Isar> openIsar({
namePtr, namePtr,
dirPtr, dirPtr,
schemaStrPtr, schemaStrPtr,
maxSizeMiB,
relaxedDurability, relaxedDurability,
compactMinFileSize ?? 0, compactMinFileSize ?? 0,
compactMinBytes ?? 0, compactMinBytes ?? 0,
@@ -122,8 +124,9 @@ Future<Isar> openIsar({
Isar openIsarSync({ Isar openIsarSync({
required List<CollectionSchema<dynamic>> schemas, required List<CollectionSchema<dynamic>> schemas,
String? directory, String? directory,
String name = 'isar', required String name,
bool relaxedDurability = true, required int maxSizeMiB,
required bool relaxedDurability,
CompactCondition? compactOnLaunch, CompactCondition? compactOnLaunch,
}) { }) {
initializeCoreBinary(); initializeCoreBinary();
@@ -146,6 +149,7 @@ Isar openIsarSync({
namePtr, namePtr,
dirPtr, dirPtr,
schemaStrPtr, schemaStrPtr,
maxSizeMiB,
relaxedDurability, relaxedDurability,
compactMinFileSize ?? 0, compactMinFileSize ?? 0,
compactMinBytes ?? 0, compactMinBytes ?? 0,
+16 -28
View File
@@ -1,5 +1,3 @@
// ignore_for_file: prefer_initializing_formals
part of isar; part of isar;
/// A where clause to traverse an Isar index. /// A where clause to traverse an Isar index.
@@ -20,20 +18,18 @@ class IdWhereClause extends WhereClause {
/// Where clause that matches all id values greater than the given [lower] /// Where clause that matches all id values greater than the given [lower]
/// bound. /// bound.
const IdWhereClause.greaterThan({ const IdWhereClause.greaterThan({
required Id lower, required Id this.lower,
this.includeLower = true, this.includeLower = true,
}) : lower = lower, }) : upper = null,
upper = null,
includeUpper = true, includeUpper = true,
super._(); super._();
/// Where clause that matches all id values less than the given [upper] /// Where clause that matches all id values less than the given [upper]
/// bound. /// bound.
const IdWhereClause.lessThan({ const IdWhereClause.lessThan({
required Id upper, required Id this.upper,
this.includeUpper = true, this.includeUpper = true,
}) : upper = upper, }) : lower = null,
lower = null,
includeLower = true, includeLower = true,
super._(); super._();
@@ -86,11 +82,10 @@ class IndexWhereClause extends WhereClause {
/// for equality. /// for equality.
const IndexWhereClause.greaterThan({ const IndexWhereClause.greaterThan({
required this.indexName, required this.indexName,
required IndexKey lower, required IndexKey this.lower,
this.includeLower = true, this.includeLower = true,
this.epsilon = Query.epsilon, this.epsilon = Query.epsilon,
}) : lower = lower, }) : upper = null,
upper = null,
includeUpper = true, includeUpper = true,
super._(); super._();
@@ -101,11 +96,10 @@ class IndexWhereClause extends WhereClause {
/// for equality. /// for equality.
const IndexWhereClause.lessThan({ const IndexWhereClause.lessThan({
required this.indexName, required this.indexName,
required IndexKey upper, required IndexKey this.upper,
this.includeUpper = true, this.includeUpper = true,
this.epsilon = Query.epsilon, this.epsilon = Query.epsilon,
}) : upper = upper, }) : lower = null,
lower = null,
includeLower = true, includeLower = true,
super._(); super._();
@@ -127,14 +121,12 @@ class IndexWhereClause extends WhereClause {
/// are checked for equality. /// are checked for equality.
const IndexWhereClause.between({ const IndexWhereClause.between({
required this.indexName, required this.indexName,
required IndexKey lower, required IndexKey this.lower,
this.includeLower = true, this.includeLower = true,
required IndexKey upper, required IndexKey this.upper,
this.includeUpper = true, this.includeUpper = true,
this.epsilon = Query.epsilon, this.epsilon = Query.epsilon,
}) : lower = lower, }) : super._();
upper = upper,
super._();
/// The Isar name of the index to be used. /// The Isar name of the index to be used.
final String indexName; final String indexName;
@@ -244,12 +236,11 @@ class FilterCondition extends FilterOperation {
/// ///
/// For lists, at least one of the values in the list has to match. /// For lists, at least one of the values in the list has to match.
const FilterCondition.equalTo({ const FilterCondition.equalTo({
required String property, required this.property,
required Object? value, required Object? value,
this.caseSensitive = true, this.caseSensitive = true,
this.epsilon = Query.epsilon, this.epsilon = Query.epsilon,
}) : type = FilterConditionType.equalTo, }) : type = FilterConditionType.equalTo,
property = property,
value1 = value, value1 = value,
include1 = true, include1 = true,
value2 = null, value2 = null,
@@ -578,20 +569,17 @@ class LinkFilter extends FilterOperation {
/// Create a filter condition based on a link. /// Create a filter condition based on a link.
const LinkFilter({ const LinkFilter({
required this.linkName, required this.linkName,
required FilterOperation filter, required FilterOperation this.filter,
}) : filter = filter, }) : lower = null,
lower = null,
upper = null, upper = null,
super._(); super._();
/// Create a filter condition based on the number of linked objects. /// Create a filter condition based on the number of linked objects.
const LinkFilter.length({ const LinkFilter.length({
required this.linkName, required this.linkName,
required int lower, required int this.lower,
required int upper, required int this.upper,
}) : filter = null, }) : filter = null,
lower = lower,
upper = upper,
assert(lower >= 0 && upper >= 0, 'Link length must be positive.'), assert(lower >= 0 && upper >= 0, 'Link length must be positive.'),
super._(); super._();
+19 -3
View File
@@ -47,6 +47,7 @@ impl IsarInstance {
name: &str, name: &str,
dir: Option<&str>, dir: Option<&str>,
mut schema: Schema, mut schema: Schema,
max_size_mib: usize,
relaxed_durability: bool, relaxed_durability: bool,
compact_condition: Option<CompactCondition>, compact_condition: Option<CompactCondition>,
) -> Result<Arc<Self>> { ) -> Result<Arc<Self>> {
@@ -65,6 +66,7 @@ impl IsarInstance {
dir, dir,
instance_id, instance_id,
schema, schema,
max_size_mib,
relaxed_durability, relaxed_durability,
compact_condition, compact_condition,
)?; )?;
@@ -107,6 +109,7 @@ impl IsarInstance {
dir: &str, dir: &str,
instance_id: u64, instance_id: u64,
mut schema: Schema, mut schema: Schema,
max_size_mib: usize,
relaxed_durability: bool, relaxed_durability: bool,
compact_condition: Option<CompactCondition>, compact_condition: Option<CompactCondition>,
) -> Result<Self> { ) -> Result<Self> {
@@ -115,8 +118,13 @@ impl IsarInstance {
Self::move_old_database(name, dir, &isar_file); Self::move_old_database(name, dir, &isar_file);
let db_count = schema.count_dbs() as u64 + 3; let db_count = schema.count_dbs() as u64 + 3;
let env = Env::create(&isar_file, db_count, relaxed_durability) let env = Env::create(
.map_err(|e| IsarError::EnvError { error: Box::new(e) })?; &isar_file,
db_count,
max_size_mib.max(1),
relaxed_durability,
)
.map_err(|e| IsarError::EnvError { error: Box::new(e) })?;
let txn = env.txn(true)?; let txn = env.txn(true)?;
let mut manager = SchemaManager::create(instance_id, &txn)?; let mut manager = SchemaManager::create(instance_id, &txn)?;
@@ -154,7 +162,15 @@ impl IsarInstance {
if let Some(instance) = instance { if let Some(instance) = instance {
Ok(instance) Ok(instance)
} else { } else {
Self::open_internal(name, dir, instance_id, schema, relaxed_durability, None) Self::open_internal(
name,
dir,
instance_id,
schema,
max_size_mib,
relaxed_durability,
None,
)
} }
} else { } else {
Ok(instance) Ok(instance)
+13 -24
View File
@@ -11,10 +11,15 @@ pub struct Env {
unsafe impl Sync for Env {} unsafe impl Sync for Env {}
unsafe impl Send for Env {} unsafe impl Send for Env {}
const MB: isize = 1 << 20; const MIB: isize = 1 << 20;
impl Env { impl Env {
pub fn create(path: &str, max_dbs: u64, relaxed_durability: bool) -> Result<Env> { pub fn create(
path: &str,
max_dbs: u64,
max_size_mib: usize,
relaxed_durability: bool,
) -> Result<Env> {
let path = CString::new(path.as_bytes()).unwrap(); let path = CString::new(path.as_bytes()).unwrap();
let mut env: *mut ffi::MDBX_env = ptr::null_mut(); let mut env: *mut ffi::MDBX_env = ptr::null_mut();
unsafe { unsafe {
@@ -31,15 +36,17 @@ impl Env {
flags |= ffi::MDBX_NOMETASYNC; flags |= ffi::MDBX_NOMETASYNC;
} }
let max_size = (max_size_mib as isize).saturating_mul(MIB);
let mut err_code = 0; let mut err_code = 0;
for i in 0..9 { for i in 0..9 {
mdbx_result(ffi::mdbx_env_set_geometry( mdbx_result(ffi::mdbx_env_set_geometry(
env, env,
MB, MIB,
0, 0,
(2000 - i * 200) * MB, max_size - i * max_size / 10,
5 * MB, 5 * MIB,
20 * MB, 20 * MIB,
-1, -1,
))?; ))?;
@@ -97,21 +104,3 @@ impl Drop for Env {
} }
} }
} }
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_create() {
get_env();
}
pub fn get_env() -> Env {
let mut dir = std::env::temp_dir();
let r: u64 = rand::random();
dir.push(&r.to_string());
Env::create(dir.to_str().unwrap(), 50, false).unwrap()
}
}
+4 -1
View File
@@ -47,7 +47,10 @@ pub fn mdbx_result(err_code: c_int) -> Result<()> {
let err = CStr::from_ptr(err_raw); let err = CStr::from_ptr(err_raw);
Err(IsarError::MdbxError { Err(IsarError::MdbxError {
code: other, code: other,
message: err.to_str().unwrap().to_string(), message: err
.to_str()
.unwrap_or("Cannot decode error message")
.to_string(),
}) })
}, },
} }
+11 -2
View File
@@ -30,6 +30,7 @@ pub unsafe extern "C" fn isar_instance_create(
name: *const c_char, name: *const c_char,
path: *const c_char, path: *const c_char,
schema_json: *const c_char, schema_json: *const c_char,
max_size_mib: i64,
relaxed_durability: bool, relaxed_durability: bool,
compact_min_file_size: u32, compact_min_file_size: u32,
compact_min_bytes: u32, compact_min_bytes: u32,
@@ -51,8 +52,14 @@ pub unsafe extern "C" fn isar_instance_create(
}) })
}; };
let instance = let instance = IsarInstance::open(
IsarInstance::open(name, path, schema, relaxed_durability, compact_condition)?; name,
path,
schema,
max_size_mib as usize,
relaxed_durability,
compact_condition,
)?;
isar.write(Arc::into_raw(instance)); isar.write(Arc::into_raw(instance));
Ok(()) Ok(())
}; };
@@ -66,6 +73,7 @@ pub unsafe extern "C" fn isar_instance_create_async(
name: *const c_char, name: *const c_char,
path: *const c_char, path: *const c_char,
schema_json: *const c_char, schema_json: *const c_char,
max_size_mib: i64,
relaxed_durability: bool, relaxed_durability: bool,
compact_min_file_size: u32, compact_min_file_size: u32,
compact_min_bytes: u32, compact_min_bytes: u32,
@@ -86,6 +94,7 @@ pub unsafe extern "C" fn isar_instance_create_async(
name.0, name.0,
path.0, path.0,
schema_json.0, schema_json.0,
max_size_mib,
relaxed_durability, relaxed_durability,
compact_min_file_size, compact_min_file_size,
compact_min_bytes, compact_min_bytes,
@@ -1,6 +1,6 @@
#Sat Aug 27 10:16:05 CEST 2022 #Sat Nov 12 16:30:49 CET 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
+5 -2
View File
@@ -5,12 +5,15 @@ Use the following commands to run the tests on a connected device:
### Unit tests ### Unit tests
``` ```
sh tool/setup_tests.sh sh tool/prepare_tests.sh
sh tool/build.sh
dart test dart test
``` ```
### Integration tests ### Integration tests
``` ```
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart sh tool/prepare_tests.sh
sh tool/build.sh
flutter test integration_test/integration_test.dart
``` ```
+6 -5
View File
@@ -26,25 +26,23 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion 31 compileSdkVersion 33
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.isar.isar_test" applicationId "dev.isar.isar_test"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 31 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
} }
@@ -56,4 +54,7 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
} }
@@ -0,0 +1,12 @@
package dev.isar.isar_test;
import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.integration_test.FlutterTestRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;
@RunWith(FlutterTestRunner.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class, true, false);
}
+2 -2
View File
@@ -2,7 +2,7 @@ buildscript {
ext.kotlin_version = '1.7.20' ext.kotlin_version = '1.7.20'
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { dependencies {
@@ -14,7 +14,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
} }
@@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017 #Wed Nov 09 23:26:39 CET 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip zipStoreBase=GRADLE_USER_HOME
@@ -1,4 +1,4 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print, depend_on_referenced_packages
import 'dart:async'; import 'dart:async';
@@ -12,21 +12,7 @@ import 'all_tests.dart' as tests;
void main() async { void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final completer = Completer<void>();
final completer = Completer<List<String>>();
executeTests(completer);
testWidgets('Isar', (WidgetTester tester) async {
await tester.pumpWidget(Container());
final errors = await completer.future;
for (final err in errors) {
print(err);
}
expect(errors.isEmpty, true);
});
}
void executeTests(Completer<List<String>> completer) {
group('Integration test', () { group('Integration test', () {
setUpAll(() async { setUpAll(() async {
if (!kIsWeb) { if (!kIsWeb) {
@@ -34,11 +20,15 @@ void executeTests(Completer<List<String>> completer) {
testTempPath = dir.path; testTempPath = dir.path;
} }
}); });
tearDownAll(() { tearDownAll(completer.complete);
final result = testCount == 0 ? ['No tests were executed'] : testErrors;
completer.complete(result);
});
tests.main(); tests.main();
}); });
testWidgets('Isar', (WidgetTester tester) async {
await tester.pumpWidget(Container());
await completer.future;
expect(testCount > 0, true);
expect(testErrors, isEmpty);
});
} }
-3
View File
@@ -1,3 +0,0 @@
import 'package:integration_test/integration_test_driver.dart';
void main() => integrationDriver();
+7
View File
@@ -3,3 +3,10 @@ export 'src/listener.dart';
export 'src/matchers.dart'; export 'src/matchers.dart';
export 'src/sync_async_helper.dart'; export 'src/sync_async_helper.dart';
export 'src/sync_future.dart'; export 'src/sync_future.dart';
export 'src/twitter/entities.dart';
export 'src/twitter/geo.dart';
export 'src/twitter/media.dart';
export 'src/twitter/tweet.dart';
export 'src/twitter/user.dart';
export 'src/twitter/util.dart';
+3 -1
View File
@@ -13,7 +13,7 @@ import 'package:path/path.dart' as path;
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:test_api/src/backend/invoker.dart'; import 'package:test_api/src/backend/invoker.dart';
const bool kIsWeb = identical(0, 0.0); const kIsWeb = identical(0, 0.0);
final testErrors = <String>[]; final testErrors = <String>[];
int testCount = 0; int testCount = 0;
@@ -111,6 +111,7 @@ Future<Isar> openTempIsar(
List<CollectionSchema<dynamic>> schemas, { List<CollectionSchema<dynamic>> schemas, {
String? name, String? name,
String? directory, String? directory,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
CompactCondition? compactOnLaunch, CompactCondition? compactOnLaunch,
bool closeAutomatically = true, bool closeAutomatically = true,
}) async { }) async {
@@ -124,6 +125,7 @@ Future<Isar> openTempIsar(
final isar = await tOpen( final isar = await tOpen(
schemas: schemas, schemas: schemas,
name: name ?? getRandomName(), name: name ?? getRandomName(),
maxSizeMiB: maxSizeMiB,
directory: directory ?? testTempPath, directory: directory ?? testTempPath,
compactOnLaunch: compactOnLaunch, compactOnLaunch: compactOnLaunch,
); );
+43 -13
View File
@@ -2,27 +2,57 @@ import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:isar_test/src/rust_target.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
String getBinaryPath() {
final rootDir = path.dirname(path.dirname(Directory.current.path));
final target = getRustTarget();
final binaryName = Platform.isWindows
? 'isar.dll'
: Platform.isMacOS
? 'libisar.dylib'
: 'libisar.so';
return path.join(rootDir, 'target', target, 'debug', binaryName);
}
Future<void> init() async { Future<void> init() async {
if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) {
final rootDir = path.dirname(path.dirname(Directory.current.path));
final binaryName = Platform.isWindows
? 'isar.dll'
: Platform.isMacOS
? 'libisar.dylib'
: 'libisar.so';
try { try {
await Isar.initializeIsarCore(); await Isar.initializeIsarCore();
} catch (e) { } catch (e) {
await Isar.initializeIsarCore( await Isar.initializeIsarCore(
libraries: {Abi.current(): getBinaryPath()}, libraries: {
Abi.macosArm64: path.join(
rootDir,
'target',
'aarch64-apple-darwin',
'release',
binaryName,
),
Abi.macosX64: path.join(
rootDir,
'target',
'x86_64-apple-darwin',
'release',
binaryName,
),
Abi.linuxArm64: path.join(
rootDir,
'target',
'aarch64-unknown-linux-gnu',
'release',
binaryName,
),
Abi.linuxX64: path.join(
rootDir,
'target',
'x86_64-unknown-linux-gnu',
'release',
binaryName,
),
Abi.windowsX64: path.join(
rootDir,
'target',
'x86_64-pc-windows-msvc',
'release',
binaryName,
),
},
); );
} }
} }
+11 -1
View File
@@ -71,7 +71,17 @@ Matcher throwsIsarError([String? contains]) {
return throwsA(isIsarError(contains)); return throwsA(isIsarError(contains));
} }
final Matcher throwsAssertionError = throwsA(isA<AssertionError>()); Matcher get throwsAssertionError {
var matcher = anything;
assert(
() {
matcher = throwsA(isA<AssertionError>());
return true;
}(),
'only in debug mode',
);
return matcher;
}
bool listEquals<T>(List<T>? a, List<T>? b) { bool listEquals<T>(List<T>? a, List<T>? b) {
if (a == null) { if (a == null) {
@@ -1,18 +0,0 @@
import 'dart:ffi';
String getRustTarget() {
switch (Abi.current()) {
case Abi.macosArm64:
return 'aarch64-apple-darwin';
case Abi.macosX64:
return 'x86_64-apple-darwin';
case Abi.linuxArm64:
return 'aarch64-unknown-linux-gnu';
case Abi.linuxX64:
return 'x86_64-unknown-linux-gnu';
case Abi.windowsX64:
return 'x86_64-pc-windows-gnu';
default:
throw UnsupportedError('Unsupported ABI: ${Abi.current()}');
}
}
@@ -10,6 +10,7 @@ Future<Isar> tOpen({
required List<CollectionSchema<dynamic>> schemas, required List<CollectionSchema<dynamic>> schemas,
String? directory, String? directory,
String name = Isar.defaultName, String name = Isar.defaultName,
int maxSizeMiB = Isar.defaultMaxSizeMiB,
bool relaxedDurability = true, bool relaxedDurability = true,
CompactCondition? compactOnLaunch, CompactCondition? compactOnLaunch,
}) { }) {
@@ -18,6 +19,7 @@ Future<Isar> tOpen({
schemas, schemas,
directory: directory, directory: directory,
name: name, name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability, relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch, compactOnLaunch: compactOnLaunch,
inspector: false, inspector: false,
@@ -28,6 +30,7 @@ Future<Isar> tOpen({
schemas, schemas,
directory: directory, directory: directory,
name: name, name: name,
maxSizeMiB: maxSizeMiB,
relaxedDurability: relaxedDurability, relaxedDurability: relaxedDurability,
compactOnLaunch: compactOnLaunch, compactOnLaunch: compactOnLaunch,
inspector: false, inspector: false,
@@ -1,12 +1,11 @@
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:isar_test/src/twitter/entities.dart';
import 'package:isar_test/src/twitter/geo.dart';
import 'package:isar_test/src/twitter/media.dart';
import 'package:isar_test/src/twitter/user.dart';
import 'package:isar_test/src/twitter/util.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'entities.dart';
import 'geo.dart';
import 'media.dart';
import 'user.dart';
import 'util.dart';
part 'tweet.g.dart'; part 'tweet.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake, createToJson: false) @JsonSerializable(fieldRename: FieldRename.snake, createToJson: false)
@@ -1,9 +1,8 @@
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:isar_test/src/twitter/entities.dart';
import 'package:isar_test/src/twitter/util.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'entities.dart';
import 'util.dart';
part 'user.g.dart'; part 'user.g.dart';
@JsonSerializable(fieldRename: FieldRename.snake, createToJson: false) @JsonSerializable(fieldRename: FieldRename.snake, createToJson: false)
+2 -2
View File
@@ -15,7 +15,7 @@ dependencies:
intl: 0.17.0 intl: 0.17.0
isar: any isar: any
isar_flutter_libs: any isar_flutter_libs: any
json_annotation: ^4.2.0 json_annotation: ^4.7.0
meta: ^1.8.0 meta: ^1.8.0
path: ^1.8.2 path: ^1.8.2
path_provider: ^2.0.10 path_provider: ^2.0.10
@@ -27,7 +27,7 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
isar_generator: any isar_generator: any
json_serializable: ^6.3.1 json_serializable: ^6.3.1
very_good_analysis: ^3.0.1 very_good_analysis: ^3.0.1
dependency_overrides: dependency_overrides:
@@ -54,10 +54,7 @@ void main() {
isarTestVm('.copyToFile() should keep the same content', () async { isarTestVm('.copyToFile() should keep the same content', () async {
final copiedDbFilename = getRandomName(); final copiedDbFilename = getRandomName();
final copiedDbFile = File( final copiedDbFile = File(
path.join( path.join(isar.directory!, '$copiedDbFilename.isar'),
isar.directory!,
'$copiedDbFilename.isar',
),
); );
await isar.copyToFile(copiedDbFile.path); await isar.copyToFile(copiedDbFile.path);
@@ -73,7 +70,6 @@ void main() {
copiedIsar.models.where(), copiedIsar.models.where(),
originalObjs, originalObjs,
); );
await copiedDbFile.delete();
}); });
isarTestVm('.copyToFile() should compact copied file', () async { isarTestVm('.copyToFile() should compact copied file', () async {
@@ -1,11 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart'; import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
import 'twitter/tweet.dart';
void main() { void main() {
group('JSON', () { group('JSON', () {
@@ -0,0 +1,38 @@
import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
part 'max_size_test.g.dart';
@collection
class Model {
final Id id = Isar.autoIncrement;
final value = '123456789' * 1000;
}
void main() {
group('Max Size', () {
test('default', () async {
final isar = await openTempIsar([ModelSchema]);
await isar.writeTxn(() async {
await isar.models.putAll(List.filled(10000, Model()));
});
});
test('1MB', () async {
final isar = await openTempIsar([ModelSchema], maxSizeMiB: 1);
expect(
isar.writeTxn(() async {
await isar.models.putAll(List.filled(100, Model()));
}),
throwsIsarError('The database is full'),
);
await isar.writeTxn(() async {
await isar.models.putAll(List.filled(50, Model()));
});
});
});
}
@@ -1,8 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter_test/flutter_test.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart'; import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart';
part 'long_string_test.g.dart'; part 'long_string_test.g.dart';
@@ -37,49 +37,51 @@ String _randomStr(int length) {
} }
void main() { void main() {
late Isar isar; group('Long String', () {
late Isar isar;
setUp(() async { setUp(() async {
isar = await openTempIsar([StringModelSchema]); isar = await openTempIsar([StringModelSchema]);
});
isarTest('Long String', () async {
final models = <StringModel>[
for (var i = 0; i < 100; i++)
StringModel(
string: '${_randomStr(50000)}test$i${_randomStr(50000)}',
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
}); });
await qEqual(isar.stringModels.where(), models); isarTest('Single', () async {
final models = <StringModel>[
for (var i = 0; i < 100; i++)
StringModel(
string: '${_randomStr(50000)}test$i${_randomStr(50000)}',
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
});
await qEqual( await qEqual(isar.stringModels.where(), models);
isar.stringModels.filter().stringContains('test75'),
[models[75]],
);
await qEqual(
isar.stringModels.filter().stringMatches('*test66*'),
[models[66]],
);
});
isarTest('Long String List', () async { await qEqual(
final models = <StringModel>[ isar.stringModels.filter().stringContains('test75'),
for (var i = 0; i < 10; i++) [models[75]],
StringModel( );
stringList: [ await qEqual(
for (var j = 0; j < 100; j++) isar.stringModels.filter().stringMatches('*test66*'),
'${_randomStr(10000)}test${i}_$j${_randomStr(10000)}', [models[66]],
], );
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
}); });
await qEqual(isar.stringModels.where(), models); isarTest('List', () async {
final models = <StringModel>[
for (var i = 0; i < 10; i++)
StringModel(
stringList: [
for (var j = 0; j < 100; j++)
'${_randomStr(10000)}test${i}_$j${_randomStr(10000)}',
],
),
];
await isar.tWriteTxn(() async {
await isar.stringModels.tPutAll(models);
});
await qEqual(isar.stringModels.where(), models);
});
}); });
} }
@@ -5,9 +5,6 @@ import 'package:isar/isar.dart';
import 'package:isar_test/isar_test.dart'; import 'package:isar_test/isar_test.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'twitter/entities.dart';
import 'twitter/tweet.dart';
Future<List<Tweet>> downloadTweets(String dir, int index) async { Future<List<Tweet>> downloadTweets(String dir, int index) async {
final i = index < 10 ? '0$index' : '$index'; final i = index < 10 ? '0$index' : '$index';
final file = File('$dir/tweets$i.json'); final file = File('$dir/tweets$i.json');
@@ -9,12 +9,17 @@ void main() {
.toList(); .toList();
final imports = files.map((String e) { final imports = files.map((String e) {
return "import '$e' as ${e.split('.')[0].replaceAll(p.separator, '_')};"; final dartPath = e.replaceAll(p.separator, '/');
final name = e.split('.')[0].replaceAll(p.separator, '_');
return "import '../$dartPath' as $name;";
}).join('\n'); }).join('\n');
final calls = files.map((String e) { final calls = files.map((String e) {
final content = File(e).readAsStringSync(); final content = File(e).readAsStringSync();
final call = "${e.split('.')[0].replaceAll(p.separator, '_')}.main();"; var call = "${e.split('.')[0].replaceAll(p.separator, '_')}.main();";
if (e.contains('stress')) {
call = 'if (stress) $call';
}
if (content.startsWith("@TestOn('vm')")) { if (content.startsWith("@TestOn('vm')")) {
return 'if (!kIsWeb) $call'; return 'if (!kIsWeb) $call';
} else { } else {
@@ -29,9 +34,11 @@ void main() {
$imports $imports
void main() { void main() {
const stress = bool.fromEnvironment('STRESS');
$calls $calls
} }
"""; """;
File('all_tests.dart').writeAsStringSync(code); Directory('integration_test').createSync();
File('integration_test${p.separator}all_tests.dart').writeAsStringSync(code);
} }
@@ -1,53 +0,0 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:isar_test/src/rust_target.dart';
import 'package:path/path.dart' as path;
void main() {
print('Building Isar Core');
final rootDir = path.dirname(path.dirname(Directory.current.path));
final rustResult = Process.runSync(
'cargo',
['build', '--target', getRustTarget()],
runInShell: true,
workingDirectory: rootDir,
);
if (rustResult.exitCode != 0) {
throw Exception('Cargo build failed: ${rustResult.stderr}');
}
/*print('Building Isar Web');
final webDir = path.join(rootDir, 'packages', 'isar_web');
final npmResult = Process.runSync(
'npm',
['run', 'build'],
runInShell: true,
workingDirectory: webDir,
);
print(webDir);
if (npmResult.exitCode != 0) {
throw Exception('Npm build failed: ${npmResult.stderr}');
}
print('Packing Isar Web');
final webpackResult = Process.runSync(
'npx',
['webpack', '--mode', 'development'],
runInShell: true,
workingDirectory: webDir,
);
if (webpackResult.exitCode != 0) {
throw Exception('Webpack build failed: ${npmResult.stderr}');
}
print('Writing Isar Web in the test dir');
final isarWebSrc =
File(path.join(webDir, 'dist', 'index.js')).readAsStringSync();
final escaped = isarWebSrc.replaceAll(r'$', r'\$');
File('lib/src/isar_web_src.dart').writeAsStringSync(
'// ignore_for_file: unnecessary_string_escapes\n'
"const isarWebSrc = '''\n$escaped''';",
);*/
}
+11 -4
View File
@@ -50,7 +50,7 @@ impl ParseCallbacks for Callbacks {
} }
const LIBMDBX_REPO: &str = "https://github.com/isar/libmdbx.git"; const LIBMDBX_REPO: &str = "https://github.com/isar/libmdbx.git";
const LIBMDBX_TAG: &str = "v0.12.2"; const LIBMDBX_TAG: &str = "v0.11.2";
fn main() { fn main() {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
@@ -61,13 +61,19 @@ fn main() {
Command::new("git") Command::new("git")
.arg("clone") .arg("clone")
.arg(LIBMDBX_REPO) .arg(LIBMDBX_REPO)
.arg("--depth")
.arg("1")
.arg("--branch") .arg("--branch")
.arg(LIBMDBX_TAG) .arg(LIBMDBX_TAG)
.output() .output()
.unwrap(); .unwrap();
/*Command::new("git")
.current_dir("libmdbx")
.arg("revert")
.arg("-n")
.arg("fe20de")
.output()
.unwrap();*/
Command::new("make") Command::new("make")
.arg("release-assets") .arg("release-assets")
.current_dir("libmdbx") .current_dir("libmdbx")
@@ -136,7 +142,8 @@ fn main() {
.define("MDBX_LOCK_SUFFIX", "\".lock\"") .define("MDBX_LOCK_SUFFIX", "\".lock\"")
.define("MDBX_TXN_CHECKOWNER", "0") .define("MDBX_TXN_CHECKOWNER", "0")
.define("MDBX_OSX_SPEED_INSTEADOF_DURABILITY", "1") .define("MDBX_OSX_SPEED_INSTEADOF_DURABILITY", "1")
.define("NDEBUG", "1") .define("MDBX_HAVE_BUILTIN_CPU_SUPPORTS", "0")
//.define("MDX_DEBUG", "7")
.file(mdbx.join("mdbx.c")) .file(mdbx.join("mdbx.c"))
.compile("libmdbx.a"); .compile("libmdbx.a");
} }
+22
View File
@@ -0,0 +1,22 @@
#!/bin/shuname
arch=$(uname -m)
if [ `uname` = "Linux" ] ;
then
if [ $arch = "x86_64" ] ;
then
cargo build --target x86_64-unknown-linux-gnu --release
else
cargo build --target aarch64-unknown-linux-gnu --release
fi
elif [ `uname` = "Darwin" ] ;
then
if [[ $arch == x86_64* ]]; then
cargo build --target x86_64-apple-darwin --release
else
cargo build --target aarch64-apple-darwin --release
fi
else
cargo build --target x86_64-pc-windows-msvc --release
fi
+2 -2
View File
@@ -45,8 +45,8 @@ elif [ "$1" = "x64" ]; then
mv "target/x86_64-linux-android/release/libisar.so" "libisar_android_x64.so" mv "target/x86_64-linux-android/release/libisar.so" "libisar_android_x64.so"
elif [ "$1" = "armv7" ]; then elif [ "$1" = "armv7" ]; then
rustup target add armv7-linux-androideabi rustup target add armv7-linux-androideabi
cargo build --target armv7-linux-androideabi --release cargo build --target armv7-linux-androideabi
mv "target/armv7-linux-androideabi/release/libisar.so" "libisar_android_armv7.so" mv "target/armv7-linux-androideabi/debug/libisar.so" "libisar_android_armv7.so"
else else
rustup target add aarch64-linux-android rustup target add aarch64-linux-android
cargo build --target aarch64-linux-android --release cargo build --target aarch64-linux-android --release
+6
View File
@@ -0,0 +1,6 @@
cd packages/isar_test
flutter pub get
dart tool/generate_long_double_test.dart
dart tool/generate_all_tests.dart
flutter pub run build_runner build