reifydb_sub_server/subscription/
create.rs1use reifydb_core::interface::catalog::{id::SubscriptionId, subscription::HydrationConfig};
5use reifydb_value::value::duration::Duration;
6#[cfg(not(reifydb_single_threaded))]
7use reifydb_value::value::frame::{column::FrameColumn, frame::Frame};
8#[cfg(not(reifydb_single_threaded))]
9use reifydb_value::{
10 params::Params,
11 value::{Value, identity::IdentityId},
12};
13#[cfg(not(reifydb_single_threaded))]
14use tracing::{debug, error};
15
16pub enum CreateSubscriptionResult {
17 Local {
18 id: SubscriptionId,
19 hydration: HydrationConfig,
20 throttle: Option<Duration>,
21 linger: Option<Duration>,
22 },
23 Remote {
24 address: String,
25 body: String,
26 token: Option<String>,
27 hydration: HydrationConfig,
28 throttle: Option<Duration>,
29 linger: Option<Duration>,
30 },
31}
32
33#[cfg(not(reifydb_single_threaded))]
34use reifydb_core::actors::server::Operation;
35
36#[cfg(not(reifydb_single_threaded))]
37use crate::{
38 dispatch::dispatch_subscribe,
39 interceptor::{RequestContext, RequestMetadata},
40 state::AppState,
41 subscription::errors::CreateSubscriptionError,
42};
43
44#[cfg(not(reifydb_single_threaded))]
45pub async fn create_subscription(
46 state: &AppState,
47 identity: IdentityId,
48 rql: &str,
49 params: Params,
50 metadata: RequestMetadata,
51) -> Result<CreateSubscriptionResult, CreateSubscriptionError> {
52 debug!("Subscription rql: {}", rql);
53
54 let ctx = RequestContext {
55 identity,
56 operation: Operation::Subscribe,
57 rql: rql.to_string(),
58 params,
59 metadata,
60 };
61 let (frames, _metrics) = dispatch_subscribe(state, ctx).await?;
62 let frame = frames.first().ok_or(CreateSubscriptionError::ExtractionFailed)?;
63
64 if let Some(remote) = extract_remote_result(frame)? {
65 return Ok(remote);
66 }
67 extract_local_result(frame)
68}
69
70#[cfg(not(reifydb_single_threaded))]
71fn extract_remote_result(frame: &Frame) -> Result<Option<CreateSubscriptionResult>, CreateSubscriptionError> {
72 let Some(addr_col) = frame.columns.iter().find(|c| c.name == "remote_address") else {
73 return Ok(None);
74 };
75 let address = match first_utf8_value(addr_col) {
76 Some(s) => s,
77 None => return Err(CreateSubscriptionError::ExtractionFailed),
78 };
79 let body = frame
80 .columns
81 .iter()
82 .find(|c| c.name == "remote_body")
83 .and_then(first_utf8_value)
84 .ok_or(CreateSubscriptionError::ExtractionFailed)?;
85 let token = frame.columns.iter().find(|c| c.name == "remote_token").and_then(first_utf8_value);
86 let enabled = first_bool_value(frame, "remote_hydration_enabled").unwrap_or(true);
87 let max_rows = first_uint8_value(frame, "remote_hydration_max_rows");
88 let throttle = first_uint8_value(frame, "remote_throttle_ms")
89 .map(|ms| Duration::from_milliseconds(ms as i64).unwrap());
90 let linger =
91 first_uint8_value(frame, "remote_linger_ms").map(|ms| Duration::from_milliseconds(ms as i64).unwrap());
92 Ok(Some(CreateSubscriptionResult::Remote {
93 address,
94 body,
95 token,
96 hydration: HydrationConfig {
97 enabled,
98 max_rows,
99 },
100 throttle,
101 linger,
102 }))
103}
104
105#[cfg(not(reifydb_single_threaded))]
106fn extract_local_result(frame: &Frame) -> Result<CreateSubscriptionResult, CreateSubscriptionError> {
107 let id = frame
108 .columns
109 .iter()
110 .find(|c| c.name == "subscription_id")
111 .and_then(|col| {
112 if col.data.is_empty() {
113 None
114 } else {
115 Some(col.data.get_value(0))
116 }
117 })
118 .and_then(|value| match value {
119 Value::Uint8(id) => Some(SubscriptionId(id)),
120 other => {
121 error!("subscription_id column has wrong type: {:?}", other);
122 None
123 }
124 })
125 .ok_or(CreateSubscriptionError::ExtractionFailed)?;
126
127 let enabled = first_bool_value(frame, "hydration_enabled").unwrap_or(true);
128 let max_rows = first_uint8_value(frame, "hydration_max_rows");
129 let throttle =
130 first_uint8_value(frame, "throttle_ms").map(|ms| Duration::from_milliseconds(ms as i64).unwrap());
131 let linger = first_uint8_value(frame, "linger_ms").map(|ms| Duration::from_milliseconds(ms as i64).unwrap());
132
133 Ok(CreateSubscriptionResult::Local {
134 id,
135 hydration: HydrationConfig {
136 enabled,
137 max_rows,
138 },
139 throttle,
140 linger,
141 })
142}
143
144#[cfg(not(reifydb_single_threaded))]
145#[inline]
146fn first_utf8_value(col: &FrameColumn) -> Option<String> {
147 if col.data.is_empty() {
148 return None;
149 }
150 match col.data.get_value(0) {
151 Value::Utf8(s) => Some(s),
152 _ => None,
153 }
154}
155
156#[cfg(not(reifydb_single_threaded))]
157#[inline]
158fn first_bool_value(frame: &Frame, name: &str) -> Option<bool> {
159 let col = frame.columns.iter().find(|c| c.name == name)?;
160 if col.data.is_empty() {
161 return None;
162 }
163 match col.data.get_value(0) {
164 Value::Boolean(b) => Some(b),
165 _ => None,
166 }
167}
168
169#[cfg(not(reifydb_single_threaded))]
170#[inline]
171fn first_uint8_value(frame: &Frame, name: &str) -> Option<u64> {
172 let col = frame.columns.iter().find(|c| c.name == name)?;
173 if col.data.is_empty() {
174 return None;
175 }
176 match col.data.get_value(0) {
177 Value::Uint8(n) => Some(n),
178 _ => None,
179 }
180}