Initial database implementation

+ Using sled as a database
+ Added functions for inserting and fetching models from the database
main
Joey Hines 2021-03-10 18:31:50 -06:00
parent a3f25f214c
commit 0bcb28bc47
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
19 changed files with 551 additions and 24 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
.idea
database/

View File

@ -8,6 +8,8 @@
The database is the core storage of Geoffrey. All models will be stored here. It is tightly
coupled with the GeoffreyAPI.
![DB Architecture](figures/db_arch.svg)
## Geoffrey API
The API provides the main logic to how models are manipulated in the Database. It is split
into two parts, the Command API and Model API.

186
Cargo.lock generated
View File

@ -6,6 +6,24 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
@ -20,6 +38,70 @@ dependencies = [
"winapi",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "geoffrey_db"
version = "0.1.0"
dependencies = [
"byteorder",
"geoffrey_models",
"lazy_static",
"serde",
"serde_json",
"sled",
]
[[package]]
name = "geoffrey_models"
version = "0.1.0"
@ -29,18 +111,60 @@ dependencies = [
"serde_json",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a"
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memoffset"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
dependencies = [
"autocfg",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -60,6 +184,31 @@ dependencies = [
"autocfg",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
@ -78,12 +227,27 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.123"
@ -115,6 +279,28 @@ dependencies = [
"serde",
]
[[package]]
name = "sled"
version = "0.34.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot",
]
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "syn"
version = "1.0.61"

View File

@ -1,4 +1,5 @@
[workspace]
members = [
"geoffrey_models"
"geoffrey_models",
"geoffrey_db"
]

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,17 @@
[package]
name = "geoffrey_db"
version = "0.1.0"
authors = ["Joey Hines <joey@ahines.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sled = "0.34.6"
serde = "1.0"
serde_json = "1.0"
geoffrey_models = { path = "../geoffrey_models" }
byteorder = "1.4.2"
[dev-dependencies]
lazy_static = "1.4.0"

View File

@ -0,0 +1,91 @@
use crate::error::Result;
use std::path::Path;
use geoffrey_models::GeoffreyDatabaseModel;
use crate::u64_to_bytes;
pub struct Database {
db: sled::Db,
}
impl Database {
pub fn new(db_path: &Path) -> Result<Self> {
let db = sled::open(db_path)?;
Ok(Self {
db,
})
}
fn get_tree<T>(&self) -> Result<sled::Tree> where T: GeoffreyDatabaseModel {
Ok(self.db.open_tree::<String>(T::tree())?)
}
pub fn insert<'a, T>(&self, mut model: T) -> Result<T> where T: GeoffreyDatabaseModel {
let id = self.db.generate_id()?;
let tree = self.get_tree::<T>()?;
let data = serde_json::to_vec(&model).unwrap();
let id_bytes = u64_to_bytes(id);
tree.insert(id_bytes, data)?;
model.set_id(id);
Ok(model)
}
pub fn get<T>(&self, id: u64) -> Result<Option<T>> where T: GeoffreyDatabaseModel {
let tree = self.get_tree::<T>()?;
let id_bytes = u64_to_bytes(id);
if let Some(bytes) = tree.get(id_bytes)? {
Ok(Some(T::try_from_bytes(&bytes).unwrap()))
}
else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use crate::database::{Database};
use std::path::Path;
use geoffrey_models::models::player::{Player, UserID};
use lazy_static::lazy_static;
lazy_static! {
static ref DB: Database = Database::new(Path::new("../database")).unwrap();
}
fn cleanup() {
DB.db.clear().unwrap();
}
#[test]
fn test_insert() {
let player = Player::new("CoolZero123", UserID::DiscordUUID(0));
let p2 = DB.insert::<Player>(player.clone()).unwrap();
assert!(p2.id.is_some());
assert_eq!(player.name, p2.name);
cleanup();
}
#[test]
fn test_get() {
let player = Player::new("CoolZero123", UserID::DiscordUUID(0));
let p2 = DB.insert::<Player>(player.clone()).unwrap();
let p3 = DB.get::<Player>(p2.id.unwrap()).unwrap();
assert!(p3.is_some());
assert_eq!(p3.unwrap().name, player.name);
cleanup();
}
}

View File

@ -0,0 +1,31 @@
pub type Result<T> = std::result::Result<T, GeoffreyDBError>;
#[derive(Debug)]
pub enum GeoffreyDBError {
SledError(sled::Error),
SerdeJsonError(serde_json::Error),
}
impl std::error::Error for GeoffreyDBError {}
impl std::fmt::Display for GeoffreyDBError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GeoffreyDBError::SledError(e) => write!(f, "Sled Error: {}", e),
GeoffreyDBError::SerdeJsonError(e) => write!(f, "Serde JSON Error: {}", e)
}
}
}
impl From<sled::Error> for GeoffreyDBError {
fn from(e: sled::Error) -> Self {
GeoffreyDBError::SledError(e)
}
}
impl From<serde_json::Error> for GeoffreyDBError {
fn from(e: serde_json::Error) -> Self {
GeoffreyDBError::SerdeJsonError(e)
}
}

View File

@ -0,0 +1,11 @@
use byteorder::{WriteBytesExt, BigEndian};
pub mod database;
pub mod error;
pub fn u64_to_bytes(n: u64) -> Vec<u8> {
let mut id_bytes= vec![0u8; 8];
id_bytes.write_u64::<BigEndian>(n).unwrap();
id_bytes
}

View File

@ -1,3 +1,17 @@
#[allow(dead_code)]
use serde::{Serialize};
use serde::de::DeserializeOwned;
pub mod models;
pub trait GeoffreyDatabaseModel: Serialize + DeserializeOwned {
fn id(&self) -> Option<u64>;
fn set_id(&mut self, id: u64);
fn tree() -> String;
fn try_from_bytes(v: &[u8]) -> Result<Self, serde_json::Error> {
Ok(serde_json::from_slice::<Self>(v)?)
}
}

View File

@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};
use crate::models::item::Item;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FarmData {
pub items_produced: Vec<Item>
}

