#sql #orm #in-memory #table #schema

rql

An in-memory pseudo database that provides functionality for managing pseudo-relational tables and their schema

7 releases (4 breaking)

✓ Uses Rust 2018 edition

0.5.1 Sep 5, 2019
0.5.0 Aug 21, 2019
0.4.0 Aug 1, 2019
0.3.0 Aug 1, 2019
0.1.0 Apr 16, 2019

#241 in Database interfaces

Download history 84/week @ 2019-12-08 7/week @ 2019-12-22 21/week @ 2020-01-12 7/week @ 2020-01-19 8/week @ 2020-02-09 7/week @ 2020-02-16 21/week @ 2020-02-23 1/week @ 2020-03-01 14/week @ 2020-03-08 7/week @ 2020-03-15 35/week @ 2020-03-22

77 downloads per month

MIT license

36KB
923 lines

Description

rql is an in-memory pseudo database for Rust. It provides functionality for managing pseudo-relational tables and their schema.

For more information, check out the documentation.


lib.rs:

Description

RQL (Rusty Query Language) is a library design to bring sql-like logic to Rust. However, bear in mind that there is no traditional database here, just some traits and adapters applied to iterators and hashmaps.

Important Note

rql does not provide a real database. All data is stored in memory. This pseudo-database can be saved to and loaded from the disk via serialization and deserialization from serde. A number of serialization protocols are supported so that you may choose one to suit your speed, size, and backward-compatibility needs.

Schema

To use an rql database, you must first define some schema. Every table is defined by a struct representing a single entry. In this example, we will define three tables: User, Group, and Member.

use rql::prelude::*;

#[derive(Serialize, Deserialize)]
struct User {
name: String,
age: u8,
}

#[derive(Serialize, Deserialize)]
struct Group {
name: String,
}

#[derive(Serialize, Deserialize)]
struct Member {
user_id: Id<User>,
group_id: Id<Group>,
permission: bool,
}

Unique id fields are not necessary, as every entry is automatically given a unique identifier. References to entries in other tables are denoted with Id<T>.

To make the actual schema, use the schema! macro:

# use rql::prelude::*;
# #[derive(Serialize, Deserialize)]
# struct User {
#     name: String,
#     age: u8,
# }
# #[derive(Serialize, Deserialize)]
# struct Group {
#     name: String,
# }
# #[derive(Serialize, Deserialize)]
# struct Member {
#     user_id: Id<User>,
#     group_id: Id<Group>,
#     permission: bool,
# }
schema! {
MySchema {
user: User,
group: Group,
member: Member,
}
}

Database operations

Below are a few simple ways of interfacing with the database.

# use rql::prelude::*;
# #[derive(Serialize, Deserialize)]
# struct User {
#     name: String,
#     age: u8,
# }
# #[derive(Serialize, Deserialize)]
# struct Group {
#     name: String,
# }
# #[derive(Serialize, Deserialize)]
# struct Member {
#     user_id: Id<User>,
#     group_id: Id<Group>,
#     permission: bool,
# }
# schema! {
#     MySchema {
#         user: User,
#         group: Group,
#         member: Member,
#     }
# }

# let _ = std::fs::remove_dir_all("test_database_example");

// Create a new database with the previously defined schema
// We pass a folder name for the database files as well as a representation type
let db = MySchema::new("test_database_example", HumanReadable).unwrap();

// Insert values into the database
// Insertion returns the new row's id
let dan   = db.user_mut().insert(User { name: "Dan".into(),   age: 25 });
let steve = db.user_mut().insert(User { name: "Steve".into(), age: 39 });
let mary  = db.user_mut().insert(User { name: "Mary".into(),  age: 31 });

let admin  = db.group_mut().insert(Group { name: "Admin".into()       });
let normal = db.group_mut().insert(Group { name: "Normal User".into() });

db.member_mut().insert(Member { user_id: dan,   group_id: admin,  permission: true  });
db.member_mut().insert(Member { user_id: steve, group_id: normal, permission: true  });
db.member_mut().insert(Member { user_id: mary,  group_id: normal, permission: false });

// Data can easily be looked up by id
db.user_mut().get_mut(dan).unwrap().age += 1;
let dan_age = db.user().get(dan).unwrap().age;
assert_eq!(dan_age, 26);

// Data can be selected from a table
let ages: Vec<u8> = db.user().select(|user| user.age).collect();

// Use `wher` to filter entries
let can_run_for_president: Vec<String> =
db.user()
.wher(|user| user.age >= 35)
.select(|user| user.name.clone())
.collect();

// Table intersections are done using `relate`
// A function relating the tables is required
for (user, permission) in db.user()
.relate(
&*db.member(),
|user, member| user.id == member.user_id && member.group_id == normal
)
.select(|(user, member)| (&user.data.name, member.permission)) {
println!("{} is a normal user with permission = {}", user, permission);
}

// Rows can be updated with `update`
for mut user in db.user_mut().update() {
user.age += 1;
}

// Rows can be deleted in a few ways

// By id
db.user_mut().delete_one(steve);

// With a where clause
db.member_mut().delete_where(|member| member.permission);

// With an iterator over ids
db.user_mut().delete_iter(|_| vec![dan, mary]);

// Changes to the database are automatically saved, so they can be loaded again
let db_copy = MySchema::new("test_database_example", HumanReadable).unwrap();
assert_eq!(db.user().len(),   db_copy.user().len()  );
assert_eq!(db.group().len(),  db_copy.group().len() );
assert_eq!(db.member().len(), db_copy.member().len());

Dependencies

~2.5MB
~52K SLoC