Git Product home page Git Product logo

mimir's Introduction

Build Status Github Stars MIT License

Mimir Banner

A batteries-included NoSQL database for Dart & Flutter based on an embedded Meilisearch instance.


Features

  • 🔎 Typo tolerant and relevant full-text search with no extra configuration needed
  • 🔥 Blazingly fast search and reads (written in Rust)
  • 🤝 Flutter friendly with a super easy-to-use API (see demo below!)
  • 🔱 Powerful, declarative, and reactive queries
  • 🔌 Cross-platform support (web hopefully coming soon!)
  • 🉑️ Diverse language support, including CJK, Hebrew, and more!

Getting Started

  • With Flutter, run flutter pub add mimir flutter_mimir
  • For Dart-only, run dart pub add mimir

For macOS, disable "App Sandbox". Also read the caveats below.

Demo

With Flutter, you can get started with as little as:

// Get an "index" to store our movies
final instance = await Mimir.defaultInstance;
final index = instance.getIndex('movies');

// Add movies to our index
await index.addDocuments(myMovies);

// Perform a search!
final results = await index.search(query: 'jarrassic par'); // returns Jurassic Park!

Demo Video

Sponsors

You can become a sponsor of my work here!

Reference Documentation

A collection of commonly used APIs ready for copy-paste into your application.

Note: unless otherwise stated, all asynchronous methods exposed in Mimir are fallible and synchronous methods are infallible. The methods are fail-fast, so you should be aware of any issues early on during development.

Getting & Creating an Index

// With Flutter (flutter_mimir)
final instance = await Mimir.defaultInstance;

// Dart-only (just mimir)
final instance = await Mimir.getInstance(
  path: instanceDirectory,
  // Following line will change based on your platform
  library: DynamicLibrary.open('libembedded_milli.so'),
); 

// Get an index (creates one lazily if not already created)
final index = instance.getIndex('movies');

// Or, specify some default settings and open the index eagerly
// If you have some settings you want to specify in advance, use openIndex!
final index = await instance.openIndex('movies', primaryKey: 'CustomIdField');

Configuring an Index

await index.updateSettings(...); // see setSettings below for arguments
final currSettings = await index.getSettings();
await index.setSettings(currSettings.copyWith(
  // The primary key (PK) is the "ID field" of documents added to mimir.
  // When null, it is automatically inferred for you, but sometimes you may
  // need to specify it manually. See the Important Caveats section for more.
  primaryKey: null,
  // Fields in documents that are included in full-text search.
  // Use null, the default, to search all fields
  searchableFields: <String>[],
  // Fields in documents that can be queried/filtered by.
  // You probably don't need to change this; it is automatically
  // updated for you.
  filterableFields: <String>[],
  // Fields in documents that can be sorted by in searches/queries.
  // You probably don't need to change this; it is automatically
  // updated for you.
  sortableFields: <String>[],
  // The ranking rules of this index, see:
  // https://docs.meilisearch.com/reference/api/settings.html#ranking-rules
  rankingRules: <String>[],
  // The stop words of this index, see:
  // https://docs.meilisearch.com/reference/api/settings.html#stop-words
  stopWords: <String>[],
  // A list of synonyms to link words with the same meaning together.
  // Note: in most cases, you probably want to add synonyms both ways, like below:
  synonyms: <Synonyms>[
    Synonyms(
      word: 'automobile',
      synonyms: ['vehicle'],
    ),
    Synonyms(
      word: 'vehicle',
      synonyms: ['automobile'],
    ),
  ],
  // Whether to enable typo tolerance in searches.
  typosEnabled: true,
  // The minimum size of a word that can have 1 typo.
  // See minWordSizeForTypos.oneTypo here:
  // https://docs.meilisearch.com/reference/api/settings.html#typo-tolerance-object
  minWordSizeForOneTypo: 5,
  // The minimum size of a word that can have 2 typos.
  // See minWordSizeForTypos.twoTypos here:
  // https://docs.meilisearch.com/reference/api/settings.html#typo-tolerance-object
  minWordSizeForTwoTypos: 9,
  // Words that disallow typos. See disableOnWords here:
  // https://docs.meilisearch.com/reference/api/settings.html#typo-tolerance-object
  disallowTyposOnWords: <String>[],
  // Fields that disallow typos. See disableOnAttributes here:
  // https://docs.meilisearch.com/reference/api/settings.html#typo-tolerance-object
  disallowTyposOnFields: <String>[],
));

Manipulating Documents

// Adding documents (replaces any documents with the same id)
await index.addDocument(document);
await index.addDocuments(documents);

// Replacing all documents
await index.setDocuments(documents);

// Deleting documents
await index.deleteDocument(id);
await index.deleteDocuments(ids);
await index.deleteAllDocuments();

// Getting documents (not querying--see next section!)
final docOrNull = await index.getDocument(someId);
final allDocs = await index.getAllDocuments();
final allDocsStream = index.documents;

Searching/Querying

// Getting a stream of results (very useful in Flutter!)
// Same arguments as index.search; see below
final documentsStream = index.searchStream(...);

