diff --git a/linera-views/src/backends/dynamo_db.rs b/linera-views/src/backends/dynamo_db.rs index 40b4003c606..6fa55bdd503 100644 --- a/linera-views/src/backends/dynamo_db.rs +++ b/linera-views/src/backends/dynamo_db.rs @@ -130,9 +130,10 @@ const VISIBLE_MAX_VALUE_SIZE: usize = RAW_MAX_VALUE_SIZE - 1 - 1; -/// Fundamental constant in DynamoDB: The maximum size of a key is 1024 bytes +/// Fundamental constant in DynamoDB: The maximum size of a key is 1024 bytes. +/// We decrease by 1 because we append a [1] as prefix. /// See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html -const MAX_KEY_SIZE: usize = 1024; +const MAX_KEY_SIZE: usize = 1023; /// Fundamental constants in DynamoDB: The maximum size of a [`TransactWriteItem`] is 4 MB. /// See https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html @@ -167,12 +168,17 @@ const MAX_BATCH_GET_ITEM_SIZE: usize = 40; /// The second attribute is the actual key value, which is generated by concatenating the /// context prefix. `The Vec` expression is obtained from `self.derive_key`. fn build_key(start_key: &[u8], key: Vec) -> HashMap { + let mut prefixed_key = vec![1]; + prefixed_key.extend(key); [ ( PARTITION_ATTRIBUTE.to_owned(), AttributeValue::B(Blob::new(start_key.to_vec())), ), - (KEY_ATTRIBUTE.to_owned(), AttributeValue::B(Blob::new(key))), + ( + KEY_ATTRIBUTE.to_owned(), + AttributeValue::B(Blob::new(prefixed_key)), + ), ] .into() } @@ -183,12 +189,17 @@ fn build_key_value( key: Vec, value: Vec, ) -> HashMap { + let mut prefixed_key = vec![1]; + prefixed_key.extend(key); [ ( PARTITION_ATTRIBUTE.to_owned(), AttributeValue::B(Blob::new(start_key.to_vec())), ), - (KEY_ATTRIBUTE.to_owned(), AttributeValue::B(Blob::new(key))), + ( + KEY_ATTRIBUTE.to_owned(), + AttributeValue::B(Blob::new(prefixed_key)), + ), ( VALUE_ATTRIBUTE.to_owned(), AttributeValue::B(Blob::new(value)), @@ -199,7 +210,6 @@ fn build_key_value( /// Checks that a key is of the correct size fn check_key_size(key: &[u8]) -> Result<(), DynamoDbStoreInternalError> { - ensure!(!key.is_empty(), DynamoDbStoreInternalError::ZeroLengthKey); ensure!( key.len() <= MAX_KEY_SIZE, DynamoDbStoreInternalError::KeyTooLong @@ -216,7 +226,7 @@ fn extract_key( .get(KEY_ATTRIBUTE) .ok_or(DynamoDbStoreInternalError::MissingKey)?; match key { - AttributeValue::B(blob) => Ok(&blob.as_ref()[prefix_len..]), + AttributeValue::B(blob) => Ok(&blob.as_ref()[1 + prefix_len..]), key => Err(DynamoDbStoreInternalError::wrong_key_type(key)), } } @@ -602,6 +612,8 @@ impl DynamoDbStoreInternal { ) -> Result { let _guard = self.acquire().await; let start_key = start_key.to_vec(); + let mut prefixed_key_prefix = vec![1]; + prefixed_key_prefix.extend(key_prefix); let response = self .client .query() @@ -611,7 +623,10 @@ impl DynamoDbStoreInternal { "{PARTITION_ATTRIBUTE} = :partition and begins_with({KEY_ATTRIBUTE}, :prefix)" )) .expression_attribute_values(":partition", AttributeValue::B(Blob::new(start_key))) - .expression_attribute_values(":prefix", AttributeValue::B(Blob::new(key_prefix))) + .expression_attribute_values( + ":prefix", + AttributeValue::B(Blob::new(prefixed_key_prefix)), + ) .set_exclusive_start_key(start_key_map) .send() .boxed_sync() @@ -746,7 +761,8 @@ impl DynamoDbStoreInternal { .ok_or(DynamoDbStoreInternalError::MissingKey)?; if let AttributeValue::B(blob) = key_attr { - let key = blob.as_ref(); + let prefixed_key = blob.as_ref(); + let key = &prefixed_key[1..]; // Remove the [1] prefix if let Some(indices) = key_to_index.get(key) { if let Some((&last, rest)) = indices.split_last() { let value = extract_value_owned(&mut item)?; @@ -966,10 +982,6 @@ pub enum DynamoDbStoreInternalError { #[error("The transact must have length at most MAX_TRANSACT_WRITE_ITEM_SIZE")] TransactUpperLimitSize, - /// Keys have to be of non-zero length. - #[error("The key must be of strictly positive length")] - ZeroLengthKey, - /// The key must have at most 1024 bytes #[error("The key must have at most 1024 bytes")] KeyTooLong, @@ -978,10 +990,6 @@ pub enum DynamoDbStoreInternalError { #[error("The key prefix must have at most 1024 bytes")] KeyPrefixTooLong, - /// Key prefixes have to be of non-zero length. - #[error("The key_prefix must be of strictly positive length")] - ZeroLengthKeyPrefix, - /// The journal is not coherent #[error(transparent)] JournalConsistencyError(#[from] JournalConsistencyError), diff --git a/linera-views/src/test_utils/mod.rs b/linera-views/src/test_utils/mod.rs index d678840234a..bd74ae729c8 100644 --- a/linera-views/src/test_utils/mod.rs +++ b/linera-views/src/test_utils/mod.rs @@ -176,7 +176,7 @@ pub async fn run_reads(store: S, key_values: Vec<(Vec, Vec store.write_batch(batch).await.unwrap(); for key_prefix in keys .iter() - .flat_map(|key| (0..key.len()).map(|u| &key[..=u])) + .flat_map(|key| (0..=key.len()).map(|u| &key[..u])) { // Getting the find_keys_by_prefix / find_key_values_by_prefix let len_prefix = key_prefix.len();