use anyhow::{Context, Result};
use sqlx::SqliteConnection;
use crate::sync::wire::ChangeWire;
pub(super) fn is_deterministic(change: &ChangeWire) -> bool {
change.op_type == "project_recurrence_occurrence"
|| (change.op_type == "create_task" && change.payload["series_id"].is_string())
}
pub(super) async fn adopt_earlier_generation(
conn: &mut SqliteConnection,
prefix: i64,
accepted: &ChangeWire,
) -> Result<()> {
let sequence = accepted
.server_seq
.context("error encrypted-tail-generation-rank")?;
let rows: Vec<(String, Option<i64>)> = sqlx::query_as(
"SELECT change_id, server_seq FROM changes
WHERE entity_type = 'task' AND entity_id = ? AND op_type = 'create_task'
AND json_extract(payload, '$.series_id') IS NOT NULL
ORDER BY server_seq IS NULL, server_seq, local_seq",
)
.bind(&accepted.entity_id)
.fetch_all(&mut *conn)
.await?;
if rows
.iter()
.any(|(_, rank)| rank.is_some_and(|rank| rank <= prefix))
{
return Ok(());
}
let Some((baseline, rank)) = rows.iter().find(|(id, _)| *id != accepted.change_id) else {
return Ok(());
};
if rank.is_some_and(|rank| rank < sequence) {
return Ok(());
}
let baseline = super::client::load_change(conn, baseline)
.await?
.context("error encrypted-tail-history-lost")?;
let labels = crate::sync::apply::adopt_generated_defaults(conn, &baseline, accepted)
.await
.map_err(|error| super::client::apply_failure(error, accepted))?;
if !labels.is_empty() {
let workspace = accepted.payload["workspace_id"]
.as_str()
.context("error encrypted-tail-apply")?;
let count = labels.len();
super::labels::reconcile_names(
conn,
prefix,
workspace,
labels,
Some((&accepted.entity_id, count)),
)
.await?;
}
Ok(())
}