1#![allow(unexpected_cfgs)]
16
17mod config;
32mod convert;
33pub mod descriptor;
34mod rest_api;
35mod thread;
36
37pub use config::{
38 RestApiConfig, SqliteSourceBuilder, SqliteSourceConfig, StartFrom, TableKeyConfig,
39};
40pub use thread::SqliteParam;
41
42use anyhow::{anyhow, Result};
43use async_trait::async_trait;
44use drasi_lib::channels::{ComponentStatus, SourceEvent, SourceEventWrapper, SubscriptionResponse};
45use drasi_lib::sources::base::SourceBase;
46use drasi_lib::Source;
47use std::collections::HashMap;
48use std::future::Future;
49use std::sync::Arc;
50use tokio::sync::{mpsc, oneshot, RwLock};
51use tracing::Instrument;
52
53use crate::thread::SqliteCommand;
54
55#[derive(Clone)]
57pub struct SqliteSourceHandle {
58 command_tx: Arc<RwLock<Option<mpsc::UnboundedSender<SqliteCommand>>>>,
59 source_id: Arc<str>,
60}
61
62impl SqliteSourceHandle {
63 async fn sender(&self) -> Result<mpsc::UnboundedSender<SqliteCommand>> {
64 self.command_tx
65 .read()
66 .await
67 .clone()
68 .ok_or_else(|| anyhow!("source '{}' is not running", self.source_id))
69 }
70
71 pub fn source_id(&self) -> &str {
73 &self.source_id
74 }
75
76 pub async fn execute(&self, sql: impl Into<String>) -> Result<usize> {
78 let (response_tx, response_rx) = oneshot::channel();
79 self.sender().await?.send(SqliteCommand::Execute {
80 sql: sql.into(),
81 response_tx,
82 })?;
83 response_rx
84 .await
85 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
86 }
87
88 pub async fn execute_parameterized(
90 &self,
91 sql: impl Into<String>,
92 params: Vec<SqliteParam>,
93 ) -> Result<usize> {
94 let (response_tx, response_rx) = oneshot::channel();
95 self.sender()
96 .await?
97 .send(SqliteCommand::ExecuteParameterized {
98 sql: sql.into(),
99 params,
100 response_tx,
101 })?;
102 response_rx
103 .await
104 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
105 }
106
107 pub async fn execute_batch(&self, sql: impl Into<String>) -> Result<()> {
109 let (response_tx, response_rx) = oneshot::channel();
110 self.sender().await?.send(SqliteCommand::ExecuteBatch {
111 sql: sql.into(),
112 response_tx,
113 })?;
114 response_rx
115 .await
116 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
117 }
118
119 pub async fn query(
121 &self,
122 sql: impl Into<String>,
123 ) -> Result<Vec<serde_json::Map<String, serde_json::Value>>> {
124 let (response_tx, response_rx) = oneshot::channel();
125 self.sender().await?.send(SqliteCommand::QueryRows {
126 sql: sql.into(),
127 response_tx,
128 })?;
129 response_rx
130 .await
131 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
132 }
133
134 pub async fn query_parameterized(
136 &self,
137 sql: impl Into<String>,
138 params: Vec<SqliteParam>,
139 ) -> Result<Vec<serde_json::Map<String, serde_json::Value>>> {
140 let (response_tx, response_rx) = oneshot::channel();
141 self.sender()
142 .await?
143 .send(SqliteCommand::QueryRowsParameterized {
144 sql: sql.into(),
145 params,
146 response_tx,
147 })?;
148 response_rx
149 .await
150 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
151 }
152
153 async fn begin_transaction(&self) -> Result<()> {
154 let (response_tx, response_rx) = oneshot::channel();
155 self.sender()
156 .await?
157 .send(SqliteCommand::BeginTransaction { response_tx })?;
158 response_rx
159 .await
160 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
161 }
162
163 async fn commit_transaction(&self) -> Result<()> {
164 let (response_tx, response_rx) = oneshot::channel();
165 self.sender()
166 .await?
167 .send(SqliteCommand::CommitTransaction { response_tx })?;
168 response_rx
169 .await
170 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
171 }
172
173 async fn rollback_transaction(&self) -> Result<()> {
174 let (response_tx, response_rx) = oneshot::channel();
175 self.sender()
176 .await?
177 .send(SqliteCommand::RollbackTransaction { response_tx })?;
178 response_rx
179 .await
180 .map_err(|_| anyhow!("sqlite thread closed response channel"))?
181 }
182
183 pub async fn execute_statements_in_transaction(&self, statements: Vec<String>) -> Result<()> {
185 self.begin_transaction().await?;
186
187 for statement in statements {
188 if let Err(err) = self.execute(statement).await {
189 let _ = self.rollback_transaction().await;
190 return Err(err);
191 }
192 }
193
194 self.commit_transaction().await
195 }
196
197 pub async fn execute_parameterized_in_transaction(
199 &self,
200 statements: Vec<(String, Vec<SqliteParam>)>,
201 ) -> Result<()> {
202 self.begin_transaction().await?;
203
204 for (sql, params) in statements {
205 if let Err(err) = self.execute_parameterized(sql, params).await {
206 let _ = self.rollback_transaction().await;
207 return Err(err);
208 }
209 }
210
211 self.commit_transaction().await
212 }
213
214 pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
216 where
217 F: FnOnce(SqliteTxHandle) -> Fut,
218 Fut: Future<Output = Result<T>>,
219 {
220 self.begin_transaction().await?;
221 let tx_handle = SqliteTxHandle {
222 handle: self.clone(),
223 };
224
225 match f(tx_handle).await {
226 Ok(value) => {
227 self.commit_transaction().await?;
228 Ok(value)
229 }
230 Err(err) => {
231 let _ = self.rollback_transaction().await;
232 Err(err)
233 }
234 }
235 }
236}
237
238#[derive(Clone)]
240pub struct SqliteTxHandle {
241 handle: SqliteSourceHandle,
242}
243
244impl SqliteTxHandle {
245 pub async fn execute(&self, sql: impl Into<String>) -> Result<usize> {
246 self.handle.execute(sql).await
247 }
248
249 pub async fn execute_batch(&self, sql: impl Into<String>) -> Result<()> {
250 self.handle.execute_batch(sql).await
251 }
252}
253
254pub struct SqliteSource {
256 base: SourceBase,
257 config: SqliteSourceConfig,
258 command_tx: Arc<RwLock<Option<mpsc::UnboundedSender<SqliteCommand>>>>,
259 thread_handle: Arc<RwLock<Option<std::thread::JoinHandle<()>>>>,
260 rest_shutdown_tx: Arc<RwLock<Option<oneshot::Sender<()>>>>,
261}
262
263impl SqliteSource {
264 pub fn builder(id: impl Into<String>) -> SqliteSourceBuilder {
265 SqliteSourceBuilder::new(id)
266 }
267
268 pub(crate) fn from_parts(base: SourceBase, config: SqliteSourceConfig) -> Result<Self> {
269 Ok(Self {
270 base,
271 config,
272 command_tx: Arc::new(RwLock::new(None)),
273 thread_handle: Arc::new(RwLock::new(None)),
274 rest_shutdown_tx: Arc::new(RwLock::new(None)),
275 })
276 }
277
278 pub fn handle(&self) -> SqliteSourceHandle {
280 SqliteSourceHandle {
281 command_tx: self.command_tx.clone(),
282 source_id: Arc::from(self.base.id.as_str()),
283 }
284 }
285}
286
287#[async_trait]
288impl Source for SqliteSource {
289 fn id(&self) -> &str {
290 &self.base.id
291 }
292
293 fn type_name(&self) -> &str {
294 "sqlite"
295 }
296
297 fn properties(&self) -> HashMap<String, serde_json::Value> {
298 use crate::descriptor::SqliteSourceConfigDto;
299
300 self.base
301 .properties_or_serialize(&SqliteSourceConfigDto::from(&self.config))
302 }
303
304 fn auto_start(&self) -> bool {
305 self.base.get_auto_start()
306 }
307
308 async fn start(&self) -> Result<()> {
309 if self.base.get_status().await == ComponentStatus::Running {
310 return Ok(());
311 }
312
313 self.base
314 .set_status(
315 ComponentStatus::Starting,
316 Some("Starting SQLite source".to_string()),
317 )
318 .await;
319
320 let (command_tx, command_rx) = mpsc::unbounded_channel::<SqliteCommand>();
321 *self.command_tx.write().await = Some(command_tx.clone());
322 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<thread::ChangeEvent>();
323
324 let thread_config = thread::SqliteThreadConfig {
325 path: self.config.path.clone(),
326 tables: self.config.tables.clone(),
327 };
328 let source_id_for_thread = self.base.id.clone();
329 let handle = std::thread::spawn(move || {
330 if let Err(err) = thread::run_sqlite_thread(thread_config, command_rx, event_tx) {
331 log::error!("sqlite source thread '{source_id_for_thread}' failed: {err}");
332 }
333 });
334 *self.thread_handle.write().await = Some(handle);
335
336 let configured_keys = self
337 .config
338 .table_keys
339 .iter()
340 .map(|item| (item.table.clone(), item.key_columns.clone()))
341 .collect::<HashMap<_, _>>();
342
343 let source_id = self.base.id.clone();
344 let dispatchers = self.base.dispatchers.clone();
345 let instance_id = self
346 .base
347 .context()
348 .await
349 .map(|ctx| ctx.instance_id)
350 .unwrap_or_default();
351
352 let span = tracing::info_span!(
353 "sqlite_source_dispatcher",
354 instance_id = %instance_id,
355 component_id = %source_id,
356 component_type = "source"
357 );
358 let task = tokio::spawn(
359 async move {
360 while let Some(event) = event_rx.recv().await {
361 let configured = configured_keys.get(&event.table).map(|v| v.as_slice());
362 let change =
363 convert::change_event_to_source_change(event, &source_id, configured);
364 let mut profiling = drasi_lib::profiling::ProfilingMetadata::new();
365 profiling.source_send_ns = Some(drasi_lib::profiling::timestamp_ns());
366
367 let wrapper = SourceEventWrapper::with_profiling(
368 source_id.clone(),
369 SourceEvent::Change(change),
370 chrono::Utc::now(),
371 profiling,
372 );
373 if let Err(err) =
374 SourceBase::dispatch_from_task(dispatchers.clone(), wrapper, &source_id)
375 .await
376 {
377 log::debug!("failed dispatching sqlite change for '{source_id}': {err}");
378 }
379 }
380 }
381 .instrument(span),
382 );
383 *self.base.task_handle.write().await = Some(task);
384
385 if let Some(rest_config) = &self.config.rest_api {
386 let (shutdown_tx, shutdown_rx) = oneshot::channel();
387 rest_api::start_rest_api(
388 rest_config.clone(),
389 self.handle(),
390 self.config.tables.clone(),
391 self.config.table_keys.clone(),
392 shutdown_rx,
393 )
394 .await?;
395 *self.rest_shutdown_tx.write().await = Some(shutdown_tx);
396 }
397
398 self.base
399 .set_status(
400 ComponentStatus::Running,
401 Some("SQLite source running".to_string()),
402 )
403 .await;
404
405 Ok(())
406 }
407
408 async fn stop(&self) -> Result<()> {
409 if self.base.get_status().await != ComponentStatus::Running {
410 return Ok(());
411 }
412
413 self.base
414 .set_status(
415 ComponentStatus::Stopping,
416 Some("Stopping SQLite source".to_string()),
417 )
418 .await;
419
420 if let Some(rest_shutdown_tx) = self.rest_shutdown_tx.write().await.take() {
421 let _ = rest_shutdown_tx.send(());
422 }
423
424 if let Some(sender) = self.command_tx.write().await.take() {
425 let _ = sender.send(SqliteCommand::Shutdown);
426 }
427
428 if let Some(handle) = self.thread_handle.write().await.take() {
429 let _ = tokio::task::spawn_blocking(move || handle.join()).await;
430 }
431
432 if let Some(task) = self.base.task_handle.write().await.take() {
433 task.abort();
434 }
435
436 self.base
437 .set_status(
438 ComponentStatus::Stopped,
439 Some("SQLite source stopped".to_string()),
440 )
441 .await;
442
443 Ok(())
444 }
445
446 async fn status(&self) -> ComponentStatus {
447 self.base.get_status().await
448 }
449
450 async fn subscribe(
451 &self,
452 settings: drasi_lib::config::SourceSubscriptionSettings,
453 ) -> Result<SubscriptionResponse> {
454 self.base
455 .subscribe_with_bootstrap(&settings, "SQLite")
456 .await
457 }
458
459 fn as_any(&self) -> &dyn std::any::Any {
460 self
461 }
462
463 async fn initialize(&self, context: drasi_lib::context::SourceRuntimeContext) {
464 self.base.initialize(context).await;
465 }
466
467 async fn set_bootstrap_provider(
468 &self,
469 provider: Box<dyn drasi_lib::bootstrap::BootstrapProvider + 'static>,
470 ) {
471 self.base.set_bootstrap_provider(provider).await;
472 }
473}
474
475#[cfg(feature = "dynamic-plugin")]
477drasi_plugin_sdk::export_plugin!(
478 plugin_id = "sqlite-source",
479 core_version = env!("CARGO_PKG_VERSION"),
480 lib_version = env!("CARGO_PKG_VERSION"),
481 plugin_version = env!("CARGO_PKG_VERSION"),
482 source_descriptors = [descriptor::SqliteSourceDescriptor],
483 reaction_descriptors = [],
484 bootstrap_descriptors = [],
485);