aven_core/sync/persistence/
server.rs1use 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}