use holo_hash::AgentPubKey;
use holochain_timestamp::Timestamp;
use sqlx::{Executor, Sqlite};
pub struct InsertScheduledFunction<'a> {
pub author: &'a AgentPubKey,
pub zome_name: &'a str,
pub scheduled_fn: &'a str,
pub maybe_schedule: &'a [u8],
pub start_at: Timestamp,
pub end_at: Timestamp,
pub ephemeral: bool,
}
pub(crate) async fn upsert_scheduled_function<'a, 'e, E>(
executor: E,
f: InsertScheduledFunction<'a>,
) -> sqlx::Result<u64>
where
E: Executor<'e, Database = Sqlite>,
{
let result = sqlx::query(
"INSERT INTO ScheduledFunction
(author, zome_name, scheduled_fn, maybe_schedule, start_at, end_at, ephemeral)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(author, zome_name, scheduled_fn) DO UPDATE SET
maybe_schedule = excluded.maybe_schedule,
start_at = excluded.start_at,
end_at = excluded.end_at,
ephemeral = excluded.ephemeral",
)
.bind(f.author.get_raw_36())
.bind(f.zome_name)
.bind(f.scheduled_fn)
.bind(f.maybe_schedule)
.bind(f.start_at.as_micros())
.bind(f.end_at.as_micros())
.bind(f.ephemeral as i64)
.execute(executor)
.await?;
Ok(result.rows_affected())
}
pub(crate) async fn delete_scheduled_function<'e, E>(
executor: E,
author: &AgentPubKey,
zome_name: &str,
scheduled_fn: &str,
) -> sqlx::Result<u64>
where
E: Executor<'e, Database = Sqlite>,
{
let result = sqlx::query(
"DELETE FROM ScheduledFunction
WHERE author = ? AND zome_name = ? AND scheduled_fn = ?",
)
.bind(author.get_raw_36())
.bind(zome_name)
.bind(scheduled_fn)
.execute(executor)
.await?;
Ok(result.rows_affected())
}
pub(crate) async fn get_expired_persisted_scheduled_functions<'e, E>(
executor: E,
author: &AgentPubKey,
now: Timestamp,
) -> sqlx::Result<Vec<(String, String, Vec<u8>)>>
where
E: Executor<'e, Database = Sqlite>,
{
#[derive(sqlx::FromRow)]
struct Row {
zome_name: String,
scheduled_fn: String,
maybe_schedule: Vec<u8>,
}
let rows: Vec<Row> = sqlx::query_as(
"SELECT zome_name, scheduled_fn, maybe_schedule
FROM ScheduledFunction
WHERE ephemeral = 0 AND author = ? AND end_at < ?",
)
.bind(author.get_raw_36())
.bind(now.as_micros())
.fetch_all(executor)
.await?;
Ok(rows
.into_iter()
.map(|r| (r.zome_name, r.scheduled_fn, r.maybe_schedule))
.collect())
}
pub(crate) async fn get_live_scheduled_functions<'e, E>(
executor: E,
author: &AgentPubKey,
now: Timestamp,
) -> sqlx::Result<Vec<(String, String, Vec<u8>, bool)>>
where
E: Executor<'e, Database = Sqlite>,
{
#[derive(sqlx::FromRow)]
struct Row {
zome_name: String,
scheduled_fn: String,
maybe_schedule: Vec<u8>,
ephemeral: i64,
}
let rows: Vec<Row> = sqlx::query_as(
"SELECT zome_name, scheduled_fn, maybe_schedule, ephemeral
FROM ScheduledFunction
WHERE author = ? AND start_at <= ? AND ? <= end_at
ORDER BY start_at ASC",
)
.bind(author.get_raw_36())
.bind(now.as_micros())
.bind(now.as_micros())
.fetch_all(executor)
.await?;
Ok(rows
.into_iter()
.map(|r| {
(
r.zome_name,
r.scheduled_fn,
r.maybe_schedule,
r.ephemeral != 0,
)
})
.collect())
}
pub(crate) async fn delete_live_ephemeral_scheduled_functions<'e, E>(
executor: E,
author: &AgentPubKey,
now: Timestamp,
) -> sqlx::Result<u64>
where
E: Executor<'e, Database = Sqlite>,
{
let result = sqlx::query(
"DELETE FROM ScheduledFunction
WHERE ephemeral = 1 AND author = ? AND start_at <= ?",
)
.bind(author.get_raw_36())
.bind(now.as_micros())
.execute(executor)
.await?;
Ok(result.rows_affected())
}
pub(crate) async fn delete_all_ephemeral_scheduled_functions<'e, E>(
executor: E,
) -> sqlx::Result<u64>
where
E: Executor<'e, Database = Sqlite>,
{
let result = sqlx::query("DELETE FROM ScheduledFunction WHERE ephemeral = 1")
.execute(executor)
.await?;
Ok(result.rows_affected())
}
pub(crate) async fn is_function_scheduled<'e, E>(
executor: E,
author: &AgentPubKey,
zome_name: &str,
scheduled_fn: &str,
) -> sqlx::Result<bool>
where
E: Executor<'e, Database = Sqlite>,
{
let row: (i64,) = sqlx::query_as(
"SELECT EXISTS (
SELECT 1 FROM ScheduledFunction
WHERE author = ? AND zome_name = ? AND scheduled_fn = ?
)",
)
.bind(author.get_raw_36())
.bind(zome_name)
.bind(scheduled_fn)
.fetch_one(executor)
.await?;
Ok(row.0 != 0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kind::Dht;
use crate::test_open_db;
use holo_hash::DnaHash;
use std::sync::Arc;
fn dht_id() -> Dht {
Dht::new(Arc::new(DnaHash::from_raw_36(vec![0u8; 36])))
}
fn agent(seed: u8) -> AgentPubKey {
AgentPubKey::from_raw_36(vec![seed; 36])
}
#[tokio::test]
async fn insert_upsert_delete_scheduled_function() {
let db = test_open_db(dht_id()).await.unwrap();
let author = agent(1);
let payload = b"schedule-blob";
db.upsert_scheduled_function(InsertScheduledFunction {
author: &author,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(100),
end_at: Timestamp::from_micros(200),
ephemeral: true,
})
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &author,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(150),
end_at: Timestamp::from_micros(250),
ephemeral: false,
})
.await
.unwrap();
db.delete_scheduled_function(&author, "z", "f")
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &author,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(100),
end_at: Timestamp::from_micros(200),
ephemeral: true,
})
.await
.unwrap();
}
#[tokio::test]
async fn expired_persisted_scoped_to_author() {
let db = test_open_db(dht_id()).await.unwrap();
let alice = agent(1);
let bob = agent(2);
let payload = b"";
let now_time = Timestamp::from_micros(200);
db.upsert_scheduled_function(InsertScheduledFunction {
author: &alice,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(50),
end_at: Timestamp::from_micros(100),
ephemeral: false,
})
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &alice,
zome_name: "z",
scheduled_fn: "g",
maybe_schedule: payload,
start_at: Timestamp::from_micros(50),
end_at: Timestamp::from_micros(300),
ephemeral: false,
})
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &alice,
zome_name: "z",
scheduled_fn: "e",
maybe_schedule: payload,
start_at: Timestamp::from_micros(50),
end_at: Timestamp::from_micros(100),
ephemeral: true,
})
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &bob,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(50),
end_at: Timestamp::from_micros(100),
ephemeral: false,
})
.await
.unwrap();
let result = db
.as_ref()
.get_expired_persisted_scheduled_functions(&alice, now_time)
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "z");
assert_eq!(result[0].1, "f");
}
#[tokio::test]
async fn delete_live_ephemeral_scoped_to_author_and_now() {
let db = test_open_db(dht_id()).await.unwrap();
let alice = agent(1);
let bob = agent(2);
let payload = b"";
db.upsert_scheduled_function(InsertScheduledFunction {
author: &alice,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(100),
end_at: Timestamp::from_micros(300),
ephemeral: true,
})
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &alice,
zome_name: "z",
scheduled_fn: "g",
maybe_schedule: payload,
start_at: Timestamp::from_micros(100),
end_at: Timestamp::from_micros(300),
ephemeral: false,
})
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &bob,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(100),
end_at: Timestamp::from_micros(300),
ephemeral: true,
})
.await
.unwrap();
db.delete_live_ephemeral_scheduled_functions(&alice, Timestamp::from_micros(150))
.await
.unwrap();
db.upsert_scheduled_function(InsertScheduledFunction {
author: &alice,
zome_name: "z",
scheduled_fn: "f",
maybe_schedule: payload,
start_at: Timestamp::from_micros(100),
end_at: Timestamp::from_micros(300),
ephemeral: true,
})
.await
.unwrap();
}
}