Skip to main content

aven_core/sync/persistence/
server.rs

1use std::collections::HashSet;
2use std::path::Path;
3use std::time::Instant;
4
5use anyhow::{Context, Result, bail};
6use sqlx::SqliteConnection;
7
8use super::{ServerSyncPage, ServerSyncResult};
9use crate::change_log::op_type;
10use crate::db::{Database, begin_immediate};
11#[cfg(test)]
12use crate::sync::wire::SyncRequest;
13use crate::sync::wire::{ChangeRow, ChangeWire, PushAck};
14
15impl Database {
16    pub async fn persist_server_sync_page(&self, page: ServerSyncPage) -> Result<ServerSyncResult> {
17        self.persist_server_sync_page_inner(page, None, crate::sync::wire::SYNC_PROTOCOL_VERSION)
18            .await
19    }
20
21    pub async fn persist_server_sync_page_with_blobs(
22        &self,
23        page: ServerSyncPage,
24        blob_dir: &Path,
25    ) -> Result<ServerSyncResult> {
26        self.persist_server_sync_page_inner(
27            page,
28            Some(blob_dir),
29            crate::sync::wire::SYNC_PROTOCOL_VERSION,
30        )
31        .await
32    }
33
34    #[cfg(test)]
35    pub(in crate::sync) async fn persist_test_protocol_page(
36        &self,
37        request: SyncRequest,
38        active_protocol: u32,
39    ) -> Result<ServerSyncResult> {
40        self.persist_server_sync_page_inner(ServerSyncPage { request }, None, active_protocol)
41            .await
42    }
43
44    async fn persist_server_sync_page_inner(
45        &self,
46        page: ServerSyncPage,
47        blob_dir: Option<&Path>,
48        active_protocol: u32,
49    ) -> Result<ServerSyncResult> {
50        let envelope =
51            crate::sync::wire::validate_request_at_protocol(&page.request, active_protocol)?;
52        for change in &page.request.changes {
53            super::super::protocol::validate_change(active_protocol, change)?;
54            crate::sync::wire::validate_local_change_shape(change)?;
55        }
56        if blob_dir.is_none()
57            && page
58                .request
59                .changes
60                .iter()
61                .any(|change| change.op_type == op_type::ATTACHMENT_ADD)
62        {
63            bail!("error attachment-blob-storage-required");
64        }
65        let blob_prepare_started = Instant::now();
66        if let Some(blob_dir) = blob_dir {
67            let mut conn = self.acquire_reader().await?;
68            super::prepare_server_blobs(&mut conn, blob_dir, &page.request.changes).await?;
69        }
70        let blob_prepare_ms = blob_prepare_started.elapsed().as_millis();
71        let mut conn = self.acquire_writer().await?;
72        let assign_started = Instant::now();
73        let (accepted_count, push_acks) =
74            super::assign_server_sequences(&mut conn, page.request.changes, blob_dir).await?;
75        let assign_ms = assign_started.elapsed().as_millis();
76        let pull_query_started = Instant::now();
77        let (changes, has_more) =
78            load_server_changes_after(&mut conn, envelope.after, envelope.pull_limit).await?;
79        let pull_query_ms = pull_query_started.elapsed().as_millis();
80        Ok(ServerSyncResult {
81            accepted_count,
82            push_acks,
83            changes,
84            has_more,
85            blob_prepare_ms,
86            assign_ms,
87            pull_query_ms,
88        })
89    }
90}
91
92pub(super) async fn assign_server_sequences(
93    conn: &mut SqliteConnection,
94    changes: Vec<ChangeWire>,
95    blob_dir: Option<&Path>,
96) -> Result<(i64, Vec<PushAck>)> {
97    if changes.is_empty() {
98        return Ok((0, Vec::new()));
99    }
100    let mut tx = begin_immediate(conn).await?;
101    let assigned_change_ids = super::load_assigned_change_ids(&mut tx, &changes).await?;
102    let unassigned_changes = changes
103        .iter()
104        .filter(|change| !assigned_change_ids.contains(&change.change_id))
105        .cloned()
106        .collect::<Vec<_>>();
107    if unassigned_changes
108        .iter()
109        .any(|change| change.op_type == op_type::ATTACHMENT_ADD)
110        && blob_dir.is_none()
111    {
112        bail!("error attachment-blob-storage-required");
113    }
114    if let Some(blob_dir) = blob_dir {
115        super::ensure_attachment_blobs_admitted(&mut tx, blob_dir, &unassigned_changes).await?;
116    }
117    let mut next_server_seq = next_available_server_seq(&mut tx).await?;
118    let mut accepted_count = 0_i64;
119    let mut push_acks = Vec::with_capacity(changes.len());
120    let mut affected_attachment_hashes = HashSet::new();
121    for change in changes {
122        let existing_server_seq = sqlx::query_scalar::<_, Option<i64>>(
123            "SELECT server_seq FROM changes WHERE change_id = ?",
124        )
125        .bind(&change.change_id)
126        .fetch_optional(&mut *tx)
127        .await?;
128        let server_seq = if let Some(existing_server_seq) = existing_server_seq {
129            super::verify_existing_change(&mut tx, &change).await?;
130            existing_server_seq.context("existing server change missing sequence")?
131        } else {
132            let server_seq = next_server_seq;
133            next_server_seq += 1;
134            let payload = change.payload.to_string();
135            sqlx::query(
136                "INSERT INTO changes(change_id, client_id, local_seq, entity_type, entity_id, field,
137                 op_type, payload, base_version, created_at, server_seq)
138                 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
139            )
140            .bind(&change.change_id)
141            .bind(&change.client_id)
142            .bind(change.local_seq)
143            .bind(&change.entity_type)
144            .bind(&change.entity_id)
145            .bind(&change.field)
146            .bind(&change.op_type)
147            .bind(payload)
148            .bind(&change.base_version)
149            .bind(&change.created_at)
150            .bind(server_seq)
151            .execute(&mut *tx)
152            .await?;
153            super::apply_server_blob_reference(&mut tx, &change, &mut affected_attachment_hashes)
154                .await?;
155            accepted_count += 1;
156            server_seq
157        };
158        push_acks.push(PushAck {
159            change_id: change.change_id,
160            server_seq,
161        });
162    }
163    let affected_attachment_hashes = affected_attachment_hashes.into_iter().collect::<Vec<_>>();
164    crate::attachments::lifecycle::reconcile_liveness_for_hashes_in_transaction(
165        &mut tx,
166        &affected_attachment_hashes,
167        &crate::attachments::lifecycle::SystemClock,
168    )
169    .await?;
170    tx.commit().await?;
171    Ok((accepted_count, push_acks))
172}
173
174async fn next_available_server_seq(conn: &mut SqliteConnection) -> Result<i64> {
175    Ok(sqlx::query_scalar!(
176        r#"SELECT COALESCE(MAX(server_seq), 0) + 1 AS "seq!: i64" FROM changes"#
177    )
178    .fetch_one(&mut *conn)
179    .await?)
180}
181
182async fn load_server_changes_after(
183    conn: &mut SqliteConnection,
184    after: i64,
185    pull_limit: u32,
186) -> Result<(Vec<ChangeWire>, bool)> {
187    let fetch_limit = i64::from(pull_limit) + 1;
188    let rows = sqlx::query_as!(
189        ChangeRow,
190        r#"SELECT change_id AS "change_id!: String", client_id AS "client_id!: String",
191         local_seq AS "local_seq!: i64", entity_type AS "entity_type!: String",
192         entity_id AS "entity_id!: String", field, op_type AS "op_type!: String",
193         payload AS "payload!: String", base_version, created_at AS "created_at!: String",
194         server_seq
195         FROM changes WHERE server_seq > ? ORDER BY server_seq LIMIT ?"#,
196        after,
197        fetch_limit,
198    )
199    .fetch_all(&mut *conn)
200    .await?;
201    let has_more = rows.len() > pull_limit as usize;
202    let changes = rows
203        .into_iter()
204        .take(pull_limit as usize)
205        .map(ChangeRow::into_wire)
206        .collect();
207    Ok((changes, has_more))
208}