Support BlobstoreWithLink in Sqlblob

Summary: We designed the schema to make this simple to implement - it's literally a metadata read and a metadata write.

Reviewed By: ikostia

Differential Revision: D22233922

fbshipit-source-id: b392b4a3a23859c6106934f73ef60084cc4de62c
This commit is contained in:
Simon Farnsworth 2020-06-26 03:52:21 -07:00 committed by Facebook GitHub Bot
parent b1c85aaf4b
commit 7938a1957a
2 changed files with 92 additions and 1 deletions

View File

@ -23,7 +23,9 @@ use crate::facebook::myadmin_delay;
use crate::myadmin_delay_dummy as myadmin_delay;
use crate::store::{ChunkSqlStore, ChunkingMethod, DataSqlStore};
use anyhow::{format_err, Error, Result};
use blobstore::{Blobstore, BlobstoreGetData, BlobstoreMetadata, CountedBlobstore};
use blobstore::{
Blobstore, BlobstoreGetData, BlobstoreMetadata, BlobstoreWithLink, CountedBlobstore,
};
use bytes::BytesMut;
use cloned::cloned;
use context::CoreContext;
@ -374,3 +376,29 @@ impl Blobstore for Sqlblob {
async move { data_store.is_present(&key).await }.boxed()
}
}
impl BlobstoreWithLink for Sqlblob {
fn link(
&self,
_ctx: CoreContext,
existing_key: String,
link_key: String,
) -> BoxFuture<'static, Result<(), Error>> {
cloned!(self.data_store);
async move {
let existing_data = data_store.get(&existing_key).await?.ok_or_else(|| {
format_err!("Key {} does not exist in the blobstore", existing_key)
})?;
data_store
.put(
&link_key,
existing_data.ctime,
&existing_data.id,
existing_data.count,
existing_data.chunking_method,
)
.await
}
.boxed()
}
}

View File

@ -131,3 +131,66 @@ async fn dedup(fb: FacebookInit) {
"Chunking method differs"
);
}
#[fbinit::compat_test]
async fn link(fb: FacebookInit) {
let ctx = CoreContext::test_mock(fb);
// Generate unique keys.
let suffix: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
let key1 = format!("manifoldblob_test_{}", suffix);
let suffix: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
let key2 = format!("manifoldblob_test_{}", suffix);
let bs = Arc::new(Sqlblob::with_sqlite_in_memory().unwrap());
let mut bytes_in = [0u8; 64];
thread_rng().fill_bytes(&mut bytes_in);
let blobstore_bytes = BlobstoreBytes::from_bytes(Bytes::copy_from_slice(&bytes_in));
assert!(
!bs.is_present(ctx.clone(), key1.clone()).await.unwrap(),
"Blob should not exist yet"
);
assert!(
!bs.is_present(ctx.clone(), key2.clone()).await.unwrap(),
"Blob should not exist yet"
);
// Write a fresh blob
bs.put(ctx.clone(), key1.clone(), blobstore_bytes.clone())
.await
.unwrap();
// Link to a different key
bs.link(ctx.clone(), key1.clone(), key2.clone())
.await
.unwrap();
// Check that reads from the two keys match
let bytes1 = bs.get(ctx.clone(), key1.clone()).await.unwrap();
let bytes2 = bs.get(ctx.clone(), key2.clone()).await.unwrap();
assert_eq!(
bytes1.unwrap().as_raw_bytes(),
bytes2.unwrap().as_raw_bytes()
);
// Reach inside the store and confirm it only stored the data once
let data_store = bs.as_inner().get_data_store();
let row1 = data_store
.get(&key1)
.await
.unwrap()
.expect("Blob 1 not found");
let row2 = data_store
.get(&key2)
.await
.unwrap()
.expect("Blob 2 not found");
assert_eq!(row1.id, row2.id, "Chunk stored under different ids");
assert_eq!(row1.count, row2.count, "Chunk count differs");
assert_eq!(
row1.chunking_method, row2.chunking_method,
"Chunking method differs"
);
}