diff --git a/CHANGELOG.md b/CHANGELOG.md index 353e0c0e..185ffd5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## unreleased +## Removed + +- `rocksdb-storage` feature and associated items (See [PR #761](https://github.com/teloxide/teloxide/pull/761) for reasoning) [**BC**] + ## 0.11.1 - 2022-10-31 ### Added diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index c5da2a04..fd75050f 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -1,6 +1,14 @@ This document describes breaking changes of `teloxide` crate, as well as the ways to update code. Note that the list of required changes is not fully exhaustive and it may lack something in rare cases. +## 0.11 -> 0.?? + +### teloxide + +`rocksdb-storage` feature and associated items were removed. +If you are using rocksdb storage, you'll need to either write `Storage` impl yourself, or use a third party crate. + + ## 0.11 -> 0.11.1 ### teloxide diff --git a/README.md b/README.md index 94b9e44b..ad1b7565 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,10 @@ - **Feature-rich.** You can use both long polling and webhooks, configure an underlying HTTPS client, set a custom URL of a Telegram API server, do graceful shutdown, and much more. - - **Simple dialogues.** Our dialogues subsystem is simple and easy-to-use, and, furthermore, is 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], [RocksDB] and [Sqlite]. + - **Simple dialogues.** Our dialogues subsystem is simple and easy-to-use, and, furthermore, is 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/ -[RocksDB]: https://rocksdb.org/ [Sqlite]: https://www.sqlite.org - **Strongly typed commands.** Define bot commands as an `enum` and teloxide will parse them automatically — just like JSON structures in [`serde-json`] and command-line arguments in [`structopt`]. diff --git a/crates/teloxide/Cargo.toml b/crates/teloxide/Cargo.toml index f7db26f8..455f13a9 100644 --- a/crates/teloxide/Cargo.toml +++ b/crates/teloxide/Cargo.toml @@ -19,7 +19,6 @@ webhooks-axum = ["webhooks", "axum", "tower", "tower-http"] sqlite-storage = ["sqlx"] redis-storage = ["redis"] -rocksdb-storage = ["rocksdb"] cbor-serializer = ["serde_cbor"] bincode-serializer = ["bincode"] @@ -43,7 +42,6 @@ full = [ "webhooks-axum", "sqlite-storage", "redis-storage", - "rocksdb-storage", "cbor-serializer", "bincode-serializer", "macros", @@ -94,9 +92,6 @@ sqlx = { version = "0.6", optional = true, default-features = false, features = "sqlite", ] } redis = { version = "0.21", features = ["tokio-comp"], optional = true } -rocksdb = { version = "0.19", optional = true, default-features = false, features = [ - "lz4", -] } serde_cbor = { version = "0.11", optional = true } bincode = { version = "1.3", optional = true } axum = { version = "0.5.13", optional = true } @@ -131,11 +126,6 @@ name = "sqlite" path = "tests/sqlite.rs" required-features = ["sqlite-storage", "cbor-serializer", "bincode-serializer"] -[[test]] -name = "rocksdb" -path = "tests/rocksdb.rs" -required-features = ["rocksdb-storage", "cbor-serializer", "bincode-serializer"] - [[example]] name = "dialogue" required-features = ["macros"] diff --git a/crates/teloxide/src/dispatching/dialogue/storage.rs b/crates/teloxide/src/dispatching/dialogue/storage.rs index e8d2c158..b78cd72e 100644 --- a/crates/teloxide/src/dispatching/dialogue/storage.rs +++ b/crates/teloxide/src/dispatching/dialogue/storage.rs @@ -9,9 +9,6 @@ mod redis_storage; #[cfg(feature = "sqlite-storage")] mod sqlite_storage; -#[cfg(feature = "rocksdb-storage")] -mod rocksdb_storage; - use futures::future::BoxFuture; use teloxide_core::types::ChatId; @@ -28,9 +25,6 @@ use std::sync::Arc; #[cfg(feature = "sqlite-storage")] pub use sqlite_storage::{SqliteStorage, SqliteStorageError}; -#[cfg(feature = "rocksdb-storage")] -pub use rocksdb_storage::{RocksDbStorage, RocksDbStorageError}; - /// A storage with an erased error type. pub type ErasedStorage = dyn Storage> + Send + Sync; @@ -47,12 +41,10 @@ pub type ErasedStorage = /// /// - [`InMemStorage`] -- a storage based on [`std::collections::HashMap`]. /// - [`RedisStorage`] -- a Redis-based storage. -/// - [`RocksDbStorage`] -- a RocksDB-based persistent storage. /// - [`SqliteStorage`] -- an SQLite-based persistent storage. /// /// [`InMemStorage`]: crate::dispatching::dialogue::InMemStorage /// [`RedisStorage`]: crate::dispatching::dialogue::RedisStorage -/// [`RocksDbStorage`]: crate::dispatching::dialogue::RocksDbStorage /// [`SqliteStorage`]: crate::dispatching::dialogue::SqliteStorage pub trait Storage { type Error; diff --git a/crates/teloxide/src/dispatching/dialogue/storage/rocksdb_storage.rs b/crates/teloxide/src/dispatching/dialogue/storage/rocksdb_storage.rs deleted file mode 100644 index 151a9a29..00000000 --- a/crates/teloxide/src/dispatching/dialogue/storage/rocksdb_storage.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::{serializer::Serializer, Storage}; -use futures::future::BoxFuture; -use rocksdb::{DBCompressionType, DBWithThreadMode, MultiThreaded}; -use serde::{de::DeserializeOwned, Serialize}; -use std::{ - convert::Infallible, - fmt::{Debug, Display}, - str, - sync::Arc, -}; -use teloxide_core::types::ChatId; -use thiserror::Error; - -/// A persistent dialogue storage based on [RocksDb](http://rocksdb.org/). -pub struct RocksDbStorage { - db: DBWithThreadMode, - serializer: S, -} - -/// An error returned from [`RocksDbStorage`]. -#[derive(Debug, Error)] -pub enum RocksDbStorageError -where - SE: Debug + Display, -{ - #[error("dialogue serialization error: {0}")] - SerdeError(SE), - - #[error("RocksDb error: {0}")] - RocksDbError(#[from] rocksdb::Error), - - /// Returned from [`RocksDbStorage::remove_dialogue`]. - #[error("row not found")] - DialogueNotFound, -} - -impl RocksDbStorage { - pub async fn open( - path: &str, - serializer: S, - options: Option, - ) -> Result, RocksDbStorageError> { - let options = match options { - Some(opts) => opts, - None => { - let mut opts = rocksdb::Options::default(); - opts.set_compression_type(DBCompressionType::Lz4); - opts.create_if_missing(true); - opts - } - }; - - let db = DBWithThreadMode::::open(&options, path)?; - Ok(Arc::new(Self { db, serializer })) - } -} - -impl Storage for RocksDbStorage -where - S: Send + Sync + Serializer + 'static, - D: Send + Serialize + DeserializeOwned + 'static, - >::Error: Debug + Display, -{ - type Error = RocksDbStorageError<>::Error>; - - /// Returns [`RocksDbStorageError::DialogueNotFound`] if a dialogue does not - /// exist. - fn remove_dialogue( - self: Arc, - ChatId(chat_id): ChatId, - ) -> BoxFuture<'static, Result<(), Self::Error>> { - Box::pin(async move { - let key = chat_id.to_le_bytes(); - - if self.db.get(key)?.is_none() { - return Err(RocksDbStorageError::DialogueNotFound); - } - - self.db.delete(key).unwrap(); - - Ok(()) - }) - } - - fn update_dialogue( - self: Arc, - ChatId(chat_id): ChatId, - dialogue: D, - ) -> BoxFuture<'static, Result<(), Self::Error>> { - Box::pin(async move { - let d = - self.serializer.serialize(&dialogue).map_err(RocksDbStorageError::SerdeError)?; - - let key = chat_id.to_le_bytes(); - self.db.put(key, &d)?; - - Ok(()) - }) - } - - fn get_dialogue( - self: Arc, - ChatId(chat_id): ChatId, - ) -> BoxFuture<'static, Result, Self::Error>> { - Box::pin(async move { - let key = chat_id.to_le_bytes(); - self.db - .get(key)? - .map(|d| self.serializer.deserialize(&d).map_err(RocksDbStorageError::SerdeError)) - .transpose() - }) - } -} diff --git a/crates/teloxide/src/features.md b/crates/teloxide/src/features.md index a0456377..fb1680e9 100644 --- a/crates/teloxide/src/features.md +++ b/crates/teloxide/src/features.md @@ -16,13 +16,11 @@ | `native-tls` | Enables the [`native-tls`] TLS implementation (**enabled by default**). | | `rustls` | Enables the [`rustls`] TLS implementation. | | `redis-storage` | Enables the [Redis] storage support for dialogues. | -| `rocksdb-storage` | Enables the [RocksDB] storage support for dialogues. | | `sqlite-storage` | Enables the [Sqlite] storage support for dialogues. | | `cbor-serializer` | Enables the [CBOR] serializer for dialogues. | | `bincode-serializer` | Enables the [Bincode] serializer for dialogues. | [Redis]: https://redis.io/ -[RocksDB]: https://rocksdb.org/ [Sqlite]: https://www.sqlite.org/ [CBOR]: https://en.wikipedia.org/wiki/CBOR [Bincode]: https://github.com/servo/bincode diff --git a/crates/teloxide/tests/rocksdb.rs b/crates/teloxide/tests/rocksdb.rs deleted file mode 100644 index 7366a262..00000000 --- a/crates/teloxide/tests/rocksdb.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{ - fmt::{Debug, Display}, - fs, - sync::Arc, -}; -use teloxide::{ - dispatching::dialogue::{RocksDbStorage, RocksDbStorageError, Serializer, Storage}, - types::ChatId, -}; - -#[tokio::test(flavor = "multi_thread")] -async fn test_rocksdb_json() { - fs::remove_dir_all("./test_db1").ok(); - fs::create_dir("./test_db1").unwrap(); - let storage = RocksDbStorage::open( - "./test_db1/test_db1.rocksdb", - teloxide::dispatching::dialogue::serializer::Json, - None, - ) - .await - .unwrap(); - test_rocksdb(storage).await; - fs::remove_dir_all("./test_db1").unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_rocksdb_bincode() { - fs::remove_dir_all("./test_db2").ok(); - fs::create_dir("./test_db2").unwrap(); - let storage = RocksDbStorage::open( - "./test_db2/test_db2.rocksdb", - teloxide::dispatching::dialogue::serializer::Bincode, - None, - ) - .await - .unwrap(); - test_rocksdb(storage).await; - fs::remove_dir_all("./test_db2").unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_rocksdb_cbor() { - fs::remove_dir_all("./test_db3").ok(); - fs::create_dir("./test_db3").unwrap(); - let storage = RocksDbStorage::open( - "./test_db3/test_db3.rocksdb", - teloxide::dispatching::dialogue::serializer::Cbor, - None, - ) - .await - .unwrap(); - test_rocksdb(storage).await; - fs::remove_dir_all("./test_db3").unwrap(); -} - -type Dialogue = String; - -macro_rules! test_dialogues { - ($storage:expr, $_0:expr, $_1:expr, $_2:expr) => { - assert_eq!(Arc::clone(&$storage).get_dialogue(ChatId(1)).await.unwrap(), $_0); - assert_eq!(Arc::clone(&$storage).get_dialogue(ChatId(11)).await.unwrap(), $_1); - assert_eq!(Arc::clone(&$storage).get_dialogue(ChatId(256)).await.unwrap(), $_2); - }; -} - -async fn test_rocksdb(storage: Arc>) -where - S: Send + Sync + Serializer + 'static, - >::Error: Debug + Display, -{ - test_dialogues!(storage, None, None, None); - - Arc::clone(&storage).update_dialogue(ChatId(1), "ABC".to_owned()).await.unwrap(); - Arc::clone(&storage).update_dialogue(ChatId(11), "DEF".to_owned()).await.unwrap(); - Arc::clone(&storage).update_dialogue(ChatId(256), "GHI".to_owned()).await.unwrap(); - - test_dialogues!( - storage, - Some("ABC".to_owned()), - Some("DEF".to_owned()), - Some("GHI".to_owned()) - ); - - Arc::clone(&storage).remove_dialogue(ChatId(1)).await.unwrap(); - Arc::clone(&storage).remove_dialogue(ChatId(11)).await.unwrap(); - Arc::clone(&storage).remove_dialogue(ChatId(256)).await.unwrap(); - - test_dialogues!(storage, None, None, None); - - // Check that a try to remove a non-existing dialogue results in an error. - assert!(matches!( - Arc::clone(&storage).remove_dialogue(ChatId(1)).await.unwrap_err(), - RocksDbStorageError::DialogueNotFound - )); -}