micromegas_analytics/lakehouse/
deny_queries_table_function.rs1use super::query_deny_list::{QueryDenyList, compile_match_expr, max_rules};
13use crate::dfext::expressions::exp_to_string;
14use crate::dfext::log_stream_table_provider::LogStreamTableProvider;
15use crate::dfext::task_log_exec_plan::TaskLogExecPlan;
16use datafusion::catalog::TableFunctionArgs;
17use datafusion::catalog::TableFunctionImpl;
18use datafusion::catalog::TableProvider;
19use datafusion::common::plan_err;
20use micromegas_tracing::prelude::*;
21use std::sync::Arc;
22
23#[derive(Debug)]
25pub struct DenyQueriesTableFunction {
26 query_denials: Arc<QueryDenyList>,
27 identity: Option<String>,
30}
31
32impl DenyQueriesTableFunction {
33 pub fn new(query_denials: Arc<QueryDenyList>, identity: Option<String>) -> Self {
34 Self {
35 query_denials,
36 identity,
37 }
38 }
39}
40
41impl TableFunctionImpl for DenyQueriesTableFunction {
42 fn call_with_args(
43 &self,
44 args: TableFunctionArgs,
45 ) -> datafusion::error::Result<Arc<dyn TableProvider>> {
46 let exprs = args.exprs();
47 let Some(match_expr) = exprs.first().map(exp_to_string).transpose()? else {
48 return plan_err!("Missing first argument, expected match_expr: String");
49 };
50 let Some(reason) = exprs.get(1).map(exp_to_string).transpose()? else {
51 return plan_err!("Missing 2nd argument, expected reason: String");
52 };
53 if reason.trim().is_empty() {
54 return plan_err!("deny_queries: reason must not be empty");
55 }
56 let Some(created_by) = self.identity.clone() else {
57 return plan_err!(
58 "deny_queries requires an authenticated caller identity, which this session \
59 does not carry"
60 );
61 };
62 let rule_count = self.query_denials.rule_count();
67 let cap = max_rules();
68 if rule_count >= cap {
69 return plan_err!(
70 "deny_queries: at the cap of {cap} rules (MICROMEGAS_QUERY_DENY_MAX_RULES); \
71 remove an existing rule with remove_query_denial(rule_id) before adding another"
72 );
73 }
74 let compiled = compile_match_expr(self.query_denials.compile_ctx(), &match_expr)?;
78
79 let query_denials = self.query_denials.clone();
80 let spawner = move || {
81 let (tx, rx) = tokio::sync::mpsc::channel(1);
82 spawn_with_context(async move {
83 match query_denials
84 .insert(&match_expr, compiled, &reason, &created_by)
85 .await
86 {
87 Ok(row) => {
88 info!("deny_queries: inserted rule {}", row.rule_id);
89 let _ = tx
90 .send(Ok((chrono::Utc::now(), row.rule_id.to_string())))
91 .await;
92 }
93 Err(e) => {
94 error!("deny_queries: insert failed: {e:?}");
95 let _ = tx.send(Err(format!("{e:?}"))).await;
96 }
97 }
98 });
99 rx
100 };
101
102 Ok(Arc::new(LogStreamTableProvider {
103 log_stream: Arc::new(TaskLogExecPlan::new(Box::new(spawner))),
104 }))
105 }
106}