├── .all-contributorsrc ├── .github └── workflows │ ├── build_test_android.yml │ ├── build_test_ios.yml │ ├── build_test_linux.yml │ ├── build_test_macos.yml │ ├── build_test_windows.yml │ ├── clippy_check.yml │ ├── conventional_commits.yml │ ├── fmt_check.yml │ ├── pr_wait.yml │ ├── release_automatic.yml │ └── release_manual.yml ├── .gitignore ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── README.md ├── benches ├── README.md ├── all.rs ├── result.json ├── results │ ├── get_random.png │ ├── insert_random.png │ ├── remove_random.png │ └── scan_random.png └── setup.rs ├── cargo_publish.sh ├── justfile ├── native_db_macro ├── .gitignore ├── Cargo.toml ├── README.md └── src │ ├── keys.rs │ ├── lib.rs │ ├── model_attributes.rs │ ├── model_native_db.rs │ ├── native_db.rs │ └── struct_name.rs ├── release.config.js ├── renovate.json ├── src ├── database.rs ├── database_builder.rs ├── database_instance.rs ├── db_type │ ├── error.rs │ ├── input.rs │ ├── key │ │ ├── inner_key_value_redb1.rs │ │ ├── key.rs │ │ ├── key_definition.rs │ │ ├── key_value.rs │ │ └── mod.rs │ ├── mod.rs │ ├── output.rs │ ├── result.rs │ └── to_input.rs ├── lib.rs ├── metadata │ ├── current_version.rs │ ├── metadata.rs │ ├── mod.rs │ └── table.rs ├── model.rs ├── models.rs ├── serialization.rs ├── snapshot.rs ├── stats.rs ├── table_definition.rs ├── transaction │ ├── internal │ │ ├── mod.rs │ │ ├── private_readable_transaction.rs │ │ ├── r_transaction.rs │ │ └── rw_transaction.rs │ ├── mod.rs │ ├── query │ │ ├── drain.rs │ │ ├── get.rs │ │ ├── len.rs │ │ ├── mod.rs │ │ └── scan │ │ │ ├── mod.rs │ │ │ ├── primary_scan.rs │ │ │ └── secondary_scan.rs │ ├── r_transaction.rs │ └── rw_transaction.rs ├── upgrade │ ├── mod.rs │ ├── redb1_to_redb2.rs │ └── secondary_index_table_multimap.rs └── watch │ ├── batch.rs │ ├── event.rs │ ├── filter.rs │ ├── mod.rs │ ├── query │ ├── get.rs │ ├── internal.rs │ ├── mod.rs │ └── scan.rs │ ├── request.rs │ └── sender.rs ├── tables.toml ├── tests ├── check_integrity.rs ├── check_type │ ├── all.rs │ ├── mod.rs │ ├── struct_custom.rs │ └── struct_simple.rs ├── compact.rs ├── convert_all.rs ├── custom_type │ ├── custom.rs │ └── mod.rs ├── data │ ├── db_0_5_x │ ├── db_0_6_0 │ ├── db_0_7_1 │ └── db_0_8-pre-0 ├── deserialization_error.rs ├── macro_def │ ├── export_keys_attribute.rs │ ├── mod.rs │ ├── primary_key.rs │ ├── primary_key_attribute.rs │ ├── secondary_key.rs │ ├── secondary_key_attribute.rs │ └── secondary_key_mix.rs ├── metadata │ ├── current_version.rs │ └── mod.rs ├── migrate │ ├── mod.rs │ ├── only_primary_key.rs │ ├── with_multiple_versions.rs │ ├── with_other_model.rs │ └── with_secondary_keys.rs ├── modules.rs ├── native_model.rs ├── primary_drain │ ├── mod.rs │ ├── only_primary_key.rs │ └── with_secondary_keys.rs ├── query │ ├── auto_update_pk.rs │ ├── auto_update_sk.rs │ ├── insert_get_pk.rs │ ├── insert_get_sk.rs │ ├── insert_len_pk.rs │ ├── insert_len_sk.rs │ ├── insert_remove_pk.rs │ ├── insert_remove_sk.rs │ ├── insert_update_pk.rs │ ├── insert_update_sk.rs │ ├── mod.rs │ ├── upsert_get_pk.rs │ └── upsert_get_sk.rs ├── refresh.rs ├── scan.rs ├── secondary_scan_operators.rs ├── simple_multithreads.rs ├── snapshot.rs ├── transaction.rs ├── upgrade │ ├── from_0_5_x_to_0_6_x.rs │ ├── from_0_7_x_to_0_8_x.rs │ └── mod.rs ├── util.rs ├── watch │ ├── mod.rs │ └── watch_optional.rs └── watch_tokio.rs └── version_update.sh /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "elliot14A", 12 | "name": "Akshith Madhur", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/84667163?v=4", 14 | "profile": "https://github.com/elliot14A", 15 | "contributions": [ 16 | "code" 17 | ] 18 | } 19 | ], 20 | "contributorsPerLine": 7, 21 | "skipCi": true, 22 | "repoType": "github", 23 | "repoHost": "https://github.com", 24 | "projectName": "native_db", 25 | "projectOwner": "vincent-herlemont" 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/build_test_android.yml: -------------------------------------------------------------------------------- 1 | name: Android 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | # At 23:00 on Thursday. 10 | - cron: '0 23 * * 4' 11 | 12 | env: 13 | RUST_BACKTRACE: full 14 | 15 | jobs: 16 | build_test: 17 | runs-on: macos-13 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | #target: [i686-linux-android, aarch64-linux-android, armv7-linux-androideabi, thumbv7neon-linux-androideabi, x86_64-linux-android] 22 | #target: [aarch64-linux-android, x86_64-linux-android, armv7-linux-androideabi] 23 | toolchain: [stable] 24 | profile: 25 | - target: aarch64-linux-android 26 | image: "system-images;android-34;google_apis;x86_64" 27 | steps: 28 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 29 | - name: Setup Rust 30 | uses: dtolnay/rust-toolchain@stable 31 | with: 32 | toolchain: ${{ matrix.toolchain }} 33 | targets: ${{ matrix.profile.target }} 34 | - name: Cache emulator 35 | uses: actions/cache@v4 36 | if: always() 37 | with: 38 | path: | 39 | /Users/runner/Library/Android/sdk/emulator 40 | key: android-emulator-global 41 | - name: Cache system-images 42 | uses: actions/cache@v4 43 | if: always() 44 | with: 45 | path: | 46 | /Users/runner/Library/Android/sdk/system-images 47 | key: android-system-images-global-${{ matrix.profile.image }} 48 | - run: echo "/Users/runner/.cargo/bin" >> $GITHUB_PATH 49 | - run: echo "/Users/runner/Library/Android/sdk/emulator" >> $GITHUB_PATH 50 | - run: echo "/Users/runner/Library/Android/sdk/platform-tools" >> $GITHUB_PATH 51 | - run: echo "/Users/runner/Library/Android/sdk/cmdline-tools/latest/bin" >> $GITHUB_PATH 52 | # Install utilities 53 | - name: Cache cargo install 54 | uses: actions/cache@v4 55 | if: always() 56 | with: 57 | path: | 58 | ~/.cargo/bin/ 59 | key: cargo-global-${{ matrix.toolchain }}-${{ github.ref }}-${{ hashFiles('**/Cargo.lock') }} 60 | - run: if ! command -v cargo-dinghy &> /dev/null; then cargo install --version 0.6.8 cargo-dinghy; fi 61 | - run: if ! command -v just &> /dev/null; then cargo install --version 1.25.2 just; fi 62 | - run: just --version 63 | - uses: hustcer/setup-nu@v3.19 64 | with: 65 | version: '0.85' 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 68 | # End install utilities 69 | - run: adb start-server 70 | - run: sdkmanager emulator 71 | - run: echo "/Users/runner/Library/Android/sdk/emulator" >> $GITHUB_PATH 72 | - run: yes | sdkmanager --install "${{ matrix.profile.image }}" 73 | # - run: sdkmanager --list_installed 74 | # - run: sdkmanager --list 75 | - run: echo "no" | avdmanager create avd -n testDevice -k "${{ matrix.profile.image }}" 76 | - run: emulator -avd testDevice -no-audio -no-window -gpu swiftshader_indirect -no-snapshot -no-boot-anim -camera-back none -camera-front none -selinux permissive -qemu -m 2048 & 77 | - run: adb wait-for-device 78 | # - name: just test_mobile_all_platforms 79 | # - run: emulator -list-avds 80 | # - run: avdmanager list 81 | - run: just test_mobile_all_devices 82 | - run: just test_android 83 | -------------------------------------------------------------------------------- /.github/workflows/build_test_ios.yml: -------------------------------------------------------------------------------- 1 | name: iOS 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | # At 23:00 on Thursday. 10 | - cron: '0 23 * * 4' 11 | 12 | env: 13 | RUST_BACKTRACE: full 14 | 15 | jobs: 16 | build_test: 17 | runs-on: macos-13 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | toolchain: [stable] 22 | device: ["iPhone 14"] 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 25 | - name: Setup Rust 26 | uses: dtolnay/rust-toolchain@stable 27 | with: 28 | toolchain: ${{ matrix.toolchain }} 29 | targets: x86_64-apple-ios 30 | # Install utilities 31 | - name: Cache cargo install 32 | uses: actions/cache@v4 33 | if: always() 34 | with: 35 | path: | 36 | ~/.cargo/bin/ 37 | key: cargo-global-${{ matrix.toolchain }}-${{ github.ref }}-${{ hashFiles('**/Cargo.lock') }} 38 | - run: if ! command -v cargo-dinghy &> /dev/null; then cargo install --version 0.6.8 cargo-dinghy; fi 39 | - run: if ! command -v just &> /dev/null; then cargo install --version 1.25.2 just; fi 40 | - run: just --version 41 | - uses: hustcer/setup-nu@v3.19 42 | with: 43 | version: '0.85' 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 46 | # End install utilities 47 | - run: just test_ios_launch_simulator "${{ matrix.device }}" 48 | # - run: just test_ios_list_simulators 49 | - run: just test_ios -------------------------------------------------------------------------------- /.github/workflows/build_test_linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | # At 23:00 on Thursday. 10 | - cron: '0 23 * * 4' 11 | 12 | env: 13 | RUST_BACKTRACE: full 14 | 15 | jobs: 16 | build_bench: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [ubuntu-latest] 22 | toolchain: [stable] 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 25 | - name: Setup Rust 26 | uses: dtolnay/rust-toolchain@stable 27 | with: 28 | toolchain: ${{ matrix.toolchain }} 29 | - uses: extractions/setup-just@v3 30 | - uses: hustcer/setup-nu@v3.19 31 | with: 32 | version: '0.85' 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 35 | - name: Just version 36 | run: just --version 37 | - name: Build 38 | run: just bench_build 39 | build_test: 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | fail-fast: true 43 | matrix: 44 | os: [ubuntu-latest] 45 | toolchain: [stable] 46 | steps: 47 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 48 | - name: Setup Rust 49 | uses: dtolnay/rust-toolchain@stable 50 | with: 51 | toolchain: ${{ matrix.toolchain }} 52 | - uses: extractions/setup-just@v3 53 | - uses: hustcer/setup-nu@v3.19 54 | with: 55 | version: '0.85' 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 58 | - name: Just version 59 | run: just --version 60 | - name: Build 61 | run: just build_all 62 | - name: Test 63 | run: just test_all -------------------------------------------------------------------------------- /.github/workflows/build_test_macos.yml: -------------------------------------------------------------------------------- 1 | name: macOS 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | # At 23:00 on Thursday. 10 | - cron: '0 23 * * 4' 11 | 12 | env: 13 | RUST_BACKTRACE: full 14 | 15 | jobs: 16 | build_test: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [macos-13] 22 | toolchain: [stable] 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 25 | - name: Setup Rust 26 | uses: dtolnay/rust-toolchain@stable 27 | with: 28 | toolchain: ${{ matrix.toolchain }} 29 | - uses: extractions/setup-just@v3 30 | - uses: hustcer/setup-nu@v3.19 31 | with: 32 | version: '0.85' 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 35 | - name: Just version 36 | run: just --version 37 | - name: Build 38 | run: just build_all 39 | - name: cat cargo.toml 40 | run: cat Cargo.toml 41 | - name: Test 42 | run: just test_all -------------------------------------------------------------------------------- /.github/workflows/build_test_windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | # At 23:00 on Thursday. 10 | - cron: '0 23 * * 4' 11 | 12 | env: 13 | RUST_BACKTRACE: full 14 | 15 | jobs: 16 | build_test: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [windows-latest] 22 | toolchain: [stable] 23 | steps: 24 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 25 | - name: Setup Rust 26 | uses: dtolnay/rust-toolchain@stable 27 | with: 28 | toolchain: ${{ matrix.toolchain }} 29 | - uses: extractions/setup-just@v3 30 | - uses: hustcer/setup-nu@v3.19 31 | with: 32 | version: '0.85' 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 35 | - name: Just version 36 | run: just --version 37 | - name: Build 38 | run: just build_all 39 | - name: Test 40 | run: just test_all -------------------------------------------------------------------------------- /.github/workflows/clippy_check.yml: -------------------------------------------------------------------------------- 1 | name: Clippy Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | - cron: '0 23 * * 4' 10 | 11 | env: 12 | RUST_BACKTRACE: full 13 | 14 | jobs: 15 | clippy_check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup Rust 20 | uses: dtolnay/rust-toolchain@stable 21 | - uses: extractions/setup-just@v3 22 | - uses: hustcer/setup-nu@v3.19 23 | with: 24 | version: '0.85' 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 27 | - name: Just version 28 | run: just --version 29 | - name: Clippy Check 30 | run: just clippy_check -------------------------------------------------------------------------------- /.github/workflows/conventional_commits.yml: -------------------------------------------------------------------------------- 1 | name: Conventional Commits 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | name: Conventional Commits 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 13 | - uses: webiny/action-conventional-commits@v1.3.0 -------------------------------------------------------------------------------- /.github/workflows/fmt_check.yml: -------------------------------------------------------------------------------- 1 | name: Fmt Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main, next ] 8 | schedule: 9 | - cron: '0 23 * * 4' 10 | 11 | env: 12 | RUST_BACKTRACE: full 13 | 14 | jobs: 15 | fmt_check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup Rust 20 | uses: dtolnay/rust-toolchain@stable 21 | - uses: extractions/setup-just@v3 22 | - uses: hustcer/setup-nu@v3.19 23 | with: 24 | version: '0.85' 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 27 | - name: Just version 28 | run: just --version 29 | - name: Fmt Check 30 | run: just fmt_check -------------------------------------------------------------------------------- /.github/workflows/pr_wait.yml: -------------------------------------------------------------------------------- 1 | name: wait-for-workflows (PR) 2 | 3 | on: 4 | pull_request_target: # secrets are available 5 | branches: [ main, next ] 6 | 7 | permissions: 8 | checks: read 9 | actions: read # read-only is enough 10 | 11 | jobs: 12 | wait-for-workflows: # <-- job name becomes the required check 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 45 15 | steps: 16 | - uses: int128/wait-for-workflows-action@v1.41.0 17 | with: 18 | # PAT inside your repo, fallback to the read-only token if missing 19 | github-token: ${{ secrets.PAT_GLOBAL || github.token }} 20 | filter-workflow-events: pull_request 21 | # the commit we’re validating is the HEAD of the PR 22 | sha: ${{ github.event.pull_request.head.sha }} 23 | -------------------------------------------------------------------------------- /.github/workflows/release_automatic.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [ main ] # only after merge 6 | 7 | permissions: 8 | contents: write # release step will need this 9 | checks: read 10 | actions: read 11 | 12 | jobs: 13 | wait-for-workflows: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 45 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | - uses: int128/wait-for-workflows-action@v1.41.0 20 | with: 21 | token: ${{ secrets.PAT_GLOBAL }} 22 | filter-workflow-events: push 23 | sha: ${{ github.sha }} 24 | release: 25 | name: Release 26 | runs-on: ubuntu-latest 27 | needs: wait-for-workflows 28 | steps: 29 | - name: Fake step 30 | run: | 31 | echo "Run the release job" 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/release_manual.yml: -------------------------------------------------------------------------------- 1 | name: Release Manual 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | if: github.ref == 'refs/heads/main' 11 | permissions: 12 | contents: write 13 | packages: write 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 17 | with: 18 | ref: main 19 | 20 | - name: install npm 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '16.20.2' 24 | 25 | - name: install @semantic-release/exec 26 | run: npm install @semantic-release/exec 27 | 28 | - name: Semantic Release 29 | uses: cycjimmy/semantic-release-action@v3 30 | with: 31 | branch: main 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.PAT_GLOBAL }} 34 | CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | # TODO: remove it used by semantic-release/exec 4 | node_modules/ 5 | package-lock.json 6 | package.json 7 | *_expanded.rs 8 | 9 | # Related to [Why do binaries have Cargo.lock in version control, but not libraries?](https://doc.rust-lang.org/cargo/faq.html#why-do-binaries-have-cargolock-in-version-control-but-not-libraries) 10 | Cargo.lock 11 | tests/data/db_x_x_x 12 | 13 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "native_db" 3 | version = "0.8.1" 4 | authors = ["Vincent Herlemont "] 5 | edition = "2021" 6 | description = "Drop-in embedded database" 7 | license = "MIT" 8 | repository = "https://github.com/vincent-herlemont/native_db" 9 | readme = "README.md" 10 | keywords = ["embedded-database", "database", "multi-platform", "android", "ios"] 11 | categories = ["database-implementations", "concurrency", "data-structures", "caching", "algorithms"] 12 | 13 | [workspace] 14 | members = ["native_db_macro"] 15 | 16 | [dependencies] 17 | redb = "=2.5.0" 18 | redb1 = { version = "=1.5.1", package = "redb", optional = true } 19 | native_db_macro = { version = "0.8.1", path = "native_db_macro" } 20 | thiserror = "2.0.0" 21 | serde = { version = "1.0" } 22 | native_model = { version = "0.6.1" } 23 | semver = "1" 24 | 25 | # Optional tokio support 26 | tokio = { version = "1.45.0", features = ["sync"], optional = true } 27 | # TODO: channels with futures 28 | # TODO: channels crossbeam 29 | 30 | 31 | [dev-dependencies] 32 | assert_fs = "1.1.3" 33 | serial_test = { version = "3.2.0", features = ["file_locks"] } 34 | shortcut_assert_fs = { version = "0.1.0" } 35 | tokio = { version = "1.45.0", features = ["test-util","macros"] } 36 | bincode = { version = "2.0.1", features = ["serde"] } 37 | criterion = { version = "0.6.0" } 38 | doc-comment = "0.3.3" 39 | uuid = { version = "1.17.0", features = ["serde", "v4"] } 40 | chrono = { version = "0.4", features = ["serde"] } 41 | rand = "0.9.1" 42 | once_cell = "1.21.3" 43 | dinghy-test = "0.8.0" 44 | itertools = "0.14.0" 45 | include_dir = "0.7" 46 | paste = "1.0.15" 47 | cc = "1.2.25" 48 | rusqlite = { version = "0.36.0", features = ["bundled"] } 49 | concat-idents = "1.1.5" 50 | 51 | 52 | [features] 53 | default = [ "upgrade_0_5_x", "upgrade_0_7_x" ] 54 | upgrade_0_5_x = [ "redb1" ] 55 | upgrade_0_7_x = [ ] 56 | 57 | [[bench]] 58 | name = "all" 59 | harness = false 60 | 61 | [build-dependencies] 62 | skeptic = "0.13.7" -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.i686-linux-android] 2 | image = "ghcr.io/cross-rs/i686-linux-android:main" 3 | 4 | [target.aarch64-linux-android] 5 | image = "ghcr.io/cross-rs/aarch64-linux-android:main" 6 | 7 | [target.x86_64-linux-android] 8 | image = "ghcr.io/cross-rs/x86_64-linux-android:main" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vincent Herlemont 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Native DB 2 | 3 | [![](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_linux.yml/badge.svg?branch=main&event=push)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_linux.yml) 4 | [![](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_macos.yml/badge.svg?branch=main&event=push)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_macos.yml) 5 | [![](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_windows.yml/badge.svg?branch=main&event=push)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_windows.yml) 6 | [![](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_ios.yml/badge.svg?branch=main&event=push)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_ios.yml) 7 | [![](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_android.yml/badge.svg?branch=main&event=push)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_test_android.yml) 8 | 9 | 10 | [![Crates.io](https://img.shields.io/crates/v/native_db)](https://crates.io/crates/native_db) 11 | [![Documentation](https://docs.rs/native_db/badge.svg)](https://docs.rs/native_db) 12 | [![License](https://img.shields.io/crates/l/native_db)](LICENSE) 13 | 14 | Here's a drop-in, fast, embedded database for multi-platform apps (server, desktop, mobile). Sync Rust types effortlessly. Enjoy! 😌🍃. 15 | 16 | # Features 17 | 18 | - Simple API 🦀. 19 | - Support for **multiple indexes** (primary, secondary, unique, non-unique, optional). 20 | - Fast, see [`sqlite` vs `redb` vs `native_db`](./benches/README.md) benchmarks. 21 | - Transparent serialization/deserialization using [native_model](https://github.com/vincent-herlemont/native_model). You can use any serialization library you want (`bincode`, `postcard`, your own etc.). 22 | - Ensure query **type safety** to prevent unexpected results caused by selecting with an incorrect type. 23 | - **Automatic model migration** 🌟. 24 | - **Thread-safe** and fully **ACID-compliant** transactions provided by [redb](https://github.com/cberner/redb). 25 | - **Real-time** subscription with filters for `insert`, `update` and `delete` operations. 26 | - Compatible with all Rust types (`enum`, `struct`, `tuple` etc.). 27 | - **Hot snapshots**. 28 | 29 | # Installation 30 | 31 | Add this to your `Cargo.toml`: 32 | ```toml 33 | [dependencies] 34 | native_db = "0.8.1" 35 | native_model = "0.4.20" 36 | ``` 37 | 38 | # Status 39 | 40 | Active development. The API is not stable yet and may change in the future. 41 | 42 | # How to use? 43 | 44 | - [Documentation API](https://docs.rs/native_db/latest/native_db/#api) 45 | - [Quick Start](https://docs.rs/native_db/latest/native_db/#quick_start) 46 | - Full example with Tauri: [native_db_tauri_vanilla](https://github.com/vincent-herlemont/native_db_tauri_vanilla) 47 | 48 | # Projects using Native DB 49 | 50 | - [polly-scheduler](https://github.com/dongbin86/polly-scheduler) 51 | - [Oku](https://okubrowser.github.io) 52 | 53 | If you want to propose your project or company that uses Native DB, please open a PR. 54 | 55 | # Example 56 | 57 | ```rust 58 | use serde::{Deserialize, Serialize}; 59 | use native_db::*; 60 | use native_db::native_model::{native_model, Model}; 61 | use once_cell::sync::Lazy; 62 | 63 | #[derive(Serialize, Deserialize, PartialEq, Debug)] 64 | #[native_model(id = 1, version = 1)] 65 | #[native_db] 66 | struct Item { 67 | #[primary_key] 68 | id: u32, 69 | #[secondary_key] 70 | name: String, 71 | } 72 | 73 | // Define the models 74 | // The lifetime of the models needs to be longer or equal to the lifetime of the database. 75 | // In many cases, it is simpler to use a static variable but it is not mandatory. 76 | static MODELS: Lazy = Lazy::new(|| { 77 | let mut models = Models::new(); 78 | models.define::().unwrap(); 79 | models 80 | }); 81 | 82 | fn main() -> Result<(), db_type::Error> { 83 | // Create a database in memory 84 | let mut db = Builder::new().create_in_memory(&MODELS)?; 85 | 86 | // Insert data (open a read-write transaction) 87 | let rw = db.rw_transaction()?; 88 | rw.insert(Item { id: 1, name: "red".to_string() })?; 89 | rw.insert(Item { id: 2, name: "green".to_string() })?; 90 | rw.insert(Item { id: 3, name: "blue".to_string() })?; 91 | rw.commit()?; 92 | 93 | // Open a read-only transaction 94 | let r = db.r_transaction()?; 95 | // Retrieve data with id=3 96 | let retrieve_data: Item = r.get().primary(3_u32)?.unwrap(); 97 | println!("data id='3': {:?}", retrieve_data); 98 | // Iterate items with name starting with "red" 99 | for item in r.scan().secondary::(ItemKey::name)?.start_with("red")? { 100 | println!("data name=\"red\": {:?}", item); 101 | } 102 | 103 | // Remove data (open a read-write transaction) 104 | let rw = db.rw_transaction()?; 105 | rw.remove(retrieve_data)?; 106 | rw.commit()?; 107 | Ok(()) 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /benches/results/get_random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/benches/results/get_random.png -------------------------------------------------------------------------------- /benches/results/insert_random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/benches/results/insert_random.png -------------------------------------------------------------------------------- /benches/results/remove_random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/benches/results/remove_random.png -------------------------------------------------------------------------------- /benches/results/scan_random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/benches/results/scan_random.png -------------------------------------------------------------------------------- /cargo_publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # How to test: 4 | # - Use docker `docker run -it --rm -v $(pwd):/mnt/native_db rust:bullseye bash` 5 | # - `cd /mnt/native_db` 6 | # - `export CARGO_TOKEN=` 7 | # - `./cargo_publish.sh` 8 | 9 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 10 | 11 | set -e 12 | set -x 13 | 14 | ARG_TOKEN="--token=$CARGO_TOKEN" 15 | 16 | cd $DIR/native_db_macro 17 | 18 | # Temporarily disable 'set -e' to handle the error manually 19 | set +e 20 | OUTPUT=$(cargo publish $ARG_TOKEN "$@" 2>&1) 21 | EXIT_CODE=$? 22 | set -e 23 | 24 | if [ $EXIT_CODE -ne 0 ]; then 25 | if echo "$OUTPUT" | grep -q "crate version .* is already uploaded"; then 26 | echo "Warning: $OUTPUT" 27 | else 28 | echo "Error: $OUTPUT" 29 | exit $EXIT_CODE 30 | fi 31 | fi 32 | 33 | cd $DIR 34 | cargo publish $ARG_TOKEN $@ -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | set shell := ["nu", "-c"] 2 | 3 | default: 4 | @just --list --unsorted; 5 | 6 | build_no_default *args: 7 | cargo build --no-default-features {{args}} 8 | 9 | # E.g. just build_default --test modules breaking_release_migration::from_0_5_x_to_0_6_x 10 | build_default *args: 11 | cargo build {{args}} 12 | 13 | build_with_optional *args: 14 | cargo build -F tokio {{args}} 15 | 16 | build_all *args: 17 | just build_no_default {{args}}; 18 | just build_default {{args}}; 19 | just build_with_optional {{args}} 20 | 21 | test_no_default *args: 22 | cargo test --no-default-features {{args}} -- --nocapture 23 | 24 | test_default *args: 25 | cargo test {{args}} -- --nocapture 26 | 27 | test_with_optional *args: 28 | cargo test -F tokio {{args}} -- --nocapture 29 | 30 | test_all *args: 31 | just test_no_default {{args}}; 32 | just test_default {{args}}; 33 | just test_with_optional {{args}} 34 | 35 | 36 | # List all available devices 37 | test_mobile_all_devices: 38 | cargo dinghy all-devices 39 | 40 | # List all available platforms 41 | test_mobile_all_platforms: 42 | echo $env.ANDROID_NDK_HOME; \ 43 | cargo dinghy all-platforms 44 | 45 | [macos] 46 | test_ios_launch_simulator device="iPhone 14": 47 | xcrun simctl boot "{{device}}" 48 | 49 | [macos] 50 | test_ios_list_simulators: 51 | xcrun simctl list 52 | 53 | # args: E.g. "--test modules watch::watch_multithreading" 54 | [macos] 55 | test_ios *args: 56 | cargo dinghy -d iphone test {{args}} 57 | 58 | # List all available android emulators 59 | test_android_list_emulators: 60 | emulator -list-avds 61 | 62 | # Launch android emulator 63 | test_android_launch_emulator emulator="Pixel_3a_API_34_extension_level_7_arm64-v8a": 64 | emulator -avd "{{emulator}}" 65 | 66 | # List all adb devices 67 | test_android_list_devices: 68 | adb devices 69 | 70 | test_android *args: 71 | cargo dinghy -d android test {{args}} 72 | 73 | bench_build: 74 | cargo bench --no-run 75 | 76 | bench bench_name: 77 | CRITERION_DEBUG=1 cargo bench --profile release --bench {{bench_name}}; \ 78 | start ./target/criterion/report/index.html 79 | 80 | bench_md bench_name: 81 | cargo criterion --message-format=json --bench {{bench_name}} | save -f --raw ./benches/result.json; \ 82 | cat ./benches/result.json | criterion-table | save -f --raw ./benches/README.md 83 | 84 | bench_r_md: 85 | cat ./benches/result.json | criterion-table | save -f --raw ./benches/README.md 86 | 87 | expand test_file_name="util": 88 | rm -f {{test_file_name}}.expanded.rs; \ 89 | RUSTFLAGS="-Zmacro-backtrace" cargo expand --test {{test_file_name}} | save -f --raw src/{{test_file_name}}_expanded.rs 90 | 91 | expand_clean: 92 | rm -f src/*_expanded.rs 93 | 94 | format: 95 | cargo clippy; \ 96 | cargo fmt --all 97 | 98 | fmt_check: 99 | cargo fmt --all -- --check 100 | 101 | clippy_check: 102 | rustc --version; \ 103 | cargo clippy --version; \ 104 | cargo clippy -- -D warnings 105 | 106 | # Format check 107 | fc: 108 | just fmt_check; \ 109 | just clippy_check -------------------------------------------------------------------------------- /native_db_macro/.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /native_db_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "native_db_macro" 3 | version = "0.8.1" 4 | authors = ["Vincent Herlemont "] 5 | edition = "2018" 6 | description = "A procedural macro for native_db" 7 | license = "MIT" 8 | repository = "https://github.com/vincent-herlemont/native_db" 9 | readme = "README.md" 10 | 11 | [lib] 12 | path = "src/lib.rs" 13 | proc-macro = true 14 | 15 | [dependencies] 16 | syn = { version = "2.0", features = ["full"] } 17 | quote = "1.0" 18 | proc-macro2 = "1.0" 19 | 20 | [features] 21 | default = [] -------------------------------------------------------------------------------- /native_db_macro/README.md: -------------------------------------------------------------------------------- 1 | A procedural macro for native_db -------------------------------------------------------------------------------- /native_db_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | mod keys; 4 | mod model_attributes; 5 | mod model_native_db; 6 | mod native_db; 7 | mod struct_name; 8 | 9 | use proc_macro::TokenStream; 10 | 11 | use native_db::native_db as native_db_impl; 12 | 13 | #[proc_macro_attribute] 14 | pub fn native_db(args: TokenStream, input: TokenStream) -> TokenStream { 15 | native_db_impl(args, input) 16 | } 17 | 18 | #[proc_macro_derive(KeyAttributes, attributes(primary_key, secondary_key))] 19 | pub fn key_attributes(_input: TokenStream) -> TokenStream { 20 | let gen = quote::quote! {}; 21 | gen.into() 22 | } 23 | 24 | trait ToTokenStream { 25 | fn new_to_token_stream(&self) -> proc_macro2::TokenStream; 26 | } 27 | -------------------------------------------------------------------------------- /native_db_macro/src/native_db.rs: -------------------------------------------------------------------------------- 1 | use crate::model_attributes::ModelAttributes; 2 | use crate::model_native_db::ModelNativeDB; 3 | use crate::struct_name::StructName; 4 | use proc_macro::TokenStream; 5 | use quote::quote; 6 | use syn::{parse_macro_input, Data, DeriveInput, Fields}; 7 | 8 | pub fn native_db(args: TokenStream, input: TokenStream) -> TokenStream { 9 | let ast = parse_macro_input!(input as DeriveInput); 10 | let struct_name = StructName::new(ast.ident.clone()); 11 | 12 | let mut attrs = ModelAttributes { 13 | struct_name: struct_name.clone(), 14 | primary_key: None, 15 | secondary_keys: Default::default(), 16 | do_export_keys: None, 17 | }; 18 | let model_attributes_parser = syn::meta::parser(|meta| attrs.parse(meta)); 19 | parse_macro_input!(args with model_attributes_parser); 20 | 21 | if let Data::Struct(data_struct) = &ast.data { 22 | if let Fields::Named(fields) = &data_struct.fields { 23 | for field in &fields.named { 24 | if let Err(err) = attrs.parse_field(field) { 25 | return TokenStream::from(err.to_compile_error()); 26 | } 27 | } 28 | } 29 | } 30 | 31 | let model_native_db = ModelNativeDB::new(struct_name.clone(), attrs.clone()); 32 | 33 | let native_db_pk = model_native_db.native_db_primary_key(); 34 | let native_db_gks = model_native_db.native_db_secondary_key(); 35 | let native_db_model = model_native_db.native_db_model(); 36 | 37 | let keys_enum_visibility = model_native_db.keys_enum_visibility(); 38 | let keys_enum_name = model_native_db.keys_enum_name(); 39 | let keys_enum = model_native_db.secondary_keys_enum(); 40 | let keys_enum_database_key = model_native_db.keys_enum_database_key(); 41 | 42 | let struct_name = struct_name.ident(); 43 | let gen = quote! { 44 | #[derive(native_db::KeyAttributes)] 45 | #ast 46 | 47 | impl native_db::db_type::ToInput for #struct_name { 48 | fn native_db_bincode_encode_to_vec(&self) -> native_db::db_type::Result> { 49 | native_db::bincode_encode_to_vec(self) 50 | } 51 | 52 | fn native_db_bincode_decode_from_slice(slice: &[u8]) -> native_db::db_type::Result { 53 | Ok(native_db::bincode_decode_from_slice(slice)?.0) 54 | } 55 | 56 | #native_db_model 57 | #native_db_pk 58 | #native_db_gks 59 | } 60 | 61 | #[allow(non_camel_case_types)] 62 | #keys_enum_visibility enum #keys_enum_name { 63 | #(#keys_enum),* 64 | } 65 | 66 | impl native_db::db_type::ToKeyDefinition for #keys_enum_name { 67 | #keys_enum_database_key 68 | } 69 | }; 70 | 71 | gen.into() 72 | } 73 | -------------------------------------------------------------------------------- /native_db_macro/src/struct_name.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Ident; 2 | 3 | #[derive(Clone, Debug)] 4 | pub(crate) struct StructName(Ident); 5 | 6 | impl StructName { 7 | pub(crate) fn ident(&self) -> &Ident { 8 | &self.0 9 | } 10 | pub(crate) fn new(ident: Ident) -> Self { 11 | Self(ident) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: ['main'], 3 | tagFormat: '${version}', 4 | plugins: [ 5 | ['@semantic-release/commit-analyzer', { 6 | releaseRules: [ 7 | {breaking: true, release: 'minor'}, 8 | {revert: true, release: 'patch'}, 9 | {type: 'feat', release: 'minor'}, 10 | {type: 'fix', release: 'patch'}, 11 | {type: 'perf', release: 'patch'}, 12 | {type: 'docs', release: 'patch'}, 13 | {emoji: ':racehorse:', release: 'patch'}, 14 | {emoji: ':bug:', release: 'patch'}, 15 | {emoji: ':penguin:', release: 'patch'}, 16 | {emoji: ':apple:', release: 'patch'}, 17 | {emoji: ':checkered_flag:', release: 'patch'}, 18 | {tag: 'BUGFIX', release: 'patch'}, 19 | {tag: 'FEATURE', release: 'minor'}, 20 | {tag: 'SECURITY', release: 'patch'}, 21 | {tag: 'Breaking', release: 'minor'}, 22 | {tag: 'Fix', release: 'patch'}, 23 | {tag: 'Update', release: 'minor'}, 24 | {tag: 'New', release: 'minor'}, 25 | {component: 'perf', release: 'patch'}, 26 | {component: 'deps', release: 'patch'}, 27 | {type: 'FEAT', release: 'minor'}, 28 | {type: 'FIX', release: 'patch'}, 29 | ], 30 | }], 31 | '@semantic-release/release-notes-generator', 32 | ['@semantic-release/exec', { 33 | "prepareCmd": "bash version_update.sh ${nextRelease.version}", 34 | "publishCmd": "bash cargo_publish.sh", 35 | }], 36 | '@semantic-release/github', 37 | ], 38 | }; -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "semanticCommits": "enabled", 4 | "semanticCommitType": "chore", 5 | "semanticCommitScope": "deps", 6 | "platformAutomerge": true, 7 | "packageRules": [ 8 | { 9 | "matchDepNames": ["redb1"], 10 | "enabled": false 11 | }, 12 | { 13 | "description": "Automerge non-major updates", 14 | "matchUpdateTypes": ["minor", "patch"], 15 | "automerge": true 16 | }, 17 | { 18 | "matchPackagePatterns": ["thiserror", "chrono", "tokio", "serde", "syn", "quote", "proc-macro2", "include_dir", "semver"], 19 | "matchUpdateTypes": ["patch"], 20 | "enabled": false 21 | }, 22 | { 23 | "matchPackagePatterns": ["*"], 24 | "rangeStrategy": "bump" 25 | }, 26 | { 27 | "description": "Automerge actions", 28 | "matchDepTypes": ["action"], 29 | "matchUpdateTypes": ["major", "minor", "patch"], 30 | "automerge": true 31 | } 32 | ], 33 | "regexManagers": [ 34 | { 35 | "fileMatch": ["^README\\.md$"], 36 | "matchStrings": [ 37 | "\"native_model\" = \"(?.*?)\"" 38 | ], 39 | "depNameTemplate": "native_model", 40 | "datasourceTemplate": "crate", 41 | "versioningTemplate": "semver" 42 | } 43 | ], 44 | "enabled": true 45 | } -------------------------------------------------------------------------------- /src/database_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::database_instance::DatabaseInstance; 2 | use crate::db_type::{Error, Result}; 3 | use crate::table_definition::NativeModelOptions; 4 | use crate::{metadata, Models}; 5 | use crate::{upgrade, watch, Database, Model}; 6 | use std::collections::HashMap; 7 | use std::path::Path; 8 | use std::sync::atomic::AtomicU64; 9 | use std::sync::{Arc, RwLock}; 10 | 11 | #[derive(Debug)] 12 | pub(crate) struct Configuration { 13 | pub(crate) cache_size_bytes: Option, 14 | } 15 | 16 | impl Configuration { 17 | pub(crate) fn new_rdb_builder(&self) -> redb::Builder { 18 | let mut redb_builder = redb::Builder::new(); 19 | if let Some(cache_size_bytes) = self.cache_size_bytes { 20 | redb_builder.set_cache_size(cache_size_bytes); 21 | } 22 | redb_builder 23 | } 24 | } 25 | 26 | #[cfg(feature = "redb1")] 27 | impl Configuration { 28 | pub(crate) fn redb1_new_rdb1_builder(&self) -> redb1::Builder { 29 | let mut redb_builder = redb1::Builder::new(); 30 | if let Some(cache_size_bytes) = self.cache_size_bytes { 31 | redb_builder.set_cache_size(cache_size_bytes); 32 | } 33 | redb_builder 34 | } 35 | } 36 | /// Builder that allows you to create a [`Database`](crate::Database) instance via [`create`](Self::create) or [`open`](Self::open) etc. 37 | #[derive(Debug)] 38 | pub struct Builder { 39 | database_configuration: Configuration, 40 | } 41 | 42 | impl Builder { 43 | fn init<'a>( 44 | &self, 45 | database_instance: DatabaseInstance, 46 | models: &'a Models, 47 | ) -> Result> { 48 | let database_metadata = metadata::load_or_create_metadata(&database_instance)?; 49 | 50 | let mut database = Database { 51 | instance: database_instance, 52 | metadata: database_metadata, 53 | primary_table_definitions: HashMap::new(), 54 | watchers: Arc::new(RwLock::new(watch::Watchers::new())), 55 | watchers_counter_id: AtomicU64::new(0), 56 | }; 57 | 58 | for (_, model_builder) in models.models_builder.iter() { 59 | database.seed_model(model_builder)?; 60 | } 61 | 62 | // TODO: Maybe we can do some migration with models here. 63 | 64 | Ok(database) 65 | } 66 | } 67 | 68 | impl Default for Builder { 69 | fn default() -> Self { 70 | Self::new() 71 | } 72 | } 73 | 74 | impl Builder { 75 | /// Construct a new [Builder] with sensible defaults. 76 | pub fn new() -> Self { 77 | Self { 78 | database_configuration: Configuration { 79 | cache_size_bytes: None, 80 | }, 81 | } 82 | } 83 | 84 | /// Similar to [redb::Builder::set_cache_size()](https://docs.rs/redb/latest/redb/struct.Builder.html#method.set_cache_size). 85 | pub fn set_cache_size(&mut self, bytes: usize) -> &mut Self { 86 | self.database_configuration.cache_size_bytes = Some(bytes); 87 | self 88 | } 89 | 90 | /// Creates a new `Db` instance using the given path. 91 | /// 92 | /// Similar to [redb::Builder.create(...)](https://docs.rs/redb/latest/redb/struct.Builder.html#method.create) 93 | pub fn create<'a>(&self, models: &'a Models, path: impl AsRef) -> Result> { 94 | let builder = self.database_configuration.new_rdb_builder(); 95 | let database_instance = DatabaseInstance::create_on_disk(builder, path)?; 96 | self.init(database_instance, models) 97 | } 98 | 99 | /// Similar to [redb::Builder::open(...)](https://docs.rs/redb/latest/redb/struct.Builder.html#method.open) 100 | /// But it also upgrades the database if needed. 101 | pub fn open<'a>(&self, models: &'a Models, path: impl AsRef) -> Result> { 102 | let builder = self.database_configuration.new_rdb_builder(); 103 | let database_instance = match DatabaseInstance::open_on_disk(builder, &path) { 104 | Err(Error::RedbDatabaseError(boxed_error)) => { 105 | if let redb::DatabaseError::UpgradeRequired(_) = *boxed_error { 106 | upgrade::upgrade_redb( 107 | &self.database_configuration, 108 | &path, 109 | &models.models_builder, 110 | ) 111 | } else { 112 | Err(Error::RedbDatabaseError(boxed_error)) 113 | } 114 | } 115 | Err(error) => return Err(error), 116 | Ok(database_instance) => Ok(database_instance), 117 | }?; 118 | upgrade::upgrade_underlying_database(&database_instance, &models.models_builder)?; 119 | self.init(database_instance, models) 120 | } 121 | 122 | /// Creates a new [`Database`](crate::Database) instance in memory. 123 | pub fn create_in_memory<'a>(&self, models: &'a Models) -> Result> { 124 | let builder = self.database_configuration.new_rdb_builder(); 125 | let database_instance = DatabaseInstance::create_in_memory(builder)?; 126 | self.init(database_instance, models) 127 | } 128 | } 129 | 130 | #[derive(Debug)] 131 | pub(crate) struct ModelBuilder { 132 | pub(crate) model: Model, 133 | pub(crate) native_model_options: NativeModelOptions, 134 | } 135 | -------------------------------------------------------------------------------- /src/database_instance.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::Result; 2 | use redb::Builder; 3 | use std::path::Path; 4 | use std::path::PathBuf; 5 | 6 | pub(crate) struct DatabaseInstance { 7 | kind: DatabaseInstanceKind, 8 | } 9 | 10 | impl DatabaseInstance { 11 | pub(crate) fn create_on_disk(builder: Builder, path: impl AsRef) -> Result { 12 | let db = builder.create(path.as_ref())?; 13 | Ok(Self { 14 | kind: DatabaseInstanceKind::OnDisk { 15 | redb_database: db, 16 | path: path.as_ref().to_path_buf(), 17 | }, 18 | }) 19 | } 20 | 21 | pub(crate) fn open_on_disk(builder: Builder, path: impl AsRef) -> Result { 22 | let db = builder.open(path.as_ref())?; 23 | Ok(Self { 24 | kind: DatabaseInstanceKind::OnDisk { 25 | redb_database: db, 26 | path: path.as_ref().to_path_buf(), 27 | }, 28 | }) 29 | } 30 | 31 | pub(crate) fn create_in_memory(builder: Builder) -> Result { 32 | let in_memory_backend = redb::backends::InMemoryBackend::new(); 33 | let db = builder.create_with_backend(in_memory_backend)?; 34 | Ok(Self { 35 | kind: DatabaseInstanceKind::InMemory { redb_database: db }, 36 | }) 37 | } 38 | 39 | pub(crate) fn redb_database(&self) -> Result<&redb::Database> { 40 | self.kind.redb_database() 41 | } 42 | 43 | pub(crate) fn redb_database_mut(&mut self) -> Result<&mut redb::Database> { 44 | self.kind.redb_database_mut() 45 | } 46 | } 47 | 48 | enum DatabaseInstanceKind { 49 | InMemory { 50 | redb_database: redb::Database, 51 | }, 52 | OnDisk { 53 | redb_database: redb::Database, 54 | #[allow(dead_code)] 55 | path: PathBuf, 56 | }, 57 | } 58 | 59 | impl DatabaseInstanceKind { 60 | pub(crate) fn redb_database(&self) -> Result<&redb::Database> { 61 | match self { 62 | DatabaseInstanceKind::InMemory { redb_database } => Ok(redb_database), 63 | DatabaseInstanceKind::OnDisk { redb_database, .. } => Ok(redb_database), 64 | } 65 | } 66 | 67 | pub(crate) fn redb_database_mut(&mut self) -> Result<&mut redb::Database> { 68 | match self { 69 | DatabaseInstanceKind::InMemory { redb_database } => Ok(redb_database), 70 | DatabaseInstanceKind::OnDisk { redb_database, .. } => Ok(redb_database), 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/db_type/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{db_type, watch}; 2 | use thiserror::Error; 3 | 4 | #[derive(Debug, Error)] 5 | pub enum Error { 6 | #[error("Redb error")] 7 | Redb(#[from] Box), 8 | 9 | #[error("Redb database error")] 10 | RedbDatabaseError(#[from] Box), 11 | 12 | #[cfg(feature = "redb1")] 13 | #[error("Legacy redb1 database error")] 14 | LegacyRedb1DatabaseError(#[from] Box), 15 | 16 | #[error("Redb transaction error")] 17 | RedbTransactionError(#[from] Box), 18 | 19 | #[cfg(feature = "redb1")] 20 | #[error("Redb redb1 transaction error")] 21 | Redb1TransactionError(#[from] Box), 22 | 23 | #[error("Redb storage error")] 24 | RedbStorageError(#[from] redb::StorageError), 25 | 26 | #[cfg(feature = "redb1")] 27 | #[error("Redb redb1 storage error")] 28 | Redb1StorageError(#[from] redb1::StorageError), 29 | 30 | #[error("Redb table error")] 31 | RedbTableError(#[from] redb::TableError), 32 | 33 | #[cfg(feature = "redb1")] 34 | #[error("Redb redb1 table error")] 35 | Redb1TableError(#[from] redb1::TableError), 36 | 37 | #[error("Redb commit error")] 38 | RedbCommitError(#[from] redb::CommitError), 39 | 40 | #[error("Redb compaction error")] 41 | RedbCompactionError(#[from] redb::CompactionError), 42 | 43 | #[error("Database instance need upgrade")] 44 | DatabaseInstanceNeedUpgrade(u8), 45 | 46 | #[error("IO error")] 47 | Io(#[from] std::io::Error), 48 | 49 | #[error("Table definition not found {table}")] 50 | TableDefinitionNotFound { table: String }, 51 | 52 | #[error("Secondary key definition not found {table} {key}")] 53 | SecondaryKeyDefinitionNotFound { table: String, key: String }, 54 | 55 | #[error("Secondary key constraint mismatch {table} {key} got: {got:?}")] 56 | SecondaryKeyConstraintMismatch { 57 | table: String, 58 | key: String, 59 | got: db_type::KeyOptions, 60 | }, 61 | 62 | #[error("The secondary key {key_name} is not unique ")] 63 | NotUniqueSecondaryKey { key_name: String }, 64 | 65 | // TODO: key with key name. 66 | #[error("Key not found {key:?}")] 67 | KeyNotFound { key: Vec }, 68 | 69 | #[error("Primary key associated with the secondary key not found")] 70 | PrimaryKeyNotFound, 71 | 72 | #[error("Duplicate key for \"{key_name}\"")] 73 | DuplicateKey { key_name: String }, 74 | 75 | #[error("Missmatched key type for \"{key_name}\" expected {expected_types:?} got {got_types:?} during {operation:?}")] 76 | MissmatchedKeyType { 77 | key_name: String, 78 | expected_types: Vec, 79 | got_types: Vec, 80 | operation: String, 81 | }, 82 | 83 | #[error("Watch event error")] 84 | WatchEventError(#[from] watch::WatchEventError), 85 | 86 | #[error("Max watcher reached (should be impossible)")] 87 | MaxWatcherReached, 88 | 89 | #[error("You can not migrate the table {0} because it is a legacy model")] 90 | MigrateLegacyModel(String), 91 | 92 | #[error("Model error")] 93 | ModelError(#[from] Box), 94 | 95 | #[error("Fail to remove secondary key: {0}")] 96 | RemoveSecondaryKeyError(String), 97 | 98 | #[error("Inccorect input data it does not match the model")] 99 | IncorrectInputData { value: Vec }, 100 | } 101 | 102 | impl From for Error { 103 | fn from(e: redb::Error) -> Self { 104 | Error::Redb(Box::new(e)) 105 | } 106 | } 107 | 108 | impl From for Error { 109 | fn from(e: redb::DatabaseError) -> Self { 110 | Error::RedbDatabaseError(Box::new(e)) 111 | } 112 | } 113 | 114 | impl From for Error { 115 | fn from(e: redb::TransactionError) -> Self { 116 | Error::RedbTransactionError(Box::new(e)) 117 | } 118 | } 119 | 120 | impl From for Error { 121 | fn from(e: native_model::Error) -> Self { 122 | Error::ModelError(Box::new(e)) 123 | } 124 | } 125 | 126 | #[cfg(feature = "redb1")] 127 | impl From for Error { 128 | fn from(e: redb1::DatabaseError) -> Self { 129 | Error::LegacyRedb1DatabaseError(Box::new(e)) 130 | } 131 | } 132 | 133 | #[cfg(feature = "redb1")] 134 | impl From for Error { 135 | fn from(e: redb1::TransactionError) -> Self { 136 | Error::Redb1TransactionError(Box::new(e)) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/db_type/input.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Error, Key, KeyDefinition, KeyEntry, KeyOptions, Result}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Input { 5 | pub(crate) primary_key: Key, 6 | pub(crate) secondary_keys: std::collections::HashMap, KeyEntry>, 7 | pub(crate) value: Vec, 8 | } 9 | 10 | impl Input { 11 | pub(crate) fn secondary_key_value( 12 | &self, 13 | secondary_key_def: &KeyDefinition, 14 | ) -> Result { 15 | let secondary_key = self.secondary_keys.get(secondary_key_def).ok_or( 16 | Error::SecondaryKeyDefinitionNotFound { 17 | table: "".to_string(), 18 | key: secondary_key_def.unique_table_name.clone(), 19 | }, 20 | )?; 21 | let out = if !secondary_key_def.options.unique { 22 | match secondary_key { 23 | KeyEntry::Default(value) => { 24 | // KeyEntry::Default(composite_key(value, &self.primary_key)) 25 | KeyEntry::Default(value.to_owned()) 26 | } 27 | KeyEntry::Optional(value) => { 28 | // let value = value 29 | // .as_ref() 30 | // .map(|value| composite_key(value, &self.primary_key)); 31 | // KeyEntry::Optional(value) 32 | KeyEntry::Optional(value.as_ref().map(|value| value.to_owned())) 33 | } 34 | } 35 | } else { 36 | secondary_key.clone() 37 | }; 38 | Ok(out) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/db_type/key/key_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Error, Result}; 2 | use crate::{db_type::Key, Model, ToKey}; 3 | use std::{hash::Hash, ops::RangeBounds}; 4 | 5 | pub trait ToKeyDefinition { 6 | fn key_definition(&self) -> KeyDefinition; 7 | } 8 | 9 | #[derive(Default, Clone, Debug)] 10 | pub struct KeyDefinition { 11 | pub(crate) unique_table_name: String, 12 | pub(crate) rust_types: Vec, 13 | pub(crate) options: O, 14 | } 15 | 16 | impl ToKeyDefinition for KeyDefinition { 17 | fn key_definition(&self) -> KeyDefinition { 18 | self.clone() 19 | } 20 | } 21 | 22 | impl KeyDefinition { 23 | pub fn new( 24 | model_id: u32, 25 | model_version: u32, 26 | name: &'static str, 27 | rust_types: Vec, 28 | options: O, 29 | ) -> Self { 30 | let table_name = format!("{}_{}_{}", model_id, model_version, name); 31 | Self { 32 | options, 33 | rust_types, 34 | unique_table_name: table_name, 35 | } 36 | } 37 | 38 | pub fn options(&self) -> &O { 39 | &self.options 40 | } 41 | } 42 | 43 | // impl From<&'static str> for KeyDefinition<()> { 44 | // fn from(name: &'static str) -> Self { 45 | // Self::new(0, 0, name, ()) 46 | // } 47 | // } 48 | 49 | // impl From<&'static str> for KeyDefinition { 50 | // fn from(name: &'static str) -> Self { 51 | // Self::new(0, 0, name, KeyOptions::default()) 52 | // } 53 | // } 54 | 55 | impl PartialEq for KeyDefinition { 56 | fn eq(&self, other: &Self) -> bool { 57 | self.unique_table_name == other.unique_table_name 58 | } 59 | } 60 | 61 | impl Eq for KeyDefinition {} 62 | 63 | impl Hash for KeyDefinition { 64 | fn hash(&self, state: &mut H) { 65 | self.unique_table_name.hash(state); 66 | } 67 | } 68 | 69 | #[derive(Clone, Debug, Default, PartialEq, Eq)] 70 | pub struct KeyOptions { 71 | pub unique: bool, 72 | pub optional: bool, 73 | } 74 | 75 | pub fn composite_key(secondary_key: &Key, primary_key: &Key) -> Key { 76 | let mut secondary_key = secondary_key.clone(); 77 | // The addition of a delimiter (a byte set to `0`) used between the concatenation 78 | // of secondary keys and primary keys ensures that there is always a byte smaller 79 | // than the value of the `end` of an inclusive range, which always ends with a byte 80 | // set to `255`. See `KeyRange` the inclusive range defined with `start..=end`. 81 | secondary_key.extend_with_delimiter(0, primary_key); 82 | secondary_key 83 | } 84 | fn _check_key_type_from_key_definition( 85 | key_definition: &KeyDefinition, 86 | ) -> Result<()> { 87 | if !K::key_names() 88 | .iter() 89 | .any(|name| key_definition.rust_types.contains(name)) 90 | { 91 | return Err(Error::MissmatchedKeyType { 92 | key_name: key_definition.unique_table_name.to_string(), 93 | expected_types: key_definition.rust_types.clone(), 94 | got_types: K::key_names(), 95 | operation: "get".to_string(), 96 | }); 97 | } 98 | Ok(()) 99 | } 100 | 101 | fn _check_key_type(model: &Model) -> Result<()> { 102 | if !K::key_names() 103 | .iter() 104 | .any(|name| model.primary_key.rust_types.contains(name)) 105 | { 106 | return Err(Error::MissmatchedKeyType { 107 | key_name: model.primary_key.unique_table_name.to_string(), 108 | expected_types: model.primary_key.rust_types.clone(), 109 | got_types: K::key_names(), 110 | operation: "get".to_string(), 111 | }); 112 | } 113 | Ok(()) 114 | } 115 | 116 | pub(crate) fn check_key_type(model: &Model, _key: &K) -> Result<()> { 117 | _check_key_type::(model) 118 | } 119 | 120 | pub(crate) fn check_key_type_from_key_definition( 121 | key_definition: &KeyDefinition, 122 | _key: &K, 123 | ) -> Result<()> { 124 | _check_key_type_from_key_definition::(key_definition) 125 | } 126 | 127 | pub(crate) fn check_range_key_range_bounds( 128 | model: &Model, 129 | _range: &impl RangeBounds, 130 | ) -> Result<()> { 131 | _check_key_type::(model) 132 | } 133 | 134 | pub(crate) fn check_range_key_range_bounds_from_key_definition( 135 | key_definition: &KeyDefinition, 136 | _range: &impl RangeBounds, 137 | ) -> Result<()> { 138 | _check_key_type_from_key_definition::(key_definition) 139 | } 140 | -------------------------------------------------------------------------------- /src/db_type/key/key_value.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::Key; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq)] 4 | pub enum KeyEntry { 5 | Default(Key), 6 | Optional(Option), 7 | } 8 | -------------------------------------------------------------------------------- /src/db_type/key/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(clippy::module_inception)] 2 | mod key; 3 | mod key_definition; 4 | mod key_value; 5 | 6 | #[cfg(feature = "redb1")] 7 | pub mod inner_key_value_redb1; 8 | 9 | pub use key::*; 10 | 11 | pub use key_definition::*; 12 | pub use key_value::*; 13 | -------------------------------------------------------------------------------- /src/db_type/mod.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod input; 3 | mod key; 4 | mod output; 5 | mod result; 6 | mod to_input; 7 | 8 | pub use error::*; 9 | pub use input::*; 10 | pub use key::*; 11 | pub(crate) use output::*; 12 | pub use result::*; 13 | pub use to_input::*; 14 | -------------------------------------------------------------------------------- /src/db_type/output.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Result, ToInput}; 2 | 3 | use super::Input; 4 | 5 | #[derive(Clone, Debug)] 6 | pub(crate) struct Output(pub(crate) Vec); 7 | 8 | impl From for Output { 9 | fn from(input: Input) -> Self { 10 | Self(input.value) 11 | } 12 | } 13 | 14 | impl From<&[u8]> for Output { 15 | fn from(slice: &[u8]) -> Self { 16 | Self(slice.to_vec()) 17 | } 18 | } 19 | 20 | impl Output { 21 | pub fn inner(&self) -> Result { 22 | T::native_db_bincode_decode_from_slice(&self.0) 23 | } 24 | } 25 | 26 | pub(crate) fn unwrap_item( 27 | item: Option>, 28 | ) -> Option> { 29 | if let Some(item) = item { 30 | let item = item.value(); 31 | let item = T::native_db_bincode_decode_from_slice(item); 32 | Some(item) 33 | } else { 34 | None 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/db_type/result.rs: -------------------------------------------------------------------------------- 1 | use super::Error; 2 | 3 | pub type Result = std::result::Result; 4 | -------------------------------------------------------------------------------- /src/db_type/to_input.rs: -------------------------------------------------------------------------------- 1 | use crate::Key; 2 | 3 | use super::{Input, KeyDefinition, KeyEntry, KeyOptions, Result}; 4 | 5 | pub trait ToInput: Sized + native_model::Model { 6 | fn native_db_model() -> crate::Model; 7 | fn native_db_primary_key(&self) -> Key; 8 | fn native_db_secondary_keys( 9 | &self, 10 | ) -> std::collections::HashMap, KeyEntry>; 11 | fn native_db_bincode_encode_to_vec(&self) -> Result>; 12 | fn native_db_bincode_decode_from_slice(slice: &[u8]) -> Result; 13 | 14 | fn native_db_input(&self) -> Result { 15 | Ok(Input { 16 | primary_key: self.native_db_primary_key(), 17 | secondary_keys: self.native_db_secondary_keys(), 18 | value: self.native_db_bincode_encode_to_vec()?, 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/metadata/current_version.rs: -------------------------------------------------------------------------------- 1 | pub const CURRENT_VERSION: &str = "0.8.1"; 2 | pub const CURRENT_NATIVE_MODEL_VERSION: &str = "0.4.19"; 3 | -------------------------------------------------------------------------------- /src/metadata/metadata.rs: -------------------------------------------------------------------------------- 1 | use super::CURRENT_NATIVE_MODEL_VERSION; 2 | use super::CURRENT_VERSION; 3 | use semver::Version; 4 | 5 | pub struct Metadata { 6 | current_version: String, 7 | current_native_model_version: String, 8 | previous_version: Option, 9 | previous_native_model_version: Option, 10 | } 11 | 12 | impl Metadata { 13 | pub(crate) fn new(previous_version: String, previous_native_model_version: String) -> Self { 14 | let current_version = Version::parse(CURRENT_VERSION).unwrap(); 15 | let current_native_model_version = Version::parse(CURRENT_NATIVE_MODEL_VERSION).unwrap(); 16 | 17 | Self { 18 | current_version: current_version.to_string(), 19 | current_native_model_version: current_native_model_version.to_string(), 20 | previous_version: Some(previous_version.to_string()), 21 | previous_native_model_version: Some(previous_native_model_version.to_string()), 22 | } 23 | } 24 | 25 | pub fn current_version(&self) -> &str { 26 | &self.current_version 27 | } 28 | 29 | pub fn current_native_model_version(&self) -> &str { 30 | &self.current_native_model_version 31 | } 32 | 33 | pub fn previous_version(&self) -> Option<&str> { 34 | self.previous_version.as_deref() 35 | } 36 | 37 | pub fn previous_native_model_version(&self) -> Option<&str> { 38 | self.previous_native_model_version.as_deref() 39 | } 40 | } 41 | 42 | impl Default for Metadata { 43 | fn default() -> Self { 44 | let current_version = Version::parse(CURRENT_VERSION).unwrap(); 45 | let current_native_model_version = Version::parse(CURRENT_NATIVE_MODEL_VERSION).unwrap(); 46 | 47 | Self { 48 | current_version: current_version.to_string(), 49 | current_native_model_version: current_native_model_version.to_string(), 50 | previous_version: None, 51 | previous_native_model_version: None, 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/metadata/mod.rs: -------------------------------------------------------------------------------- 1 | mod current_version; 2 | #[allow(clippy::module_inception)] 3 | mod metadata; 4 | mod table; 5 | 6 | pub(crate) use current_version::*; 7 | pub use metadata::*; 8 | pub(crate) use table::*; 9 | -------------------------------------------------------------------------------- /src/metadata/table.rs: -------------------------------------------------------------------------------- 1 | use super::Metadata; 2 | use crate::db_type::Result; 3 | use redb::TableDefinition; 4 | 5 | pub const VERSION_NATIVE_DB_NAME: &str = "version_native_db"; 6 | pub const VERSION_NATIVE_MODEL_NAME: &str = "version_native_model"; 7 | 8 | use crate::database_instance::DatabaseInstance; 9 | 10 | const TABLE: TableDefinition<&str, &str> = TableDefinition::new("metadata"); 11 | 12 | pub fn save_metadata(database_instance: &DatabaseInstance, configuration: &Metadata) -> Result<()> { 13 | let table = database_instance.redb_database()?; 14 | let write_thx = table.begin_write()?; 15 | { 16 | let mut table = write_thx.open_table(TABLE)?; 17 | table.insert(VERSION_NATIVE_DB_NAME, configuration.current_version())?; 18 | table.insert( 19 | VERSION_NATIVE_MODEL_NAME, 20 | configuration.current_native_model_version(), 21 | )?; 22 | } 23 | write_thx.commit()?; 24 | 25 | Ok(()) 26 | } 27 | 28 | pub fn load_or_create_metadata(database_instance: &DatabaseInstance) -> Result { 29 | let database = database_instance.redb_database()?; 30 | let read_thx = database.begin_read()?; 31 | 32 | if let Ok(table) = read_thx.open_table(TABLE) { 33 | let current_version = table 34 | .get(VERSION_NATIVE_DB_NAME)? 35 | .expect("Fatal error: current_version not found"); 36 | let current_native_model_version = table 37 | .get(VERSION_NATIVE_MODEL_NAME)? 38 | .expect("Fatal error: current_native_model_version not found"); 39 | Ok(Metadata::new( 40 | current_version.value().to_string(), 41 | current_native_model_version.value().to_string(), 42 | )) 43 | } else { 44 | // Create the metadata table if it does not exist 45 | let metadata = Metadata::default(); 46 | save_metadata(database_instance, &metadata)?; 47 | Ok(metadata) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/model.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Error, KeyDefinition, KeyOptions, Result}; 2 | use std::collections::HashSet; 3 | 4 | /// See the documentation [crate::Models::define] to see how to define a model. 5 | #[derive(Clone, Debug)] 6 | pub struct Model { 7 | pub primary_key: KeyDefinition<()>, 8 | pub secondary_keys: HashSet>, 9 | } 10 | 11 | impl Model { 12 | pub fn check_secondary_options( 13 | &self, 14 | secondary_key: &KeyDefinition, 15 | check: F, 16 | ) -> Result<()> 17 | where 18 | F: Fn(KeyOptions) -> bool, 19 | { 20 | let key = self.secondary_keys.get(secondary_key).ok_or_else(|| { 21 | Error::SecondaryKeyDefinitionNotFound { 22 | table: self.primary_key.unique_table_name.to_string(), 23 | key: secondary_key.unique_table_name.clone(), 24 | } 25 | })?; 26 | 27 | if check(key.options.clone()) { 28 | Ok(()) 29 | } else { 30 | Err(Error::SecondaryKeyConstraintMismatch { 31 | table: self.primary_key.unique_table_name.to_string(), 32 | key: secondary_key.unique_table_name.clone(), 33 | got: key.options.clone(), 34 | }) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/serialization.rs: -------------------------------------------------------------------------------- 1 | pub fn bincode_encode_to_vec(value: &T) -> crate::db_type::Result> 2 | where 3 | T: serde::Serialize + native_model::Model, 4 | { 5 | native_model::encode(value).map_err(|e| e.into()) 6 | } 7 | 8 | pub fn bincode_decode_from_slice(slice: &[u8]) -> crate::db_type::Result<(T, usize)> 9 | where 10 | T: serde::de::DeserializeOwned + native_model::Model, 11 | { 12 | let (data, _) = native_model::decode(slice.to_vec())?; 13 | Ok((data, 0)) 14 | } 15 | -------------------------------------------------------------------------------- /src/snapshot.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::Result; 2 | use crate::{Builder, Database, Models}; 3 | use redb::ReadableMultimapTable; 4 | use redb::ReadableTable; 5 | use std::path::Path; 6 | 7 | impl Database<'_> { 8 | pub fn snapshot<'a>(&self, models: &'a Models, path: &Path) -> Result> { 9 | let new_db = Builder::new().create(models, path)?; 10 | let r = self.instance.redb_database()?.begin_read()?; 11 | let w = new_db.instance.redb_database()?.begin_write()?; 12 | { 13 | // Copy primary tables 14 | for primary_table_definition in self.primary_table_definitions.values() { 15 | let table = r.open_table(primary_table_definition.redb)?; 16 | let mut new_table = w.open_table(primary_table_definition.redb)?; 17 | for result in table.iter()? { 18 | let (key, value) = result?; 19 | new_table.insert(key.value(), value.value())?; 20 | } 21 | 22 | // Copy secondary tables 23 | for secondary_table_definition in primary_table_definition.secondary_tables.values() 24 | { 25 | let table = r.open_multimap_table(secondary_table_definition.redb)?; 26 | let mut new_table = w.open_multimap_table(secondary_table_definition.redb)?; 27 | for result in table.iter()? { 28 | let (secondary_key, primary_keys) = result?; 29 | for primary_key in primary_keys { 30 | let primary_key = primary_key?; 31 | new_table.insert(secondary_key.value(), primary_key.value())?; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | w.commit()?; 38 | Ok(new_db) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Stats { 3 | pub primary_tables: Vec, 4 | pub secondary_tables: Vec, 5 | } 6 | 7 | #[derive(Debug)] 8 | pub struct StatsTable { 9 | pub name: String, 10 | pub n_entries: Option, 11 | } 12 | -------------------------------------------------------------------------------- /src/table_definition.rs: -------------------------------------------------------------------------------- 1 | use crate::database_builder::ModelBuilder; 2 | use crate::db_type::{Key, KeyDefinition, KeyOptions}; 3 | use std::collections::HashMap; 4 | use std::fmt::Debug; 5 | 6 | pub(crate) type RedbPrimaryTableDefinition<'a> = redb::TableDefinition<'a, Key, &'static [u8]>; 7 | pub(crate) type RedbSecondaryTableDefinition<'a> = redb::MultimapTableDefinition<'a, Key, Key>; 8 | 9 | pub struct PrimaryTableDefinition<'a> { 10 | pub(crate) model: crate::Model, 11 | pub(crate) redb: RedbPrimaryTableDefinition<'a>, 12 | pub(crate) secondary_tables: HashMap, SecondaryTableDefinition<'a>>, 13 | pub(crate) native_model_options: NativeModelOptions, 14 | } 15 | 16 | #[derive(Clone, Debug, Default)] 17 | pub struct NativeModelOptions { 18 | pub(crate) native_model_id: u32, 19 | pub(crate) native_model_version: u32, 20 | // If a model as a new version, the old version is still available but marked as legacy. 21 | // NOTE: Is impossible to write or read on a legacy table definition. 22 | // Just a migration to a new version is allowed. 23 | pub(crate) native_model_legacy: bool, 24 | } 25 | 26 | impl<'a> From<(&ModelBuilder, RedbPrimaryTableDefinition<'a>)> for PrimaryTableDefinition<'a> { 27 | fn from(input: (&ModelBuilder, RedbPrimaryTableDefinition<'a>)) -> Self { 28 | let (builder, redb) = input; 29 | Self { 30 | model: builder.model.clone(), 31 | redb, 32 | secondary_tables: HashMap::new(), 33 | native_model_options: builder.native_model_options.clone(), 34 | } 35 | } 36 | } 37 | 38 | impl Debug for PrimaryTableDefinition<'_> { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | use redb::TableHandle; 41 | f.debug_struct("TableDefinition") 42 | .field("name", &self.redb.name()) 43 | .field("model_id", &self.native_model_options.native_model_id) 44 | .field( 45 | "model_version", 46 | &self.native_model_options.native_model_version, 47 | ) 48 | .field("legacy", &self.native_model_options.native_model_legacy) 49 | .finish() 50 | } 51 | } 52 | 53 | #[derive(Clone)] 54 | pub(crate) struct SecondaryTableDefinition<'a> { 55 | pub(crate) redb: RedbSecondaryTableDefinition<'a>, 56 | } 57 | 58 | impl<'a> From> for SecondaryTableDefinition<'a> { 59 | fn from(rdb: RedbSecondaryTableDefinition<'a>) -> SecondaryTableDefinition<'a> { 60 | Self { redb: rdb } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/transaction/internal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod private_readable_transaction; 2 | pub mod r_transaction; 3 | pub mod rw_transaction; 4 | -------------------------------------------------------------------------------- /src/transaction/internal/private_readable_transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{ 2 | Error, Key, KeyDefinition, KeyOptions, Output, Result, ToKey, ToKeyDefinition, 3 | }; 4 | use crate::table_definition::PrimaryTableDefinition; 5 | use crate::Model; 6 | use redb::ReadableTableMetadata; 7 | use redb::{ReadableMultimapTable, ReadableTable}; 8 | use std::collections::HashMap; 9 | 10 | pub trait PrivateReadableTransaction<'db, 'txn> { 11 | type RedbPrimaryTable: ReadableTable; 12 | type RedbSecondaryTable: ReadableMultimapTable; 13 | 14 | type RedbTransaction<'db_bis> 15 | where 16 | Self: 'db_bis; 17 | 18 | fn table_definitions(&self) -> &HashMap; 19 | 20 | fn get_primary_table(&'txn self, model: &Model) -> Result; 21 | 22 | fn get_secondary_table( 23 | &'txn self, 24 | model: &Model, 25 | secondary_key: &KeyDefinition, 26 | ) -> Result; 27 | 28 | fn get_by_primary_key(&'txn self, model: Model, key: impl ToKey) -> Result> { 29 | let table = self.get_primary_table(&model)?; 30 | let key = key.to_key(); 31 | let item = table.get(key)?; 32 | Ok(item.map(|item| item.value().into())) 33 | } 34 | 35 | fn get_by_secondary_key( 36 | &'txn self, 37 | model: Model, 38 | key_def: impl ToKeyDefinition, 39 | key: impl ToKey, 40 | ) -> Result> { 41 | let secondary_key = key_def.key_definition(); 42 | // Provide a better error for the test of unicity of the secondary key 43 | model.check_secondary_options(&secondary_key, |options| options.unique)?; 44 | 45 | let table = self.get_secondary_table(&model, &secondary_key)?; 46 | 47 | let mut primary_keys = table.get(key.to_key())?; 48 | let primary_key = if let Some(primary_key) = primary_keys.next() { 49 | let primary_key = primary_key?; 50 | primary_key.value().to_owned() 51 | } else { 52 | return Ok(None); 53 | }; 54 | 55 | Ok(Some( 56 | self.get_by_primary_key(model, primary_key)? 57 | .ok_or(Error::PrimaryKeyNotFound)?, 58 | )) 59 | } 60 | 61 | fn primary_len(&'txn self, model: Model) -> Result { 62 | let table = self.get_primary_table(&model)?; 63 | let result = table.len()?; 64 | Ok(result) 65 | } 66 | 67 | fn secondary_len( 68 | &'txn self, 69 | model: Model, 70 | key_def: impl ToKeyDefinition, 71 | ) -> Result { 72 | let table = self.get_secondary_table(&model, &key_def.key_definition())?; 73 | let result = table.len()?; 74 | Ok(result) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/transaction/internal/r_transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Error, Key, KeyDefinition, KeyOptions, Result}; 2 | use crate::table_definition::PrimaryTableDefinition; 3 | use crate::transaction::internal::private_readable_transaction::PrivateReadableTransaction; 4 | use crate::Model; 5 | use std::collections::HashMap; 6 | 7 | pub struct InternalRTransaction<'db> { 8 | pub(crate) redb_transaction: redb::ReadTransaction, 9 | pub(crate) table_definitions: &'db HashMap>, 10 | } 11 | 12 | impl<'db, 'txn> PrivateReadableTransaction<'db, 'txn> for InternalRTransaction<'db> 13 | where 14 | Self: 'txn, 15 | Self: 'db, 16 | { 17 | type RedbPrimaryTable = redb::ReadOnlyTable; 18 | type RedbSecondaryTable = redb::ReadOnlyMultimapTable; 19 | 20 | type RedbTransaction<'db_bis> 21 | = redb::ReadTransaction 22 | where 23 | Self: 'db_bis; 24 | 25 | fn table_definitions(&self) -> &HashMap { 26 | self.table_definitions 27 | } 28 | 29 | fn get_primary_table(&'txn self, model: &Model) -> Result { 30 | let table_definition = self 31 | .table_definitions() 32 | .get(model.primary_key.unique_table_name.as_str()) 33 | .ok_or_else(|| Error::TableDefinitionNotFound { 34 | table: model.primary_key.unique_table_name.to_string(), 35 | })?; 36 | let table = self.redb_transaction.open_table(table_definition.redb)?; 37 | Ok(table) 38 | } 39 | 40 | fn get_secondary_table( 41 | &'txn self, 42 | model: &Model, 43 | secondary_key: &KeyDefinition, 44 | ) -> Result { 45 | let main_table_definition = self 46 | .table_definitions() 47 | .get(model.primary_key.unique_table_name.as_str()) 48 | .ok_or_else(|| Error::TableDefinitionNotFound { 49 | table: model.primary_key.unique_table_name.to_string(), 50 | })?; 51 | let secondary_table_definition = main_table_definition 52 | .secondary_tables 53 | .get(secondary_key) 54 | .ok_or_else(|| Error::TableDefinitionNotFound { 55 | table: secondary_key.unique_table_name.to_string(), 56 | })?; 57 | let table = self 58 | .redb_transaction 59 | .open_multimap_table(secondary_table_definition.redb)?; 60 | Ok(table) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/transaction/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod internal; 2 | 3 | /// All database interactions. 4 | pub mod query; 5 | 6 | mod r_transaction; 7 | 8 | mod rw_transaction; 9 | 10 | /// Read-only transaction. 11 | pub use r_transaction::*; 12 | /// Read-write transaction. 13 | pub use rw_transaction::*; 14 | -------------------------------------------------------------------------------- /src/transaction/query/drain.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{KeyOptions, Result, ToInput, ToKeyDefinition}; 2 | use crate::transaction::internal::rw_transaction::InternalRwTransaction; 3 | 4 | pub struct RwDrain<'db, 'txn> { 5 | pub(crate) internal: &'txn InternalRwTransaction<'db>, 6 | } 7 | 8 | impl RwDrain<'_, '_> { 9 | /// Drain all items. 10 | /// 11 | /// **TODO: needs to be improved, so don't use it yet.** 12 | pub fn primary(&self) -> Result> { 13 | let model = T::native_db_model(); 14 | let out = self.internal.concrete_primary_drain(model)?; 15 | let out = out 16 | .into_iter() 17 | .map(|b| b.inner()) 18 | .collect::>>()?; 19 | Ok(out) 20 | } 21 | 22 | /// Drain all items with a given secondary key. 23 | /// 24 | /// **TODO: needs to be implemented** 25 | pub fn secondary(&self, _key_def: impl ToKeyDefinition) { 26 | todo!() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/transaction/query/len.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{KeyOptions, Result, ToInput, ToKeyDefinition}; 2 | use crate::transaction::internal::private_readable_transaction::PrivateReadableTransaction; 3 | use crate::transaction::internal::r_transaction::InternalRTransaction; 4 | use crate::transaction::internal::rw_transaction::InternalRwTransaction; 5 | 6 | /// Get the number of values in the database. 7 | pub struct RLen<'db, 'txn> { 8 | pub(crate) internal: &'txn InternalRTransaction<'db>, 9 | } 10 | 11 | impl RLen<'_, '_> { 12 | /// Get the number of values. 13 | /// 14 | /// # Example 15 | /// ```rust 16 | /// use native_db::*; 17 | /// use native_db::native_model::{native_model, Model}; 18 | /// use serde::{Deserialize, Serialize}; 19 | /// 20 | /// #[derive(Serialize, Deserialize)] 21 | /// #[native_model(id=1, version=1)] 22 | /// #[native_db] 23 | /// struct Data { 24 | /// #[primary_key] 25 | /// id: u64, 26 | /// } 27 | /// 28 | /// fn main() -> Result<(), db_type::Error> { 29 | /// let mut models = Models::new(); 30 | /// models.define::()?; 31 | /// let db = Builder::new().create_in_memory(&models)?; 32 | /// 33 | /// // Open a read transaction 34 | /// let r = db.r_transaction()?; 35 | /// 36 | /// // Get all values 37 | /// let _number:u64 = r.len().primary::()?; 38 | /// Ok(()) 39 | /// } 40 | /// ``` 41 | pub fn primary(&self) -> Result { 42 | let model = T::native_db_model(); 43 | let result = self.internal.primary_len(model)?; 44 | Ok(result) 45 | } 46 | 47 | /// Get the number of values by secondary key. 48 | /// 49 | /// Anatomy of a secondary key it is a `enum` with the following structure: `Key::`. 50 | /// 51 | /// If the secondary key is [`optional`](struct.Builder.html#optional) you will 52 | /// get all values that have the secondary key set. 53 | /// 54 | /// # Example 55 | /// ```rust 56 | /// use native_db::*; 57 | /// use native_db::native_model::{native_model, Model}; 58 | /// use serde::{Deserialize, Serialize}; 59 | /// 60 | /// #[derive(Serialize, Deserialize)] 61 | /// #[native_model(id=1, version=1)] 62 | /// #[native_db] 63 | /// struct Data { 64 | /// #[primary_key] 65 | /// id: u64, 66 | /// #[secondary_key(optional)] 67 | /// name: Option, 68 | /// } 69 | /// 70 | /// 71 | /// fn main() -> Result<(), db_type::Error> { 72 | /// let mut models = Models::new(); 73 | /// models.define::()?; 74 | /// let db = Builder::new().create_in_memory(&models)?; 75 | /// 76 | /// // Open a read transaction 77 | /// let r = db.r_transaction()?; 78 | /// 79 | /// // Get the number of values with the secondary key set 80 | /// let _number:u64 = r.len().secondary::(DataKey::name)?; 81 | /// Ok(()) 82 | /// } 83 | /// ``` 84 | pub fn secondary(&self, key_def: impl ToKeyDefinition) -> Result { 85 | let model = T::native_db_model(); 86 | let result = self.internal.secondary_len(model, key_def)?; 87 | Ok(result) 88 | } 89 | } 90 | 91 | pub struct RwLen<'db, 'txn> { 92 | pub(crate) internal: &'txn InternalRwTransaction<'db>, 93 | } 94 | 95 | impl RwLen<'_, '_> { 96 | /// Get the number of values. 97 | /// 98 | /// Same as [`RLen::primary()`](struct.RLen.html#method.primary). 99 | pub fn primary(&self) -> Result { 100 | let model = T::native_db_model(); 101 | let result = self.internal.primary_len(model)?; 102 | Ok(result) 103 | } 104 | 105 | /// Get the number of values by secondary key. 106 | /// 107 | /// Same as [`RLen::secondary()`](struct.RLen.html#method.secondary). 108 | pub fn secondary(&self, key_def: impl ToKeyDefinition) -> Result { 109 | let model = T::native_db_model(); 110 | let result = self.internal.secondary_len(model, key_def)?; 111 | Ok(result) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/transaction/query/mod.rs: -------------------------------------------------------------------------------- 1 | mod drain; 2 | mod get; 3 | mod len; 4 | mod scan; 5 | 6 | pub use drain::*; 7 | pub use get::*; 8 | pub use len::*; 9 | pub use scan::*; 10 | -------------------------------------------------------------------------------- /src/transaction/query/scan/mod.rs: -------------------------------------------------------------------------------- 1 | mod primary_scan; 2 | mod secondary_scan; 3 | 4 | use crate::db_type::{Key, KeyOptions, Result, ToInput, ToKeyDefinition}; 5 | pub use primary_scan::*; 6 | pub use secondary_scan::*; 7 | 8 | use crate::transaction::internal::private_readable_transaction::PrivateReadableTransaction; 9 | use crate::transaction::internal::r_transaction::InternalRTransaction; 10 | use crate::transaction::internal::rw_transaction::InternalRwTransaction; 11 | 12 | /// Get values from the database. 13 | pub struct RScan<'db, 'txn> { 14 | pub(crate) internal: &'txn InternalRTransaction<'db>, 15 | } 16 | 17 | impl RScan<'_, '_> { 18 | /// Get a values from the database by primary key. 19 | /// 20 | /// - [`all`](crate::transaction::query::PrimaryScan::all) - Scan all items. 21 | /// - [`start_with`](crate::transaction::query::PrimaryScan::start_with) - Scan items with a primary key starting with a key. 22 | /// - [`range`](crate::transaction::query::PrimaryScan::range) - Scan items with a primary key in a given range. 23 | pub fn primary( 24 | &self, 25 | ) -> Result, T>> { 26 | let model = T::native_db_model(); 27 | let table = self.internal.get_primary_table(&model)?; 28 | let out = PrimaryScan::new(table); 29 | Ok(out) 30 | } 31 | 32 | #[allow(clippy::type_complexity)] 33 | /// Get a values from the database by secondary key. 34 | /// 35 | /// - [`all`](crate::transaction::query::SecondaryScan::all) - Scan all items. 36 | /// - [`start_with`](crate::transaction::query::SecondaryScan::start_with) - Scan items with a secondary key starting with a key. 37 | /// - [`range`](crate::transaction::query::SecondaryScan::range) - Scan items with a secondary key in a given range. 38 | pub fn secondary( 39 | &self, 40 | key_def: impl ToKeyDefinition, 41 | ) -> Result< 42 | SecondaryScan< 43 | redb::ReadOnlyTable, 44 | redb::ReadOnlyMultimapTable, 45 | T, 46 | >, 47 | > { 48 | let model = T::native_db_model(); 49 | let primary_table = self.internal.get_primary_table(&model)?; 50 | let secondary_key = key_def.key_definition(); 51 | let secondary_table = self.internal.get_secondary_table(&model, &secondary_key)?; 52 | let out = SecondaryScan::new(primary_table, secondary_table, key_def); 53 | Ok(out) 54 | } 55 | } 56 | 57 | pub struct RwScan<'db, 'txn> { 58 | pub(crate) internal: &'txn InternalRwTransaction<'db>, 59 | } 60 | 61 | impl<'db, 'txn> RwScan<'db, 'txn> 62 | where 63 | 'txn: 'db, 64 | { 65 | /// Get a values from the database by primary key. 66 | /// 67 | /// - [`all`](crate::transaction::query::PrimaryScan::all) - Scan all items. 68 | /// - [`start_with`](crate::transaction::query::PrimaryScan::start_with) - Scan items with a primary key starting with a key. 69 | /// - [`range`](crate::transaction::query::PrimaryScan::range) - Scan items with a primary key in a given range. 70 | pub fn primary( 71 | &self, 72 | ) -> Result, T>> { 73 | let model = T::native_db_model(); 74 | let table = self.internal.get_primary_table(&model)?; 75 | let out = PrimaryScan::new(table); 76 | Ok(out) 77 | } 78 | 79 | #[allow(clippy::type_complexity)] 80 | /// Get a values from the database by secondary key. 81 | /// 82 | /// - [`all`](crate::transaction::query::SecondaryScan::all) - Scan all items. 83 | /// - [`start_with`](crate::transaction::query::SecondaryScan::start_with) - Scan items with a secondary key starting with a key. 84 | /// - [`range`](crate::transaction::query::SecondaryScan::range) - Scan items with a secondary key in a given range. 85 | pub fn secondary( 86 | &self, 87 | key_def: impl ToKeyDefinition, 88 | ) -> Result< 89 | SecondaryScan, redb::MultimapTable<'db, Key, Key>, T>, 90 | > { 91 | let model = T::native_db_model(); 92 | let primary_table = self.internal.get_primary_table(&model)?; 93 | let secondary_key = key_def.key_definition(); 94 | let secondary_table = self.internal.get_secondary_table(&model, &secondary_key)?; 95 | let out = SecondaryScan::new(primary_table, secondary_table, key_def); 96 | Ok(out) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/transaction/r_transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::transaction::internal::r_transaction::InternalRTransaction; 2 | use crate::transaction::query::RGet; 3 | use crate::transaction::query::RLen; 4 | use crate::transaction::query::RScan; 5 | 6 | pub struct RTransaction<'db> { 7 | pub(crate) internal: InternalRTransaction<'db>, 8 | } 9 | 10 | impl<'db> RTransaction<'db> { 11 | /// Get a value from the database. 12 | /// 13 | /// - [`primary`](crate::transaction::query::RGet::primary) - Get a item by primary key. 14 | /// - [`secondary`](crate::transaction::query::RGet::secondary) - Get a item by secondary key. 15 | pub fn get<'txn>(&'txn self) -> RGet<'db, 'txn> { 16 | RGet { 17 | internal: &self.internal, 18 | } 19 | } 20 | 21 | /// Get values from the database. 22 | /// 23 | /// - [`primary`](crate::transaction::query::RScan::primary) - Scan items by primary key. 24 | /// - [`secondary`](crate::transaction::query::RScan::secondary) - Scan items by secondary key. 25 | pub fn scan<'txn>(&'txn self) -> RScan<'db, 'txn> { 26 | RScan { 27 | internal: &self.internal, 28 | } 29 | } 30 | 31 | /// Get the number of values in the database. 32 | /// 33 | /// - [`primary`](crate::transaction::query::RLen::primary) - Get the number of items by primary key. 34 | /// - [`secondary`](crate::transaction::query::RLen::secondary) - Get the number of items by secondary key. 35 | pub fn len<'txn>(&'txn self) -> RLen<'db, 'txn> { 36 | RLen { 37 | internal: &self.internal, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/upgrade/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "redb1")] 2 | mod redb1_to_redb2; 3 | #[cfg(feature = "upgrade_0_7_x")] 4 | mod secondary_index_table_multimap; 5 | 6 | use std::{collections::HashMap, path::Path}; 7 | 8 | use crate::{database_instance::DatabaseInstance, db_type::Result, Configuration, ModelBuilder}; 9 | 10 | pub(crate) fn upgrade_redb( 11 | database_configuration: &Configuration, 12 | path: impl AsRef, 13 | _model_builder: &HashMap, 14 | ) -> Result { 15 | #[cfg(feature = "redb1")] 16 | redb1_to_redb2::upgrade_redb1_to_redb2(database_configuration, &path, _model_builder)?; 17 | 18 | let redb_builder = database_configuration.new_rdb_builder(); 19 | let database_instance = DatabaseInstance::open_on_disk(redb_builder, &path)?; 20 | 21 | Ok(database_instance) 22 | } 23 | 24 | pub(crate) fn upgrade_underlying_database( 25 | _database_instance: &DatabaseInstance, 26 | _model_builder: &HashMap, 27 | ) -> Result<()> { 28 | #[cfg(feature = "upgrade_0_7_x")] 29 | secondary_index_table_multimap::upgrade_secondary_index_table_multimap( 30 | _database_instance, 31 | _model_builder, 32 | )?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /src/upgrade/redb1_to_redb2.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::inner_key_value_redb1::DatabaseInnerKeyValue as Redb1DatabaseInnerKeyValue; 2 | use crate::db_type::Key as Redb2DatabaseInnerKeyValue; 3 | use crate::db_type::Result; 4 | use crate::table_definition::{ 5 | RedbPrimaryTableDefinition as Redb2PrimaryTableDefinition, 6 | RedbSecondaryTableDefinition as Redb2SecondaryTableDefinition, 7 | }; 8 | use crate::{Configuration, ModelBuilder}; 9 | use redb as redb2; 10 | use redb1; 11 | use std::collections::HashMap; 12 | use std::path::Path; 13 | 14 | pub(crate) type Redb1PrimaryTableDefinition<'a> = 15 | redb1::TableDefinition<'a, Redb1DatabaseInnerKeyValue, &'static [u8]>; 16 | pub(crate) type Redb1SecondaryTableDefinition<'a> = 17 | redb1::TableDefinition<'a, Redb1DatabaseInnerKeyValue, Redb1DatabaseInnerKeyValue>; 18 | 19 | fn upgrade_primary_table( 20 | table_name: &str, 21 | db1: &redb1::Database, 22 | db2: &redb2::Database, 23 | ) -> Result { 24 | let redb1_primary_table_definition: Redb1PrimaryTableDefinition = 25 | redb1::TableDefinition::new(table_name); 26 | let redb2_primary_table_definition: Redb2PrimaryTableDefinition = 27 | redb2::TableDefinition::new(table_name); 28 | 29 | let redb1_read_txn: redb1::ReadTransaction = db1.begin_read()?; 30 | let redb2_write_txn = db2.begin_write()?; 31 | 32 | { 33 | let redb1_table = 34 | if let Ok(redb1_table) = redb1_read_txn.open_table(redb1_primary_table_definition) { 35 | redb1_table 36 | } else { 37 | return Ok(false); 38 | }; 39 | let mut redb2_table = redb2_write_txn.open_table(redb2_primary_table_definition)?; 40 | 41 | use redb1::ReadableTable; 42 | for r in redb1_table.iter()? { 43 | let (key, value) = r?; 44 | let key = Redb2DatabaseInnerKeyValue::from(key.value()); 45 | redb2_table.insert(key, value.value())?; 46 | } 47 | } 48 | 49 | redb2_write_txn.commit()?; 50 | 51 | Ok(true) 52 | } 53 | 54 | fn upgrade_secondary_table( 55 | table_name: &str, 56 | db1: &redb1::Database, 57 | db2: &redb2::Database, 58 | ) -> Result<()> { 59 | let redb1_primary_table_definition: Redb1SecondaryTableDefinition = 60 | redb1::TableDefinition::new(table_name); 61 | let redb2_primary_table_definition: Redb2SecondaryTableDefinition = 62 | redb2::MultimapTableDefinition::new(table_name); 63 | 64 | let redb1_read_txn: redb1::ReadTransaction = db1.begin_read()?; 65 | let redb2_write_txn = db2.begin_write()?; 66 | 67 | { 68 | let redb1_table = redb1_read_txn.open_table(redb1_primary_table_definition)?; 69 | let mut redb2_table = 70 | redb2_write_txn.open_multimap_table(redb2_primary_table_definition)?; 71 | 72 | use redb1::ReadableTable; 73 | for r in redb1_table.iter()? { 74 | let (key, value) = r?; 75 | let key = Redb2DatabaseInnerKeyValue::from(key.value()); 76 | let value = Redb2DatabaseInnerKeyValue::from(value.value()); 77 | redb2_table.insert(key, value)?; 78 | } 79 | } 80 | 81 | redb2_write_txn.commit()?; 82 | 83 | Ok(()) 84 | } 85 | 86 | pub(crate) fn upgrade_redb1_to_redb2( 87 | database_configuration: &Configuration, 88 | path: impl AsRef, 89 | model_builder: &HashMap, 90 | ) -> Result<()> { 91 | let redb1_builder = database_configuration.redb1_new_rdb1_builder(); 92 | let redb2_builder = database_configuration.new_rdb_builder(); 93 | 94 | let redb1_path = path.as_ref().to_path_buf(); 95 | let redb2_path = redb1_path.with_file_name(format!( 96 | "{}_redb2", 97 | redb1_path.file_name().unwrap().to_str().unwrap() 98 | )); 99 | 100 | let db1 = redb1_builder.open(&redb1_path)?; 101 | let mut db2 = redb2_builder.create(&redb2_path)?; 102 | 103 | for model_builder in model_builder.values() { 104 | let exist = upgrade_primary_table( 105 | model_builder.model.primary_key.unique_table_name.as_str(), 106 | &db1, 107 | &db2, 108 | )?; 109 | if !exist { 110 | continue; 111 | } 112 | 113 | for secondary_key in model_builder.model.secondary_keys.iter() { 114 | let secondary_table_name = secondary_key.unique_table_name.as_str(); 115 | upgrade_secondary_table(secondary_table_name, &db1, &db2)?; 116 | } 117 | } 118 | 119 | db2.compact()?; 120 | drop(db2); 121 | drop(db1); 122 | 123 | std::fs::remove_file(&redb1_path)?; 124 | std::fs::rename(&redb2_path, &redb1_path)?; 125 | 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /src/upgrade/secondary_index_table_multimap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::db_type::Result; 4 | use crate::table_definition::RedbSecondaryTableDefinition; 5 | use crate::{database_instance, Key, ModelBuilder, ToKey}; 6 | use redb::ReadableMultimapTable; 7 | 8 | pub(crate) type OldSecondaryTableDefinition<'a> = redb::TableDefinition<'a, Key, Key>; 9 | 10 | pub(crate) fn upgrade_secondary_index_table_multimap( 11 | database_instance: &database_instance::DatabaseInstance, 12 | model_builder: &HashMap, 13 | ) -> Result<()> { 14 | // List secondary index tables 15 | for model_builder in model_builder.values() { 16 | for secondary_key in model_builder.model.secondary_keys.iter() { 17 | let secondary_table_name = secondary_key.unique_table_name.as_str(); 18 | let secondary_table_name_tmp = secondary_table_name.to_string() + "_tmp"; 19 | 20 | let old_table_definition: OldSecondaryTableDefinition = 21 | redb::TableDefinition::new(secondary_table_name); 22 | let tmp_table_definition: RedbSecondaryTableDefinition = 23 | redb::MultimapTableDefinition::new(&secondary_table_name_tmp); 24 | let new_table_definition: RedbSecondaryTableDefinition = 25 | redb::MultimapTableDefinition::new(secondary_table_name); 26 | 27 | let db = database_instance.redb_database()?; 28 | // Drain all data from the old table to the tmp table 29 | let rw = db.begin_write()?; 30 | { 31 | let mut table = if let Ok(table) = rw.open_table(old_table_definition) { 32 | table 33 | } else { 34 | continue; 35 | }; 36 | // Read an insert in the tmp table 37 | let mut tmp_table = rw.open_multimap_table(tmp_table_definition)?; 38 | loop { 39 | let result = if let Some(result) = table.pop_first()? { 40 | result 41 | } else { 42 | break; 43 | }; 44 | let (key, value) = result; 45 | 46 | let key_combination = key.value(); 47 | let key_combinaison = key_combination.as_slice(); 48 | let primary_key = value.value(); 49 | let primary_key = primary_key.as_slice(); 50 | // Secondary key = primary key trim end of primary key 51 | let secondary_key = 52 | key_combinaison[0..key_combinaison.len() - primary_key.len()].to_vec(); 53 | 54 | tmp_table.insert(secondary_key.to_key(), primary_key.to_key())?; 55 | } 56 | } 57 | // Remove the old table 58 | rw.delete_table(old_table_definition)?; 59 | rw.commit()?; 60 | 61 | // Drain all data from the tmp table to the new table 62 | let rw = db.begin_write()?; 63 | { 64 | let tmp_table = rw.open_multimap_table(tmp_table_definition)?; 65 | let mut table = rw.open_multimap_table(new_table_definition)?; 66 | for result in tmp_table.iter()? { 67 | let (secondary_key, primary_keys) = result?; 68 | for primary_key in primary_keys { 69 | let primary_key = primary_key?; 70 | table.insert(secondary_key.value(), primary_key.value())?; 71 | } 72 | } 73 | } 74 | // Remove the tmp table 75 | rw.delete_multimap_table(tmp_table_definition)?; 76 | rw.commit()?; 77 | } 78 | } 79 | 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /src/watch/batch.rs: -------------------------------------------------------------------------------- 1 | use crate::watch::{Event, WatcherRequest}; 2 | use std::fmt::Debug; 3 | 4 | #[derive(Clone)] 5 | pub struct Batch(Vec<(WatcherRequest, Event)>); 6 | 7 | impl Batch { 8 | pub(crate) fn new() -> Self { 9 | Self(Vec::new()) 10 | } 11 | 12 | pub(crate) fn add(&mut self, watcher_request: WatcherRequest, event: Event) { 13 | self.0.push((watcher_request, event)); 14 | } 15 | } 16 | 17 | impl Iterator for Batch { 18 | type Item = (WatcherRequest, Event); 19 | 20 | fn next(&mut self) -> Option { 21 | self.0.pop() 22 | } 23 | } 24 | 25 | impl Debug for Batch { 26 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 27 | write!(f, "[")?; 28 | for (watcher_request, event) in &self.0 { 29 | write!(f, "({:?}, {:?}), ", watcher_request.primary_key, event)?; 30 | } 31 | write!(f, "]") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/watch/event.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Output, Result, ToInput}; 2 | use std::fmt::Debug; 3 | 4 | #[derive(Clone)] 5 | pub enum Event { 6 | Insert(Insert), 7 | Update(Update), 8 | Delete(Delete), 9 | } 10 | 11 | impl Event { 12 | pub(crate) fn new_insert(value: Output) -> Self { 13 | Self::Insert(Insert(value)) 14 | } 15 | 16 | pub(crate) fn new_update(old_value: Output, new_value: Output) -> Self { 17 | Self::Update(Update { 18 | old: old_value, 19 | new: new_value, 20 | }) 21 | } 22 | 23 | pub(crate) fn new_delete(value: Output) -> Self { 24 | Self::Delete(Delete(value)) 25 | } 26 | } 27 | 28 | /// Get the inner value of the event 29 | /// 30 | /// NOTE: for update, it returns the new value 31 | impl Event { 32 | pub fn inner(&self) -> Result { 33 | match self { 34 | Event::Insert(insert) => insert.inner(), 35 | Event::Update(update) => update.inner_new(), 36 | Event::Delete(delete) => delete.inner(), 37 | } 38 | } 39 | } 40 | 41 | impl Debug for Event { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | match self { 44 | Event::Insert(_) => write!(f, "Insert"), 45 | Event::Update(_) => write!(f, "Update"), 46 | Event::Delete(_) => write!(f, "Delete"), 47 | } 48 | } 49 | } 50 | 51 | #[derive(Clone)] 52 | pub struct Insert(pub(crate) Output); 53 | 54 | impl Insert { 55 | pub fn inner(&self) -> Result { 56 | self.0.inner() 57 | } 58 | } 59 | 60 | #[derive(Clone)] 61 | pub struct Update { 62 | pub(crate) old: Output, 63 | pub(crate) new: Output, 64 | } 65 | 66 | impl Update { 67 | pub fn inner_old(&self) -> Result { 68 | self.old.inner() 69 | } 70 | pub fn inner_new(&self) -> Result { 71 | self.new.inner() 72 | } 73 | } 74 | 75 | #[derive(Clone)] 76 | pub struct Delete(pub(crate) Output); 77 | 78 | impl Delete { 79 | pub fn inner(&self) -> Result { 80 | self.0.inner() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/watch/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Key, KeyDefinition, KeyOptions, ToKeyDefinition}; 2 | 3 | #[derive(Eq, PartialEq, Clone)] 4 | pub(crate) struct TableFilter { 5 | pub(crate) table_name: String, 6 | pub(crate) key_filter: KeyFilter, 7 | } 8 | 9 | #[derive(Eq, PartialEq, Clone)] 10 | pub(crate) enum KeyFilter { 11 | Primary(Option), 12 | PrimaryStartWith(Key), 13 | Secondary(KeyDefinition, Option), 14 | SecondaryStartWith(KeyDefinition, Key), 15 | } 16 | 17 | impl TableFilter { 18 | pub(crate) fn new_primary(table_name: String, key: Option) -> Self { 19 | Self { 20 | table_name, 21 | key_filter: KeyFilter::Primary(key.map(|k| k.to_owned())), 22 | } 23 | } 24 | 25 | pub(crate) fn new_primary_start_with(table_name: String, key_prefix: Key) -> Self { 26 | Self { 27 | table_name, 28 | key_filter: KeyFilter::PrimaryStartWith(key_prefix.to_owned()), 29 | } 30 | } 31 | 32 | pub(crate) fn new_secondary>( 33 | table_name: String, 34 | key_def: &K, 35 | key: Option, 36 | ) -> Self { 37 | Self { 38 | table_name, 39 | key_filter: KeyFilter::Secondary(key_def.key_definition(), key.map(|k| k.to_owned())), 40 | } 41 | } 42 | 43 | pub(crate) fn new_secondary_start_with>( 44 | table_name: String, 45 | key: &K, 46 | key_prefix: Key, 47 | ) -> Self { 48 | Self { 49 | table_name, 50 | key_filter: KeyFilter::SecondaryStartWith(key.key_definition(), key_prefix.to_owned()), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/watch/mod.rs: -------------------------------------------------------------------------------- 1 | mod batch; 2 | mod event; 3 | mod filter; 4 | pub mod query; 5 | mod request; 6 | mod sender; 7 | 8 | pub(crate) use batch::*; 9 | pub use event::*; 10 | pub(crate) use filter::*; 11 | pub(crate) use request::*; 12 | pub(crate) use sender::*; 13 | 14 | use std::{ 15 | sync::{Arc, RwLock}, 16 | vec, 17 | }; 18 | 19 | #[cfg(not(feature = "tokio"))] 20 | use std::sync::mpsc::SendError; 21 | #[cfg(feature = "tokio")] 22 | use tokio::sync::mpsc::error::SendError; 23 | 24 | use thiserror::Error; 25 | 26 | #[derive(Error, Debug)] 27 | pub enum WatchEventError { 28 | #[error("LockErrorPoisoned")] 29 | LockErrorPoisoned, 30 | #[cfg(not(feature = "tokio"))] 31 | #[error("SendError")] 32 | SendError(#[from] std::sync::mpsc::SendError), 33 | #[cfg(feature = "tokio")] 34 | #[error("SendError")] 35 | SendError(#[from] tokio::sync::mpsc::error::SendError), 36 | } 37 | 38 | #[cfg(not(feature = "tokio"))] 39 | pub type MpscSender = std::sync::mpsc::Sender; 40 | #[cfg(not(feature = "tokio"))] 41 | pub type MpscReceiver = std::sync::mpsc::Receiver; 42 | 43 | #[cfg(feature = "tokio")] 44 | pub type MpscSender = tokio::sync::mpsc::UnboundedSender; 45 | #[cfg(feature = "tokio")] 46 | pub type MpscReceiver = tokio::sync::mpsc::UnboundedReceiver; 47 | 48 | pub(crate) fn push_batch( 49 | senders: Arc>, 50 | batch: Batch, 51 | ) -> Result<(), WatchEventError> { 52 | let watchers = senders 53 | .read() 54 | .map_err(|_| WatchEventError::LockErrorPoisoned)?; 55 | 56 | let mut unused_watchers = vec![]; 57 | for (watcher_request, event) in batch { 58 | for (id, sender) in watchers.find_senders(&watcher_request) { 59 | let l_sender = sender.lock().unwrap(); 60 | if let Err(SendError(_)) = l_sender.send(event.clone()) { 61 | unused_watchers.push(id); 62 | } 63 | } 64 | } 65 | // Drop the lock before removing the watchers to avoid deadlock 66 | drop(watchers); 67 | 68 | // Remove unused watchers 69 | if !unused_watchers.is_empty() { 70 | let mut w = senders 71 | .write() 72 | .map_err(|_| WatchEventError::LockErrorPoisoned)?; 73 | for id in unused_watchers { 74 | w.remove_sender(id); 75 | } 76 | } 77 | 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /src/watch/query/get.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{ 2 | check_key_type, check_key_type_from_key_definition, KeyOptions, Result, ToInput, ToKey, 3 | ToKeyDefinition, 4 | }; 5 | use crate::watch; 6 | use crate::watch::query::internal; 7 | use crate::watch::MpscReceiver; 8 | 9 | /// Watch only one value. 10 | pub struct WatchGet<'db, 'w> { 11 | pub(crate) internal: &'w internal::InternalWatch<'db>, 12 | } 13 | 14 | impl WatchGet<'_, '_> { 15 | /// Watch the primary key. 16 | /// 17 | /// Returns a channel receiver and the watcher id. 18 | /// The watcher id can be used to unwatch the channel. 19 | /// 20 | /// # Example 21 | /// ```rust 22 | /// use native_db::*; 23 | /// use native_db::native_model::{native_model, Model}; 24 | /// use serde::{Deserialize, Serialize}; 25 | /// 26 | /// #[derive(Serialize, Deserialize)] 27 | /// #[native_model(id=1, version=1)] 28 | /// #[native_db] 29 | /// struct Data { 30 | /// #[primary_key] 31 | /// id: u64, 32 | /// } 33 | /// 34 | /// fn main() -> Result<(), db_type::Error> { 35 | /// let mut models = Models::new(); 36 | /// models.define::()?; 37 | /// let db = Builder::new().create_in_memory(&models)?; 38 | /// 39 | /// // Watch the primary key 40 | /// let (_recv, _id) = db.watch().get().primary::(1u64)?; 41 | /// Ok(()) 42 | /// } 43 | /// ``` 44 | pub fn primary( 45 | &self, 46 | key: impl ToKey, 47 | ) -> Result<(MpscReceiver, u64)> { 48 | let model = T::native_db_model(); 49 | check_key_type(&model, &key)?; 50 | self.internal.watch_primary::(key) 51 | } 52 | 53 | /// Watch the secondary key. 54 | /// 55 | /// Returns a channel receiver and the watcher id. 56 | /// The watcher id can be used to unwatch the channel. 57 | /// 58 | /// # Example 59 | /// ```rust 60 | /// use native_db::*; 61 | /// use native_db::native_model::{native_model, Model}; 62 | /// use serde::{Deserialize, Serialize}; 63 | /// 64 | /// #[derive(Serialize, Deserialize)] 65 | /// #[native_model(id=1, version=1)] 66 | /// #[native_db] 67 | /// struct Data { 68 | /// #[primary_key] 69 | /// id: u64, 70 | /// #[secondary_key] 71 | /// name: String, 72 | /// } 73 | /// 74 | /// fn main() -> Result<(), db_type::Error> { 75 | /// let mut models = Models::new(); 76 | /// models.define::()?; 77 | /// let db = Builder::new().create_in_memory(&models)?; 78 | /// 79 | /// // Watch the secondary key name 80 | /// let (_recv, _id) = db.watch().get().secondary::(DataKey::name, "test")?; 81 | /// Ok(()) 82 | /// } 83 | /// ``` 84 | pub fn secondary( 85 | &self, 86 | key_def: impl ToKeyDefinition, 87 | key: impl ToKey, 88 | ) -> Result<(MpscReceiver, u64)> { 89 | check_key_type_from_key_definition(&key_def.key_definition(), &key)?; 90 | self.internal.watch_secondary::(&key_def, key) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/watch/query/internal.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Error, KeyOptions, Result, ToInput, ToKey, ToKeyDefinition}; 2 | use crate::watch; 3 | use crate::watch::{MpscReceiver, TableFilter}; 4 | use std::sync::atomic::AtomicU64; 5 | use std::sync::{Arc, Mutex, RwLock}; 6 | 7 | pub(crate) struct InternalWatch<'db> { 8 | pub(crate) watchers: &'db Arc>, 9 | pub(crate) watchers_counter_id: &'db AtomicU64, 10 | } 11 | 12 | impl InternalWatch<'_> { 13 | fn watch_generic( 14 | &self, 15 | table_filter: watch::TableFilter, 16 | ) -> Result<(MpscReceiver, u64)> { 17 | #[cfg(not(feature = "tokio"))] 18 | let (event_sender, event_receiver) = std::sync::mpsc::channel(); 19 | #[cfg(feature = "tokio")] 20 | let (event_sender, event_receiver) = tokio::sync::mpsc::unbounded_channel(); 21 | let event_sender = Arc::new(Mutex::new(event_sender)); 22 | let id = self.generate_watcher_id()?; 23 | let mut watchers = self.watchers.write().unwrap(); 24 | watchers.add_sender(id, &table_filter, Arc::clone(&event_sender)); 25 | Ok((event_receiver, id)) 26 | } 27 | 28 | fn generate_watcher_id(&self) -> Result { 29 | let value = self 30 | .watchers_counter_id 31 | .fetch_add(1, std::sync::atomic::Ordering::SeqCst); 32 | if value == u64::MAX { 33 | Err(Error::MaxWatcherReached) 34 | } else { 35 | Ok(value) 36 | } 37 | } 38 | 39 | pub(crate) fn watch_primary( 40 | &self, 41 | key: impl ToKey, 42 | ) -> Result<(MpscReceiver, u64)> { 43 | let table_name = T::native_db_model().primary_key; 44 | let key = key.to_key(); 45 | let table_filter = 46 | TableFilter::new_primary(table_name.unique_table_name.clone(), Some(key)); 47 | self.watch_generic(table_filter) 48 | } 49 | 50 | pub(crate) fn watch_primary_all( 51 | &self, 52 | ) -> Result<(MpscReceiver, u64)> { 53 | let table_name = T::native_db_model().primary_key; 54 | let table_filter = TableFilter::new_primary(table_name.unique_table_name.clone(), None); 55 | self.watch_generic(table_filter) 56 | } 57 | 58 | pub(crate) fn watch_primary_start_with( 59 | &self, 60 | start_with: impl ToKey, 61 | ) -> Result<(MpscReceiver, u64)> { 62 | let table_name = T::native_db_model().primary_key; 63 | let start_with = start_with.to_key(); 64 | let table_filter = 65 | TableFilter::new_primary_start_with(table_name.unique_table_name.clone(), start_with); 66 | self.watch_generic(table_filter) 67 | } 68 | 69 | pub(crate) fn watch_secondary( 70 | &self, 71 | key_def: &impl ToKeyDefinition, 72 | key: impl ToKey, 73 | ) -> Result<(MpscReceiver, u64)> { 74 | let table_name = T::native_db_model().primary_key; 75 | let key = key.to_key(); 76 | let table_filter = 77 | TableFilter::new_secondary(table_name.unique_table_name.clone(), key_def, Some(key)); 78 | self.watch_generic(table_filter) 79 | } 80 | 81 | pub(crate) fn watch_secondary_all( 82 | &self, 83 | key_def: &impl ToKeyDefinition, 84 | ) -> Result<(MpscReceiver, u64)> { 85 | let table_name = T::native_db_model().primary_key; 86 | let table_filter = 87 | TableFilter::new_secondary(table_name.unique_table_name.clone(), key_def, None); 88 | self.watch_generic(table_filter) 89 | } 90 | 91 | pub(crate) fn watch_secondary_start_with( 92 | &self, 93 | key_def: &impl ToKeyDefinition, 94 | start_with: impl ToKey, 95 | ) -> Result<(MpscReceiver, u64)> { 96 | let table_name = T::native_db_model().primary_key; 97 | let start_with = start_with.to_key(); 98 | let table_filter = TableFilter::new_secondary_start_with( 99 | table_name.unique_table_name.clone(), 100 | key_def, 101 | start_with, 102 | ); 103 | self.watch_generic(table_filter) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/watch/query/mod.rs: -------------------------------------------------------------------------------- 1 | mod get; 2 | mod internal; 3 | mod scan; 4 | 5 | pub use get::*; 6 | pub(crate) use internal::*; 7 | pub use scan::*; 8 | 9 | /// Watch queries. 10 | pub struct Watch<'db> { 11 | pub(crate) internal: InternalWatch<'db>, 12 | } 13 | 14 | impl<'db> Watch<'db> { 15 | /// Watch only one value. 16 | /// 17 | /// - [`primary`](crate::watch::query::WatchGet::primary) - Watch a item by primary key. 18 | /// - [`secondary`](crate::watch::query::WatchGet::secondary) - Watch a item by secondary key. 19 | pub fn get<'w>(&'w self) -> WatchGet<'db, 'w> { 20 | WatchGet { 21 | internal: &self.internal, 22 | } 23 | } 24 | /// Watch multiple values. 25 | /// 26 | /// - [`primary`](crate::watch::query::WatchScan::primary) - Watch items by primary key. 27 | /// - [`secondary`](crate::watch::query::WatchScan::secondary) - Watch items by secondary key. 28 | pub fn scan<'w>(&'w self) -> WatchScan<'db, 'w> { 29 | WatchScan { 30 | internal: &self.internal, 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/watch/request.rs: -------------------------------------------------------------------------------- 1 | use crate::db_type::{Key, KeyDefinition, KeyEntry, KeyOptions}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Clone)] 5 | pub struct WatcherRequest { 6 | // TODO: Maybe replace table_name by KeyDefinition<()> or other 7 | pub(crate) table_name: String, 8 | pub(crate) primary_key: Key, 9 | pub(crate) secondary_keys_value: HashMap, KeyEntry>, 10 | } 11 | 12 | impl WatcherRequest { 13 | pub fn new( 14 | table_name: String, 15 | primary_key: Key, 16 | secondary_keys: HashMap, KeyEntry>, 17 | ) -> Self { 18 | Self { 19 | table_name, 20 | primary_key, 21 | secondary_keys_value: secondary_keys, 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tables.toml: -------------------------------------------------------------------------------- 1 | [top_comments] 2 | Overview = """ 3 | 4 | - :warning: This benchmark is an initial version and it can certainly be greatly improved to make the results as relevant as possible. Feel free to open issues to improve it. 5 | - :point_right: Native DB will be further improved in the future as performance issues have not yet been addressed. That is indeed the purpose of this benchmark, which is to provide visibility on what needs to be improved. 6 | 7 | Comparison between [`Native DB`](https://github.com/vincent-herlemont/native_db) vs [`Redb`](https://github.com/cberner/redb) vs [`SQLite`](https://www.sqlite.org/) 8 | 9 | - Why compare with `Redb`? 10 | - To highlight the `Native DB` overhead, because `Redb` is the backend of `Native DB`, it should "normally" always be faster than `Native DB`. 11 | - Why compare with `SQLite`? 12 | - Because even though `SQLite` offers a lot more options, `Native DB` can be seen as a very light alternative to `SQLite`. 13 | - And the other databases? 14 | - Knowing the capabilities of `Native DB` compared to `Redb` with the benchmark below, you can check the benchmark of redb here: [cberner/redb/benchmarks](https://github.com/cberner/redb?tab=readme-ov-file#benchmarks) 15 | 16 | The benchmarks ignore: 17 | - [`native_model`](https://github.com/vincent-herlemont/native_model) overhead. 18 | - Serialization overhead used by `native_model` like `bincode`,`postcard` etc. 19 | - The fact that `redb` can write the data using zero-copy. 20 | 21 | Explanation: 22 | - `1:SK`, `10:SK`, `50:SK`, `100:SK`, `N:SK` in this case `N` is the number of secondary keys (`SK`) for the same data. Regarding SQLite, it is the column with each having a secondary index. 23 | - `1:T`, `n:T` represent the number of operations per transaction. 24 | - `1:T` means one operation per transaction, for example, for insertion, it means there is only one insert operation per transaction. 25 | - `n:T` means `n` operations per transaction, `n` is defined by `criterion`, meaning that all operations are within a single transaction. 26 | - We can see that `Redb` sometimes has no comparisons (`N/A`) because `Redb` is a key-value database and does not support secondary indexes. Therefore, it is pointless to compare with more or fewer secondary indexes. 27 | """ 28 | 29 | [table_comments] 30 | delete = """ 31 | :warning: We can see that when all operations are in a single transaction (`n:T`), Native DB has a huge overhead. An issue has been created to resolve this problem [#256](https://github.com/vincent-herlemont/native_db/issues/256). 32 | """ -------------------------------------------------------------------------------- /tests/check_integrity.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn test_check_integrity() { 17 | let tf = TmpFs::new().unwrap(); 18 | let db_path = tf.path("test"); 19 | 20 | let mut models = Models::new(); 21 | models.define::().unwrap(); 22 | let mut db = Builder::new().create(&models, db_path.clone()).unwrap(); 23 | 24 | // Insert 1 item 25 | let rw = db.rw_transaction().unwrap(); 26 | rw.insert(Item { 27 | id: 1, 28 | name: "test".to_string(), 29 | }) 30 | .unwrap(); 31 | rw.commit().unwrap(); 32 | 33 | let out = db.check_integrity().unwrap(); 34 | assert!(out); 35 | } 36 | -------------------------------------------------------------------------------- /tests/check_type/mod.rs: -------------------------------------------------------------------------------- 1 | mod all; 2 | mod struct_custom; 3 | mod struct_simple; 4 | -------------------------------------------------------------------------------- /tests/check_type/struct_custom.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 6 | #[native_model(id = 1, version = 1)] 7 | #[native_db( 8 | primary_key(custom_id -> u32), 9 | // secondary_key(custom_sk -> Vec), 10 | // secondary_key(custom_sk_o -> Option>, optional), 11 | // secondary_key(custom_sk_u -> Vec, unique), 12 | // secondary_key(custom_sk_o_u -> Option>, unique, optional), 13 | secondary_key(custom_sk_no_u -> Option>, unique), 14 | )] 15 | struct ItemCustomPk { 16 | id: u32, 17 | all_sk: Vec, 18 | } 19 | 20 | impl ItemCustomPk { 21 | fn custom_id(&self) -> u32 { 22 | self.id 23 | } 24 | 25 | // fn custom_sk(&self) -> Vec { 26 | // self.all_sk.clone() 27 | // } 28 | 29 | // fn custom_sk_u(&self) -> Vec { 30 | // self.all_sk.clone() 31 | // } 32 | 33 | // fn custom_sk_o(&self) -> Option> { 34 | // Some(self.all_sk.clone()) 35 | // } 36 | 37 | // fn custom_sk_o_u(&self) -> Option> { 38 | // Some(self.all_sk.clone()) 39 | // } 40 | 41 | fn custom_sk_no_u(&self) -> Option> { 42 | Some(self.all_sk.clone()) 43 | } 44 | } 45 | 46 | #[test] 47 | fn test_get_primary_key_custom_pk() { 48 | let item = ItemCustomPk { 49 | id: 1, 50 | all_sk: vec!["1".to_string()], 51 | }; 52 | 53 | let mut models = Models::new(); 54 | models.define::().unwrap(); 55 | let db = Builder::new().create_in_memory(&models).unwrap(); 56 | 57 | let rw = db.rw_transaction().unwrap(); 58 | rw.insert(item.clone()).unwrap(); 59 | rw.commit().unwrap(); 60 | 61 | // // Get primary key for read transaction for unique 62 | // let r = db.r_transaction().unwrap(); 63 | // let id = vec!["1".to_string()]; 64 | // let result_item = r 65 | // .get() 66 | // .secondary(ItemCustomPkKey::custom_sk_u, id) 67 | // .unwrap() 68 | // .unwrap(); 69 | 70 | // assert_eq!(item, result_item); 71 | 72 | // // Get primary key for read transaction for unique optional 73 | // let r = db.r_transaction().unwrap(); 74 | // let id = vec!["1".to_string()]; 75 | // let result_item = r 76 | // .get() 77 | // .secondary(ItemCustomPkKey::custom_sk_o_u, id) 78 | // .unwrap() 79 | // .unwrap(); 80 | 81 | // assert_eq!(item, result_item); 82 | 83 | // Get primary key for read transaction for unique not optional 84 | let r = db.r_transaction().unwrap(); 85 | let id = Some(vec!["1".to_string()]); 86 | let result_item = r 87 | .get() 88 | .secondary(ItemCustomPkKey::custom_sk_no_u, id) 89 | .unwrap() 90 | .unwrap(); 91 | 92 | assert_eq!(item, result_item); 93 | } 94 | -------------------------------------------------------------------------------- /tests/compact.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn test_compact() { 17 | let tf = TmpFs::new().unwrap(); 18 | let db_path = tf.path("test"); 19 | 20 | let mut models = Models::new(); 21 | models.define::().unwrap(); 22 | let mut db = Builder::new().create(&models, db_path.clone()).unwrap(); 23 | 24 | // Insert 1000 items 25 | let rw = db.rw_transaction().unwrap(); 26 | for i in 0..999 { 27 | rw.insert(Item { 28 | id: i, 29 | name: format!("test_{}", i), 30 | }) 31 | .unwrap(); 32 | } 33 | rw.commit().unwrap(); 34 | 35 | // Check the size of the database 36 | let metadata = std::fs::metadata(db_path.clone()).unwrap(); 37 | let file_size = metadata.len(); 38 | assert_eq!(file_size, 1589248); 39 | dbg!(file_size); 40 | 41 | let out = db.compact().unwrap(); 42 | assert!(out); 43 | 44 | // Check the size of the compacted database 45 | let metadata = std::fs::metadata(db_path.clone()).unwrap(); 46 | let file_size = metadata.len(); 47 | assert_eq!(file_size, 876544); 48 | } 49 | -------------------------------------------------------------------------------- /tests/convert_all.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | type Item = ItemV1; 7 | 8 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 9 | #[native_model(id = 1, version = 1)] 10 | #[native_db] 11 | struct ItemV0 { 12 | #[primary_key] 13 | pub id: u32, 14 | } 15 | 16 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 17 | #[native_model(id = 2, version = 1)] 18 | #[native_db] 19 | struct ItemV1 { 20 | #[primary_key] 21 | pub id: String, 22 | } 23 | 24 | impl From for ItemV1 { 25 | fn from(item: ItemV0) -> Self { 26 | ItemV1 { 27 | id: item.id.to_string(), 28 | } 29 | } 30 | } 31 | 32 | #[test] 33 | fn convert_all() { 34 | let tf = TmpFs::new().unwrap(); 35 | 36 | let mut models = Models::new(); 37 | models.define::().unwrap(); 38 | models.define::().unwrap(); 39 | let db = Builder::new() 40 | .create(&models, tf.path("test").as_std_path()) 41 | .unwrap(); 42 | 43 | let a = ItemV0 { id: 42 }; 44 | 45 | let rw_txn = db.rw_transaction().unwrap(); 46 | rw_txn.insert(a.clone()).unwrap(); 47 | rw_txn.commit().unwrap(); 48 | 49 | // Check if a is saved 50 | let txn = db.rw_transaction().unwrap(); 51 | let a1 = txn.get().primary(a.id).unwrap().unwrap(); 52 | assert_eq!(a, a1); 53 | txn.commit().unwrap(); 54 | 55 | #[allow(unused_mut)] 56 | #[cfg(not(feature = "tokio"))] 57 | let (mut recv_av1, _id) = db.watch().scan().primary().all::().unwrap(); 58 | #[allow(unused_mut)] 59 | #[cfg(not(feature = "tokio"))] 60 | let (mut recv_av2, _id) = db.watch().scan().primary().all::().unwrap(); 61 | 62 | #[cfg(feature = "tokio")] 63 | let (mut recv_av1, _id) = db.watch().scan().primary().all::().unwrap(); 64 | #[cfg(feature = "tokio")] 65 | let (mut recv_av2, _id) = db.watch().scan().primary().all::().unwrap(); 66 | 67 | // Migrate 68 | let rw_txn = db.rw_transaction().unwrap(); 69 | rw_txn.convert_all::().unwrap(); 70 | rw_txn.commit().unwrap(); 71 | 72 | // Check is there is no event from AV1 73 | assert!(recv_av1.try_recv().is_err()); 74 | // Check is there is no event from AV2 75 | assert!(recv_av2.try_recv().is_err()); 76 | 77 | // Check migration 78 | let r_txn = db.r_transaction().unwrap(); 79 | let len_av1 = r_txn.len().primary::().unwrap(); 80 | assert_eq!(len_av1, 0); 81 | let len_av2 = r_txn.len().primary::().unwrap(); 82 | assert_eq!(len_av2, 1); 83 | 84 | let a2: Item = r_txn.get().primary("42").unwrap().unwrap(); 85 | assert_eq!( 86 | a2, 87 | Item { 88 | id: "42".to_string() 89 | } 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /tests/custom_type/custom.rs: -------------------------------------------------------------------------------- 1 | use native_db::{ 2 | db_type::{Key, ToKey}, 3 | native_db, Builder, Models, 4 | }; 5 | use native_model::{native_model, Model}; 6 | use serde::{Deserialize, Serialize}; 7 | use shortcut_assert_fs::TmpFs; 8 | 9 | #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] 10 | struct City(String); 11 | 12 | impl ToKey for City { 13 | fn to_key(&self) -> Key { 14 | Key::new(self.0.as_bytes().to_vec()) 15 | } 16 | 17 | fn key_names() -> Vec { 18 | vec!["City".to_string()] 19 | } 20 | } 21 | 22 | // Test genrate fields: 23 | // - primary_key 24 | // - secondary_keys (unique) 25 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 26 | #[native_model(id = 1, version = 1)] 27 | #[native_db] 28 | struct ItemFields { 29 | #[primary_key] 30 | city1: City, 31 | #[secondary_key(unique)] 32 | city2: City, 33 | #[secondary_key(optional)] 34 | city3: Option, 35 | } 36 | 37 | #[test] 38 | fn insert_item_fields() { 39 | let item = ItemFields { 40 | city1: City("New York".to_string()), 41 | city2: City("New York".to_string()), 42 | city3: Some(City("New York".to_string())), 43 | }; 44 | 45 | let tf = TmpFs::new().unwrap(); 46 | let mut models = Models::new(); 47 | models.define::().unwrap(); 48 | let db = Builder::new() 49 | .create(&models, tf.path("test").as_std_path()) 50 | .unwrap(); 51 | 52 | let rw = db.rw_transaction().unwrap(); 53 | rw.insert(item.clone()).unwrap(); 54 | rw.commit().unwrap(); 55 | 56 | let r = db.r_transaction().unwrap(); 57 | let result_item = r 58 | .get() 59 | .secondary(ItemFieldsKey::city2, item.city2.clone()) 60 | .unwrap() 61 | .unwrap(); 62 | assert_eq!(item, result_item); 63 | } 64 | 65 | // Test genrate functions: 66 | // - primary_key 67 | // - secondary_keys (unique) 68 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 69 | #[native_model(id = 1, version = 1)] 70 | #[native_db( 71 | primary_key(m_city1 -> City), 72 | secondary_key(m_city2 -> City, unique), 73 | secondary_key(m_city2_ref -> City, unique), 74 | secondary_key(m_city3 -> Option, optional), 75 | )] 76 | struct ItemFunctions { 77 | city1: City, 78 | city2: City, 79 | city3: Option, 80 | } 81 | 82 | impl ItemFunctions { 83 | fn m_city1(&self) -> City { 84 | self.city1.clone() 85 | } 86 | 87 | fn m_city2(&self) -> City { 88 | self.city2.clone() 89 | } 90 | 91 | fn m_city2_ref(&self) -> &City { 92 | &self.city2 93 | } 94 | 95 | fn m_city3(&self) -> Option { 96 | self.city3.clone() 97 | } 98 | } 99 | 100 | #[test] 101 | fn test_item_functions() { 102 | let item = ItemFunctions { 103 | city1: City("New York".to_string()), 104 | city2: City("New York".to_string()), 105 | city3: Some(City("New York".to_string())), 106 | }; 107 | 108 | let tf = TmpFs::new().unwrap(); 109 | let mut models = Models::new(); 110 | models.define::().unwrap(); 111 | let db = Builder::new() 112 | .create(&models, tf.path("test").as_std_path()) 113 | .unwrap(); 114 | 115 | let rw = db.rw_transaction().unwrap(); 116 | rw.insert(item.clone()).unwrap(); 117 | rw.commit().unwrap(); 118 | 119 | let r = db.r_transaction().unwrap(); 120 | let result_item = r 121 | .get() 122 | .secondary(ItemFunctionsKey::m_city2, item.city2.clone()) 123 | .unwrap() 124 | .unwrap(); 125 | assert_eq!(item, result_item); 126 | } 127 | -------------------------------------------------------------------------------- /tests/custom_type/mod.rs: -------------------------------------------------------------------------------- 1 | mod custom; 2 | -------------------------------------------------------------------------------- /tests/data/db_0_5_x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/tests/data/db_0_5_x -------------------------------------------------------------------------------- /tests/data/db_0_6_0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/tests/data/db_0_6_0 -------------------------------------------------------------------------------- /tests/data/db_0_7_1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/tests/data/db_0_7_1 -------------------------------------------------------------------------------- /tests/data/db_0_8-pre-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincent-herlemont/native_db/b51149ae5bb75c0e444f130edf644cb3e4e11fd7/tests/data/db_0_8-pre-0 -------------------------------------------------------------------------------- /tests/deserialization_error.rs: -------------------------------------------------------------------------------- 1 | use native_db::db_type::Error; 2 | use native_db::db_type::Result; 3 | use native_db::*; 4 | 5 | use itertools::Itertools; 6 | use native_model::{native_model, Error as ModelError, Model}; 7 | use serde::{Deserialize, Serialize}; 8 | use shortcut_assert_fs::TmpFs; 9 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 10 | #[native_model(id = 1, version = 1)] 11 | #[native_db] 12 | struct Item1 { 13 | // The type of the primary key must be u32, see generation of the test "create_local_database_for_tests". 14 | // We change the type of the primary key to String to generate a deserialization error. 15 | #[primary_key] 16 | id: String, 17 | #[secondary_key(unique)] 18 | name: String, 19 | } 20 | 21 | use include_dir::{include_dir, Dir}; 22 | static PROJECT_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/data"); 23 | 24 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 25 | #[test] 26 | fn create_local_database_for_tests() { 27 | let tmp = TmpFs::new().unwrap(); 28 | tmp.copy_assets(&PROJECT_DIR).unwrap(); 29 | tmp.display_dir_entries(); 30 | let database_path = tmp.path("db_0_8-pre-0").to_path_buf(); 31 | 32 | let mut models = Models::new(); 33 | models.define::().unwrap(); 34 | let db = Builder::new().open(&models, &database_path).unwrap(); 35 | let r = db.r_transaction().unwrap(); 36 | let result: Result> = r.scan().primary().unwrap().all().unwrap().try_collect(); 37 | assert!(matches!( 38 | result, 39 | Err(Error::ModelError(boxed_error)) 40 | if matches!(*boxed_error, ModelError::DecodeBodyError(_)) 41 | )); 42 | } 43 | -------------------------------------------------------------------------------- /tests/macro_def/export_keys_attribute.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use native_db::*; 3 | use native_model::{native_model, Model}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | // TODO somehow test visibility of keys enum from a sibling/child crate? 7 | 8 | /// Test struct to ensure `#[native_db(export_keys = true)]` compiles successfully 9 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 10 | #[native_model(id = 1, version = 1)] 11 | #[native_db(export_keys = true)] 12 | struct Item { 13 | #[primary_key] 14 | id: u32, 15 | #[secondary_key] 16 | name: String, 17 | } 18 | 19 | #[test] 20 | fn test_insert_my_item() { 21 | let item = Item { 22 | id: 1, 23 | name: "test".to_string(), 24 | }; 25 | 26 | let key = item.native_db_primary_key(); 27 | assert_eq!(key, 1_u32.to_key()); 28 | 29 | let mut models: Models = Models::new(); 30 | models.define::().unwrap(); 31 | 32 | let db = Builder::new().create_in_memory(&models).unwrap(); 33 | 34 | let rw = db.rw_transaction().unwrap(); 35 | rw.insert(Item { 36 | id: 1, 37 | name: "test".to_string(), 38 | }) 39 | .unwrap(); 40 | rw.commit().unwrap(); 41 | 42 | // Get primary key 43 | let r = db.r_transaction().unwrap(); 44 | let result_item: Item = r.get().primary(1u32).unwrap().unwrap(); 45 | assert_eq!(result_item.id, 1); 46 | assert_eq!(result_item.name, "test"); 47 | 48 | // Get secondary key 49 | let r = db.r_transaction().unwrap(); 50 | let result_item: Vec = r 51 | .scan() 52 | .secondary(ItemKey::name) 53 | .unwrap() 54 | .all() 55 | .unwrap() 56 | .try_collect() 57 | .unwrap(); 58 | assert_eq!(result_item.len(), 1); 59 | assert_eq!(result_item[0].id, 1); 60 | assert_eq!(result_item[0].name, "test"); 61 | } 62 | -------------------------------------------------------------------------------- /tests/macro_def/mod.rs: -------------------------------------------------------------------------------- 1 | mod export_keys_attribute; 2 | mod primary_key; 3 | mod primary_key_attribute; 4 | mod secondary_key; 5 | mod secondary_key_attribute; 6 | mod secondary_key_mix; 7 | -------------------------------------------------------------------------------- /tests/macro_def/primary_key.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 6 | #[native_model(id = 1, version = 1)] 7 | #[native_db(primary_key(compute_primary_key -> String))] 8 | struct Item { 9 | id: u32, 10 | name: String, 11 | } 12 | 13 | impl Item { 14 | pub fn compute_primary_key(&self) -> String { 15 | format!("{}-{}", self.id, self.name) 16 | } 17 | } 18 | 19 | #[test] 20 | fn test_insert_my_item() { 21 | let item = Item { 22 | id: 1, 23 | name: "test".to_string(), 24 | }; 25 | 26 | let key = item.native_db_primary_key(); 27 | assert_eq!(key, "1-test".to_key()); 28 | } 29 | -------------------------------------------------------------------------------- /tests/macro_def/primary_key_attribute.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 6 | #[native_model(id = 1, version = 1)] 7 | #[native_db] 8 | struct Item { 9 | #[primary_key] 10 | id: u32, 11 | name: String, 12 | } 13 | // TODO: Test for other type enum tuple etc ... 14 | 15 | #[test] 16 | fn test_insert_my_item() { 17 | let item = Item { 18 | id: 1, 19 | name: "test".to_string(), 20 | }; 21 | 22 | let key = item.native_db_primary_key(); 23 | assert_eq!(key, 1_u32.to_key()); 24 | } 25 | -------------------------------------------------------------------------------- /tests/macro_def/secondary_key.rs: -------------------------------------------------------------------------------- 1 | use native_db::db_type::{KeyDefinition, KeyEntry, ToInput}; 2 | use native_db::*; 3 | use native_model::{native_model, Model}; 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 8 | #[native_model(id = 1, version = 1)] 9 | #[native_db( 10 | primary_key(compute_primary_key -> String), 11 | secondary_key(compute_secondary_key -> String), 12 | )] 13 | struct ItemSecondary { 14 | id: u32, 15 | name: String, 16 | } 17 | 18 | impl ItemSecondary { 19 | pub fn compute_primary_key(&self) -> String { 20 | format!("{}-{}", self.id, self.name) 21 | } 22 | pub fn compute_secondary_key(&self) -> String { 23 | format!("{}-{}", self.name, self.id) 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_secondary() { 29 | let item = ItemSecondary { 30 | id: 1, 31 | name: "test".to_string(), 32 | }; 33 | 34 | let primary_key = item.native_db_primary_key(); 35 | assert_eq!(primary_key, "1-test".to_key()); 36 | 37 | let secondary_key: HashMap<_, KeyEntry> = item.native_db_secondary_keys(); 38 | assert_eq!(secondary_key.len(), 1); 39 | assert_eq!( 40 | secondary_key 41 | .get(&KeyDefinition::new( 42 | 1, 43 | 1, 44 | "compute_secondary_key", 45 | vec!["String".to_string()], 46 | Default::default() 47 | )) 48 | .unwrap(), 49 | &KeyEntry::Default("test-1".to_key()) 50 | ); 51 | } 52 | 53 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 54 | #[native_model(id = 2, version = 1)] 55 | #[native_db( 56 | primary_key(compute_primary_key -> String), 57 | secondary_key(compute_secondary_key -> String, unique) 58 | )] 59 | struct ItemSecondaryUnique { 60 | id: u32, 61 | name: String, 62 | } 63 | 64 | impl ItemSecondaryUnique { 65 | pub fn compute_primary_key(&self) -> String { 66 | format!("{}-{}", self.id, self.name) 67 | } 68 | pub fn compute_secondary_key(&self) -> String { 69 | format!("{}-{}", self.name, self.id) 70 | } 71 | } 72 | 73 | #[test] 74 | fn test_secondary_unique() { 75 | let item = ItemSecondaryUnique { 76 | id: 1, 77 | name: "test".to_string(), 78 | }; 79 | 80 | let primary_key = item.native_db_primary_key(); 81 | assert_eq!(primary_key, "1-test".to_key()); 82 | 83 | let secondary_key: HashMap<_, KeyEntry> = item.native_db_secondary_keys(); 84 | assert_eq!(secondary_key.len(), 1); 85 | assert_eq!( 86 | secondary_key 87 | .get(&KeyDefinition::new( 88 | 2, 89 | 1, 90 | "compute_secondary_key", 91 | vec!["String".to_string()], 92 | Default::default() 93 | )) 94 | .unwrap(), 95 | &KeyEntry::Default("test-1".to_key()) 96 | ); 97 | } 98 | 99 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 100 | #[native_model(id = 2, version = 1)] 101 | #[native_db( 102 | primary_key(compute_primary_key -> String), 103 | secondary_key(compute_secondary_key -> Option, optional) 104 | )] 105 | struct ItemSecondaryOptional { 106 | id: u32, 107 | name: Option, 108 | } 109 | 110 | impl ItemSecondaryOptional { 111 | pub fn compute_primary_key(&self) -> String { 112 | format!("{}", self.id) 113 | } 114 | pub fn compute_secondary_key(&self) -> Option { 115 | self.name 116 | .as_ref() 117 | .map(|name| format!("{}-{}", name, self.id)) 118 | } 119 | } 120 | 121 | #[test] 122 | fn test_secondary_optional() { 123 | let item = ItemSecondaryOptional { 124 | id: 1, 125 | name: Some("test".to_string()), 126 | }; 127 | 128 | let primary_key = item.native_db_primary_key(); 129 | assert_eq!(primary_key, "1".to_key()); 130 | 131 | let secondary_key: HashMap<_, KeyEntry> = item.native_db_secondary_keys(); 132 | assert_eq!(secondary_key.len(), 1); 133 | assert_eq!( 134 | secondary_key 135 | .get(&KeyDefinition::new( 136 | 2, 137 | 1, 138 | "compute_secondary_key", 139 | vec!["Option".to_string()], 140 | Default::default() 141 | )) 142 | .unwrap(), 143 | &KeyEntry::Optional(Some("test-1".to_key())) 144 | ); 145 | 146 | let item_none = ItemSecondaryOptional { id: 2, name: None }; 147 | let secondary_key: HashMap<_, KeyEntry> = item_none.native_db_secondary_keys(); 148 | assert_eq!(secondary_key.len(), 1); 149 | assert_eq!( 150 | secondary_key 151 | .get(&KeyDefinition::new( 152 | 2, 153 | 1, 154 | "compute_secondary_key", 155 | vec!["Option".to_string()], 156 | Default::default() 157 | )) 158 | .unwrap(), 159 | &KeyEntry::Optional(None) 160 | ); 161 | } 162 | -------------------------------------------------------------------------------- /tests/macro_def/secondary_key_mix.rs: -------------------------------------------------------------------------------- 1 | use native_db::db_type::ToInput; 2 | use native_db::db_type::{KeyDefinition, KeyEntry}; 3 | use native_db::*; 4 | use native_model::{native_model, Model}; 5 | use serde::{Deserialize, Serialize}; 6 | use std::collections::HashMap; 7 | use std::vec; 8 | 9 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 10 | #[native_model(id = 1, version = 1)] 11 | #[native_db( 12 | primary_key(compute_primary_key -> String), 13 | secondary_key(compute_secondary_key -> String), 14 | )] 15 | struct ItemSecondaryMix { 16 | id: u32, 17 | #[secondary_key(unique)] 18 | name: String, 19 | } 20 | 21 | impl ItemSecondaryMix { 22 | pub fn compute_primary_key(&self) -> String { 23 | format!("{}-{}", self.id, self.name) 24 | } 25 | pub fn compute_secondary_key(&self) -> String { 26 | format!("{}-{}", self.name, self.id) 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_secondary() { 32 | let item = ItemSecondaryMix { 33 | id: 1, 34 | name: "test".to_string(), 35 | }; 36 | 37 | let primary_key = item.native_db_primary_key(); 38 | assert_eq!(primary_key, "1-test".to_key()); 39 | 40 | let secondary_key: HashMap<_, KeyEntry> = item.native_db_secondary_keys(); 41 | assert_eq!(secondary_key.len(), 2); 42 | assert_eq!( 43 | secondary_key.get(&KeyDefinition::new( 44 | 1, 45 | 1, 46 | "compute_secondary_key", 47 | vec!["String".to_string()], 48 | Default::default() 49 | )), 50 | Some(&KeyEntry::Default("test-1".to_key())) 51 | ); 52 | 53 | assert_eq!( 54 | secondary_key 55 | .get(&KeyDefinition::new( 56 | 1, 57 | 1, 58 | "name", 59 | vec!["String".to_string()], 60 | Default::default() 61 | )) 62 | .unwrap(), 63 | &KeyEntry::Default("test".to_key()) 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /tests/metadata/current_version.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 6 | #[native_model(id = 1, version = 1)] 7 | #[native_db] 8 | struct Item1 { 9 | #[primary_key] 10 | id: u32, 11 | #[secondary_key(unique)] 12 | name: String, 13 | } 14 | 15 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 16 | #[native_model(id = 2, version = 1)] 17 | #[native_db] 18 | struct Item2 { 19 | #[primary_key] 20 | id: u32, 21 | #[secondary_key(optional)] 22 | id2: Option, 23 | #[secondary_key] 24 | name: String, 25 | } 26 | 27 | #[test] 28 | #[cfg(feature = "upgrade_0_7_x")] 29 | fn test_current_version() { 30 | use std::path::PathBuf; 31 | #[cfg(any(target_os = "android", target_os = "ios"))] 32 | let database_path = { dinghy_test::test_project_path().join("tests/data/db_0_7_1") }; 33 | 34 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 35 | let database_path = { 36 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 37 | PathBuf::from(format!("{}/tests/data/db_0_7_1", root_project_path)) 38 | }; 39 | 40 | use shortcut_assert_fs::TmpFs; 41 | let tmp = TmpFs::new().unwrap(); 42 | 43 | // Copy the legacy database to the temporary directory. 44 | let tmp_database_path = tmp.path("db_0_7_1"); 45 | std::fs::copy(&database_path, &tmp_database_path).unwrap(); 46 | 47 | // Open the legacy database with the upgrade feature. This must succeed. 48 | let mut models = Models::new(); 49 | models.define::().unwrap(); 50 | models.define::().unwrap(); 51 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 52 | 53 | let metadata = db.metadata(); 54 | assert_eq!(metadata.current_version(), env!("CARGO_PKG_VERSION")); 55 | assert_eq!(metadata.current_native_model_version(), "0.4.19"); 56 | 57 | // During open, the database add the metadata table 58 | assert_eq!(metadata.previous_version(), None); 59 | assert_eq!(metadata.previous_native_model_version(), None); 60 | 61 | assert!(db.upgrading_from_version("<0.8.0").unwrap()); 62 | 63 | drop(db); 64 | 65 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 66 | 67 | let metadata = db.metadata(); 68 | assert_eq!(metadata.current_version(), env!("CARGO_PKG_VERSION")); 69 | assert_eq!(metadata.current_native_model_version(), "0.4.19"); 70 | 71 | // During open, the database add the metadata table 72 | assert_eq!(metadata.previous_version(), Some(env!("CARGO_PKG_VERSION"))); 73 | assert_eq!(metadata.previous_native_model_version(), Some("0.4.19")); 74 | 75 | assert!(!db.upgrading_from_version("<0.8.0").unwrap()); 76 | } 77 | 78 | // TODO: add test for version <=0.8.0 79 | -------------------------------------------------------------------------------- /tests/metadata/mod.rs: -------------------------------------------------------------------------------- 1 | mod current_version; 2 | -------------------------------------------------------------------------------- /tests/migrate/mod.rs: -------------------------------------------------------------------------------- 1 | mod only_primary_key; 2 | mod with_multiple_versions; 3 | mod with_other_model; 4 | mod with_secondary_keys; 5 | -------------------------------------------------------------------------------- /tests/migrate/only_primary_key.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db( 9 | primary_key(generate_my_primary_key -> String), 10 | )] 11 | struct ItemV1 { 12 | id: u32, 13 | name: String, 14 | } 15 | 16 | impl ItemV1 { 17 | #[allow(dead_code)] 18 | pub fn generate_my_primary_key(&self) -> String { 19 | format!("{}-{}", self.id, self.name) 20 | } 21 | } 22 | 23 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 24 | #[native_model(id = 1, version = 2, from = ItemV1)] 25 | #[native_db( 26 | primary_key(generate_my_primary_key -> String), 27 | )] 28 | struct ItemV2 { 29 | id: u64, 30 | name: String, 31 | } 32 | 33 | impl From for ItemV2 { 34 | fn from(item: ItemV1) -> Self { 35 | ItemV2 { 36 | id: item.id as u64, 37 | name: item.name, 38 | } 39 | } 40 | } 41 | 42 | impl From for ItemV1 { 43 | fn from(item: ItemV2) -> Self { 44 | ItemV1 { 45 | id: item.id as u32, 46 | name: item.name, 47 | } 48 | } 49 | } 50 | 51 | impl ItemV2 { 52 | #[allow(dead_code)] 53 | pub fn generate_my_primary_key(&self) -> String { 54 | format!("{}-{}", self.id, self.name) 55 | } 56 | } 57 | 58 | #[test] 59 | fn test_migrate() { 60 | let tf = TmpFs::new().unwrap(); 61 | let mut models = Models::new(); 62 | models.define::().unwrap(); 63 | let db = Builder::new() 64 | .create(&models, tf.path("test").as_std_path()) 65 | .unwrap(); 66 | 67 | let item = ItemV1 { 68 | id: 1, 69 | name: "test".to_string(), 70 | }; 71 | 72 | let rw_txn = db.rw_transaction().unwrap(); 73 | rw_txn.insert(item).unwrap(); 74 | rw_txn.commit().unwrap(); 75 | 76 | let r_txn = db.r_transaction().unwrap(); 77 | 78 | let item: ItemV1 = r_txn.get().primary("1-test").unwrap().unwrap(); 79 | assert_eq!( 80 | item, 81 | ItemV1 { 82 | id: 1, 83 | name: "test".to_string(), 84 | } 85 | ); 86 | drop(r_txn); 87 | drop(db); 88 | 89 | let mut models = Models::new(); 90 | models.define::().unwrap(); 91 | models.define::().unwrap(); 92 | let db = Builder::new() 93 | .create(&models, tf.path("test").as_std_path()) 94 | .unwrap(); 95 | 96 | let rw = db.rw_transaction().unwrap(); 97 | rw.migrate::().unwrap(); 98 | rw.commit().unwrap(); 99 | 100 | let r_txn = db.r_transaction().unwrap(); 101 | let item: ItemV2 = r_txn.get().primary("1-test").unwrap().unwrap(); 102 | assert_eq!( 103 | item, 104 | ItemV2 { 105 | id: 1, 106 | name: "test".to_string(), 107 | } 108 | ); 109 | 110 | let stats = db.redb_stats().unwrap(); 111 | assert_eq!(stats.primary_tables.len(), 2); 112 | assert_eq!(stats.primary_tables[0].name, "1_1_generate_my_primary_key"); 113 | assert_eq!(stats.primary_tables[0].n_entries, Some(0)); 114 | assert_eq!(stats.primary_tables[1].name, "1_2_generate_my_primary_key"); 115 | assert_eq!(stats.primary_tables[1].n_entries, Some(1)); 116 | assert_eq!(stats.secondary_tables.len(), 0); 117 | } 118 | -------------------------------------------------------------------------------- /tests/migrate/with_other_model.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct ItemV1 { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 16 | #[native_model(id = 1, version = 2, from = ItemV1)] 17 | #[native_db] 18 | struct ItemV2 { 19 | #[primary_key] 20 | id: u32, 21 | name_v2: String, 22 | } 23 | 24 | impl From for ItemV2 { 25 | fn from(item: ItemV1) -> Self { 26 | ItemV2 { 27 | id: item.id, 28 | name_v2: item.name, 29 | } 30 | } 31 | } 32 | 33 | impl From for ItemV1 { 34 | fn from(item: ItemV2) -> Self { 35 | ItemV1 { 36 | id: item.id, 37 | name: item.name_v2, 38 | } 39 | } 40 | } 41 | 42 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug)] 43 | #[native_model(id = 2, version = 1)] 44 | #[native_db] 45 | struct Item2 { 46 | #[primary_key] 47 | id: u32, 48 | name2: String, 49 | } 50 | 51 | #[test] 52 | fn test_migrate() { 53 | let tf = TmpFs::new().unwrap(); 54 | let mut models = Models::new(); 55 | models.define::().unwrap(); 56 | models.define::().unwrap(); 57 | let db = Builder::new() 58 | .create(&models, tf.path("test").as_std_path()) 59 | .unwrap(); 60 | 61 | let item_2 = Item2 { 62 | id: 1, 63 | name2: "test2".to_string(), 64 | }; 65 | let rw_txn = db.rw_transaction().unwrap(); 66 | rw_txn.insert(item_2).unwrap(); 67 | rw_txn.commit().unwrap(); 68 | 69 | let item = ItemV1 { 70 | id: 1, 71 | name: "test".to_string(), 72 | }; 73 | 74 | let rw_txn = db.rw_transaction().unwrap(); 75 | rw_txn.insert(item).unwrap(); 76 | rw_txn.commit().unwrap(); 77 | 78 | let r_txn = db.r_transaction().unwrap(); 79 | 80 | let item: ItemV1 = r_txn.get().primary(1u32).unwrap().unwrap(); 81 | assert_eq!( 82 | item, 83 | ItemV1 { 84 | id: 1, 85 | name: "test".to_string(), 86 | } 87 | ); 88 | drop(r_txn); 89 | drop(db); 90 | 91 | let mut models = Models::new(); 92 | models.define::().unwrap(); 93 | models.define::().unwrap(); 94 | models.define::().unwrap(); 95 | let db = Builder::new() 96 | .create(&models, tf.path("test").as_std_path()) 97 | .unwrap(); 98 | 99 | let rw = db.rw_transaction().unwrap(); 100 | rw.migrate::().unwrap(); 101 | rw.commit().unwrap(); 102 | 103 | let r_txn = db.r_transaction().unwrap(); 104 | let item: ItemV2 = r_txn.get().primary(1u32).unwrap().unwrap(); 105 | assert_eq!( 106 | item, 107 | ItemV2 { 108 | id: 1, 109 | name_v2: "test".to_string(), 110 | } 111 | ); 112 | 113 | let item: Item2 = r_txn.get().primary(1u32).unwrap().unwrap(); 114 | assert_eq!( 115 | item, 116 | Item2 { 117 | id: 1, 118 | name2: "test2".to_string(), 119 | } 120 | ); 121 | 122 | let stats = db.redb_stats().unwrap(); 123 | assert_eq!(stats.primary_tables.len(), 3); 124 | assert_eq!(stats.primary_tables[0].name, "1_1_id"); 125 | assert_eq!(stats.primary_tables[0].n_entries, Some(0)); 126 | assert_eq!(stats.primary_tables[1].name, "1_2_id"); 127 | assert_eq!(stats.primary_tables[1].n_entries, Some(1)); 128 | assert_eq!(stats.primary_tables[2].name, "2_1_id"); 129 | assert_eq!(stats.primary_tables[2].n_entries, Some(1)); 130 | assert_eq!(stats.secondary_tables.len(), 0); 131 | } 132 | -------------------------------------------------------------------------------- /tests/modules.rs: -------------------------------------------------------------------------------- 1 | mod check_type; 2 | mod custom_type; 3 | mod macro_def; 4 | 5 | mod metadata; 6 | mod migrate; 7 | mod primary_drain; 8 | mod query; 9 | mod upgrade; 10 | mod watch; 11 | -------------------------------------------------------------------------------- /tests/native_model.rs: -------------------------------------------------------------------------------- 1 | use bincode::{config, Decode, Encode}; 2 | use native_db::*; 3 | use native_db_macro::native_db; 4 | use native_model::{native_model, Model}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | pub struct Bincode; 8 | 9 | impl native_model::Encode for Bincode { 10 | type Error = bincode::error::EncodeError; 11 | fn encode(obj: &T) -> std::result::Result, bincode::error::EncodeError> { 12 | bincode::encode_to_vec(obj, config::standard()) 13 | } 14 | } 15 | 16 | impl> native_model::Decode for Bincode { 17 | type Error = bincode::error::DecodeError; 18 | fn decode(data: Vec) -> std::result::Result { 19 | bincode::decode_from_slice(&data, config::standard()).map(|(result, _)| result) 20 | } 21 | } 22 | 23 | #[derive(Serialize, Deserialize, Encode, Decode, Eq, PartialEq, Debug)] 24 | #[native_model(id = 1, version = 1, with = Bincode)] 25 | #[native_db(primary_key(compute_primary_key -> Vec))] 26 | struct ItemV1 { 27 | id: u32, 28 | name: String, 29 | } 30 | 31 | impl ItemV1 { 32 | #[allow(dead_code)] 33 | pub fn compute_primary_key(&self) -> Vec { 34 | format!("{}-{}", self.id, self.name).into() 35 | } 36 | } 37 | 38 | #[test] 39 | fn test_native_encode() { 40 | let my_item = ItemV1 { 41 | id: 1, 42 | name: "test".to_string(), 43 | }; 44 | 45 | let my_item_packed = native_model::encode(&my_item).unwrap(); 46 | let (my_item_unpacked, _) = native_model::decode::(my_item_packed).unwrap(); 47 | assert_eq!(my_item, my_item_unpacked); 48 | } 49 | -------------------------------------------------------------------------------- /tests/primary_drain/mod.rs: -------------------------------------------------------------------------------- 1 | mod only_primary_key; 2 | mod with_secondary_keys; 3 | -------------------------------------------------------------------------------- /tests/primary_drain/only_primary_key.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db( 9 | primary_key(generate_my_primary_key -> Vec) 10 | )] 11 | struct Item { 12 | id: u32, 13 | name: String, 14 | } 15 | 16 | impl Item { 17 | pub fn generate_my_primary_key(&self) -> Vec { 18 | self.id.to_le_bytes().to_vec() 19 | } 20 | 21 | pub fn inc(&mut self) -> &Self { 22 | self.id += 1; 23 | self 24 | } 25 | } 26 | 27 | #[test] 28 | fn drain_all() { 29 | let tf = TmpFs::new().unwrap(); 30 | 31 | let mut item = Item { 32 | id: 1, 33 | name: "test".to_string(), 34 | }; 35 | 36 | let mut models = Models::new(); 37 | models.define::().unwrap(); 38 | let db = Builder::new() 39 | .create(&models, tf.path("test").as_std_path()) 40 | .unwrap(); 41 | 42 | // Insert 5 items 43 | let rw = db.rw_transaction().unwrap(); 44 | rw.insert(item.clone()).unwrap(); 45 | rw.insert(item.inc().clone()).unwrap(); 46 | rw.insert(item.inc().clone()).unwrap(); 47 | rw.insert(item.inc().clone()).unwrap(); 48 | rw.insert(item.inc().clone()).unwrap(); 49 | rw.commit().unwrap(); 50 | 51 | // Count items 52 | let r = db.r_transaction().unwrap(); 53 | let len = r.len().primary::().unwrap(); 54 | assert_eq!(len, 5); 55 | 56 | // Drain items 57 | let rw = db.rw_transaction().unwrap(); 58 | let items = rw.drain().primary::().unwrap(); 59 | assert_eq!(items.len(), 5); 60 | rw.commit().unwrap(); 61 | 62 | // Count items 63 | let r = db.r_transaction().unwrap(); 64 | let len = r.len().primary::().unwrap(); 65 | assert_eq!(len, 0); 66 | } 67 | -------------------------------------------------------------------------------- /tests/primary_drain/with_secondary_keys.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db( 9 | primary_key(generate_my_primary_key -> u32), 10 | secondary_key(generate_my_secondary_key -> String, unique) 11 | )] 12 | struct Item { 13 | id: u32, 14 | name: String, 15 | tag: String, 16 | } 17 | 18 | impl Item { 19 | pub fn generate_my_primary_key(&self) -> u32 { 20 | self.id 21 | } 22 | 23 | pub fn generate_my_secondary_key(&self) -> String { 24 | let mut tag = self.tag.clone(); 25 | let primary_key = self.generate_my_primary_key().to_string(); 26 | tag.push_str(&primary_key); 27 | tag 28 | } 29 | 30 | pub fn inc(&mut self) -> &Self { 31 | self.id += 1; 32 | self 33 | } 34 | } 35 | 36 | #[test] 37 | fn drain_all() { 38 | let tf = TmpFs::new().unwrap(); 39 | 40 | let mut item = Item { 41 | id: 1, 42 | name: "test".to_string(), 43 | tag: "red".to_string(), 44 | }; 45 | 46 | let mut models = Models::new(); 47 | models.define::().unwrap(); 48 | let db = Builder::new() 49 | .create(&models, tf.path("test").as_std_path()) 50 | .unwrap(); 51 | 52 | // Insert 5 items 53 | let rw = db.rw_transaction().unwrap(); 54 | rw.insert(item.clone()).unwrap(); 55 | rw.insert(item.inc().clone()).unwrap(); 56 | rw.insert(item.inc().clone()).unwrap(); 57 | rw.insert(item.inc().clone()).unwrap(); 58 | rw.insert(item.inc().clone()).unwrap(); 59 | rw.commit().unwrap(); 60 | 61 | let stats = db.redb_stats().unwrap(); 62 | assert_eq!(stats.primary_tables.len(), 1); 63 | assert_eq!(stats.primary_tables[0].name, "1_1_generate_my_primary_key"); 64 | assert_eq!(stats.primary_tables[0].n_entries, Some(5)); 65 | assert_eq!(stats.secondary_tables.len(), 1); 66 | assert_eq!( 67 | stats.secondary_tables[0].name, 68 | "1_1_generate_my_secondary_key" 69 | ); 70 | assert_eq!(stats.secondary_tables[0].n_entries, Some(5)); 71 | 72 | // Count items 73 | let r = db.r_transaction().unwrap(); 74 | let len = r.len().primary::().unwrap(); 75 | assert_eq!(len, 5); 76 | 77 | // Drain items 78 | let rw = db.rw_transaction().unwrap(); 79 | let items = rw.drain().primary::().unwrap(); 80 | assert_eq!(items.len(), 5); 81 | rw.commit().unwrap(); 82 | 83 | // Count items 84 | let r = db.r_transaction().unwrap(); 85 | let len = r.len().primary::().unwrap(); 86 | assert_eq!(len, 0); 87 | 88 | let stats = db.redb_stats().unwrap(); 89 | assert_eq!(stats.primary_tables.len(), 1); 90 | assert_eq!(stats.primary_tables[0].name, "1_1_generate_my_primary_key"); 91 | assert_eq!(stats.primary_tables[0].n_entries, Some(0)); 92 | assert_eq!(stats.secondary_tables.len(), 1); 93 | assert_eq!( 94 | stats.secondary_tables[0].name, 95 | "1_1_generate_my_secondary_key" 96 | ); 97 | assert_eq!(stats.secondary_tables[0].n_entries, Some(0)); 98 | } 99 | -------------------------------------------------------------------------------- /tests/query/auto_update_pk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn auto_update_non_existent() { 17 | let tf = TmpFs::new().unwrap(); 18 | let mut models = Models::new(); 19 | models.define::().unwrap(); 20 | let db = Builder::new() 21 | .create(&models, tf.path("test").as_std_path()) 22 | .unwrap(); 23 | 24 | let rw = db.rw_transaction().unwrap(); 25 | let result = rw.auto_update(Item { 26 | id: 1, 27 | name: "test".to_string(), 28 | }); 29 | assert!(result.unwrap().is_none()); 30 | rw.commit().unwrap(); 31 | } 32 | 33 | #[test] 34 | fn auto_update_existing() { 35 | let tf = TmpFs::new().unwrap(); 36 | let mut models = Models::new(); 37 | models.define::().unwrap(); 38 | let db = Builder::new() 39 | .create(&models, tf.path("test").as_std_path()) 40 | .unwrap(); 41 | 42 | let initial_item = Item { 43 | id: 1, 44 | name: "initial".to_string(), 45 | }; 46 | let updated_item = Item { 47 | id: 1, 48 | name: "updated".to_string(), 49 | }; 50 | 51 | // Insert initial item 52 | let rw = db.rw_transaction().unwrap(); 53 | rw.insert(initial_item.clone()).unwrap(); 54 | rw.commit().unwrap(); 55 | 56 | // Update the item 57 | let rw = db.rw_transaction().unwrap(); 58 | let old_value = rw.auto_update(updated_item.clone()).unwrap(); 59 | assert!(old_value.is_some()); 60 | assert_eq!(old_value.unwrap(), initial_item); 61 | 62 | // Verify the update 63 | let current: Item = rw.get().primary(1u32).unwrap().unwrap(); 64 | assert_eq!(current, updated_item); 65 | rw.commit().unwrap(); 66 | } 67 | 68 | #[test] 69 | fn auto_update_multiple() { 70 | let tf = TmpFs::new().unwrap(); 71 | let mut models = Models::new(); 72 | models.define::().unwrap(); 73 | let db = Builder::new() 74 | .create(&models, tf.path("test").as_std_path()) 75 | .unwrap(); 76 | 77 | // Insert multiple items 78 | let rw = db.rw_transaction().unwrap(); 79 | for i in 1..=3 { 80 | rw.insert(Item { 81 | id: i, 82 | name: format!("item{}", i), 83 | }) 84 | .unwrap(); 85 | } 86 | rw.commit().unwrap(); 87 | 88 | // Update middle item 89 | let rw = db.rw_transaction().unwrap(); 90 | let old_value = rw 91 | .auto_update(Item { 92 | id: 2, 93 | name: "updated".to_string(), 94 | }) 95 | .unwrap(); 96 | assert!(old_value.is_some()); 97 | assert_eq!( 98 | old_value.unwrap(), 99 | Item { 100 | id: 2, 101 | name: "item2".to_string() 102 | } 103 | ); 104 | 105 | // Verify other items unchanged 106 | let item1: Item = rw.get().primary(1u32).unwrap().unwrap(); 107 | assert_eq!(item1.name, "item1"); 108 | let item2: Item = rw.get().primary(2u32).unwrap().unwrap(); 109 | assert_eq!(item2.name, "updated"); 110 | let item3: Item = rw.get().primary(3u32).unwrap().unwrap(); 111 | assert_eq!(item3.name, "item3"); 112 | rw.commit().unwrap(); 113 | } 114 | -------------------------------------------------------------------------------- /tests/query/auto_update_sk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | #[secondary_key] 13 | name: String, 14 | #[secondary_key(unique)] 15 | code: String, 16 | } 17 | 18 | #[test] 19 | fn auto_update_non_existent() { 20 | let tf = TmpFs::new().unwrap(); 21 | let mut models = Models::new(); 22 | models.define::().unwrap(); 23 | let db = Builder::new() 24 | .create(&models, tf.path("test").as_std_path()) 25 | .unwrap(); 26 | 27 | let rw = db.rw_transaction().unwrap(); 28 | let result = rw.auto_update(Item { 29 | id: 1, 30 | name: "test".to_string(), 31 | code: "A1".to_string(), 32 | }); 33 | assert!(result.unwrap().is_none()); 34 | rw.commit().unwrap(); 35 | } 36 | 37 | #[test] 38 | fn auto_update_existing_check_secondary_keys() { 39 | let tf = TmpFs::new().unwrap(); 40 | let mut models = Models::new(); 41 | models.define::().unwrap(); 42 | let db = Builder::new() 43 | .create(&models, tf.path("test").as_std_path()) 44 | .unwrap(); 45 | 46 | let initial_item = Item { 47 | id: 1, 48 | name: "initial".to_string(), 49 | code: "A1".to_string(), 50 | }; 51 | let updated_item = Item { 52 | id: 1, 53 | name: "updated".to_string(), 54 | code: "B1".to_string(), 55 | }; 56 | 57 | // Insert initial item 58 | let rw = db.rw_transaction().unwrap(); 59 | rw.insert(initial_item.clone()).unwrap(); 60 | rw.commit().unwrap(); 61 | 62 | // Update the item 63 | let rw = db.rw_transaction().unwrap(); 64 | let old_value = rw.auto_update(updated_item.clone()).unwrap(); 65 | assert!(old_value.is_some()); 66 | assert_eq!(old_value.unwrap(), initial_item); 67 | 68 | // Verify primary key lookup 69 | let current: Item = rw.get().primary(1u32).unwrap().unwrap(); 70 | assert_eq!(current, updated_item); 71 | 72 | // Verify secondary key lookup (non-unique) 73 | let by_name: Vec = rw 74 | .scan() 75 | .secondary(ItemKey::name) 76 | .unwrap() 77 | .all() 78 | .unwrap() 79 | .collect::, _>>() 80 | .unwrap(); 81 | assert_eq!(by_name.len(), 1); 82 | assert_eq!(by_name[0], updated_item); 83 | 84 | // Verify secondary key lookup (unique) 85 | let by_code: Item = rw 86 | .get() 87 | .secondary(ItemKey::code, "B1".to_string()) 88 | .unwrap() 89 | .unwrap(); 90 | assert_eq!(by_code, updated_item); 91 | 92 | // Old secondary keys should not exist 93 | let old_by_code: Option = rw.get().secondary(ItemKey::code, "A1".to_string()).unwrap(); 94 | assert!(old_by_code.is_none()); 95 | 96 | rw.commit().unwrap(); 97 | } 98 | 99 | #[test] 100 | fn auto_update_multiple_with_secondary_keys() { 101 | let tf = TmpFs::new().unwrap(); 102 | let mut models = Models::new(); 103 | models.define::().unwrap(); 104 | let db = Builder::new() 105 | .create(&models, tf.path("test").as_std_path()) 106 | .unwrap(); 107 | 108 | // Insert multiple items 109 | let rw = db.rw_transaction().unwrap(); 110 | for i in 1..=3 { 111 | rw.insert(Item { 112 | id: i, 113 | name: format!("item{}", i), 114 | code: format!("A{}", i), 115 | }) 116 | .unwrap(); 117 | } 118 | rw.commit().unwrap(); 119 | 120 | // Update middle item 121 | let rw = db.rw_transaction().unwrap(); 122 | let old_value = rw 123 | .auto_update(Item { 124 | id: 2, 125 | name: "updated".to_string(), 126 | code: "B2".to_string(), 127 | }) 128 | .unwrap(); 129 | assert!(old_value.is_some()); 130 | assert_eq!( 131 | old_value.unwrap(), 132 | Item { 133 | id: 2, 134 | name: "item2".to_string(), 135 | code: "A2".to_string(), 136 | } 137 | ); 138 | 139 | // Verify all items by primary key 140 | let item1: Item = rw.get().primary(1u32).unwrap().unwrap(); 141 | assert_eq!(item1.name, "item1"); 142 | assert_eq!(item1.code, "A1"); 143 | 144 | let item2: Item = rw.get().primary(2u32).unwrap().unwrap(); 145 | assert_eq!(item2.name, "updated"); 146 | assert_eq!(item2.code, "B2"); 147 | 148 | let item3: Item = rw.get().primary(3u32).unwrap().unwrap(); 149 | assert_eq!(item3.name, "item3"); 150 | assert_eq!(item3.code, "A3"); 151 | 152 | // Verify secondary key updates 153 | let by_code2: Item = rw 154 | .get() 155 | .secondary(ItemKey::code, "B2".to_string()) 156 | .unwrap() 157 | .unwrap(); 158 | assert_eq!(by_code2, item2); 159 | 160 | // Old secondary key should not exist 161 | let old_by_code2: Option = rw.get().secondary(ItemKey::code, "A2".to_string()).unwrap(); 162 | assert!(old_by_code2.is_none()); 163 | 164 | rw.commit().unwrap(); 165 | } 166 | -------------------------------------------------------------------------------- /tests/query/insert_get_pk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn insert_get() { 17 | let item = Item { 18 | id: 1, 19 | name: "test".to_string(), 20 | }; 21 | 22 | let tf = TmpFs::new().unwrap(); 23 | let mut models = Models::new(); 24 | models.define::().unwrap(); 25 | let db = Builder::new() 26 | .create(&models, tf.path("test").as_std_path()) 27 | .unwrap(); 28 | 29 | let rw = db.rw_transaction().unwrap(); 30 | rw.insert(item.clone()).unwrap(); 31 | rw.commit().unwrap(); 32 | 33 | let r = db.r_transaction().unwrap(); 34 | let result_item = r.get().primary(1u32).unwrap().unwrap(); 35 | assert_eq!(item, result_item); 36 | } 37 | 38 | #[test] 39 | fn test_insert_duplicate_key() { 40 | let tf = TmpFs::new().unwrap(); 41 | 42 | let item_1 = Item { 43 | id: 1, 44 | name: "test1".to_string(), 45 | }; 46 | let item_2 = Item { 47 | id: 1, 48 | name: "test2".to_string(), 49 | }; 50 | 51 | let mut models = Models::new(); 52 | models.define::().unwrap(); 53 | let db = Builder::new() 54 | .create(&models, tf.path("test").as_std_path()) 55 | .unwrap(); 56 | 57 | let rw = db.rw_transaction().unwrap(); 58 | rw.insert(item_1.clone()).unwrap(); 59 | let result = rw.insert(item_2.clone()); 60 | assert!(result.is_err()); 61 | assert!(matches!( 62 | result.unwrap_err(), 63 | db_type::Error::DuplicateKey { .. } 64 | )); 65 | } 66 | -------------------------------------------------------------------------------- /tests/query/insert_get_sk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db( 9 | primary_key(pk -> String), 10 | secondary_key(gk_1 -> String, unique) 11 | )] 12 | struct Item { 13 | id: u32, 14 | name: String, 15 | } 16 | 17 | impl Item { 18 | pub fn pk(&self) -> String { 19 | format!("{}", self.id) 20 | } 21 | 22 | pub fn gk_1(&self) -> String { 23 | format!("{}-{}", self.name, self.id) 24 | } 25 | } 26 | 27 | #[test] 28 | fn insert_get_read_write_transaction() { 29 | let tf = TmpFs::new().unwrap(); 30 | 31 | let item = Item { 32 | id: 1, 33 | name: "test".to_string(), 34 | }; 35 | 36 | let mut models = Models::new(); 37 | models.define::().unwrap(); 38 | let db = Builder::new() 39 | .create(&models, tf.path("test").as_std_path()) 40 | .unwrap(); 41 | 42 | let rw = db.rw_transaction().unwrap(); 43 | rw.insert(item.clone()).unwrap(); 44 | rw.commit().unwrap(); 45 | 46 | let rw = db.rw_transaction().unwrap(); 47 | let result_item = rw 48 | .get() 49 | .secondary(ItemKey::gk_1, "test-1") 50 | .unwrap() 51 | .unwrap(); 52 | assert_eq!(item, result_item); 53 | rw.commit().unwrap(); 54 | } 55 | 56 | #[test] 57 | fn insert_get_read_transaction() { 58 | let tf = TmpFs::new().unwrap(); 59 | 60 | let item = Item { 61 | id: 1, 62 | name: "test".to_string(), 63 | }; 64 | 65 | let mut models = Models::new(); 66 | models.define::().unwrap(); 67 | let db = Builder::new() 68 | .create(&models, tf.path("test").as_std_path()) 69 | .unwrap(); 70 | 71 | let rw = db.rw_transaction().unwrap(); 72 | rw.insert(item.clone()).unwrap(); 73 | rw.commit().unwrap(); 74 | 75 | let r = db.r_transaction().unwrap(); 76 | let result_item = r.get().secondary(ItemKey::gk_1, "test-1").unwrap().unwrap(); 77 | 78 | assert_eq!(item, result_item); 79 | } 80 | 81 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 82 | #[native_model(id = 1, version = 1)] 83 | #[native_db] 84 | struct ItemDuplicate { 85 | #[primary_key] 86 | id: u32, 87 | #[secondary_key(unique)] 88 | name: String, 89 | } 90 | 91 | #[test] 92 | fn test_insert_duplicate_key() { 93 | let tf = TmpFs::new().unwrap(); 94 | 95 | let item_1 = ItemDuplicate { 96 | id: 1, 97 | name: "test".to_string(), 98 | }; 99 | let item_2 = ItemDuplicate { 100 | id: 2, 101 | name: "test".to_string(), 102 | }; 103 | 104 | let mut models = Models::new(); 105 | models.define::().unwrap(); 106 | let db = Builder::new() 107 | .create(&models, tf.path("test").as_std_path()) 108 | .unwrap(); 109 | 110 | let rw = db.rw_transaction().unwrap(); 111 | rw.insert(item_1).unwrap(); 112 | let result = rw.insert(item_2); 113 | assert!(result.is_err()); 114 | assert!(matches!( 115 | result.unwrap_err(), 116 | db_type::Error::DuplicateKey { .. } 117 | )); 118 | } 119 | 120 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 121 | #[native_model(id = 1, version = 1)] 122 | #[native_db] 123 | struct ItemOptional { 124 | #[primary_key] 125 | id: u32, 126 | #[secondary_key(unique, optional)] 127 | name: Option, 128 | } 129 | 130 | #[test] 131 | fn test_insert_optional() { 132 | let tf = TmpFs::new().unwrap(); 133 | 134 | let item_1 = ItemOptional { 135 | id: 1, 136 | name: Some("test".to_string()), 137 | }; 138 | let item_2 = ItemOptional { id: 2, name: None }; 139 | 140 | let mut models = Models::new(); 141 | models.define::().unwrap(); 142 | let db = Builder::new() 143 | .create(&models, tf.path("test").as_std_path()) 144 | .unwrap(); 145 | 146 | let rw = db.rw_transaction().unwrap(); 147 | rw.insert(item_1.clone()).unwrap(); 148 | rw.insert(item_2.clone()).unwrap(); 149 | rw.commit().unwrap(); 150 | 151 | let stats = db.redb_stats().unwrap(); 152 | assert_eq!(stats.primary_tables.len(), 1); 153 | assert_eq!(stats.primary_tables[0].name, "1_1_id"); 154 | assert_eq!(stats.primary_tables[0].n_entries, Some(2)); 155 | assert_eq!(stats.secondary_tables.len(), 1); 156 | assert_eq!(stats.secondary_tables[0].name, "1_1_name"); 157 | assert_eq!(stats.secondary_tables[0].n_entries, Some(1)); 158 | 159 | let r = db.r_transaction().unwrap(); 160 | let result_item = r 161 | .get() 162 | .secondary(ItemOptionalKey::name, Some("test")) 163 | .unwrap() 164 | .unwrap(); 165 | assert_eq!(item_1, result_item); 166 | } 167 | -------------------------------------------------------------------------------- /tests/query/insert_len_pk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn insert_len_read_transaction() { 17 | let tf = TmpFs::new().unwrap(); 18 | 19 | let item = Item { 20 | id: 1, 21 | name: "test".to_string(), 22 | }; 23 | 24 | let mut models = Models::new(); 25 | models.define::().unwrap(); 26 | let db = Builder::new() 27 | .create(&models, tf.path("test").as_std_path()) 28 | .unwrap(); 29 | 30 | let rw = db.rw_transaction().unwrap(); 31 | rw.insert(item.clone()).unwrap(); 32 | rw.commit().unwrap(); 33 | 34 | let r = db.r_transaction().unwrap(); 35 | let result_item = r.len().primary::().unwrap(); 36 | assert_eq!(1, result_item); 37 | 38 | let item = Item { 39 | id: 2, 40 | name: "test".to_string(), 41 | }; 42 | 43 | let rw = db.rw_transaction().unwrap(); 44 | rw.insert(item.clone()).unwrap(); 45 | rw.commit().unwrap(); 46 | 47 | let r = db.r_transaction().unwrap(); 48 | let result_item = r.len().primary::().unwrap(); 49 | assert_eq!(2, result_item); 50 | } 51 | -------------------------------------------------------------------------------- /tests/query/insert_len_sk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | #[secondary_key(optional)] 13 | name: Option, 14 | } 15 | 16 | #[test] 17 | fn insert_len_read_transaction() { 18 | let tf = TmpFs::new().unwrap(); 19 | 20 | let item = Item { id: 1, name: None }; 21 | 22 | let mut models = Models::new(); 23 | models.define::().unwrap(); 24 | let db = Builder::new() 25 | .create(&models, tf.path("test").as_std_path()) 26 | .unwrap(); 27 | 28 | let rw = db.rw_transaction().unwrap(); 29 | rw.insert(item.clone()).unwrap(); 30 | rw.commit().unwrap(); 31 | 32 | let r = db.r_transaction().unwrap(); 33 | let result_item = r.len().secondary::(ItemKey::name).unwrap(); 34 | assert_eq!(0, result_item); 35 | 36 | let item = Item { 37 | id: 2, 38 | name: Some("test".to_string()), 39 | }; 40 | 41 | let rw = db.rw_transaction().unwrap(); 42 | rw.insert(item.clone()).unwrap(); 43 | rw.commit().unwrap(); 44 | 45 | let r = db.r_transaction().unwrap(); 46 | let result_item = r.len().secondary::(ItemKey::name).unwrap(); 47 | assert_eq!(1, result_item); 48 | } 49 | -------------------------------------------------------------------------------- /tests/query/insert_remove_pk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn insert_get() { 17 | let tf = TmpFs::new().unwrap(); 18 | 19 | let item = Item { 20 | id: 1, 21 | name: "test".to_string(), 22 | }; 23 | 24 | let mut models = Models::new(); 25 | models.define::().unwrap(); 26 | let db = Builder::new() 27 | .create(&models, tf.path("test").as_std_path()) 28 | .unwrap(); 29 | 30 | let rw = db.rw_transaction().unwrap(); 31 | rw.insert(item.clone()).unwrap(); 32 | rw.commit().unwrap(); 33 | 34 | let stats = db.redb_stats().unwrap(); 35 | assert_eq!(stats.primary_tables.len(), 1); 36 | assert_eq!(stats.primary_tables[0].name, "1_1_id"); 37 | assert_eq!(stats.primary_tables[0].n_entries, Some(1)); 38 | 39 | let rw = db.rw_transaction().unwrap(); 40 | let old_value = rw.remove(item.clone()).unwrap(); 41 | assert_eq!(old_value, item); 42 | rw.commit().unwrap(); 43 | 44 | let stats = db.redb_stats().unwrap(); 45 | assert_eq!(stats.primary_tables.len(), 1); 46 | assert_eq!(stats.primary_tables[0].name, "1_1_id"); 47 | assert_eq!(stats.primary_tables[0].n_entries, Some(0)); 48 | } 49 | -------------------------------------------------------------------------------- /tests/query/insert_update_pk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn insert_update_pk() { 17 | let tf = TmpFs::new().unwrap(); 18 | 19 | let item = Item { 20 | id: 1, 21 | name: "test".to_string(), 22 | }; 23 | 24 | let mut models = Models::new(); 25 | models.define::().unwrap(); 26 | let db = Builder::new() 27 | .create(&models, tf.path("test").as_std_path()) 28 | .unwrap(); 29 | 30 | // Insert the item 31 | let rw = db.rw_transaction().unwrap(); 32 | rw.insert(item.clone()).unwrap(); 33 | rw.commit().unwrap(); 34 | 35 | // Check if the item is in the database 36 | let txn = db.r_transaction().unwrap(); 37 | let item2: Item = txn.get().primary(1u32).unwrap().unwrap(); 38 | assert_eq!(item, item2); 39 | 40 | let item2 = Item { 41 | id: 2, 42 | name: "test2".to_string(), 43 | }; 44 | 45 | // Update the item 46 | let rw = db.rw_transaction().unwrap(); 47 | rw.update(item.clone(), item2.clone()).unwrap(); 48 | rw.commit().unwrap(); 49 | 50 | // Check if the item v1 is not in the database 51 | let r = db.r_transaction().unwrap(); 52 | let item2: Option = r.get().primary(1u32).unwrap(); 53 | assert_eq!(item2, None); 54 | 55 | // Check if the item v2 is in the database 56 | let r = db.r_transaction().unwrap(); 57 | let item2: Item = r.get().primary(2u32).unwrap().unwrap(); 58 | assert_eq!(item2, item2); 59 | 60 | // Check if length is 1 61 | let r = db.r_transaction().unwrap(); 62 | let length = r.len().primary::().unwrap(); 63 | assert_eq!(length, 1); 64 | } 65 | -------------------------------------------------------------------------------- /tests/query/insert_update_sk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | #[secondary_key(unique)] 13 | name: String, 14 | } 15 | 16 | #[test] 17 | fn insert_update_sk() { 18 | let tf = TmpFs::new().unwrap(); 19 | 20 | let item = Item { 21 | id: 1, 22 | name: "test".to_string(), 23 | }; 24 | 25 | let mut models = Models::new(); 26 | models.define::().unwrap(); 27 | let db = Builder::new() 28 | .create(&models, tf.path("test").as_std_path()) 29 | .unwrap(); 30 | 31 | // Insert the item 32 | let rw = db.rw_transaction().unwrap(); 33 | rw.insert(item.clone()).unwrap(); 34 | rw.commit().unwrap(); 35 | 36 | // Check if the item is in the database by primary key 37 | let r = db.r_transaction().unwrap(); 38 | let item2: Item = r.get().primary(1u32).unwrap().unwrap(); 39 | assert_eq!(item, item2); 40 | 41 | // Check if the item is in the database by secondary key 42 | let r = db.r_transaction().unwrap(); 43 | let item2: Item = r.get().secondary(ItemKey::name, "test").unwrap().unwrap(); 44 | assert_eq!(item, item2); 45 | 46 | let item_v2 = Item { 47 | id: 2, 48 | name: "test2".to_string(), 49 | }; 50 | 51 | // Update the item 52 | let rw = db.rw_transaction().unwrap(); 53 | rw.update(item.clone(), item_v2.clone()).unwrap(); 54 | rw.commit().unwrap(); 55 | 56 | // Check if the item v1 is not in the database by primary key 57 | let r = db.r_transaction().unwrap(); 58 | let item2: Option = r.get().primary(1u32).unwrap(); 59 | assert_eq!(item2, None); 60 | 61 | // Check if the item v1 is not in the database by secondary key 62 | let r = db.r_transaction().unwrap(); 63 | let item2: Option = r.get().secondary(ItemKey::name, "test").unwrap(); 64 | assert_eq!(item2, None); 65 | 66 | // Check if the item v2 is in the database by primary key 67 | let r = db.r_transaction().unwrap(); 68 | let item2: Item = r.get().secondary(ItemKey::name, "test2").unwrap().unwrap(); 69 | assert_eq!(item2, item_v2); 70 | 71 | // Check if the item v2 is in the database by secondary key 72 | let r = db.r_transaction().unwrap(); 73 | let item2: Item = r.get().primary(2u32).unwrap().unwrap(); 74 | assert_eq!(item2, item_v2); 75 | 76 | // Check length is 1 77 | let r = db.r_transaction().unwrap(); 78 | let length = r.len().primary::().unwrap(); 79 | assert_eq!(length, 1); 80 | } 81 | -------------------------------------------------------------------------------- /tests/query/mod.rs: -------------------------------------------------------------------------------- 1 | // Insert 2 | mod insert_get_pk; 3 | mod insert_get_sk; 4 | mod insert_len_pk; 5 | mod insert_len_sk; 6 | mod insert_remove_pk; 7 | mod insert_remove_sk; 8 | mod insert_update_pk; 9 | mod insert_update_sk; 10 | 11 | // Upsert 12 | mod upsert_get_pk; 13 | mod upsert_get_sk; 14 | 15 | // Auto Update 16 | mod auto_update_pk; 17 | mod auto_update_sk; 18 | -------------------------------------------------------------------------------- /tests/query/upsert_get_pk.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn upsert_get() { 17 | let item = Item { 18 | id: 1, 19 | name: "test".to_string(), 20 | }; 21 | 22 | let tf = TmpFs::new().unwrap(); 23 | let mut models = Models::new(); 24 | models.define::().unwrap(); 25 | let db = Builder::new() 26 | .create(&models, tf.path("test").as_std_path()) 27 | .unwrap(); 28 | 29 | let rw = db.rw_transaction().unwrap(); 30 | rw.upsert(item.clone()).unwrap(); 31 | rw.commit().unwrap(); 32 | 33 | let r = db.r_transaction().unwrap(); 34 | let result_item = r.get().primary(1u32).unwrap().unwrap(); 35 | assert_eq!(item, result_item); 36 | } 37 | 38 | #[test] 39 | fn test_upsert_duplicate_key() { 40 | let tf = TmpFs::new().unwrap(); 41 | 42 | let item_1 = Item { 43 | id: 1, 44 | name: "test".to_string(), 45 | }; 46 | 47 | let mut models = Models::new(); 48 | models.define::().unwrap(); 49 | let db = Builder::new() 50 | .create(&models, tf.path("test").as_std_path()) 51 | .unwrap(); 52 | 53 | let rw = db.rw_transaction().unwrap(); 54 | rw.upsert(item_1.clone()).unwrap(); 55 | let result = rw.upsert(item_1.clone()); 56 | assert!(result.is_ok()); 57 | } 58 | -------------------------------------------------------------------------------- /tests/refresh.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 6 | #[native_model(id = 1, version = 1)] 7 | #[native_db] 8 | struct Item1 { 9 | #[primary_key] 10 | id: u32, 11 | #[secondary_key(unique)] 12 | name: String, 13 | } 14 | 15 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 16 | #[native_model(id = 2, version = 1)] 17 | #[native_db] 18 | struct Item2 { 19 | #[primary_key] 20 | id: u32, 21 | #[secondary_key(optional)] 22 | id2: Option, 23 | #[secondary_key] 24 | name: String, 25 | } 26 | 27 | // TODO: when we remove the feature upgrade_0_7_x, we must rewrite the test 28 | #[test] 29 | #[cfg(feature = "upgrade_0_7_x")] 30 | fn test_refresh() { 31 | use std::path::PathBuf; 32 | #[cfg(any(target_os = "android", target_os = "ios"))] 33 | let database_path = { dinghy_test::test_project_path().join("tests/data/db_0_7_1") }; 34 | 35 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 36 | let database_path = { 37 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 38 | PathBuf::from(format!("{}/tests/data/db_0_7_1", root_project_path)) 39 | }; 40 | 41 | use shortcut_assert_fs::TmpFs; 42 | let tmp = TmpFs::new().unwrap(); 43 | 44 | // Copy the legacy database to the temporary directory. 45 | let tmp_database_path = tmp.path("db_0_7_1"); 46 | std::fs::copy(&database_path, &tmp_database_path).unwrap(); 47 | 48 | // Open the legacy database with the upgrade feature. This must succeed. 49 | let mut models = Models::new(); 50 | models.define::().unwrap(); 51 | models.define::().unwrap(); 52 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 53 | // TODO: during open, the database must be upgraded to the latest version. 54 | 55 | tmp.display_dir_entries(); 56 | let stats = db.redb_stats().unwrap(); 57 | assert_eq!(stats.primary_tables.len(), 2); 58 | assert_eq!(stats.primary_tables[0].name, "1_1_id"); 59 | assert_eq!(stats.primary_tables[0].n_entries, Some(1)); 60 | assert_eq!(stats.primary_tables[1].name, "2_1_id"); 61 | assert_eq!(stats.primary_tables[1].n_entries, Some(1000)); 62 | assert_eq!(stats.secondary_tables.len(), 3); 63 | assert_eq!(stats.secondary_tables[0].name, "1_1_name"); 64 | assert_eq!(stats.secondary_tables[0].n_entries, Some(1)); 65 | assert_eq!(stats.secondary_tables[1].name, "2_1_id2"); 66 | assert_eq!(stats.secondary_tables[1].n_entries, Some(500)); 67 | assert_eq!(stats.secondary_tables[2].name, "2_1_name"); 68 | assert_eq!(stats.secondary_tables[2].n_entries, Some(1000)); 69 | 70 | // Is not usefull but it's generaly used with the method upgrading_from_version. 71 | if db.upgrading_from_version("<0.8.0").unwrap() { 72 | // Refresh the database 73 | let rw = db.rw_transaction().unwrap(); 74 | rw.refresh::().unwrap(); 75 | rw.refresh::().unwrap(); 76 | rw.commit().unwrap(); 77 | } else { 78 | assert!(false); 79 | } 80 | 81 | let stats = db.redb_stats().unwrap(); 82 | assert_eq!(stats.primary_tables.len(), 2); 83 | assert_eq!(stats.primary_tables[0].name, "1_1_id"); 84 | assert_eq!(stats.primary_tables[0].n_entries, Some(1)); 85 | assert_eq!(stats.primary_tables[1].name, "2_1_id"); 86 | assert_eq!(stats.primary_tables[1].n_entries, Some(1000)); 87 | assert_eq!(stats.secondary_tables.len(), 3); 88 | assert_eq!(stats.secondary_tables[0].name, "1_1_name"); 89 | assert_eq!(stats.secondary_tables[0].n_entries, Some(1)); 90 | assert_eq!(stats.secondary_tables[1].name, "2_1_id2"); 91 | assert_eq!(stats.secondary_tables[1].n_entries, Some(500)); 92 | assert_eq!(stats.secondary_tables[2].name, "2_1_name"); 93 | assert_eq!(stats.secondary_tables[2].n_entries, Some(1000)); 94 | } 95 | -------------------------------------------------------------------------------- /tests/simple_multithreads.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | use std::sync::Arc; 6 | use std::thread; 7 | 8 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 9 | #[native_model(id = 1, version = 1)] 10 | #[native_db] 11 | struct Item { 12 | #[primary_key] 13 | id: u32, 14 | name: String, 15 | } 16 | 17 | #[test] 18 | fn multi_threads() { 19 | let tf = TmpFs::new().unwrap(); 20 | 21 | let mut models = Models::new(); 22 | models.define::().unwrap(); 23 | let db = Builder::new() 24 | .create(&models, tf.path("test").as_std_path()) 25 | .unwrap(); 26 | 27 | let db = Arc::new(db); 28 | 29 | thread::scope(|s| { 30 | let db_thread_1 = db.clone(); 31 | let handle_thread_1 = s.spawn(move || { 32 | let item_a = Item { 33 | id: 1, 34 | name: "a".to_string(), 35 | }; 36 | { 37 | let rw = db_thread_1.rw_transaction().unwrap(); 38 | rw.insert(item_a).unwrap(); 39 | rw.commit().unwrap(); 40 | } 41 | }); 42 | 43 | let db_thread_2 = db.clone(); 44 | let handle_thread_2 = s.spawn(move || { 45 | let item_b = Item { 46 | id: 2, 47 | name: "b".to_string(), 48 | }; 49 | { 50 | let rw = db_thread_2.rw_transaction().unwrap(); 51 | rw.insert(item_b).unwrap(); 52 | rw.commit().unwrap(); 53 | } 54 | }); 55 | 56 | handle_thread_1.join().unwrap(); 57 | handle_thread_2.join().unwrap(); 58 | }); 59 | 60 | { 61 | let r = db.r_transaction().unwrap(); 62 | let len = r.len().primary::().unwrap(); 63 | assert_eq!(len, 2); 64 | 65 | let item_a: Item = r.get().primary(1u32).unwrap().unwrap(); 66 | assert_eq!(item_a.name, "a".to_string()); 67 | 68 | let item_b: Item = r.get().primary(2u32).unwrap().unwrap(); 69 | assert_eq!(item_b.name, "b".to_string()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/snapshot.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | 6 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 7 | #[native_model(id = 1, version = 1)] 8 | #[native_db] 9 | struct Item { 10 | #[primary_key] 11 | id: u32, 12 | name: String, 13 | } 14 | 15 | #[test] 16 | fn test_snapshot() { 17 | let tf = TmpFs::new().unwrap(); 18 | let mut models = Models::new(); 19 | models.define::().unwrap(); 20 | 21 | let db = Builder::new().create_in_memory(&models).unwrap(); 22 | 23 | let rw = db.rw_transaction().unwrap(); 24 | rw.insert(Item { 25 | id: 1, 26 | name: "test".to_string(), 27 | }) 28 | .unwrap(); 29 | rw.commit().unwrap(); 30 | 31 | let db_snapshot = db 32 | .snapshot(&models, tf.path("snapshot.db").as_std_path()) 33 | .unwrap(); 34 | 35 | let r = db_snapshot.r_transaction().unwrap(); 36 | let result_item = r.get().primary(1u32).unwrap().unwrap(); 37 | assert_eq!( 38 | Item { 39 | id: 1, 40 | name: "test".to_string() 41 | }, 42 | result_item 43 | ); 44 | 45 | tf.display_dir_entries(); 46 | } 47 | -------------------------------------------------------------------------------- /tests/transaction.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | use shortcut_assert_fs::TmpFs; 5 | use std::panic::AssertUnwindSafe; 6 | 7 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 8 | #[native_model(id = 1, version = 1)] 9 | #[native_db] 10 | struct Item { 11 | #[primary_key] 12 | id: u32, 13 | name: String, 14 | } 15 | 16 | #[test] 17 | fn test_transaction_obj_1() { 18 | let tf = TmpFs::new().unwrap(); 19 | 20 | let mut models = Models::new(); 21 | models.define::().unwrap(); 22 | let db = Builder::new() 23 | .create(&models, tf.path("test").as_std_path()) 24 | .unwrap(); 25 | 26 | let item = Item { 27 | id: 1, 28 | name: "test".to_string(), 29 | }; 30 | 31 | let rw = db.rw_transaction().unwrap(); 32 | rw.insert(item).unwrap(); 33 | rw.commit().unwrap(); 34 | 35 | let r = db.r_transaction().unwrap(); 36 | let result: Item = r.get().primary(1u32).unwrap().unwrap(); 37 | assert_eq!(result.id, 1); 38 | } 39 | 40 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 41 | #[native_model(id = 2, version = 1)] 42 | #[native_db] 43 | struct Item2 { 44 | #[primary_key] 45 | id: u32, 46 | name: String, 47 | } 48 | 49 | #[test] 50 | fn test_transaction_obj_1_and_obj_2() { 51 | let tf = TmpFs::new().unwrap(); 52 | 53 | let mut models = Models::new(); 54 | models.define::().unwrap(); 55 | models.define::().unwrap(); 56 | let db = Builder::new() 57 | .create(&models, tf.path("test").as_std_path()) 58 | .unwrap(); 59 | 60 | let item_1 = Item { 61 | id: 1, 62 | name: "test".to_string(), 63 | }; 64 | let item_2 = Item2 { 65 | id: 2, 66 | name: "test".to_string(), 67 | }; 68 | 69 | let rw = db.rw_transaction().unwrap(); 70 | rw.insert(item_1).unwrap(); 71 | rw.insert(item_2).unwrap(); 72 | rw.commit().unwrap(); 73 | 74 | let r = db.r_transaction().unwrap(); 75 | let result: Item = r.get().primary(1u32).unwrap().unwrap(); 76 | assert_eq!(result.id, 1); 77 | let result: Item2 = r.get().primary(2u32).unwrap().unwrap(); 78 | assert_eq!(result.id, 2); 79 | } 80 | 81 | #[test] 82 | fn test_abort_transaction_obj_1_and_obj_2() { 83 | let tf = TmpFs::new().unwrap(); 84 | 85 | let mut models = Models::new(); 86 | models.define::().unwrap(); 87 | models.define::().unwrap(); 88 | let db = Builder::new() 89 | .create(&models, tf.path("test").as_std_path()) 90 | .unwrap(); 91 | 92 | let item_1 = Item { 93 | id: 1, 94 | name: "test".to_string(), 95 | }; 96 | 97 | let rw = db.rw_transaction().unwrap(); 98 | rw.insert(item_1).unwrap(); 99 | rw.abort().unwrap(); 100 | // After abort, the transaction, the transaction can not be used anymore. 101 | //rw.insert(item_2).unwrap(); 102 | //rw.commit().unwrap(); 103 | 104 | let r = db.r_transaction().unwrap(); 105 | assert!(r.get().primary::(1u32).unwrap().is_none()); 106 | } 107 | 108 | #[allow(unreachable_code)] 109 | #[test] 110 | fn test_transaction_fail() { 111 | let tf = TmpFs::new().unwrap(); 112 | 113 | let mut models = Models::new(); 114 | models.define::().unwrap(); 115 | let db = Builder::new() 116 | .create(&models, tf.path("test").as_std_path()) 117 | .unwrap(); 118 | 119 | let item_1 = Item { 120 | id: 1, 121 | name: "test".to_string(), 122 | }; 123 | 124 | let rw = db.rw_transaction().unwrap(); 125 | rw.insert(item_1).unwrap(); 126 | rw.commit().unwrap(); 127 | 128 | let r = db.r_transaction().unwrap(); 129 | let result: Item = r.get().primary(1u32).unwrap().unwrap(); 130 | assert_eq!(result.id, 1); 131 | 132 | let item_2 = Item { 133 | id: 2, 134 | name: "test".to_string(), 135 | }; 136 | let result = std::panic::catch_unwind(AssertUnwindSafe(|| { 137 | let rw = db.rw_transaction().unwrap(); 138 | rw.insert(item_2).unwrap(); 139 | panic!("Random panic here...") 140 | })); 141 | 142 | assert!(result.is_err()); 143 | 144 | let r = db.r_transaction().unwrap(); 145 | let result: Option = r.get().primary(2u32).unwrap(); 146 | assert!(result.is_none()); 147 | } 148 | -------------------------------------------------------------------------------- /tests/upgrade/from_0_5_x_to_0_6_x.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use native_model::{native_model, Model}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 6 | #[native_model(id = 1, version = 1)] 7 | #[native_db] 8 | struct Item1 { 9 | #[primary_key] 10 | id: u32, 11 | #[secondary_key(unique)] 12 | name: String, 13 | } 14 | 15 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 16 | #[native_model(id = 2, version = 1)] 17 | #[native_db] 18 | struct Item2 { 19 | #[primary_key] 20 | id: u32, 21 | #[secondary_key(optional)] 22 | id2: Option, 23 | #[secondary_key] 24 | name: String, 25 | } 26 | 27 | #[test] 28 | #[cfg(not(feature = "upgrade_0_5_x"))] 29 | fn try_to_open_legacy_database_without_upgrade_feature() { 30 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 31 | let database_path = format!("{}/tests/data/db_0_5_x", root_project_path); 32 | 33 | // Try to open the legacy database. This must fail with an UpgradeRequired error. 34 | let mut models = Models::new(); 35 | models.define::().unwrap(); 36 | let db_error: Result, db_type::Error> = 37 | Builder::new().open(&models, &database_path); 38 | assert!(db_error.is_err()); 39 | assert!(matches!( 40 | db_error, 41 | Result::Err(db_type::Error::RedbDatabaseError(boxed_error)) 42 | if matches!(*boxed_error, redb::DatabaseError::UpgradeRequired(1)) 43 | )); 44 | } 45 | 46 | #[test] 47 | #[cfg(feature = "upgrade_0_5_x")] 48 | fn try_to_open_legacy_database_with_upgrade_feature() { 49 | use std::path::PathBuf; 50 | #[cfg(any(target_os = "android", target_os = "ios"))] 51 | let database_path = { dinghy_test::test_project_path().join("tests/data/db_0_5_x") }; 52 | 53 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 54 | let database_path = { 55 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 56 | PathBuf::from(format!("{}/tests/data/db_0_5_x", root_project_path)) 57 | }; 58 | 59 | use shortcut_assert_fs::TmpFs; 60 | let tmp = TmpFs::new().unwrap(); 61 | 62 | // Copy the legacy database to the temporary directory. 63 | let tmp_database_path = tmp.path("db_0_5_x"); 64 | std::fs::copy(&database_path, &tmp_database_path).unwrap(); 65 | 66 | // Open the legacy database with the upgrade feature. This must succeed. 67 | let mut models = Models::new(); 68 | models.define::().unwrap(); 69 | models.define::().unwrap(); 70 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 71 | // TODO: during open, the database must be upgraded to the latest version. 72 | 73 | tmp.display_dir_entries(); 74 | 75 | // Check the content of the database. 76 | let r_txn = db.r_transaction().unwrap(); 77 | let len = r_txn.len().primary::().unwrap(); 78 | assert_eq!(len, 1); 79 | 80 | let r_txn = db.r_transaction().unwrap(); 81 | let len = r_txn.len().primary::().unwrap(); 82 | assert_eq!(len, 1000); 83 | } 84 | 85 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 86 | #[native_model(id = 3, version = 1)] 87 | #[native_db] 88 | struct Item3 { 89 | #[primary_key] 90 | id: u32, 91 | #[secondary_key(optional)] 92 | id2: Option, 93 | #[secondary_key] 94 | name: String, 95 | } 96 | 97 | #[test] 98 | #[cfg(feature = "upgrade_0_5_x")] 99 | fn try_to_open_legacy_database_with_upgrade_feature_with_other_model() { 100 | use std::path::PathBuf; 101 | #[cfg(any(target_os = "android", target_os = "ios"))] 102 | let database_path = { dinghy_test::test_project_path().join("tests/data/db_0_5_x") }; 103 | 104 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 105 | let database_path = { 106 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 107 | PathBuf::from(format!("{}/tests/data/db_0_5_x", root_project_path)) 108 | }; 109 | 110 | use shortcut_assert_fs::TmpFs; 111 | let tmp = TmpFs::new().unwrap(); 112 | 113 | // Copy the legacy database to the temporary directory. 114 | let tmp_database_path = tmp.path("db_0_5_x"); 115 | std::fs::copy(&database_path, &tmp_database_path).unwrap(); 116 | 117 | // Open the legacy database with the upgrade feature. This must succeed. 118 | let mut models = Models::new(); 119 | models.define::().unwrap(); 120 | models.define::().unwrap(); 121 | models.define::().unwrap(); 122 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 123 | // TODO: during open, the database must be upgraded to the latest version. 124 | 125 | tmp.display_dir_entries(); 126 | 127 | // Check the content of the database. 128 | let r_txn = db.r_transaction().unwrap(); 129 | let len = r_txn.len().primary::().unwrap(); 130 | assert_eq!(len, 1); 131 | 132 | let r_txn = db.r_transaction().unwrap(); 133 | let len = r_txn.len().primary::().unwrap(); 134 | assert_eq!(len, 1000); 135 | } 136 | -------------------------------------------------------------------------------- /tests/upgrade/from_0_7_x_to_0_8_x.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use native_db::*; 3 | use native_model::{native_model, Model}; 4 | use redb::ReadableTable; 5 | use serde::{Deserialize, Serialize}; 6 | use std::path::PathBuf; 7 | 8 | const OLD_TABLE: redb::TableDefinition = redb::TableDefinition::new("2_1_name"); 9 | const NEW_TABLE: redb::MultimapTableDefinition = 10 | redb::MultimapTableDefinition::new("2_1_name"); 11 | 12 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 13 | #[native_model(id = 1, version = 1)] 14 | #[native_db] 15 | struct Item1 { 16 | #[primary_key] 17 | id: u32, 18 | #[secondary_key(unique)] 19 | name: String, 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 23 | #[native_model(id = 2, version = 1)] 24 | #[native_db] 25 | struct Item2 { 26 | #[primary_key] 27 | id: u32, 28 | #[secondary_key(optional)] 29 | id2: Option, 30 | #[secondary_key] 31 | name: String, 32 | } 33 | 34 | #[test] 35 | fn upgrade_from_0_7_x_to_0_8_x() { 36 | #[cfg(any(target_os = "android", target_os = "ios"))] 37 | let database_path = { dinghy_test::test_project_path().join("tests/data/db_0_7_1") }; 38 | 39 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 40 | let database_path = { 41 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 42 | PathBuf::from(format!("{}/tests/data/db_0_7_1", root_project_path)) 43 | }; 44 | 45 | use redb::ReadableMultimapTable; 46 | use shortcut_assert_fs::TmpFs; 47 | let tmp = TmpFs::new().unwrap(); 48 | 49 | // Copy the legacy database to the temporary directory. 50 | let tmp_database_path = tmp.path("db_0_7_1"); 51 | std::fs::copy(&database_path, &tmp_database_path).unwrap(); 52 | 53 | // Check before refresh the number of bytes of the secondary table. 54 | // We check that the delimiter is not included in the secondary table. 55 | { 56 | let db = redb::Database::open(&tmp_database_path).unwrap(); 57 | let rx = db.begin_read().unwrap(); 58 | let table = rx.open_table(OLD_TABLE).unwrap(); 59 | let (key, _) = table.first().unwrap().unwrap(); 60 | assert_eq!( 61 | key.value(), 62 | Key::new(vec![105, 116, 101, 109, 50, 95, 48, 0, 0, 0, 0]) 63 | ); 64 | } 65 | 66 | // Refresh the database 67 | let mut models = Models::new(); 68 | models.define::().unwrap(); 69 | models.define::().unwrap(); 70 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 71 | drop(db); 72 | 73 | { 74 | // Check after refresh the number of bytes of the secondary table. 75 | // We check that the delimiter is not included in the secondary table. 76 | let db = redb::Database::open(&tmp_database_path).unwrap(); 77 | let rx = db.begin_read().unwrap(); 78 | let table = rx.open_multimap_table(NEW_TABLE).unwrap(); 79 | let mut primary_key = None; 80 | let mut secondary_key = None; 81 | for result in table.iter().unwrap() { 82 | let result = result.unwrap(); 83 | secondary_key = Some(result.0); 84 | for l_primary_key in result.1 { 85 | let l_primary_key = l_primary_key.unwrap(); 86 | primary_key = Some(l_primary_key); 87 | } 88 | break; 89 | } 90 | 91 | assert_eq!( 92 | secondary_key.unwrap().value(), 93 | Key::new(vec![105, 116, 101, 109, 50, 95, 48]) 94 | ); 95 | assert_eq!(primary_key.unwrap().value(), Key::new(vec![0, 0, 0, 0])); 96 | drop(rx); 97 | drop(db); 98 | } 99 | 100 | // More tests on database integrity 101 | let mut models = Models::new(); 102 | models.define::().unwrap(); 103 | models.define::().unwrap(); 104 | let db = Builder::new().open(&models, &tmp_database_path).unwrap(); 105 | 106 | let r = db.r_transaction().unwrap(); 107 | let len = r.len().primary::().unwrap(); 108 | assert_eq!(len, 1); 109 | 110 | let len = r.len().primary::().unwrap(); 111 | assert_eq!(len, 1000); 112 | 113 | let item5: Item2 = r.get().primary(5_u32).unwrap().unwrap(); 114 | assert_eq!(item5.id, 5); 115 | assert_eq!(item5.id2, None); 116 | assert_eq!(item5.name, "item2_5"); 117 | 118 | let items: Vec = r 119 | .scan() 120 | .secondary(Item2Key::name) 121 | .unwrap() 122 | .range("item2_5".."item2_599") 123 | .unwrap() 124 | .try_collect() 125 | .unwrap(); 126 | assert_eq!(items.len(), 110); 127 | 128 | let items: Vec = r 129 | .scan() 130 | .secondary(Item2Key::name) 131 | .unwrap() 132 | .range("item2_5"..="item2_599") 133 | .unwrap() 134 | .try_collect() 135 | .unwrap(); 136 | assert_eq!(items.len(), 111); 137 | } 138 | -------------------------------------------------------------------------------- /tests/upgrade/mod.rs: -------------------------------------------------------------------------------- 1 | mod from_0_5_x_to_0_6_x; 2 | 3 | #[cfg(feature = "upgrade_0_7_x")] 4 | mod from_0_7_x_to_0_8_x; 5 | -------------------------------------------------------------------------------- /tests/util.rs: -------------------------------------------------------------------------------- 1 | use native_db::*; 2 | use shortcut_assert_fs::TmpFs; 3 | 4 | #[test] 5 | fn test_builder() { 6 | let tf = TmpFs::new().unwrap(); 7 | // Create without error 8 | let mut _db = Builder::new() 9 | .create(&Models::new(), tf.path("test")) 10 | .unwrap(); 11 | } 12 | 13 | #[test] 14 | fn test_builder_with_set_cache_size() { 15 | let tf = TmpFs::new().unwrap(); 16 | // Create without error 17 | let mut builder = Builder::new(); 18 | let _db = builder 19 | .set_cache_size(100) 20 | .create(&Models::new(), tf.path("test")) 21 | .unwrap(); 22 | } 23 | 24 | #[test] 25 | fn test_open_unexisting_database() { 26 | let tf = TmpFs::new().unwrap(); 27 | // Open an unexisting database 28 | assert!(Builder::new() 29 | .open(&Models::new(), tf.path("test")) 30 | .is_err()); 31 | } 32 | 33 | #[test] 34 | fn test_open_existing_database() { 35 | let tf = TmpFs::new().unwrap(); 36 | 37 | // Create a database 38 | let builder = Builder::new(); 39 | let models = Models::new(); 40 | let db = builder.create(&models, tf.path("test")).unwrap(); 41 | drop(db); 42 | 43 | // Open an existing database 44 | let _db = Builder::new().open(&models, tf.path("test")).unwrap(); 45 | } 46 | 47 | use native_model::{native_model, Model}; 48 | use serde::{Deserialize, Serialize}; 49 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 50 | #[native_model(id = 1, version = 1)] 51 | #[native_db] 52 | struct Item1 { 53 | #[primary_key] 54 | id: u32, 55 | #[secondary_key(unique)] 56 | name: String, 57 | } 58 | 59 | #[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)] 60 | #[native_model(id = 2, version = 1)] 61 | #[native_db] 62 | struct Item2 { 63 | #[primary_key] 64 | id: u32, 65 | #[secondary_key(optional)] 66 | id2: Option, 67 | #[secondary_key] 68 | name: String, 69 | } 70 | 71 | #[cfg(not(any(target_os = "android", target_os = "ios")))] 72 | #[test] 73 | fn create_local_database_for_tests() { 74 | let root_project_path = env!("CARGO_MANIFEST_DIR"); 75 | let tmp_data_dir_path = format!("{}/tests/data", root_project_path); 76 | 77 | std::fs::create_dir_all(tmp_data_dir_path.clone()).unwrap(); 78 | 79 | let database_path = format!("{}/db_x_x_x", tmp_data_dir_path); 80 | 81 | if std::fs::metadata(&database_path).is_ok() { 82 | std::fs::remove_file(&database_path).unwrap(); 83 | } 84 | 85 | let mut models = Models::new(); 86 | models.define::().unwrap(); 87 | models.define::().unwrap(); 88 | let db = Builder::new().create(&models, &database_path).unwrap(); 89 | let rw = db.rw_transaction().unwrap(); 90 | let item = Item1 { 91 | id: 1, 92 | name: "item1".to_string(), 93 | }; 94 | 95 | // Genereate 1000 Item2 with random values 96 | for i in 0..1000 { 97 | let id2 = if i % 2 == 0 { Some(i) } else { None }; 98 | let item = Item2 { 99 | id: i, 100 | id2, 101 | name: format!("item2_{}", i), 102 | }; 103 | rw.insert(item).unwrap(); 104 | } 105 | 106 | rw.insert(item).unwrap(); 107 | rw.commit().unwrap(); 108 | 109 | let ro = db.r_transaction().unwrap(); 110 | let len = ro.len().primary::().unwrap(); 111 | assert_eq!(len, 1); 112 | 113 | let len = ro.len().primary::().unwrap(); 114 | assert_eq!(len, 1000); 115 | } 116 | -------------------------------------------------------------------------------- /tests/watch/watch_optional.rs: -------------------------------------------------------------------------------- 1 | use native_db::watch::Event; 2 | use native_db::*; 3 | use native_model::{native_model, Model}; 4 | use serde::{Deserialize, Serialize}; 5 | use shortcut_assert_fs::TmpFs; 6 | 7 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 8 | #[native_model(id = 1, version = 1)] 9 | #[native_db] 10 | struct ItemAOptional { 11 | #[primary_key] 12 | id: u32, 13 | #[secondary_key(unique, optional)] 14 | name: Option, 15 | } 16 | 17 | #[test] 18 | fn watch_one_secondary_key_some() { 19 | let tf = TmpFs::new().unwrap(); 20 | 21 | let mut models: Models = Models::new(); 22 | models.define::().unwrap(); 23 | let db = Builder::new() 24 | .create(&models, tf.path("test").as_std_path()) 25 | .unwrap(); 26 | 27 | let a = ItemAOptional { 28 | id: 1, 29 | name: Some("a".to_string()), 30 | }; 31 | 32 | let (recv, _) = db 33 | .watch() 34 | .get() 35 | .secondary::(ItemAOptionalKey::name, Some("a")) 36 | .unwrap(); 37 | 38 | let rw = db.rw_transaction().unwrap(); 39 | rw.insert(a.clone()).unwrap(); 40 | rw.commit().unwrap(); 41 | 42 | for _ in 0..1 { 43 | let inner_event: ItemAOptional = 44 | if let Event::Insert(event) = recv.recv_timeout(super::TIMEOUT).unwrap() { 45 | event.inner().unwrap() 46 | } else { 47 | panic!("wrong event") 48 | }; 49 | assert_eq!(inner_event, a); 50 | } 51 | assert!(recv.try_recv().is_err()); 52 | } 53 | 54 | #[test] 55 | fn watch_one_secondary_key_none() { 56 | let tf = TmpFs::new().unwrap(); 57 | 58 | let mut models = Models::new(); 59 | models.define::().unwrap(); 60 | let db = Builder::new() 61 | .create(&models, tf.path("test").as_std_path()) 62 | .unwrap(); 63 | 64 | let a = ItemAOptional { id: 1, name: None }; 65 | 66 | let (recv, _) = db 67 | .watch() 68 | .get() 69 | .secondary::(ItemAOptionalKey::name, Some("a")) 70 | .unwrap(); 71 | 72 | let rw = db.rw_transaction().unwrap(); 73 | rw.insert(a.clone()).unwrap(); 74 | rw.commit().unwrap(); 75 | 76 | for _ in 0..1 { 77 | let result = recv.recv_timeout(super::TIMEOUT); 78 | assert!(result.is_err()); 79 | assert!(matches!( 80 | result.unwrap_err(), 81 | std::sync::mpsc::RecvTimeoutError::Timeout 82 | )); 83 | } 84 | assert!(recv.try_recv().is_err()); 85 | } 86 | 87 | #[test] 88 | fn watch_start_with_by_key() { 89 | let tf = TmpFs::new().unwrap(); 90 | 91 | let mut models = Models::new(); 92 | models.define::().unwrap(); 93 | let db = Builder::new() 94 | .create(&models, tf.path("test").as_std_path()) 95 | .unwrap(); 96 | 97 | let item_a_1_k = ItemAOptional { 98 | id: 1, 99 | name: Some("a_1".to_string()), 100 | }; 101 | let item_a_2_k = ItemAOptional { 102 | id: 2, 103 | name: Some("a_2".to_string()), 104 | }; 105 | let item_a_3_k = ItemAOptional { 106 | id: 3, 107 | name: Some("b_1".to_string()), 108 | }; 109 | 110 | let (recv, _) = db 111 | .watch() 112 | .scan() 113 | .secondary(ItemAOptionalKey::name) 114 | .start_with::(Some("a")) 115 | .unwrap(); 116 | 117 | let rw = db.rw_transaction().unwrap(); 118 | rw.insert(item_a_1_k.clone()).unwrap(); 119 | rw.insert(item_a_2_k.clone()).unwrap(); 120 | rw.insert(item_a_3_k.clone()).unwrap(); 121 | rw.commit().unwrap(); 122 | 123 | for _ in 0..2 { 124 | let inner_event: ItemAOptional = 125 | if let Event::Insert(event) = recv.recv_timeout(super::TIMEOUT).unwrap() { 126 | event.inner().unwrap() 127 | } else { 128 | panic!("wrong event") 129 | }; 130 | assert!(inner_event == item_a_1_k || inner_event == item_a_2_k); 131 | } 132 | assert!(recv.try_recv().is_err()); 133 | } 134 | -------------------------------------------------------------------------------- /tests/watch_tokio.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "tokio")] 2 | 3 | use native_db::*; 4 | use native_db::{watch::Event, Models}; 5 | use native_model::{native_model, Model}; 6 | use serde::{Deserialize, Serialize}; 7 | use shortcut_assert_fs::TmpFs; 8 | 9 | #[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] 10 | #[native_model(id = 1, version = 1)] 11 | #[native_db] 12 | struct ItemA { 13 | #[primary_key] 14 | id: u32, 15 | } 16 | 17 | #[tokio::test] 18 | async fn watch_one_primary_key() { 19 | let tf = TmpFs::new().unwrap(); 20 | 21 | let mut models = Models::new(); 22 | models.define::().unwrap(); 23 | let db = Builder::new() 24 | .create(&models, tf.path("test").as_std_path()) 25 | .unwrap(); 26 | 27 | let a = ItemA { id: 1 }; 28 | 29 | let (mut recv, _) = db.watch().get().primary::(a.id).unwrap(); 30 | 31 | let tx = db.rw_transaction().unwrap(); 32 | tx.insert(a.clone()).unwrap(); 33 | tx.commit().unwrap(); 34 | 35 | for _ in 0..1 { 36 | let inner_event: ItemA = if let Event::Insert(event) = recv.recv().await.unwrap() { 37 | event.inner().unwrap() 38 | } else { 39 | panic!("wrong event") 40 | }; 41 | assert_eq!(inner_event, a); 42 | } 43 | assert!(recv.try_recv().is_err()); 44 | } 45 | 46 | // TODO: maybe do others tests but it should the same as a std::sync::mpsc::channel. 47 | -------------------------------------------------------------------------------- /version_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # How to use: `./version_update.sh 0.8.0` 4 | 5 | # How to test: 6 | # - Use docker `docker run -it --rm -v $(pwd):/mnt/native_db ubuntu bash` 7 | # - `/mnt/native_db` 8 | # - `./version_update.sh 0.8.0` 9 | 10 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 11 | 12 | # Bash script to update version for native_db and native_db_macro 13 | 14 | # Semantic release version obtained from argument 15 | NEW_VERSION=$1 16 | 17 | # Exit if NEW_VERSION is not set 18 | if [ -z "$NEW_VERSION" ]; then 19 | echo "NEW_VERSION argument not set" 20 | exit 1 21 | fi 22 | 23 | # Directories containing Cargo.toml files to update 24 | declare -a directories=("." "native_db_macro") 25 | 26 | for directory in "${directories[@]}" 27 | do 28 | # Check if Cargo.toml and README.md exist 29 | if [ -f "$directory/Cargo.toml" ] && [ -f "$directory/README.md" ]; then 30 | echo "Updating version in $directory/Cargo.toml to $NEW_VERSION" 31 | # Use sed to find and replace the version string in the Cargo.toml 32 | sed -i -E "s/^version = \"[0-9]+\.[0-9]+\.[0-9]+\"/version = \"$NEW_VERSION\"/g" "$directory/Cargo.toml" 33 | 34 | # Update the dependency version for native_db_macro in native_db's Cargo.toml 35 | if [ "$directory" == "." ]; then 36 | sed -i -E "s/native_db_macro = \{ version = \"[0-9]+\.[0-9]+\.[0-9]+\", path = \"native_db_macro\" \}/native_db_macro = { version = \"$NEW_VERSION\", path = \"native_db_macro\" }/g" "$directory/Cargo.toml" 37 | 38 | # Extract native_model version from Cargo.toml 39 | NATIVE_MODEL_VERSION=$(grep -oP '(?<=native_model = \{ version = ")[^"]*' "$directory/Cargo.toml") 40 | echo "Updating native_model version in $directory/Cargo.toml to $NATIVE_MODEL_VERSION" 41 | 42 | # Use sed to find and replace the version string in the README.md 43 | sed -i -E "s/native_db = \"[0-9]+\.[0-9]+\.[0-9]+\"/native_db = \"$NEW_VERSION\"/g" "$directory/README.md" 44 | sed -i -E "s/native_model = \"[0-9]+\.[0-9]+\.[0-9]+\"/native_model = \"$NATIVE_MODEL_VERSION\"/g" "$directory/README.md" 45 | 46 | # Replace on src/metadata/current_version.rs: const CURRENT_VERSION: &str = "x.x.x"; 47 | sed -i -E "s/pub const CURRENT_VERSION: \&str = \"[0-9]+\.[0-9]+\.[0-9]+\";/pub const CURRENT_VERSION: \&str = \"$NEW_VERSION\";/g" "$directory/src/metadata/current_version.rs" 48 | # Replace on src/metadata/current_native_model_version.rs: const CURRENT_NATIVE_MODEL_VERSION: &str = "x.x.x"; 49 | sed -i -E "s/pub const CURRENT_NATIVE_MODEL_VERSION: \&str = \"[0-9]+\.[0-9]+\.[0-9]+\";/pub const CURRENT_NATIVE_MODEL_VERSION: \&str = \"$NATIVE_MODEL_VERSION\";/g" "$directory/src/metadata/current_native_model_version.rs" 50 | 51 | # Replace on tests/metadata/current_version.rs: assert_eq!(metadata.current_native_model_version(), "x.x.x"); 52 | sed -i -E "s/assert_eq!\(metadata.current_native_model_version(), \"[0-9]+\.[0-9]+\.[0-9]+\";/assert_eq!\(metadata.current_native_model_version(), \"$NATIVE_MODEL_VERSION\";/g" "$directory/tests/metadata/current_version.rs" 53 | # Replace on tests/metadata/current_version.rs: assert_eq!(metadata.current_native_model_version(), Some("x.x.x")); 54 | sed -i -E "s/assert_eq!\(metadata.current_native_model_version(), Some\(\"[0-9]+\.[0-9]+\.[0-9]+\";/assert_eq!\(metadata.current_native_model_version(), Some\(\"$NATIVE_MODEL_VERSION\";/g" "$directory/tests/metadata/current_version.rs" 55 | fi 56 | fi 57 | done 58 | 59 | 60 | cd "$DIR/" 61 | 62 | # Commit 63 | git commit --all --message "chore: update version to $NEW_VERSION" 64 | git push --------------------------------------------------------------------------------