use diesel as sqlite orm & setup db init schedule

This commit is contained in:
appflowy 2021-07-09 16:34:50 +08:00
parent ae23a41445
commit 8bf355f956
25 changed files with 363 additions and 2 deletions

View File

@ -16,6 +16,8 @@
<sourceFolder url="file://$MODULE_DIR$/scripts/flowy-tool/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-test/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-user/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-db/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/rust-lib/flowy-infra/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/.pub" />
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/.dart_tool" />
<excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/af_protobuf/build" />

View File

@ -8,6 +8,8 @@ members = [
"flowy-ast",
"flowy-derive",
"flowy-test",
"flowy-infra",
"flowy-db",
]
[profile.dev]

1
rust-lib/flowy-db/.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=/tmp/database.sql

View File

@ -0,0 +1,12 @@
[package]
name = "flowy-db"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = {version = "1.4.7", features = ["sqlite"]}
diesel_derives = {version = "1.4.1", features = ["sqlite"]}
diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
flowy-infra = {path = "../flowy-infra"}

View File

@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

View File

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE user_table;

View File

@ -0,0 +1,8 @@
-- Your SQL goes here
CREATE TABLE user_table (
id TEXT NOT NULL PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
password TEXT NOT NULL DEFAULT '',
email TEXT NOT NULL DEFAULT ''
);

View File

@ -0,0 +1,17 @@
use crate::errors::FlowyDBError;
use diesel_migrations::*;
use flowy_infra::sqlite::*;
use std::path::Path;
embed_migrations!("../flowy-db/migrations/");
pub const DB_NAME: &str = "flowy-database.db";
pub fn init(storage_path: &str) -> Result<DataBase, FlowyDBError> {
if !Path::new(storage_path).exists() {
std::fs::create_dir_all(storage_path)?;
}
let pool_config = PoolConfig::default();
let database = DataBase::new(storage_path, DB_NAME, pool_config)?;
Ok(database)
}

View File

@ -0,0 +1,16 @@
use flowy_infra::Error;
use std::io;
#[derive(Debug)]
pub enum FlowyDBError {
InitError(String),
IOError(String),
}
impl std::convert::From<flowy_infra::Error> for FlowyDBError {
fn from(error: flowy_infra::Error) -> Self { FlowyDBError::InitError(format!("{:?}", error)) }
}
impl std::convert::From<io::Error> for FlowyDBError {
fn from(error: io::Error) -> Self { FlowyDBError::IOError(format!("{:?}", error)) }
}

View File

@ -0,0 +1,15 @@
mod database;
mod errors;
mod schema;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_derives;
#[macro_use]
extern crate diesel_migrations;
pub use flowy_infra::sqlite::DataBase;
pub use database::init;
pub use errors::*;

View File

@ -0,0 +1,8 @@
table! {
user_table (id) {
id -> Text,
name -> Text,
password -> Text,
email -> Text,
}
}

View File

@ -0,0 +1,16 @@
[package]
name = "flowy-infra"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
r2d2 = "0.8.9"
diesel = {version = "1.4.7", features = ["sqlite"]}
diesel_derives = {version = "1.4.1", features = ["sqlite"]}
diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
lazy_static = "1.4.0"
scheduled-thread-pool = "0.2.5"
error-chain = "=0.12.0"
log = "0.4.11"

View File

@ -0,0 +1,22 @@
use error_chain::{
error_chain,
error_chain_processing,
impl_error_chain_kind,
impl_error_chain_processed,
impl_extract_backtrace,
};
error_chain! {
errors {
UnknownMigrationExists(v: String) {
display("unknown migration version: '{}'", v),
}
}
foreign_links {
R2D2(::r2d2::Error);
Migrations(::diesel_migrations::RunMigrationsError);
Diesel(::diesel::result::Error);
Connection(::diesel::ConnectionError);
Io(::std::io::Error);
}
}

View File

@ -0,0 +1,13 @@
#[allow(deprecated, clippy::large_enum_variant)]
mod errors;
pub mod sqlite;
pub use errors::{Error, ErrorKind, Result};
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@ -0,0 +1,36 @@
use crate::{
errors::*,
sqlite::pool::{ConnectionManager, ConnectionPool, PoolConfig},
};
use r2d2::PooledConnection;
pub struct DataBase {
uri: String,
pool: ConnectionPool,
}
impl DataBase {
pub fn new(dir: &str, name: &str, pool_config: PoolConfig) -> Result<Self> {
let uri = db_file_uri(dir, name);
let pool = ConnectionPool::new(pool_config, &uri)?;
Ok(Self { uri, pool })
}
pub fn get_uri(&self) -> &str { &self.uri }
pub fn get_conn(&self) -> Result<PooledConnection<ConnectionManager>> {
let conn = self.pool.get()?;
Ok(conn)
}
}
pub fn db_file_uri(dir: &str, name: &str) -> String {
use std::path::MAIN_SEPARATOR;
let mut uri = dir.to_owned();
if !uri.ends_with(MAIN_SEPARATOR) {
uri.push(MAIN_SEPARATOR);
}
uri.push_str(name);
uri
}

View File

@ -0,0 +1,5 @@
mod database;
mod pool;
pub use database::*;
pub use pool::*;

View File

