use std::sync::Arc;
use anyhow::Result;
use reblessive::TreeStack;
use crate::catalog::providers::{DatabaseProvider, NamespaceProvider, TableProvider};
use crate::dbs::{MessageBroker, Session};
use crate::doc::{Document, DocumentContext, NsDbCtx};
use crate::kvs::{Datastore, Transaction};
use crate::lq::event::LiveEvent;
use crate::val::TableName;
pub(crate) async fn replay_table_live_events(
ds: &Datastore,
txn: Arc<Transaction>,
ns_name: &str,
db_name: &str,
table: &TableName,
events: &[LiveEvent],
broker: Arc<dyn MessageBroker>,
) -> Result<()> {
if events.is_empty() {
return Ok(());
}
let mut ctx = ds.setup_ctx()?;
ctx.set_transaction(Arc::clone(&txn));
ctx.set_broker(Some(broker));
let ctx = ctx.freeze();
let ns = ctx.tx().expect_ns_by_name(ns_name).await?;
let db = ctx.tx().expect_db_by_name(ns_name, db_name).await?;
let tb = ctx.tx().expect_tb_by_name(ns_name, db_name, table).await?;
let parent = NsDbCtx {
ns,
db,
};
let doc_ctx = DocumentContext::initialise(&ctx, &parent, tb, table, None, true).await?;
let opt = ds.setup_options(&Session::default().with_ns(ns_name).with_db(db_name));
let mut stack = TreeStack::new();
for event in events {
let doc_ctx = doc_ctx.clone();
stack
.enter(|stk| Document::replay_live_event(stk, &ctx, &opt, doc_ctx, event))
.finish()
.await?;
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use std::time::Duration;
use crate::channel::Receiver;
use crate::cnf::ConfigMap;
use crate::dbs::{Capabilities, Session};
use crate::kvs::Datastore;
use crate::types::{PublicNotification, PublicValue};
async fn new_ds(engine: &str) -> (Receiver<PublicNotification>, Datastore) {
let (send, recv) = crate::channel::bounded(1000);
let config = ConfigMap::empty().with_key_value("live_query_engine", engine);
let ds = Datastore::builder()
.with_capabilities(Capabilities::all())
.with_auth(false)
.with_notify(send)
.with_config(config)
.build_with_path("memory")
.await
.unwrap();
(recv, ds)
}
fn record_session(ns: &str, db: &str, user_key: &str) -> Session {
use crate::types::{PublicRecordId, PublicRecordIdKey};
Session::for_record(
ns,
db,
"user",
PublicValue::RecordId(PublicRecordId {
table: "user".to_string().into(),
key: PublicRecordIdKey::String(user_key.to_string()),
}),
)
.with_rt(true)
}
async fn drain(recv: &Receiver<PublicNotification>) -> Vec<PublicNotification> {
let mut out = Vec::new();
while let Ok(Ok(n)) = tokio::time::timeout(Duration::from_millis(200), recv.recv()).await {
out.push(n);
}
out
}
fn content(notifs: Vec<PublicNotification>) -> Vec<String> {
let mut keys: Vec<String> = notifs
.into_iter()
.map(|n| format!("{:?}|{:?}|{:?}", n.action, n.record, n.result))
.collect();
keys.sort();
keys
}
async fn deliver(engine: &str) -> Vec<PublicNotification> {
let (recv, ds) = new_ds(engine).await;
let (ns, db) = ("test", "test");
let owner = Session::owner().with_ns(ns).with_db(db);
ds.execute(
"DEFINE TABLE doc PERMISSIONS FOR select FULL; \
DEFINE ACCESS user ON DATABASE TYPE RECORD; \
DEFINE FIELD owner ON doc TYPE record<user>; \
DEFINE FIELD name ON doc TYPE string; \
DEFINE FIELD secret ON doc TYPE string PERMISSIONS FOR select WHERE false; \
DEFINE FIELD derived ON doc TYPE string \
COMPUTED string::concat('derived_', name) \
PERMISSIONS FOR select NONE; \
DEFINE FIELD mine ON doc TYPE string \
COMPUTED string::concat('hi_', name) \
PERMISSIONS FOR select WHERE owner = $auth",
&owner,
None,
)
.await
.unwrap();
let owner_live = Session::owner().with_ns(ns).with_db(db).with_rt(true);
let alice = record_session(ns, db, "alice");
ds.execute("LIVE SELECT * FROM doc", &owner_live, None).await.unwrap();
ds.execute("LIVE SELECT * FROM doc", &alice, None).await.unwrap();
ds.execute("LIVE SELECT DIFF FROM doc", &owner_live, None).await.unwrap();
ds.execute("LIVE SELECT * FROM doc WHERE owner = user:alice", &owner_live, None)
.await
.unwrap();
while recv.try_recv().is_ok() {}
ds.live_query_router_process().await.unwrap();
ds.execute(
"CREATE doc:1 SET owner = user:alice, name = 'one', secret = 's1'",
&owner,
None,
)
.await
.unwrap();
ds.execute("CREATE doc:2 SET owner = user:bob, name = 'two', secret = 's2'", &owner, None)
.await
.unwrap();
ds.execute("UPDATE doc:1 SET name = 'one-updated'", &owner, None).await.unwrap();
ds.execute("DELETE doc:2", &owner, None).await.unwrap();
ds.live_query_router_process().await.unwrap();
ds.live_query_router_process().await.unwrap();
drain(&recv).await
}
#[tokio::test]
async fn router_delivery_matches_inline_delivery() {
let inline = deliver("inline").await;
let router = deliver("router").await;
assert!(!inline.is_empty(), "the inline engine must deliver notifications");
assert!(!router.is_empty(), "the router engine must deliver notifications");
assert_eq!(
content(inline),
content(router),
"router-delivered content must equal inline-delivered content",
);
}
#[tokio::test]
async fn router_delivers_events_captured_before_first_pass() {
use crate::catalog::providers::CatalogProvider;
use crate::kvs::LockType::Optimistic;
use crate::kvs::TransactionType::Write;
let (recv, ds) = new_ds("router").await;
let (ns, db) = ("test", "test");
let tx = ds.transaction(Write, Optimistic).await.unwrap();
tx.ensure_ns_db(None, ns, db).await.unwrap();
tx.commit().await.unwrap();
let owner = Session::owner().with_ns(ns).with_db(db);
ds.execute("DEFINE TABLE doc", &owner, None).await.unwrap();
let live = Session::owner().with_ns(ns).with_db(db).with_rt(true);
ds.execute("LIVE SELECT * FROM doc", &live, None).await.unwrap();
while recv.try_recv().is_ok() {}
ds.execute("CREATE doc:1 SET n = 1", &owner, None).await.unwrap();
ds.live_query_router_process().await.unwrap();
let notifs = drain(&recv).await;
assert_eq!(notifs.len(), 1, "first router pass must deliver the startup-window event");
}
}