reifydb_sub_server/
subscribe.rs1use std::fmt;
5
6#[cfg(not(reifydb_single_threaded))]
7use reifydb_core::error::diagnostic::internal::internal;
8use reifydb_core::interface::catalog::id::SubscriptionId;
9use reifydb_engine::engine::StandardEngine;
10#[cfg(not(reifydb_single_threaded))]
11use reifydb_type::error::Error;
12#[cfg(not(reifydb_single_threaded))]
13use reifydb_type::value::frame::column::FrameColumn;
14use reifydb_type::{
15 Result as TypeResult,
16 params::Params,
17 value::{Value, frame::frame::Frame, identity::IdentityId},
18};
19#[cfg(not(reifydb_single_threaded))]
20use tracing::debug;
21#[allow(unused_imports)]
22use tracing::error;
23
24pub enum CreateSubscriptionError {
26 Execute(ExecuteError),
27 ExtractionFailed,
28}
29
30impl fmt::Display for CreateSubscriptionError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 CreateSubscriptionError::Execute(e) => write!(f, "{}", e),
34 CreateSubscriptionError::ExtractionFailed => write!(f, "Failed to extract subscription ID"),
35 }
36 }
37}
38
39impl fmt::Debug for CreateSubscriptionError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 CreateSubscriptionError::Execute(e) => f.debug_tuple("Execute").field(e).finish(),
43 CreateSubscriptionError::ExtractionFailed => write!(f, "ExtractionFailed"),
44 }
45 }
46}
47
48impl From<ExecuteError> for CreateSubscriptionError {
49 fn from(err: ExecuteError) -> Self {
50 CreateSubscriptionError::Execute(err)
51 }
52}
53
54pub enum CreateSubscriptionResult {
56 Local(SubscriptionId),
57 Remote {
58 address: String,
59 rql: String,
60 token: Option<String>,
61 },
62}
63
64#[cfg(not(reifydb_single_threaded))]
65use reifydb_core::actors::server::Operation;
66#[cfg(not(reifydb_single_threaded))]
67use tokio::task::spawn_blocking;
68
69use crate::execute::ExecuteError;
70#[cfg(not(reifydb_single_threaded))]
71use crate::{
72 dispatch::dispatch_subscribe,
73 interceptor::{RequestContext, RequestMetadata},
74 state::AppState,
75};
76
77#[cfg(not(reifydb_single_threaded))]
79pub async fn create_subscription(
80 state: &AppState,
81 identity: IdentityId,
82 rql: &str,
83 metadata: RequestMetadata,
84) -> Result<CreateSubscriptionResult, CreateSubscriptionError> {
85 let subscription_rql = format!("CREATE SUBSCRIPTION AS {{ {} }}", rql);
86 debug!("Subscription rql: {}", subscription_rql);
87
88 let ctx = RequestContext {
89 identity,
90 operation: Operation::Subscribe,
91 rql: subscription_rql,
92 params: Params::None,
93 metadata,
94 };
95 let (frames, _metrics) = dispatch_subscribe(state, ctx).await?;
96 let frame = frames.first().ok_or(CreateSubscriptionError::ExtractionFailed)?;
97
98 if let Some(remote) = extract_remote_result(frame)? {
99 return Ok(remote);
100 }
101 extract_local_result(frame)
102}
103
104#[cfg(not(reifydb_single_threaded))]
105fn extract_remote_result(frame: &Frame) -> Result<Option<CreateSubscriptionResult>, CreateSubscriptionError> {
106 let Some(addr_col) = frame.columns.iter().find(|c| c.name == "remote_address") else {
107 return Ok(None);
108 };
109 let address = match first_utf8_value(addr_col) {
110 Some(s) => s,
111 None => return Err(CreateSubscriptionError::ExtractionFailed),
112 };
113 let rql = frame
114 .columns
115 .iter()
116 .find(|c| c.name == "remote_rql")
117 .and_then(first_utf8_value)
118 .ok_or(CreateSubscriptionError::ExtractionFailed)?;
119 let token = frame.columns.iter().find(|c| c.name == "remote_token").and_then(first_utf8_value);
120 Ok(Some(CreateSubscriptionResult::Remote {
121 address,
122 rql,
123 token,
124 }))
125}
126
127#[cfg(not(reifydb_single_threaded))]
128fn extract_local_result(frame: &Frame) -> Result<CreateSubscriptionResult, CreateSubscriptionError> {
129 frame.columns
130 .iter()
131 .find(|c| c.name == "subscription_id")
132 .and_then(|col| {
133 if col.data.is_empty() {
134 None
135 } else {
136 Some(col.data.get_value(0))
137 }
138 })
139 .and_then(|value| match value {
140 Value::Uint8(id) => Some(SubscriptionId(id)),
141 other => {
142 error!("subscription_id column has wrong type: {:?}", other);
143 None
144 }
145 })
146 .map(CreateSubscriptionResult::Local)
147 .ok_or(CreateSubscriptionError::ExtractionFailed)
148}
149
150#[cfg(not(reifydb_single_threaded))]
151#[inline]
152fn first_utf8_value(col: &FrameColumn) -> Option<String> {
153 if col.data.is_empty() {
154 return None;
155 }
156 match col.data.get_value(0) {
157 Value::Utf8(s) => Some(s),
158 _ => None,
159 }
160}
161
162pub fn extract_subscription_id(frames: &[Frame]) -> Option<SubscriptionId> {
167 let frame = frames.first()?;
168 frame.columns
169 .iter()
170 .find(|c| c.name == "subscription_id")
171 .and_then(|col| {
172 if !col.data.is_empty() {
173 Some(col.data.get_value(0))
174 } else {
175 None
176 }
177 })
178 .and_then(|value| match value {
179 Value::Uint8(id) => Some(SubscriptionId(id)),
180 _ => None,
181 })
182}
183
184pub fn cleanup_subscription_sync(engine: &StandardEngine, subscription_id: SubscriptionId) -> TypeResult<()> {
186 let rql = format!("drop subscription if exists subscription_{};", subscription_id.0);
187 engine.admin_as(IdentityId::system(), &rql, Params::None).check()?;
188 Ok(())
189}
190
191#[cfg(not(reifydb_single_threaded))]
193pub async fn cleanup_subscription(state: &AppState, subscription_id: SubscriptionId) -> TypeResult<()> {
194 let engine = state.engine_clone();
195
196 spawn_blocking(move || cleanup_subscription_sync(&engine, subscription_id))
197 .await
198 .map_err(|e| Error(Box::new(internal(format!("Blocking task error: {:?}", e)))))?
199}