20260427 拆分独立repo-bytes.db数据库文件,cir materialize 移除旧兼容
This commit is contained in:
@@ -7,6 +7,7 @@ use crate::storage::{RawByHashEntry, RocksStore, StorageError, StorageResult};
|
||||
|
||||
const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:";
|
||||
const RAW_BLOB_KEY_PREFIX: &str = "rawblob:";
|
||||
const REPO_BYTES_KEY_PREFIX: &str = "sha256:";
|
||||
|
||||
fn raw_by_hash_key(sha256_hex: &str) -> String {
|
||||
format!("{RAW_BY_HASH_KEY_PREFIX}{sha256_hex}")
|
||||
@@ -16,6 +17,10 @@ fn raw_blob_key(sha256_hex: &str) -> String {
|
||||
format!("{RAW_BLOB_KEY_PREFIX}{sha256_hex}")
|
||||
}
|
||||
|
||||
fn repo_bytes_key(sha256_hex: &str) -> String {
|
||||
format!("{REPO_BYTES_KEY_PREFIX}{sha256_hex}")
|
||||
}
|
||||
|
||||
fn validate_blob_sha256_hex(sha256_hex: &str) -> StorageResult<()> {
|
||||
if sha256_hex.len() != 64 || !sha256_hex.as_bytes().iter().all(u8::is_ascii_hexdigit) {
|
||||
return Err(StorageError::InvalidData {
|
||||
@@ -65,6 +70,12 @@ pub struct ExternalRawStoreDb {
|
||||
db: Arc<DB>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExternalRepoBytesDb {
|
||||
path: PathBuf,
|
||||
db: Arc<DB>,
|
||||
}
|
||||
|
||||
impl ExternalRawStoreDb {
|
||||
pub fn open(path: impl Into<PathBuf>) -> StorageResult<Self> {
|
||||
let path = path.into();
|
||||
@@ -158,6 +169,73 @@ impl ExternalRawStoreDb {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalRepoBytesDb {
|
||||
pub fn open(path: impl Into<PathBuf>) -> StorageResult<Self> {
|
||||
let path = path.into();
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| StorageError::RocksDb(e.to_string()))?;
|
||||
}
|
||||
let mut opts = Options::default();
|
||||
opts.create_if_missing(true);
|
||||
opts.set_compression_type(rocksdb::DBCompressionType::Lz4);
|
||||
let db = DB::open(&opts, &path).map_err(|e| StorageError::RocksDb(e.to_string()))?;
|
||||
Ok(Self {
|
||||
path,
|
||||
db: Arc::new(db),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn put_blob_bytes_batch(&self, blobs: &[(String, Vec<u8>)]) -> StorageResult<()> {
|
||||
if blobs.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut batch = WriteBatch::default();
|
||||
for (sha256_hex, bytes) in blobs {
|
||||
validate_blob_sha256_hex(sha256_hex)?;
|
||||
validate_blob_bytes(bytes)?;
|
||||
let key = repo_bytes_key(sha256_hex);
|
||||
batch.put(key.as_bytes(), bytes.as_slice());
|
||||
}
|
||||
self.db
|
||||
.write(batch)
|
||||
.map_err(|e| StorageError::RocksDb(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_blob_bytes(&self, sha256_hex: &str) -> StorageResult<Option<Vec<u8>>> {
|
||||
validate_blob_sha256_hex(sha256_hex)?;
|
||||
let key = repo_bytes_key(sha256_hex);
|
||||
self.db
|
||||
.get(key.as_bytes())
|
||||
.map_err(|e| StorageError::RocksDb(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn get_blob_bytes_batch(
|
||||
&self,
|
||||
sha256_hexes: &[String],
|
||||
) -> StorageResult<Vec<Option<Vec<u8>>>> {
|
||||
if sha256_hexes.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let keys: Vec<String> = sha256_hexes
|
||||
.iter()
|
||||
.map(|hash| {
|
||||
validate_blob_sha256_hex(hash)?;
|
||||
Ok::<String, StorageError>(repo_bytes_key(hash))
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
self.db
|
||||
.multi_get(keys.iter().map(|key| key.as_bytes()))
|
||||
.into_iter()
|
||||
.map(|res| res.map_err(|e| StorageError::RocksDb(e.to_string())))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl RawObjectStore for RocksStore {
|
||||
fn get_raw_entry(&self, sha256_hex: &str) -> StorageResult<Option<RawByHashEntry>> {
|
||||
self.get_raw_by_hash_entry(sha256_hex)
|
||||
@@ -254,7 +332,7 @@ impl RawObjectStore for ExternalRawStoreDb {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ExternalRawStoreDb, RawObjectStore};
|
||||
use super::{ExternalRawStoreDb, ExternalRepoBytesDb, RawObjectStore};
|
||||
use crate::storage::{RawByHashEntry, RocksStore, StorageError, StorageResult};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -551,4 +629,47 @@ mod tests {
|
||||
.put_raw_entries_batch(&[])
|
||||
.expect("empty put succeeds");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_repo_bytes_db_roundtrips_blob_bytes_without_raw_entry() {
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let repo_bytes =
|
||||
ExternalRepoBytesDb::open(td.path().join("repo-bytes.db")).expect("open repo bytes");
|
||||
let bytes = b"repo-bytes-object".to_vec();
|
||||
let hash = sha256_hex(&bytes);
|
||||
|
||||
repo_bytes
|
||||
.put_blob_bytes_batch(&[(hash.clone(), bytes.clone())])
|
||||
.expect("put repo bytes");
|
||||
|
||||
assert_eq!(
|
||||
repo_bytes.get_blob_bytes(&hash).expect("get repo bytes"),
|
||||
Some(bytes.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
repo_bytes
|
||||
.get_blob_bytes_batch(&[hash, "00".repeat(32)])
|
||||
.expect("get repo bytes batch"),
|
||||
vec![Some(bytes), None]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_repo_bytes_db_rejects_invalid_inputs() {
|
||||
let td = tempfile::tempdir().expect("tempdir");
|
||||
let repo_bytes =
|
||||
ExternalRepoBytesDb::open(td.path().join("repo-bytes.db")).expect("open repo bytes");
|
||||
|
||||
assert!(
|
||||
repo_bytes
|
||||
.put_blob_bytes_batch(&[("not-a-valid-hash".to_string(), b"blob".to_vec())])
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
repo_bytes
|
||||
.put_blob_bytes_batch(&[(sha256_hex(b"blob"), Vec::new())])
|
||||
.is_err()
|
||||
);
|
||||
assert!(repo_bytes.get_blob_bytes("not-a-valid-hash").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user