blob: a7e2ea91d3eb370979b145ee5dc8bdfc9aa09cbf [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{EventLog, StoreError, sessions};
use maka_agent_facts::session::CopyState;
use sqlx::{Connection, SqliteConnection};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AbandonRevision {
Abandoned,
Retained,
}
/// Shares the accepting operation's transaction. A receipt survives abandonment
/// so a late worker cannot resurrect a removed draft with its old Session ID.
pub(crate) async fn retain(
connection: &mut SqliteConnection,
session: &str,
) -> Result<(), StoreError> {
match state(connection, session).await? {
Some(CopyState::Abandoned) => Err(StoreError::SessionNotFound),
Some(CopyState::Preparing) => {
sqlx::query("UPDATE session_history_copies SET state='committed' WHERE session_id=?")
.bind(session)
.execute(&mut *connection)
.await?;
sessions::advance_revision(connection, session).await
}
None | Some(CopyState::Committed) => Ok(()),
}
}
async fn state(
connection: &mut SqliteConnection,
session: &str,
) -> Result<Option<CopyState>, StoreError> {
sqlx::query_scalar::<_, String>("SELECT state FROM session_history_copies WHERE session_id=?")
.bind(session)
.fetch_optional(connection)
.await?
.map(|value| value.parse().map_err(sessions::invalid))
.transpose()
}
impl EventLog {
/// Only unused revisions can disappear. Acceptance, dependent creation and
/// this decision serialize on the same durable writer; replies are replayable.
pub async fn abandon_revision(&self, session: &str) -> Result<AbandonRevision, StoreError> {
self.validate_root()?;
sessions::validate_id(session)?;
let session = session.to_owned();
self.connection
.run(move |connection| {
Box::pin(async move {
let mut tx = connection.begin_with("BEGIN IMMEDIATE").await?;
match state(&mut tx, &session)
.await?
.ok_or(StoreError::SessionNotFound)?
{
CopyState::Abandoned => return Ok(AbandonRevision::Abandoned),
CopyState::Committed => return Ok(AbandonRevision::Retained),
CopyState::Preparing => {}
}
// The ledger keeps both identity and allocated family index. Only
// destination-owned resources and disposable projections are removed.
for statement in [
"DELETE FROM session_history_artifacts WHERE session_id=?",
"DELETE FROM session_history_members WHERE session_id=?",
"DELETE FROM session_revision_sources WHERE session_id=?",
"DELETE FROM transcript_cache.rows WHERE session_id=?",
"DELETE FROM transcript_cache.text WHERE session_id=?",
"DELETE FROM transcript_directory WHERE session_id=?",
"DELETE FROM transcript_failures WHERE session_id=?",
"DELETE FROM catalog_messages WHERE session_id=?",
"DELETE FROM session_read_state WHERE session_id=?",
"DELETE FROM artifacts WHERE session_id=?",
"DELETE FROM artifact_catalog WHERE session_id=?",
"DELETE FROM message_queue_state WHERE session_id=?",
] {
sqlx::query(statement)
.bind(&session)
.execute(&mut *tx)
.await?;
}
sqlx::query("DELETE FROM session_control WHERE id=?")
.bind(&session)
.execute(&mut *tx)
.await?;
sqlx::query(
"UPDATE session_history_copies SET state='abandoned' WHERE session_id=?",
)
.bind(&session)
.execute(&mut *tx)
.await?;
sessions::advance_catalog(&mut tx).await?;
tx.commit().await.map_err(StoreError::CommitUnknown)?;
Ok(AbandonRevision::Abandoned)
})
})
.await
}
}