Ver código fonte

use diesel as sqlite orm & setup db init schedule

appflowy 3 anos atrás
pai
commit
8bf355f956

+ 2 - 0
.idea/appflowy_client.iml

@@ -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" />

+ 2 - 0
rust-lib/Cargo.toml

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

+ 1 - 0
rust-lib/flowy-db/.env

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

+ 12 - 0
rust-lib/flowy-db/Cargo.toml

@@ -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"}

+ 5 - 0
rust-lib/flowy-db/diesel.toml

@@ -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"

+ 0 - 0
rust-lib/flowy-db/migrations/.gitkeep


+ 2 - 0
rust-lib/flowy-db/migrations/2021-07-09-063045_flowy-user/down.sql

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

+ 8 - 0
rust-lib/flowy-db/migrations/2021-07-09-063045_flowy-user/up.sql

@@ -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 ''
+);

+ 17 - 0
rust-lib/flowy-db/src/database.rs

@@ -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)
+}

+ 16 - 0
rust-lib/flowy-db/src/errors.rs

@@ -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)) }
+}

+ 15 - 0
rust-lib/flowy-db/src/lib.rs

@@ -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::*;

+ 8 - 0
rust-lib/flowy-db/src/schema.rs

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

+ 16 - 0
rust-lib/flowy-infra/Cargo.toml

@@ -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"

+ 22 - 0
rust-lib/flowy-infra/src/errors.rs

@@ -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);
+    }
+}

+ 13 - 0
rust-lib/flowy-infra/src/lib.rs

@@ -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);
+    }
+}

+ 36 - 0
rust-lib/flowy-infra/src/sqlite/database.rs

@@ -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
+}

+ 5 - 0
rust-lib/flowy-infra/src/sqlite/mod.rs

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

+ 134 - 0
rust-lib/flowy-infra/src/sqlite/pool.rs

@@ -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() } }
+}

+ 2 - 0
rust-lib/flowy-user/Cargo.toml

@@ -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"] }

+ 26 - 0
rust-lib/flowy-user/src/domain/database.rs

@@ -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(); }
+}

+ 1 - 0
rust-lib/flowy-user/src/domain/mod.rs

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

+ 0 - 1
rust-lib/flowy-user/src/error.rs

@@ -1 +0,0 @@
-

+ 15 - 0
rust-lib/flowy-user/src/errors.rs

@@ -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)) }
+}

+ 1 - 1
rust-lib/flowy-user/src/lib.rs

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

+ 4 - 0
scripts/install_diesel.sh

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