1use std::{fmt, str::FromStr};
5
6use anyhow::anyhow;
7use async_trait::async_trait;
8use linera_client::config::GenesisConfig;
9use linera_execution::WasmRuntime;
10use linera_storage::{DbStorage, Storage, DEFAULT_NAMESPACE};
11#[cfg(feature = "storage-service")]
12use linera_storage_service::{
13 client::ServiceStoreClient,
14 common::{ServiceStoreConfig, ServiceStoreInternalConfig},
15};
16#[cfg(feature = "dynamodb")]
17use linera_views::dynamo_db::{DynamoDbStore, DynamoDbStoreConfig};
18use linera_views::{
19 memory::{MemoryStore, MemoryStoreConfig},
20 store::{CommonStoreConfig, KeyValueStore},
21};
22use serde::{Deserialize, Serialize};
23use tracing::error;
24#[allow(unused_imports)]
25use {anyhow::bail, linera_views::store::AdminKeyValueStore as _};
26#[cfg(all(feature = "rocksdb", feature = "scylladb"))]
27use {
28 linera_storage::ChainStatesFirstAssignment,
29 linera_views::backends::dual::{DualStore, DualStoreConfig},
30 std::path::Path,
31};
32#[cfg(feature = "rocksdb")]
33use {
34 linera_views::rocks_db::{PathWithGuard, RocksDbSpawnMode, RocksDbStore, RocksDbStoreConfig},
35 std::path::PathBuf,
36};
37#[cfg(feature = "scylladb")]
38use {
39 linera_views::scylla_db::{ScyllaDbStore, ScyllaDbStoreConfig},
40 std::num::NonZeroU16,
41 tracing::debug,
42};
43
44#[derive(Clone, Debug, Deserialize, Serialize)]
46pub enum StoreConfig {
47 #[cfg(feature = "storage-service")]
49 Service {
50 config: ServiceStoreConfig,
51 namespace: String,
52 },
53 Memory {
55 config: MemoryStoreConfig,
56 namespace: String,
57 },
58 #[cfg(feature = "rocksdb")]
60 RocksDb {
61 config: RocksDbStoreConfig,
62 namespace: String,
63 },
64 #[cfg(feature = "dynamodb")]
66 DynamoDb {
67 config: DynamoDbStoreConfig,
68 namespace: String,
69 },
70 #[cfg(feature = "scylladb")]
72 ScyllaDb {
73 config: ScyllaDbStoreConfig,
74 namespace: String,
75 },
76 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
77 DualRocksDbScyllaDb {
78 config: DualStoreConfig<RocksDbStoreConfig, ScyllaDbStoreConfig>,
79 namespace: String,
80 },
81}
82
83#[derive(Clone, Debug)]
85#[cfg_attr(any(test), derive(Eq, PartialEq))]
86pub enum StorageConfig {
87 #[cfg(feature = "storage-service")]
89 Service {
90 endpoint: String,
92 },
93 Memory,
95 #[cfg(feature = "rocksdb")]
97 RocksDb {
98 path: PathBuf,
100 spawn_mode: RocksDbSpawnMode,
102 },
103 #[cfg(feature = "dynamodb")]
105 DynamoDb {
106 use_dynamodb_local: bool,
108 },
109 #[cfg(feature = "scylladb")]
111 ScyllaDb {
112 uri: String,
114 },
115 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
116 DualRocksDbScyllaDb {
117 path_with_guard: PathWithGuard,
119 spawn_mode: RocksDbSpawnMode,
121 uri: String,
123 },
124}
125
126impl StorageConfig {
127 pub fn maybe_append_shard_path(&mut self, _shard: usize) -> std::io::Result<()> {
128 match self {
129 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
130 StorageConfig::DualRocksDbScyllaDb {
131 path_with_guard,
132 spawn_mode: _,
133 uri: _,
134 } => {
135 let shard_str = format!("shard_{}", _shard);
136 path_with_guard.path_buf.push(shard_str);
137 std::fs::create_dir_all(&path_with_guard.path_buf)
138 }
139 _ => Ok(()),
140 }
141 }
142}
143
144#[derive(Clone, Debug)]
146#[cfg_attr(any(test), derive(Eq, PartialEq))]
147pub struct StorageConfigNamespace {
148 pub storage_config: StorageConfig,
150 pub namespace: String,
152}
153
154const MEMORY: &str = "memory";
155const MEMORY_EXT: &str = "memory:";
156#[cfg(feature = "storage-service")]
157const STORAGE_SERVICE: &str = "service:";
158#[cfg(feature = "rocksdb")]
159const ROCKS_DB: &str = "rocksdb:";
160#[cfg(feature = "dynamodb")]
161const DYNAMO_DB: &str = "dynamodb:";
162#[cfg(feature = "scylladb")]
163const SCYLLA_DB: &str = "scylladb:";
164#[cfg(all(feature = "rocksdb", feature = "scylladb"))]
165const DUAL_ROCKS_DB_SCYLLA_DB: &str = "dualrocksdbscylladb:";
166
167impl FromStr for StorageConfigNamespace {
168 type Err = anyhow::Error;
169
170 fn from_str(input: &str) -> Result<Self, Self::Err> {
171 if input == MEMORY {
172 let namespace = DEFAULT_NAMESPACE.to_string();
173 let storage_config = StorageConfig::Memory;
174 return Ok(StorageConfigNamespace {
175 storage_config,
176 namespace,
177 });
178 }
179 if let Some(s) = input.strip_prefix(MEMORY_EXT) {
180 let namespace = s.to_string();
181 let storage_config = StorageConfig::Memory;
182 return Ok(StorageConfigNamespace {
183 storage_config,
184 namespace,
185 });
186 }
187 #[cfg(feature = "storage-service")]
188 if let Some(s) = input.strip_prefix(STORAGE_SERVICE) {
189 if s.is_empty() {
190 bail!(
191 "For Storage service, the formatting has to be service:endpoint:namespace,\
192example service:tcp:127.0.0.1:7878:table_do_my_test"
193 );
194 }
195 let parts = s.split(':').collect::<Vec<_>>();
196 if parts.len() != 4 {
197 bail!("We should have one endpoint and one namespace");
198 }
199 let protocol = parts[0];
200 if protocol != "tcp" {
201 bail!("Only allowed protocol is tcp");
202 }
203 let endpoint = parts[1];
204 let port = parts[2];
205 let mut endpoint = endpoint.to_string();
206 endpoint.push(':');
207 endpoint.push_str(port);
208 let endpoint = endpoint.to_string();
209 let namespace = parts[3].to_string();
210 let storage_config = StorageConfig::Service { endpoint };
211 return Ok(StorageConfigNamespace {
212 storage_config,
213 namespace,
214 });
215 }
216 #[cfg(feature = "rocksdb")]
217 if let Some(s) = input.strip_prefix(ROCKS_DB) {
218 if s.is_empty() {
219 bail!(
220 "For RocksDB, the formatting has to be rocksdb:directory or rocksdb:directory:spawn_mode:namespace");
221 }
222 let parts = s.split(':').collect::<Vec<_>>();
223 if parts.len() == 1 {
224 let path = parts[0].to_string().into();
225 let namespace = DEFAULT_NAMESPACE.to_string();
226 let spawn_mode = RocksDbSpawnMode::SpawnBlocking;
227 let storage_config = StorageConfig::RocksDb { path, spawn_mode };
228 return Ok(StorageConfigNamespace {
229 storage_config,
230 namespace,
231 });
232 }
233 if parts.len() == 2 || parts.len() == 3 {
234 let path = parts[0].to_string().into();
235 let spawn_mode = match parts[1] {
236 "spawn_blocking" => Ok(RocksDbSpawnMode::SpawnBlocking),
237 "block_in_place" => Ok(RocksDbSpawnMode::BlockInPlace),
238 "runtime" => Ok(RocksDbSpawnMode::get_spawn_mode_from_runtime()),
239 _ => Err(anyhow!("Failed to parse {} as a spawn_mode", parts[1])),
240 }?;
241 let namespace = if parts.len() == 2 {
242 DEFAULT_NAMESPACE.to_string()
243 } else {
244 parts[2].to_string()
245 };
246 let storage_config = StorageConfig::RocksDb { path, spawn_mode };
247 return Ok(StorageConfigNamespace {
248 storage_config,
249 namespace,
250 });
251 }
252 bail!("We should have one, two or three parts");
253 }
254 #[cfg(feature = "dynamodb")]
255 if let Some(s) = input.strip_prefix(DYNAMO_DB) {
256 let mut parts = s.splitn(2, ':');
257 let namespace = parts
258 .next()
259 .ok_or_else(|| anyhow!("Missing DynamoDB table name, e.g. {DYNAMO_DB}TABLE"))?
260 .to_string();
261 let use_dynamodb_local = match parts.next() {
262 None | Some("env") => false,
263 Some("dynamodb_local") => true,
264 Some(unknown) => {
265 bail!(
266 "Invalid DynamoDB endpoint {unknown:?}. \
267 Expected {DYNAMO_DB}TABLE:[env|dynamodb_local]"
268 );
269 }
270 };
271 let storage_config = StorageConfig::DynamoDb { use_dynamodb_local };
272 return Ok(StorageConfigNamespace {
273 storage_config,
274 namespace,
275 });
276 }
277 #[cfg(feature = "scylladb")]
278 if let Some(s) = input.strip_prefix(SCYLLA_DB) {
279 let mut uri: Option<String> = None;
280 let mut namespace: Option<String> = None;
281 let parse_error: &'static str = "Correct format is tcp:db_hostname:port.";
282 if !s.is_empty() {
283 let mut parts = s.split(':');
284 while let Some(part) = parts.next() {
285 match part {
286 "tcp" => {
287 let address = parts.next().ok_or_else(|| {
288 anyhow!("Failed to find address for {s}. {parse_error}")
289 })?;
290 let port_str = parts.next().ok_or_else(|| {
291 anyhow!("Failed to find port for {s}. {parse_error}")
292 })?;
293 let port = NonZeroU16::from_str(port_str).map_err(|_| {
294 anyhow!(
295 "Failed to find parse port {port_str} for {s}. {parse_error}",
296 )
297 })?;
298 if uri.is_some() {
299 bail!("The uri has already been assigned");
300 }
301 uri = Some(format!("{}:{}", &address, port));
302 }
303 _ if part.starts_with("table") => {
304 if namespace.is_some() {
305 bail!("The namespace has already been assigned");
306 }
307 namespace = Some(part.to_string());
308 }
309 _ => {
310 bail!("the entry \"{part}\" is not matching");
311 }
312 }
313 }
314 }
315 let uri = uri.unwrap_or("localhost:9042".to_string());
316 let namespace = namespace.unwrap_or(DEFAULT_NAMESPACE.to_string());
317 let storage_config = StorageConfig::ScyllaDb { uri };
318 debug!("ScyllaDB connection info: {:?}", storage_config);
319 return Ok(StorageConfigNamespace {
320 storage_config,
321 namespace,
322 });
323 }
324 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
325 if let Some(s) = input.strip_prefix(DUAL_ROCKS_DB_SCYLLA_DB) {
326 let parts = s.split(':').collect::<Vec<_>>();
327 if parts.len() != 5 && parts.len() != 6 {
328 bail!(
329 "For DualRocksDbScyllaDb, the formatting has to be dualrocksdbscylladb:directory:mode:tcp:hostname:port:namespace"
330 );
331 }
332 let path = Path::new(parts[0]);
333 let path = path.to_path_buf();
334 let path_with_guard = PathWithGuard::new(path);
335 let spawn_mode = match parts[1] {
336 "spawn_blocking" => Ok(RocksDbSpawnMode::SpawnBlocking),
337 "block_in_place" => Ok(RocksDbSpawnMode::BlockInPlace),
338 "runtime" => Ok(RocksDbSpawnMode::get_spawn_mode_from_runtime()),
339 _ => Err(anyhow!("Failed to parse {} as a spawn_mode", parts[1])),
340 }?;
341 let protocol = parts[2];
342 if protocol != "tcp" {
343 bail!("The only allowed protocol is tcp");
344 }
345 let address = parts[3];
346 let port_str = parts[4];
347 let port = NonZeroU16::from_str(port_str)
348 .map_err(|_| anyhow!("Failed to find parse port {port_str} for {s}"))?;
349 let uri = format!("{}:{}", &address, port);
350 let storage_config = StorageConfig::DualRocksDbScyllaDb {
351 path_with_guard,
352 spawn_mode,
353 uri,
354 };
355 let namespace = if parts.len() == 5 {
356 DEFAULT_NAMESPACE.to_string()
357 } else {
358 parts[5].to_string()
359 };
360 return Ok(StorageConfigNamespace {
361 storage_config,
362 namespace,
363 });
364 }
365 error!("available storage: memory");
366 #[cfg(feature = "storage-service")]
367 error!("Also available is linera-storage-service");
368 #[cfg(feature = "rocksdb")]
369 error!("Also available is RocksDB");
370 #[cfg(feature = "dynamodb")]
371 error!("Also available is DynamoDB");
372 #[cfg(feature = "scylladb")]
373 error!("Also available is ScyllaDB");
374 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
375 error!("Also available is DualRocksDbScyllaDb");
376 Err(anyhow!("The input has not matched: {input}"))
377 }
378}
379
380impl StorageConfigNamespace {
381 pub async fn add_common_config(
383 &self,
384 common_config: CommonStoreConfig,
385 ) -> Result<StoreConfig, anyhow::Error> {
386 let namespace = self.namespace.clone();
387 match &self.storage_config {
388 #[cfg(feature = "storage-service")]
389 StorageConfig::Service { endpoint } => {
390 let endpoint = endpoint.clone();
391 let inner_config = ServiceStoreInternalConfig {
392 endpoint,
393 common_config: common_config.reduced(),
394 };
395 let config = ServiceStoreConfig {
396 inner_config,
397 storage_cache_config: common_config.storage_cache_config,
398 };
399 Ok(StoreConfig::Service { config, namespace })
400 }
401 StorageConfig::Memory => {
402 let config = MemoryStoreConfig {
403 common_config: common_config.reduced(),
404 };
405 Ok(StoreConfig::Memory { config, namespace })
406 }
407 #[cfg(feature = "rocksdb")]
408 StorageConfig::RocksDb { path, spawn_mode } => {
409 let path_buf = path.to_path_buf();
410 let path_with_guard = PathWithGuard::new(path_buf);
411 let config = RocksDbStoreConfig::new(*spawn_mode, path_with_guard, common_config);
412 Ok(StoreConfig::RocksDb { config, namespace })
413 }
414 #[cfg(feature = "dynamodb")]
415 StorageConfig::DynamoDb { use_dynamodb_local } => {
416 let config = DynamoDbStoreConfig::new(*use_dynamodb_local, common_config);
417 Ok(StoreConfig::DynamoDb { config, namespace })
418 }
419 #[cfg(feature = "scylladb")]
420 StorageConfig::ScyllaDb { uri } => {
421 let config = ScyllaDbStoreConfig::new(uri.to_string(), common_config);
422 Ok(StoreConfig::ScyllaDb { config, namespace })
423 }
424 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
425 StorageConfig::DualRocksDbScyllaDb {
426 path_with_guard,
427 spawn_mode,
428 uri,
429 } => {
430 let first_config = RocksDbStoreConfig::new(
431 *spawn_mode,
432 path_with_guard.clone(),
433 common_config.clone(),
434 );
435 let second_config = ScyllaDbStoreConfig::new(uri.to_string(), common_config);
436 let config = DualStoreConfig {
437 first_config,
438 second_config,
439 };
440 Ok(StoreConfig::DualRocksDbScyllaDb { config, namespace })
441 }
442 }
443 }
444}
445
446impl fmt::Display for StorageConfigNamespace {
447 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
448 let namespace = &self.namespace;
449 match &self.storage_config {
450 #[cfg(feature = "storage-service")]
451 StorageConfig::Service { endpoint } => {
452 write!(f, "service:tcp:{}:{}", endpoint, namespace)
453 }
454 StorageConfig::Memory => {
455 write!(f, "memory:{}", namespace)
456 }
457 #[cfg(feature = "rocksdb")]
458 StorageConfig::RocksDb { path, spawn_mode } => {
459 let spawn_mode = spawn_mode.to_string();
460 write!(f, "rocksdb:{}:{}:{}", path.display(), spawn_mode, namespace)
461 }
462 #[cfg(feature = "dynamodb")]
463 StorageConfig::DynamoDb { use_dynamodb_local } => match use_dynamodb_local {
464 true => write!(f, "dynamodb:{}:dynamodb_local", namespace),
465 false => write!(f, "dynamodb:{}:env", namespace),
466 },
467 #[cfg(feature = "scylladb")]
468 StorageConfig::ScyllaDb { uri } => {
469 write!(f, "scylladb:tcp:{}:{}", uri, namespace)
470 }
471 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
472 StorageConfig::DualRocksDbScyllaDb {
473 path_with_guard,
474 spawn_mode,
475 uri,
476 } => {
477 write!(
478 f,
479 "dualrocksdbscylladb:{}:{}:tcp:{}:{}",
480 path_with_guard.path_buf.display(),
481 spawn_mode,
482 uri,
483 namespace
484 )
485 }
486 }
487 }
488}
489
490#[async_trait]
491pub trait Runnable {
492 type Output;
493
494 async fn run<S>(self, storage: S) -> Self::Output
495 where
496 S: Storage + Clone + Send + Sync + 'static;
497}
498
499#[async_trait]
500pub trait RunnableWithStore {
501 type Output;
502
503 async fn run<S>(
504 self,
505 config: S::Config,
506 namespace: String,
507 ) -> Result<Self::Output, anyhow::Error>
508 where
509 S: KeyValueStore + Clone + Send + Sync + 'static,
510 S::Error: Send + Sync;
511}
512
513impl StoreConfig {
514 #[allow(unused_variables)]
515 pub async fn run_with_storage<Job>(
516 self,
517 genesis_config: &GenesisConfig,
518 wasm_runtime: Option<WasmRuntime>,
519 job: Job,
520 ) -> Result<Job::Output, anyhow::Error>
521 where
522 Job: Runnable,
523 {
524 match self {
525 StoreConfig::Memory { config, namespace } => {
526 let store_config = MemoryStoreConfig::new(config.common_config.max_stream_queries);
527 let mut storage = DbStorage::<MemoryStore, _>::maybe_create_and_connect(
528 &store_config,
529 &namespace,
530 wasm_runtime,
531 )
532 .await?;
533 genesis_config.initialize_storage(&mut storage).await?;
535 Ok(job.run(storage).await)
536 }
537 #[cfg(feature = "storage-service")]
538 StoreConfig::Service { config, namespace } => {
539 let storage =
540 DbStorage::<ServiceStoreClient, _>::connect(&config, &namespace, wasm_runtime)
541 .await?;
542 Ok(job.run(storage).await)
543 }
544 #[cfg(feature = "rocksdb")]
545 StoreConfig::RocksDb { config, namespace } => {
546 let storage =
547 DbStorage::<RocksDbStore, _>::connect(&config, &namespace, wasm_runtime)
548 .await?;
549 Ok(job.run(storage).await)
550 }
551 #[cfg(feature = "dynamodb")]
552 StoreConfig::DynamoDb { config, namespace } => {
553 let storage =
554 DbStorage::<DynamoDbStore, _>::connect(&config, &namespace, wasm_runtime)
555 .await?;
556 Ok(job.run(storage).await)
557 }
558 #[cfg(feature = "scylladb")]
559 StoreConfig::ScyllaDb { config, namespace } => {
560 let storage =
561 DbStorage::<ScyllaDbStore, _>::connect(&config, &namespace, wasm_runtime)
562 .await?;
563 Ok(job.run(storage).await)
564 }
565 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
566 StoreConfig::DualRocksDbScyllaDb { config, namespace } => {
567 let storage = DbStorage::<
568 DualStore<RocksDbStore, ScyllaDbStore, ChainStatesFirstAssignment>,
569 _,
570 >::connect(&config, &namespace, wasm_runtime)
571 .await?;
572 Ok(job.run(storage).await)
573 }
574 }
575 }
576
577 #[allow(unused_variables)]
578 pub async fn run_with_store<Job>(self, job: Job) -> Result<Job::Output, anyhow::Error>
579 where
580 Job: RunnableWithStore,
581 {
582 match self {
583 StoreConfig::Memory { .. } => {
584 Err(anyhow!("Cannot run admin operations on the memory store"))
585 }
586 #[cfg(feature = "storage-service")]
587 StoreConfig::Service { config, namespace } => {
588 Ok(job.run::<ServiceStoreClient>(config, namespace).await?)
589 }
590 #[cfg(feature = "rocksdb")]
591 StoreConfig::RocksDb { config, namespace } => {
592 Ok(job.run::<RocksDbStore>(config, namespace).await?)
593 }
594 #[cfg(feature = "dynamodb")]
595 StoreConfig::DynamoDb { config, namespace } => {
596 Ok(job.run::<DynamoDbStore>(config, namespace).await?)
597 }
598 #[cfg(feature = "scylladb")]
599 StoreConfig::ScyllaDb { config, namespace } => {
600 Ok(job.run::<ScyllaDbStore>(config, namespace).await?)
601 }
602 #[cfg(all(feature = "rocksdb", feature = "scylladb"))]
603 StoreConfig::DualRocksDbScyllaDb { config, namespace } => Ok(job
604 .run::<DualStore<RocksDbStore, ScyllaDbStore, ChainStatesFirstAssignment>>(
605 config, namespace,
606 )
607 .await?),
608 }
609 }
610
611 pub async fn initialize(self, config: &GenesisConfig) -> Result<(), anyhow::Error> {
612 self.run_with_store(InitializeStorageJob(config)).await
613 }
614}
615
616struct InitializeStorageJob<'a>(&'a GenesisConfig);
617
618#[async_trait]
619impl RunnableWithStore for InitializeStorageJob<'_> {
620 type Output = ();
621
622 async fn run<S>(
623 self,
624 config: S::Config,
625 namespace: String,
626 ) -> Result<Self::Output, anyhow::Error>
627 where
628 S: KeyValueStore + Clone + Send + Sync + 'static,
629 S::Error: Send + Sync,
630 {
631 let mut storage =
632 DbStorage::<S, _>::maybe_create_and_connect(&config, &namespace, None).await?;
633 self.0.initialize_storage(&mut storage).await?;
634 Ok(())
635 }
636}
637
638#[test]
639fn test_memory_storage_config_from_str() {
640 assert_eq!(
641 StorageConfigNamespace::from_str("memory:").unwrap(),
642 StorageConfigNamespace {
643 storage_config: StorageConfig::Memory,
644 namespace: "".into()
645 }
646 );
647 assert_eq!(
648 StorageConfigNamespace::from_str("memory").unwrap(),
649 StorageConfigNamespace {
650 storage_config: StorageConfig::Memory,
651 namespace: DEFAULT_NAMESPACE.into()
652 }
653 );
654 assert_eq!(
655 StorageConfigNamespace::from_str("memory:table_linera").unwrap(),
656 StorageConfigNamespace {
657 storage_config: StorageConfig::Memory,
658 namespace: DEFAULT_NAMESPACE.into()
659 }
660 );
661}
662
663#[cfg(feature = "storage-service")]
664#[test]
665fn test_shared_store_config_from_str() {
666 assert_eq!(
667 StorageConfigNamespace::from_str("service:tcp:127.0.0.1:8942:linera").unwrap(),
668 StorageConfigNamespace {
669 storage_config: StorageConfig::Service {
670 endpoint: "127.0.0.1:8942".to_string()
671 },
672 namespace: "linera".into()
673 }
674 );
675 assert!(StorageConfigNamespace::from_str("service:tcp:127.0.0.1:8942").is_err());
676 assert!(StorageConfigNamespace::from_str("service:tcp:127.0.0.1:linera").is_err());
677}
678
679#[cfg(feature = "rocksdb")]
680#[test]
681fn test_rocks_db_storage_config_from_str() {
682 assert!(StorageConfigNamespace::from_str("rocksdb_foo.db").is_err());
683 assert_eq!(
684 StorageConfigNamespace::from_str("rocksdb:foo.db").unwrap(),
685 StorageConfigNamespace {
686 storage_config: StorageConfig::RocksDb {
687 path: "foo.db".into(),
688 spawn_mode: RocksDbSpawnMode::SpawnBlocking,
689 },
690 namespace: DEFAULT_NAMESPACE.to_string()
691 }
692 );
693 assert_eq!(
694 StorageConfigNamespace::from_str("rocksdb:foo.db:block_in_place").unwrap(),
695 StorageConfigNamespace {
696 storage_config: StorageConfig::RocksDb {
697 path: "foo.db".into(),
698 spawn_mode: RocksDbSpawnMode::BlockInPlace,
699 },
700 namespace: DEFAULT_NAMESPACE.to_string()
701 }
702 );
703 assert_eq!(
704 StorageConfigNamespace::from_str("rocksdb:foo.db:block_in_place:chosen_namespace").unwrap(),
705 StorageConfigNamespace {
706 storage_config: StorageConfig::RocksDb {
707 path: "foo.db".into(),
708 spawn_mode: RocksDbSpawnMode::BlockInPlace,
709 },
710 namespace: "chosen_namespace".into()
711 }
712 );
713}
714
715#[cfg(feature = "dynamodb")]
716#[test]
717fn test_aws_storage_config_from_str() {
718 assert_eq!(
719 StorageConfigNamespace::from_str("dynamodb:table").unwrap(),
720 StorageConfigNamespace {
721 storage_config: StorageConfig::DynamoDb {
722 use_dynamodb_local: false
723 },
724 namespace: "table".to_string()
725 }
726 );
727 assert_eq!(
728 StorageConfigNamespace::from_str("dynamodb:table:env").unwrap(),
729 StorageConfigNamespace {
730 storage_config: StorageConfig::DynamoDb {
731 use_dynamodb_local: false
732 },
733 namespace: "table".to_string()
734 }
735 );
736 assert_eq!(
737 StorageConfigNamespace::from_str("dynamodb:table:dynamodb_local").unwrap(),
738 StorageConfigNamespace {
739 storage_config: StorageConfig::DynamoDb {
740 use_dynamodb_local: true
741 },
742 namespace: "table".to_string()
743 }
744 );
745 assert!(StorageConfigNamespace::from_str("dynamodb").is_err());
746 assert!(StorageConfigNamespace::from_str("dynamodb:").is_err());
747 assert!(StorageConfigNamespace::from_str("dynamodb:1").is_err());
748 assert!(StorageConfigNamespace::from_str("dynamodb:wrong:endpoint").is_err());
749}
750
751#[cfg(feature = "scylladb")]
752#[test]
753fn test_scylla_db_storage_config_from_str() {
754 assert_eq!(
755 StorageConfigNamespace::from_str("scylladb:").unwrap(),
756 StorageConfigNamespace {
757 storage_config: StorageConfig::ScyllaDb {
758 uri: "localhost:9042".to_string()
759 },
760 namespace: DEFAULT_NAMESPACE.to_string()
761 }
762 );
763 assert_eq!(
764 StorageConfigNamespace::from_str("scylladb:tcp:db_hostname:230:table_other_storage")
765 .unwrap(),
766 StorageConfigNamespace {
767 storage_config: StorageConfig::ScyllaDb {
768 uri: "db_hostname:230".to_string()
769 },
770 namespace: "table_other_storage".to_string()
771 }
772 );
773 assert_eq!(
774 StorageConfigNamespace::from_str("scylladb:tcp:db_hostname:230").unwrap(),
775 StorageConfigNamespace {
776 storage_config: StorageConfig::ScyllaDb {
777 uri: "db_hostname:230".to_string()
778 },
779 namespace: DEFAULT_NAMESPACE.to_string()
780 }
781 );
782 assert!(StorageConfigNamespace::from_str("scylladb:-10").is_err());
783 assert!(StorageConfigNamespace::from_str("scylladb:70000").is_err());
784 assert!(StorageConfigNamespace::from_str("scylladb:230:234").is_err());
785 assert!(StorageConfigNamespace::from_str("scylladb:tcp:address1").is_err());
786 assert!(StorageConfigNamespace::from_str("scylladb:tcp:address1:tcp:/address2").is_err());
787 assert!(StorageConfigNamespace::from_str("scylladb:wrong").is_err());
788}