use anyhow::{Result, bail};
use reblessive::tree::Stk;
use surrealdb_types::ToSql;
use crate::catalog::providers::TableProvider;
use crate::catalog::{
CompiledSubscription, NodeLiveQuery, SubscriptionDefinition, SubscriptionFields,
SubscriptionQuery,
};
use crate::ctx::FrozenContext;
use crate::dbs::{Options, ParameterCapturePass, Variables};
use crate::doc::CursorDoc;
use crate::exe::FlowResultExt as _;
use crate::exec::Error as ExecError;
use crate::expr::statements::live::{LiveFields, LiveStatement, is_document_dependent};
use crate::expr::visit::Visit;
use crate::key::schema::{NodeLiveQueryKey, SubscriptionKey};
use crate::val::Value;
#[instrument(level = "trace", name = "LiveStatement::compute", skip_all)]
pub(crate) async fn live_statement_compute(
this: &LiveStatement,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
) -> Result<Value> {
ctx.realtime()?;
opt.valid_for_db()?;
let nid = ctx.node_id();
let mut vars = Variables::new();
let mut pass = ParameterCapturePass {
context: ctx,
captures: &mut vars,
};
if let LiveFields::Select(x) = &this.fields {
let _ = x.visit(&mut pass);
};
let _ = this.what.visit(&mut pass);
if let Some(cond) = &this.cond {
let _ = cond.0.visit(&mut pass);
}
if let Some(fetch) = &this.fetch {
for i in fetch.iter() {
let _ = i.0.visit(&mut pass);
}
}
let fields = match &this.fields {
LiveFields::Diff => SubscriptionFields::Diff,
LiveFields::Select(x) => SubscriptionFields::Select(x.clone()),
};
let query = SubscriptionQuery::Compiled(CompiledSubscription {
fields,
what: this.what.clone(),
cond: this.cond.clone().map(|c| c.0),
fetch: this.fetch.as_ref().map(|fs| fs.iter().map(|f| f.0.clone()).collect()),
});
let mut subscription_definition = SubscriptionDefinition {
id: this.id,
node: this.node,
query,
auth: Some(opt.auth.as_ref().clone()),
session: ctx.value("session").cloned(),
vars: vars.0.into_iter().map(|(k, v)| (k.into_string(), v)).collect(),
};
let live_query_id = subscription_definition.id;
match stk
.run(|stk| crate::legacy::expr_compute(&this.what, stk, ctx, opt, doc))
.await
.catch_return()?
{
Value::Table(tb) => {
subscription_definition.node = nid;
let (ns, db) = ctx.expect_ns_db_ids(opt).await?;
let txn = ctx.tx();
{
let (ns, db) = opt.ns_db()?;
let tbdef = txn.expect_tb_by_name(ns, db, &tb).await?;
anyhow::ensure!(
crate::kvs::lightweight::lightweight_relation(&tbdef.table_type).is_none(),
crate::exec::Error::Thrown(
"a LIVE query cannot subscribe to a LIGHTWEIGHT relation".to_owned()
)
);
}
if let Some(cond) = &this.cond
&& !is_document_dependent(&cond.0)
&& let Err(e) = stk
.run(|stk| crate::legacy::expr_compute(&cond.0, stk, ctx, opt, None))
.await
.catch_return()
{
bail!("LIVE query WHERE clause is invalid and will never match: {e}");
}
let key = NodeLiveQueryKey {
nd: nid,
lq: live_query_id,
};
txn.replace_key(
&key,
&NodeLiveQuery {
ns,
db,
tb: tb.clone(),
},
)
.await?;
let key = SubscriptionKey {
ns,
db,
tb: std::borrow::Cow::Borrowed(&tb),
lq: live_query_id,
};
txn.replace_key(&key, &subscription_definition.to_stored()).await?;
txn.bump_table_lives_cache(ns, db, &tb).await?;
txn.clear_cache();
}
v => {
bail!(ExecError::LiveStatement {
value: v.to_sql(),
});
}
};
Ok(crate::val::Uuid(live_query_id).into())
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use std::sync::Arc;
use anyhow::Result;
use crate::catalog::providers::{CatalogProvider, TableProvider};
use crate::channel::Receiver;
use crate::dbs::{Capabilities, Session};
use crate::kvs::Datastore;
use crate::kvs::TransactionType::Write;
use crate::syn;
use crate::types::{
PublicAction, PublicNotification, PublicRecordId, PublicRecordIdKey, PublicValue,
};
pub async fn new_ds() -> Result<(Receiver<PublicNotification>, Arc<Datastore>)> {
let (send, recv) = crate::channel::bounded(1000);
let ds = Datastore::builder()
.with_capabilities(Capabilities::all())
.with_notify(send)
.build_with_path("memory")
.await?;
Ok((recv, ds))
}
#[tokio::test]
async fn test_table_definition_is_created_for_live_query() {
let (recv, dbs) = new_ds().await.unwrap();
let (ns, db, tb) = ("test", "test", "person");
let ses = Session::owner().with_ns(ns).with_db(db).with_rt(true);
let tx = dbs.transaction(Write).await.unwrap();
let db = tx.ensure_ns_db(None, ns, db).await.unwrap();
tx.commit().await.unwrap();
let tx = dbs.transaction(Write).await.unwrap();
let table_occurrences = &*(tx.all_tb(db.namespace_id, db.database_id, None).await.unwrap());
assert!(table_occurrences.is_empty());
tx.cancel().await.unwrap();
let define_statement = format!("DEFINE TABLE {tb};");
dbs.execute(&define_statement, &ses, None).await.unwrap();
let lq_stmt = format!("LIVE SELECT * FROM {}", tb);
let live_query_response = &mut dbs.execute(&lq_stmt, &ses, None).await.unwrap();
let live_id = live_query_response.remove(0).result.unwrap();
let live_id = match live_id {
PublicValue::Uuid(id) => id,
_ => panic!("expected uuid"),
};
let tx = dbs.transaction(Write).await.unwrap();
let table_occurrences = &*(tx.all_tb(db.namespace_id, db.database_id, None).await.unwrap());
assert_eq!(table_occurrences.len(), 1);
assert_eq!(table_occurrences[0].name, tb);
tx.cancel().await.unwrap();
let create_statement = format!("CREATE {tb}:test_true SET condition = true");
let create_response = &mut dbs.execute(&create_statement, &ses, None).await.unwrap();
assert_eq!(create_response.len(), 1);
let expected_record: PublicValue = syn::value(&format!(
"[{{
id: {tb}:test_true,
condition: true,
}}]"
))
.unwrap();
let tmp = create_response.remove(0).result.unwrap();
assert_eq!(tmp, expected_record);
let tx = dbs.transaction(Write).await.unwrap();
let table_occurrences = &*(tx.all_tb(db.namespace_id, db.database_id, None).await.unwrap());
assert_eq!(table_occurrences.len(), 1);
assert_eq!(table_occurrences[0].name, tb);
tx.cancel().await.unwrap();
let notification = recv.recv().await.unwrap();
assert_eq!(
notification,
PublicNotification::new(
live_id,
None,
PublicAction::Create,
PublicValue::RecordId(PublicRecordId {
table: tb.into(),
key: PublicRecordIdKey::String("test_true".to_owned())
}),
syn::value(&format!(
"{{
id: {tb}:test_true,
condition: true,
}}"
))
.unwrap(),
)
);
}
#[tokio::test]
async fn test_table_exists_for_live_query() {
let (_, dbs) = new_ds().await.unwrap();
let (ns, db, tb) = ("test", "test", "person");
let ses = Session::owner().with_ns(ns).with_db(db).with_rt(true);
let tx = dbs.transaction(Write).await.unwrap();
let db = tx.ensure_ns_db(None, ns, db).await.unwrap();
tx.commit().await.unwrap();
let tx = dbs.transaction(Write).await.unwrap();
let table_occurrences = &*(tx.all_tb(db.namespace_id, db.database_id, None).await.unwrap());
assert!(table_occurrences.is_empty());
tx.cancel().await.unwrap();
let create_statement = format!("CREATE {}:test_true SET condition = true", tb);
dbs.execute(&create_statement, &ses, None).await.unwrap();
let tx = dbs.transaction(Write).await.unwrap();
let table_occurrences = &*(tx.all_tb(db.namespace_id, db.database_id, None).await.unwrap());
assert_eq!(table_occurrences.len(), 1);
assert_eq!(table_occurrences[0].name, tb);
tx.cancel().await.unwrap();
let lq_stmt = format!("LIVE SELECT * FROM {}", tb);
dbs.execute(&lq_stmt, &ses, None).await.unwrap();
let tx = dbs.transaction(Write).await.unwrap();
let table_occurrences = &*(tx.all_tb(db.namespace_id, db.database_id, None).await.unwrap());
assert_eq!(table_occurrences.len(), 1);
assert_eq!(table_occurrences[0].name, tb);
tx.cancel().await.unwrap();
}
}