Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
187 lines
4.4 KiB
Rust
187 lines
4.4 KiB
Rust
use std::ops::Deref;
|
|
|
|
use super::{Blob, ListedBlob, SetBlob, error::Result, storage::SqliteDocStorage};
|
|
|
|
impl SqliteDocStorage {
|
|
pub async fn get_blob(&self, key: String) -> Result<Option<Blob>> {
|
|
let result = sqlx::query_as!(
|
|
Blob,
|
|
"SELECT key, data, size, mime, created_at FROM blobs WHERE key = ? AND deleted_at IS NULL",
|
|
key
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
pub async fn set_blob(&self, blob: SetBlob) -> Result<()> {
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO blobs (key, data, mime, size)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT(key)
|
|
DO UPDATE SET data=$2, mime=$3, size=$4, deleted_at=NULL;"#,
|
|
)
|
|
.bind(blob.key)
|
|
.bind(blob.data.deref())
|
|
.bind(blob.mime)
|
|
.bind(blob.data.len() as i64)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_blob(&self, key: String, permanently: bool) -> Result<()> {
|
|
if permanently {
|
|
sqlx::query("DELETE FROM blobs WHERE key = ?")
|
|
.bind(&key)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
} else {
|
|
sqlx::query("UPDATE blobs SET deleted_at = CURRENT_TIMESTAMP WHERE key = ?")
|
|
.bind(&key)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn release_blobs(&self) -> Result<()> {
|
|
sqlx::query("DELETE FROM blobs WHERE deleted_at IS NOT NULL;")
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn list_blobs(&self) -> Result<Vec<ListedBlob>> {
|
|
let result = sqlx::query_as!(
|
|
ListedBlob,
|
|
"SELECT key, size, mime, created_at FROM blobs WHERE deleted_at IS NULL ORDER BY created_at DESC;"
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use sqlx::Row;
|
|
|
|
use super::{super::Data, *};
|
|
|
|
async fn get_storage() -> SqliteDocStorage {
|
|
let storage = SqliteDocStorage::new(":memory:".to_string());
|
|
storage.connect().await.unwrap();
|
|
|
|
storage
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_blob() {
|
|
let storage = get_storage().await;
|
|
|
|
for i in 1..5u32 {
|
|
storage
|
|
.set_blob(SetBlob {
|
|
key: format!("test_{}", i),
|
|
data: Into::<Data>::into(vec![0, 0]),
|
|
mime: "text/plain".to_string(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
let result = storage.get_blob("test_1".to_string()).await.unwrap();
|
|
|
|
assert!(result.is_some());
|
|
|
|
storage.delete_blob("test_".to_string(), false).await.unwrap();
|
|
|
|
let result = storage.get_blob("test".to_string()).await.unwrap();
|
|
assert!(result.is_none());
|
|
|
|
storage.delete_blob("test_2".to_string(), true).await.unwrap();
|
|
|
|
let result = storage.get_blob("test".to_string()).await.unwrap();
|
|
assert!(result.is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn list_blobs() {
|
|
let storage = get_storage().await;
|
|
|
|
let blobs = storage.list_blobs().await.unwrap();
|
|
|
|
assert_eq!(blobs.len(), 0);
|
|
|
|
for i in 1..5u32 {
|
|
storage
|
|
.set_blob(SetBlob {
|
|
key: format!("test_{}", i),
|
|
data: Into::<Data>::into(vec![0, 0]),
|
|
mime: "text/plain".to_string(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
let blobs = storage.list_blobs().await.unwrap();
|
|
|
|
assert_eq!(blobs.len(), 4);
|
|
assert_eq!(
|
|
blobs.iter().map(|b| b.key.as_str()).collect::<Vec<_>>(),
|
|
vec!["test_1", "test_2", "test_3", "test_4"]
|
|
);
|
|
|
|
storage.delete_blob("test_2".to_string(), false).await.unwrap();
|
|
|
|
storage.delete_blob("test_3".to_string(), true).await.unwrap();
|
|
|
|
let query = sqlx::query("SELECT COUNT(*) as len FROM blobs;")
|
|
.fetch_one(&storage.pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(query.get::<i64, &str>("len"), 3);
|
|
|
|
let blobs = storage.list_blobs().await.unwrap();
|
|
assert_eq!(blobs.len(), 2);
|
|
assert_eq!(
|
|
blobs.iter().map(|b| b.key.as_str()).collect::<Vec<_>>(),
|
|
vec!["test_1", "test_4"]
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn release_blobs() {
|
|
let storage = get_storage().await;
|
|
|
|
for i in 1..5u32 {
|
|
storage
|
|
.set_blob(SetBlob {
|
|
key: format!("test_{}", i),
|
|
data: Into::<Data>::into(vec![0, 0]),
|
|
mime: "text/plain".to_string(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
storage.delete_blob("test_2".to_string(), false).await.unwrap();
|
|
storage.release_blobs().await.unwrap();
|
|
|
|
let query = sqlx::query("SELECT COUNT(*) as len FROM blobs;")
|
|
.fetch_one(&storage.pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(query.get::<i64, &str>("len"), 3);
|
|
}
|
|
}
|