Skip to main content

linera_views/backends/
scylla_db.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Implements [`crate::store::KeyValueStore`] for the ScyllaDB database.
5//!
6//! The current connection is done via a Session and a corresponding primary key called
7//! "namespace". The maximum number of concurrent queries is controlled by
8//! `max_concurrent_queries`.
9
10use std::{
11    collections::{BTreeSet, HashMap},
12    ops::Deref,
13    sync::{
14        atomic::{AtomicI64, Ordering},
15        Arc,
16    },
17    time::{SystemTime, UNIX_EPOCH},
18};
19
20use async_lock::{Semaphore, SemaphoreGuard};
21use futures::{future::join_all, StreamExt as _};
22use linera_base::{ensure, util::future::FutureSyncExt as _};
23use scylla::{
24    client::{
25        execution_profile::{ExecutionProfile, ExecutionProfileHandle},
26        session::Session,
27        session_builder::SessionBuilder,
28    },
29    deserialize::{DeserializationError, TypeCheckError},
30    errors::{
31        DbError, ExecutionError, IntoRowsResultError, NewSessionError, NextPageError, NextRowError,
32        PagerExecutionError, PrepareError, RequestAttemptError, RequestError, RowsError,
33    },
34    policies::{
35        load_balancing::{DefaultPolicy, LoadBalancingPolicy},
36        retry::DefaultRetryPolicy,
37    },
38    response::PagingState,
39    statement::{batch::BatchType, prepared::PreparedStatement, Consistency},
40    value::CqlValue,
41};
42use serde::{Deserialize, Serialize};
43use static_assertions as sa;
44use thiserror::Error;
45
46#[cfg(with_metrics)]
47use crate::metering::MeteredDatabase;
48#[cfg(with_testing)]
49use crate::store::TestKeyValueDatabase;
50use crate::{
51    batch::{SimpleUnorderedBatch, UnorderedBatch},
52    common::{get_uleb128_size, get_upper_bound_option},
53    journaling::{JournalingError, JournalingKeyValueDatabase},
54    lru_caching::{LruCachingConfig, LruCachingDatabase},
55    store::{
56        DirectWritableKeyValueStore, KeyValueDatabase, KeyValueStoreError, ReadableKeyValueStore,
57        WithError,
58    },
59};
60
61/// Fundamental constant in ScyllaDB: The maximum size of a multi keys query
62/// The limit is in reality 100. But we need one entry for the root key.
63const MAX_MULTI_KEYS: usize = 100 - 1;
64
65/// The maximal size of an operation on ScyllaDB seems to be 16 MiB
66/// https://www.scylladb.com/2019/03/27/best-practices-for-scylla-applications/
67/// "There is a hard limit at 16 MiB, and nothing bigger than that can arrive at once
68///  at the database at any particular time"
69const MAX_OPERATION_SIZE: usize = 16 * 1024 * 1024;
70
71/// A batch is issued as one unlogged batch whose statements all share the partition key,
72/// so ScyllaDB merges them into a single mutation weighed against `MAX_OPERATION_SIZE`.
73const BATCH_STATEMENT_OVERHEAD: usize = 256;
74
75/// So, we set up the maximal size of 16 MiB minus 10 KiB for the keys and minus the
76/// per-statement reserve for the values.
77const RAW_MAX_VALUE_SIZE: usize =
78    MAX_OPERATION_SIZE - MAX_KEY_SIZE - MAX_BATCH_SIZE * BATCH_STATEMENT_OVERHEAD;
79const MAX_KEY_SIZE: usize = 10 * 1024;
80const MAX_BATCH_TOTAL_SIZE: usize = RAW_MAX_VALUE_SIZE + MAX_KEY_SIZE;
81
82// A full batch and its per-statement reserve must fit in a single ScyllaDB operation.
83sa::const_assert!(
84    MAX_BATCH_TOTAL_SIZE + MAX_BATCH_SIZE * BATCH_STATEMENT_OVERHEAD <= MAX_OPERATION_SIZE
85);
86
87/// The `RAW_MAX_VALUE_SIZE` is the maximum size on the ScyllaDB storage.
88/// However, the value being written can also be the serialization of a `SimpleUnorderedBatch`
89/// Therefore the actual `MAX_VALUE_SIZE` is lower.
90/// At the maximum the key size is `MAX_KEY_SIZE` bytes and we pack just one entry.
91/// So if the key has 10240 bytes this gets us the inequality
92/// `1 + 1 + 1 + serialized_size(MAX_KEY_SIZE)? + serialized_size(x)? <= RAW_MAX_VALUE_SIZE`.
93/// and so this simplifies to `1 + 1 + 1 + (2 + 10240) + (4 + x) <= RAW_MAX_VALUE_SIZE`
94/// Note on the above formula:
95/// * We write 4 because `get_uleb128_size(RAW_MAX_VALUE_SIZE) = 4)`
96/// * We write `1 + 1 + 1`  because the `UnorderedBatch` has three entries.
97///
98/// This gets us to a maximal value of 15476727.
99const MAX_VALUE_SIZE: usize = RAW_MAX_VALUE_SIZE
100    - MAX_KEY_SIZE
101    - get_uleb128_size(RAW_MAX_VALUE_SIZE)
102    - get_uleb128_size(MAX_KEY_SIZE)
103    - 3;
104
105/// The maximal number of statements in a single batch. ScyllaDB rejects batches of more
106/// than 14796 statements; this stays well below that.
107const MAX_BATCH_SIZE: usize = 5000;
108
109/// The keyspace to use for the ScyllaDB database.
110const KEYSPACE: &str = "kv";
111
112/// The client for ScyllaDB:
113/// * The session allows to pass queries
114/// * The namespace that is being assigned to the database
115/// * The prepared queries used for implementing the features of `KeyValueStore`.
116struct ScyllaDbClient {
117    session: Session,
118    namespace: String,
119    read_value: PreparedStatement,
120    read_writetime: PreparedStatement,
121    contains_key: PreparedStatement,
122    write_batch_delete_prefix_unbounded: PreparedStatement,
123    write_batch_delete_prefix_bounded: PreparedStatement,
124    write_batch_deletion: PreparedStatement,
125    write_batch_insertion: PreparedStatement,
126    // Variants carrying an explicit `USING TIMESTAMP ?` marker, used by the
127    // single-batch exclusive-mode write path (`write_batch_exclusive`).
128    write_batch_delete_prefix_unbounded_ts: PreparedStatement,
129    write_batch_delete_prefix_bounded_ts: PreparedStatement,
130    write_batch_deletion_ts: PreparedStatement,
131    write_batch_insertion_ts: PreparedStatement,
132    find_keys_by_prefix_unbounded: PreparedStatement,
133    find_keys_by_prefix_bounded: PreparedStatement,
134    find_key_values_by_prefix_unbounded: PreparedStatement,
135    find_key_values_by_prefix_bounded: PreparedStatement,
136    multi_key_values: papaya::HashMap<usize, PreparedStatement>,
137    multi_keys: papaya::HashMap<usize, PreparedStatement>,
138}
139
140impl ScyllaDbClient {
141    async fn new(session: Session, namespace: &str) -> Result<Self, ScyllaDbStoreInternalError> {
142        let namespace = namespace.to_string();
143        let read_value = session
144            .prepare(format!(
145                "SELECT v FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k = ?"
146            ))
147            .await?;
148
149        let read_writetime = session
150            .prepare(format!(
151                "SELECT WRITETIME(v) FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k = ?"
152            ))
153            .await?;
154
155        let contains_key = session
156            .prepare(format!(
157                "SELECT root_key FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k = ?"
158            ))
159            .await?;
160
161        let write_batch_delete_prefix_unbounded = session
162            .prepare(format!(
163                "DELETE FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k >= ?"
164            ))
165            .await?;
166
167        let write_batch_delete_prefix_bounded = session
168            .prepare(format!(
169                "DELETE FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k >= ? AND k < ?"
170            ))
171            .await?;
172
173        let write_batch_deletion = session
174            .prepare(format!(
175                "DELETE FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k = ?"
176            ))
177            .await?;
178
179        let write_batch_insertion = session
180            .prepare(format!(
181                "INSERT INTO {KEYSPACE}.\"{namespace}\" (root_key, k, v) VALUES (?, ?, ?)"
182            ))
183            .await?;
184
185        // Timestamped variants used by the single-batch exclusive-mode path. The
186        // explicit `USING TIMESTAMP ?` lets prefix-deletions (`T`) and the
187        // insertions/deletions (`T + 1`) share one atomic batch without the range
188        // tombstone shadowing the inserts.
189        let write_batch_delete_prefix_unbounded_ts = session
190            .prepare(format!(
191                "DELETE FROM {KEYSPACE}.\"{namespace}\" USING TIMESTAMP ? WHERE root_key = ? AND k >= ?"
192            ))
193            .await?;
194
195        let write_batch_delete_prefix_bounded_ts = session
196            .prepare(format!(
197                "DELETE FROM {KEYSPACE}.\"{namespace}\" USING TIMESTAMP ? \
198                 WHERE root_key = ? AND k >= ? AND k < ?"
199            ))
200            .await?;
201
202        let write_batch_deletion_ts = session
203            .prepare(format!(
204                "DELETE FROM {KEYSPACE}.\"{namespace}\" USING TIMESTAMP ? WHERE root_key = ? AND k = ?"
205            ))
206            .await?;
207
208        let write_batch_insertion_ts = session
209            .prepare(format!(
210                "INSERT INTO {KEYSPACE}.\"{namespace}\" (root_key, k, v) VALUES (?, ?, ?) \
211                 USING TIMESTAMP ?"
212            ))
213            .await?;
214
215        let find_keys_by_prefix_unbounded = session
216            .prepare(format!(
217                "SELECT k FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k >= ?"
218            ))
219            .await?;
220
221        let find_keys_by_prefix_bounded = session
222            .prepare(format!(
223                "SELECT k FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k >= ? AND k < ?"
224            ))
225            .await?;
226
227        let find_key_values_by_prefix_unbounded = session
228            .prepare(format!(
229                "SELECT k,v FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k >= ?"
230            ))
231            .await?;
232
233        let find_key_values_by_prefix_bounded = session
234            .prepare(format!(
235                "SELECT k,v FROM {KEYSPACE}.\"{namespace}\" WHERE root_key = ? AND k >= ? AND k < ?"
236            ))
237            .await?;
238
239        Ok(Self {
240            session,
241            namespace,
242            read_value,
243            read_writetime,
244            contains_key,
245            write_batch_delete_prefix_unbounded,
246            write_batch_delete_prefix_bounded,
247            write_batch_deletion,
248            write_batch_insertion,
249            write_batch_delete_prefix_unbounded_ts,
250            write_batch_delete_prefix_bounded_ts,
251            write_batch_deletion_ts,
252            write_batch_insertion_ts,
253            find_keys_by_prefix_unbounded,
254            find_keys_by_prefix_bounded,
255            find_key_values_by_prefix_unbounded,
256            find_key_values_by_prefix_bounded,
257            multi_key_values: papaya::HashMap::new(),
258            multi_keys: papaya::HashMap::new(),
259        })
260    }
261
262    fn build_default_policy() -> Arc<dyn LoadBalancingPolicy> {
263        DefaultPolicy::builder().token_aware(true).build()
264    }
265
266    fn build_default_execution_profile_handle(
267        policy: Arc<dyn LoadBalancingPolicy>,
268    ) -> ExecutionProfileHandle {
269        let default_profile = ExecutionProfile::builder()
270            .load_balancing_policy(policy)
271            .retry_policy(Arc::new(DefaultRetryPolicy::new()))
272            .consistency(Consistency::LocalQuorum)
273            .build();
274        default_profile.into_handle()
275    }
276
277    async fn build_default_session(uri: &str) -> Result<Session, ScyllaDbStoreInternalError> {
278        // This explicitly sets a lot of default parameters for clarity and for making future changes
279        // easier.
280        SessionBuilder::new()
281            .known_node(uri)
282            .default_execution_profile_handle(Self::build_default_execution_profile_handle(
283                Self::build_default_policy(),
284            ))
285            .build()
286            .boxed_sync()
287            .await
288            .map_err(Into::into)
289    }
290
291    async fn get_multi_key_values_statement(
292        &self,
293        num_markers: usize,
294    ) -> Result<PreparedStatement, ScyllaDbStoreInternalError> {
295        if let Some(prepared_statement) = self.multi_key_values.pin().get(&num_markers) {
296            return Ok(prepared_statement.clone());
297        }
298        let markers = std::iter::repeat_n("?", num_markers)
299            .collect::<Vec<_>>()
300            .join(",");
301        let prepared_statement = self
302            .session
303            .prepare(format!(
304                "SELECT k,v FROM {}.\"{}\" WHERE root_key = ? AND k IN ({})",
305                KEYSPACE, self.namespace, markers
306            ))
307            .await?;
308        self.multi_key_values
309            .pin()
310            .insert(num_markers, prepared_statement.clone());
311        Ok(prepared_statement)
312    }
313
314    async fn get_multi_keys_statement(
315        &self,
316        num_markers: usize,
317    ) -> Result<PreparedStatement, ScyllaDbStoreInternalError> {
318        if let Some(prepared_statement) = self.multi_keys.pin().get(&num_markers) {
319            return Ok(prepared_statement.clone());
320        };
321        let markers = std::iter::repeat_n("?", num_markers)
322            .collect::<Vec<_>>()
323            .join(",");
324        let prepared_statement = self
325            .session
326            .prepare(format!(
327                "SELECT k FROM {}.\"{}\" WHERE root_key = ? AND k IN ({})",
328                KEYSPACE, self.namespace, markers
329            ))
330            .await?;
331        self.multi_keys
332            .pin()
333            .insert(num_markers, prepared_statement.clone());
334        Ok(prepared_statement)
335    }
336
337    fn check_key_size(key: &[u8]) -> Result<(), ScyllaDbStoreInternalError> {
338        ensure!(
339            key.len() <= MAX_KEY_SIZE,
340            ScyllaDbStoreInternalError::KeyTooLong
341        );
342        Ok(())
343    }
344
345    fn check_value_size(value: &[u8]) -> Result<(), ScyllaDbStoreInternalError> {
346        ensure!(
347            value.len() <= RAW_MAX_VALUE_SIZE,
348            ScyllaDbStoreInternalError::ValueTooLong
349        );
350        Ok(())
351    }
352
353    /// Validates a key supplied by a caller's batch. Besides the size limit, the
354    /// key must be non-empty: the empty (zero-length) key is `WRITETIME_SENTINEL_KEY`,
355    /// reserved for the per-store timestamp sentinel that exclusive mode writes
356    /// internally. Prefix scans now deliberately hide that key, so any caller
357    /// content stored there would be silently invisible to reads.
358    fn check_batch_key(key: &[u8]) -> Result<(), ScyllaDbStoreInternalError> {
359        Self::check_key_size(key)?;
360        ensure!(!key.is_empty(), ScyllaDbStoreInternalError::ZeroLengthKey);
361        Ok(())
362    }
363
364    fn check_batch_len(batch: &UnorderedBatch) -> Result<(), ScyllaDbStoreInternalError> {
365        ensure!(
366            batch.len() <= MAX_BATCH_SIZE,
367            ScyllaDbStoreInternalError::BatchTooLong
368        );
369        Ok(())
370    }
371
372    async fn read_value_internal(
373        &self,
374        root_key: &[u8],
375        key: Vec<u8>,
376    ) -> Result<Option<Vec<u8>>, ScyllaDbStoreInternalError> {
377        Self::check_key_size(&key)?;
378        let session = &self.session;
379        // Read the value of a key
380        let values = (root_key.to_vec(), key);
381
382        let (result, _) = session
383            .execute_single_page(&self.read_value, &values, PagingState::start())
384            .await
385            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
386        let rows = result.into_rows_result()?;
387        let mut rows = rows.rows::<(Vec<u8>,)>()?;
388        Ok(match rows.next() {
389            Some(row) => Some(row?.0),
390            None => None,
391        })
392    }
393
394    fn get_occurrences_map(
395        keys: &[Vec<u8>],
396    ) -> Result<HashMap<&[u8], Vec<usize>>, ScyllaDbStoreInternalError> {
397        let mut map = HashMap::<&[u8], Vec<usize>>::new();
398        for (i_key, key) in keys.iter().enumerate() {
399            Self::check_key_size(key)?;
400            map.entry(key).or_default().push(i_key);
401        }
402        Ok(map)
403    }
404
405    async fn read_multi_values_internal(
406        &self,
407        root_key: &[u8],
408        keys: &[Vec<u8>],
409    ) -> Result<Vec<Option<Vec<u8>>>, ScyllaDbStoreInternalError> {
410        let mut values = vec![None; keys.len()];
411        let map = Self::get_occurrences_map(keys)?;
412        let statement = self.get_multi_key_values_statement(map.len()).await?;
413        let mut inputs = vec![root_key];
414        inputs.extend(map.keys().copied());
415        let mut rows = Box::pin(self.session.execute_iter(statement, &inputs))
416            .await?
417            .rows_stream::<(Vec<u8>, Vec<u8>)>()?;
418
419        while let Some(row) = rows.next().await {
420            let (key, value) = row?;
421            if let Some((&last, rest)) = map[key.as_slice()].split_last() {
422                for position in rest {
423                    values[*position] = Some(value.clone());
424                }
425                values[last] = Some(value);
426            }
427        }
428        Ok(values)
429    }
430
431    async fn contains_keys_internal(
432        &self,
433        root_key: &[u8],
434        keys: &[Vec<u8>],
435    ) -> Result<Vec<bool>, ScyllaDbStoreInternalError> {
436        let mut values = vec![false; keys.len()];
437        let map = Self::get_occurrences_map(keys)?;
438        let statement = self.get_multi_keys_statement(map.len()).await?;
439        let mut inputs = vec![root_key];
440        inputs.extend(map.keys().copied());
441        let mut rows = Box::pin(self.session.execute_iter(statement, &inputs))
442            .await?
443            .rows_stream::<(Vec<u8>,)>()?;
444
445        while let Some(row) = rows.next().await {
446            let (key,) = row?;
447            for i_key in &map[key.as_slice()] {
448                values[*i_key] = true;
449            }
450        }
451
452        Ok(values)
453    }
454
455    async fn contains_key_internal(
456        &self,
457        root_key: &[u8],
458        key: Vec<u8>,
459    ) -> Result<bool, ScyllaDbStoreInternalError> {
460        Self::check_key_size(&key)?;
461        let session = &self.session;
462        // Read the value of a key
463        let values = (root_key.to_vec(), key);
464
465        let (result, _) = session
466            .execute_single_page(&self.contains_key, &values, PagingState::start())
467            .await
468            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
469        let rows = result.into_rows_result()?;
470        let mut rows = rows.rows::<(Vec<u8>,)>()?;
471        Ok(rows.next().is_some())
472    }
473
474    /// Reads the write-time of a single row in microseconds since Unix epoch,
475    /// returning `None` if the row does not exist or carries no live value.
476    async fn read_writetime_internal(
477        &self,
478        root_key: &[u8],
479        key: Vec<u8>,
480    ) -> Result<Option<i64>, ScyllaDbStoreInternalError> {
481        Self::check_key_size(&key)?;
482        let session = &self.session;
483        let values = (root_key.to_vec(), key);
484        let (result, _) = session
485            .execute_single_page(&self.read_writetime, &values, PagingState::start())
486            .await
487            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
488        let rows = result.into_rows_result()?;
489        let mut rows = rows.rows::<(Option<i64>,)>()?;
490        Ok(match rows.next() {
491            Some(row) => row?.0,
492            None => None,
493        })
494    }
495
496    /// Issues an unlogged batch that contains only prefix-delete statements,
497    /// letting the coordinator assign the write timestamp. Shared mode only.
498    async fn write_batch_prefix_deletes_shared(
499        &self,
500        root_key: &[u8],
501        key_prefix_deletions: Vec<Vec<u8>>,
502    ) -> Result<(), ScyllaDbStoreInternalError> {
503        if key_prefix_deletions.is_empty() {
504            return Ok(());
505        }
506        let session = &self.session;
507        let mut batch_query = scylla::statement::batch::Batch::new(BatchType::Unlogged);
508        let mut batch_values = Vec::new();
509        let q_unbounded = &self.write_batch_delete_prefix_unbounded;
510        let q_bounded = &self.write_batch_delete_prefix_bounded;
511        for key_prefix in key_prefix_deletions {
512            Self::check_key_size(&key_prefix)?;
513            match get_upper_bound_option(&key_prefix) {
514                None => {
515                    batch_values.push(vec![root_key.to_vec(), key_prefix]);
516                    batch_query.append_statement(q_unbounded.clone());
517                }
518                Some(upper_bound) => {
519                    batch_values.push(vec![root_key.to_vec(), key_prefix, upper_bound]);
520                    batch_query.append_statement(q_bounded.clone());
521                }
522            }
523        }
524        session
525            .batch(&batch_query, batch_values)
526            .await
527            .map_err(ScyllaDbStoreInternalError::SharedWriteBatchExecutionError)?;
528        Ok(())
529    }
530
531    /// Issues an unlogged batch containing the single-key deletions and the
532    /// insertions, letting the coordinator assign the write timestamp. Shared mode only.
533    async fn write_simple_batch_shared(
534        &self,
535        root_key: &[u8],
536        batch: SimpleUnorderedBatch,
537    ) -> Result<(), ScyllaDbStoreInternalError> {
538        if batch.deletions.is_empty() && batch.insertions.is_empty() {
539            return Ok(());
540        }
541        let session = &self.session;
542        let mut batch_query = scylla::statement::batch::Batch::new(BatchType::Unlogged);
543        let mut batch_values = Vec::new();
544        let q_deletion = &self.write_batch_deletion;
545        for key in batch.deletions {
546            Self::check_batch_key(&key)?;
547            batch_values.push(vec![root_key.to_vec(), key]);
548            batch_query.append_statement(q_deletion.clone());
549        }
550        let q_insertion = &self.write_batch_insertion;
551        for (key, value) in batch.insertions {
552            Self::check_batch_key(&key)?;
553            Self::check_value_size(&value)?;
554            batch_values.push(vec![root_key.to_vec(), key, value]);
555            batch_query.append_statement(q_insertion.clone());
556        }
557        session
558            .batch(&batch_query, batch_values)
559            .await
560            .map_err(ScyllaDbStoreInternalError::SharedWriteBatchExecutionError)?;
561        Ok(())
562    }
563
564    /// Issues the whole write as a single atomic unlogged batch, used in
565    /// exclusive mode. Every statement carries an explicit `USING TIMESTAMP`:
566    /// the prefix-deletions use `t`, while the single-key deletions, the
567    /// insertions, and the sentinel write use `t + 1`. The higher timestamp on
568    /// the data ensures a range tombstone never shadows an insertion belonging
569    /// to the same logical batch (at equal timestamps, dead cells win over live
570    /// cells). Because the intended ordering is fixed by these timestamps rather
571    /// than by send order, the prefix-deletions and the data can — and must —
572    /// share one batch, preserving the atomicity that `write_batch` callers rely
573    /// on. The sentinel write at `WRITETIME_SENTINEL_KEY` lets a future process
574    /// recover this store's timestamp floor (see `ensure_ts_seeded`).
575    async fn write_batch_exclusive(
576        &self,
577        root_key: &[u8],
578        batch: UnorderedBatch,
579        t: i64,
580    ) -> Result<(), ScyllaDbStoreInternalError> {
581        let UnorderedBatch {
582            key_prefix_deletions,
583            simple_unordered_batch:
584                SimpleUnorderedBatch {
585                    deletions,
586                    insertions,
587                },
588        } = batch;
589        let session = &self.session;
590        let mut batch_query = scylla::statement::batch::Batch::new(BatchType::Unlogged);
591        let mut batch_values = Vec::new();
592
593        // Prefix-deletions at timestamp `t`.
594        for key_prefix in key_prefix_deletions {
595            Self::check_key_size(&key_prefix)?;
596            match get_upper_bound_option(&key_prefix) {
597                None => {
598                    batch_values.push(vec![
599                        CqlValue::BigInt(t),
600                        CqlValue::Blob(root_key.to_vec()),
601                        CqlValue::Blob(key_prefix),
602                    ]);
603                    batch_query
604                        .append_statement(self.write_batch_delete_prefix_unbounded_ts.clone());
605                }
606                Some(upper_bound) => {
607                    batch_values.push(vec![
608                        CqlValue::BigInt(t),
609                        CqlValue::Blob(root_key.to_vec()),
610                        CqlValue::Blob(key_prefix),
611                        CqlValue::Blob(upper_bound),
612                    ]);
613                    batch_query.append_statement(self.write_batch_delete_prefix_bounded_ts.clone());
614                }
615            }
616        }
617
618        // Single-key deletions, insertions, and the sentinel at timestamp `t + 1`.
619        let t_data = t + 1;
620        for key in deletions {
621            Self::check_batch_key(&key)?;
622            batch_values.push(vec![
623                CqlValue::BigInt(t_data),
624                CqlValue::Blob(root_key.to_vec()),
625                CqlValue::Blob(key),
626            ]);
627            batch_query.append_statement(self.write_batch_deletion_ts.clone());
628        }
629        for (key, value) in insertions {
630            Self::check_batch_key(&key)?;
631            Self::check_value_size(&value)?;
632            batch_values.push(vec![
633                CqlValue::Blob(root_key.to_vec()),
634                CqlValue::Blob(key),
635                CqlValue::Blob(value),
636                CqlValue::BigInt(t_data),
637            ]);
638            batch_query.append_statement(self.write_batch_insertion_ts.clone());
639        }
640        batch_values.push(vec![
641            CqlValue::Blob(root_key.to_vec()),
642            CqlValue::Blob(WRITETIME_SENTINEL_KEY.to_vec()),
643            CqlValue::Blob(Vec::new()),
644            CqlValue::BigInt(t_data),
645        ]);
646        batch_query.append_statement(self.write_batch_insertion_ts.clone());
647
648        session
649            .batch(&batch_query, batch_values)
650            .await
651            .map_err(ScyllaDbStoreInternalError::ExclusiveWriteBatchExecutionError)?;
652        Ok(())
653    }
654
655    async fn find_keys_by_prefix_internal(
656        &self,
657        root_key: &[u8],
658        key_prefix: Vec<u8>,
659    ) -> Result<Vec<Vec<u8>>, ScyllaDbStoreInternalError> {
660        Self::check_key_size(&key_prefix)?;
661        let session = &self.session;
662        // Read the value of a key
663        let len = key_prefix.len();
664        let query_unbounded = &self.find_keys_by_prefix_unbounded;
665        let query_bounded = &self.find_keys_by_prefix_bounded;
666        let rows = match get_upper_bound_option(&key_prefix) {
667            None => {
668                let values = (root_key.to_vec(), key_prefix.clone());
669                Box::pin(session.execute_iter(query_unbounded.clone(), values)).await?
670            }
671            Some(upper_bound) => {
672                let values = (root_key.to_vec(), key_prefix.clone(), upper_bound);
673                Box::pin(session.execute_iter(query_bounded.clone(), values)).await?
674            }
675        };
676        let mut rows = rows.rows_stream::<(Vec<u8>,)>()?;
677        let mut keys = Vec::new();
678        while let Some(row) = rows.next().await {
679            let (key,) = row?;
680            // Skip the reserved timestamp sentinel (exclusive mode writes it at the
681            // empty clustering key). It is an internal implementation detail and must
682            // not surface to callers; it can only match an empty-prefix scan.
683            if key == WRITETIME_SENTINEL_KEY {
684                continue;
685            }
686            let short_key = key[len..].to_vec();
687            keys.push(short_key);
688        }
689        Ok(keys)
690    }
691
692    async fn find_key_values_by_prefix_internal(
693        &self,
694        root_key: &[u8],
695        key_prefix: Vec<u8>,
696    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ScyllaDbStoreInternalError> {
697        Self::check_key_size(&key_prefix)?;
698        let session = &self.session;
699        // Read the value of a key
700        let len = key_prefix.len();
701        let query_unbounded = &self.find_key_values_by_prefix_unbounded;
702        let query_bounded = &self.find_key_values_by_prefix_bounded;
703        let rows = match get_upper_bound_option(&key_prefix) {
704            None => {
705                let values = (root_key.to_vec(), key_prefix.clone());
706                Box::pin(session.execute_iter(query_unbounded.clone(), values)).await?
707            }
708            Some(upper_bound) => {
709                let values = (root_key.to_vec(), key_prefix.clone(), upper_bound);
710                Box::pin(session.execute_iter(query_bounded.clone(), values)).await?
711            }
712        };
713        let mut rows = rows.rows_stream::<(Vec<u8>, Vec<u8>)>()?;
714        let mut key_values = Vec::new();
715        while let Some(row) = rows.next().await {
716            let (key, value) = row?;
717            // Skip the reserved timestamp sentinel; see `find_keys_by_prefix_internal`.
718            if key == WRITETIME_SENTINEL_KEY {
719                continue;
720            }
721            let short_key = key[len..].to_vec();
722            key_values.push((short_key, value));
723        }
724        Ok(key_values)
725    }
726}
727
728/// The client itself and the keeping of the count of active connections.
729#[derive(Clone)]
730pub struct ScyllaDbStoreInternal {
731    store: Arc<ScyllaDbClient>,
732    semaphore: Option<Arc<Semaphore>>,
733    root_key: Vec<u8>,
734    /// Whether this store was opened with `open_exclusive`. When true, `write_batch`
735    /// resolves in-batch prefix/insert collisions via per-statement `USING TIMESTAMP`;
736    /// when false, it splits the batch into two sequential sub-batches with
737    /// server-side timestamps to preserve ordering across writers.
738    is_exclusive: bool,
739    /// Per-partition timestamp floor for exclusive-mode `USING TIMESTAMP` writes.
740    /// Value 0 means unseeded; populated lazily on first write by reading
741    /// `WRITETIME` of a sentinel row. Each batch reserves 2 µs (T and T+1).
742    ts_floor: Arc<AtomicI64>,
743}
744
745/// Database-level connection to ScyllaDB for managing namespaces and partitions.
746#[derive(Clone)]
747pub struct ScyllaDbDatabaseInternal {
748    store: Arc<ScyllaDbClient>,
749    semaphore: Option<Arc<Semaphore>>,
750}
751
752impl WithError for ScyllaDbDatabaseInternal {
753    type Error = ScyllaDbStoreInternalError;
754}
755
756/// The error type for [`ScyllaDbStoreInternal`]
757#[derive(Error, Debug)]
758pub enum ScyllaDbStoreInternalError {
759    /// BCS serialization error.
760    #[error(transparent)]
761    BcsError(#[from] bcs::Error),
762
763    /// A deserialization error
764    #[error(transparent)]
765    DeserializationError(#[from] DeserializationError),
766
767    /// A row error
768    #[error(transparent)]
769    RowsError(#[from] RowsError),
770
771    /// A conversion error in the accessed data
772    #[error(transparent)]
773    IntoRowsResultError(#[from] IntoRowsResultError),
774
775    /// A type check error
776    #[error(transparent)]
777    TypeCheckError(#[from] TypeCheckError),
778
779    /// A pager execution error
780    #[error(transparent)]
781    PagerExecutionError(#[from] PagerExecutionError),
782
783    /// A prepare error
784    #[error(transparent)]
785    PrepareError(#[from] PrepareError),
786
787    /// An execution error during a query (except write-batch).
788    #[error("query execution error: {0}")]
789    ExecutionError(#[source] ExecutionError),
790
791    /// An execution error during a write-batch operation on a store opened in
792    /// exclusive mode, which backs a view.
793    #[error("write batch execution error (exclusive mode): {0}")]
794    ExclusiveWriteBatchExecutionError(#[source] ExecutionError),
795
796    /// An execution error during a write-batch operation on a store opened in
797    /// shared mode, which backs no view.
798    #[error("write batch execution error (shared mode): {0}")]
799    SharedWriteBatchExecutionError(#[source] ExecutionError),
800
801    /// A session creation error
802    #[error(transparent)]
803    NewSessionError(#[from] NewSessionError),
804
805    /// A next row error in ScyllaDB
806    #[error(transparent)]
807    NextRowError(#[from] NextRowError),
808
809    /// Namespace contains forbidden characters
810    #[error("Namespace contains forbidden characters")]
811    InvalidNamespace,
812
813    /// The key must have at most `MAX_KEY_SIZE` bytes
814    #[error("The key must have at most MAX_KEY_SIZE")]
815    KeyTooLong,
816
817    /// The value must have at most `RAW_MAX_VALUE_SIZE` bytes
818    #[error("The value must have at most RAW_MAX_VALUE_SIZE")]
819    ValueTooLong,
820
821    /// The batch is too long to be written
822    #[error("The batch is too long to be written")]
823    BatchTooLong,
824
825    /// Keys have to be of nonzero length (the empty key is reserved for the
826    /// timestamp sentinel).
827    #[error("The key must be of nonzero length")]
828    ZeroLengthKey,
829}
830
831impl KeyValueStoreError for ScyllaDbStoreInternalError {
832    const BACKEND: &'static str = "scylla_db";
833
834    fn must_reload_view(&self) -> bool {
835        // Errors (notably timeouts) during a `write_batch` leave it undetermined whether
836        // the batch was applied. That only invalidates in-memory state when the store
837        // backs a view, which is the case in exclusive mode; a shared store backs none,
838        // so the same ambiguity is reported as an ordinary, retryable error.
839        matches!(self, Self::ExclusiveWriteBatchExecutionError(_))
840    }
841}
842
843impl WithError for ScyllaDbStoreInternal {
844    type Error = ScyllaDbStoreInternalError;
845}
846
847impl ReadableKeyValueStore for ScyllaDbStoreInternal {
848    const MAX_KEY_SIZE: usize = MAX_KEY_SIZE;
849
850    fn root_key(&self) -> Result<Vec<u8>, ScyllaDbStoreInternalError> {
851        Ok(self.root_key[1..].to_vec())
852    }
853
854    async fn read_value_bytes(
855        &self,
856        key: &[u8],
857    ) -> Result<Option<Vec<u8>>, ScyllaDbStoreInternalError> {
858        let store = self.store.deref();
859        let _guard = self.acquire().await;
860        Box::pin(store.read_value_internal(&self.root_key, key.to_vec())).await
861    }
862
863    async fn contains_key(&self, key: &[u8]) -> Result<bool, ScyllaDbStoreInternalError> {
864        let store = self.store.deref();
865        let _guard = self.acquire().await;
866        Box::pin(store.contains_key_internal(&self.root_key, key.to_vec())).await
867    }
868
869    async fn contains_keys(
870        &self,
871        keys: &[Vec<u8>],
872    ) -> Result<Vec<bool>, ScyllaDbStoreInternalError> {
873        if keys.is_empty() {
874            return Ok(Vec::new());
875        }
876        let store = self.store.deref();
877        let _guard = self.acquire().await;
878        let handles = keys
879            .chunks(MAX_MULTI_KEYS)
880            .map(|keys| store.contains_keys_internal(&self.root_key, keys));
881        let results: Vec<_> = join_all(handles)
882            .await
883            .into_iter()
884            .collect::<Result<_, _>>()?;
885        Ok(results.into_iter().flatten().collect())
886    }
887
888    async fn read_multi_values_bytes(
889        &self,
890        keys: &[Vec<u8>],
891    ) -> Result<Vec<Option<Vec<u8>>>, ScyllaDbStoreInternalError> {
892        if keys.is_empty() {
893            return Ok(Vec::new());
894        }
895        let store = self.store.deref();
896        let _guard = self.acquire().await;
897        let handles = keys
898            .chunks(MAX_MULTI_KEYS)
899            .map(|keys| store.read_multi_values_internal(&self.root_key, keys));
900        let results: Vec<_> = join_all(handles)
901            .await
902            .into_iter()
903            .collect::<Result<_, _>>()?;
904        Ok(results.into_iter().flatten().collect())
905    }
906
907    async fn find_keys_by_prefix(
908        &self,
909        key_prefix: &[u8],
910    ) -> Result<Vec<Vec<u8>>, ScyllaDbStoreInternalError> {
911        let store = self.store.deref();
912        let _guard = self.acquire().await;
913        Box::pin(store.find_keys_by_prefix_internal(&self.root_key, key_prefix.to_vec())).await
914    }
915
916    async fn find_key_values_by_prefix(
917        &self,
918        key_prefix: &[u8],
919    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, ScyllaDbStoreInternalError> {
920        let store = self.store.deref();
921        let _guard = self.acquire().await;
922        Box::pin(store.find_key_values_by_prefix_internal(&self.root_key, key_prefix.to_vec()))
923            .await
924    }
925}
926
927impl DirectWritableKeyValueStore for ScyllaDbStoreInternal {
928    const MAX_BATCH_SIZE: usize = MAX_BATCH_SIZE;
929    const MAX_BATCH_TOTAL_SIZE: usize = MAX_BATCH_TOTAL_SIZE;
930    const MAX_VALUE_SIZE: usize = MAX_VALUE_SIZE;
931
932    // ScyllaDB cannot take a `crate::batch::Batch` directly. Indeed, if a delete is
933    // followed by a write, then the delete takes priority. See the sentence "The first
934    // tie-breaking rule when two cells have the same write timestamp is that dead cells
935    // win over live cells" from
936    // https://github.com/scylladb/scylladb/blob/master/docs/dev/timestamp-conflict-resolution.md
937    //
938    // We therefore order the prefix-deletions strictly before the insertions:
939    //   * In exclusive mode we own the timestamps, so we issue a single atomic CQL
940    //     batch with explicit per-statement `USING TIMESTAMP` (`T` for the
941    //     prefix-deletions, `T + 1` for the data). See `write_batch_exclusive`.
942    //   * In shared mode the coordinator owns the timestamps, so we split the write
943    //     into two sequential CQL batches.
944    type Batch = UnorderedBatch;
945
946    async fn write_batch(&self, batch: Self::Batch) -> Result<(), ScyllaDbStoreInternalError> {
947        let store = self.store.deref();
948        let _guard = self.acquire().await;
949        ScyllaDbClient::check_batch_len(&batch)?;
950        if self.is_exclusive {
951            // A single atomic batch; ordering is pinned by the explicit timestamps.
952            let t = self.next_batch_ts().await?;
953            store.write_batch_exclusive(&self.root_key, batch, t).await
954        } else {
955            store
956                .write_batch_prefix_deletes_shared(&self.root_key, batch.key_prefix_deletions)
957                .await?;
958            store
959                .write_simple_batch_shared(&self.root_key, batch.simple_unordered_batch)
960                .await?;
961            Ok(())
962        }
963    }
964}
965
966impl ScyllaDbStoreInternal {
967    /// Seeds the per-store timestamp floor on first write in exclusive mode.
968    /// Reads `WRITETIME` of this chain's row in the reserved sentinel
969    /// partition (written by every prior exclusive batch). Falls back to the
970    /// current wall clock if the row does not yet exist. Idempotent — only
971    /// the first caller wins the compare-exchange.
972    async fn ensure_ts_seeded(&self) -> Result<(), ScyllaDbStoreInternalError> {
973        if self.ts_floor.load(Ordering::Relaxed) > 0 {
974            return Ok(());
975        }
976        let writetime = self
977            .store
978            .read_writetime_internal(&self.root_key, WRITETIME_SENTINEL_KEY.to_vec())
979            .await?
980            .unwrap_or(0);
981        let now_us = SystemTime::now()
982            .duration_since(UNIX_EPOCH)
983            .ok()
984            .and_then(|d| i64::try_from(d.as_micros()).ok())
985            .unwrap_or(0);
986        // `writetime` is the last batch's `T + 1`, i.e. the highest timestamp it
987        // consumed; that is exactly what `ts_floor` tracks, so seed it directly.
988        let seed = now_us.max(writetime);
989        if self
990            .ts_floor
991            .compare_exchange(0, seed, Ordering::Relaxed, Ordering::Relaxed)
992            .is_err()
993        {
994            // Another caller seeded first; their value wins.
995        }
996        Ok(())
997    }
998
999    /// Returns the base timestamp `T` for the next batch in exclusive mode.
1000    /// The batch may also use `T + 1`; the generator advances by 2 per call,
1001    /// preserving monotonicity across batches in this process.
1002    async fn next_batch_ts(&self) -> Result<i64, ScyllaDbStoreInternalError> {
1003        self.ensure_ts_seeded().await?;
1004        loop {
1005            let prev = self.ts_floor.load(Ordering::Relaxed);
1006            let now_us = SystemTime::now()
1007                .duration_since(UNIX_EPOCH)
1008                .ok()
1009                .and_then(|d| i64::try_from(d.as_micros()).ok())
1010                .unwrap_or(prev);
1011            let next = std::cmp::max(now_us, prev + 1);
1012            // The batch uses `next` (`T`) and `next + 1` (`T + 1`); store the latter
1013            // so the following batch starts strictly above both.
1014            if self
1015                .ts_floor
1016                .compare_exchange_weak(prev, next + 1, Ordering::Relaxed, Ordering::Relaxed)
1017                .is_ok()
1018            {
1019                return Ok(next);
1020            }
1021        }
1022    }
1023}
1024
1025// ScyllaDB requires that the keys are non-empty.
1026fn get_big_root_key(root_key: &[u8]) -> Vec<u8> {
1027    let mut big_key = vec![0];
1028    big_key.extend(root_key);
1029    big_key
1030}
1031
1032/// Reserved clustering key inside each chain's partition that holds the
1033/// timestamp sentinel used to seed the per-store client timestamp generator
1034/// in exclusive mode. The empty clustering key is unused by any caller:
1035/// views always write keys prefixed with a tag byte (>= `MIN_VIEW_TAG`),
1036/// and the journaling layer writes 6-byte keys starting with `[0, ...]`.
1037const WRITETIME_SENTINEL_KEY: &[u8] = &[];
1038
1039/// The type for building a new ScyllaDB Key Value Store
1040#[derive(Clone, Debug, Deserialize, Serialize)]
1041pub struct ScyllaDbStoreInternalConfig {
1042    /// The URL to which the requests have to be sent
1043    pub uri: String,
1044    /// Maximum number of concurrent database queries allowed for this client.
1045    pub max_concurrent_queries: Option<usize>,
1046    /// The replication factor.
1047    pub replication_factor: u32,
1048}
1049
1050impl KeyValueDatabase for ScyllaDbDatabaseInternal {
1051    type Config = ScyllaDbStoreInternalConfig;
1052    type Store = ScyllaDbStoreInternal;
1053
1054    fn get_name() -> String {
1055        "scylladb internal".to_string()
1056    }
1057
1058    async fn connect(
1059        config: &Self::Config,
1060        namespace: &str,
1061    ) -> Result<Self, ScyllaDbStoreInternalError> {
1062        Self::check_namespace(namespace)?;
1063        let session = ScyllaDbClient::build_default_session(&config.uri).await?;
1064        let store = ScyllaDbClient::new(session, namespace).await?;
1065        let store = Arc::new(store);
1066        let semaphore = config
1067            .max_concurrent_queries
1068            .map(|n| Arc::new(Semaphore::new(n)));
1069        Ok(Self { store, semaphore })
1070    }
1071
1072    fn open_shared(&self, root_key: &[u8]) -> Result<Self::Store, ScyllaDbStoreInternalError> {
1073        let store = self.store.clone();
1074        let semaphore = self.semaphore.clone();
1075        let root_key = get_big_root_key(root_key);
1076        Ok(ScyllaDbStoreInternal {
1077            store,
1078            semaphore,
1079            root_key,
1080            is_exclusive: false,
1081            ts_floor: Arc::new(AtomicI64::new(0)),
1082        })
1083    }
1084
1085    fn open_exclusive(&self, root_key: &[u8]) -> Result<Self::Store, ScyllaDbStoreInternalError> {
1086        let store = self.store.clone();
1087        let semaphore = self.semaphore.clone();
1088        let root_key = get_big_root_key(root_key);
1089        Ok(ScyllaDbStoreInternal {
1090            store,
1091            semaphore,
1092            root_key,
1093            is_exclusive: true,
1094            ts_floor: Arc::new(AtomicI64::new(0)),
1095        })
1096    }
1097
1098    async fn list_all(config: &Self::Config) -> Result<Vec<String>, ScyllaDbStoreInternalError> {
1099        let session = ScyllaDbClient::build_default_session(&config.uri).await?;
1100        let statement = session
1101            .prepare(format!("DESCRIBE KEYSPACE {KEYSPACE}"))
1102            .await?;
1103        let result = Box::pin(session.execute_iter(statement, &[])).await;
1104        let miss_msg = format!("'{KEYSPACE}' not found in keyspaces");
1105        let result = match result {
1106            Ok(result) => result,
1107            Err(error) => {
1108                let invalid_or_keyspace_not_found = match &error {
1109                    PagerExecutionError::NextPageError(NextPageError::RequestFailure(
1110                        RequestError::LastAttemptError(RequestAttemptError::DbError(db_error, msg)),
1111                    )) => *db_error == DbError::Invalid && msg.as_str() == miss_msg,
1112                    _ => false,
1113                };
1114                if invalid_or_keyspace_not_found {
1115                    return Ok(Vec::new());
1116                } else {
1117                    return Err(ScyllaDbStoreInternalError::PagerExecutionError(error));
1118                }
1119            }
1120        };
1121        let mut namespaces = Vec::new();
1122        let mut rows_stream = result.rows_stream::<(String, String, String, String)>()?;
1123        while let Some(row) = rows_stream.next().await {
1124            let (_, object_kind, name, _) = row?;
1125            if object_kind == "table" {
1126                namespaces.push(name);
1127            }
1128        }
1129        Ok(namespaces)
1130    }
1131
1132    async fn list_root_keys(&self) -> Result<Vec<Vec<u8>>, ScyllaDbStoreInternalError> {
1133        let statement = self
1134            .store
1135            .session
1136            .prepare(format!(
1137                "SELECT root_key FROM {}.\"{}\" ALLOW FILTERING",
1138                KEYSPACE, self.store.namespace
1139            ))
1140            .await?;
1141
1142        // Execute the query
1143        let rows = Box::pin(self.store.session.execute_iter(statement, &[])).await?;
1144        let mut rows = rows.rows_stream::<(Vec<u8>,)>()?;
1145        let mut root_keys = BTreeSet::new();
1146        while let Some(row) = rows.next().await {
1147            let (root_key,) = row?;
1148            let root_key = root_key[1..].to_vec();
1149            root_keys.insert(root_key);
1150        }
1151        Ok(root_keys.into_iter().collect::<Vec<_>>())
1152    }
1153
1154    async fn delete_all(store_config: &Self::Config) -> Result<(), ScyllaDbStoreInternalError> {
1155        let session = ScyllaDbClient::build_default_session(&store_config.uri).await?;
1156        let statement = session
1157            .prepare(format!("DROP KEYSPACE IF EXISTS {KEYSPACE}"))
1158            .await?;
1159
1160        session
1161            .execute_single_page(&statement, &[], PagingState::start())
1162            .await
1163            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
1164        Ok(())
1165    }
1166
1167    async fn exists(
1168        config: &Self::Config,
1169        namespace: &str,
1170    ) -> Result<bool, ScyllaDbStoreInternalError> {
1171        Self::check_namespace(namespace)?;
1172        let session = ScyllaDbClient::build_default_session(&config.uri).await?;
1173
1174        // We check the way the test can fail. It can fail in different ways.
1175        let result = session
1176            .prepare(format!(
1177                "SELECT root_key FROM {KEYSPACE}.\"{namespace}\" LIMIT 1 ALLOW FILTERING"
1178            ))
1179            .await;
1180
1181        // The missing table translates into a very specific error that we matched
1182        let miss_msg1 = format!("unconfigured table {namespace}");
1183        let miss_msg1 = miss_msg1.as_str();
1184        let miss_msg2 = "Undefined name root_key in selection clause";
1185        let miss_msg3 = format!("Keyspace {KEYSPACE} does not exist");
1186        let Err(error) = result else {
1187            // If OK, then the table exists
1188            return Ok(true);
1189        };
1190        let missing_table = match &error {
1191            PrepareError::AllAttemptsFailed {
1192                first_attempt: RequestAttemptError::DbError(db_error, msg),
1193            } => {
1194                if *db_error != DbError::Invalid {
1195                    false
1196                } else {
1197                    msg.as_str() == miss_msg1
1198                        || msg.as_str() == miss_msg2
1199                        || msg.as_str() == miss_msg3
1200                }
1201            }
1202            _ => false,
1203        };
1204        if missing_table {
1205            Ok(false)
1206        } else {
1207            Err(ScyllaDbStoreInternalError::PrepareError(error))
1208        }
1209    }
1210
1211    async fn create(
1212        config: &Self::Config,
1213        namespace: &str,
1214    ) -> Result<(), ScyllaDbStoreInternalError> {
1215        Self::check_namespace(namespace)?;
1216        let session = ScyllaDbClient::build_default_session(&config.uri).await?;
1217
1218        // Create a keyspace if it doesn't exist
1219        let statement = session
1220            .prepare(format!(
1221                "CREATE KEYSPACE IF NOT EXISTS {} WITH REPLICATION = {{ \
1222                    'class' : 'NetworkTopologyStrategy', \
1223                    'replication_factor' : {} \
1224                }}",
1225                KEYSPACE, config.replication_factor
1226            ))
1227            .await?;
1228        session
1229            .execute_single_page(&statement, &[], PagingState::start())
1230            .await
1231            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
1232
1233        // This explicitly sets a lot of default parameters for clarity and for making future
1234        // changes easier.
1235        let statement = session
1236            .prepare(format!(
1237                "CREATE TABLE {KEYSPACE}.\"{namespace}\" (\
1238                    root_key blob, \
1239                    k blob, \
1240                    v blob, \
1241                    PRIMARY KEY (root_key, k) \
1242                ) \
1243                WITH compaction = {{ \
1244                    'class'          : 'LeveledCompactionStrategy', \
1245                    'sstable_size_in_mb' : 160 \
1246                }} \
1247                AND compression = {{ \
1248                    'sstable_compression': 'LZ4Compressor', \
1249                    'chunk_length_in_kb':'4' \
1250                }} \
1251                AND caching = {{ \
1252                    'enabled': 'true' \
1253                }} \
1254                AND gc_grace_seconds = 0 \
1255                AND tombstone_gc = {{'mode': 'immediate'}}"
1256            ))
1257            .await?;
1258        session
1259            .execute_single_page(&statement, &[], PagingState::start())
1260            .await
1261            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
1262        Ok(())
1263    }
1264
1265    async fn delete(
1266        config: &Self::Config,
1267        namespace: &str,
1268    ) -> Result<(), ScyllaDbStoreInternalError> {
1269        Self::check_namespace(namespace)?;
1270        let session = ScyllaDbClient::build_default_session(&config.uri).await?;
1271        let statement = session
1272            .prepare(format!("DROP TABLE IF EXISTS {KEYSPACE}.\"{namespace}\";"))
1273            .await?;
1274        session
1275            .execute_single_page(&statement, &[], PagingState::start())
1276            .await
1277            .map_err(ScyllaDbStoreInternalError::ExecutionError)?;
1278        Ok(())
1279    }
1280}
1281
1282impl ScyllaDbStoreInternal {
1283    /// Obtains the semaphore lock on the database if needed.
1284    async fn acquire(&self) -> Option<SemaphoreGuard<'_>> {
1285        match &self.semaphore {
1286            None => None,
1287            Some(count) => Some(count.acquire().await),
1288        }
1289    }
1290}
1291
1292impl ScyllaDbDatabaseInternal {
1293    fn check_namespace(namespace: &str) -> Result<(), ScyllaDbStoreInternalError> {
1294        if !namespace.is_empty()
1295            && namespace.len() <= 48
1296            && namespace
1297                .chars()
1298                .all(|c| c.is_ascii_alphanumeric() || c == '_')
1299        {
1300            return Ok(());
1301        }
1302        Err(ScyllaDbStoreInternalError::InvalidNamespace)
1303    }
1304}
1305
1306#[cfg(with_testing)]
1307impl TestKeyValueDatabase for JournalingKeyValueDatabase<ScyllaDbDatabaseInternal> {
1308    async fn new_test_config(
1309    ) -> Result<ScyllaDbStoreInternalConfig, JournalingError<ScyllaDbStoreInternalError>> {
1310        // TODO(#4114): Read the port from an environment variable.
1311        let uri = "localhost:9042".to_string();
1312        Ok(ScyllaDbStoreInternalConfig {
1313            uri,
1314            max_concurrent_queries: Some(10),
1315            replication_factor: 1,
1316        })
1317    }
1318}
1319
1320/// The `ScyllaDbDatabase` composed type with metrics
1321#[cfg(with_metrics)]
1322pub type ScyllaDbDatabase = MeteredDatabase<
1323    LruCachingDatabase<MeteredDatabase<JournalingKeyValueDatabase<ScyllaDbDatabaseInternal>>>,
1324>;
1325
1326/// The `ScyllaDbDatabase` composed type
1327#[cfg(not(with_metrics))]
1328pub type ScyllaDbDatabase =
1329    LruCachingDatabase<JournalingKeyValueDatabase<ScyllaDbDatabaseInternal>>;
1330
1331/// The `ScyllaDbStoreConfig` input type
1332pub type ScyllaDbStoreConfig = LruCachingConfig<ScyllaDbStoreInternalConfig>;
1333
1334/// The combined error type for the `ScyllaDbDatabase`.
1335pub type ScyllaDbStoreError = JournalingError<ScyllaDbStoreInternalError>;
1336
1337#[cfg(test)]
1338mod tests {
1339    use scylla::errors::ExecutionError;
1340
1341    use super::*;
1342
1343    /// A write batch whose outcome is undetermined invalidates in-memory state only when
1344    /// the store backs a view, which is the case in exclusive mode.
1345    #[test]
1346    fn write_batch_error_reloads_the_view_only_in_exclusive_mode() {
1347        assert!(
1348            ScyllaDbStoreInternalError::ExclusiveWriteBatchExecutionError(
1349                ExecutionError::EmptyPlan
1350            )
1351            .must_reload_view()
1352        );
1353        assert!(!ScyllaDbStoreInternalError::SharedWriteBatchExecutionError(
1354            ExecutionError::EmptyPlan
1355        )
1356        .must_reload_view());
1357    }
1358
1359    /// No other error asks for a view reload.
1360    #[test]
1361    fn other_errors_do_not_reload_the_view() {
1362        assert!(!ScyllaDbStoreInternalError::ZeroLengthKey.must_reload_view());
1363        assert!(
1364            !ScyllaDbStoreInternalError::ExecutionError(ExecutionError::EmptyPlan)
1365                .must_reload_view()
1366        );
1367    }
1368}