@ -0,0 +1,134 @@
use crate::errors::*;
use diesel::{connection::Connection, SqliteConnection};
use r2d2::{ManageConnection, Pool};
use scheduled_thread_pool::ScheduledThreadPool;
use std::{
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
time::Duration,
};
lazy_static::lazy_static! {
static ref DB_POOL: Arc<ScheduledThreadPool> = Arc::new(
ScheduledThreadPool::with_name("db-pool-{}:", 4)
);
}
pub struct ConnectionPool {
pub(crate) inner: Pool<ConnectionManager>,
}
impl std::ops::Deref for ConnectionPool {
type Target = Pool<ConnectionManager>;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl ConnectionPool {
pub fn new<T>(config: PoolConfig, uri: T) -> Result<Self>
where
T: Into<String>,
{
let manager = ConnectionManager::new(uri);
let thread_pool = DB_POOL.clone();
let config = Arc::new(config);
let pool = r2d2::Pool::builder()
.thread_pool(thread_pool)
.min_idle(Some(config.min_idle))
.max_size(config.max_size)
.max_lifetime(None)
.connection_timeout(config.connection_timeout)
.idle_timeout(Some(config.idle_timeout))
.build_unchecked(manager);
Ok(ConnectionPool { inner: pool })
}
}
#[derive(Default, Debug, Clone)]
pub struct ConnCounter(Arc<ConnCounterInner>);
impl std::ops::Deref for ConnCounter {
type Target = ConnCounterInner;
fn deref(&self) -> &Self::Target { &*self.0 }
}
#[derive(Default, Debug)]
pub struct ConnCounterInner {
max_number: AtomicUsize,
current_number: AtomicUsize,
}
impl ConnCounterInner {
pub fn get_max_num(&self) -> usize { self.max_number.load(SeqCst) }
pub fn reset(&self) {
// reset max_number to current_number
let _ = self
.max_number
.fetch_update(SeqCst, SeqCst, |_| Some(self.current_number.load(SeqCst)));
}
}
pub type OnExecFunc = Box<dyn Fn() -> Box<dyn Fn(&SqliteConnection, &str)> + Send + Sync>;
pub struct PoolConfig {
min_idle: u32,
max_size: u32,
connection_timeout: Duration,
idle_timeout: Duration,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
min_idle: 1,
max_size: 10,
connection_timeout: Duration::from_secs(10),
idle_timeout: Duration::from_secs(5 * 60),
}
}
}
impl PoolConfig {
#[allow(dead_code)]
pub fn min_idle(mut self, min_idle: u32) -> Self {
self.min_idle = min_idle;
self
}
#[allow(dead_code)]
pub fn max_size(mut self, max_size: u32) -> Self {
self.max_size = max_size;
self
}
}
pub struct ConnectionManager {
db_uri: String,
}
impl ManageConnection for ConnectionManager {
type Connection = SqliteConnection;
type Error = crate::Error;
fn connect(&self) -> Result<Self::Connection> {
if !std::path::PathBuf::from(&self.db_uri).exists() {
log::error!("db file not exists");
}
Ok(SqliteConnection::establish(&self.db_uri)?)
}
fn is_valid(&self, conn: &mut Self::Connection) -> Result<()> {
Ok(conn.execute("SELECT 1").map(|_| ())?)
}
fn has_broken(&self, _conn: &mut Self::Connection) -> bool { false }
}
impl ConnectionManager {
pub fn new<S: Into<String>>(uri: S) -> Self { ConnectionManager { db_uri: uri.into() } }
}

View File

@ -10,6 +10,8 @@ derive_more = {version = "0.99", features = ["display"]}
flowy-dispatch = { path = "../flowy-dispatch" }
flowy-log = { path = "../flowy-log" }
flowy-derive = { path = "../flowy-derive" }
flowy-db = { path = "../flowy-db" }
tracing = { version = "0.1", features = ["log"] }
bytes = "1.0"
serde = { version = "1.0", features = ["derive"] }

View File

@ -0,0 +1,26 @@
use crate::errors::UserError;
use flowy_db::DataBase;
use lazy_static::lazy_static;
use std::sync::{
atomic::{AtomicBool, Ordering},
RwLock,
};
lazy_static! {
pub static ref DB: RwLock<Option<DataBase>> = RwLock::new(None);
}
static DB_INIT: AtomicBool = AtomicBool::new(false);
pub fn init_user_db(dir: &str) -> Result<(), UserError> {
let database = flowy_db::init(dir)?;
*(DB.write()?) = Some(database);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_db_test() { init_user_db(".").unwrap(); }
}

View File

@ -1,3 +1,4 @@
pub use user::*;
mod database;
pub mod user;

View File

@ -1 +0,0 @@

View File

@ -0,0 +1,15 @@
use std::sync::PoisonError;
#[derive(Debug)]
pub enum UserError {
DBInitFail(String),
PoisonError(String),
}
impl std::convert::From<flowy_db::FlowyDBError> for UserError {
fn from(error: flowy_db::FlowyDBError) -> Self { UserError::DBInitFail(format!("{:?}", error)) }
}
impl<T> std::convert::From<PoisonError<T>> for UserError {
fn from(error: PoisonError<T>) -> Self { UserError::PoisonError(format!("{:?}", error)) }
}

View File

@ -1,5 +1,5 @@
mod domain;
mod error;
mod errors;
pub mod event;
mod handlers;
pub mod module;

4
scripts/install_diesel.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
brew install sqlite3
cargo install diesel_cli --no-default-features --features sqlite