From fc5c855a23008279ec8a45f9b507612829d03be1 Mon Sep 17 00:00:00 2001 From: S-Y-rat Date: Fri, 31 Jul 2020 09:31:08 +0300 Subject: [PATCH 01/19] Sqlite-storage feature added --- .../dialogue/storage/sqlite_storage.rs | 48 +++++++++++++++++++ tests/sqlite.rs | 6 +++ 2 files changed, 54 insertions(+) create mode 100644 src/dispatching/dialogue/storage/sqlite_storage.rs create mode 100644 tests/sqlite.rs diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs new file mode 100644 index 00000000..e497228c --- /dev/null +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -0,0 +1,48 @@ +use super::{serializer::Serializer, Storage}; +use std::{ + convert::Infallible, + fmt::{Debug, Display}, + sync::Arc, +}; +use rusqlite::{params, Connection, Error, Result}; +use serde::{de::DeserializeOwned, Serialize}; +use thiserror::Error; +use tokio::sync::Mutex; + +pub enum SqliteStorageLocation { + InMemory, + Path(String), +} + +// An error returned from [`SqliteStorage`]. +#[derive(Debug, Error)] +pub enum SqliteStorageError +where + SE: Debug + Display, +{ + #[error("parsing/serializing error: {0}")] + SerdeError(SE), + #[error("error from Sqlite: {0}")] + SqliteError(#[from] Error), +} + +pub struct SqliteStorage { + conn: Mutex, + serializer: S, +} + +impl SqliteStorage { + pub async fn open( + path: SqliteStorageLocation, + serializer: S, + ) -> Result, SqliteStorageError>{ + let url = match path { + SqliteStorageLocation::InMemory => String::from("sqlite::memory:"), + SqliteStorageLocation::Path(p) => p, + }; + Ok(Arc::new(Self { + conn: Mutex::new(Connection::open(&url[..])?), + serializer, + })) + } +} diff --git a/tests/sqlite.rs b/tests/sqlite.rs new file mode 100644 index 00000000..786e3c2f --- /dev/null +++ b/tests/sqlite.rs @@ -0,0 +1,6 @@ +use teloxide::dispatching::dialogue::SqliteStorage; + +#[tokio::test] +async fn test_sqlite() { + todo!() +} From d0ab14d593a89d359350af25800f7f0232fc2add Mon Sep 17 00:00:00 2001 From: S-Y-rat Date: Fri, 31 Jul 2020 15:20:27 +0300 Subject: [PATCH 02/19] Make sqlite-storage visible --- Cargo.toml | 2 ++ src/dispatching/dialogue/mod.rs | 3 +++ src/dispatching/dialogue/storage/mod.rs | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a1ee05cc..3297f330 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ authors = [ maintenance = { status = "actively-developed" } [features] +sqlite-storage = ["rusqlite"] redis-storage = ["redis"] cbor-serializer = ["serde_cbor"] bincode-serializer = ["bincode"] @@ -50,6 +51,7 @@ futures = "0.3.5" pin-project = "0.4.22" serde_with_macros = "1.1.0" +rusqlite = { version = "0.23", optional = true } redis = { version = "0.16.0", optional = true } serde_cbor = { version = "0.11.1", optional = true } bincode = { version = "1.3.1", optional = true } diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 420a755b..b88d8425 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -159,4 +159,7 @@ pub use transition::{ #[cfg(feature = "redis-storage")] pub use storage::{RedisStorage, RedisStorageError}; +#[cfg(feature = "sqlite-storage")] +pub use storage::{SqliteStorage, SqliteStorageError}; + pub use storage::{serializer, InMemStorage, Serializer, Storage}; diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index ed5319c8..175ad183 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -5,6 +5,9 @@ mod in_mem_storage; #[cfg(feature = "redis-storage")] mod redis_storage; +#[cfg(feature = "sqlite-storage")] +mod sqlite_storage; + use futures::future::BoxFuture; pub use in_mem_storage::InMemStorage; @@ -13,6 +16,9 @@ pub use redis_storage::{RedisStorage, RedisStorageError}; pub use serializer::Serializer; use std::sync::Arc; +#[cfg(feature = "sqlite-storage")] +pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; + /// A storage of dialogues. /// /// You can implement this trait for a structure that communicates with a DB and From c09eca1e3986e869bf912f15ec8eb165ba4b02e2 Mon Sep 17 00:00:00 2001 From: S-Y-rat Date: Fri, 31 Jul 2020 15:59:26 +0300 Subject: [PATCH 03/19] Switch from rusqlite to sqlx with sqlite feature enabled --- Cargo.toml | 8 ++++++-- .../dialogue/storage/sqlite_storage.rs | 15 +++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3297f330..baaf9910 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ authors = [ maintenance = { status = "actively-developed" } [features] -sqlite-storage = ["rusqlite"] +sqlite-storage = ["sqlx"] redis-storage = ["redis"] cbor-serializer = ["serde_cbor"] bincode-serializer = ["bincode"] @@ -51,7 +51,11 @@ futures = "0.3.5" pin-project = "0.4.22" serde_with_macros = "1.1.0" -rusqlite = { version = "0.23", optional = true } +sqlx = { version = "0.4.0-beta.1", optional = true, default-features = false, features = [ + "runtime-tokio", + "macros", + "sqlite" +] } redis = { version = "0.16.0", optional = true } serde_cbor = { version = "0.11.1", optional = true } bincode = { version = "1.3.1", optional = true } diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index e497228c..7912caa4 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -4,10 +4,13 @@ use std::{ fmt::{Debug, Display}, sync::Arc, }; -use rusqlite::{params, Connection, Error, Result}; +use sqlx::{SqliteConnection, Connection, sqlite::SqliteError}; use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; -use tokio::sync::Mutex; +use tokio::{ + sync::Mutex, + task::block_in_place, +}; pub enum SqliteStorageLocation { InMemory, @@ -23,11 +26,11 @@ where #[error("parsing/serializing error: {0}")] SerdeError(SE), #[error("error from Sqlite: {0}")] - SqliteError(#[from] Error), + SqliteError(#[from] SqliteError), } pub struct SqliteStorage { - conn: Mutex, + conn: Mutex, serializer: S, } @@ -35,13 +38,13 @@ impl SqliteStorage { pub async fn open( path: SqliteStorageLocation, serializer: S, - ) -> Result, SqliteStorageError>{ + ) -> Result, Box>{ let url = match path { SqliteStorageLocation::InMemory => String::from("sqlite::memory:"), SqliteStorageLocation::Path(p) => p, }; Ok(Arc::new(Self { - conn: Mutex::new(Connection::open(&url[..])?), + conn: Mutex::new(SqliteConnection::connect(&url[..]).await?), serializer, })) } From 8413b6b2b7591d890d7afc1db4f2995f720d8a2c Mon Sep 17 00:00:00 2001 From: S-Y-rat Date: Fri, 31 Jul 2020 21:37:35 +0300 Subject: [PATCH 04/19] Implemented connection tests --- src/dispatching/dialogue/mod.rs | 2 +- src/dispatching/dialogue/storage/mod.rs | 2 +- .../dialogue/storage/sqlite_storage.rs | 50 +++++++++++++------ tests/sqlite.rs | 19 +++++-- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index b88d8425..f8b46e8a 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -160,6 +160,6 @@ pub use transition::{ pub use storage::{RedisStorage, RedisStorageError}; #[cfg(feature = "sqlite-storage")] -pub use storage::{SqliteStorage, SqliteStorageError}; +pub use storage::{SqliteStorage, SqliteStorageLocation, SqliteStorageError}; pub use storage::{serializer, InMemStorage, Serializer, Storage}; diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 175ad183..7fdf79d1 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -17,7 +17,7 @@ pub use serializer::Serializer; use std::sync::Arc; #[cfg(feature = "sqlite-storage")] -pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; +pub use sqlite_storage::{SqliteStorage, SqliteStorageLocation, SqliteStorageError}; /// A storage of dialogues. /// diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 7912caa4..6356b4c7 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -1,16 +1,13 @@ -use super::{serializer::Serializer, Storage}; +// use super::{serializer::Serializer, Storage}; +// use futures::future::BoxFuture; use std::{ convert::Infallible, fmt::{Debug, Display}, - sync::Arc, }; -use sqlx::{SqliteConnection, Connection, sqlite::SqliteError}; -use serde::{de::DeserializeOwned, Serialize}; +use sqlx::sqlite::SqlitePool; +// use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; -use tokio::{ - sync::Mutex, - task::block_in_place, -}; +// use tokio::task::block_in_place; pub enum SqliteStorageLocation { InMemory, @@ -26,11 +23,11 @@ where #[error("parsing/serializing error: {0}")] SerdeError(SE), #[error("error from Sqlite: {0}")] - SqliteError(#[from] SqliteError), + SqliteError(Box), } pub struct SqliteStorage { - conn: Mutex, + conn: SqlitePool, serializer: S, } @@ -38,14 +35,39 @@ impl SqliteStorage { pub async fn open( path: SqliteStorageLocation, serializer: S, - ) -> Result, Box>{ + ) -> Result>{ let url = match path { SqliteStorageLocation::InMemory => String::from("sqlite::memory:"), SqliteStorageLocation::Path(p) => p, }; - Ok(Arc::new(Self { - conn: Mutex::new(SqliteConnection::connect(&url[..]).await?), + Ok(Self { + conn: SqlitePool::connect(&url[..]).await + .expect("Impossible sqlite error"), serializer, - })) + }) } } + +// impl Storage for SqliteStorage +// where +// S: Send + Sync + Serializer + 'static, +// D: Send + Serialize + DeserializeOwned + 'static, +// >::Error: Debug + Display, +// { +// type Error = SqliteStorageError<>::Error>; + +// fn remove_dialogue( +// self: Arc, +// chat_id: i64, +// ) -> BoxFuture<'static, Result, Self::Error>> { +// Box::pin(async move { +// todo!() +// }); +// } + +// fn update_dialogue( +// self: Arc, +// chat_id: i64, +// dialogue: D +// ) { todo!() } +// } \ No newline at end of file diff --git a/tests/sqlite.rs b/tests/sqlite.rs index 786e3c2f..aac879ff 100644 --- a/tests/sqlite.rs +++ b/tests/sqlite.rs @@ -1,6 +1,19 @@ -use teloxide::dispatching::dialogue::SqliteStorage; +use teloxide::dispatching::dialogue::{ + serializer::{Bincode, CBOR, JSON}, + SqliteStorage, SqliteStorageLocation::InMemory +}; #[tokio::test] -async fn test_sqlite() { - todo!() +async fn test_sqlite_json() { + let _storage = SqliteStorage::open(InMemory, JSON).await.unwrap(); } + +#[tokio::test] +async fn test_sqlite_cbor() { + let _storage = SqliteStorage::open(InMemory, CBOR).await.unwrap(); +} + +#[tokio::test] +async fn test_sqlite_bincode() { + let _storage = SqliteStorage::open(InMemory, Bincode).await.unwrap(); +} \ No newline at end of file From e751624d6faed2002e2be00108dfa0865ff55ed1 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Mon, 19 Oct 2020 02:15:46 +0300 Subject: [PATCH 05/19] Implement sqlite storage methods #144 --- Cargo.toml | 2 +- src/dispatching/dialogue/mod.rs | 2 +- src/dispatching/dialogue/storage/mod.rs | 2 +- .../dialogue/storage/sqlite_storage.rs | 125 +++++++++++------- tests/sqlite.rs | 19 --- 5 files changed, 81 insertions(+), 69 deletions(-) delete mode 100644 tests/sqlite.rs diff --git a/Cargo.toml b/Cargo.toml index d2dc00d2..d81f3cc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ serde_with_macros = "1.1.0" sqlx = { version = "0.4.0-beta.1", optional = true, default-features = false, features = [ "runtime-tokio", "macros", - "sqlite" + "sqlite", ] } redis = { version = "0.16.0", optional = true } serde_cbor = { version = "0.11.1", optional = true } diff --git a/src/dispatching/dialogue/mod.rs b/src/dispatching/dialogue/mod.rs index 45f67e06..32f50dd5 100644 --- a/src/dispatching/dialogue/mod.rs +++ b/src/dispatching/dialogue/mod.rs @@ -165,6 +165,6 @@ pub use teloxide_macros::Transition; pub use storage::{RedisStorage, RedisStorageError}; #[cfg(feature = "sqlite-storage")] -pub use storage::{SqliteStorage, SqliteStorageLocation, SqliteStorageError}; +pub use storage::{SqliteStorage, SqliteStorageError}; pub use storage::{serializer, InMemStorage, Serializer, Storage}; diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 7fdf79d1..175ad183 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -17,7 +17,7 @@ pub use serializer::Serializer; use std::sync::Arc; #[cfg(feature = "sqlite-storage")] -pub use sqlite_storage::{SqliteStorage, SqliteStorageLocation, SqliteStorageError}; +pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; /// A storage of dialogues. /// diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 6356b4c7..32e7fd1f 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -1,18 +1,15 @@ -// use super::{serializer::Serializer, Storage}; -// use futures::future::BoxFuture; +use super::{serializer::Serializer, Storage}; +use futures::future::BoxFuture; +use serde::{de::DeserializeOwned, Serialize}; +use sqlx::sqlite::{SqliteConnectOptions, SqliteConnection}; +use sqlx::{ConnectOptions, Executor}; use std::{ convert::Infallible, fmt::{Debug, Display}, + sync::Arc, }; -use sqlx::sqlite::SqlitePool; -// use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; -// use tokio::task::block_in_place; - -pub enum SqliteStorageLocation { - InMemory, - Path(String), -} +use tokio::sync::Mutex; // An error returned from [`SqliteStorage`]. #[derive(Debug, Error)] @@ -20,54 +17,88 @@ pub enum SqliteStorageError where SE: Debug + Display, { - #[error("parsing/serializing error: {0}")] + #[error("dialogue serialization error: {0}")] SerdeError(SE), - #[error("error from Sqlite: {0}")] - SqliteError(Box), + #[error("sqlite error: {0}")] + SqliteError(#[from] sqlx::Error), } +// TODO: make JSON serializer to be default pub struct SqliteStorage { - conn: SqlitePool, + conn: Mutex, serializer: S, } -impl SqliteStorage { +impl SqliteStorage { pub async fn open( - path: SqliteStorageLocation, + path: &str, serializer: S, - ) -> Result>{ - let url = match path { - SqliteStorageLocation::InMemory => String::from("sqlite::memory:"), - SqliteStorageLocation::Path(p) => p, - }; - Ok(Self { - conn: SqlitePool::connect(&url[..]).await - .expect("Impossible sqlite error"), - serializer, - }) + ) -> Result, SqliteStorageError> { + let mut conn = + SqliteConnectOptions::new().filename(path).create_if_missing(true).c§onnect().await?; + + // TODO: think about a schema migration mechanism. + conn.execute( + r#" +CREATE TABLE IF NOT EXISTS teloxide_dialogues ( + chat_id BIGINT PRIMARY KEY, + dialogue BLOB NOT NULL +); + "#, + ) + .await?; + + Ok(Arc::new(Self { conn: Mutex::new(conn), serializer })) } } -// impl Storage for SqliteStorage -// where -// S: Send + Sync + Serializer + 'static, -// D: Send + Serialize + DeserializeOwned + 'static, -// >::Error: Debug + Display, -// { -// type Error = SqliteStorageError<>::Error>; +impl Storage for SqliteStorage +where + S: Send + Sync + Serializer + 'static, + D: Send + Serialize + DeserializeOwned + 'static, + >::Error: Debug + Display, +{ + type Error = SqliteStorageError<>::Error>; -// fn remove_dialogue( -// self: Arc, -// chat_id: i64, -// ) -> BoxFuture<'static, Result, Self::Error>> { -// Box::pin(async move { -// todo!() -// }); -// } + fn remove_dialogue( + self: Arc, + chat_id: i64, + ) -> BoxFuture<'static, Result, Self::Error>> { + Box::pin(async move { + self.conn + .lock() + .await + .execute( + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?").bind(chat_id), + ) + .await?; + Ok(None) + }) + } -// fn update_dialogue( -// self: Arc, -// chat_id: i64, -// dialogue: D -// ) { todo!() } -// } \ No newline at end of file + fn update_dialogue( + self: Arc, + chat_id: i64, + dialogue: D, + ) -> BoxFuture<'static, Result, Self::Error>> { + Box::pin(async move { + let dialogue = + self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; + self.conn + .lock() + .await + .execute( + sqlx::query( + r#" +INSERT INTO teloxide_dialogues VALUES (?, ?) WHERE chat_id = ? +ON CONFLICT(chat_id) DO UPDATE SET dialogue=excluded.dialogue + "#, + ) + .bind(chat_id) + .bind(dialogue), + ) + .await?; + Ok(None) + }) + } +} diff --git a/tests/sqlite.rs b/tests/sqlite.rs deleted file mode 100644 index aac879ff..00000000 --- a/tests/sqlite.rs +++ /dev/null @@ -1,19 +0,0 @@ -use teloxide::dispatching::dialogue::{ - serializer::{Bincode, CBOR, JSON}, - SqliteStorage, SqliteStorageLocation::InMemory -}; - -#[tokio::test] -async fn test_sqlite_json() { - let _storage = SqliteStorage::open(InMemory, JSON).await.unwrap(); -} - -#[tokio::test] -async fn test_sqlite_cbor() { - let _storage = SqliteStorage::open(InMemory, CBOR).await.unwrap(); -} - -#[tokio::test] -async fn test_sqlite_bincode() { - let _storage = SqliteStorage::open(InMemory, Bincode).await.unwrap(); -} \ No newline at end of file From fb996d943d7804070083019f5709942d3f1dc6fa Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Mon, 19 Oct 2020 02:34:48 +0300 Subject: [PATCH 06/19] Fix typo --- src/dispatching/dialogue/storage/sqlite_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 32e7fd1f..123e8bbc 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -35,7 +35,7 @@ impl SqliteStorage { serializer: S, ) -> Result, SqliteStorageError> { let mut conn = - SqliteConnectOptions::new().filename(path).create_if_missing(true).c§onnect().await?; + SqliteConnectOptions::new().filename(path).create_if_missing(true).connect().await?; // TODO: think about a schema migration mechanism. conn.execute( From 16b0b47ecfec09169d05ea36c2b5dd88caacf702 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Thu, 22 Oct 2020 21:30:34 +0300 Subject: [PATCH 07/19] Properly implement SqliteStorage methods --- .../dialogue/storage/sqlite_storage.rs | 85 +++++++++++++------ 1 file changed, 58 insertions(+), 27 deletions(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 123e8bbc..197a6d10 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -1,15 +1,14 @@ use super::{serializer::Serializer, Storage}; use futures::future::BoxFuture; use serde::{de::DeserializeOwned, Serialize}; -use sqlx::sqlite::{SqliteConnectOptions, SqliteConnection}; -use sqlx::{ConnectOptions, Executor}; +use sqlx::sqlite::SqlitePool; +use sqlx::Executor; use std::{ convert::Infallible, fmt::{Debug, Display}, sync::Arc, }; use thiserror::Error; -use tokio::sync::Mutex; // An error returned from [`SqliteStorage`]. #[derive(Debug, Error)] @@ -25,20 +24,23 @@ where // TODO: make JSON serializer to be default pub struct SqliteStorage { - conn: Mutex, + pool: SqlitePool, serializer: S, } +#[derive(sqlx::FromRow)] +struct DialogueDBRow { + dialogue: Vec, +} + impl SqliteStorage { pub async fn open( path: &str, serializer: S, ) -> Result, SqliteStorageError> { - let mut conn = - SqliteConnectOptions::new().filename(path).create_if_missing(true).connect().await?; - - // TODO: think about a schema migration mechanism. - conn.execute( + let pool = SqlitePool::connect(format!("sqlite:{}?mode=rwc", path).as_str()).await?; + let mut conn = pool.acquire().await?; + sqlx::query( r#" CREATE TABLE IF NOT EXISTS teloxide_dialogues ( chat_id BIGINT PRIMARY KEY, @@ -46,9 +48,26 @@ CREATE TABLE IF NOT EXISTS teloxide_dialogues ( ); "#, ) + .execute(&mut conn) .await?; - Ok(Arc::new(Self { conn: Mutex::new(conn), serializer })) + Ok(Arc::new(Self { pool, serializer })) + } +} + +async fn get_dialogue( + pool: &SqlitePool, + chat_id: i64, +) -> Result>>, sqlx::Error> { + match sqlx::query_as::<_, DialogueDBRow>( + "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", + ) + .bind(chat_id) + .fetch_optional(pool) + .await? + { + Some(r) => Ok(Some(Box::new(r.dialogue))), + _ => Ok(None), } } @@ -65,14 +84,18 @@ where chat_id: i64, ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { - self.conn - .lock() - .await - .execute( - sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?").bind(chat_id), - ) - .await?; - Ok(None) + match get_dialogue(&self.pool, chat_id).await? { + None => Ok(None), + Some(d) => { + let prev_dialogue = + self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?; + sqlx::query("DELETE FROM teloxide_dialogues WHERE chat_id = ?") + .bind(chat_id) + .execute(&self.pool) + .await?; + Ok(Some(prev_dialogue)) + } + } }) } @@ -82,23 +105,31 @@ where dialogue: D, ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { - let dialogue = + let serialized_dialogue = self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; - self.conn - .lock() - .await + let prev_dialogue = get_dialogue(&self.pool, chat_id).await?; + + self.pool + .acquire() + .await? .execute( sqlx::query( r#" -INSERT INTO teloxide_dialogues VALUES (?, ?) WHERE chat_id = ? -ON CONFLICT(chat_id) DO UPDATE SET dialogue=excluded.dialogue - "#, + INSERT INTO teloxide_dialogues VALUES (?, ?) WHERE chat_id = ? + ON CONFLICT(chat_id) DO UPDATE SET dialogue=excluded.dialogue + "#, ) .bind(chat_id) - .bind(dialogue), + .bind(serialized_dialogue), ) .await?; - Ok(None) + + Ok(match prev_dialogue { + None => None, + Some(d) => { + Some(self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?) + } + }) }) } } From f8a00e64d8c334590f3f237b764e937244242329 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Thu, 22 Oct 2020 21:47:43 +0300 Subject: [PATCH 08/19] Update sqlite dialog after all serializations --- .../dialogue/storage/sqlite_storage.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 197a6d10..16c88202 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -105,9 +105,14 @@ where dialogue: D, ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { - let serialized_dialogue = + let prev_dialogue = match get_dialogue(&self.pool, chat_id).await? { + Some(d) => { + Some(self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?) + } + None => None, + }; + let upd_dialogue = self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; - let prev_dialogue = get_dialogue(&self.pool, chat_id).await?; self.pool .acquire() @@ -120,16 +125,10 @@ where "#, ) .bind(chat_id) - .bind(serialized_dialogue), + .bind(upd_dialogue), ) .await?; - - Ok(match prev_dialogue { - None => None, - Some(d) => { - Some(self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?) - } - }) + Ok(prev_dialogue) }) } } From 844a198753b7ff0df587000f1d3f1c9a169d9c64 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Thu, 22 Oct 2020 23:19:29 +0300 Subject: [PATCH 09/19] Cargo +nightly fmt, gardening --- .../dialogue/storage/sqlite_storage.rs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 16c88202..21a285ce 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -1,8 +1,7 @@ use super::{serializer::Serializer, Storage}; use futures::future::BoxFuture; use serde::{de::DeserializeOwned, Serialize}; -use sqlx::sqlite::SqlitePool; -use sqlx::Executor; +use sqlx::{sqlite::SqlitePool, Executor}; use std::{ convert::Infallible, fmt::{Debug, Display}, @@ -59,16 +58,18 @@ async fn get_dialogue( pool: &SqlitePool, chat_id: i64, ) -> Result>>, sqlx::Error> { - match sqlx::query_as::<_, DialogueDBRow>( - "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", + Ok( + match sqlx::query_as::<_, DialogueDBRow>( + "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", + ) + .bind(chat_id) + .fetch_optional(pool) + .await? + { + Some(r) => Some(Box::new(r.dialogue)), + _ => None, + }, ) - .bind(chat_id) - .fetch_optional(pool) - .await? - { - Some(r) => Ok(Some(Box::new(r.dialogue))), - _ => Ok(None), - } } impl Storage for SqliteStorage @@ -84,8 +85,7 @@ where chat_id: i64, ) -> BoxFuture<'static, Result, Self::Error>> { Box::pin(async move { - match get_dialogue(&self.pool, chat_id).await? { - None => Ok(None), + Ok(match get_dialogue(&self.pool, chat_id).await? { Some(d) => { let prev_dialogue = self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?; @@ -93,9 +93,10 @@ where .bind(chat_id) .execute(&self.pool) .await?; - Ok(Some(prev_dialogue)) + Some(prev_dialogue) } - } + _ => None, + }) }) } @@ -109,7 +110,7 @@ where Some(d) => { Some(self.serializer.deserialize(&d).map_err(SqliteStorageError::SerdeError)?) } - None => None, + _ => None, }; let upd_dialogue = self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; From 7ba0a5b5a45726e0c2668504bd877b417ca9a6eb Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Fri, 23 Oct 2020 22:26:27 +0300 Subject: [PATCH 10/19] Fix SQL syntax error --- src/dispatching/dialogue/storage/sqlite_storage.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 21a285ce..634403e0 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -5,6 +5,7 @@ use sqlx::{sqlite::SqlitePool, Executor}; use std::{ convert::Infallible, fmt::{Debug, Display}, + str, sync::Arc, }; use thiserror::Error; @@ -114,14 +115,13 @@ where }; let upd_dialogue = self.serializer.serialize(&dialogue).map_err(SqliteStorageError::SerdeError)?; - self.pool .acquire() .await? .execute( sqlx::query( r#" - INSERT INTO teloxide_dialogues VALUES (?, ?) WHERE chat_id = ? + INSERT INTO teloxide_dialogues VALUES (?, ?) ON CONFLICT(chat_id) DO UPDATE SET dialogue=excluded.dialogue "#, ) From 5e1fe064cd57b9df9ede83a2533625bc24dcab75 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 19:51:55 +0300 Subject: [PATCH 11/19] Add tests for sqlite storage --- .github/workflows/ci.yml | 4 +-- .gitignore | 3 +- Cargo.toml | 5 +++ tests/sqlite.rs | 67 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 tests/sqlite.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9a4c47a..0044f83e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,9 @@ jobs: include: - rust: stable - features: "--features \"redis-storage cbor-serializer bincode-serializer frunk-\"" + features: "--features \"redis-storage sqlite-storage cbor-serializer bincode-serializer frunk-\"" - rust: beta - features: "--features \"redis-storage cbor-serializer bincode-serializer frunk-\"" + features: "--features \"redis-storage sqlite-storage cbor-serializer bincode-serializer frunk-\"" - rust: nightly features: "--all-features" diff --git a/.gitignore b/.gitignore index 7967ab0a..8b037b5b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ Cargo.lock .idea/ .vscode/ -examples/*/target \ No newline at end of file +examples/*/target +test_db*.sqlite diff --git a/Cargo.toml b/Cargo.toml index d81f3cc0..85a1d9ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,3 +81,8 @@ all-features = true name = "redis" path = "tests/redis.rs" required-features = ["redis-storage", "cbor-serializer", "bincode-serializer"] + +[[test]] +name = "sqlite" +path = "tests/sqlite.rs" +required-features = ["sqlite-storage", "cbor-serializer", "bincode-serializer"] diff --git a/tests/sqlite.rs b/tests/sqlite.rs new file mode 100644 index 00000000..f2af4f65 --- /dev/null +++ b/tests/sqlite.rs @@ -0,0 +1,67 @@ +use std::{ + fmt::{Debug, Display}, + future::Future, + sync::Arc, +}; +use teloxide::dispatching::dialogue::{Serializer, SqliteStorage, Storage}; + +#[tokio::test(threaded_scheduler)] +async fn test_sqlite_json() { + let storage = + SqliteStorage::open("./test_db1.sqlite", teloxide::dispatching::dialogue::serializer::JSON) + .await + .unwrap(); + test_sqlite(storage).await; +} + +#[tokio::test(threaded_scheduler)] +async fn test_sqlite_bincode() { + let storage = SqliteStorage::open( + "./test_db2.sqlite", + teloxide::dispatching::dialogue::serializer::Bincode, + ) + .await + .unwrap(); + test_sqlite(storage).await; +} + +#[tokio::test(threaded_scheduler)] +async fn test_sqlite_cbor() { + let storage = + SqliteStorage::open("./test_db3.sqlite", teloxide::dispatching::dialogue::serializer::CBOR) + .await + .unwrap(); + test_sqlite(storage).await; +} + +type Dialogue = String; + +async fn test_sqlite(storage: Arc>) +where + S: Send + Sync + Serializer + 'static, + >::Error: Debug + Display, +{ + check_dialogue(None, Arc::clone(&storage).update_dialogue(1, "ABC".to_owned())).await; + check_dialogue(None, Arc::clone(&storage).update_dialogue(11, "DEF".to_owned())).await; + check_dialogue(None, Arc::clone(&storage).update_dialogue(256, "GHI".to_owned())).await; + + // 1 - ABC, 11 - DEF, 256 - GHI + + check_dialogue("ABC", Arc::clone(&storage).update_dialogue(1, "JKL".to_owned())).await; + check_dialogue("GHI", Arc::clone(&storage).update_dialogue(256, "MNO".to_owned())).await; + + // 1 - GKL, 11 - DEF, 256 - MNO + + check_dialogue("JKL", Arc::clone(&storage).remove_dialogue(1)).await; + check_dialogue("DEF", Arc::clone(&storage).remove_dialogue(11)).await; + check_dialogue("MNO", Arc::clone(&storage).remove_dialogue(256)).await; +} + +async fn check_dialogue( + expected: impl Into>, + actual: impl Future, E>>, +) where + E: Debug, +{ + assert_eq!(expected.into().map(ToOwned::to_owned), actual.await.unwrap()) +} From ef2a3b36aee81f7dc2b0d715cf8123e94cb8c671 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 19:57:51 +0300 Subject: [PATCH 12/19] Add a bot example for SqliteStorage --- examples/sqlite_remember_bot/Cargo.toml | 20 +++++++ examples/sqlite_remember_bot/sqlite.db | Bin 0 -> 4096 bytes examples/sqlite_remember_bot/sqlite.db-shm | Bin 0 -> 32768 bytes examples/sqlite_remember_bot/sqlite.db-wal | Bin 0 -> 350232 bytes examples/sqlite_remember_bot/src/main.rs | 50 ++++++++++++++++++ examples/sqlite_remember_bot/src/states.rs | 23 ++++++++ .../sqlite_remember_bot/src/transitions.rs | 35 ++++++++++++ 7 files changed, 128 insertions(+) create mode 100644 examples/sqlite_remember_bot/Cargo.toml create mode 100644 examples/sqlite_remember_bot/sqlite.db create mode 100644 examples/sqlite_remember_bot/sqlite.db-shm create mode 100644 examples/sqlite_remember_bot/sqlite.db-wal create mode 100644 examples/sqlite_remember_bot/src/main.rs create mode 100644 examples/sqlite_remember_bot/src/states.rs create mode 100644 examples/sqlite_remember_bot/src/transitions.rs diff --git a/examples/sqlite_remember_bot/Cargo.toml b/examples/sqlite_remember_bot/Cargo.toml new file mode 100644 index 00000000..b65aa346 --- /dev/null +++ b/examples/sqlite_remember_bot/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "sqlite_remember_bot" +version = "0.1.0" +authors = ["Sergey Levitin "] +edition = "2018" + +[dependencies] +log = "0.4.8" +pretty_env_logger = "0.4.0" +tokio = { version = "0.2.11", features = ["rt-threaded", "macros"] } + +# You can also choose "cbor-serializer" or built-in JSON serializer +teloxide = { path = "../../", features = ["sqlite-storage", "bincode-serializer", "redis-storage"] } +teloxide-macros = { git = "https://github.com/teloxide/teloxide-macros", branch = "master" } + +serde = "1.0.104" +futures = "0.3.5" + +thiserror = "1.0.15" +derive_more = "0.99.9" diff --git a/examples/sqlite_remember_bot/sqlite.db b/examples/sqlite_remember_bot/sqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..41ade9e5d196d38ea43f480680e2ead84f89a719 GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYDv}$qUjhK(-m98b?E5 nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nC=3Ar-H8V& literal 0 HcmV?d00001 diff --git a/examples/sqlite_remember_bot/sqlite.db-shm b/examples/sqlite_remember_bot/sqlite.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..7f9864c7eac7ba4b85364c7dacd7572566b8f50a GIT binary patch literal 32768 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 OfB*pk*(h+NFYtei&6nH& literal 0 HcmV?d00001 diff --git a/examples/sqlite_remember_bot/src/main.rs b/examples/sqlite_remember_bot/src/main.rs new file mode 100644 index 00000000..e095150a --- /dev/null +++ b/examples/sqlite_remember_bot/src/main.rs @@ -0,0 +1,50 @@ +#[macro_use] +extern crate derive_more; + +mod states; +mod transitions; + +use states::*; + +use teloxide::{ + dispatching::dialogue::{serializer::JSON, SqliteStorage, Storage}, + prelude::*, +}; +use thiserror::Error; + +type StorageError = as Storage>::Error; + +#[derive(Debug, Error)] +enum Error { + #[error("error from Telegram: {0}")] + TelegramError(#[from] RequestError), + #[error("error from storage: {0}")] + StorageError(#[from] StorageError), +} + +type In = DialogueWithCx; + +async fn handle_message(cx: UpdateWithCx, dialogue: Dialogue) -> TransitionOut { + match cx.update.text_owned() { + None => { + cx.answer_str("Send me a text message.").await?; + next(dialogue) + } + Some(ans) => dialogue.react(cx, ans).await, + } +} + +#[tokio::main] +async fn main() { + let bot = Bot::from_env(); + Dispatcher::new(bot) + .messages_handler(DialogueDispatcher::with_storage( + |DialogueWithCx { cx, dialogue }: In| async move { + let dialogue = dialogue.expect("std::convert::Infallible"); + handle_message(cx, dialogue).await.expect("Something wrong with the bot!") + }, + SqliteStorage::open("sqlite.db", JSON).await.unwrap(), + )) + .dispatch() + .await; +} diff --git a/examples/sqlite_remember_bot/src/states.rs b/examples/sqlite_remember_bot/src/states.rs new file mode 100644 index 00000000..0bb65bd7 --- /dev/null +++ b/examples/sqlite_remember_bot/src/states.rs @@ -0,0 +1,23 @@ +use teloxide_macros::Transition; + +use serde::{Deserialize, Serialize}; + +#[derive(Transition, From, Serialize, Deserialize)] +pub enum Dialogue { + Start(StartState), + HaveNumber(HaveNumberState), +} + +impl Default for Dialogue { + fn default() -> Self { + Self::Start(StartState) + } +} + +#[derive(Serialize, Deserialize)] +pub struct StartState; + +#[derive(Serialize, Deserialize)] +pub struct HaveNumberState { + pub number: i32, +} diff --git a/examples/sqlite_remember_bot/src/transitions.rs b/examples/sqlite_remember_bot/src/transitions.rs new file mode 100644 index 00000000..dcc78db9 --- /dev/null +++ b/examples/sqlite_remember_bot/src/transitions.rs @@ -0,0 +1,35 @@ +use teloxide::prelude::*; +use teloxide_macros::teloxide; + +use super::states::*; + +#[teloxide(subtransition)] +async fn start(state: StartState, cx: TransitionIn, ans: String) -> TransitionOut { + if let Ok(number) = ans.parse() { + cx.answer_str(format!("Remembered number {}. Now use /get or /reset", number)).await?; + next(HaveNumberState { number }) + } else { + cx.answer_str("Please, send me a number").await?; + next(state) + } +} + +#[teloxide(subtransition)] +async fn have_number( + state: HaveNumberState, + cx: TransitionIn, + ans: String, +) -> TransitionOut { + let num = state.number; + + if ans.starts_with("/get") { + cx.answer_str(format!("Here is your number: {}", num)).await?; + next(state) + } else if ans.starts_with("/reset") { + cx.answer_str("Resetted number").await?; + next(StartState) + } else { + cx.answer_str("Please, send /get or /reset").await?; + next(state) + } +} From d5b86ac086d12db982ec58ce0c0cedc7de6142d7 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 19:59:08 +0300 Subject: [PATCH 13/19] Get rid of an unnesessary TODO --- src/dispatching/dialogue/storage/sqlite_storage.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 634403e0..85e13f6a 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -22,7 +22,6 @@ where SqliteError(#[from] sqlx::Error), } -// TODO: make JSON serializer to be default pub struct SqliteStorage { pool: SqlitePool, serializer: S, From 5bb53571836b65b54ef2f15542c0740e65932acc Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 20:05:48 +0300 Subject: [PATCH 14/19] Gardening + update docs --- .gitignore | 2 +- README.md | 4 +++- examples/sqlite_remember_bot/sqlite.db | Bin 4096 -> 0 bytes examples/sqlite_remember_bot/sqlite.db-shm | Bin 32768 -> 0 bytes examples/sqlite_remember_bot/sqlite.db-wal | Bin 350232 -> 0 bytes examples/sqlite_remember_bot/src/main.rs | 2 +- 6 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 examples/sqlite_remember_bot/sqlite.db delete mode 100644 examples/sqlite_remember_bot/sqlite.db-shm delete mode 100644 examples/sqlite_remember_bot/sqlite.db-wal diff --git a/.gitignore b/.gitignore index 8b037b5b..b1c241c5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ Cargo.lock .idea/ .vscode/ examples/*/target -test_db*.sqlite +*.sqlite diff --git a/README.md b/README.md index 75f9ffcf..dca16841 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,11 @@ [functional reactive design]: https://en.wikipedia.org/wiki/Functional_reactive_programming [other adaptors]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html - - **Dialogues management subsystem.** We have designed our dialogues management subsystem to be easy-to-use, and, furthermore, to be agnostic of how/where dialogues are stored. For example, you can just replace a one line to achieve [persistence]. Out-of-the-box storages include [Redis]. + - **Dialogues management subsystem.** We have designed our dialogues management subsystem to be easy-to-use, and, furthermore, to be agnostic of how/where dialogues are stored. For example, you can just replace a one line to achieve [persistence]. Out-of-the-box storages include [Redis] and [Sqlite]. [persistence]: https://en.wikipedia.org/wiki/Persistence_(computer_science) [Redis]: https://redis.io/ +[Sqlite]: https://www.sqlite.org - **Strongly typed bot commands.** You can describe bot commands as enumerations, and then they'll be automatically constructed from strings — just like JSON structures in [serde-json] and command-line arguments in [structopt]. @@ -371,6 +372,7 @@ The second one produces very strange compiler messages due to the `#[tokio::main ## Cargo features - `redis-storage` -- enables the [Redis] support. + - `sqlite-storage` -- enables the [Sqlite] support. - `cbor-serializer` -- enables the [CBOR] serializer for dialogues. - `bincode-serializer` -- enables the [Bincode] serializer for dialogues. - `frunk` -- enables [`teloxide::utils::UpState`], which allows mapping from a structure of `field1, ..., fieldN` to a structure of `field1, ..., fieldN, fieldN+1`. diff --git a/examples/sqlite_remember_bot/sqlite.db b/examples/sqlite_remember_bot/sqlite.db deleted file mode 100644 index 41ade9e5d196d38ea43f480680e2ead84f89a719..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYDv}$qUjhK(-m98b?E5 nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nC=3Ar-H8V& diff --git a/examples/sqlite_remember_bot/sqlite.db-shm b/examples/sqlite_remember_bot/sqlite.db-shm deleted file mode 100644 index 7f9864c7eac7ba4b85364c7dacd7572566b8f50a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 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 OfB*pk*(h+NFYtei&6nH& diff --git a/examples/sqlite_remember_bot/src/main.rs b/examples/sqlite_remember_bot/src/main.rs index e095150a..f48ed648 100644 --- a/examples/sqlite_remember_bot/src/main.rs +++ b/examples/sqlite_remember_bot/src/main.rs @@ -43,7 +43,7 @@ async fn main() { let dialogue = dialogue.expect("std::convert::Infallible"); handle_message(cx, dialogue).await.expect("Something wrong with the bot!") }, - SqliteStorage::open("sqlite.db", JSON).await.unwrap(), + SqliteStorage::open("db.sqlite", JSON).await.unwrap(), )) .dispatch() .await; From d0f727099c9b9d43d92929031da803579317c284 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 20:08:00 +0300 Subject: [PATCH 15/19] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ceaad7..de30e5f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Allow arbitrary error types to be returned from (sub)transitions ([issue 242](https://github.com/teloxide/teloxide/issues/242)). - The `respond` function, a shortcut for `ResponseResult::Ok(())`. + - The `sqlite-storage` feature -- enables SQLite support. ### Changed - Allow `bot_name` be `N`, where `N: Into + ...` in `commands_repl` & `commands_repl_with_listener`. From e456e525d8e04ca102eb58b18443412b310ace5c Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 20:18:13 +0300 Subject: [PATCH 16/19] Update authors of sqlite_remember_bot --- examples/sqlite_remember_bot/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sqlite_remember_bot/Cargo.toml b/examples/sqlite_remember_bot/Cargo.toml index b65aa346..cf4cb204 100644 --- a/examples/sqlite_remember_bot/Cargo.toml +++ b/examples/sqlite_remember_bot/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sqlite_remember_bot" version = "0.1.0" -authors = ["Sergey Levitin "] +authors = ["Maximilian Siling ", "Sergey Levitin "] edition = "2018" [dependencies] From 75c7899f2c9f29c7704ca084e3ac374450fead61 Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 21:08:14 +0300 Subject: [PATCH 17/19] Actualize storages docs --- CONTRIBUTING.md | 2 +- src/dispatching/dialogue/storage/in_mem_storage.rs | 7 +++++-- src/dispatching/dialogue/storage/mod.rs | 8 +++++++- src/dispatching/dialogue/storage/sqlite_storage.rs | 5 ++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4587993e..4216667f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ To change the source code, fork the `dev` branch of this repository and work ins ``` cargo clippy --all --all-features --all-targets cargo test --all -cargo doc --open +cargo doc --open --all-features # Using nightly rustfmt cargo +nightly fmt --all -- --check ``` diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index 78fc842f..c423eb02 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -8,8 +8,11 @@ use tokio::sync::Mutex; /// /// ## Note /// All the dialogues will be lost after you restart your bot. If you need to -/// store them somewhere on a drive, you need to implement a storage -/// communicating with a DB. +/// store them somewhere on a drive, should should use [`SqliteStorage`], +/// [`RedisStorage`] or implement your own. +/// +/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage +/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage #[derive(Debug)] pub struct InMemStorage { map: Mutex>, diff --git a/src/dispatching/dialogue/storage/mod.rs b/src/dispatching/dialogue/storage/mod.rs index 175ad183..44a75d12 100644 --- a/src/dispatching/dialogue/storage/mod.rs +++ b/src/dispatching/dialogue/storage/mod.rs @@ -24,9 +24,15 @@ pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; /// You can implement this trait for a structure that communicates with a DB and /// be sure that after you restart your bot, all the dialogues won't be lost. /// -/// For a storage based on a simple hash map, see [`InMemStorage`]. +/// Currently we support the following storages out of the box: +/// +/// - [`InMemStorage`] - a storage based on a simple hash map +/// - [`RedisStorage`] - a Redis-based storage +/// - [`SqliteStorage`] - an SQLite-based persistent storage /// /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage +/// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage +/// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage pub trait Storage { type Error; diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index 85e13f6a..c88c9558 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -10,7 +10,9 @@ use std::{ }; use thiserror::Error; -// An error returned from [`SqliteStorage`]. +/// An error returned from [`SqliteStorage`]. +/// +/// [`SqliteStorage`]: struct.SqliteStorage.html #[derive(Debug, Error)] pub enum SqliteStorageError where @@ -22,6 +24,7 @@ where SqliteError(#[from] sqlx::Error), } +/// A persistent storage based on [SQLite](https://www.sqlite.org/). pub struct SqliteStorage { pool: SqlitePool, serializer: S, From 633d5b0d64272e66d17e89fa04a3d0e38e4d443b Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sat, 24 Oct 2020 21:09:17 +0300 Subject: [PATCH 18/19] Fix typo --- src/dispatching/dialogue/storage/in_mem_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatching/dialogue/storage/in_mem_storage.rs b/src/dispatching/dialogue/storage/in_mem_storage.rs index c423eb02..468a305e 100644 --- a/src/dispatching/dialogue/storage/in_mem_storage.rs +++ b/src/dispatching/dialogue/storage/in_mem_storage.rs @@ -8,7 +8,7 @@ use tokio::sync::Mutex; /// /// ## Note /// All the dialogues will be lost after you restart your bot. If you need to -/// store them somewhere on a drive, should should use [`SqliteStorage`], +/// store them somewhere on a drive, you should use [`SqliteStorage`], /// [`RedisStorage`] or implement your own. /// /// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage From 32600ff8f64802baefec8c5392962bb9db3ce21e Mon Sep 17 00:00:00 2001 From: Sergey Levitin Date: Sun, 25 Oct 2020 11:50:47 +0300 Subject: [PATCH 19/19] Gardening + docs improvals --- CONTRIBUTING.md | 2 +- .../dialogue/storage/sqlite_storage.rs | 58 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4216667f..c5853790 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ To change the source code, fork the `dev` branch of this repository and work ins ``` cargo clippy --all --all-features --all-targets cargo test --all -cargo doc --open --all-features +RUSTDOCFLAGS="--cfg docsrs" cargo doc --open --all-features # Using nightly rustfmt cargo +nightly fmt --all -- --check ``` diff --git a/src/dispatching/dialogue/storage/sqlite_storage.rs b/src/dispatching/dialogue/storage/sqlite_storage.rs index c88c9558..ca68b693 100644 --- a/src/dispatching/dialogue/storage/sqlite_storage.rs +++ b/src/dispatching/dialogue/storage/sqlite_storage.rs @@ -10,6 +10,12 @@ use std::{ }; use thiserror::Error; +/// A persistent storage based on [SQLite](https://www.sqlite.org/). +pub struct SqliteStorage { + pool: SqlitePool, + serializer: S, +} + /// An error returned from [`SqliteStorage`]. /// /// [`SqliteStorage`]: struct.SqliteStorage.html @@ -24,17 +30,6 @@ where SqliteError(#[from] sqlx::Error), } -/// A persistent storage based on [SQLite](https://www.sqlite.org/). -pub struct SqliteStorage { - pool: SqlitePool, - serializer: S, -} - -#[derive(sqlx::FromRow)] -struct DialogueDBRow { - dialogue: Vec, -} - impl SqliteStorage { pub async fn open( path: &str, @@ -57,24 +52,6 @@ CREATE TABLE IF NOT EXISTS teloxide_dialogues ( } } -async fn get_dialogue( - pool: &SqlitePool, - chat_id: i64, -) -> Result>>, sqlx::Error> { - Ok( - match sqlx::query_as::<_, DialogueDBRow>( - "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", - ) - .bind(chat_id) - .fetch_optional(pool) - .await? - { - Some(r) => Some(Box::new(r.dialogue)), - _ => None, - }, - ) -} - impl Storage for SqliteStorage where S: Send + Sync + Serializer + 'static, @@ -135,3 +112,26 @@ where }) } } + +#[derive(sqlx::FromRow)] +struct DialogueDBRow { + dialogue: Vec, +} + +async fn get_dialogue( + pool: &SqlitePool, + chat_id: i64, +) -> Result>>, sqlx::Error> { + Ok( + match sqlx::query_as::<_, DialogueDBRow>( + "SELECT dialogue FROM teloxide_dialogues WHERE chat_id = ?", + ) + .bind(chat_id) + .fetch_optional(pool) + .await? + { + Some(r) => Some(Box::new(r.dialogue)), + _ => None, + }, + ) +}