mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 04:51:49 +08:00
feat: native sync state (#14190)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added indexed clock management capabilities for documents, enabling
get, set, and clear operations across Android, iOS, Electron, and web
platforms.
* **Refactor**
* Improved storage architecture to dynamically select platform-specific
implementations (SQLite for Electron, IndexedDB for others).
* **Bug Fixes**
* Enhanced document operations to properly maintain and clean up indexer
synchronization state during document lifecycle changes.
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -41,6 +41,11 @@ impl SqliteDocStorage {
|
||||
.bind(&meta.space_id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
sqlx::query("UPDATE indexer_sync SET doc_id = $1 WHERE doc_id = $2;")
|
||||
.bind(&space_id)
|
||||
.bind(&meta.space_id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query("UPDATE peer_clocks SET doc_id = $1 WHERE doc_id = $2;")
|
||||
.bind(&space_id)
|
||||
@@ -207,6 +212,11 @@ impl SqliteDocStorage {
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query("DELETE FROM indexer_sync WHERE doc_id = ?;")
|
||||
.bind(&doc_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use super::{error::Result, storage::SqliteDocStorage, DocIndexedClock};
|
||||
|
||||
impl SqliteDocStorage {
|
||||
pub async fn get_doc_indexed_clock(&self, doc_id: String) -> Result<Option<DocIndexedClock>> {
|
||||
let record = sqlx::query!(
|
||||
r#"SELECT doc_id, indexed_clock as "indexed_clock: NaiveDateTime", indexer_version
|
||||
FROM indexer_sync WHERE doc_id = ?"#,
|
||||
doc_id
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(record.map(|rec| DocIndexedClock {
|
||||
doc_id: rec.doc_id,
|
||||
timestamp: rec.indexed_clock,
|
||||
indexer_version: rec.indexer_version,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn set_doc_indexed_clock(
|
||||
&self,
|
||||
doc_id: String,
|
||||
indexed_clock: NaiveDateTime,
|
||||
indexer_version: i64,
|
||||
) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO indexer_sync (doc_id, indexed_clock, indexer_version)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT(doc_id)
|
||||
DO UPDATE SET indexed_clock=$2, indexer_version=$3;
|
||||
"#,
|
||||
)
|
||||
.bind(doc_id)
|
||||
.bind(indexed_clock)
|
||||
.bind(indexer_version)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn clear_doc_indexed_clock(&self, doc_id: String) -> Result<()> {
|
||||
sqlx::query("DELETE FROM indexer_sync WHERE doc_id = ?;")
|
||||
.bind(doc_id)
|
||||
.execute(&self.pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::Utc;
|
||||
|
||||
use super::*;
|
||||
|
||||
async fn get_storage() -> SqliteDocStorage {
|
||||
let storage = SqliteDocStorage::new(":memory:".to_string());
|
||||
storage.connect().await.unwrap();
|
||||
|
||||
storage
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn set_and_get_indexed_clock() {
|
||||
let storage = get_storage().await;
|
||||
let ts = Utc::now().naive_utc();
|
||||
|
||||
storage
|
||||
.set_doc_indexed_clock("doc1".to_string(), ts, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let clock = storage
|
||||
.get_doc_indexed_clock("doc1".to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(clock.doc_id, "doc1");
|
||||
assert_eq!(clock.timestamp, ts);
|
||||
assert_eq!(clock.indexer_version, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_indexed_clock() {
|
||||
let storage = get_storage().await;
|
||||
let ts = Utc::now().naive_utc();
|
||||
|
||||
storage
|
||||
.set_doc_indexed_clock("doc1".to_string(), ts, 1)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
storage
|
||||
.clear_doc_indexed_clock("doc1".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let clock = storage
|
||||
.get_doc_indexed_clock("doc1".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(clock.is_none());
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod doc;
|
||||
pub mod doc_sync;
|
||||
pub mod error;
|
||||
pub mod indexer;
|
||||
pub mod indexer_sync;
|
||||
pub mod pool;
|
||||
pub mod storage;
|
||||
|
||||
@@ -55,6 +56,14 @@ pub struct DocClock {
|
||||
pub timestamp: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[napi(object)]
|
||||
pub struct DocIndexedClock {
|
||||
pub doc_id: String,
|
||||
pub timestamp: NaiveDateTime,
|
||||
pub indexer_version: i64,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct SetBlob {
|
||||
pub key: String,
|
||||
@@ -235,6 +244,47 @@ impl DocStoragePool {
|
||||
Ok(self.get(universal_id).await?.get_doc_clock(doc_id).await?)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn get_doc_indexed_clock(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
) -> Result<Option<DocIndexedClock>> {
|
||||
Ok(
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.get_doc_indexed_clock(doc_id)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn set_doc_indexed_clock(
|
||||
&self,
|
||||
universal_id: String,
|
||||
doc_id: String,
|
||||
indexed_clock: NaiveDateTime,
|
||||
indexer_version: i64,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.set_doc_indexed_clock(doc_id, indexed_clock, indexer_version)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn clear_doc_indexed_clock(&self, universal_id: String, doc_id: String) -> Result<()> {
|
||||
self
|
||||
.get(universal_id)
|
||||
.await?
|
||||
.clear_doc_indexed_clock(doc_id)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi(async_runtime)]
|
||||
pub async fn get_blob(&self, universal_id: String, key: String) -> Result<Option<Blob>> {
|
||||
Ok(self.get(universal_id).await?.get_blob(key).await?)
|
||||
|
||||
Reference in New Issue
Block a user