// Performing a search/query (using movies here)!
final movies = index.search(
  // The string to use for full-text search. Can be user-supplied.
  // To do a regular database query without full-text search, leave this null.
  query: 'some wordz with typoes to saerch for',
  // The filter used to filter results in a full-text search or query.
  // See the next section; this is a very handy feature in mimir.
  // Set to null to not filter out any documents.
  filter: Mimir.where('director', isEqualTo: 'Alfred Hitchcock'),
  // The fields to sort by (in ascending or descending order).
  // Can be left as null to sort by relevance (to the query text)!
  sortBy: [
    // Sort by year, newest to oldest
    SortBy.desc('year'),
    // In case 2+ documents share the same year, sort by increasing profit next
    SortBy.asc('profit'),
  ],
  // If you want to limit the number of results you get, use the resultsLimit.
  // Defaults to null, which means return all matches.
  resultsLimit: null,
  // Defaults to null, see https://docs.meilisearch.com/reference/api/search.html#matching-strategy
  matchingStrategy: null,
);

Filtering Search/Query Results

There is a "raw" filtering API in mimir provided by Filter, but it is recommended to use the following API that creates Filters for you instead.

Here are the methods you need to be aware of:

  • Mimir.or(subFilters) creates an "or" filter (like ||) of the sub-filters
  • Mimir.and(subFilters) creates an "and" filter (like &&) of the sub-filters
  • Mimir.not(subFilter) creates a "not" filter (like !someCondition) of the sub-filter
  • Mimir.where(condition) creates a single filter from a given condition.
  • The above can be composed together to create powerful, declarative queries!

Heres an example that shows these methods in practice.

Say our Dart boolean logic is (formatted to show intent):

(
    (
        (movie['fruit'] == 'apple')
        &&
        (movie['year'] >= 2000 && movie['year'] <= 2009)
    )
    ||
    movie['colors'].any((color) => {'red', 'green'}.contains(color))
)

Then our "raw" filter API logic would be:

final filter = Filter.or([
  Filter.and([
    Filter.equal(field: 'fruit', value: 'apple'),
    Filter.between(field: 'year', from: '2000', to: '2009'),
  ]),
  Filter.inValues(field: 'colors', values: ['red', 'green']),
])

Which is somewhat hard to read.

Here's what the recommended approach would look like:

final filter = Mimir.or([
  Mimir.and([
    Mimir.where('fruit', isEqualTo: 'apple'),
    Mimir.where('year', isBetween: ('2000', '2009')),
  ]),
  Mimir.where('colors', containsAtLeastOneOf: ['red', 'green']),
])

I think most would agree that this is the easiest of the three to understand, as it almost reads as pure English, even for complex conditions.

Important Caveats

Please read these caveats before adding mimir to your project.

  • Every document in mimir needs to have a "primary key"
    • The PK is automatically inferred via a field ending in id (or simply just id)
    • If you have multiple fields ending in id, use instance.openIndex('indexName', primaryKey: 'theActualId')
    • The contents of the PK field can be a number, or a string matching the regex ^[a-zA-Z0-9-_]*$
      • In English: PKs can be alphanumeric and contain - and _
  • Unfortunately, you can only open 1 index on iOS devices at the moment; see here for more details and a workaround.
  • macOS App Sandbox is not supported at the moment, meaning you will not be able to submit apps to the Mac App Store
    • You will still be able to distribute macOS applications on your own
    • See more details here
  • Resource usage
    • While modern devices run mimir just fine, several thousand detailed documents can easily consume several MB of disk space and RAM
    • This is due to Milli, a heavy-weight core component of Meilisearch, which gives mimir a lot of its power
    • If you do not need all the features provided by mimir, also consider a more lightweight alternative!
      • Hive for simple key-value storage
      • Isar for more sophisticated use-cases
        • Note: while Isar does have full-text search, it is neither typo-tolerant nor relevant!
      • If you need easy, typo-tolerant, relevant full-text search, you will want mimir!
        • I am unaware of any other databases that currently provide typo-tolerant full-text search in Flutter, which is why I made mimir in the first place!
    • Mimir can add a couple hundred MB to your app bundle size and <100 MB to the on-device size
      • These numbers will hopefully be reduced in the future once Dart gets "Native Assets"

mimir's People

Contributors

dependabot[bot] avatar github-actions[bot] avatar gregoryconrad avatar mimiractionsbot avatar rorystephenson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

mimir's Issues

Add Linux/Windows support

Consider removing Future requirement for getInstance and getIndex

This is essentially a debate on whether to eagerly or lazily initialize instances/indices.

Pros:

  • Users will not have to deal with async for the main API points, which (as-is):
    • Is often frustrating to have to account for when writing main()
    • Increases time to first frame (for Flutter)
  • Rust implementation already ensures initialization on every method call (by starting with ensureFoobarInitialized)

