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