6 releases

0.1.63 May 28, 2023
0.1.62 May 27, 2023

#20 in Database implementations

Download history 31/week @ 2023-05-14 80/week @ 2023-05-21 55/week @ 2023-05-28

166 downloads per month

Apache-2.0

98KB
2.5K SLoC

Rust

Flinch is an in-memory, real-time document database designed for fast, efficient full-text search and querying. It comes with a built-in full-text search engine that enables both "search-as-you-type" and like search capabilities. Flinch was created with the goal of providing a high-performance search solution that can be integrated into various applications.

Features

  • In-memory database: Flinch stores documents in memory, allowing for ultra-fast search performance.
  • Real-time updates: Flinch supports real-time updates, enabling users to add, update, and delete documents in real-time.
  • Full-text search: Flinch has a built-in full-text search engine that provides powerful search capabilities, including search-as-you-type and wildcard search.
  • Lightweight and easy to use: Flinch is designed to be lightweight and easy to use, with a simple API that allows developers to quickly integrate it into their applications.
  • Document-oriented: Flinch is document-oriented, allowing users to store and retrieve documents as JSON objects.
  • Highly scalable: Flinch is designed to be highly scalable, allowing users to handle large volumes of documents and queries efficiently.
  • Query: Document query faster than ⚡️
  • Open source: Flinch is an open-source project, allowing users to contribute to its development and customize it to suit their needs.

Insertion performance is slow. Indexing takes time and it's quite normal.

How to use

As library

async fn library() {
    let col_opts = CollectionOptions {
        name: Some(COLLECTION.to_string()),
        index_opts: vec![format!("name")],
        search_opts: vec![format!("name")],
        view_opts: vec![ViewConfig{
            prop: "age".to_string(),
            expected: "18".to_string(),
            view_name: "ADULT".to_string(),
        }],
        range_opts: vec![format!("age")],
        clips_opts: vec![format!("name")],
    };
    let database = Database::init();
    database.add(col_opts).expect("created new collection");

    println!("ls Collections {:?}", database.ls());

    let instance = database.using(COLLECTION);
    assert!(instance.is_ok());

    let instance = instance.unwrap();
    let collection = instance.value();
    assert_eq!(collection.len(),0);

    let (sx, mut rx) = tokio::sync::mpsc::channel(30000);
    collection.sub(sx).await.expect("subscribe to channel");

    let insert = Instant::now();
    let record_size = 10_000;
    for i in 0..record_size {
        collection.put(format!("P_{}",&i), QueryBased::from_str(
            serde_json::to_string(
                &User {
                    name: format!("julfikar{}",&i),
                    age: i,
                }
            ).unwrap().as_str()
        ).unwrap()).await.unwrap();
    }
    assert_eq!(collection.len(),record_size as usize);
    println!("insert:: {:?}",insert.elapsed());

    let x = collection.put(format!("P_{}",0), QueryBased::from_str(
        serde_json::to_string(
            &User {
                name: format!("julfikar-replace"),
                age: 10000,
            }
        ).unwrap().as_str()
    ).unwrap()).await;
    assert!(x.is_ok());
    println!("replaced value in {}",x.unwrap());

    let single = collection.get(&format!("P_0"));
    assert!(single.data.is_some());
    println!("single:: {:?}", single.time_taken);

    let multi = collection.multi_get(vec![&format!("P_1"),&format!("P_0")]);
    assert_ne!(multi.data.len(),0);
    println!("multi:: {:?}",multi.time_taken);

    let gidx = collection.get_index("julfikar100");
    assert!(gidx.data.is_some());
    println!("index:: {:?} {:?}",gidx.time_taken,gidx.data.unwrap().1.data);

    let search = collection.search("Julfikar0");
    assert_ne!(search.data.len(),0);
    println!("search index:: {} res {}",search.time_taken, search.data.len());

    let like_search = collection.like_search("Julfikar 101");
    assert_ne!(like_search.data.len(),0);
    println!("search:: {} res {}",like_search.time_taken, like_search.data.len());

    let view = collection.fetch_view("ADULT");
    assert_ne!(view.data.len(),0);
    println!("view:: {} res {}",view.time_taken, view.data.len());

    collection.drop().await;

    assert_eq!(collection.len(),0,"after::drop");

    let mut i = 0;
    loop {
        let event = rx.recv().await.unwrap();
        match event {
            PubSubEvent::Data(d) => {
                match d {
                    ActionType::Insert(k, _v) => {
                        println!("inserted :pub/sub: {}",k);
                    }
                    ActionType::Remove(k) => {
                        println!("removed :: {}",k);
                    }
                };
            }
            PubSubEvent::Subscribed(_s) => {

            }
        };
        i += 1;
        if i == 10 { // for demo, listen till 10 message only
            break;
        }
    }
}

Query example

async fn query_example() {
    let col_opts = CollectionOptions {
        name: Some(COLLECTION.to_string()),
        index_opts: vec![format!("name")],
        search_opts: vec![format!("name")],
        view_opts: vec![ViewConfig{
            prop: "age".to_string(),
            expected: "18".to_string(),
            view_name: "ADULT".to_string(),
        }],
        range_opts: vec![format!("age")],
        clips_opts: vec![format!("name")],
    };
    let options = serde_json::to_string(&col_opts).unwrap();
    let planner = Query::new();
    let res = planner.exec(format!("new({});",options.as_str()).as_str()).await;
    println!("new::collection::error {:?}",res.error);

    let record_size = 2;
    for i in 0..record_size {
        let v = serde_json::to_string(
            &User {
                name: format!("julfikar{}",&i),
                age: i,
            }
        ).unwrap();
        let query = format!("put({}).into('{}');", v, &COLLECTION);
        let x = planner.exec(query.as_str()).await;
        assert_eq!(x.error, FlinchError::None);
    }

    let res = planner.exec(format!("get.when(:map(\"name\") == \"julfikar1\":).from('{}');",&COLLECTION).as_str()).await;
    println!("{:?}",res);

    let res = planner.exec(format!("get.index('julfikar1').from('{}');",&COLLECTION).as_str()).await;
    println!("{:?}",res);

    let res = planner.exec(format!("search.query('julfikar 1').from('{}');",&COLLECTION).as_str()).await;
    println!("{:?}",res);

    let res = planner.exec(format!("search.when(:map(\"age\") == 0:).query('julfikar').from('{}');",&COLLECTION).as_str()).await;
    println!("{:?}",res);
}

Introducing FLQL

Create collection
new({});

Drop collection
drop('');

Check if pointer exists in collection
exists('').into('');

Length of collection
length('');

Update or Insert into collection
put({}).into('');

Conditional Update or Insert into collection
put({}).when(:includes(array_filter('e.f$.g'),2):).into('');

Update or Insert into collection to a Pointer
put({}).pointer('').into('');

Get from collection
get.from('');

Conditional Get from collection
get.when(:includes(array_filter('e.f$.g'),2):).from('');

Get Pointer from collection
get.pointer('').from('');

Get View from collection
get.view('').from('');

Get Clip from collection
get.clip('').from('');

Get index from collection
get.index('').from('');

Get range from collection
get.range(start:'', end:'', on:'').from('');

Search query
search.query('').from('');

Conditional Search query
search.when(#func(args)#).query('').from('');

Delete from collection
delete.from('');

Conditional Delete from collection
delete.when(:includes(array_filter('e.f$.g'),2):).from('');

Delete Pointer from collection
delete.pointer('').from('');

Delete View from collection
delete.view('').from('');

Delete Clip from collection
delete.clip('').from('');

Dependencies

~12–19MB
~327K SLoC