Cons:

  • If async is needed in the future (but probably not?), that would require a substantial breaking change
  • Users will not know if there's an issue with an instance/index until it is lazily initialized
    • But this should be clearly visible during testing?
  • Using RwLocks instead of Mutex means that two diff function calls could try to initialize an uninitialized instance/index at the same time, which would either:
    • Cause a dead lock (worse case, if one of the read locks aren't dropped) (edit: fixed here)
    • Reinitialize a just-initialized lock (edit: fixed here)

Fix map_size based on the target platform

Calculate the max map size based on the target platform.

Use the largest multiple of 2^24 (should be a multiple of any OS page size) that is less than usize::MAX so that more storage can be used on both 32 bit & 64 bit platforms.

Pseudocode:

const MAX_SUPPORTED_PAGE_SIZE: usize = 2^24;
const MAP_SIZE: usize = usize::MAX - usize::MAX % MAX_SUPPORTED_PAGE_SIZE;

Can't get instance.

on the initial Mimir.getInstance command I should provide libembedded_milli.so file. However, can't find it anywhere.
Pretty sure this is not the issue in package. But, unfortunately, cannot solve it by myself. On the readme page no info on that.

Running tests fails - possible config issue

Hi, I'm trying to use mimir to better understand the guide you wrote for flutter_rust_bridge.

I am facing an issue which I can replicate with mimir:

atom@Dell-Desktop:~$ git clone https://github.com/GregoryConrad/mimir && cd mimir && melos clean && melos bootstrap && melos run test
Cloning into 'mimir'...
remote: Enumerating objects: 2609, done.
remote: Counting objects: 100% (576/576), done.
remote: Compressing objects: 100% (318/318), done.
remote: Total 2609 (delta 350), reused 322 (delta 257), pack-reused 2033
Receiving objects: 100% (2609/2609), 3.42 MiB | 10.97 MiB/s, done.
Resolving deltas: 100% (1364/1364), done.
Cleaning workspace...

Workspace cleaned. You will need to run the bootstrap command again to use this workspace.
melos bootstrap
  └> /home/atom/mimir

Running "flutter pub get" in workspace packages...
  ✓ mimir
    └> packages/mimir
  ✓ example
    └> packages/mimir/example
  ✓ flutter_mimir
    └> packages/flutter_mimir
  ✓ flutter_example
    └> packages/flutter_mimir/example
  > SUCCESS

Generating IntelliJ IDE files...
  > SUCCESS

 -> 4 packages bootstrapped
melos run test
  └> melos run test:dart --no-select && melos run test:flutter --no-select
     └> RUNNING

melos run test:dart
  └> melos exec -c 1 --fail-fast -- "dart test test"
     └> RUNNING

$ melos exec
  └> dart test test
     └> RUNNING (in 2 packages)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
example:
00:01 +0 -1: test/example_test.dart: Main function runs correctly [E]
  Invalid argument(s): Failed to load dynamic library '../../../target/release/libembedded_milli.so': ../../../target/release/libembedded_milli.so: cannot open shared object file: No such file or directory
  dart:ffi                             new DynamicLibrary.open
  package:example/example.dart 136:25  getLibrary
  package:example/example.dart 16:28   main


To run this test again: /home/atom/snap/flutter/common/flutter/bin/cache/dart-sdk/bin/dart test test/example_test.dart -p vm --plain-name 'Main function runs correctly'
00:01 +0 -1: Some tests failed.

Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

$ melos exec
  └> dart test test
     └> FAILED (in 1 packages)
        └> example (with exit code 1)

melos run test:dart
  └> melos exec -c 1 --fail-fast -- "dart test test"
     └> FAILED
ScriptException: The script test:dart failed to execute


melos run test
  └> melos run test:dart --no-select && melos run test:flutter --no-select
     └> FAILED
ScriptException: The script test failed to execute

As you can see, this test fails because the shared library cannot be found.

According to the guide you wrote, the shared library binaries are distributed through GitHub Releases, so I did not build them before running the tests. If I do build the shared library however, the result is similar:

Click to expand
atom@Dell-Desktop:~$ git clone https://github.com/GregoryConrad/mimir && cd mimir && melos clean && melos bootstrap && bash scripts/build-other.sh && melos run test
Cloning into 'mimir'...
remote: Enumerating objects: 2609, done.
remote: Counting objects: 100% (542/542), done.
remote: Compressing objects: 100% (311/311), done.
remote: Total 2609 (delta 315), reused 295 (delta 230), pack-reused 2067
Receiving objects: 100% (2609/2609), 3.42 MiB | 11.60 MiB/s, done.
Resolving deltas: 100% (1358/1358), done.
Cleaning workspace...

Workspace cleaned. You will need to run the bootstrap command again to use this workspace.
melos bootstrap
  └> /home/atom/mimir

Running "flutter pub get" in workspace packages...
  ✓ example
    └> packages/mimir/example
  ✓ mimir
    └> packages/mimir
  ✓ flutter_mimir
    └> packages/flutter_mimir
  ✓ flutter_example
    └> packages/flutter_mimir/example
  > SUCCESS

Generating IntelliJ IDE files...
  > SUCCESS

 -> 4 packages bootstrapped
    Updating crates.io index
     Ignored package `cargo-zigbuild v0.14.2` is already installed, use --force to override
    Updating crates.io index
     Ignored package `cargo-xwin v0.13.3` is already installed, use --force to override
info: component 'rust-std' for target 'aarch64-unknown-linux-gnu' is up to date
    Updating crates.io index
    Updating git repository `https://github.com/GregoryConrad/milli`
    Updating git repository `https://github.com/meilisearch/heed`
    Updating git repository `https://github.com/meilisearch/lmdb-rs`
   Compiling proc-macro2 v1.0.49
   Compiling quote v1.0.23
   Compiling unicode-ident v1.0.6
...
SNIP
...
   Compiling lindera v0.14.0
   Compiling charabia v0.6.0
   Compiling milli v0.37.0 (https://github.com/GregoryConrad/milli?branch=filter-parser-convenience#50954d31)
    Finished release [optimized] target(s) in 2m 11s
info: component 'rust-std' for target 'x86_64-unknown-linux-gnu' is up to date
   Compiling libc v0.2.139
   Compiling cfg-if v1.0.0
   Compiling serde v1.0.151
...
SNIP
...
   Compiling lindera v0.14.0
   Compiling charabia v0.6.0
   Compiling milli v0.37.0 (https://github.com/GregoryConrad/milli?branch=filter-parser-convenience#50954d31)
    Finished release [optimized] target(s) in 1m 22s
info: component 'rust-std' for target 'aarch64-pc-windows-msvc' is up to date
   Compiling cfg-if v1.0.0
   Compiling serde v1.0.151
   Compiling winapi v0.3.9
...
SNIP
...
   Compiling lindera v0.14.0
   Compiling charabia v0.6.0
   Compiling milli v0.37.0 (https://github.com/GregoryConrad/milli?branch=filter-parser-convenience#50954d31)
    Finished release [optimized] target(s) in 1m 27s
info: component 'rust-std' for target 'x86_64-pc-windows-msvc' is up to date
   Compiling cfg-if v1.0.0
   Compiling serde v1.0.151
   Compiling winapi v0.3.9
...
SNIP
...
   Compiling lindera v0.14.0
   Compiling charabia v0.6.0
   Compiling milli v0.37.0 (https://github.com/GregoryConrad/milli?branch=filter-parser-convenience#50954d31)
    Finished release [optimized] target(s) in 1m 27s
linux-arm64/
linux-arm64/libembedded_milli.so
linux-x64/
linux-x64/libembedded_milli.so
windows-arm64/
windows-arm64/embedded_milli.dll
windows-x64/
windows-x64/embedded_milli.dll
melos run test
  └> melos run test:dart --no-select && melos run test:flutter --no-select
     └> RUNNING

melos run test:dart
  └> melos exec -c 1 --fail-fast -- "dart test test"
     └> RUNNING

$ melos exec
  └> dart test test
     └> RUNNING (in 2 packages)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
example:
00:01 +0 -1: test/example_test.dart: Main function runs correctly [E]
  Invalid argument(s): Failed to load dynamic library '../../../target/release/libembedded_milli.so': ../../../target/release/libembedded_milli.so: cannot open shared object file: No such file or directory
  dart:ffi                             new DynamicLibrary.open
  package:example/example.dart 136:25  getLibrary
  package:example/example.dart 16:28   main


To run this test again: /home/atom/snap/flutter/common/flutter/bin/cache/dart-sdk/bin/dart test test/example_test.dart -p vm --plain-name 'Main function runs correctly'
00:02 +0 -1: Some tests failed.

Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

$ melos exec
  └> dart test test
     └> FAILED (in 1 packages)
        └> example (with exit code 1)

melos run test:dart
  └> melos exec -c 1 --fail-fast -- "dart test test"
     └> FAILED
ScriptException: The script test:dart failed to execute


melos run test
  └> melos run test:dart --no-select && melos run test:flutter --no-select
     └> FAILED
ScriptException: The script test failed to execute

I would appreciate it if you could give any advice on any debugging steps I could take to set up my system to run passing tests from this repository.

System information

Click to expand

I am running Ubuntu Linux via WSL.

Windows

C:\Users\Atom>ver

Microsoft Windows [Version 10.0.19045.2364]

WSL

C:\Users\Atom>wsl --version
WSL version: 1.0.3.0
Kernel version: 5.15.79.1
WSLg version: 1.0.47
MSRDC version: 1.2.3575
Direct3D version: 1.606.4
DXCore version: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windows version: 10.0.19045.2364

Linux Kernel

atom@Dell-Desktop:~$ uname -a
Linux Dell-Desktop 5.15.79.1-microsoft-standard-WSL2 #1 SMP Wed Nov 23 01:01:46 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

Flutter

atom@Dell-Desktop:~$ flutter doctor -v
[✓] Flutter (Channel stable, 3.3.10, on Ubuntu 22.04.1 LTS 5.15.79.1-microsoft-standard-WSL2, locale C.UTF-8)
    • Flutter version 3.3.10 on channel stable at /home/atom/snap/flutter/common/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 135454af32 (9 days ago), 2022-12-15 07:36:55 -0800
    • Engine revision 3316dd8728
    • Dart version 2.18.6
    • DevTools version 2.15.0

[✗] Android toolchain - develop for Android devices
    ✗ Unable to locate Android SDK.
      Install Android Studio from: https://developer.android.com/studio/index.html
      On first launch it will assist you in installing the Android SDK components.
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).
      If the Android SDK has been installed to a custom location, please use
      `flutter config --android-sdk` to update to that location.


[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[✓] Linux toolchain - develop for Linux desktop
    • clang version 10.0.0-4ubuntu1
    • cmake version 3.16.3
    • ninja version 1.10.0
    • pkg-config version 0.29.1

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).

[✓] Connected device (1 available)
    • Linux (desktop) • linux • linux-x64 • Ubuntu 22.04.1 LTS 5.15.79.1-microsoft-standard-WSL2

[✓] HTTP Host Availability
    • All required HTTP hosts are available

! Doctor found issues in 3 categories.

Flutter Snap

(this is how Flutter and Dart are installed on my system)

atom@Dell-Desktop:~$ snap info flutter
name:      flutter
summary:   Flutter SDK
publisher: Flutter Team✓
store-url: https://snapcraft.io/flutter
contact:   https://github.com/flutter/flutter/issues
license:   unset
description: |
  Flutter is Google’s UI toolkit for building beautiful, natively compiled
  applications for mobile, web, and desktop from a single codebase.
commands:
  - flutter.dart
  - flutter
  - flutter.openurl
snap-id:      YO0Adf4fqgxL7i3SaCn00oxEfd5CNQ63
tracking:     latest/stable
refresh-date: yesterday at 17:27 GMT
channels:
  latest/stable:    0+git.ccbc698 2022-10-20 (130) 215MB classic
  latest/candidate: ↑
  latest/beta:      ↑
  latest/edge:      0+git.74565fa 2022-11-29 (133) 216MB classic
installed:          0+git.ccbc698            (130) 215MB classic

Rust

atom@Dell-Desktop:~$ rustup --version
rustup 1.25.1 (bb60b1e89 2022-07-12)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.66.0 (69f9c33d7 2022-12-12)`

Unable to get it to work on New Flutter application

Hey I'm trying to use mimir in my Flutter project. I just added the following code and run on macOS

import 'package:flutter/material.dart';
import 'package:flutter_mimir/flutter_mimir.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Mimir.defaultInstance;
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(),
    );
  }
}

When I run, immediately get the following error

Exception has occurred.
FfiException (FfiException(RESULT_ERROR, Operation not permitted (os error 1), null))

The error is thrown in the getInstance function of the package

Future<MimirInstance> getInstance({
    required String path,
    required ExternalLibrary library,
  }) async {
    _milli ??= createWrapperImpl(library);
    await _milli!
        .ensureInstanceInitialized(instanceDir: path, tmpDir: tmpDir());
    return _instances.putIfAbsent(
      path,
      () => MimirInstanceImpl(path, _milli!),
    );
  }

Flutter Doctor

[✓] Flutter (Channel stable, 3.10.5, on macOS 13.4 22F66 darwin-arm64, locale en-NP)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.3.1)

Autogenerate the document `Id`s

Hey, I was looking at your README. The project looks really cool!
I was just wondering about this part in the important caveats:

Document ids:
- Documents without an id field will have one autogenerated (out of necessity)
- I would recommend adding an id field to your documents when possible though
- Current bug in milli: you need your id field at the top of your documents
See meilisearch/product#206 for more

From what I understand, Mimir is going to check if there is already an id in the documents, and if the document doesn't have one, then it's going to autogenerate an id.
Thus, in any case, your primary key is going to be id. Why don't you just specify it to meilisearch, so it doesn't try to infer anything?

`FfiException(RESULT_ERROR, Permission denied (os error 13), null)` on Android

When I run the current Flutter example (0.0.1-dev.8) from this repo on an Android 12 device I get this error:
"[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: FfiException(RESULT_ERROR, Permission denied (os error 13), null)"

The full log looks like this:

Launching lib/main.dart on GM1913 in debug mode...
Running Gradle task 'assembleDebug'...
✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app-debug.apk...
I/flutter_exampl(29161): Late-enabling -Xcheck:jni
E/flutter_exampl(29161): Unknown bits set in runtime_flags: 0x40000000
W/flutter_example(29161): type=1400 audit(0.0:21546): avc: denied { read } for name="max_map_count" dev="proc" ino=2695311 scontext=u:r:untrusted_app:s0:c9,c266,c512,c768 tcontext=u:object_r:proc_max_map_count:s0 tclass=file permissive=0 app=com.example.flutter_example
W/flutter_exampl(29161): Accessing hidden method Landroid/view/accessibility/AccessibilityNodeInfo;->getSourceNodeId()J (unsupported,test-api, reflection, allowed)
W/flutter_exampl(29161): Accessing hidden method Landroid/view/accessibility/AccessibilityRecord;->getSourceNodeId()J (unsupported, reflection, allowed)
W/flutter_exampl(29161): Accessing hidden field Landroid/view/accessibility/AccessibilityNodeInfo;->mChildNodeIds:Landroid/util/LongArray; (unsupported, reflection, allowed)
W/flutter_exampl(29161): Accessing hidden method Landroid/util/LongArray;->get(I)J (unsupported, reflection, allowed)
E/flutter_exampl(29161): ofbOpen failed with error=No such file or directory
E/flutter_exampl(29161): sysOpen failed with error=No such file or directory
Debug service listening on ws://127.0.0.1:60657/4Atkq9oWN-E=/ws
Syncing files to device GM1913...
D/ExtensionsLoader(29161): createInstance(64bit) : createExtendedFactory
D/ExtensionsLoader(29161): Opened libSchedAssistExtImpl.so
D/ExtensionsLoader(29161): createInstance(64bit) : createExtendedFactory
D/ExtensionsLoader(29161): Opened libSchedAssistExtImpl.so
V/OplusZoomWindowDecorViewHelper(29161): setLastReportedMergedConfiguration mZoomDisplayHeight: 3120 getDecorView.204776305
D/hw-ProcessState(29161): Binder ioctl to enable oneway spam detection failed: Invalid argument
D/ViewRootImpl[MainActivity](29161):  debugCancelDraw some OnPreDrawListener onPreDraw return false,cancelDraw=true,count=50,android.view.ViewRootImpl@8b44e8c
D/SurfaceComposerClient(29161): VRR [FRTC] client handle [bufferId:18446744073709551615 framenumber:0] [ffffffff, ffffffff]
D/SurfaceComposerClient(29161): VRR [FRTC] client handle [bufferId:18446744073709551615 framenumber:0] [ffffffff, ffffffff]
D/ViewRootImpl[MainActivity](29161):  debugCancelDraw  cancelDraw=false,count = 143,android.view.ViewRootImpl@8b44e8c
E/flutter (29161): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: FfiException(RESULT_ERROR, Permission denied (os error 13), null)
E/flutter (29161): #0      FlutterRustBridgeBase._transformRust2DartMessage (package:flutter_rust_bridge/src/basic.dart:129:9)
E/flutter (29161): #1      FlutterRustBridgeBase.executeNormal.<anonymous closure> (package:flutter_rust_bridge/src/basic.dart:70:9)
E/flutter (29161): <asynchronous suspension>
E/flutter (29161): #2      MimirIndexImpl.addDocuments (package:mimir/src/impl/index_impl.dart:28:5)
E/flutter (29161): <asynchronous suspension>
E/flutter (29161): 
E/BLASTBufferQueue(29161): BLASTBufferItemConsumer::onDisconnect()
E/BLASTBufferQueue(29161): BLASTBufferItemConsumer::onDisconnect()

Delete documents by filter from Meilisearch 1.2

How should un-filterable fields be handled when used to delete?

  1. Trigger a reindex to run delete (seems like a bad option)
  2. Error out and say that this needs to be manually added in index settings (think this might be better)

Web support (theoretical)

Web support for mimir is currently not possible due to the lack of a publicly-available libc implementation for WASM made with JS & browser APIs. This concept of a browser-based libc sounds weird, because it is, but it isn't actually too bad; keep reading to see why.

A libc implementation via web APIs is theoretically possible (and has been done in some capacity!), as seen with https://webvm.io. WebVM was created with CheerpX and is written about in more detail here.

Considering an entire VM runs natively in the browser already using an in-progress libc implementation, and that VM itself isn't terribly slow, I see promise for the possibility of web support for this library (and many others) in the not-so distant future. (Again, this all relies on an open-source libc implementation that can be used by Rust.)

libcs for the web are already available to differing degrees of completeness. Now it is just a matter of being able to use them.

Cannot allocate memory (os error 12)

The exception occurs on an iPhone 7 Plus device, iOS 15.7.7

FfiException(RESULT_ERROR, Cannot allocate memory (os error 12), null)
flutter: #0 FlutterRustBridgeBase._transformRust2DartMessage (package:flutter_rust_bridge/src/basic.dart:129:9)
flutter: #1 FlutterRustBridgeBase.executeNormal. (package:flutter_rust_bridge/src/basic.dart:70:9)
flutter:
flutter: #2 MimirInterface.getInstance (package:mimir/src/interface.dart:44:5)
flutter:

In an iPhone 14 pro max, it happens when creating a second index.

Create per-instance configuration options

Concept taken from #9

These instance configuration options will be stored in a file, named milli_instance_settings.json, which will reside in the instance directory alongside the index directories. It can have a format similar to the following:

{
  "someDefaultSetting": "someDefaultValue",
  "indices": {
    "someUserGivenIndexName": {
      "milliVersion": "v1",
    }
  }
}

Example should not depend on other packages

Hi, I just looked into the example and it seems to utilize rearch, another one of your packages.
I am not really in the liberty to judge how your repository should work, but it might put off people to using this package, especially while using a undocumented one.

Add Android support

2 possible ways:

  • .aar way (seems to be better) and distribute online
    • reference this dependency from build.gradle
  • CMake way
    • probably a nightmare with all the .a files

Ios run error

flutter_mimir: ^0.0.2

Flutter: Flutter 3.10.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f468f3366c (8 weeks ago) • 2023-07-12 15:19:05 -0700
Engine • revision cdbeda788a
Tools • Dart 3.0.6 • DevTools 2.23.1

Dart: Dart SDK version: 3.0.7 (stable) (Mon Jul 24 13:17:56 2023 +0000) on "macos_arm64"

Error:
Error (Xcode): Undefined symbol: _drop_dart_object

Error (Xcode): Undefined symbol: _free_WireSyncReturn

Error (Xcode): Undefined symbol: _get_dart_object

Error (Xcode): Undefined symbol: _inflate_Filter_And

Error (Xcode): Undefined symbol: _inflate_Filter_Between

Error (Xcode): Undefined symbol: _inflate_Filter_Equal

Error (Xcode): Undefined symbol: _inflate_Filter_Exists

Error (Xcode): Undefined symbol: _inflate_Filter_GreaterThan

Error (Xcode): Undefined symbol: _inflate_Filter_GreaterThanOrEqual

Error (Xcode): Undefined symbol: _inflate_Filter_InValues

Error (Xcode): Undefined symbol: _inflate_Filter_IsEmpty

Error (Xcode): Undefined symbol: _inflate_Filter_IsNull

Error (Xcode): Undefined symbol: _inflate_Filter_LessThan

Error (Xcode): Undefined symbol: _inflate_Filter_LessThanOrEqual

Error (Xcode): Undefined symbol: _inflate_Filter_Not

Error (Xcode): Undefined symbol: _inflate_Filter_NotEqual

Error (Xcode): Undefined symbol: _inflate_Filter_Or

Error (Xcode): Undefined symbol: _inflate_SortBy_Asc

Error (Xcode): Undefined symbol: _inflate_SortBy_Desc

Error (Xcode): Undefined symbol: _new_StringList_0

Error (Xcode): Undefined symbol: _new_box_autoadd_filter_0

Error (Xcode): Undefined symbol: _new_box_autoadd_mimir_index_settings_0

Error (Xcode): Undefined symbol: _new_box_autoadd_terms_matching_strategy_0

Error (Xcode): Undefined symbol: _new_box_autoadd_u32_0

Error (Xcode): Undefined symbol: _new_box_filter_0

Error (Xcode): Undefined symbol: _new_dart_opaque

Error (Xcode): Undefined symbol: _new_list_filter_0

Error (Xcode): Undefined symbol: _new_list_sort_by_0

Error (Xcode): Undefined symbol: _new_list_synonyms_0

Error (Xcode): Undefined symbol: _new_uint_8_list_0

Error (Xcode): Undefined symbol: _store_dart_post_cobject

Error (Xcode): Undefined symbol: _wire_add_documents

Error (Xcode): Undefined symbol: _wire_delete_all_documents

Error (Xcode): Undefined symbol: _wire_delete_documents

Error (Xcode): Undefined symbol: _wire_ensure_index_initialized

Error (Xcode): Undefined symbol: _wire_ensure_instance_initialized

Error (Xcode): Undefined symbol: _wire_get_all_documents

Error (Xcode): Undefined symbol: _wire_get_document

Error (Xcode): Undefined symbol: _wire_get_settings

Error (Xcode): Undefined symbol: _wire_number_of_documents

Error (Xcode): Undefined symbol: _wire_search_documents

Error (Xcode): Undefined symbol: _wire_set_documents

Error (Xcode): Undefined symbol: _wire_set_settings

Could not build the application for the simulator.
Error launc
2023-09-08_17-59-52
hing application on iPhone 14 Pro Max.

Auto increment the index map_size when run out of space

Right now, there is a hard default of 2^24 bytes for each index (let default_map_size = 16_777_216;), which should accommodate most indices. However, this is suboptimal for two reasons:

  1. Uses up far too much memory on simpler datasets
  2. Cannot be expanded to accommodate larger datasets

I'm not sure how meilisearch handles this (I will look into it later), but an initial thought is the following:

  1. Start each new index off with 2^17 (131072) or 2^18 (262144) bytes in the map call. I think these are reasonable starting values that should accommodate a lot of datasets while still not using an excessive amount of RAM (0.1 and 0.3 mb respectively).
  2. Whenever an add operation fails because the database runs out of memory, close the index and reopen it with 2x the previous memory. This can be seen as the opposite of exponential backoff.

This solution means there will be a performance hit when adding a huge batch of documents at once, but I think that the good this approach brings outweighs that performance hit.

Dart 3 (Macros & Native Assets) Migration

Planned Dart 3 (Macros) Migration

  • Update & add docs
  • Update & add to README
  • Update & add tests
  • Add new examples with macros

mimir

  • Rename MimirIndex to RawMimirIndex? Maybe we could add a type alias and deprecate one?
  • TypedMimirIndex (created in macros): class TypedMimirIndex<T> {…}
    • An adapter around RawMimirIndex that handles JSON conversions
  • Macro that defines extension on MimirInstance that has a TypedMimirIndex<ClassName> get classNameIndex for a certain user-defined ClassName
    • Instead of depending on a JSON package, ideally we can just check for the existence of toMap/toJson and fromMap/fromJson in macros placed on ClassName
  • Macro that adds static where method(s) on each user-defined class for easier & type-safe queries (supporting String, number, and boolean).
    • E.g., for @mimir class Movie {...}, we could have something like Mimir.and([Movie.whereTitleIsEqualTo('Forrest Gump'), Movie.whereYearIsBetween(2000, 2010)]) or Mimir.and([Mimir.where(titleIsEqualTo: 'Forrest Gump'), Mimir.where(yearIsBetween: (2000, 2010))])
    • Instead of just one where method, which would be nice/concise, I think it would be better to add several functions for each field. Reason being: isBetween requires an and parameter, which cannot be type-safe. Also, multiple methods for each field forces the method use to be checked at compile time instead of runtime (say, if a user specifies both isEqualTo and isGreaterThan inside the same .where())
    • Actually, revisiting this, perhaps we can make the isBetween field a tuple when records/patterns are supported in Dart 3. Although we will still have the issue where you can have multiple things defined in the same where call, perhaps we can add a few where variants: whereOr & whereAnd, which break down their members into Mimir.or and Mimir.and for convenience.
  • Macro that adds SortBy generators for each field
    • Proposed API: Movie.sortByAsc.title and Movie.sortByDesc.year, for example, would simply return SortBy.asc('title') and SortBy.desc('year')
  • Native Assets instead of custom packaging system in flutter_mimir

Note: there will be no extension methods placed on user-defined classes (i.e. create(), updateWith(), delete()) in plain mimir because there is no way to know the associated instance.
While we could add our own _mimirInstance field to user-defined classes, this approach has numerous issues.

flutter_mimir

  • Remove re-export of mimir, and update README with so
    • Not great to re-export right now anyways, but mimir API bindings rely on a particular binary, so flutter_mimir has to enforce this by including a bundled version.
  • defaultMimirInstance macro that adds methods to user-defined classes
    • I am inclined to do this as it is super convenient; however, I feel like it might open people up to some anti-patterns (using static members instead of DI)
      • Think we can come to good compromise by stating dangers in documentation
    • Possible because we can use the defaultInstance directly via Mimir.defaultInstance
    • create(), updateWith(…), delete() methods planned
      • create() can probably just call updateWith(...) for its implementation
    • static search(), searchStream(), documents, ... to each class as static as well

Add updateSettings method

getSettings and setSettings work fine but it would be easier to just expose an updateSettings method

Explanation of capabilities

Hi, just wanted to clarify if I can do the same things that millisearch does such as parse CJK in this package?
If so, I think it would be a much greater option to what is available in other SQL libraries.

Also would like to know if you think this library is production ready or if there are glaring issues right now

Switch to redb backend

Having the LMDB backend from milli is the root of a lot of problems (#10, #101, #227); we need a redb polyfill over in heed.

Unfortunately, I won't be able to get around to this for quite some time. If you know Rust, or want to learn some, I'd really appreciate some help here. I'd be more than happy to help guide you on what to do to get this landed.

ci: automate release creation

Here's what I am thinking:

  • Manually invoked GitHub action on macOS
    • I don't want it running whenever main is updated
  • Gets Melos to do a version bump based on previous git tags (melos version)
  • Builds binaries for all platforms melos run build, if we can cross-compile to all from macOS
  • Creates tag for new release
  • Moves major version tag (e.g. v0, v1, etc.) to the just-created tag
  • Publishes new release to GitHub with the needed binaries
  • Publishes new release to pub.dev

Consider adding automatic filterable fields

This would require the following changes:

  1. In search, catch FfiExceptions that specify 1+ fields are not indexed for filtering
  2. Update the index settings to also include (append to the end of the list) those fields passed into the function under filter as filterable fields
  3. Rerun the search and return the new result

Pros:

  • API is dead simple. Nobody would need to think about manually adding fields to filter

Cons:

  • Requires reindexing. However, with setSettings as-is, calling it at every start up will already re-index everything

ID issue

As stated in the readme:

Every document in mimir needs to have a field ending in id (or simply just id)
If you have multiple fields ending in id, please open an issue so we can discuss

I have id field, but also categoryId, rootId, createdById, etc.
In my case it'd be enough if mimir first checked for an id field and only then continues search for fields ending in id.
But for configurability reasons it's better to clearly specify id field during documents import.

Clean up the repo

  • README
    • Image(s)
    • Example usage
  • Version numbers
  • Package descriptions
  • Changelogs

Consider supporting macOS App Sandbox

Right now, we are getting sandbox violations from POSIX semaphores not following the proper naming scheme.

See here:

POSIX semaphores and shared memory names must begin with the application group identifier, followed by a slash (/), followed by a name of your choosing.

Currently they are /MDB.... This issue really sucks because I looked briefly in the LMDB source and did not really see great support for customizing POSIX semaphore prefixes as-is. There is some MUTEXNAME_PREFIX macro, but it really doesn't allow for customization as far as I can tell. This would require submitting an issue to LMDB directly and see what they recommend.

Note: The maximum length of a POSIX semaphore name is only 31 bytes, so if you need to use POSIX semaphores, you should keep your app group names short.

Another yippee.

To learn more about application groups, read The Application Group Container Directory, then read Adding an Application to an Application Group in Entitlement Key Reference.

...And this would require library-users to go through plenty of hoops during compilation and to change entitlements.

Crash only on physical iOS/iPadOS devices

Crash

Crash not replicable on macOS (sandbox off), or in Simulator. Relevant part of crash:

Thread 19 name:  frb_workerpool
Thread 19 Crashed:
0   libsystem_kernel.dylib        	       0x1e27b8cfc semget + 8
1   flutter_mimir                 	       0x10b33f084 mdb_env_setup_locks + 372
2   flutter_mimir                 	       0x10b33ed2c mdb_env_open + 336
3   flutter_mimir                 	       0x10af40444 heed::env::EnvOpenOptions::open::hc7263e6fd266de76 + 1228
4   flutter_mimir                 	       0x10af7a624 embedded_milli::embedded_milli::ensure_instance_initialized::hf763e83f9633c12a + 1164
5   flutter_mimir                 	       0x10af7a90c embedded_milli::embedded_milli::ensure_index_initialized::h53b307f61f6c8011 + 48
6   flutter_mimir                 	       0x10af7b5d4 embedded_milli::embedded_milli::add_documents::h50fd6c2e5d7b38ff + 68
7   flutter_mimir                 	       0x10afc8fc4 embedded_milli::api::add_documents::haf1943972aabc139 + 116
8   flutter_mimir                 	       0x10af54dc4 std::panicking::try::h450c0f89f6339be7 + 180
9   flutter_mimir                 	       0x10af45840 _$LT$F$u20$as$u20$threadpool..FnBox$GT$::call_box::h5c5ee30a5525b974 + 88
10  flutter_mimir                 	       0x10b278410 std::sys_common::backtrace::__rust_begin_short_backtrace::h84efbdd5c73f968d + 360
11  flutter_mimir                 	       0x10b27ddf8 core::ops::function::FnOnce::call_once$u7b$$u7b$vtable.shim$u7d$$u7d$::hf32b2315de981118 + 136
12  flutter_mimir                 	       0x10b2c248c std::sys::unix::thread::Thread::new::thread_start::hbc52c4a0aa5ba80b + 36
13  libsystem_pthread.dylib       	       0x1f2ec66cc _pthread_start + 148
14  libsystem_pthread.dylib       	       0x1f2ec5ba4 thread_start + 8

Specifically:

0   libsystem_kernel.dylib        	       0x1e27b8cfc semget + 8
1   flutter_mimir                 	       0x10b33f084 mdb_env_setup_locks + 372

Related

https://openldap-technical.openldap.narkive.com/HPIr7bYG/crash-with-lmdb-on-ios

https://developer.apple.com/forums/thread/674207

https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24

Possible Fix

Fix C compilation

First off, we simply can't use System V semaphores as per Apple's documentation.
When compiling mdb.c, we are currently hitting this branch (at least on iOS):

#elif defined(MDB_USE_SYSV_SEM)

We need to hit this branch instead:

#elif defined(MDB_USE_POSIX_SEM)

This applies to all Apple platforms since macOS might be sandboxed too.

Thus, we will need to see if we can change the C compilation as-is, or whether we will need to submit a PR to heed to use POSIX semaphores instead (or at least add a Rust/Cargo feature for it).

Entitlement Changes

As per Apple's documentation, we may need to do some additional work with Application Group/entitlements. I have zero clue what an Application Group is right now so that will be something to look into once the first part of the fix is done, assuming that doesn't fix the issue entirely.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.