├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile.toml ├── README.md ├── docker └── docker-compose.yaml ├── src ├── async_commands.rs ├── commands.rs ├── lib.rs └── types.rs └── tests ├── async_command_tests └── mod.rs ├── test_async_std_commands.rs ├── test_async_tokio_commands.rs └── test_commands.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | schedule: 7 | - cron: '0 0 * * *' # Nightly to cover cases where the server might breaks the client 8 | 9 | name: Continuous integration 10 | 11 | jobs: 12 | check: 13 | name: Check 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | override: true 22 | - uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | 26 | test: 27 | name: Test Suite 28 | runs-on: ubuntu-latest 29 | 30 | services: 31 | redis: 32 | image: redislabs/redismod 33 | options: >- 34 | --health-cmd "redis-cli ping" 35 | --health-interval 10s 36 | --health-timeout 5s 37 | --health-retries 5 38 | ports: 39 | - 6379:6379 40 | 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions-rs/toolchain@v1 44 | with: 45 | profile: minimal 46 | toolchain: stable 47 | override: true 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: test 51 | args: --all-features 52 | env: 53 | REDIS_HOST: 0.0.0.0 54 | REDIS_PORT: 6379 55 | 56 | fmt: 57 | name: Rustfmt 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: actions/checkout@v2 61 | - uses: actions-rs/toolchain@v1 62 | with: 63 | profile: minimal 64 | toolchain: stable 65 | override: true 66 | - run: rustup component add rustfmt 67 | - uses: actions-rs/cargo@v1 68 | with: 69 | command: fmt 70 | args: --all -- --check 71 | 72 | clippy: 73 | name: Clippy 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v2 77 | - uses: actions-rs/toolchain@v1 78 | with: 79 | profile: minimal 80 | toolchain: stable 81 | override: true 82 | - run: rustup component add clippy 83 | - uses: actions-rs/cargo@v1 84 | with: 85 | command: clippy 86 | args: -- -D warnings 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.idea/ 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redis_ts" 3 | version = "0.5.4" 4 | authors = ["protom "] 5 | keywords = ["redis", "database"] 6 | description = "API for Redis time series types." 7 | homepage = "https://github.com/tompro/redis_ts" 8 | repository = "https://github.com/tompro/redis_ts" 9 | documentation = "https://docs.rs/redis_ts" 10 | license = "BSD-3-Clause" 11 | readme = "README.md" 12 | edition = "2018" 13 | exclude = ["docker"] 14 | 15 | [dependencies] 16 | redis = { version = "0.25.2", optional = true } 17 | 18 | [features] 19 | default = ['redis'] 20 | tokio-comp = ['redis/tokio-comp'] 21 | async-std-comp = ['redis/async-std-comp'] 22 | 23 | [dev-dependencies] 24 | tokio = { version = "1", features = ["rt"] } 25 | futures = "0.3.5" 26 | async-std = { version = "1.8.0", features = ["tokio1"] } 27 | 28 | [[test]] 29 | name = "test_async_std_commands" 30 | required-features = ['async-std-comp'] 31 | 32 | [[test]] 33 | name = "test_async_tokio_commands" 34 | required-features = ['tokio-comp'] 35 | 36 | [package.metadata.docs.rs] 37 | all-features = true 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Thomas Profelt 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.publish] 2 | description = "Publish to crates.io" 3 | dependencies = ["gh_checks"] 4 | command = "cargo" 5 | args = ["publish", "--all-features"] 6 | 7 | [tasks.gh_checks] 8 | dependencies = [ 9 | "cargo_check", 10 | "test", 11 | "check_fmt", 12 | "clippy" 13 | ] 14 | 15 | [tasks.cargo_check] 16 | description = "Runs cargo check" 17 | command = "cargo" 18 | args = ["check"] 19 | 20 | [tasks.check_fmt] 21 | description = "Runs fmt in check mode" 22 | install_crate = "rustfmt" 23 | command = "cargo" 24 | args = ["fmt", "--all", "--", "--check"] 25 | 26 | [tasks.test] 27 | description = "Runs tests with all features" 28 | command = "cargo" 29 | args = ["test", "--all-features"] 30 | 31 | [tasks.doc] 32 | description = "Generates docs with all features" 33 | command = "cargo" 34 | args = ["doc", "--all-features"] 35 | 36 | [tasks.clippy] 37 | description = "Runs clippy" 38 | install_crate = "clippy" 39 | command = "cargo" 40 | args = ["clippy", "--", "-D", "warnings"] 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redis_ts 2 | 3 | [![crates.io](https://img.shields.io/badge/crates.io-v0.5.4-orange)](https://crates.io/crates/redis_ts) 4 | ![Continuous integration](https://github.com/tompro/redis_ts/workflows/Continuous%20integration/badge.svg) 5 | 6 | redis_ts provides a small trait with extension functions for the 7 | [redis](https://docs.rs/redis) crate to allow 8 | working with redis time series data that can be installed as 9 | a [redis module](https://oss.redislabs.com/redistimeseries). Time 10 | series commands are available as synchronous and asynchronous versions. 11 | 12 | The crate is called `redis_ts` and you can depend on it via cargo. You will 13 | also need redis in your dependencies. It has been tested against redis 0.25.2 14 | but should work with versions higher than that. 15 | 16 | ```ini 17 | [dependencies] 18 | redis = "0.25.2" 19 | redis_ts = "0.5.4" 20 | ``` 21 | 22 | Or via git: 23 | 24 | ```ini 25 | [dependencies.redis_ts] 26 | git = "https://github.com/tompro/redis_ts.git" 27 | ``` 28 | 29 | With async feature inherited from the [redis](https://docs.rs/redis) crate (either: 'async-std-comp' or 'tokio-comp): 30 | 31 | ```ini 32 | [dependencies] 33 | redis = "0.25.2" 34 | redis_ts = { version = "0.5.4", features = ['tokio-comp'] } 35 | ``` 36 | 37 | ## Synchronous usage 38 | 39 | To enable redis time series commands you simply load the 40 | redis_ts::TsCommands into the scope. All redis time series 41 | commands will then be available on your redis connection. 42 | 43 | 44 | ```rust 45 | use redis::Commands; 46 | use redis_ts::{TsCommands, TsOptions}; 47 | 48 | let client = redis::Client::open("redis://127.0.0.1/")?; 49 | let mut con = client.get_connection()?; 50 | 51 | let _:() = con.ts_create("my_ts", TsOptions::default())?; 52 | ``` 53 | 54 | ## Asynchronous usage 55 | 56 | To enable redis time series async commands you simply load the 57 | redis_ts::TsAsyncCommands into the scope. All redis time series 58 | commands will then be available on your async redis connection. 59 | 60 | ```rust 61 | use redis::AsyncCommands; 62 | use redis_ts::{AsyncTsCommands, TsOptions}; 63 | 64 | let client = redis::Client::open("redis://127.0.0.1/")?; 65 | let mut con = client.get_async_connection().await?; 66 | 67 | let _:() = con.ts_create("my_ts", TsOptions::default()).await?; 68 | ``` 69 | 70 | ## Compatibility note 71 | 72 | Versions >= 0.5 contains a breaking change in the argument list of range queries. With some recent additions in the 73 | Redis time series module the number of arguments for ts_range, ts_revrange, ts_mrange and ts_mrevrange have simply 74 | grown to long. All existing and the new arguments are now replaced by a single `TsRangeQuery` struct for which there 75 | is also a builder available. 76 | -------------------------------------------------------------------------------- /docker/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | redis: 4 | image: docker.io/redislabs/redismod 5 | ports: 6 | - "6379:6379" 7 | -------------------------------------------------------------------------------- /src/async_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::types::*; 2 | use redis::aio::ConnectionLike; 3 | use redis::{cmd, FromRedisValue, RedisFuture, ToRedisArgs}; 4 | 5 | /// Provides a high level synchronous API to work with redis time series data types. Uses some abstractions 6 | /// for easier handling of time series related redis command arguments. All commands are directly 7 | /// available on ConnectionLike types from the redis crate. 8 | /// ```rust,no_run 9 | /// # async fn run() -> redis::RedisResult<()> { 10 | /// use redis::AsyncCommands; 11 | /// use redis_ts::{AsyncTsCommands, TsOptions}; 12 | /// 13 | /// let client = redis::Client::open("redis://127.0.0.1/")?; 14 | /// let mut con = client.get_async_connection().await?; 15 | /// 16 | /// let _:() = con.ts_create("my_ts", TsOptions::default()).await?; 17 | /// let ts:u64 = con.ts_add_now("my_ts", 2.0).await?; 18 | /// let v:Option<(u64,f64)> = con.ts_get("my_ts").await?; 19 | /// # Ok(()) } 20 | /// ``` 21 | /// 22 | pub trait AsyncTsCommands: ConnectionLike + Send + Sized { 23 | /// Returns information about a redis time series key. 24 | fn ts_info<'a, K: ToRedisArgs + Send + Sync + 'a>(&'a mut self, key: K) -> RedisFuture { 25 | Box::pin(async move { cmd("TS.INFO").arg(key).query_async(self).await }) 26 | } 27 | 28 | /// Creates a new redis time series key. 29 | fn ts_create<'a, K: ToRedisArgs + Send + Sync + 'a, RV: FromRedisValue>( 30 | &'a mut self, 31 | key: K, 32 | options: TsOptions, 33 | ) -> RedisFuture { 34 | Box::pin(async move { 35 | cmd("TS.CREATE") 36 | .arg(key) 37 | .arg(options) 38 | .query_async(self) 39 | .await 40 | }) 41 | } 42 | 43 | /// Modifies an existing redis time series configuration. 44 | fn ts_alter<'a, K: ToRedisArgs + Send + Sync + 'a, RV: FromRedisValue>( 45 | &'a mut self, 46 | key: K, 47 | options: TsOptions, 48 | ) -> RedisFuture { 49 | Box::pin(async move { 50 | cmd("TS.ALTER") 51 | .arg(key) 52 | .arg(options.uncompressed(false)) 53 | .query_async(self) 54 | .await 55 | }) 56 | } 57 | 58 | /// Adds a single time series value with a timestamp to an existing redis time series. 59 | fn ts_add< 60 | 'a, 61 | K: ToRedisArgs + Send + Sync + 'a, 62 | TS: ToRedisArgs + Send + Sync + 'a, 63 | V: ToRedisArgs + Send + Sync + 'a, 64 | RV: FromRedisValue, 65 | >( 66 | &'a mut self, 67 | key: K, 68 | ts: TS, 69 | value: V, 70 | ) -> RedisFuture { 71 | Box::pin(async move { 72 | cmd("TS.ADD") 73 | .arg(key) 74 | .arg(ts) 75 | .arg(value) 76 | .query_async(self) 77 | .await 78 | }) 79 | } 80 | 81 | /// Adds a single time series value to an existing redis time series with redis system 82 | /// time as timestamp. 83 | fn ts_add_now< 84 | 'a, 85 | K: ToRedisArgs + Send + Sync + 'a, 86 | V: ToRedisArgs + Send + Sync + 'a, 87 | RV: FromRedisValue, 88 | >( 89 | &'a mut self, 90 | key: K, 91 | value: V, 92 | ) -> RedisFuture { 93 | Box::pin(async move { 94 | cmd("TS.ADD") 95 | .arg(key) 96 | .arg("*") 97 | .arg(value) 98 | .query_async(self) 99 | .await 100 | }) 101 | } 102 | 103 | /// Adds a single time series value to a redis time series. If the time series does not 104 | /// yet exist it will be created with given settings. 105 | fn ts_add_create< 106 | 'a, 107 | K: ToRedisArgs + Send + Sync + 'a, 108 | TS: ToRedisArgs + Send + Sync + 'a, 109 | V: ToRedisArgs + Send + Sync + 'a, 110 | RV: FromRedisValue, 111 | >( 112 | &'a mut self, 113 | key: K, 114 | ts: TS, 115 | value: V, 116 | options: TsOptions, 117 | ) -> RedisFuture { 118 | Box::pin(async move { 119 | cmd("TS.ADD") 120 | .arg(key) 121 | .arg(ts) 122 | .arg(value) 123 | .arg(options) 124 | .query_async(self) 125 | .await 126 | }) 127 | } 128 | 129 | /// Adds multiple time series values to an existing redis time series. 130 | fn ts_madd< 131 | 'a, 132 | K: ToRedisArgs + Send + Sync + 'a, 133 | TS: ToRedisArgs + Send + Sync + 'a, 134 | V: ToRedisArgs + Send + Sync + 'a, 135 | RV: FromRedisValue, 136 | >( 137 | &'a mut self, 138 | values: &'a [(K, TS, V)], 139 | ) -> RedisFuture { 140 | Box::pin(async move { cmd("TS.MADD").arg(values).query_async(self).await }) 141 | } 142 | 143 | /// Increments a time series value with redis system time. 144 | fn ts_incrby_now< 145 | 'a, 146 | K: ToRedisArgs + Send + Sync + 'a, 147 | V: ToRedisArgs + Send + Sync + 'a, 148 | RV: FromRedisValue, 149 | >( 150 | &'a mut self, 151 | key: K, 152 | value: V, 153 | ) -> RedisFuture { 154 | Box::pin(async move { cmd("TS.INCRBY").arg(key).arg(value).query_async(self).await }) 155 | } 156 | 157 | /// Increments a time series value with given timestamp. 158 | fn ts_incrby< 159 | 'a, 160 | K: ToRedisArgs + Send + Sync + 'a, 161 | V: ToRedisArgs + Send + Sync + 'a, 162 | TS: ToRedisArgs + Send + Sync + 'a, 163 | RV: FromRedisValue, 164 | >( 165 | &'a mut self, 166 | key: K, 167 | ts: TS, 168 | value: V, 169 | ) -> RedisFuture { 170 | Box::pin(async move { 171 | cmd("TS.INCRBY") 172 | .arg(key) 173 | .arg(value) 174 | .arg("TIMESTAMP") 175 | .arg(ts) 176 | .query_async(self) 177 | .await 178 | }) 179 | } 180 | 181 | /// Increments a time series value with timestamp. Time series will be created if it 182 | /// not already exists. 183 | fn ts_incrby_create< 184 | 'a, 185 | K: ToRedisArgs + Send + Sync + 'a, 186 | V: ToRedisArgs + Send + Sync + 'a, 187 | TS: ToRedisArgs + Send + Sync + 'a, 188 | RV: FromRedisValue, 189 | >( 190 | &'a mut self, 191 | key: K, 192 | ts: TS, 193 | value: V, 194 | options: TsOptions, 195 | ) -> RedisFuture { 196 | Box::pin(async move { 197 | cmd("TS.INCRBY") 198 | .arg(key) 199 | .arg(value) 200 | .arg("TIMESTAMP") 201 | .arg(ts) 202 | .arg(options) 203 | .query_async(self) 204 | .await 205 | }) 206 | } 207 | 208 | /// Decrements a time series value with redis system time. 209 | fn ts_decrby_now< 210 | 'a, 211 | K: ToRedisArgs + Send + Sync + 'a, 212 | V: ToRedisArgs + Send + Sync + 'a, 213 | RV: FromRedisValue, 214 | >( 215 | &'a mut self, 216 | key: K, 217 | value: V, 218 | ) -> RedisFuture { 219 | Box::pin(async move { cmd("TS.DECRBY").arg(key).arg(value).query_async(self).await }) 220 | } 221 | 222 | /// Decrements a time series value with given timestamp. 223 | fn ts_decrby< 224 | 'a, 225 | K: ToRedisArgs + Send + Sync + 'a, 226 | V: ToRedisArgs + Send + Sync + 'a, 227 | TS: ToRedisArgs + Send + Sync + 'a, 228 | RV: FromRedisValue, 229 | >( 230 | &'a mut self, 231 | key: K, 232 | ts: TS, 233 | value: V, 234 | ) -> RedisFuture { 235 | Box::pin(async move { 236 | cmd("TS.DECRBY") 237 | .arg(key) 238 | .arg(value) 239 | .arg("TIMESTAMP") 240 | .arg(ts) 241 | .query_async(self) 242 | .await 243 | }) 244 | } 245 | 246 | /// Decrements a time series value with timestamp. Time series will be created if it 247 | /// not already exists. 248 | fn ts_decrby_create< 249 | 'a, 250 | K: ToRedisArgs + Send + Sync + 'a, 251 | V: ToRedisArgs + Send + Sync + 'a, 252 | TS: ToRedisArgs + Send + Sync + 'a, 253 | RV: FromRedisValue, 254 | >( 255 | &'a mut self, 256 | key: K, 257 | ts: TS, 258 | value: V, 259 | options: TsOptions, 260 | ) -> RedisFuture { 261 | Box::pin(async move { 262 | cmd("TS.DECRBY") 263 | .arg(key) 264 | .arg(value) 265 | .arg("TIMESTAMP") 266 | .arg(ts) 267 | .arg(options) 268 | .query_async(self) 269 | .await 270 | }) 271 | } 272 | 273 | /// Creates a new redis time series compaction rule. 274 | fn ts_createrule<'a, K: ToRedisArgs + Send + Sync + 'a, RV: FromRedisValue>( 275 | &'a mut self, 276 | source_key: K, 277 | dest_key: K, 278 | aggregation_type: TsAggregationType, 279 | ) -> RedisFuture { 280 | Box::pin(async move { 281 | cmd("TS.CREATERULE") 282 | .arg(source_key) 283 | .arg(dest_key) 284 | .arg(aggregation_type) 285 | .query_async(self) 286 | .await 287 | }) 288 | } 289 | 290 | /// Deletes an existing redis time series compaction rule. 291 | fn ts_deleterule<'a, K: ToRedisArgs + Send + Sync + 'a, RV: FromRedisValue>( 292 | &'a mut self, 293 | source_key: K, 294 | dest_key: K, 295 | ) -> RedisFuture { 296 | Box::pin(async move { 297 | cmd("TS.DELETERULE") 298 | .arg(source_key) 299 | .arg(dest_key) 300 | .query_async(self) 301 | .await 302 | }) 303 | } 304 | 305 | /// Returns the latest (current) value in a redis time series. 306 | fn ts_get<'a, K: ToRedisArgs + Send + Sync + 'a, TS: FromRedisValue, V: FromRedisValue>( 307 | &'a mut self, 308 | key: K, 309 | ) -> RedisFuture> { 310 | Box::pin(async move { cmd("TS.GET").arg(key).query_async(self).await.or(Ok(None)) }) 311 | } 312 | 313 | /// Returns the latest (current) value from multiple redis time series. 314 | fn ts_mget<'a, TS: Default + FromRedisValue + 'a, V: Default + FromRedisValue + 'a>( 315 | &mut self, 316 | filter_options: TsFilterOptions, 317 | ) -> RedisFuture> { 318 | Box::pin(async move { cmd("TS.MGET").arg(filter_options).query_async(self).await }) 319 | } 320 | 321 | #[doc(hidden)] 322 | fn range< 323 | 'a, 324 | K: ToRedisArgs + Send + Sync + 'a, 325 | TS: Default + FromRedisValue + Copy, 326 | V: Default + FromRedisValue + Copy, 327 | >( 328 | &'a mut self, 329 | command: &str, 330 | key: K, 331 | query: TsRangeQuery, 332 | ) -> RedisFuture> { 333 | let mut c = cmd(command); 334 | c.arg(key).arg(query); 335 | Box::pin(async move { c.query_async(self).await }) 336 | } 337 | 338 | /// Executes a redis time series range query. 339 | fn ts_range< 340 | 'a, 341 | K: ToRedisArgs + Send + Sync + 'a, 342 | TS: Default + FromRedisValue + Copy, 343 | V: Default + FromRedisValue + Copy, 344 | >( 345 | &'a mut self, 346 | key: K, 347 | query: TsRangeQuery, 348 | ) -> RedisFuture> { 349 | self.range("TS.RANGE", key, query) 350 | } 351 | 352 | /// Executes a redis time series revrange query. 353 | fn ts_revrange< 354 | 'a, 355 | K: ToRedisArgs + Send + Sync + 'a, 356 | TS: Default + FromRedisValue + Copy, 357 | V: Default + FromRedisValue + Copy, 358 | >( 359 | &'a mut self, 360 | key: K, 361 | query: TsRangeQuery, 362 | ) -> RedisFuture> { 363 | self.range("TS.REVRANGE", key, query) 364 | } 365 | 366 | #[doc(hidden)] 367 | fn mrange<'a, TS: Default + FromRedisValue + Copy, V: Default + FromRedisValue + Copy>( 368 | &mut self, 369 | command: &str, 370 | query: TsRangeQuery, 371 | filter_options: TsFilterOptions, 372 | ) -> RedisFuture> { 373 | let mut c = cmd(command); 374 | c.arg(query).arg(filter_options); 375 | 376 | Box::pin(async move { c.query_async(self).await }) 377 | } 378 | 379 | /// Executes multiple redis time series range queries. 380 | fn ts_mrange<'a, TS: Default + FromRedisValue + Copy, V: Default + FromRedisValue + Copy>( 381 | &mut self, 382 | query: TsRangeQuery, 383 | filter_options: TsFilterOptions, 384 | ) -> RedisFuture> { 385 | self.mrange("TS.MRANGE", query, filter_options) 386 | } 387 | 388 | /// Executes multiple redis time series revrange queries. 389 | fn ts_mrevrange<'a, TS: Default + FromRedisValue + Copy, V: Default + FromRedisValue + Copy>( 390 | &mut self, 391 | query: TsRangeQuery, 392 | filter_options: TsFilterOptions, 393 | ) -> RedisFuture> { 394 | self.mrange("TS.MREVRANGE", query, filter_options) 395 | } 396 | 397 | /// Returns a filtered list of redis time series keys. 398 | fn ts_queryindex(&mut self, filter_options: TsFilterOptions) -> RedisFuture> { 399 | Box::pin(async move { 400 | cmd("TS.QUERYINDEX") 401 | .arg(filter_options.get_filters()) 402 | .query_async(self) 403 | .await 404 | }) 405 | } 406 | } 407 | 408 | impl AsyncTsCommands for T where T: Send + ConnectionLike {} 409 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::types::*; 2 | use redis::{cmd, ConnectionLike, FromRedisValue, RedisResult, ToRedisArgs}; 3 | 4 | /// Provides a high level synchronous API to work with redis time series data types. Uses some abstractions 5 | /// for easier handling of time series related redis command arguments. All commands are directly 6 | /// available on ConnectionLike types from the redis crate. 7 | /// ```rust,no_run 8 | /// # fn run() -> redis::RedisResult<()> { 9 | /// use redis::Commands; 10 | /// use redis_ts::{TsCommands, TsOptions}; 11 | /// 12 | /// let client = redis::Client::open("redis://127.0.0.1/")?; 13 | /// let mut con = client.get_connection()?; 14 | /// 15 | /// let _:() = con.ts_create("my_ts", TsOptions::default())?; 16 | /// let ts:u64 = con.ts_add_now("my_ts", 2.0)?; 17 | /// let v:Option<(u64,f64)> = con.ts_get("my_ts")?; 18 | /// # Ok(()) } 19 | /// ``` 20 | /// 21 | pub trait TsCommands: ConnectionLike + Sized { 22 | /// Creates a new redis time series key. 23 | fn ts_create( 24 | &mut self, 25 | key: K, 26 | options: TsOptions, 27 | ) -> RedisResult { 28 | cmd("TS.CREATE").arg(key).arg(options).query(self) 29 | } 30 | 31 | /// Modifies an existing redis time series configuration. 32 | fn ts_alter( 33 | &mut self, 34 | key: K, 35 | options: TsOptions, 36 | ) -> RedisResult { 37 | cmd("TS.ALTER") 38 | .arg(key) 39 | .arg(options.uncompressed(false)) 40 | .query(self) 41 | } 42 | 43 | /// Adds a single time series value with a timestamp to an existing redis time series. 44 | fn ts_add( 45 | &mut self, 46 | key: K, 47 | ts: TS, 48 | value: V, 49 | ) -> RedisResult { 50 | cmd("TS.ADD").arg(key).arg(ts).arg(value).query(self) 51 | } 52 | 53 | /// Adds a single time series value to an existing redis time series with redis system 54 | /// time as timestamp. 55 | fn ts_add_now( 56 | &mut self, 57 | key: K, 58 | value: V, 59 | ) -> RedisResult { 60 | cmd("TS.ADD").arg(key).arg("*").arg(value).query(self) 61 | } 62 | 63 | /// Adds a single time series value to a redis time series. If the time series does not 64 | /// yet exist it will be created with given settings. 65 | fn ts_add_create( 66 | &mut self, 67 | key: K, 68 | ts: TS, 69 | value: V, 70 | options: TsOptions, 71 | ) -> RedisResult { 72 | cmd("TS.ADD") 73 | .arg(key) 74 | .arg(ts) 75 | .arg(value) 76 | .arg(options) 77 | .query(self) 78 | } 79 | 80 | /// Adds multiple time series values to an existing redis time series. 81 | fn ts_madd( 82 | &mut self, 83 | values: &[(K, TS, V)], 84 | ) -> RedisResult { 85 | cmd("TS.MADD").arg(values).query(self) 86 | } 87 | 88 | /// Increments a time series value with redis system time. 89 | fn ts_incrby_now( 90 | &mut self, 91 | key: K, 92 | value: V, 93 | ) -> RedisResult { 94 | cmd("TS.INCRBY").arg(key).arg(value).query(self) 95 | } 96 | 97 | /// Increments a time series value with given timestamp. 98 | fn ts_incrby( 99 | &mut self, 100 | key: K, 101 | ts: TS, 102 | value: V, 103 | ) -> RedisResult { 104 | cmd("TS.INCRBY") 105 | .arg(key) 106 | .arg(value) 107 | .arg("TIMESTAMP") 108 | .arg(ts) 109 | .query(self) 110 | } 111 | 112 | /// Increments a time series value with timestamp. Time series will be created if it 113 | /// not already exists. 114 | fn ts_incrby_create( 115 | &mut self, 116 | key: K, 117 | ts: TS, 118 | value: V, 119 | options: TsOptions, 120 | ) -> RedisResult { 121 | cmd("TS.INCRBY") 122 | .arg(key) 123 | .arg(value) 124 | .arg("TIMESTAMP") 125 | .arg(ts) 126 | .arg(options) 127 | .query(self) 128 | } 129 | 130 | /// Decrements a time series value with redis system time. 131 | fn ts_decrby_now( 132 | &mut self, 133 | key: K, 134 | value: V, 135 | ) -> RedisResult { 136 | cmd("TS.DECRBY").arg(key).arg(value).query(self) 137 | } 138 | 139 | /// Decrements a time series value with given timestamp. 140 | fn ts_decrby( 141 | &mut self, 142 | key: K, 143 | ts: TS, 144 | value: V, 145 | ) -> RedisResult { 146 | cmd("TS.DECRBY") 147 | .arg(key) 148 | .arg(value) 149 | .arg("TIMESTAMP") 150 | .arg(ts) 151 | .query(self) 152 | } 153 | 154 | /// Decrements a time series value with timestamp. Time series will be created if it 155 | /// not already exists. 156 | fn ts_decrby_create( 157 | &mut self, 158 | key: K, 159 | ts: TS, 160 | value: V, 161 | options: TsOptions, 162 | ) -> RedisResult { 163 | cmd("TS.DECRBY") 164 | .arg(key) 165 | .arg(value) 166 | .arg("TIMESTAMP") 167 | .arg(ts) 168 | .arg(options) 169 | .query(self) 170 | } 171 | 172 | /// Creates a new redis time series compaction rule. 173 | fn ts_createrule( 174 | &mut self, 175 | source_key: K, 176 | dest_key: K, 177 | aggregation_type: TsAggregationType, 178 | ) -> RedisResult { 179 | cmd("TS.CREATERULE") 180 | .arg(source_key) 181 | .arg(dest_key) 182 | .arg(aggregation_type) 183 | .query(self) 184 | } 185 | 186 | /// Deletes an existing redis time series compaction rule. 187 | fn ts_deleterule( 188 | &mut self, 189 | source_key: K, 190 | dest_key: K, 191 | ) -> RedisResult { 192 | cmd("TS.DELETERULE") 193 | .arg(source_key) 194 | .arg(dest_key) 195 | .query(self) 196 | } 197 | 198 | #[doc(hidden)] 199 | fn range( 200 | &mut self, 201 | command: &str, 202 | key: K, 203 | query: TsRangeQuery, 204 | ) -> RedisResult> { 205 | let mut c = cmd(command); 206 | c.arg(key).arg(query).query(self) 207 | } 208 | 209 | /// Executes a redis time series range query. 210 | fn ts_range( 211 | &mut self, 212 | key: K, 213 | query: TsRangeQuery, 214 | ) -> RedisResult> { 215 | self.range("TS.RANGE", key, query) 216 | } 217 | 218 | /// Executes a redis time series revrange query. 219 | fn ts_revrange( 220 | &mut self, 221 | key: K, 222 | query: TsRangeQuery, 223 | ) -> RedisResult> { 224 | self.range("TS.REVRANGE", key, query) 225 | } 226 | 227 | #[doc(hidden)] 228 | fn mrange( 229 | &mut self, 230 | command: &str, 231 | query: TsRangeQuery, 232 | filter_options: TsFilterOptions, 233 | ) -> RedisResult> { 234 | let mut c = cmd(command); 235 | c.arg(query).arg(filter_options); 236 | c.query(self) 237 | } 238 | 239 | /// Executes multiple redis time series range queries. 240 | fn ts_mrange( 241 | &mut self, 242 | query: TsRangeQuery, 243 | filter_options: TsFilterOptions, 244 | ) -> RedisResult> { 245 | self.mrange("TS.MRANGE", query, filter_options) 246 | } 247 | 248 | /// Executes multiple redis time series revrange queries. 249 | fn ts_mrevrange( 250 | &mut self, 251 | query: TsRangeQuery, 252 | filter_options: TsFilterOptions, 253 | ) -> RedisResult> { 254 | self.mrange("TS.MREVRANGE", query, filter_options) 255 | } 256 | 257 | /// Returns the latest (current) value in a redis time series. 258 | fn ts_get( 259 | &mut self, 260 | key: K, 261 | ) -> RedisResult> { 262 | cmd("TS.GET").arg(key).query(self).or(Ok(None)) 263 | } 264 | 265 | /// Returns the latest (current) value from multiple redis time series. 266 | fn ts_mget( 267 | &mut self, 268 | filter_options: TsFilterOptions, 269 | ) -> RedisResult> { 270 | cmd("TS.MGET").arg(filter_options).query(self) 271 | } 272 | 273 | /// Returns information about a redis time series key. 274 | fn ts_info(&mut self, key: K) -> RedisResult { 275 | cmd("TS.INFO").arg(key).query(self) 276 | } 277 | 278 | /// Returns a filtered list of redis time series keys. 279 | fn ts_queryindex(&mut self, filter_options: TsFilterOptions) -> RedisResult> { 280 | cmd("TS.QUERYINDEX") 281 | .arg(filter_options.get_filters()) 282 | .query(self) 283 | } 284 | } 285 | 286 | impl TsCommands for T where T: ConnectionLike {} 287 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! redis_ts provides a small trait with extension functions for the 2 | //! [redis](https://docs.rs/redis) crate to allow 3 | //! working with redis time series data that can be installed as 4 | //! a [redis module](https://oss.redislabs.com/redistimeseries). Time 5 | //! series commands are available as synchronous and asynchronous versions. 6 | //! 7 | //! The crate is called `redis_ts` and you can depend on it via cargo. You will 8 | //! also need redis in your dependencies. It has been tested against redis 0.25.2 9 | //! but should work with versions higher than that. 10 | //! 11 | //! ```ini 12 | //! [dependencies] 13 | //! redis = "0.25.2" 14 | //! redis_ts = "0.5.4" 15 | //! ``` 16 | //! 17 | //! Or via git: 18 | //! 19 | //! ```ini 20 | //! [dependencies.redis_ts] 21 | //! git = "https://github.com/tompro/redis_ts.git" 22 | //! ``` 23 | //! 24 | //! With async feature inherited from the [redis](https://docs.rs/redis) 25 | //! crate (either: 'async-std-comp' or 'tokio-comp): 26 | //! ```ini 27 | //! [dependencies] 28 | //! redis = "0.25.2" 29 | //! redis_ts = { version = "0.5.4", features = ['tokio-comp'] } 30 | //! ``` 31 | //! 32 | //! # Synchronous usage 33 | //! 34 | //! To enable redis time series commands you simply load the 35 | //! redis_ts::TsCommands into the scope. All redis time series 36 | //! commands will then be available on your redis connection. 37 | //! 38 | //! 39 | //! ```rust,no_run 40 | //! # fn run() -> redis::RedisResult<()> { 41 | //! use redis::Commands; 42 | //! use redis_ts::{TsCommands, TsOptions}; 43 | //! 44 | //! let client = redis::Client::open("redis://127.0.0.1/")?; 45 | //! let mut con = client.get_connection()?; 46 | //! 47 | //! let _:() = con.ts_create("my_ts", TsOptions::default())?; 48 | //! # Ok(()) } 49 | //! ``` 50 | //! 51 | //! 52 | //! # Asynchronous usage 53 | //! 54 | //! To enable redis time series async commands you simply load the 55 | //! redis_ts::TsAsyncCommands into the scope. All redis time series 56 | //! commands will then be available on your async redis connection. 57 | //! 58 | //! ```rust,no_run 59 | //! # #[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] 60 | //! # async fn run() -> redis::RedisResult<()> { 61 | //! use redis::AsyncCommands; 62 | //! use redis_ts::{AsyncTsCommands, TsOptions}; 63 | //! 64 | //! let client = redis::Client::open("redis://127.0.0.1/")?; 65 | //! let mut con = client.get_async_connection().await?; 66 | //! 67 | //! let _:() = con.ts_create("my_ts", TsOptions::default()).await?; 68 | //! # Ok(()) } 69 | //! ``` 70 | //! 71 | //! # Supported commands 72 | //! 73 | //! The following examples work with the synchronous and asynchronous 74 | //! API. For simplicity all examples will use the synchronous API. To 75 | //! use them async simply run them whithin an async function and append 76 | //! the .await after the command call. 77 | //! 78 | //! ## TS.CREATE 79 | //! Creates new time series keys. TsOptions can help you build the time 80 | //! series configuration you want to have. 81 | //! 82 | //! ```rust,no_run 83 | //! # fn run() -> redis::RedisResult<()> { 84 | //! # use redis::Commands; 85 | //! # use redis_ts::{TsCommands, TsOptions, TsDuplicatePolicy}; 86 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 87 | //! # let mut con = client.get_connection()?; 88 | //! let my_opts = TsOptions::default() 89 | //! .retention_time(60000) 90 | //! .uncompressed(false) 91 | //! .duplicate_policy(TsDuplicatePolicy::Last) 92 | //! .label("component", "engine") 93 | //! .label("sensor", "temperature"); 94 | //! 95 | //! let _:() = con.ts_create("my_engine", my_opts)?; 96 | //! # Ok(()) } 97 | //! ``` 98 | //! 99 | //! ## TS.ALTER 100 | //! Modifies existing time series keys. Note: You can not modify the uncompressed 101 | //! option of an existing time series so the flag will be ignored. 102 | //! 103 | //! ```rust,no_run 104 | //! # fn run() -> redis::RedisResult<()> { 105 | //! # use redis::Commands; 106 | //! # use redis_ts::{TsCommands, TsOptions}; 107 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 108 | //! # let mut con = client.get_connection()?; 109 | //! let my_opts = TsOptions::default() 110 | //! .retention_time(600000) 111 | //! .label("component", "spark_plug") 112 | //! .label("sensor", "temperature"); 113 | //! 114 | //! let _:() = con.ts_alter("my_engine", my_opts)?; 115 | //! # Ok(()) } 116 | //! ``` 117 | //! 118 | //! ## TS.ADD 119 | //! Add a value to time series. When providing time series options with 120 | //! the add command the series will be created if it does not yet exist. 121 | //! 122 | //! ```rust,no_run 123 | //! # fn run() -> redis::RedisResult<()> { 124 | //! # use redis::Commands; 125 | //! # use redis_ts::{TsCommands, TsOptions}; 126 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 127 | //! # let mut con = client.get_connection()?; 128 | //! /// With a timestamp 129 | //! let ts:u64 = con.ts_add("my_engine", 123456789, 36.1)?; 130 | //! 131 | //! /// Auto redis timestamp 132 | //! let now:u64 = con.ts_add_now("my_engine", 36.2)?; 133 | //! 134 | //! /// Add with auto create. 135 | //! let my_opts = TsOptions::default() 136 | //! .retention_time(600000) 137 | //! .label("component", "spark_plug") 138 | //! .label("sensor", "temperature"); 139 | //! 140 | //! let create_ts:u64 = con.ts_add_create("my_engine", "*", 35.7, my_opts)?; 141 | //! # Ok(()) } 142 | //! ``` 143 | //! 144 | //! ## TS.MADD 145 | //! Add multiple values to one or multiple time series. 146 | //! 147 | //! ```rust,no_run 148 | //! # fn run() -> redis::RedisResult<()> { 149 | //! # use redis::Commands; 150 | //! # use redis_ts::{TsCommands, TsOptions}; 151 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 152 | //! # let mut con = client.get_connection()?; 153 | //! let r:Vec = con.ts_madd(&[ 154 | //! ("my_engine", 1234, 36.0), 155 | //! ("other_engine", 4321, 33.9) 156 | //! ])?; 157 | //! # Ok(()) } 158 | //! ``` 159 | //! 160 | //! ## TS.INCRBY 161 | //! Increment a time series value. 162 | //! 163 | //! ```rust,no_run 164 | //! # fn run() -> redis::RedisResult<()> { 165 | //! # use redis::Commands; 166 | //! # use redis_ts::{TsCommands, TsOptions}; 167 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 168 | //! # let mut con = client.get_connection()?; 169 | //! /// With a timestamp 170 | //! let ts:u64 = con.ts_incrby("my_engine", 123456789, 2)?; 171 | //! 172 | //! /// Auto redis timestamp 173 | //! let now:u64 = con.ts_incrby_now("my_engine", 7.0)?; 174 | //! 175 | //! /// With auto create. 176 | //! let my_opts = TsOptions::default() 177 | //! .retention_time(600000) 178 | //! .label("component", "spark_plug") 179 | //! .label("sensor", "temperature"); 180 | //! 181 | //! let create_ts:u64 = con.ts_incrby_create("my_engine", "*", 16.97, my_opts)?; 182 | //! # Ok(()) } 183 | //! ``` 184 | //! 185 | //! ## TS.DECRBY 186 | //! Decrement a time series value. 187 | //! 188 | //! ```rust,no_run 189 | //! # fn run() -> redis::RedisResult<()> { 190 | //! # use redis::Commands; 191 | //! # use redis_ts::{TsCommands, TsOptions}; 192 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 193 | //! # let mut con = client.get_connection()?; 194 | //! /// With a timestamp 195 | //! let ts:u64 = con.ts_decrby("my_engine", 123456789, 2)?; 196 | //! 197 | //! /// Auto redis timestamp 198 | //! let now:u64 = con.ts_decrby_now("my_engine", 7.0)?; 199 | //! 200 | //! /// With auto create. 201 | //! let my_opts = TsOptions::default() 202 | //! .retention_time(600000) 203 | //! .label("component", "spark_plug") 204 | //! .label("sensor", "temperature"); 205 | //! 206 | //! let create_ts:u64 = con.ts_decrby_create("my_engine", "*", 16.97, my_opts)?; 207 | //! # Ok(()) } 208 | //! ``` 209 | //! 210 | //! ## TS.CREATERULE 211 | //! Create time series compaction rules. 212 | //! 213 | //! ```rust,no_run 214 | //! # fn run() -> redis::RedisResult<()> { 215 | //! # use redis::Commands; 216 | //! # use redis_ts::{TsCommands, TsAggregationType}; 217 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 218 | //! # let mut con = client.get_connection()?; 219 | //! let _:() = con.ts_createrule("my_engine", "my_engine_avg", TsAggregationType::Avg(5000))?; 220 | //! # Ok(()) } 221 | //! ``` 222 | //! 223 | //! ## TS.DELETERULE 224 | //! Delete time series compaction rules. 225 | //! 226 | //! ```rust,no_run 227 | //! # fn run() -> redis::RedisResult<()> { 228 | //! # use redis::Commands; 229 | //! # use redis_ts::{TsCommands, TsOptions}; 230 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 231 | //! # let mut con = client.get_connection()?; 232 | //! let _:() = con.ts_deleterule("my_engine", "my_engine_avg")?; 233 | //! # Ok(()) } 234 | //! ``` 235 | //! 236 | //! ## TS.RANGE/TS.REVRANGE 237 | //! Query for a range of time series data. 238 | //! 239 | //! ```rust,no_run 240 | //! fn run() -> redis::RedisResult<()> { 241 | //! # use redis::Commands; 242 | //! # use redis_ts::{TsCommands, TsRange, TsAggregationType, TsRangeQuery}; 243 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 244 | //! # let mut con = client.get_connection()?; 245 | //! let first_three_avg:TsRange = con.ts_range( 246 | //! "my_engine", 247 | //! TsRangeQuery::default() 248 | //! .count(3) 249 | //! .aggregation_type(TsAggregationType::Avg(5000)) 250 | //! )?; 251 | //! 252 | //! let range_raw:TsRange = con.ts_range( 253 | //! "my_engine", 254 | //! TsRangeQuery::default().from(1234).to(5678) 255 | //! )?; 256 | //! 257 | //! let rev_range_raw:TsRange = con.ts_revrange( 258 | //! "my_engine", 259 | //! TsRangeQuery::default().from(1234).to(5678) 260 | //! )?; 261 | //! # Ok(()) } 262 | //! ``` 263 | //! 264 | //! ## TS.MRANGE/TS.MREVRANGE 265 | //! Batch query multiple ranges of time series data. 266 | //! 267 | //! ```rust,no_run 268 | //! # fn run() -> redis::RedisResult<()> { 269 | //! # use redis::Commands; 270 | //! # use redis_ts::{TsCommands, TsMrange, TsAggregationType, TsFilterOptions, TsRangeQuery}; 271 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 272 | //! # let mut con = client.get_connection()?; 273 | //! let first_three_avg:TsMrange = con.ts_mrange( 274 | //! TsRangeQuery::default().count(3).aggregation_type(TsAggregationType::Avg(5000)), 275 | //! TsFilterOptions::default().equals("sensor", "temperature") 276 | //! )?; 277 | //! 278 | //! let range_raw:TsMrange = con.ts_mrange( 279 | //! TsRangeQuery::default().from(1234).to(5678), 280 | //! TsFilterOptions::default().equals("sensor", "temperature") 281 | //! )?; 282 | //! 283 | //! let rev_range_raw:TsMrange = con.ts_mrevrange( 284 | //! TsRangeQuery::default().from(1234).to(5678), 285 | //! TsFilterOptions::default().equals("sensor", "temperature") 286 | //! )?; 287 | //! # Ok(()) } 288 | //! ``` 289 | //! 290 | //! ## TS.GET 291 | //! Get the most recent value of a time series. 292 | //! 293 | //! ```rust,no_run 294 | //! # fn run() -> redis::RedisResult<()> { 295 | //! # use redis::Commands; 296 | //! # use redis_ts::{TsCommands}; 297 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 298 | //! # let mut con = client.get_connection()?; 299 | //! let latest:Option<(u64,f64)> = con.ts_get("my_engine")?; 300 | //! # Ok(()) } 301 | //! ``` 302 | //! 303 | //! ## TS.MGET 304 | //! Get the most recent value of multiple time series. 305 | //! 306 | //! ```rust,no_run 307 | //! # fn run() -> redis::RedisResult<()> { 308 | //! # use redis::Commands; 309 | //! # use redis_ts::{TsCommands, TsMget, TsFilterOptions}; 310 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 311 | //! # let mut con = client.get_connection()?; 312 | //! let temperature:TsMget = con.ts_mget( 313 | //! TsFilterOptions::default().equals("sensor", "temperature").with_labels(true) 314 | //! )?; 315 | //! # Ok(()) } 316 | //! ``` 317 | //! 318 | //! ## TS.INFO 319 | //! Get information about a time series key. 320 | //! 321 | //! ```rust,no_run 322 | //! # fn run() -> redis::RedisResult<()> { 323 | //! # use redis::Commands; 324 | //! # use redis_ts::{TsCommands,TsInfo}; 325 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 326 | //! # let mut con = client.get_connection()?; 327 | //! let info:TsInfo = con.ts_info("my_engine")?; 328 | //! # Ok(()) } 329 | //! ``` 330 | //! 331 | //! ## TS.QUERYINDEX 332 | //! Get the keys of time series filtered by given filter. 333 | //! 334 | //! ```rust,no_run 335 | //! # fn run() -> redis::RedisResult<()> { 336 | //! # use redis::Commands; 337 | //! # use redis_ts::{TsCommands,TsFilterOptions}; 338 | //! # let client = redis::Client::open("redis://127.0.0.1/")?; 339 | //! # let mut con = client.get_connection()?; 340 | //! let index:Vec = con.ts_queryindex( 341 | //! TsFilterOptions::default().equals("sensor", "temperature") 342 | //! )?; 343 | //! # Ok(()) } 344 | //! ``` 345 | //! 346 | extern crate core; 347 | 348 | #[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] 349 | pub use crate::async_commands::AsyncTsCommands; 350 | 351 | pub use crate::commands::TsCommands; 352 | 353 | pub use crate::types::{ 354 | TsAggregationType, TsAlign, TsBucketTimestamp, TsDuplicatePolicy, TsFilterOptions, TsInfo, 355 | TsMget, TsMrange, TsMrangeEntry, TsOptions, TsRange, TsRangeQuery, 356 | }; 357 | 358 | #[cfg(any(feature = "tokio-comp", feature = "async-std-comp"))] 359 | mod async_commands; 360 | 361 | mod commands; 362 | mod types; 363 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use redis::{ 2 | from_redis_value, FromRedisValue, RedisError, RedisResult, RedisWrite, ToRedisArgs, Value, 3 | }; 4 | use std::collections::HashMap; 5 | use std::fmt::{Debug, Display}; 6 | use std::str; 7 | 8 | /// Allows you to specify a redis time series aggreation with a time 9 | /// bucket. 10 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 11 | pub enum TsAggregationType { 12 | Avg(u64), 13 | Sum(u64), 14 | Min(u64), 15 | Max(u64), 16 | Range(u64), 17 | Count(u64), 18 | First(u64), 19 | Last(u64), 20 | StdP(u64), 21 | StdS(u64), 22 | VarP(u64), 23 | VarS(u64), 24 | Twa(u64), 25 | } 26 | 27 | impl ToRedisArgs for TsAggregationType { 28 | fn write_redis_args(&self, out: &mut W) 29 | where 30 | W: ?Sized + RedisWrite, 31 | { 32 | let (t, val) = match *self { 33 | TsAggregationType::Avg(v) => ("avg", v), 34 | TsAggregationType::Sum(v) => ("sum", v), 35 | TsAggregationType::Min(v) => ("min", v), 36 | TsAggregationType::Max(v) => ("max", v), 37 | TsAggregationType::Range(v) => ("range", v), 38 | TsAggregationType::Count(v) => ("count", v), 39 | TsAggregationType::First(v) => ("first", v), 40 | TsAggregationType::Last(v) => ("last", v), 41 | TsAggregationType::StdP(v) => ("std.p", v), 42 | TsAggregationType::StdS(v) => ("std.s", v), 43 | TsAggregationType::VarP(v) => ("var.p", v), 44 | TsAggregationType::VarS(v) => ("var.s", v), 45 | TsAggregationType::Twa(v) => ("twa", v), 46 | }; 47 | 48 | out.write_arg(b"AGGREGATION"); 49 | out.write_arg(t.as_bytes()); 50 | val.write_redis_args(out); 51 | } 52 | } 53 | 54 | ///A time bucket alignment control for AGGREGATION. It controls the time bucket 55 | /// timestamps by changing the reference timestamp on which a bucket is defined. 56 | /// - Start: The reference timestamp will be the query start interval time. 57 | /// - End: The reference timestamp will be the query end interval time. 58 | /// - Ts(time): A specific timestamp: align the reference timestamp to a specific time. 59 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 60 | pub enum TsAlign { 61 | Start, 62 | End, 63 | Ts(u64), 64 | } 65 | 66 | impl ToRedisArgs for TsAlign { 67 | fn write_redis_args(&self, out: &mut W) 68 | where 69 | W: ?Sized + RedisWrite, 70 | { 71 | out.write_arg(b"ALIGN"); 72 | match self { 73 | TsAlign::Start => out.write_arg(b"-"), 74 | TsAlign::End => out.write_arg(b"+"), 75 | TsAlign::Ts(v) => v.write_redis_args(out), 76 | } 77 | } 78 | } 79 | 80 | /// Bucket timestamp controls how bucket timestamps are reported. 81 | /// - Low: the bucket's start time (default). 82 | /// - High: the bucket's end time. 83 | /// - Mid: the bucket's mid time (rounded down if not an integer). 84 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 85 | pub enum TsBucketTimestamp { 86 | Low, 87 | High, 88 | Mid, 89 | } 90 | 91 | impl ToRedisArgs for TsBucketTimestamp { 92 | fn write_redis_args(&self, out: &mut W) 93 | where 94 | W: ?Sized + RedisWrite, 95 | { 96 | out.write_arg(b"BUCKETTIMESTAMP"); 97 | match self { 98 | TsBucketTimestamp::Low => out.write_arg(b"-"), 99 | TsBucketTimestamp::High => out.write_arg(b"+"), 100 | TsBucketTimestamp::Mid => out.write_arg(b"~"), 101 | } 102 | } 103 | } 104 | 105 | #[derive(Clone, Debug, Copy)] 106 | pub enum Integer { 107 | Usize(usize), 108 | U8(u8), 109 | U16(u16), 110 | U32(u32), 111 | U64(u64), 112 | Isize(isize), 113 | I8(i8), 114 | I16(i16), 115 | I32(i32), 116 | I64(i64), 117 | } 118 | 119 | impl ToRedisArgs for Integer { 120 | fn write_redis_args(&self, out: &mut W) 121 | where 122 | W: ?Sized + RedisWrite, 123 | { 124 | match self { 125 | Integer::Usize(v) => v.write_redis_args(out), 126 | Integer::U8(v) => v.write_redis_args(out), 127 | Integer::U16(v) => v.write_redis_args(out), 128 | Integer::U32(v) => v.write_redis_args(out), 129 | Integer::U64(v) => v.write_redis_args(out), 130 | Integer::Isize(v) => v.write_redis_args(out), 131 | Integer::I8(v) => v.write_redis_args(out), 132 | Integer::I16(v) => v.write_redis_args(out), 133 | Integer::I32(v) => v.write_redis_args(out), 134 | Integer::I64(v) => v.write_redis_args(out), 135 | } 136 | } 137 | } 138 | 139 | impl From for Integer { 140 | fn from(value: usize) -> Self { 141 | Integer::Usize(value) 142 | } 143 | } 144 | 145 | impl From for Integer { 146 | fn from(value: u8) -> Self { 147 | Integer::U8(value) 148 | } 149 | } 150 | 151 | impl From for Integer { 152 | fn from(value: u16) -> Self { 153 | Integer::U16(value) 154 | } 155 | } 156 | 157 | impl From for Integer { 158 | fn from(value: u32) -> Self { 159 | Integer::U32(value) 160 | } 161 | } 162 | 163 | impl From for Integer { 164 | fn from(value: u64) -> Self { 165 | Integer::U64(value) 166 | } 167 | } 168 | 169 | impl From for Integer { 170 | fn from(value: isize) -> Self { 171 | Integer::Isize(value) 172 | } 173 | } 174 | 175 | impl From for Integer { 176 | fn from(value: i8) -> Self { 177 | Integer::I8(value) 178 | } 179 | } 180 | 181 | impl From for Integer { 182 | fn from(value: i16) -> Self { 183 | Integer::I16(value) 184 | } 185 | } 186 | 187 | impl From for Integer { 188 | fn from(value: i32) -> Self { 189 | Integer::I32(value) 190 | } 191 | } 192 | 193 | impl From for Integer { 194 | fn from(value: i64) -> Self { 195 | Integer::I64(value) 196 | } 197 | } 198 | 199 | /// Let's you build a ts range query with all options via a builder pattern: 200 | /// 201 | /// ```rust 202 | /// use redis_ts::{TsAggregationType, TsBucketTimestamp, TsRangeQuery}; 203 | /// let query = TsRangeQuery::default() 204 | /// .from(1234) 205 | /// .to(5678) 206 | /// .latest(true) 207 | /// .filter_by_value(1.0, 5.0) 208 | /// .aggregation_type(TsAggregationType::Avg(5000)) 209 | /// .bucket_timestamp(TsBucketTimestamp::High) 210 | /// .empty(true); 211 | /// ``` 212 | /// 213 | #[derive(Default, Debug, Clone)] 214 | pub struct TsRangeQuery { 215 | from: Option, 216 | to: Option, 217 | latest: bool, 218 | filter_by_ts: Vec, 219 | filter_by_value: Option<(f64, f64)>, 220 | count: Option, 221 | align: Option, 222 | aggregation_type: Option, 223 | bucket_timestamp: Option, 224 | empty: bool, 225 | } 226 | 227 | impl TsRangeQuery { 228 | /// Start timestamp of the series to query. Defaults to '-' (earliest sample) 229 | /// if left empty. 230 | pub fn from>(mut self, from: T) -> Self { 231 | self.from = Some(Into::into(from)); 232 | self 233 | } 234 | 235 | /// End timestamp of the series to query. Defaults to '+' (latest sample) 236 | /// if left empty. 237 | pub fn to>(mut self, to: T) -> Self { 238 | self.to = Some(Into::into(to)); 239 | self 240 | } 241 | 242 | /// Will enable the LATEST flag on the query. 243 | pub fn latest(mut self, latest: bool) -> Self { 244 | self.latest = latest; 245 | self 246 | } 247 | 248 | /// Will enable the FILTER_BY_TS option with given timestamps. Will only 249 | /// be added if the given Vec contains any ts values. 250 | pub fn filter_by_ts>(mut self, ts: Vec) -> Self { 251 | self.filter_by_ts = ts.into_iter().map(|v| Into::into(v)).collect(); 252 | self 253 | } 254 | 255 | /// Will enable the FILTER_BY_VALUE option with given min and max values. 256 | pub fn filter_by_value(mut self, min: f64, max: f64) -> Self { 257 | self.filter_by_value = Some((min, max)); 258 | self 259 | } 260 | 261 | /// Determines the max amount of returned samples. 262 | pub fn count(mut self, count: u64) -> Self { 263 | self.count = Some(count); 264 | self 265 | } 266 | 267 | /// Controls the aggregation alignment. Will only be added if the query actually 268 | /// contains aggregation params. 269 | pub fn align(mut self, align: TsAlign) -> Self { 270 | self.align = Some(align); 271 | self 272 | } 273 | 274 | /// The type of aggregation to be performed on the series. 275 | pub fn aggregation_type(mut self, aggregation_type: TsAggregationType) -> Self { 276 | self.aggregation_type = Some(aggregation_type); 277 | self 278 | } 279 | 280 | /// Controls reporting of aggregation bucket timestamps. Will only be added if the 281 | /// query actually contains aggregation params. 282 | pub fn bucket_timestamp(mut self, bucket_timestamp: TsBucketTimestamp) -> Self { 283 | self.bucket_timestamp = Some(bucket_timestamp); 284 | self 285 | } 286 | 287 | /// Enables the EMPTY flag on the query. 288 | pub fn empty(mut self, empty: bool) -> Self { 289 | self.empty = empty; 290 | self 291 | } 292 | } 293 | 294 | impl ToRedisArgs for TsRangeQuery { 295 | fn write_redis_args(&self, out: &mut W) 296 | where 297 | W: ?Sized + RedisWrite, 298 | { 299 | if let Some(ref from) = self.from { 300 | from.write_redis_args(out); 301 | } else { 302 | out.write_arg(b"-"); 303 | } 304 | 305 | if let Some(ref to) = self.to { 306 | to.write_redis_args(out); 307 | } else { 308 | out.write_arg(b"+"); 309 | } 310 | 311 | if self.latest { 312 | out.write_arg(b"LATEST"); 313 | } 314 | 315 | if !self.filter_by_ts.is_empty() { 316 | out.write_arg(b"FILTER_BY_TS"); 317 | for ts in self.filter_by_ts.iter() { 318 | ts.write_redis_args(out); 319 | } 320 | } 321 | 322 | if let Some((min, max)) = self.filter_by_value { 323 | out.write_arg(b"FILTER_BY_VALUE"); 324 | min.write_redis_args(out); 325 | max.write_redis_args(out); 326 | } 327 | 328 | if let Some(count) = self.count { 329 | out.write_arg(b"COUNT"); 330 | count.write_redis_args(out); 331 | } 332 | 333 | if let Some(ref agg) = self.aggregation_type { 334 | if let Some(ref align) = self.align { 335 | align.write_redis_args(out); 336 | } 337 | 338 | agg.write_redis_args(out); 339 | 340 | if let Some(ref bkt_ts) = self.bucket_timestamp { 341 | bkt_ts.write_redis_args(out); 342 | } 343 | 344 | if self.empty { 345 | out.write_arg(b"EMPTY") 346 | } 347 | } 348 | } 349 | } 350 | 351 | /// Different options for handling inserts of duplicate values. Block 352 | /// is the behaviour redis time series was using before preventing all 353 | /// inserts of values older or equal to latest value in series. Fist 354 | /// will simply ignore the new value (as opposed to returning an error), 355 | /// Last will use the new value, Min the lower and Max the higher value. 356 | #[derive(PartialEq, Eq, Clone, Debug)] 357 | pub enum TsDuplicatePolicy { 358 | Block, 359 | First, 360 | Last, 361 | Min, 362 | Max, 363 | Other(String), 364 | } 365 | 366 | impl ToRedisArgs for TsDuplicatePolicy { 367 | fn write_redis_args(&self, out: &mut W) 368 | where 369 | W: ?Sized + RedisWrite, 370 | { 371 | let policy = match self { 372 | TsDuplicatePolicy::Block => "BLOCK", 373 | TsDuplicatePolicy::First => "FIRST", 374 | TsDuplicatePolicy::Last => "LAST", 375 | TsDuplicatePolicy::Min => "MIN", 376 | TsDuplicatePolicy::Max => "MAX", 377 | TsDuplicatePolicy::Other(v) => v.as_str(), 378 | }; 379 | out.write_arg(b"DUPLICATE_POLICY"); 380 | out.write_arg(policy.as_bytes()); 381 | } 382 | } 383 | 384 | impl FromRedisValue for TsDuplicatePolicy { 385 | fn from_redis_value(v: &Value) -> RedisResult { 386 | let string: String = from_redis_value(v)?; 387 | let res = match string.as_str() { 388 | "block" => TsDuplicatePolicy::Block, 389 | "first" => TsDuplicatePolicy::First, 390 | "last" => TsDuplicatePolicy::Last, 391 | "min" => TsDuplicatePolicy::Min, 392 | "max" => TsDuplicatePolicy::Max, 393 | v => TsDuplicatePolicy::Other(v.to_string()), 394 | }; 395 | Ok(res) 396 | } 397 | } 398 | 399 | /// Options for a redis time series key. Can be used in multiple redis 400 | /// time series calls (CREATE, ALTER, ADD, ...). The uncompressed option 401 | /// will only be respected in a TS.CREATE. 402 | #[derive(Default, Debug, Clone)] 403 | pub struct TsOptions { 404 | retention_time: Option, 405 | uncompressed: bool, 406 | labels: Option>>, 407 | duplicate_policy: Option, 408 | chunk_size: Option, 409 | } 410 | 411 | /// TsOptions allows you to build up your redis time series configuration. It 412 | /// supports default and a builder pattern so you can use it the following way: 413 | /// 414 | /// ```rust 415 | /// use redis_ts::TsOptions; 416 | /// use redis_ts::TsDuplicatePolicy; 417 | /// 418 | /// let opts:TsOptions = TsOptions::default() 419 | /// .retention_time(60000) 420 | /// .uncompressed(false) 421 | /// .chunk_size(16000) 422 | /// .duplicate_policy(TsDuplicatePolicy::Last) 423 | /// .label("label_1", "value_1") 424 | /// .label("label_2", "value_2"); 425 | /// ``` 426 | /// 427 | impl TsOptions { 428 | /// Specifies the retention time in millis for this time series options. 429 | pub fn retention_time(mut self, time: u64) -> Self { 430 | self.retention_time = Some(time); 431 | self 432 | } 433 | 434 | /// Switches this time series into uncompressed mode. Note that 435 | /// redis ts only respects this flag in TS.CREATE. All other options 436 | /// usages will ignore this flag. 437 | pub fn uncompressed(mut self, value: bool) -> Self { 438 | self.uncompressed = value; 439 | self 440 | } 441 | 442 | /// Resets all labels to the items in given labels. All labels that 443 | /// where previously present will be removed. If the labels are empty 444 | /// no labels will be used for the time series. 445 | pub fn labels(mut self, labels: Vec<(&str, &str)>) -> Self { 446 | if !labels.is_empty() { 447 | self.labels = Some(ToRedisArgs::to_redis_args(&labels)); 448 | } else { 449 | self.labels = None; 450 | } 451 | self 452 | } 453 | 454 | /// Adds a single label to this time series options. 455 | pub fn label(mut self, name: &str, value: &str) -> Self { 456 | let mut l = ToRedisArgs::to_redis_args(&vec![(name, value)]); 457 | let mut res: Vec> = vec![]; 458 | if let Some(mut cur) = self.labels { 459 | res.append(&mut cur); 460 | } 461 | res.append(&mut l); 462 | self.labels = Some(res); 463 | self 464 | } 465 | 466 | /// Overrides the servers default dplicatePoliciy. 467 | pub fn duplicate_policy(mut self, policy: TsDuplicatePolicy) -> Self { 468 | self.duplicate_policy = Some(policy); 469 | self 470 | } 471 | 472 | /// Sets the allocation size for data in bytes. 473 | pub fn chunk_size(mut self, size: u64) -> Self { 474 | self.chunk_size = Some(size); 475 | self 476 | } 477 | } 478 | 479 | impl ToRedisArgs for TsOptions { 480 | fn write_redis_args(&self, out: &mut W) 481 | where 482 | W: ?Sized + RedisWrite, 483 | { 484 | if let Some(ref rt) = self.retention_time { 485 | out.write_arg(b"RETENTION"); 486 | out.write_arg(format!("{rt}").as_bytes()); 487 | } 488 | 489 | if self.uncompressed { 490 | out.write_arg(b"UNCOMPRESSED"); 491 | } 492 | 493 | if let Some(ref policy) = self.duplicate_policy { 494 | policy.write_redis_args(out); 495 | } 496 | 497 | if let Some(ref alloc) = self.chunk_size { 498 | out.write_arg(b"CHUNK_SIZE"); 499 | out.write_arg(format!("{alloc}").as_bytes()); 500 | } 501 | 502 | if let Some(ref l) = self.labels { 503 | out.write_arg(b"LABELS"); 504 | for arg in l { 505 | out.write_arg(arg); 506 | } 507 | } 508 | } 509 | } 510 | 511 | /// Let's you build redis time series filter query options via a builder pattern. Filters 512 | /// can be used in different commands like TS.MGET, TS.MRANGE and TS.QUERYINDEX. 513 | #[derive(Debug, Default, Clone)] 514 | pub struct TsFilterOptions { 515 | with_labels: bool, 516 | filters: Vec, 517 | } 518 | 519 | /// TsFilterOptions allows you to build up your redis time series filter query. It 520 | /// supports default and a builder pattern so you can use it the following way: 521 | /// 522 | /// ```rust 523 | /// use redis_ts::TsFilterOptions; 524 | /// 525 | /// let filters = TsFilterOptions::default() 526 | /// .with_labels(true) 527 | /// .equals("label_1", "value_1") 528 | /// .not_equals("label_2", "hello") 529 | /// .in_set("label_3", vec!["a", "b", "c"]) 530 | /// .not_in_set("label_3", vec!["d", "e"]) 531 | /// .has_label("some_other") 532 | /// .not_has_label("unwanted"); 533 | /// ``` 534 | /// 535 | impl TsFilterOptions { 536 | /// Will add the WITHLABELS flag to the filter query. The query response will have 537 | /// label information attached. 538 | pub fn with_labels(mut self, value: bool) -> Self { 539 | self.with_labels = value; 540 | self 541 | } 542 | 543 | /// Select time series where the given label contains the the given value. 544 | pub fn equals( 545 | mut self, 546 | name: L, 547 | value: V, 548 | ) -> Self { 549 | self.filters.push(TsFilter { 550 | name: format!("{name}"), 551 | value: format!("{value}"), 552 | compare: TsCompare::Eq, 553 | }); 554 | self 555 | } 556 | 557 | /// Select time series where given label does not contain the given value. 558 | pub fn not_equals( 559 | mut self, 560 | name: L, 561 | value: V, 562 | ) -> Self { 563 | self.filters.push(TsFilter { 564 | name: format!("{name}"), 565 | value: format!("{value}"), 566 | compare: TsCompare::NotEq, 567 | }); 568 | self 569 | } 570 | 571 | /// Select time series where given label contains any of the given values. 572 | pub fn in_set( 573 | mut self, 574 | name: L, 575 | values: Vec, 576 | ) -> Self { 577 | let set = format!( 578 | "({})", 579 | values 580 | .iter() 581 | .map(|v| { format!("{v}") }) 582 | .collect::>() 583 | .join(",") 584 | ); 585 | self.filters.push(TsFilter { 586 | name: format!("{name}"), 587 | value: set, 588 | compare: TsCompare::Eq, 589 | }); 590 | self 591 | } 592 | 593 | /// Select time series where given label does not contain any of the given values. 594 | pub fn not_in_set( 595 | mut self, 596 | name: L, 597 | values: Vec, 598 | ) -> Self { 599 | let set = format!( 600 | "({})", 601 | values 602 | .iter() 603 | .map(|v| { format!("{v}") }) 604 | .collect::>() 605 | .join(",") 606 | ); 607 | self.filters.push(TsFilter { 608 | name: format!("{name}"), 609 | value: set, 610 | compare: TsCompare::NotEq, 611 | }); 612 | self 613 | } 614 | 615 | /// Select all time series that have the given label. 616 | pub fn has_label(mut self, name: L) -> Self { 617 | self.filters.push(TsFilter { 618 | name: format!("{name}"), 619 | value: "".to_string(), 620 | compare: TsCompare::NotEq, 621 | }); 622 | self 623 | } 624 | 625 | /// Select all time series that do not have the given label. 626 | pub fn not_has_label(mut self, name: L) -> Self { 627 | self.filters.push(TsFilter { 628 | name: format!("{name}"), 629 | value: "".to_string(), 630 | compare: TsCompare::Eq, 631 | }); 632 | self 633 | } 634 | 635 | pub fn get_filters(self) -> Vec { 636 | self.filters 637 | } 638 | } 639 | 640 | impl ToRedisArgs for TsFilterOptions { 641 | fn write_redis_args(&self, out: &mut W) 642 | where 643 | W: ?Sized + RedisWrite, 644 | { 645 | if self.with_labels { 646 | out.write_arg(b"WITHLABELS"); 647 | } 648 | out.write_arg(b"FILTER"); 649 | 650 | for f in self.filters.iter() { 651 | f.write_redis_args(out) 652 | } 653 | } 654 | } 655 | 656 | /// Provides information about a redis time series key. 657 | #[derive(Debug, Default)] 658 | pub struct TsInfo { 659 | pub total_samples: u64, 660 | pub memory_usage: u64, 661 | pub first_timestamp: u64, 662 | pub last_timestamp: u64, 663 | pub retention_time: u64, 664 | pub chunk_count: u64, 665 | pub max_samples_per_chunk: u16, 666 | pub chunk_size: u64, 667 | pub duplicate_policy: Option, 668 | pub labels: Vec<(String, String)>, 669 | pub source_key: Option, 670 | pub rules: Vec<(String, u64, String)>, 671 | } 672 | 673 | impl FromRedisValue for TsInfo { 674 | fn from_redis_value(v: &Value) -> RedisResult { 675 | match *v { 676 | Value::Bulk(ref values) => { 677 | let mut result = TsInfo::default(); 678 | let mut map: HashMap = HashMap::new(); 679 | 680 | for pair in values.chunks(2) { 681 | map.insert(from_redis_value(&pair[0])?, pair[1].clone()); 682 | } 683 | 684 | //println!("{:?}", map); 685 | 686 | if let Some(v) = map.get("totalSamples") { 687 | result.total_samples = from_redis_value(v)?; 688 | } 689 | 690 | if let Some(v) = map.get("memoryUsage") { 691 | result.memory_usage = from_redis_value(v)?; 692 | } 693 | 694 | if let Some(v) = map.get("firstTimestamp") { 695 | result.first_timestamp = from_redis_value(v)?; 696 | } 697 | 698 | if let Some(v) = map.get("lastTimestamp") { 699 | result.last_timestamp = from_redis_value(v)?; 700 | } 701 | 702 | if let Some(v) = map.get("retentionTime") { 703 | result.retention_time = from_redis_value(v)?; 704 | } 705 | 706 | if let Some(v) = map.get("chunkCount") { 707 | result.chunk_count = from_redis_value(v)?; 708 | } 709 | 710 | if let Some(v) = map.get("maxSamplesPerChunk") { 711 | result.max_samples_per_chunk = from_redis_value(v)?; 712 | } 713 | 714 | if let Some(v) = map.get("chunkSize") { 715 | result.chunk_size = from_redis_value(v)?; 716 | } 717 | 718 | if let Some(v) = map.get("sourceKey") { 719 | result.source_key = from_redis_value(v)?; 720 | } 721 | 722 | if let Some(v) = map.get("duplicatePolicy") { 723 | result.duplicate_policy = from_redis_value(v)?; 724 | } 725 | 726 | result.rules = match map.get("rules") { 727 | Some(Value::Bulk(ref values)) => values 728 | .iter() 729 | .flat_map(|value| match value { 730 | Value::Bulk(ref vs) => Some(( 731 | from_redis_value(&vs[0]).unwrap(), 732 | from_redis_value(&vs[1]).unwrap(), 733 | from_redis_value(&vs[2]).unwrap(), 734 | )), 735 | _ => None, 736 | }) 737 | .collect(), 738 | _ => vec![], 739 | }; 740 | 741 | result.labels = match map.get("labels") { 742 | Some(Value::Bulk(ref values)) => values 743 | .iter() 744 | .flat_map(|value| match value { 745 | Value::Bulk(ref vs) => Some(( 746 | from_redis_value(&vs[0]).unwrap(), 747 | from_redis_value(&vs[1]).unwrap(), 748 | )), 749 | _ => None, 750 | }) 751 | .collect(), 752 | _ => vec![], 753 | }; 754 | 755 | Ok(result) 756 | } 757 | _ => Err(RedisError::from(std::io::Error::new( 758 | std::io::ErrorKind::Other, 759 | "no_ts_info_data", 760 | ))), 761 | } 762 | } 763 | } 764 | 765 | /// Represents a TS.MGET redis time series result. The concrete types for timestamp 766 | /// and value eg can be provided from the call site. 767 | #[derive(Debug)] 768 | pub struct TsMget { 769 | pub values: Vec>, 770 | } 771 | 772 | impl FromRedisValue for TsMget { 773 | fn from_redis_value(v: &Value) -> RedisResult { 774 | let res = match *v { 775 | Value::Bulk(ref values) => TsMget { 776 | values: FromRedisValue::from_redis_values(values)?, 777 | }, 778 | _ => TsMget { values: vec![] }, 779 | }; 780 | Ok(res) 781 | } 782 | } 783 | 784 | /// Represents a TS.MGET redis time series entry. The concrete types for timestamp 785 | /// and value eg can be provided from the call site. 786 | #[derive(Debug, Default)] 787 | pub struct TsMgetEntry { 788 | pub key: String, 789 | pub labels: Vec<(String, String)>, 790 | pub value: Option<(TS, V)>, 791 | } 792 | 793 | impl FromRedisValue 794 | for TsMgetEntry 795 | { 796 | fn from_redis_value(v: &Value) -> RedisResult { 797 | match *v { 798 | Value::Bulk(ref values) => { 799 | let result = TsMgetEntry:: { 800 | key: from_redis_value(&values[0])?, 801 | labels: match values[1] { 802 | Value::Bulk(ref vs) => vs 803 | .iter() 804 | .flat_map(|value| match value { 805 | Value::Bulk(ref v) => Some(( 806 | from_redis_value(&v[0]).unwrap(), 807 | from_redis_value(&v[1]).unwrap(), 808 | )), 809 | _ => None, 810 | }) 811 | .collect(), 812 | _ => vec![], 813 | }, 814 | value: match values[2] { 815 | Value::Bulk(ref vs) if !vs.is_empty() => Some(( 816 | from_redis_value(&vs[0]).unwrap(), 817 | from_redis_value(&vs[1]).unwrap(), 818 | )), 819 | _ => None, 820 | }, 821 | }; 822 | 823 | Ok(result) 824 | } 825 | _ => Err(RedisError::from(std::io::Error::new( 826 | std::io::ErrorKind::Other, 827 | "no_mget_data", 828 | ))), 829 | } 830 | } 831 | } 832 | 833 | /// Represents a TS.RANGE redis time series result. The concrete types for timestamp 834 | /// and value eg can be provided from the call site. 835 | #[derive(Debug)] 836 | pub struct TsRange { 837 | pub values: Vec<(TS, V)>, 838 | } 839 | 840 | impl FromRedisValue for TsRange { 841 | fn from_redis_value(v: &Value) -> RedisResult { 842 | match *v { 843 | Value::Bulk(ref values) => { 844 | let items: Vec> = FromRedisValue::from_redis_values(values)?; 845 | Ok(TsRange { 846 | values: items.iter().map(|i| (i.ts, i.value)).collect(), 847 | }) 848 | } 849 | _ => Err(RedisError::from(std::io::Error::new( 850 | std::io::ErrorKind::Other, 851 | "no_range_data", 852 | ))), 853 | } 854 | } 855 | } 856 | 857 | /// Represents a TS.MRANGE redis time series result with multiple entries. The concrete types for timestamp 858 | /// and value eg can be provided from the call site. 859 | #[derive(Debug)] 860 | pub struct TsMrange { 861 | pub values: Vec>, 862 | } 863 | 864 | impl FromRedisValue 865 | for TsMrange 866 | { 867 | fn from_redis_value(v: &Value) -> RedisResult { 868 | let res = match *v { 869 | Value::Bulk(ref values) => TsMrange { 870 | values: FromRedisValue::from_redis_values(values)?, 871 | }, 872 | _ => TsMrange { values: vec![] }, 873 | }; 874 | Ok(res) 875 | } 876 | } 877 | 878 | /// Represents a TS.MRANGE redis time series value. The concrete types for timestamp 879 | /// and value eg can be provided from the call site. 880 | #[derive(Debug, Default)] 881 | pub struct TsMrangeEntry { 882 | pub key: String, 883 | pub labels: Vec<(String, String)>, 884 | pub values: Vec<(TS, V)>, 885 | } 886 | 887 | impl FromRedisValue 888 | for TsMrangeEntry 889 | { 890 | fn from_redis_value(v: &Value) -> RedisResult { 891 | match *v { 892 | Value::Bulk(ref values) => { 893 | let result = TsMrangeEntry:: { 894 | key: from_redis_value(&values[0]).unwrap(), 895 | labels: match values[1] { 896 | Value::Bulk(ref vs) => vs 897 | .iter() 898 | .flat_map(|value| match value { 899 | Value::Bulk(ref v) => Some(( 900 | from_redis_value(&v[0]).unwrap(), 901 | from_redis_value(&v[1]).unwrap(), 902 | )), 903 | _ => None, 904 | }) 905 | .collect(), 906 | _ => vec![], 907 | }, 908 | values: match values[2] { 909 | Value::Bulk(ref vs) => { 910 | let items: Vec> = 911 | FromRedisValue::from_redis_values(vs)?; 912 | items.iter().map(|i| (i.ts, i.value)).collect() 913 | } 914 | _ => vec![], 915 | }, 916 | }; 917 | 918 | Ok(result) 919 | } 920 | _ => Err(RedisError::from(std::io::Error::new( 921 | std::io::ErrorKind::Other, 922 | "no_mget_data", 923 | ))), 924 | } 925 | } 926 | } 927 | 928 | #[derive(Debug)] 929 | struct TsValueReply { 930 | pub ts: TS, 931 | pub value: V, 932 | } 933 | 934 | impl FromRedisValue for TsValueReply { 935 | fn from_redis_value(v: &Value) -> RedisResult { 936 | match *v { 937 | Value::Bulk(ref values) => Ok(TsValueReply { 938 | ts: from_redis_value(&values[0]).unwrap(), 939 | value: from_redis_value(&values[1]).unwrap(), 940 | }), 941 | _ => Err(RedisError::from(std::io::Error::new( 942 | std::io::ErrorKind::Other, 943 | "no_value_data", 944 | ))), 945 | } 946 | } 947 | } 948 | 949 | #[derive(PartialEq, Eq, Clone, Debug, Copy)] 950 | enum TsCompare { 951 | Eq, 952 | NotEq, 953 | } 954 | 955 | impl ToRedisArgs for TsCompare { 956 | fn write_redis_args(&self, out: &mut W) 957 | where 958 | W: ?Sized + RedisWrite, 959 | { 960 | let val = match *self { 961 | TsCompare::Eq => "=", 962 | TsCompare::NotEq => "!=", 963 | }; 964 | 965 | val.write_redis_args(out); 966 | } 967 | } 968 | 969 | #[derive(Debug, Clone)] 970 | pub struct TsFilter { 971 | name: String, 972 | value: String, 973 | compare: TsCompare, 974 | } 975 | 976 | impl ToRedisArgs for TsFilter { 977 | fn write_redis_args(&self, out: &mut W) 978 | where 979 | W: ?Sized + RedisWrite, 980 | { 981 | let comp = match self.compare { 982 | TsCompare::Eq => "=", 983 | TsCompare::NotEq => "!=", 984 | }; 985 | 986 | let arg = format!("{}{}{}", self.name, comp, self.value); 987 | out.write_arg(arg.as_bytes()); 988 | } 989 | } 990 | -------------------------------------------------------------------------------- /tests/async_command_tests/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate async_std; 2 | extern crate redis; 3 | extern crate redis_ts; 4 | 5 | use redis::aio::Connection; 6 | use redis::AsyncCommands; 7 | use redis_ts::AsyncTsCommands; 8 | use redis_ts::{ 9 | TsAggregationType, TsDuplicatePolicy, TsFilterOptions, TsInfo, TsMget, TsMrange, TsOptions, 10 | TsRange, TsRangeQuery, 11 | }; 12 | use std::env; 13 | use std::thread; 14 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 15 | 16 | async fn get_con() -> Connection { 17 | let client = redis::Client::open(get_redis_url()).unwrap(); 18 | client.get_async_connection().await.unwrap() 19 | } 20 | 21 | async fn prepare_ts(name: &str) -> Connection { 22 | let mut con = get_con().await; 23 | let _: () = con.del(name).await.unwrap(); 24 | let _: () = con.ts_create(name, TsOptions::default()).await.unwrap(); 25 | con 26 | } 27 | 28 | fn sleep(ms: u64) { 29 | let millis = Duration::from_millis(ms); 30 | thread::sleep(millis); 31 | } 32 | 33 | /// Create and verify ts create info future 34 | pub async fn ts_create_info(name: &str) -> TsInfo { 35 | let mut con = get_con().await; 36 | let _: () = con.del(name).await.unwrap(); 37 | let _: () = con 38 | .ts_create( 39 | name, 40 | TsOptions::default() 41 | .label("l", name) 42 | .duplicate_policy(TsDuplicatePolicy::Max), 43 | ) 44 | .await 45 | .unwrap(); 46 | let r: TsInfo = con.ts_info(name).await.unwrap(); 47 | r 48 | } 49 | 50 | pub fn verify_ts_create_info(res: TsInfo, name: &str) { 51 | assert_eq!(res.labels, vec![("l".to_string(), name.to_string())]); 52 | assert_eq!(res.duplicate_policy, Some(TsDuplicatePolicy::Max)); 53 | } 54 | 55 | /// Create/verify ts add 56 | pub async fn ts_add(name: &str) -> u64 { 57 | let mut con = prepare_ts(name).await; 58 | let r: u64 = con.ts_add(name, 1234567890, 2.2).await.unwrap(); 59 | r 60 | } 61 | 62 | pub fn verify_ts_add(ts: u64) { 63 | assert_eq!(ts, 1234567890); 64 | } 65 | 66 | /// Create/verify ts add now 67 | pub async fn ts_add_now(name: &str) -> (u64, u64) { 68 | let now = SystemTime::now() 69 | .duration_since(UNIX_EPOCH) 70 | .unwrap() 71 | .as_millis() as u64; 72 | let mut con = prepare_ts(name).await; 73 | let ts: u64 = con.ts_add_now(name, 2.2).await.unwrap(); 74 | (now, ts) 75 | } 76 | 77 | pub fn verify_ts_add_now(times: (u64, u64)) { 78 | assert!(times.0 <= times.1); 79 | } 80 | 81 | /// Create/verify ts add create 82 | pub async fn ts_add_create(name: &str) -> () { 83 | let mut con = get_con().await; 84 | let _: () = con.del(name).await.unwrap(); 85 | let ts: u64 = con 86 | .ts_add_create(name, 1234567890, 2.2, TsOptions::default()) 87 | .await 88 | .unwrap(); 89 | assert_eq!(ts, 1234567890); 90 | 91 | let ts2: u64 = con 92 | .ts_add_create(name, "*", 2.3, TsOptions::default()) 93 | .await 94 | .unwrap(); 95 | assert!(ts2 > ts); 96 | } 97 | 98 | pub async fn ts_add_replace(name: &str) { 99 | let mut con = get_con().await; 100 | let _: () = con.del(name).await.unwrap(); 101 | let _: () = con 102 | .ts_create( 103 | name, 104 | TsOptions::default().duplicate_policy(TsDuplicatePolicy::Last), 105 | ) 106 | .await 107 | .unwrap(); 108 | let _: u64 = con.ts_add(name, 1234567890u64, 2.2f64).await.unwrap(); 109 | let _: u64 = con.ts_add(name, 1234567890u64, 3.2f64).await.unwrap(); 110 | let stored: (u64, f64) = con.ts_get(name).await.unwrap().unwrap(); 111 | assert_eq!(stored.1, 3.2); 112 | } 113 | 114 | pub async fn ts_madd(name: &str) { 115 | let second_name = &format!("{:}2", name); 116 | let mut con = prepare_ts(name).await; 117 | let _ = prepare_ts(second_name).await; 118 | let expected: Vec = vec![1234, 4321]; 119 | let res: Vec = con 120 | .ts_madd(&[(name, 1234, 1.0), (second_name, 4321, 2.0)]) 121 | .await 122 | .unwrap(); 123 | assert_eq!(expected, res); 124 | } 125 | 126 | pub async fn ts_incrby_now(name: &str) { 127 | let mut con = prepare_ts(name).await; 128 | let _: () = con.ts_incrby_now(name, 1).await.unwrap(); 129 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 130 | assert_eq!(v1.unwrap().1, 1.0); 131 | sleep(1); 132 | let _: () = con.ts_incrby_now(name, 5).await.unwrap(); 133 | let v2: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 134 | assert_eq!(v2.unwrap().1, 6.0); 135 | } 136 | 137 | pub async fn ts_decrby_now(name: &str) { 138 | let mut con = prepare_ts(name).await; 139 | let _: () = con.ts_add_now(name, 10).await.unwrap(); 140 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 141 | assert_eq!(v1.unwrap().1, 10.0); 142 | sleep(1); 143 | 144 | let _: () = con.ts_decrby_now(name, 1).await.unwrap(); 145 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 146 | assert_eq!(v1.unwrap().1, 9.0); 147 | sleep(1); 148 | 149 | let _: () = con.ts_decrby_now(name, 5).await.unwrap(); 150 | let v2: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 151 | assert_eq!(v2.unwrap().1, 4.0); 152 | } 153 | 154 | pub async fn ts_incrby(name: &str) { 155 | let mut con = prepare_ts(name).await; 156 | 157 | let _: () = con.ts_incrby(name, 123, 1).await.unwrap(); 158 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 159 | assert_eq!(v1.unwrap(), (123, 1.0)); 160 | 161 | let _: () = con.ts_incrby(name, 1234, 5).await.unwrap(); 162 | let v2: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 163 | assert_eq!(v2.unwrap(), (1234, 6.0)); 164 | } 165 | 166 | pub async fn ts_decrby(name: &str) { 167 | let mut con = prepare_ts(name).await; 168 | let _: () = con.ts_add(name, 12, 10).await.unwrap(); 169 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 170 | assert_eq!(v1.unwrap(), (12, 10.0)); 171 | 172 | let _: () = con.ts_decrby(name, 123, 1).await.unwrap(); 173 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 174 | assert_eq!(v1.unwrap(), (123, 9.0)); 175 | 176 | let _: () = con.ts_decrby(name, 1234, 5).await.unwrap(); 177 | let v2: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 178 | assert_eq!(v2.unwrap(), (1234, 4.0)); 179 | } 180 | 181 | pub async fn ts_incrby_create(name: &str) { 182 | let mut con = get_con().await; 183 | let _: () = con.del(name).await.unwrap(); 184 | 185 | let _: () = con 186 | .ts_incrby_create(name, 123, 1, TsOptions::default()) 187 | .await 188 | .unwrap(); 189 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 190 | assert_eq!(v1.unwrap(), (123, 1.0)); 191 | 192 | let _: () = con 193 | .ts_incrby_create(name, 1234, 5, TsOptions::default()) 194 | .await 195 | .unwrap(); 196 | let v2: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 197 | assert_eq!(v2.unwrap(), (1234, 6.0)); 198 | } 199 | 200 | pub async fn ts_decrby_create(name: &str) { 201 | let mut con = get_con().await; 202 | let _: () = con.del(name).await.unwrap(); 203 | 204 | let _: () = con 205 | .ts_decrby_create(name, 123, 1, TsOptions::default()) 206 | .await 207 | .unwrap(); 208 | let v1: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 209 | assert_eq!(v1.unwrap(), (123, -1.0)); 210 | 211 | let _: () = con 212 | .ts_decrby_create(name, 1234, 5, TsOptions::default()) 213 | .await 214 | .unwrap(); 215 | let v2: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 216 | assert_eq!(v2.unwrap(), (1234, -6.0)); 217 | } 218 | 219 | pub async fn ts_create_delete_rule(name: &str) { 220 | let name2 = &format!("{:}2", name); 221 | let mut con = prepare_ts(name).await; 222 | let _ = prepare_ts(name2).await; 223 | let _: () = con 224 | .ts_createrule(name, name2, TsAggregationType::Avg(5000)) 225 | .await 226 | .unwrap(); 227 | 228 | let info: TsInfo = con.ts_info(name).await.unwrap(); 229 | assert_eq!( 230 | info.rules, 231 | vec![(name2.to_string(), 5000, "AVG".to_string())] 232 | ); 233 | 234 | let _: () = con.ts_deleterule(name, name2).await.unwrap(); 235 | let info: TsInfo = con.ts_info(name).await.unwrap(); 236 | assert_eq!(info.rules, vec![]); 237 | } 238 | 239 | pub async fn ts_get(name: &str) { 240 | let mut con = prepare_ts(name).await; 241 | let _: () = con.ts_add(name, 1234, 2.0).await.unwrap(); 242 | let res: Option<(u64, f64)> = con.ts_get(name).await.unwrap(); 243 | assert_eq!(Some((1234, 2.0)), res); 244 | } 245 | 246 | pub async fn ts_mget(name: &str) { 247 | let name2 = &format!("{:}2", name); 248 | let name3 = &format!("{:}3", name); 249 | let label = &format!("{:}label", name); 250 | let mut con = get_con().await; 251 | let opts: TsOptions = TsOptions::default().label("l", label); 252 | 253 | let _: () = con.del(name).await.unwrap(); 254 | let _: () = con.del(name2).await.unwrap(); 255 | let _: () = con.del(name3).await.unwrap(); 256 | 257 | let _: () = con.ts_create(name, opts.clone()).await.unwrap(); 258 | let _: () = con.ts_create(name2, opts.clone()).await.unwrap(); 259 | let _: () = con.ts_create(name3, opts.clone()).await.unwrap(); 260 | 261 | let _: () = con 262 | .ts_madd(&[ 263 | (name, 12, 1.0), 264 | (name, 123, 2.0), 265 | (name, 1234, 3.0), 266 | (name2, 21, 1.0), 267 | (name2, 321, 2.0), 268 | (name2, 4321, 3.0), 269 | ]) 270 | .await 271 | .unwrap(); 272 | 273 | let res: TsMget = con 274 | .ts_mget( 275 | TsFilterOptions::default() 276 | .equals("l", label) 277 | .with_labels(true), 278 | ) 279 | .await 280 | .unwrap(); 281 | 282 | assert_eq!(res.values.len(), 3); 283 | assert_eq!(res.values[0].value, Some((1234, 3.0))); 284 | assert_eq!(res.values[1].value, Some((4321, 3.0))); 285 | assert_eq!(res.values[2].value, None); 286 | } 287 | 288 | pub async fn ts_get_ts_info(name: &str) { 289 | let mut con = get_con().await; 290 | let _: () = con.del(name).await.unwrap(); 291 | let _: () = con 292 | .ts_create( 293 | name, 294 | TsOptions::default() 295 | .label("a", "b") 296 | .duplicate_policy(TsDuplicatePolicy::Block) 297 | .chunk_size(4096 * 2), 298 | ) 299 | .await 300 | .unwrap(); 301 | let _: () = con.ts_add(name, "1234", 2.0).await.unwrap(); 302 | let info: TsInfo = con.ts_info(name).await.unwrap(); 303 | assert_eq!(info.total_samples, 1); 304 | assert_eq!(info.first_timestamp, 1234); 305 | assert_eq!(info.last_timestamp, 1234); 306 | assert_eq!(info.chunk_count, 1); 307 | assert_eq!(info.duplicate_policy, Some(TsDuplicatePolicy::Block)); 308 | assert_eq!(info.chunk_size, 4096 * 2); 309 | assert_eq!(info.labels, vec![("a".to_string(), "b".to_string())]); 310 | } 311 | 312 | pub async fn ts_alter(name: &str) { 313 | let mut con = get_con().await; 314 | let _: () = con.del(name).await.unwrap(); 315 | let _: () = con 316 | .ts_create( 317 | name, 318 | TsOptions::default() 319 | .label("a", "b") 320 | .duplicate_policy(TsDuplicatePolicy::Block) 321 | .chunk_size(4096 * 2), 322 | ) 323 | .await 324 | .unwrap(); 325 | let _: () = con.ts_add(name, "1234", 2.0).await.unwrap(); 326 | let info: TsInfo = con.ts_info(name).await.unwrap(); 327 | assert_eq!(info.chunk_count, 1); 328 | assert_eq!(info.chunk_size, 4096 * 2); 329 | assert_eq!(info.labels, vec![("a".to_string(), "b".to_string())]); 330 | 331 | let _: () = con 332 | .ts_alter( 333 | name, 334 | TsOptions::default().chunk_size(4096 * 4).label("c", "d"), 335 | ) 336 | .await 337 | .unwrap(); 338 | let info2: TsInfo = con.ts_info(name).await.unwrap(); 339 | assert_eq!(info2.chunk_size, 4096 * 4); 340 | assert_eq!(info2.labels, vec![("c".to_string(), "d".to_string())]); 341 | } 342 | 343 | pub async fn ts_range(name: &str) { 344 | let name2 = &format!("{:}2", name); 345 | let mut con = prepare_ts(name).await; 346 | let _ = prepare_ts(name2).await; 347 | let _: () = con 348 | .ts_madd(&[(name, 12, 1.0), (name, 123, 2.0), (name, 1234, 3.0)]) 349 | .await 350 | .unwrap(); 351 | 352 | let query = TsRangeQuery::default(); 353 | 354 | let res: TsRange = con.ts_range(name, query.clone()).await.unwrap(); 355 | assert_eq!(res.values, vec![(12, 1.0), (123, 2.0), (1234, 3.0)]); 356 | 357 | let one_res: TsRange = con.ts_range(name, query.clone().count(1)).await.unwrap(); 358 | assert_eq!(one_res.values, vec![(12, 1.0)]); 359 | 360 | let range_res: TsRange = con 361 | .ts_range(name, query.clone().filter_by_ts(vec![12, 123])) 362 | .await 363 | .unwrap(); 364 | assert_eq!(range_res.values, vec![(12, 1.0), (123, 2.0)]); 365 | 366 | let sum: TsRange = con 367 | .ts_range( 368 | name, 369 | query 370 | .clone() 371 | .filter_by_ts(vec![12, 123]) 372 | .aggregation_type(TsAggregationType::Sum(10000)), 373 | ) 374 | .await 375 | .unwrap(); 376 | assert_eq!(sum.values, vec![(0, 3.0)]); 377 | 378 | let res: TsRange = con.ts_range(name2, query.clone()).await.unwrap(); 379 | assert_eq!(res.values, vec![]); 380 | } 381 | 382 | pub async fn ts_revrange(name: &str) { 383 | let name2 = &format!("{:}2", name); 384 | let mut con = prepare_ts(name).await; 385 | let _ = prepare_ts(name2).await; 386 | let _: () = con 387 | .ts_madd(&[(name, 12, 1.0), (name, 123, 2.0), (name, 1234, 3.0)]) 388 | .await 389 | .unwrap(); 390 | 391 | let query = TsRangeQuery::default(); 392 | 393 | let res: TsRange = con.ts_revrange(name, query.clone()).await.unwrap(); 394 | assert_eq!(res.values, vec![(1234, 3.0), (123, 2.0), (12, 1.0)]); 395 | 396 | let one_res: TsRange = con.ts_revrange(name, query.clone().count(1)).await.unwrap(); 397 | assert_eq!(one_res.values, vec![(1234, 3.0)]); 398 | 399 | let range_res: TsRange = con 400 | .ts_revrange(name, query.clone().filter_by_ts(vec![12, 123])) 401 | .await 402 | .unwrap(); 403 | assert_eq!(range_res.values, vec![(123, 2.0), (12, 1.0)]); 404 | 405 | let sum: TsRange = con 406 | .ts_revrange( 407 | name, 408 | query 409 | .clone() 410 | .filter_by_ts(vec![12, 123]) 411 | .aggregation_type(TsAggregationType::Sum(10000)), 412 | ) 413 | .await 414 | .unwrap(); 415 | assert_eq!(sum.values, vec![(0, 3.0)]); 416 | 417 | let res: TsRange = con.ts_revrange(name2, query.clone()).await.unwrap(); 418 | assert_eq!(res.values, vec![]); 419 | } 420 | 421 | pub async fn ts_mrange(name: &str) { 422 | let name2: &str = &format!("{:}2", name); 423 | let label = &format!("{:}label", name); 424 | 425 | let mut con = get_con().await; 426 | let _: () = con.del(name).await.unwrap(); 427 | let _: () = con.del(name2).await.unwrap(); 428 | let opts: TsOptions = TsOptions::default().label("l", label); 429 | let _: () = con.ts_create(name, opts.clone()).await.unwrap(); 430 | let _: () = con.ts_create(name2, opts.clone()).await.unwrap(); 431 | let _: () = con 432 | .ts_madd(&[ 433 | (name, 12, 1.0), 434 | (name, 123, 2.0), 435 | (name, 1234, 3.0), 436 | (name2, 21, 1.0), 437 | (name2, 321, 2.0), 438 | (name2, 4321, 3.0), 439 | ]) 440 | .await 441 | .unwrap(); 442 | 443 | let query = TsRangeQuery::default(); 444 | 445 | let res: TsMrange = con 446 | .ts_mrange( 447 | query.clone(), 448 | TsFilterOptions::default() 449 | .equals("l", label) 450 | .with_labels(true), 451 | ) 452 | .await 453 | .unwrap(); 454 | assert_eq!(res.values.len(), 2); 455 | assert_eq!( 456 | res.values[1].values, 457 | vec![(21, 1.0), (321, 2.0), (4321, 3.0)] 458 | ); 459 | assert_eq!(res.values[0].key, name); 460 | assert_eq!(res.values[1].key, name2); 461 | assert_eq!( 462 | res.values[0].labels, 463 | vec![("l".to_string(), label.to_string())] 464 | ); 465 | 466 | let res2: TsMrange = con 467 | .ts_mrange( 468 | query.clone(), 469 | TsFilterOptions::default() 470 | .equals("none", "existing") 471 | .with_labels(true), 472 | ) 473 | .await 474 | .unwrap(); 475 | assert!(res2.values.is_empty()); 476 | } 477 | 478 | pub async fn ts_mrevrange(name: &str) { 479 | let name2: &str = &format!("{:}2", name); 480 | let label = &format!("{:}label", name); 481 | 482 | let mut con = get_con().await; 483 | let _: () = con.del(name).await.unwrap(); 484 | let _: () = con.del(name2).await.unwrap(); 485 | let opts: TsOptions = TsOptions::default().label("l", label); 486 | let _: () = con.ts_create(name, opts.clone()).await.unwrap(); 487 | let _: () = con.ts_create(name2, opts.clone()).await.unwrap(); 488 | let _: () = con 489 | .ts_madd(&[ 490 | (name, 12, 1.0), 491 | (name, 123, 2.0), 492 | (name, 1234, 3.0), 493 | (name2, 21, 1.0), 494 | (name2, 321, 2.0), 495 | (name2, 4321, 3.0), 496 | ]) 497 | .await 498 | .unwrap(); 499 | 500 | let query = TsRangeQuery::default(); 501 | 502 | let res: TsMrange = con 503 | .ts_mrevrange( 504 | query.clone(), 505 | TsFilterOptions::default() 506 | .equals("l", label) 507 | .with_labels(true), 508 | ) 509 | .await 510 | .unwrap(); 511 | assert_eq!(res.values.len(), 2); 512 | assert_eq!( 513 | res.values[1].values, 514 | vec![(4321, 3.0), (321, 2.0), (21, 1.0)] 515 | ); 516 | assert_eq!(res.values[0].key, name); 517 | assert_eq!(res.values[1].key, name2); 518 | assert_eq!( 519 | res.values[0].labels, 520 | vec![("l".to_string(), label.to_string())] 521 | ); 522 | 523 | let res2: TsMrange = con 524 | .ts_mrevrange( 525 | query.clone(), 526 | TsFilterOptions::default() 527 | .equals("none", "existing") 528 | .with_labels(true), 529 | ) 530 | .await 531 | .unwrap(); 532 | assert!(res2.values.is_empty()); 533 | } 534 | 535 | pub async fn ts_queryindex(name: &str) { 536 | let mut con = get_con().await; 537 | let _: () = con.del(name).await.unwrap(); 538 | let _: () = con 539 | .ts_create(name, TsOptions::default().label("a", "b")) 540 | .await 541 | .unwrap(); 542 | let _: () = con.ts_add(name, "1234", 2.0).await.unwrap(); 543 | let index: Vec = con 544 | .ts_queryindex(TsFilterOptions::default().equals("a", "b")) 545 | .await 546 | .unwrap(); 547 | assert!(index.contains(&name.to_string())); 548 | } 549 | 550 | fn get_redis_url() -> String { 551 | let redis_host_key = "REDIS_HOST"; 552 | let redis_host_port = "REDIS_PORT"; 553 | 554 | let redis_host = match env::var(redis_host_key) { 555 | Ok(host) => host, 556 | _ => "localhost".to_string(), 557 | }; 558 | 559 | let redis_port = match env::var(redis_host_port) { 560 | Ok(port) => port, 561 | _ => "6379".to_string(), 562 | }; 563 | 564 | format!("redis://{}:{}/", redis_host, redis_port) 565 | } 566 | -------------------------------------------------------------------------------- /tests/test_async_std_commands.rs: -------------------------------------------------------------------------------- 1 | extern crate async_std; 2 | use crate::async_command_tests::*; 3 | use async_std::task; 4 | use futures::Future; 5 | mod async_command_tests; 6 | 7 | fn block_on(f: F) -> F::Output 8 | where 9 | F: Future, 10 | { 11 | task::block_on(f) 12 | } 13 | 14 | #[test] 15 | fn testing_ts_create_info() { 16 | let res = block_on(ts_create_info("test_ts_create_info_async_std")); 17 | verify_ts_create_info(res, "test_ts_create_info_async_std"); 18 | } 19 | 20 | #[test] 21 | fn test_ts_add() { 22 | let res = block_on(ts_add("test_ts_add_async_std")); 23 | verify_ts_add(res); 24 | } 25 | 26 | #[test] 27 | fn test_ts_add_now() { 28 | let res = block_on(ts_add_now("test_ts_add_now_async_std")); 29 | verify_ts_add_now(res); 30 | } 31 | 32 | #[test] 33 | fn test_ts_add_create() { 34 | let _: () = block_on(ts_add_create("test_ts_add_create_async_std")); 35 | } 36 | 37 | #[test] 38 | fn test_ts_add_replace() { 39 | let _: () = block_on(ts_add_replace("test_ts_add_replace_async_std")); 40 | } 41 | 42 | #[test] 43 | fn test_ts_madd() { 44 | let _: () = block_on(ts_madd("test_ts_madd_async_std")); 45 | } 46 | 47 | #[test] 48 | fn test_ts_incrby_now() { 49 | let _: () = block_on(ts_incrby_now("async_test_ts_incrby_now_std")); 50 | } 51 | 52 | #[test] 53 | fn test_ts_decrby_now() { 54 | let _: () = block_on(ts_decrby_now("async_test_ts_decrby_now_std")); 55 | } 56 | 57 | #[test] 58 | fn test_ts_incrby() { 59 | let _: () = block_on(ts_incrby("async_test_ts_incrby_std")); 60 | } 61 | 62 | #[test] 63 | fn test_ts_decrby() { 64 | let _: () = block_on(ts_decrby("async_test_ts_decrby_std")); 65 | } 66 | 67 | #[test] 68 | fn test_ts_incrby_create() { 69 | let _: () = block_on(ts_incrby_create("async_test_ts_incrby_create_std")); 70 | } 71 | 72 | #[test] 73 | fn test_ts_decrby_create() { 74 | let _: () = block_on(ts_decrby_create("async_test_ts_decrby_create_std")); 75 | } 76 | 77 | #[test] 78 | fn test_ts_create_delete_rule() { 79 | let _: () = block_on(ts_create_delete_rule( 80 | "async_test_ts_create_delete_rule_std", 81 | )); 82 | } 83 | 84 | #[test] 85 | fn test_ts_get() { 86 | let _: () = block_on(ts_get("async_test_ts_get_std")); 87 | } 88 | 89 | #[test] 90 | fn test_ts_mget() { 91 | let _: () = block_on(ts_mget("async_test_ts_mget_std")); 92 | } 93 | 94 | #[test] 95 | fn test_ts_get_ts_info() { 96 | let _: () = block_on(ts_get_ts_info("async_test_ts_get_ts_info_std")); 97 | } 98 | 99 | #[test] 100 | fn test_ts_alter() { 101 | let _: () = block_on(ts_alter("async_test_ts_alter_std")); 102 | } 103 | 104 | #[test] 105 | fn test_ts_range() { 106 | let _: () = block_on(ts_range("async_test_ts_range_std")); 107 | } 108 | 109 | #[test] 110 | fn test_ts_revrange() { 111 | let _: () = block_on(ts_revrange("async_test_ts_revrange_std")); 112 | } 113 | 114 | #[test] 115 | fn test_ts_mrange() { 116 | let _: () = block_on(ts_mrange("async_test_ts_mrange_std")); 117 | } 118 | 119 | #[test] 120 | fn test_ts_mrevrange() { 121 | let _: () = block_on(ts_mrevrange("async_test_ts_mrevrange_std")); 122 | } 123 | 124 | #[test] 125 | fn test_ts_queryindex() { 126 | let _: () = block_on(ts_queryindex("async_test_ts_queryindex_std")); 127 | } 128 | -------------------------------------------------------------------------------- /tests/test_async_tokio_commands.rs: -------------------------------------------------------------------------------- 1 | extern crate tokio; 2 | use crate::async_command_tests::*; 3 | use futures::Future; 4 | mod async_command_tests; 5 | 6 | fn block_on(f: F) -> F::Output 7 | where 8 | F: Future, 9 | { 10 | let mut builder = tokio::runtime::Builder::new_current_thread(); 11 | let runtime = builder.enable_io().build().unwrap(); 12 | runtime.block_on(f) 13 | } 14 | 15 | #[test] 16 | fn test_ts_create_info() { 17 | let res = block_on(ts_create_info("test_ts_create_info_async_tokio")); 18 | verify_ts_create_info(res, "test_ts_create_info_async_tokio"); 19 | } 20 | 21 | #[test] 22 | fn test_ts_add() { 23 | let res = block_on(ts_add("test_ts_add_async_tokio")); 24 | verify_ts_add(res); 25 | } 26 | 27 | #[test] 28 | fn test_ts_add_now() { 29 | let res = block_on(ts_add_now("test_ts_add_now_async_tokio")); 30 | verify_ts_add_now(res); 31 | } 32 | 33 | #[test] 34 | fn test_ts_add_create() { 35 | let _: () = block_on(ts_add_create("test_ts_add_create_async_tokio")); 36 | } 37 | 38 | #[test] 39 | fn test_ts_add_replace() { 40 | let _: () = block_on(ts_add_replace("test_ts_add_replace_async_tokio")); 41 | } 42 | 43 | #[test] 44 | fn test_ts_madd() { 45 | let _: () = block_on(ts_madd("test_ts_madd_async_tokio")); 46 | } 47 | 48 | #[test] 49 | fn test_ts_incrby_now() { 50 | let _: () = block_on(ts_incrby_now("async_test_ts_incrby_now_tokio")); 51 | } 52 | 53 | #[test] 54 | fn test_ts_decrby_now() { 55 | let _: () = block_on(ts_decrby_now("async_test_ts_decrby_now_tokio")); 56 | } 57 | 58 | #[test] 59 | fn test_ts_incrby() { 60 | let _: () = block_on(ts_incrby("async_test_ts_incrby_tokio")); 61 | } 62 | 63 | #[test] 64 | fn test_ts_decrby() { 65 | let _: () = block_on(ts_decrby("async_test_ts_decrby_tokio")); 66 | } 67 | 68 | #[test] 69 | fn test_ts_incrby_create() { 70 | let _: () = block_on(ts_incrby_create("async_test_ts_incrby_create_tokio")); 71 | } 72 | 73 | #[test] 74 | fn test_ts_decrby_create() { 75 | let _: () = block_on(ts_decrby_create("async_test_ts_decrby_create_tokio")); 76 | } 77 | 78 | #[test] 79 | fn test_ts_create_delete_rule() { 80 | let _: () = block_on(ts_create_delete_rule( 81 | "async_test_ts_create_delete_rule_tokio", 82 | )); 83 | } 84 | 85 | #[test] 86 | fn test_ts_get() { 87 | let _: () = block_on(ts_get("async_test_ts_get_tokio")); 88 | } 89 | 90 | #[test] 91 | fn test_ts_mget() { 92 | let _: () = block_on(ts_mget("async_test_ts_mget_tokio")); 93 | } 94 | 95 | #[test] 96 | fn test_ts_get_ts_info() { 97 | let _: () = block_on(ts_get_ts_info("async_test_ts_get_ts_info_tokio")); 98 | } 99 | 100 | #[test] 101 | fn test_ts_alter() { 102 | let _: () = block_on(ts_alter("async_test_ts_alter_tokio")); 103 | } 104 | 105 | #[test] 106 | fn test_ts_range() { 107 | let _: () = block_on(ts_range("async_test_ts_range_tokio")); 108 | } 109 | 110 | #[test] 111 | fn test_ts_revrange() { 112 | let _: () = block_on(ts_revrange("async_test_ts_revrange_tokio")); 113 | } 114 | 115 | #[test] 116 | fn test_ts_mrange() { 117 | let _: () = block_on(ts_mrange("async_test_ts_mrange_tokio")); 118 | } 119 | 120 | #[test] 121 | fn test_ts_mrevrange() { 122 | let _: () = block_on(ts_mrevrange("async_test_ts_mrevrange_tokio")); 123 | } 124 | 125 | #[test] 126 | fn test_ts_queryindex() { 127 | let _: () = block_on(ts_queryindex("async_test_ts_queryindex_tokio")); 128 | } 129 | -------------------------------------------------------------------------------- /tests/test_commands.rs: -------------------------------------------------------------------------------- 1 | extern crate redis; 2 | extern crate redis_ts; 3 | 4 | use redis::{Commands, Connection, Value}; 5 | use redis_ts::{ 6 | TsAggregationType, TsCommands, TsDuplicatePolicy, TsFilterOptions, TsInfo, TsMget, TsMrange, 7 | TsOptions, TsRange, TsRangeQuery, 8 | }; 9 | 10 | use std::thread; 11 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 12 | 13 | fn get_con() -> Connection { 14 | let client = redis::Client::open("redis://localhost/").unwrap(); 15 | client.get_connection().expect("Failed to get connection!") 16 | } 17 | 18 | fn default_settings() -> TsOptions { 19 | TsOptions::default().retention_time(60000).label("a", "b") 20 | } 21 | 22 | fn sleep(ms: u64) { 23 | let millis = Duration::from_millis(ms); 24 | thread::sleep(millis); 25 | } 26 | 27 | #[test] 28 | fn test_create_ts_no_options() { 29 | let _: () = get_con().del("test_ts_no_op").unwrap(); 30 | let r: Value = get_con() 31 | .ts_create("test_ts_no_op", TsOptions::default()) 32 | .unwrap(); 33 | assert_eq!(Value::Okay, r); 34 | let info = get_con().ts_info("test_ts_no_op").unwrap(); 35 | assert_eq!(info.retention_time, 0); 36 | assert_eq!(info.labels, vec![]); 37 | } 38 | 39 | #[test] 40 | fn test_create_ts_retention() { 41 | let _: () = get_con().del("test_ts_ret").unwrap(); 42 | let opts = TsOptions::default().retention_time(60000); 43 | let r: Value = get_con().ts_create("test_ts_ret", opts).unwrap(); 44 | assert_eq!(Value::Okay, r); 45 | let info: TsInfo = get_con().ts_info("test_ts_ret").unwrap(); 46 | assert_eq!(info.labels, vec![]); 47 | assert_eq!(info.retention_time, 60000); 48 | } 49 | 50 | #[test] 51 | fn test_create_ts_labels() { 52 | let _: () = get_con().del("test_ts_lab").unwrap(); 53 | let opts = TsOptions::default().label("a", "b"); 54 | let r: Value = get_con().ts_create("test_ts_lab", opts).unwrap(); 55 | assert_eq!(Value::Okay, r); 56 | let info: TsInfo = get_con().ts_info("test_ts_lab").unwrap(); 57 | assert_eq!(info.labels, vec![("a".to_string(), "b".to_string())]); 58 | assert_eq!(info.retention_time, 0); 59 | } 60 | 61 | #[test] 62 | fn test_create_ts_all() { 63 | let _: () = get_con().del("test_ts_all").unwrap(); 64 | let opts = TsOptions::default() 65 | .retention_time(60000) 66 | .label("a", "b") 67 | .label("c", "d") 68 | .uncompressed(true); 69 | let r: Value = get_con().ts_create("test_ts_all", opts).unwrap(); 70 | assert_eq!(Value::Okay, r); 71 | } 72 | 73 | #[test] 74 | fn test_create_ts_duplicate() { 75 | let _: () = get_con().del("test_ts_duplicate").unwrap(); 76 | let opts = TsOptions::default().duplicate_policy(TsDuplicatePolicy::Min); 77 | let r: Value = get_con().ts_create("test_ts_duplicate", opts).unwrap(); 78 | assert_eq!(Value::Okay, r); 79 | let info: TsInfo = get_con().ts_info("test_ts_duplicate").unwrap(); 80 | assert_eq!(info.duplicate_policy, Some(TsDuplicatePolicy::Min)); 81 | } 82 | 83 | #[test] 84 | fn test_ts_add() { 85 | let _: () = get_con().del("test_ts_add").unwrap(); 86 | let _: Value = get_con() 87 | .ts_create("test_ts_add", default_settings()) 88 | .unwrap(); 89 | let ts: u64 = get_con().ts_add("test_ts_add", 1234567890, 2.2).unwrap(); 90 | assert_eq!(ts, 1234567890); 91 | } 92 | 93 | #[test] 94 | fn test_ts_add_replace() { 95 | let _: () = get_con().del("test_ts_add_replace").unwrap(); 96 | let _: Value = get_con() 97 | .ts_create( 98 | "test_ts_add_replace", 99 | default_settings().duplicate_policy(TsDuplicatePolicy::Last), 100 | ) 101 | .unwrap(); 102 | let _: u64 = get_con() 103 | .ts_add("test_ts_add_replace", 1234567890, 2.2) 104 | .unwrap(); 105 | let _: u64 = get_con() 106 | .ts_add("test_ts_add_replace", 1234567890, 3.2) 107 | .unwrap(); 108 | let stored: (u64, f64) = get_con().ts_get("test_ts_add_replace").unwrap().unwrap(); 109 | assert_eq!(stored.1, 3.2); 110 | } 111 | 112 | #[test] 113 | fn test_ts_add_now() { 114 | let _: () = get_con().del("test_ts_add_now").unwrap(); 115 | let now = SystemTime::now() 116 | .duration_since(UNIX_EPOCH) 117 | .unwrap() 118 | .as_millis() as u64; 119 | let _: Value = get_con() 120 | .ts_create("test_ts_add_now", default_settings()) 121 | .unwrap(); 122 | let ts: u64 = get_con().ts_add_now("test_ts_add_now", 2.2).unwrap(); 123 | assert!(now <= ts); 124 | } 125 | 126 | #[test] 127 | fn test_ts_add_create() { 128 | let _: () = get_con().del("test_ts_add_create").unwrap(); 129 | let ts: u64 = get_con() 130 | .ts_add_create("test_ts_add_create", 1234567890, 2.2, default_settings()) 131 | .unwrap(); 132 | assert_eq!(ts, 1234567890); 133 | let ts2: u64 = get_con() 134 | .ts_add_create("test_ts_add_create", "*", 2.3, default_settings()) 135 | .unwrap(); 136 | assert!(ts2 > ts); 137 | } 138 | 139 | #[test] 140 | fn test_ts_add_create_overwrite_duplicate_policiy() { 141 | let redists_options_block = TsOptions::default().duplicate_policy(TsDuplicatePolicy::Block); 142 | let redists_options_last = TsOptions::default().duplicate_policy(TsDuplicatePolicy::Last); 143 | 144 | // delete as we want to start clean 145 | let _: () = get_con().del("test_ts_add_create_overwrite").unwrap(); 146 | 147 | // create with BLOCK 148 | let ts: u64 = get_con() 149 | .ts_add_create( 150 | "test_ts_add_create_overwrite", 151 | 1234567890, 152 | 2.2, 153 | redists_options_block.clone(), 154 | ) 155 | .unwrap(); 156 | assert_eq!(ts, 1234567890); 157 | 158 | // should be block 159 | let info1 = get_con().ts_info("test_ts_add_create_overwrite").unwrap(); 160 | assert_eq!(info1.duplicate_policy.unwrap(), TsDuplicatePolicy::Block); 161 | 162 | // update and try chnage to last should have no errors 163 | let ts2: u64 = get_con() 164 | .ts_add_create( 165 | "test_ts_add_create_overwrite", 166 | "*", 167 | 2.3, 168 | redists_options_last.clone(), 169 | ) 170 | .unwrap(); 171 | assert!(ts2 > ts); 172 | 173 | // update should not have changed the BLOCK setting 174 | let info2 = get_con().ts_info("test_ts_add_create_overwrite").unwrap(); 175 | assert_eq!(info2.duplicate_policy.unwrap(), TsDuplicatePolicy::Block); 176 | 177 | // start fresh 178 | let _: () = get_con().del("test_ts_add_create_overwrite").unwrap(); 179 | 180 | // create with LAST 181 | let ts: u64 = get_con() 182 | .ts_add_create( 183 | "test_ts_add_create_overwrite", 184 | 1234567890, 185 | 2.2, 186 | redists_options_last.clone(), 187 | ) 188 | .unwrap(); 189 | assert_eq!(ts, 1234567890); 190 | 191 | // should be LAST 192 | let info1 = get_con().ts_info("test_ts_add_create_overwrite").unwrap(); 193 | assert_eq!(info1.duplicate_policy.unwrap(), TsDuplicatePolicy::Last); 194 | // 195 | // update and try change to block should have no errors 196 | let ts2: u64 = get_con() 197 | .ts_add_create( 198 | "test_ts_add_create_overwrite", 199 | "*", 200 | 2.3, 201 | redists_options_block.clone(), 202 | ) 203 | .unwrap(); 204 | assert!(ts2 > ts); 205 | 206 | // update should not have changed the LAST setting 207 | let info2 = get_con().ts_info("test_ts_add_create_overwrite").unwrap(); 208 | assert_eq!(info2.duplicate_policy.unwrap(), TsDuplicatePolicy::Last); 209 | } 210 | 211 | #[test] 212 | fn test_ts_madd() { 213 | let _: () = get_con().del("test_ts_madd").unwrap(); 214 | let _: () = get_con().del("test_ts2_madd").unwrap(); 215 | let _: Value = get_con() 216 | .ts_create("test_ts_madd", default_settings()) 217 | .unwrap(); 218 | let _: Value = get_con() 219 | .ts_create("test_ts2_madd", default_settings()) 220 | .unwrap(); 221 | 222 | let expected: Vec = vec![1234, 4321]; 223 | let res: Vec = get_con() 224 | .ts_madd(&[("test_ts_madd", 1234, 1.0), ("test_ts2_madd", 4321, 2.0)]) 225 | .unwrap(); 226 | assert_eq!(expected, res); 227 | } 228 | 229 | #[test] 230 | fn test_ts_incrby_now() { 231 | let _: () = get_con().del("test_ts_incrby_now").unwrap(); 232 | let _: Value = get_con() 233 | .ts_create("test_ts_incrby_now", default_settings()) 234 | .unwrap(); 235 | 236 | let _: () = get_con().ts_incrby_now("test_ts_incrby_now", 1).unwrap(); 237 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_incrby_now").unwrap(); 238 | assert_eq!(v1.unwrap().1, 1.0); 239 | sleep(1); 240 | let _: () = get_con().ts_incrby_now("test_ts_incrby_now", 5).unwrap(); 241 | let v2: Option<(u64, f64)> = get_con().ts_get("test_ts_incrby_now").unwrap(); 242 | assert_eq!(v2.unwrap().1, 6.0); 243 | } 244 | 245 | #[test] 246 | fn test_ts_decrby_now() { 247 | let _: () = get_con().del("test_ts_decrby_now").unwrap(); 248 | let _: Value = get_con() 249 | .ts_create("test_ts_decrby_now", default_settings()) 250 | .unwrap(); 251 | 252 | let _: () = get_con().ts_add_now("test_ts_decrby_now", 10).unwrap(); 253 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby_now").unwrap(); 254 | assert_eq!(v1.unwrap().1, 10.0); 255 | sleep(1); 256 | 257 | let _: () = get_con().ts_decrby_now("test_ts_decrby_now", 1).unwrap(); 258 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby_now").unwrap(); 259 | assert_eq!(v1.unwrap().1, 9.0); 260 | sleep(1); 261 | 262 | let _: () = get_con().ts_decrby_now("test_ts_decrby_now", 5).unwrap(); 263 | let v2: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby_now").unwrap(); 264 | assert_eq!(v2.unwrap().1, 4.0); 265 | } 266 | 267 | #[test] 268 | fn test_ts_incrby() { 269 | let _: () = get_con().del("test_ts_incrby").unwrap(); 270 | let _: Value = get_con() 271 | .ts_create("test_ts_incrby", default_settings()) 272 | .unwrap(); 273 | 274 | let _: () = get_con().ts_incrby("test_ts_incrby", 123, 1).unwrap(); 275 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_incrby").unwrap(); 276 | assert_eq!(v1.unwrap(), (123, 1.0)); 277 | 278 | let _: () = get_con().ts_incrby("test_ts_incrby", 1234, 5).unwrap(); 279 | let v2: Option<(u64, f64)> = get_con().ts_get("test_ts_incrby").unwrap(); 280 | assert_eq!(v2.unwrap(), (1234, 6.0)); 281 | } 282 | 283 | #[test] 284 | fn test_ts_decrby() { 285 | let _: () = get_con().del("test_ts_decrby").unwrap(); 286 | let _: Value = get_con() 287 | .ts_create("test_ts_decrby", default_settings()) 288 | .unwrap(); 289 | let _: () = get_con().ts_add("test_ts_decrby", 12, 10).unwrap(); 290 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby").unwrap(); 291 | assert_eq!(v1.unwrap(), (12, 10.0)); 292 | 293 | let _: () = get_con().ts_decrby("test_ts_decrby", 123, 1).unwrap(); 294 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby").unwrap(); 295 | assert_eq!(v1.unwrap(), (123, 9.0)); 296 | 297 | let _: () = get_con().ts_decrby("test_ts_decrby", 1234, 5).unwrap(); 298 | let v2: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby").unwrap(); 299 | assert_eq!(v2.unwrap(), (1234, 4.0)); 300 | } 301 | 302 | #[test] 303 | fn test_ts_incrby_create() { 304 | let _: () = get_con().del("test_ts_incrby_create").unwrap(); 305 | 306 | let _: () = get_con() 307 | .ts_incrby_create("test_ts_incrby_create", 123, 1, default_settings()) 308 | .unwrap(); 309 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_incrby_create").unwrap(); 310 | assert_eq!(v1.unwrap(), (123, 1.0)); 311 | 312 | let _: () = get_con() 313 | .ts_incrby_create("test_ts_incrby_create", 1234, 5, default_settings()) 314 | .unwrap(); 315 | let v2: Option<(u64, f64)> = get_con().ts_get("test_ts_incrby_create").unwrap(); 316 | assert_eq!(v2.unwrap(), (1234, 6.0)); 317 | } 318 | 319 | #[test] 320 | fn test_ts_decrby_create() { 321 | let _: () = get_con().del("test_ts_decrby_create").unwrap(); 322 | 323 | let _: () = get_con() 324 | .ts_decrby_create("test_ts_decrby_create", 123, 1, default_settings()) 325 | .unwrap(); 326 | let v1: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby_create").unwrap(); 327 | assert_eq!(v1.unwrap(), (123, -1.0)); 328 | 329 | let _: () = get_con() 330 | .ts_decrby_create("test_ts_decrby_create", 1234, 5, default_settings()) 331 | .unwrap(); 332 | let v2: Option<(u64, f64)> = get_con().ts_get("test_ts_decrby_create").unwrap(); 333 | assert_eq!(v2.unwrap(), (1234, -6.0)); 334 | } 335 | 336 | #[test] 337 | fn test_ts_create_delete_rule() { 338 | let _: () = get_con().del("test_ts_create_delete_rule").unwrap(); 339 | let _: () = get_con().del("test_ts_create_delete_rule2").unwrap(); 340 | let _: Value = get_con() 341 | .ts_create("test_ts_create_delete_rule", default_settings()) 342 | .unwrap(); 343 | let _: Value = get_con() 344 | .ts_create("test_ts_create_delete_rule2", default_settings()) 345 | .unwrap(); 346 | let _: () = get_con() 347 | .ts_createrule( 348 | "test_ts_create_delete_rule", 349 | "test_ts_create_delete_rule2", 350 | TsAggregationType::Avg(5000), 351 | ) 352 | .unwrap(); 353 | 354 | let info: TsInfo = get_con().ts_info("test_ts_create_delete_rule").unwrap(); 355 | assert_eq!( 356 | info.rules, 357 | vec![( 358 | "test_ts_create_delete_rule2".to_string(), 359 | 5000, 360 | "AVG".to_string() 361 | )] 362 | ); 363 | 364 | let _: () = get_con() 365 | .ts_deleterule("test_ts_create_delete_rule", "test_ts_create_delete_rule2") 366 | .unwrap(); 367 | let info: TsInfo = get_con().ts_info("test_ts_create_delete_rule").unwrap(); 368 | assert_eq!(info.rules, vec![]); 369 | } 370 | 371 | #[test] 372 | fn test_ts_get() { 373 | let _: () = get_con().del("test_ts_get").unwrap(); 374 | let _: Value = get_con() 375 | .ts_create("test_ts_get", default_settings()) 376 | .unwrap(); 377 | let _: () = get_con().ts_add("test_ts_get", 1234, 2.0).unwrap(); 378 | let res: Option<(u64, f64)> = get_con().ts_get("test_ts_get").unwrap(); 379 | assert_eq!(Some((1234, 2.0)), res); 380 | } 381 | 382 | #[test] 383 | fn test_ts_mget() { 384 | let _: () = get_con().del("test_ts_mget").unwrap(); 385 | let _: () = get_con().del("test_ts_mget2").unwrap(); 386 | let _: () = get_con().del("test_ts_mget3").unwrap(); 387 | let opts: TsOptions = TsOptions::default().label("l", "mget"); 388 | let _: Value = get_con().ts_create("test_ts_mget", opts.clone()).unwrap(); 389 | let _: Value = get_con().ts_create("test_ts_mget2", opts.clone()).unwrap(); 390 | let _: Value = get_con().ts_create("test_ts_mget3", opts.clone()).unwrap(); 391 | let _: () = get_con() 392 | .ts_madd(&[ 393 | ("test_ts_mget", 12, 1.0), 394 | ("test_ts_mget", 123, 2.0), 395 | ("test_ts_mget", 1234, 3.0), 396 | ("test_ts_mget2", 21, 1.0), 397 | ("test_ts_mget2", 321, 2.0), 398 | ("test_ts_mget2", 4321, 3.0), 399 | ]) 400 | .unwrap(); 401 | let res: TsMget = get_con() 402 | .ts_mget( 403 | TsFilterOptions::default() 404 | .equals("l", "mget") 405 | .with_labels(true), 406 | ) 407 | .unwrap(); 408 | 409 | assert_eq!(res.values.len(), 3); 410 | assert_eq!(res.values[0].value, Some((1234, 3.0))); 411 | assert_eq!(res.values[1].value, Some((4321, 3.0))); 412 | assert_eq!(res.values[2].value, None); 413 | } 414 | 415 | #[test] 416 | fn test_ts_get_ts_info() { 417 | let _: () = get_con().del("test_ts_get_ts_info").unwrap(); 418 | let _: Value = get_con() 419 | .ts_create( 420 | "test_ts_get_ts_info", 421 | default_settings() 422 | .duplicate_policy(TsDuplicatePolicy::Last) 423 | .chunk_size(4096 * 2), 424 | ) 425 | .unwrap(); 426 | let _: () = get_con() 427 | .ts_add("test_ts_get_ts_info", "1234", 2.0) 428 | .unwrap(); 429 | let info: TsInfo = get_con().ts_info("test_ts_get_ts_info").unwrap(); 430 | assert_eq!(info.total_samples, 1); 431 | assert_eq!(info.first_timestamp, 1234); 432 | assert_eq!(info.last_timestamp, 1234); 433 | assert_eq!(info.chunk_count, 1); 434 | assert_eq!(info.chunk_size, 4096 * 2); 435 | assert_eq!(info.labels, vec![("a".to_string(), "b".to_string())]); 436 | } 437 | 438 | #[test] 439 | fn test_ts_alter() { 440 | let _: () = get_con().del("test_ts_alter").unwrap(); 441 | let _: Value = get_con() 442 | .ts_create( 443 | "test_ts_alter", 444 | default_settings() 445 | .duplicate_policy(TsDuplicatePolicy::Last) 446 | .chunk_size(4096 * 2), 447 | ) 448 | .unwrap(); 449 | let _: () = get_con().ts_add("test_ts_alter", "1234", 2.0).unwrap(); 450 | let info: TsInfo = get_con().ts_info("test_ts_alter").unwrap(); 451 | assert_eq!(info.chunk_count, 1); 452 | assert_eq!(info.chunk_size, 4096 * 2); 453 | assert_eq!(info.labels, vec![("a".to_string(), "b".to_string())]); 454 | 455 | let _: Value = get_con() 456 | .ts_alter( 457 | "test_ts_alter", 458 | TsOptions::default().chunk_size(4096 * 4).label("c", "d"), 459 | ) 460 | .unwrap(); 461 | let info2: TsInfo = get_con().ts_info("test_ts_alter").unwrap(); 462 | assert_eq!(info2.chunk_size, 4096 * 4); 463 | assert_eq!(info2.labels, vec![("c".to_string(), "d".to_string())]); 464 | } 465 | 466 | #[test] 467 | fn test_ts_range() { 468 | let _: () = get_con().del("test_ts_range").unwrap(); 469 | let _: () = get_con().del("test_ts_range2").unwrap(); 470 | let _: () = get_con() 471 | .ts_create("test_ts_range", default_settings()) 472 | .unwrap(); 473 | let _: () = get_con() 474 | .ts_create("test_ts_range2", default_settings()) 475 | .unwrap(); 476 | let _: () = get_con() 477 | .ts_madd(&[ 478 | ("test_ts_range", 12, 1.0), 479 | ("test_ts_range", 123, 2.0), 480 | ("test_ts_range", 1234, 3.0), 481 | ]) 482 | .unwrap(); 483 | 484 | let query = TsRangeQuery::default(); 485 | 486 | let res: TsRange = get_con().ts_range("test_ts_range", query.clone()).unwrap(); 487 | assert_eq!(res.values, vec![(12, 1.0), (123, 2.0), (1234, 3.0)]); 488 | 489 | let ts_filtered_res: TsRange = get_con() 490 | .ts_range("test_ts_range", query.clone().filter_by_ts(vec![123, 1234])) 491 | .unwrap(); 492 | assert_eq!(ts_filtered_res.values, vec![(123, 2.0), (1234, 3.0)]); 493 | 494 | let value_filtered_res: TsRange = get_con() 495 | .ts_range("test_ts_range", query.clone().filter_by_value(1.5, 2.5)) 496 | .unwrap(); 497 | assert_eq!(value_filtered_res.values, vec![(123, 2.0)]); 498 | 499 | let one_res: TsRange = get_con() 500 | .ts_range("test_ts_range", query.clone().count(1)) 501 | .unwrap(); 502 | assert_eq!(one_res.values, vec![(12, 1.0)]); 503 | 504 | let range_query = query.clone().from(12).to(123); 505 | 506 | let range_res: TsRange = get_con() 507 | .ts_range("test_ts_range", range_query.clone()) 508 | .unwrap(); 509 | assert_eq!(range_res.values, vec![(12, 1.0), (123, 2.0)]); 510 | 511 | let sum: TsRange = get_con() 512 | .ts_range( 513 | "test_ts_range", 514 | range_query 515 | .clone() 516 | .aggregation_type(TsAggregationType::Sum(10000)), 517 | ) 518 | .unwrap(); 519 | assert_eq!(sum.values, vec![(0, 3.0)]); 520 | 521 | let res: TsRange = get_con().ts_range("test_ts_range2", query.clone()).unwrap(); 522 | assert_eq!(res.values, vec![]); 523 | } 524 | 525 | #[test] 526 | fn test_ts_revrange() { 527 | let _: () = get_con().del("test_ts_revrange").unwrap(); 528 | let _: () = get_con().del("test_ts_revrange2").unwrap(); 529 | let _: () = get_con() 530 | .ts_create("test_ts_revrange", default_settings()) 531 | .unwrap(); 532 | let _: () = get_con() 533 | .ts_create("test_ts_revrange2", default_settings()) 534 | .unwrap(); 535 | let _: () = get_con() 536 | .ts_madd(&[ 537 | ("test_ts_revrange", 12, 1.0), 538 | ("test_ts_revrange", 123, 2.0), 539 | ("test_ts_revrange", 1234, 3.0), 540 | ]) 541 | .unwrap(); 542 | 543 | let res: TsRange = get_con() 544 | .ts_revrange("test_ts_revrange", TsRangeQuery::default()) 545 | .unwrap(); 546 | assert_eq!(res.values, vec![(1234, 3.0), (123, 2.0), (12, 1.0)]); 547 | 548 | let ts_filtered_res: TsRange = get_con() 549 | .ts_revrange( 550 | "test_ts_revrange", 551 | TsRangeQuery::default().filter_by_ts(vec![123, 1234]), 552 | ) 553 | .unwrap(); 554 | assert_eq!(ts_filtered_res.values, vec![(1234, 3.0), (123, 2.0)]); 555 | 556 | let value_filtered_res: TsRange = get_con() 557 | .ts_revrange( 558 | "test_ts_revrange", 559 | TsRangeQuery::default().filter_by_value(1.5, 2.5), 560 | ) 561 | .unwrap(); 562 | assert_eq!(value_filtered_res.values, vec![(123, 2.0)]); 563 | 564 | let one_res: TsRange = get_con() 565 | .ts_revrange("test_ts_revrange", TsRangeQuery::default().count(1)) 566 | .unwrap(); 567 | assert_eq!(one_res.values, vec![(1234, 3.0)]); 568 | 569 | let range_res: TsRange = get_con() 570 | .ts_revrange("test_ts_revrange", TsRangeQuery::default().from(12).to(123)) 571 | .unwrap(); 572 | assert_eq!(range_res.values, vec![(123, 2.0), (12, 1.0)]); 573 | 574 | let sum: TsRange = get_con() 575 | .ts_revrange( 576 | "test_ts_revrange", 577 | TsRangeQuery::default() 578 | .from(12) 579 | .to(123) 580 | .aggregation_type(TsAggregationType::Sum(10000)), 581 | ) 582 | .unwrap(); 583 | assert_eq!(sum.values, vec![(0, 3.0)]); 584 | 585 | let res: TsRange = get_con() 586 | .ts_revrange("test_ts_revrange2", TsRangeQuery::default()) 587 | .unwrap(); 588 | assert_eq!(res.values, vec![]); 589 | } 590 | 591 | #[test] 592 | fn test_ts_mrange() { 593 | let _: () = get_con().del("test_ts_mrange").unwrap(); 594 | let _: () = get_con().del("test_ts_mrange2").unwrap(); 595 | let opts: TsOptions = TsOptions::default().label("l", "mrange"); 596 | let _: () = get_con().ts_create("test_ts_mrange", opts.clone()).unwrap(); 597 | let _: () = get_con() 598 | .ts_create("test_ts_mrange2", opts.clone()) 599 | .unwrap(); 600 | let _: () = get_con() 601 | .ts_madd(&[ 602 | ("test_ts_mrange", 12, 1.0), 603 | ("test_ts_mrange", 123, 2.0), 604 | ("test_ts_mrange", 1234, 3.0), 605 | ("test_ts_mrange2", 21, 1.0), 606 | ("test_ts_mrange2", 321, 2.0), 607 | ("test_ts_mrange2", 4321, 3.0), 608 | ]) 609 | .unwrap(); 610 | 611 | let res: TsMrange = get_con() 612 | .ts_mrange( 613 | TsRangeQuery::default(), 614 | TsFilterOptions::default() 615 | .equals("l", "mrange") 616 | .with_labels(true), 617 | ) 618 | .unwrap(); 619 | assert_eq!(res.values.len(), 2); 620 | assert_eq!( 621 | res.values[1].values, 622 | vec![(21, 1.0), (321, 2.0), (4321, 3.0)] 623 | ); 624 | assert_eq!(res.values[0].key, "test_ts_mrange"); 625 | assert_eq!(res.values[1].key, "test_ts_mrange2"); 626 | assert_eq!( 627 | res.values[0].labels, 628 | vec![("l".to_string(), "mrange".to_string())] 629 | ); 630 | 631 | let res2: TsMrange = get_con() 632 | .ts_mrange( 633 | TsRangeQuery::default(), 634 | TsFilterOptions::default() 635 | .equals("none", "existing") 636 | .with_labels(true), 637 | ) 638 | .unwrap(); 639 | assert!(res2.values.is_empty()); 640 | } 641 | 642 | #[test] 643 | fn test_ts_mrevrange() { 644 | let _: () = get_con().del("test_ts_mrevrange").unwrap(); 645 | let _: () = get_con().del("test_ts_mrevrange2").unwrap(); 646 | let opts: TsOptions = TsOptions::default().label("l", "mrevrange"); 647 | let _: () = get_con() 648 | .ts_create("test_ts_mrevrange", opts.clone()) 649 | .unwrap(); 650 | let _: () = get_con() 651 | .ts_create("test_ts_mrevrange2", opts.clone()) 652 | .unwrap(); 653 | let _: () = get_con() 654 | .ts_madd(&[ 655 | ("test_ts_mrevrange", 12, 1.0), 656 | ("test_ts_mrevrange", 123, 2.0), 657 | ("test_ts_mrevrange", 1234, 3.0), 658 | ("test_ts_mrevrange2", 21, 1.0), 659 | ("test_ts_mrevrange2", 321, 2.0), 660 | ("test_ts_mrevrange2", 4321, 3.0), 661 | ]) 662 | .unwrap(); 663 | 664 | let res: TsMrange = get_con() 665 | .ts_mrevrange( 666 | TsRangeQuery::default(), 667 | TsFilterOptions::default() 668 | .equals("l", "mrevrange") 669 | .with_labels(true), 670 | ) 671 | .unwrap(); 672 | assert_eq!(res.values.len(), 2); 673 | assert_eq!( 674 | res.values[1].values, 675 | vec![(4321, 3.0), (321, 2.0), (21, 1.0)] 676 | ); 677 | assert_eq!(res.values[0].key, "test_ts_mrevrange"); 678 | assert_eq!(res.values[1].key, "test_ts_mrevrange2"); 679 | assert_eq!( 680 | res.values[0].labels, 681 | vec![("l".to_string(), "mrevrange".to_string())] 682 | ); 683 | 684 | let res2: TsMrange = get_con() 685 | .ts_mrange( 686 | TsRangeQuery::default(), 687 | TsFilterOptions::default() 688 | .equals("none", "existing") 689 | .with_labels(true), 690 | ) 691 | .unwrap(); 692 | assert!(res2.values.is_empty()); 693 | } 694 | 695 | #[test] 696 | fn test_ts_queryindex() { 697 | let _: () = get_con().del("test_ts_queryindex").unwrap(); 698 | let _: Value = get_con() 699 | .ts_create("test_ts_queryindex", default_settings()) 700 | .unwrap(); 701 | let _: () = get_con().ts_add("test_ts_queryindex", "1234", 2.0).unwrap(); 702 | let index: Vec = get_con() 703 | .ts_queryindex(TsFilterOptions::default().equals("a", "b")) 704 | .unwrap(); 705 | assert!(index.contains(&"test_ts_queryindex".to_string())); 706 | } 707 | --------------------------------------------------------------------------------