View File

@ -5,3 +5,11 @@ pub struct Item {
pub name: String,
}
impl Item {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string()
}
}
}

View File

@ -0,0 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MarketData {
pub shops: Vec<u64>,
}

View File

@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
use crate::GeoffreyDatabaseModel;
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Meta {
id: Option<u64>,
version: u64,
database_version: u64,
}
impl GeoffreyDatabaseModel for Meta {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id);
}
fn tree() -> String {
"meta".to_string()
}
}

View File

@ -1,19 +1,28 @@
use serde::{Deserialize, Serialize};
use crate::models::player::Player;
use crate::models::shop::ShopData;
use crate::models::town::TownData;
use crate::models::market::MarketData;
use crate::models::farm::FarmData;
use crate::GeoffreyDatabaseModel;
pub mod player;
pub mod shop;
pub mod item;
pub mod town;
pub mod farm;
pub mod market;
pub mod token;
mod meta;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum LocationData {
Base,
Shop(ShopData),
Town,
Market,
Town(TownData),
Market(MarketData),
Attraction,
PublicFarm,
PublicFarm(FarmData),
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
@ -52,7 +61,7 @@ pub struct Tunnel {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Location {
id: u64,
id: Option<u64>,
name: String,
position: Position,
owners: Vec<Player>,
@ -61,11 +70,10 @@ pub struct Location {
}
impl Location {
fn new (id: u64, name: &str, position: Position, owners: Vec<Player>, tunnel: Option<Tunnel>, loc_data: LocationData) -> Self {
pub fn new (name: &str, position: Position, owners: Vec<Player>, tunnel: Option<Tunnel>, loc_data: LocationData) -> Self {
Location {
id,
id: None,
name: name.to_string(),
position,
owners,
@ -75,6 +83,20 @@ impl Location {
}
}
impl GeoffreyDatabaseModel for Location {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id);
}
fn tree() -> String {
"location".to_string()
}
}
#[cfg(test)]
mod tests {
use crate::models::{Location, Position, Dimension, LocationData};
@ -82,8 +104,8 @@ mod tests {
#[test]
fn test_new_base() {
let player = Player {name: "CoolZero123".to_string(), user_ids: vec![UserID::DiscordUUID(0)]};
Location::new(0, "test", Position {x: 0, y: 0, dimension: Dimension::Overworld}, vec![player], None, LocationData::Base);
let player = Player::new("CoolZero123", UserID::DiscordUUID(0));
Location::new("test", Position {x: 0, y: 0, dimension: Dimension::Overworld}, vec![player], None, LocationData::Base);
}
}

View File

@ -1,14 +1,39 @@
use serde::{Deserialize, Serialize};
use crate::GeoffreyDatabaseModel;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum UserID {
DiscordUUID(u64),
MinecraftUUID(String),
GeoffreyID(u64)
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Player {
pub id: Option<u64>,
pub name: String,
pub user_ids: Vec<UserID>
}
impl Player {
pub fn new(name: &str, user_id: UserID) -> Self {
Self {
id: None,
name: name.to_string(),
user_ids: vec![user_id]
}
}
}
impl GeoffreyDatabaseModel for Player {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id);
}
fn tree() -> String {
"player".to_string()
}
}

View File

@ -27,16 +27,3 @@ pub struct ShopData {
pub item_listings: Vec<ItemListing>
}
impl ShopData {
fn add_item(&mut self, item_listing: ItemListing) {
self.item_listings.push(item_listing);
}
fn filter_items<F>(&self, filter: F) -> Vec<ItemListing> where F: FnMut(&&ItemListing) -> bool {
self.item_listings.iter().filter(filter).cloned().collect()
}
fn remove_item(&mut self, item_name: &str) {
self.item_listings.retain(|listing| &listing.item.name != item_name)
}
}

View File

@ -0,0 +1,84 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use crate::GeoffreyDatabaseModel;
pub enum Permissions {
ModelGet = 0,
ModelPost,
Command,
ModCommand,
Admin,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Token {
pub id: Option<u64>,
permission: u64,
pub created: DateTime<Utc>,
pub modified: DateTime<Utc>
}
impl Token {
pub fn new() -> Self {
Self {
id: None,
permission: 0,
created: Utc::now(),
modified: Utc::now()
}
}
pub fn set_permission(&mut self, permission: Permissions) {
self.permission = self.permission | (1u64 << permission as u32);
}
pub fn clear_permission(&mut self, permission: Permissions) {
self.permission = self.permission & !(1u64 << permission as u32);
}
pub fn check_permission(&self, permission: Permissions) -> bool {
(self.permission >> permission as u32) & 0x1u64 == 0x1u64
}
pub fn clear_all_permission(&mut self) {
self.permission = 0x0u64;
}
}
impl GeoffreyDatabaseModel for Token {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id);
}
fn tree() -> String {
"token".to_string()
}
}
impl PartialEq for Token {
fn eq(&self, other: &Self) -> bool {
self.permission == other.permission
}
}
#[cfg(test)]
mod tests {
use crate::models::token::{Token, Permissions};
#[test]
fn test_token() {
let mut token = Token::new(0);
token.set_permission(Permissions::ModelGet);
assert_eq!(token.permission, 0x1u64);
token.set_permission(Permissions::ModelPost);
assert_eq!(token.permission, 0x3u64);
}
}

View File

@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
pub struct TownData {
pub resident: Vec<u64>
}