1use std::{collections::HashMap, io::Write, sync::Arc, time::Duration};
2
3use axum::{Extension, http::StatusCode, response::IntoResponse};
4use bytes::{BufMut, Bytes, BytesMut};
5use compact_str::{ToCompactString, format_compact};
6use http_body_util::{Full, StreamBody};
7use klukai_types::{
8 agent::Agent,
9 api::{ChangeId, QueryEvent, QueryEventMeta, Statement},
10 pubsub::{MatcherCreated, MatcherError, MatcherHandle, NormalizeStatementError, SubsManager},
11 sqlite::SqlitePoolError,
12 tripwire::Tripwire,
13 updates::Handle,
14};
15use rusqlite::Connection;
16use serde::Deserialize;
17use tokio::{
18 sync::{
19 RwLock as TokioRwLock,
20 broadcast::{self, error::RecvError},
21 mpsc::{self, error::TryRecvError},
22 },
23 task::{JoinError, block_in_place},
24};
25use tokio_stream::wrappers::ReceiverStream;
26use tokio_util::sync::CancellationToken;
27use tracing::{debug, error, info, warn};
28use uuid::Uuid;
29
30#[derive(Clone, Copy, Debug, Default, Deserialize)]
31pub struct SubParams {
32 #[serde(default)]
33 from: Option<ChangeId>,
34 #[serde(default)]
35 skip_rows: bool,
36}
37
38pub async fn api_v1_sub_by_id(
39 Extension(agent): Extension<Agent>,
40 Extension(bcast_cache): Extension<SharedMatcherBroadcastCache>,
41 Extension(tripwire): Extension<Tripwire>,
42 axum::extract::Path(id): axum::extract::Path<Uuid>,
43 axum::extract::Query(params): axum::extract::Query<SubParams>,
44) -> hyper::Response<
45 StreamBody<impl futures::Stream<Item = Result<hyper::body::Frame<Bytes>, std::io::Error>>>,
46> {
47 sub_by_id(
48 agent.subs_manager().clone(),
49 id,
50 params,
51 bcast_cache,
52 tripwire,
53 )
54 .await
55}
56
57async fn sub_by_id(
58 subs: SubsManager,
59 id: Uuid,
60 params: SubParams,
61 bcast_cache: SharedMatcherBroadcastCache,
62 tripwire: Tripwire,
63) -> hyper::Response<
64 StreamBody<impl futures::Stream<Item = Result<hyper::body::Frame<Bytes>, std::io::Error>>>,
65> {
66 let matcher_rx = bcast_cache.read().await.get(&id).and_then(|tx| {
67 subs.get(&id).map(|matcher| {
68 debug!("found matcher by id {id}");
69 (matcher, tx.subscribe())
70 })
71 });
72
73 let (matcher, rx) = match matcher_rx {
74 Some(matcher_rx) => matcher_rx,
75 None => {
76 let mut bcast_cache_write = bcast_cache.write().await;
78
79 if let Some(matcher_rx) = bcast_cache_write.get(&id).and_then(|tx| {
80 subs.get(&id).map(|matcher| {
81 debug!("found matcher by id {id}");
82 (matcher, tx.subscribe())
83 })
84 }) {
85 matcher_rx
86 } else {
87 bcast_cache_write.remove(&id);
88 if let Some(handle) = subs.remove(&id) {
89 info!(sub_id = %id, "Removed subscription from sub_by_id");
90 tokio::spawn(async move {
91 handle.cleanup().await;
92 });
93 }
94
95 let error_json = serde_json::to_vec(&QueryEvent::Error(format_compact!(
97 "could not find subscription with id {id}"
98 )))
99 .expect("could not serialize queries stream error");
100
101 let (error_tx, error_rx) = tokio::sync::mpsc::channel::<
102 Result<hyper::body::Frame<Bytes>, std::io::Error>,
103 >(1);
104 let _ = error_tx
105 .send(Ok(hyper::body::Frame::data(Bytes::from(error_json))))
106 .await;
107 let error_stream = ReceiverStream::new(error_rx);
108 let error_body = StreamBody::new(error_stream);
109
110 return hyper::Response::builder()
111 .status(hyper::StatusCode::NOT_FOUND)
112 .body(error_body)
113 .expect("could not build error response");
114 }
115 }
116 };
117
118 let (evt_tx, evt_rx) = mpsc::channel(512);
119
120 let query_hash = matcher.hash().to_owned();
121 tokio::spawn(catch_up_sub(matcher, params, rx, evt_tx));
122
123 let (stream_tx, stream_rx) =
125 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(512);
126
127 let stream = ReceiverStream::new(stream_rx);
129 let body = StreamBody::new(stream);
130
131 tokio::spawn(forward_bytes_to_body_sender(
132 id, evt_rx, stream_tx, tripwire,
133 ));
134
135 hyper::Response::builder()
136 .status(hyper::StatusCode::OK)
137 .header("corro-query-id", id.to_string())
138 .header("corro-query-hash", query_hash)
139 .body(body)
140 .expect("could not build query response body")
141}
142
143fn make_query_event_bytes(
144 buf: &mut BytesMut,
145 query_evt: &QueryEvent,
146) -> serde_json::Result<(Bytes, QueryEventMeta)> {
147 {
148 let mut writer = buf.writer();
149 serde_json::to_writer(&mut writer, query_evt)?;
150
151 writer
153 .write_all(b"\n")
154 .expect("could not write new line to BytesMut Writer");
155 }
156
157 Ok((buf.split().freeze(), query_evt.meta()))
158}
159
160const MAX_UNSUB_TIME: Duration = Duration::from_secs(10 * 60);
161const RECEIVERS_CHECK_INTERVAL: Duration = Duration::from_secs(60);
163
164pub async fn process_sub_channel(
165 subs: SubsManager,
166 id: Uuid,
167 tx: broadcast::Sender<(Bytes, QueryEventMeta)>,
168 mut evt_rx: mpsc::Receiver<QueryEvent>,
169) {
170 let mut buf = BytesMut::new();
171
172 let mut deadline = if tx.receiver_count() == 0 {
173 Some(Box::pin(tokio::time::sleep(MAX_UNSUB_TIME)))
174 } else {
175 None
176 };
177
178 let mut subs_check = tokio::time::interval(RECEIVERS_CHECK_INTERVAL);
181
182 loop {
183 let deadline_check = async {
184 if let Some(sleep) = deadline.as_mut() {
185 sleep.await
186 } else {
187 futures::future::pending().await
188 }
189 };
190
191 let query_evt = tokio::select! {
192 biased;
193 Some(query_evt) = evt_rx.recv() => query_evt,
194 _ = deadline_check => {
195 if tx.receiver_count() == 0 {
196 info!(sub_id = %id, "All listeners for subscription are gone and didn't come back within {MAX_UNSUB_TIME:?}");
197 break;
198 }
199
200 deadline = None;
202 continue;
203 },
204 _ = subs_check.tick() => {
205 if tx.receiver_count() == 0 {
206 if deadline.is_none() {
207 deadline = Some(Box::pin(tokio::time::sleep(MAX_UNSUB_TIME)));
208 }
209 } else {
210 deadline = None;
211 };
212 continue;
213 },
214 else => {
215 break;
216 }
217 };
218
219 let is_still_active = match make_query_event_bytes(&mut buf, &query_evt) {
220 Ok(b) => tx.send(b).is_ok(),
221 Err(e) => {
222 _ = tx.send((
223 error_to_query_event_bytes(&mut buf, e),
224 QueryEventMeta::Error,
225 ));
226 break;
227 }
228 };
229
230 if is_still_active {
231 deadline = None;
232 } else {
233 debug!(sub_id = %id, "no active listeners to receive subscription event: {query_evt:?}");
234 if deadline.is_none() {
235 deadline = Some(Box::pin(tokio::time::sleep(MAX_UNSUB_TIME)));
236 }
237 }
238 }
239
240 warn!(sub_id = %id, "subscription query channel done");
241
242 let handle = match subs.remove(&id) {
244 Some(h) => {
245 info!(sub_id = %id, "Removed subscription from process_sub_channel");
246 h
247 }
248 None => {
249 warn!(sub_id = %id, "subscription handle was already gone. odd!");
250 return;
251 }
252 };
253
254 handle.cleanup().await;
256}
257
258fn expanded_statement(conn: &Connection, stmt: &Statement) -> rusqlite::Result<Option<String>> {
259 Ok(match stmt {
260 Statement::Simple(query)
261 | Statement::Verbose {
262 query,
263 params: None,
264 named_params: None,
265 } => conn.prepare(query)?.expanded_sql(),
266 Statement::WithParams(query, params)
267 | Statement::Verbose {
268 query,
269 params: Some(params),
270 ..
271 } => {
272 let mut prepped = conn.prepare(query)?;
273 for (i, param) in params.iter().enumerate() {
274 prepped.raw_bind_parameter(i + 1, param)?;
275 }
276 prepped.expanded_sql()
277 }
278 Statement::WithNamedParams(query, params)
279 | Statement::Verbose {
280 query,
281 named_params: Some(params),
282 ..
283 } => {
284 let mut prepped = conn.prepare(query)?;
285 for (k, v) in params.iter() {
286 let idx = match prepped.parameter_index(k)? {
287 Some(idx) => idx,
288 None => continue,
289 };
290 prepped.raw_bind_parameter(idx, v)?;
291 }
292 prepped.expanded_sql()
293 }
294 })
295}
296
297async fn expand_sql(agent: &Agent, stmt: &Statement) -> Result<String, MatcherUpsertError> {
298 let conn = agent.pool().read().await?;
299 expanded_statement(&conn, stmt)?.ok_or(MatcherUpsertError::CouldNotExpand)
300}
301
302#[derive(Debug, thiserror::Error)]
303#[allow(clippy::large_enum_variant)]
304pub enum MatcherUpsertError {
305 #[error(transparent)]
306 Pool(#[from] SqlitePoolError),
307 #[error(transparent)]
308 Sqlite(#[from] rusqlite::Error),
309 #[error("could not expand sql statement")]
310 CouldNotExpand,
311 #[error(transparent)]
312 NormalizeStatement(#[from] Box<NormalizeStatementError>),
313 #[error(transparent)]
314 Matcher(#[from] MatcherError),
315 #[error("a `from` query param was supplied, but no existing subscription found")]
316 SubFromWithoutMatcher,
317 #[error("found a subscription, but missing broadcaster")]
318 MissingBroadcaster,
319}
320
321impl MatcherUpsertError {
322 fn status_code(&self) -> StatusCode {
323 match self {
324 MatcherUpsertError::Pool(_)
325 | MatcherUpsertError::CouldNotExpand
326 | MatcherUpsertError::MissingBroadcaster => StatusCode::INTERNAL_SERVER_ERROR,
327 MatcherUpsertError::Sqlite(_)
328 | MatcherUpsertError::NormalizeStatement(_)
329 | MatcherUpsertError::Matcher(_)
330 | MatcherUpsertError::SubFromWithoutMatcher => StatusCode::BAD_REQUEST,
331 }
332 }
333}
334
335impl From<MatcherUpsertError> for hyper::Response<Full<Bytes>> {
336 fn from(value: MatcherUpsertError) -> Self {
337 hyper::Response::builder()
338 .status(hyper::StatusCode::from_u16(value.status_code().as_u16()).unwrap())
339 .body(Full::new(Bytes::from(
340 serde_json::to_vec(&QueryEvent::Error(value.to_compact_string()))
341 .expect("could not serialize queries stream error"),
342 )))
343 .expect("could not build error response")
344 }
345}
346pub type MatcherBroadcastCache = HashMap<Uuid, broadcast::Sender<(Bytes, QueryEventMeta)>>;
347pub type SharedMatcherBroadcastCache = Arc<TokioRwLock<MatcherBroadcastCache>>;
348
349#[derive(Debug, thiserror::Error)]
350pub enum CatchUpError {
351 #[error(transparent)]
352 Pool(#[from] SqlitePoolError),
353 #[error(transparent)]
354 Sqlite(#[from] rusqlite::Error),
355 #[error(transparent)]
356 Send(#[from] mpsc::error::SendError<(Bytes, QueryEventMeta)>),
357 #[error(transparent)]
358 SerdeJson(#[from] serde_json::Error),
359 #[error(transparent)]
360 Matcher(#[from] MatcherError),
361 #[error(transparent)]
362 Join(#[from] JoinError),
363}
364
365fn error_to_query_event_bytes<E: ToCompactString>(buf: &mut BytesMut, e: E) -> Bytes {
366 {
367 let mut writer = buf.writer();
368 serde_json::to_writer(&mut writer, &QueryEvent::Error(e.to_compact_string()))
369 .expect("could not write QueryEvent::Error to buffer");
370
371 writer
373 .write_all(b"\n")
374 .expect("could not write new line to BytesMut Writer");
375 }
376
377 buf.split().freeze()
378}
379
380fn error_to_query_event_bytes_with_meta<E: ToCompactString>(
381 buf: &mut BytesMut,
382 e: E,
383) -> (Bytes, QueryEventMeta) {
384 (error_to_query_event_bytes(buf, e), QueryEventMeta::Error)
385}
386
387async fn catch_up_sub_anew(
388 matcher: &MatcherHandle,
389 evt_tx: &mpsc::Sender<(Bytes, QueryEventMeta)>,
390) -> Result<ChangeId, CatchUpError> {
391 let (q_tx, mut q_rx) = mpsc::channel(10240);
392
393 let task = tokio::spawn({
394 let evt_tx = evt_tx.clone();
395 async move {
396 let mut buf = BytesMut::new();
397 while let Some(event) = q_rx.recv().await {
398 evt_tx
399 .send(make_query_event_bytes(&mut buf, &event)?)
400 .await?;
401 }
402 Ok::<_, CatchUpError>(())
403 }
404 });
405
406 let last_change_id = {
407 let mut conn = matcher.pool().get().await?;
408 block_in_place(|| {
409 let conn_tx = conn.transaction()?;
410 matcher.all_rows(&conn_tx, q_tx)
411 })?
412 };
413
414 task.await??;
415
416 Ok(last_change_id)
417}
418
419async fn catch_up_sub_from(
420 matcher: &MatcherHandle,
421 from: ChangeId,
422 evt_tx: &mpsc::Sender<(Bytes, QueryEventMeta)>,
423) -> Result<ChangeId, CatchUpError> {
424 let (q_tx, mut q_rx) = mpsc::channel(10240);
425
426 let task = tokio::spawn({
427 let evt_tx = evt_tx.clone();
428 async move {
429 let mut buf = BytesMut::new();
430 while let Some(event) = q_rx.recv().await {
431 evt_tx
432 .send(make_query_event_bytes(&mut buf, &event)?)
433 .await?;
434 }
435 Ok::<_, CatchUpError>(())
436 }
437 });
438
439 let last_change_id = {
440 let conn = matcher.pool().get().await?;
441 block_in_place(|| matcher.changes_since(from, &conn, q_tx))?
442 };
443
444 task.await??;
445
446 Ok(last_change_id)
447}
448
449pub async fn catch_up_sub(
450 matcher: MatcherHandle,
451 params: SubParams,
452 mut sub_rx: broadcast::Receiver<(Bytes, QueryEventMeta)>,
453 evt_tx: mpsc::Sender<(Bytes, QueryEventMeta)>,
454) {
455 debug!("catching up sub {} params: {:?}", matcher.id(), params);
456
457 let mut buf = BytesMut::new();
458
459 let (queue_tx, mut queue_rx) = mpsc::channel(10240);
461
462 let cancel = CancellationToken::new();
463 let _drop_guard = cancel.clone().drop_guard();
465
466 let queue_task = tokio::spawn({
467 let cancel = cancel.clone();
468 async move {
469 loop {
470 let (buf, meta) = tokio::select! {
471 _ = cancel.cancelled() => {
472 break;
473 },
474 Ok(res) = sub_rx.recv() => res,
475 else => break
476 };
477
478 if let QueryEventMeta::Change(change_id) = meta
479 && let Err(_e) = queue_tx.try_send((buf, change_id))
480 {
481 return Err(eyre::eyre!(
482 "catching up too slowly, gave up after buffering {MAX_EVENTS_BUFFER_SIZE} events"
483 ));
484 }
485 }
486 Ok(sub_rx)
487 }
488 });
489
490 let mut last_change_id = {
491 let res = match params.from {
492 Some(ChangeId(0)) | None => {
493 if params.skip_rows {
494 let conn = match matcher.pool().get().await {
495 Ok(conn) => conn,
496 Err(e) => {
497 _ = evt_tx
498 .send(error_to_query_event_bytes_with_meta(&mut buf, e))
499 .await;
500 return;
501 }
502 };
503 block_in_place(|| matcher.max_change_id(&conn)).map_err(CatchUpError::from)
504 } else {
505 catch_up_sub_anew(&matcher, &evt_tx).await
506 }
507 }
508 Some(from) => catch_up_sub_from(&matcher, from, &evt_tx).await,
509 };
510
511 match res {
512 Ok(change_id) => change_id,
513 Err(e) => {
514 if !matches!(e, CatchUpError::Send(_)) {
515 _ = evt_tx
516 .send(error_to_query_event_bytes_with_meta(&mut buf, e))
517 .await;
518 }
519 return;
520 }
521 }
522 };
523
524 let mut min_change_id = last_change_id + 1;
525 info!(sub_id = %matcher.id(), "minimum expected change id: {min_change_id:?}");
526
527 let mut pending_event = None;
528
529 let last_sub_change_id = match queue_rx.try_recv() {
530 Ok((event_buf, change_id)) => {
531 info!(sub_id = %matcher.id(), "last change id received by subscription: {change_id:?}");
532 pending_event = Some((event_buf, change_id));
533 Some(change_id)
534 }
535 Err(e) => match e {
536 TryRecvError::Empty => {
537 let last_change_id_sent = matcher.last_change_id_sent();
538 info!(sub_id = %matcher.id(), "last change id sent by subscription: {last_change_id_sent:?}");
539 if last_change_id_sent <= last_change_id {
540 None
541 } else {
542 Some(last_change_id_sent)
543 }
544 }
545 TryRecvError::Disconnected => {
546 _ = evt_tx
548 .send(error_to_query_event_bytes_with_meta(
549 &mut buf,
550 "exceeded events buffer",
551 ))
552 .await;
553 return;
554 }
555 },
556 };
557
558 if let Some(change_id) = last_sub_change_id {
559 debug!(sub_id = %matcher.id(), "got a change to check: {change_id:?}");
560 for i in 0..5 {
561 min_change_id = last_change_id + 1;
562
563 if change_id >= min_change_id {
564 info!(sub_id = %matcher.id(), "attempt #{} to catch up subcription from change id: {change_id:?} (last: {last_change_id:?})", i+1);
566
567 let res = catch_up_sub_from(&matcher, last_change_id, &evt_tx).await;
568
569 match res {
570 Ok(new_last_change_id) => last_change_id = new_last_change_id,
571 Err(e) => {
572 if !matches!(e, CatchUpError::Send(_)) {
573 _ = evt_tx
574 .send(error_to_query_event_bytes_with_meta(&mut buf, e))
575 .await;
576 }
577 return;
578 }
579 }
580 } else {
581 break;
582 }
583 tokio::time::sleep(Duration::from_millis(100)).await;
585 }
586 if change_id >= min_change_id {
587 _ = evt_tx
588 .send(error_to_query_event_bytes_with_meta(
589 &mut buf,
590 "could not catch up to changes in 5 attempts",
591 ))
592 .await;
593 return;
594 }
595 }
596
597 info!(sub_id = %matcher.id(), "subscription is caught up, no gaps in change id. last change id: {last_change_id:?}, last_sub_change_id: {last_sub_change_id:?}");
598
599 if let Some((event_buf, change_id)) = pending_event {
600 info!(sub_id = %matcher.id(), "had a pending event we popped from the queue, id: {change_id:?} (last change id: {last_change_id:?})");
601 if change_id > last_change_id {
602 info!(sub_id = %matcher.id(), "change was more recent, sending!");
603 if let Err(_e) = evt_tx
604 .send((event_buf, QueryEventMeta::Change(change_id)))
605 .await
606 {
607 warn!(sub_id = %matcher.id(), "could not send buffered events to subscriber, receiver must be gone!");
608 return;
609 }
610
611 last_change_id = change_id;
612 }
613 }
614
615 cancel.cancel();
617
618 while let Some((event_buf, change_id)) = queue_rx.recv().await {
619 info!(sub_id = %matcher.id(), "processing buffered change, id: {change_id:?} (last change id: {last_change_id:?})");
620 if change_id > last_change_id {
621 info!(sub_id = %matcher.id(), "change was more recent, sending!");
622 if let Err(_e) = evt_tx
623 .send((event_buf, QueryEventMeta::Change(change_id)))
624 .await
625 {
626 warn!(sub_id = %matcher.id(), "could not send buffered events to subscriber, receiver must be gone!");
627 return;
628 }
629
630 last_change_id = change_id;
631 }
632 }
633
634 let sub_rx = match queue_task.await {
635 Ok(Ok(sub_rx)) => sub_rx,
636 Ok(Err(e)) => {
637 _ = evt_tx
638 .send(error_to_query_event_bytes_with_meta(&mut buf, e))
639 .await;
640 return;
641 }
642 Err(e) => {
643 _ = evt_tx
644 .send(error_to_query_event_bytes_with_meta(&mut buf, e))
645 .await;
646 return;
647 }
648 };
649
650 forward_sub_to_sender(matcher, sub_rx, evt_tx, params.skip_rows).await
651}
652
653pub async fn upsert_sub(
654 handle: MatcherHandle,
655 maybe_created: Option<MatcherCreated>,
656 subs: &SubsManager,
657 bcast_write: &mut MatcherBroadcastCache,
658 params: SubParams,
659 tx: mpsc::Sender<(Bytes, QueryEventMeta)>,
660) -> Result<Uuid, MatcherUpsertError> {
661 if let Some(created) = maybe_created {
662 if params.from.is_some() {
663 return Err(MatcherUpsertError::SubFromWithoutMatcher);
664 }
665
666 let (sub_tx, sub_rx) = broadcast::channel(10240);
667
668 tokio::spawn(forward_sub_to_sender(
669 handle.clone(),
670 sub_rx,
671 tx,
672 params.skip_rows,
673 ));
674
675 bcast_write.insert(handle.id(), sub_tx.clone());
676
677 tokio::spawn(process_sub_channel(
678 subs.clone(),
679 handle.id(),
680 sub_tx,
681 created.evt_rx,
682 ));
683
684 Ok(handle.id())
685 } else {
686 let id = handle.id();
687 let sub_tx = bcast_write
688 .get(&id)
689 .cloned()
690 .ok_or(MatcherUpsertError::MissingBroadcaster)?;
691 debug!("found matcher handle");
692
693 tokio::spawn(catch_up_sub(handle, params, sub_tx.subscribe(), tx));
694
695 Ok(id)
696 }
697}
698
699pub async fn api_v1_subs(
700 Extension(agent): Extension<Agent>,
701 Extension(bcast_cache): Extension<SharedMatcherBroadcastCache>,
702 Extension(tripwire): Extension<Tripwire>,
703 axum::extract::Query(params): axum::extract::Query<SubParams>,
704 axum::extract::Json(stmt): axum::extract::Json<Statement>,
705) -> impl IntoResponse {
706 let stmt = match expand_sql(&agent, &stmt).await {
707 Ok(stmt) => stmt,
708 Err(e) => {
709 let error_msg = format!("Error: {}", e);
711 let error_json = serde_json::to_vec(&error_msg).expect("could not serialize error");
712 let (error_tx, error_rx) =
713 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(1);
714 let _ = error_tx
715 .send(Ok(hyper::body::Frame::data(Bytes::from(error_json))))
716 .await;
717 let error_stream = ReceiverStream::new(error_rx);
718 let error_body = StreamBody::new(error_stream);
719
720 return hyper::Response::builder()
721 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
722 .body(error_body)
723 .expect("could not build error response");
724 }
725 };
726
727 info!("Received subscription request for query: {stmt}");
728
729 let mut bcast_write = bcast_cache.write().await;
730
731 let subs = agent.subs_manager();
732
733 let upsert_res = subs.get_or_insert(
734 &stmt,
735 &agent.config().db.subscriptions_path(),
736 &agent.schema().read(),
737 agent.pool(),
738 tripwire.clone(),
739 );
740
741 let (handle, maybe_created) = match upsert_res {
742 Ok(res) => res,
743 Err(e) => {
744 let error_msg = format!("Error: {}", MatcherUpsertError::from(e));
746 let error_json = serde_json::to_vec(&error_msg).expect("could not serialize error");
747 let (error_tx, error_rx) =
748 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(1);
749 let _ = error_tx
750 .send(Ok(hyper::body::Frame::data(Bytes::from(error_json))))
751 .await;
752 let error_stream = ReceiverStream::new(error_rx);
753 let error_body = StreamBody::new(error_stream);
754
755 return hyper::Response::builder()
756 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
757 .body(error_body)
758 .expect("could not build error response");
759 }
760 };
761
762 let (stream_tx, stream_rx) =
763 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(10240);
764 let stream = ReceiverStream::new(stream_rx);
765 let body = StreamBody::new(stream);
766
767 let (forward_tx, forward_rx) = mpsc::channel(10240);
768
769 tokio::spawn(forward_bytes_to_body_sender(
770 handle.id(),
771 forward_rx,
772 stream_tx,
773 tripwire.clone(),
774 ));
775
776 let query_hash = handle.hash().to_owned();
777 let matcher_id = match upsert_sub(
778 handle,
779 maybe_created,
780 subs,
781 &mut bcast_write,
782 params,
783 forward_tx,
784 )
785 .await
786 {
787 Ok(id) => id,
788 Err(e) => {
789 let error_msg = format!("Error: {}", e);
790 let error_json = serde_json::to_vec(&error_msg).expect("could not serialize error");
791 let (error_tx, error_rx) =
792 tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(1);
793 let _ = error_tx
794 .send(Ok(hyper::body::Frame::data(Bytes::from(error_json))))
795 .await;
796 let error_stream = ReceiverStream::new(error_rx);
797 let error_body = StreamBody::new(error_stream);
798
799 return hyper::Response::builder()
800 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
801 .body(error_body)
802 .expect("could not build error response");
803 }
804 };
805
806 hyper::Response::builder()
807 .status(hyper::StatusCode::OK)
808 .header("corro-query-id", matcher_id.to_string())
809 .header("corro-query-hash", query_hash)
810 .body(body)
811 .expect("could not generate ok http response for query request")
812}
813
814const MAX_EVENTS_BUFFER_SIZE: usize = 1024;
815
816async fn forward_sub_to_sender(
817 handle: MatcherHandle,
818 mut sub_rx: broadcast::Receiver<(Bytes, QueryEventMeta)>,
819 tx: mpsc::Sender<(Bytes, QueryEventMeta)>,
820 skip_rows: bool,
821) {
822 info!(sub_id = %handle.id(), "forwarding subscription events to a sender");
823
824 loop {
825 let (event_buf, meta) = tokio::select! {
826 res = sub_rx.recv() => {
827 match res {
828 Ok((event_buf, meta)) => (event_buf, meta),
829 Err(RecvError::Lagged(skipped)) => {
830 warn!(sub_id = %handle.id(), "subscription skipped {} events, aborting", skipped);
831 return;
832 },
833 Err(RecvError::Closed) => {
834 info!(sub_id = %handle.id(), "events subcription ran out");
835 return;
836 },
837 }
838 },
839 _ = handle.cancelled() => {
840 info!(sub_id = %handle.id(), "subscription cancelled, aborting forwarding bytes to subscriber");
841 return;
842 },
843 };
844
845 if skip_rows
846 && matches!(
847 meta,
848 QueryEventMeta::Columns | QueryEventMeta::Row(_) | QueryEventMeta::EndOfQuery(_)
849 )
850 {
851 continue;
852 }
853 if let Err(e) = tx.send((event_buf, meta)).await {
854 warn!(sub_id = %handle.id(), "could not send subscription event to channel: {e}");
855 return;
856 }
857 }
858}
859
860async fn handle_sub_event(
861 sub_id: Uuid,
862 buf: &mut BytesMut,
863 event_buf: Bytes,
864 meta: QueryEventMeta,
865 tx: &tokio::sync::mpsc::Sender<Result<hyper::body::Frame<Bytes>, std::io::Error>>,
866 last_change_id: &mut ChangeId,
867) -> hyper::Result<()> {
868 match meta {
869 QueryEventMeta::EndOfQuery(Some(change_id)) | QueryEventMeta::Change(change_id) => {
870 if !last_change_id.is_zero() && change_id > *last_change_id + 1 {
871 warn!(%sub_id, "non-contiguous change id (> + 1) received: {change_id:?}, last seen: {last_change_id:?}");
872 } else if !last_change_id.is_zero() && change_id == *last_change_id {
873 warn!(%sub_id, "duplicate change id received: {change_id:?}, last seen: {last_change_id:?}");
874 } else if change_id < *last_change_id {
875 warn!(%sub_id, "smaller change id received: {change_id:?}, last seen: {last_change_id:?}");
876 }
877 *last_change_id = change_id;
878 }
879 _ => {
880 }
882 }
883 buf.extend_from_slice(&event_buf);
884 let to_send = if buf.len() >= 64 * 1024 {
885 buf.split().freeze()
886 } else {
887 return Ok(());
888 };
889
890 if tx
891 .send(Ok(hyper::body::Frame::data(to_send)))
892 .await
893 .is_err()
894 {
895 warn!(sub_id = %sub_id, "stream channel closed, stopping event handling");
898 return Ok(()); }
900 Ok(())
901}
902
903async fn forward_bytes_to_body_sender(
904 sub_id: Uuid,
905 mut rx: mpsc::Receiver<(Bytes, QueryEventMeta)>,
906 tx: tokio::sync::mpsc::Sender<Result<hyper::body::Frame<Bytes>, std::io::Error>>,
907 mut tripwire: Tripwire,
908) {
909 let mut buf = BytesMut::new();
910
911 let send_deadline = tokio::time::sleep(Duration::from_millis(10));
912 tokio::pin!(send_deadline);
913
914 let mut last_change_id = ChangeId(0);
915
916 loop {
917 tokio::select! {
918 biased;
919 res = rx.recv() => {
920 match res {
921 Some((event_buf, meta)) => {
922 if let Err(e) = handle_sub_event(sub_id, &mut buf, event_buf, meta, &tx, &mut last_change_id).await {
923 warn!(%sub_id, "could not forward subscription query event to receiver: {e}");
924 return;
925 }
926 },
927 None => break,
928 }
929 },
930 _ = &mut send_deadline => {
931 if !buf.is_empty() {
932 if let Err(e) = tx.send(Ok(hyper::body::Frame::data(buf.split().freeze()))).await {
933 warn!(%sub_id, "could not forward subscription query event to receiver: {e}");
934 return;
935 }
936 } else {
937 send_deadline.as_mut().reset(tokio::time::Instant::now() + Duration::from_millis(10));
938 continue;
939 }
940 },
941 _ = &mut tripwire => {
942 break;
943 }
944 }
945 }
946
947 while let Ok((event_buf, meta)) = rx.try_recv() {
948 if let Err(e) =
949 handle_sub_event(sub_id, &mut buf, event_buf, meta, &tx, &mut last_change_id).await
950 {
951 warn!(%sub_id, "could not forward subscription query event to receiver: {e}");
952 return;
953 }
954 }
955
956 if !buf.is_empty()
957 && let Err(e) = tx.send(Ok(hyper::body::Frame::data(buf.freeze()))).await
958 {
959 warn!(%sub_id, "could not forward last subscription query event to receiver: {e}");
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use axum::response::IntoResponse;
966 use http_body_util::BodyExt;
967 use klukai_tests::launch_test_agent;
968 use klukai_tests::tempdir::TempDir;
969 use klukai_types::{
970 actor::ActorId,
971 api::{ColumnName, NotifyEvent, SqliteValue::Integer, TableName},
972 base::{CrsqlDbVersion, CrsqlSeq},
973 broadcast::{ChangeSource, ChangeV1, Changeset},
974 change::Change,
975 dbsr,
976 pubsub::pack_columns,
977 spawn::wait_for_all_pending_handles,
978 tripwire::Tripwire,
979 {
980 api::{ChangeId, RowId},
981 config::Config,
982 pubsub::ChangeType,
983 },
984 };
985 use serde::de::DeserializeOwned;
986 use std::time::Instant;
987 use tokio::time::timeout;
988 use tokio_util::codec::{Decoder, LinesCodec};
989
990 use super::*;
991 use crate::{
992 agent::{process_multiple_changes, setup},
993 api::public::{
994 TimeoutParams, api_v1_db_schema, api_v1_transactions,
995 update::{SharedUpdateBroadcastCache, api_v1_updates},
996 },
997 };
998
999 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1000 async fn test_api_v1_subs() -> eyre::Result<()> {
1001 _ = tracing_subscriber::fmt::try_init();
1002
1003 let (tripwire, _tripwire_worker, _tripwire_tx) = Tripwire::new_simple();
1004
1005 let dir = TempDir::new(tempfile::tempdir()?);
1006
1007 let (agent, _agent_options) = setup(
1008 Config::builder()
1009 .db_path(dir.path().join("corrosion.db").display().to_string())
1010 .gossip_addr("127.0.0.1:0".parse()?)
1011 .api_addr("127.0.0.1:0".parse()?)
1012 .build()?,
1013 tripwire.clone(),
1014 )
1015 .await?;
1016
1017 let (status_code, _body) = api_v1_db_schema(
1018 Extension(agent.clone()),
1019 axum::Json(vec![klukai_tests::TEST_SCHEMA.into()]),
1020 )
1021 .await;
1022
1023 assert_eq!(status_code, StatusCode::OK);
1024
1025 let (status_code, body) = api_v1_transactions(
1026 Extension(agent.clone()),
1027 axum::extract::Query(TimeoutParams { timeout: None }),
1028 axum::Json(vec![
1029 Statement::WithParams(
1030 "insert into tests (id, text) values (?,?)".into(),
1031 vec![1i64.into(), "service-name".into()],
1032 ),
1033 Statement::WithParams(
1034 "insert into tests (id, text) values (?,?)".into(),
1035 vec![2i64.into(), "service-name-2".into()],
1036 ),
1037 ]),
1038 )
1039 .await;
1040
1041 assert_eq!(status_code, StatusCode::OK);
1042
1043 assert!(body.0.results.len() == 2);
1044
1045 let bcast_cache: SharedMatcherBroadcastCache = Default::default();
1046 let update_bcast_cache: SharedUpdateBroadcastCache = Default::default();
1047
1048 {
1049 let mut res = api_v1_subs(
1050 Extension(agent.clone()),
1051 Extension(bcast_cache.clone()),
1052 Extension(tripwire.clone()),
1053 axum::extract::Query(SubParams::default()),
1054 axum::Json(Statement::Simple("select * from tests".into())),
1055 )
1056 .await
1057 .into_response();
1058
1059 if !res.status().is_success() {
1060 let b = res.body_mut().collect().await.unwrap().to_bytes();
1061 println!("body: {}", String::from_utf8_lossy(&b));
1062 }
1063
1064 assert_eq!(res.status(), StatusCode::OK);
1065
1066 tokio::time::sleep(Duration::from_secs(1)).await;
1070
1071 let mut notify_res = api_v1_updates(
1072 Extension(agent.clone()),
1073 Extension(update_bcast_cache.clone()),
1074 Extension(tripwire.clone()),
1075 axum::extract::Path("tests".to_string()),
1076 )
1077 .await
1078 .into_response();
1079
1080 if !notify_res.status().is_success() {
1081 let b = notify_res.body_mut().collect().await.unwrap().to_bytes();
1082 println!("body: {}", String::from_utf8_lossy(&b));
1083 }
1084
1085 assert_eq!(notify_res.status(), StatusCode::OK);
1086
1087 let (status_code, _) = api_v1_transactions(
1088 Extension(agent.clone()),
1089 axum::extract::Query(TimeoutParams { timeout: None }),
1090 axum::Json(vec![Statement::WithParams(
1091 "insert into tests (id, text) values (?,?)".into(),
1092 vec![3i64.into(), "service-name-3".into()],
1093 )]),
1094 )
1095 .await;
1096
1097 assert_eq!(status_code, StatusCode::OK);
1098
1099 let mut rows = RowsIter {
1100 body: res.into_body(),
1101 codec: LinesCodec::new(),
1102 buf: BytesMut::new(),
1103 done: false,
1104 };
1105
1106 let mut notify_rows = RowsIter {
1107 body: notify_res.into_body(),
1108 codec: LinesCodec::new(),
1109 buf: BytesMut::new(),
1110 done: false,
1111 };
1112
1113 assert_eq!(
1114 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1115 QueryEvent::Columns(vec!["id".into(), "text".into()])
1116 );
1117
1118 assert_eq!(
1119 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1120 QueryEvent::Row(RowId(1), vec![1i64.into(), "service-name".into()])
1121 );
1122
1123 assert_eq!(
1124 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1125 QueryEvent::Row(RowId(2), vec![2i64.into(), "service-name-2".into()])
1126 );
1127
1128 let evt = rows.recv::<QueryEvent>().await.unwrap().unwrap();
1129 assert!(matches!(evt, QueryEvent::EndOfQuery { .. }));
1130
1131 assert_eq!(
1132 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1133 QueryEvent::Change(
1134 ChangeType::Insert,
1135 RowId(3),
1136 vec![3i64.into(), "service-name-3".into()],
1137 ChangeId(1)
1138 )
1139 );
1140
1141 let (status_code, _) = api_v1_transactions(
1142 Extension(agent.clone()),
1143 axum::extract::Query(TimeoutParams { timeout: None }),
1144 axum::Json(vec![Statement::WithParams(
1145 "insert into tests (id, text) values (?,?)".into(),
1146 vec![4i64.into(), "service-name-4".into()],
1147 )]),
1148 )
1149 .await;
1150
1151 assert_eq!(status_code, StatusCode::OK);
1152
1153 assert_eq!(
1154 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1155 QueryEvent::Change(
1156 ChangeType::Insert,
1157 RowId(4),
1158 vec![4i64.into(), "service-name-4".into()],
1159 ChangeId(2)
1160 )
1161 );
1162
1163 assert_eq!(
1164 notify_rows.recv::<NotifyEvent>().await.unwrap().unwrap(),
1165 NotifyEvent::Notify(ChangeType::Update, vec![3i64.into()],)
1166 );
1167
1168 assert_eq!(
1169 notify_rows.recv::<NotifyEvent>().await.unwrap().unwrap(),
1170 NotifyEvent::Notify(ChangeType::Update, vec![4i64.into()],)
1171 );
1172
1173 let mut res = api_v1_subs(
1174 Extension(agent.clone()),
1175 Extension(bcast_cache.clone()),
1176 Extension(tripwire.clone()),
1177 axum::extract::Query(SubParams {
1178 from: Some(1.into()),
1179 ..Default::default()
1180 }),
1181 axum::Json(Statement::Simple("select * from tests".into())),
1182 )
1183 .await
1184 .into_response();
1185
1186 if !res.status().is_success() {
1187 let b = res.body_mut().collect().await.unwrap().to_bytes();
1188 println!("body: {}", String::from_utf8_lossy(&b));
1189 }
1190
1191 assert_eq!(res.status(), StatusCode::OK);
1192
1193 let mut rows_from = RowsIter {
1194 body: res.into_body(),
1195 codec: LinesCodec::new(),
1196 buf: BytesMut::new(),
1197 done: false,
1198 };
1199
1200 assert_eq!(
1201 rows_from.recv::<QueryEvent>().await.unwrap().unwrap(),
1202 QueryEvent::Change(
1203 ChangeType::Insert,
1204 RowId(4),
1205 vec![4i64.into(), "service-name-4".into()],
1206 ChangeId(2)
1207 )
1208 );
1209
1210 let mut notify_res2 = api_v1_updates(
1212 Extension(agent.clone()),
1213 Extension(update_bcast_cache.clone()),
1214 Extension(tripwire.clone()),
1215 axum::extract::Path("tests".to_string()),
1216 )
1217 .await
1218 .into_response();
1219
1220 if !notify_res2.status().is_success() {
1221 let b = notify_res2.body_mut().collect().await.unwrap().to_bytes();
1222 println!("body: {}", String::from_utf8_lossy(&b));
1223 }
1224
1225 assert_eq!(notify_res2.status(), StatusCode::OK);
1226
1227 let mut notify_rows2 = RowsIter {
1228 body: notify_res2.into_body(),
1229 codec: LinesCodec::new(),
1230 buf: BytesMut::new(),
1231 done: false,
1232 };
1233
1234 let (status_code, _) = api_v1_transactions(
1235 Extension(agent.clone()),
1236 axum::extract::Query(TimeoutParams { timeout: None }),
1237 axum::Json(vec![Statement::WithParams(
1238 "insert into tests (id, text) values (?,?)".into(),
1239 vec![5i64.into(), "service-name-5".into()],
1240 )]),
1241 )
1242 .await;
1243
1244 assert_eq!(status_code, StatusCode::OK);
1245
1246 let query_evt = QueryEvent::Change(
1247 ChangeType::Insert,
1248 RowId(5),
1249 vec![5i64.into(), "service-name-5".into()],
1250 ChangeId(3),
1251 );
1252
1253 assert_eq!(rows.recv::<QueryEvent>().await.unwrap().unwrap(), query_evt);
1254
1255 assert_eq!(
1256 rows_from.recv::<QueryEvent>().await.unwrap().unwrap(),
1257 query_evt
1258 );
1259
1260 let notify_evt = NotifyEvent::Notify(ChangeType::Update, vec![5i64.into()]);
1261
1262 assert_eq!(
1263 notify_rows.recv::<NotifyEvent>().await.unwrap().unwrap(),
1264 notify_evt
1265 );
1266
1267 assert_eq!(
1268 notify_rows2.recv::<NotifyEvent>().await.unwrap().unwrap(),
1269 notify_evt
1270 );
1271
1272 let mut res = api_v1_subs(
1275 Extension(agent.clone()),
1276 Extension(bcast_cache.clone()),
1277 Extension(tripwire.clone()),
1278 axum::extract::Query(SubParams::default()),
1279 axum::Json(Statement::Simple("select * from tests".into())),
1280 )
1281 .await
1282 .into_response();
1283
1284 if !res.status().is_success() {
1285 let b = res.body_mut().collect().await.unwrap().to_bytes();
1286 println!("body: {}", String::from_utf8_lossy(&b));
1287 }
1288
1289 assert_eq!(res.status(), StatusCode::OK);
1290
1291 let mut rows = RowsIter {
1292 body: res.into_body(),
1293 codec: LinesCodec::new(),
1294 buf: BytesMut::new(),
1295 done: false,
1296 };
1297
1298 assert_eq!(
1299 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1300 QueryEvent::Columns(vec!["id".into(), "text".into()])
1301 );
1302
1303 assert_eq!(
1304 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1305 QueryEvent::Row(RowId(1), vec![1i64.into(), "service-name".into()])
1306 );
1307
1308 assert_eq!(
1309 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1310 QueryEvent::Row(RowId(2), vec![2i64.into(), "service-name-2".into()])
1311 );
1312
1313 assert_eq!(
1314 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1315 QueryEvent::Row(RowId(3), vec![3i64.into(), "service-name-3".into()],)
1316 );
1317
1318 assert_eq!(
1319 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1320 QueryEvent::Row(RowId(4), vec![4i64.into(), "service-name-4".into()],)
1321 );
1322
1323 assert_eq!(
1324 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1325 QueryEvent::Row(RowId(5), vec![5i64.into(), "service-name-5".into()])
1326 );
1327
1328 let (status_code, _) = api_v1_transactions(
1329 Extension(agent.clone()),
1330 axum::extract::Query(TimeoutParams { timeout: None }),
1331 axum::Json(vec![Statement::WithParams(
1332 "insert into tests (id, text) values (?,?)".into(),
1333 vec![6i64.into(), "service-name-6".into()],
1334 )]),
1335 )
1336 .await;
1337
1338 assert_eq!(status_code, StatusCode::OK);
1339
1340 let (status_code, _) = api_v1_transactions(
1341 Extension(agent.clone()),
1342 axum::extract::Query(TimeoutParams { timeout: None }),
1343 axum::Json(vec![Statement::WithParams(
1344 "delete from tests where id = ?".into(),
1345 vec![6i64.into()],
1346 )]),
1347 )
1348 .await;
1349
1350 assert_eq!(status_code, StatusCode::OK);
1351
1352 match notify_rows.recv::<NotifyEvent>().await.unwrap().unwrap() {
1355 NotifyEvent::Notify(ChangeType::Update, pk) => {
1356 assert_eq!(pk, vec![6i64.into()]);
1357 assert_eq!(
1358 notify_rows.recv::<NotifyEvent>().await.unwrap().unwrap(),
1359 NotifyEvent::Notify(ChangeType::Delete, vec![6i64.into()],)
1360 );
1361 }
1362 NotifyEvent::Notify(ChangeType::Delete, pk) => {
1363 assert_eq!(pk, vec![6i64.into()]);
1364 assert!(
1366 tokio::time::timeout(
1367 Duration::from_secs(2),
1368 notify_rows.recv::<NotifyEvent>()
1369 )
1370 .await
1371 .is_err()
1372 );
1373 }
1374 _ => panic!("expected notify event"),
1375 }
1376 }
1377
1378 let mut res = api_v1_subs(
1381 Extension(agent.clone()),
1382 Extension(bcast_cache.clone()),
1383 Extension(tripwire.clone()),
1384 axum::extract::Query(SubParams {
1385 from: Some(1.into()),
1386 ..Default::default()
1387 }),
1388 axum::Json(Statement::Simple("select * from tests".into())),
1389 )
1390 .await
1391 .into_response();
1392
1393 if !res.status().is_success() {
1394 let b = res.body_mut().collect().await.unwrap().to_bytes();
1395 println!("body: {}", String::from_utf8_lossy(&b));
1396 }
1397
1398 assert_eq!(res.status(), StatusCode::OK);
1399
1400 let mut rows_from = RowsIter {
1401 body: res.into_body(),
1402 codec: LinesCodec::new(),
1403 buf: BytesMut::new(),
1404 done: false,
1405 };
1406
1407 assert_eq!(
1408 rows_from.recv::<QueryEvent>().await.unwrap().unwrap(),
1409 QueryEvent::Change(
1410 ChangeType::Insert,
1411 RowId(4),
1412 vec![4i64.into(), "service-name-4".into()],
1413 ChangeId(2)
1414 )
1415 );
1416
1417 assert_eq!(
1418 rows_from.recv::<QueryEvent>().await.unwrap().unwrap(),
1419 QueryEvent::Change(
1420 ChangeType::Insert,
1421 RowId(5),
1422 vec![5i64.into(), "service-name-5".into()],
1423 ChangeId(3),
1424 )
1425 );
1426
1427 let mut res = api_v1_subs(
1429 Extension(agent.clone()),
1430 Extension(bcast_cache.clone()),
1431 Extension(tripwire.clone()),
1432 axum::extract::Query(SubParams {
1433 from: Some(0.into()),
1434 ..Default::default()
1435 }),
1436 axum::Json(Statement::Simple("select * from tests".into())),
1437 )
1438 .await
1439 .into_response();
1440
1441 if !res.status().is_success() {
1442 let b = res.body_mut().collect().await.unwrap().to_bytes();
1443 println!("body: {}", String::from_utf8_lossy(&b));
1444 }
1445
1446 assert_eq!(res.status(), StatusCode::OK);
1447
1448 let mut rows_zero = RowsIter {
1449 body: res.into_body(),
1450 codec: LinesCodec::new(),
1451 buf: BytesMut::new(),
1452 done: false,
1453 };
1454
1455 assert_eq!(
1456 rows_zero.recv::<QueryEvent>().await.unwrap().unwrap(),
1457 QueryEvent::Columns(vec!["id".into(), "text".into()])
1458 );
1459
1460 assert_eq!(
1461 rows_zero.recv::<QueryEvent>().await.unwrap().unwrap(),
1462 QueryEvent::Row(RowId(1), vec![1i64.into(), "service-name".into()])
1463 );
1464
1465 assert_eq!(
1466 rows_zero.recv::<QueryEvent>().await.unwrap().unwrap(),
1467 QueryEvent::Row(RowId(2), vec![2i64.into(), "service-name-2".into()])
1468 );
1469
1470 assert_eq!(
1471 rows_zero.recv::<QueryEvent>().await.unwrap().unwrap(),
1472 QueryEvent::Row(RowId(3), vec![3i64.into(), "service-name-3".into()],)
1473 );
1474
1475 assert_eq!(
1476 rows_zero.recv::<QueryEvent>().await.unwrap().unwrap(),
1477 QueryEvent::Row(RowId(4), vec![4i64.into(), "service-name-4".into()])
1478 );
1479
1480 assert_eq!(
1481 rows_zero.recv::<QueryEvent>().await.unwrap().unwrap(),
1482 QueryEvent::Row(RowId(5), vec![5i64.into(), "service-name-5".into()])
1483 );
1484
1485 assert_eq!(
1486 rows_zero
1487 .recv::<QueryEvent>()
1488 .await
1489 .unwrap()
1490 .unwrap()
1491 .meta(),
1492 QueryEventMeta::EndOfQuery(Some(ChangeId(3)))
1493 );
1494
1495 let mut res = api_v1_subs(
1497 Extension(agent.clone()),
1498 Extension(bcast_cache.clone()),
1499 Extension(tripwire.clone()),
1500 axum::extract::Query(SubParams {
1501 skip_rows: true,
1502 ..Default::default()
1503 }),
1504 axum::Json(Statement::Simple("select * from tests".into())),
1505 )
1506 .await
1507 .into_response();
1508
1509 if !res.status().is_success() {
1510 let b = res.body_mut().collect().await.unwrap().to_bytes();
1511 println!("body: {}", String::from_utf8_lossy(&b));
1512 }
1513
1514 assert_eq!(res.status(), StatusCode::OK);
1515
1516 let mut rows_from = RowsIter {
1517 body: res.into_body(),
1518 codec: LinesCodec::new(),
1519 buf: BytesMut::new(),
1520 done: false,
1521 };
1522
1523 let (status_code, _) = api_v1_transactions(
1524 Extension(agent.clone()),
1525 axum::extract::Query(TimeoutParams { timeout: None }),
1526 axum::Json(vec![Statement::WithParams(
1527 "insert into tests (id, text) values (?,?)".into(),
1528 vec![6i64.into(), "service-name-6".into()],
1529 )]),
1530 )
1531 .await;
1532
1533 assert_eq!(status_code, StatusCode::OK);
1534
1535 assert_eq!(
1536 rows_from.recv::<QueryEvent>().await.unwrap().unwrap(),
1537 QueryEvent::Change(
1538 ChangeType::Insert,
1539 RowId(6),
1540 vec![6i64.into(), "service-name-6".into()],
1541 ChangeId(4),
1542 )
1543 );
1544
1545 let mut res = api_v1_subs(
1548 Extension(agent.clone()),
1549 Extension(bcast_cache.clone()),
1550 Extension(tripwire.clone()),
1551 axum::extract::Query(SubParams {
1552 skip_rows: true,
1553 from: Some(ChangeId(3)),
1554 }),
1555 axum::Json(Statement::Simple("select * from tests".into())),
1556 )
1557 .await
1558 .into_response();
1559
1560 if !res.status().is_success() {
1561 let b = res.body_mut().collect().await.unwrap().to_bytes();
1562 println!("body: {}", String::from_utf8_lossy(&b));
1563 }
1564
1565 assert_eq!(res.status(), StatusCode::OK);
1566
1567 let mut rows_from = RowsIter {
1568 body: res.into_body(),
1569 codec: LinesCodec::new(),
1570 buf: BytesMut::new(),
1571 done: false,
1572 };
1573
1574 assert_eq!(
1575 tokio::time::timeout(Duration::from_secs(5), rows_from.recv::<QueryEvent>())
1576 .await
1577 .unwrap()
1578 .unwrap()
1579 .unwrap(),
1580 QueryEvent::Change(
1581 ChangeType::Insert,
1582 RowId(6),
1583 vec![6i64.into(), "service-name-6".into()],
1584 ChangeId(4),
1585 )
1586 );
1587
1588 Ok(())
1589 }
1590
1591 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
1592 async fn match_buffered_changes() -> eyre::Result<()> {
1593 _ = tracing_subscriber::fmt::try_init();
1594
1595 let (tripwire, tripwire_worker, tripwire_tx) = Tripwire::new_simple();
1596
1597 let ta1 = launch_test_agent(|conf| conf.build(), tripwire.clone()).await?;
1598 let tx_timeout = Duration::from_secs(60);
1599
1600 let schema = "CREATE TABLE buftests (
1601 pk int NOT NULL PRIMARY KEY,
1602 col1 text,
1603 col2 text
1604 );";
1605
1606 let (status_code, _body) = api_v1_db_schema(
1607 Extension(ta1.agent.clone()),
1608 axum::Json(vec![schema.into()]),
1609 )
1610 .await;
1611 assert_eq!(status_code, StatusCode::OK);
1612
1613 let actor_id = ActorId(uuid::Uuid::new_v4());
1614
1615 let change1 = Change {
1616 table: TableName("buftests".into()),
1617 pk: pack_columns(&[1i64.into()])?,
1618 cid: ColumnName("col1".into()),
1619 val: "one".into(),
1620 col_version: 1,
1621 db_version: CrsqlDbVersion(1),
1622 seq: CrsqlSeq(0),
1623 site_id: actor_id.to_bytes(),
1624 cl: 1,
1625 };
1626
1627 let change2 = Change {
1628 table: TableName("buftests".into()),
1629 pk: pack_columns(&[1i64.into()])?,
1630 cid: ColumnName("col2".into()),
1631 val: "one line".into(),
1632 col_version: 1,
1633 db_version: CrsqlDbVersion(1),
1634 seq: CrsqlSeq(1),
1635 site_id: actor_id.to_bytes(),
1636 cl: 1,
1637 };
1638
1639 let changes = ChangeV1 {
1640 actor_id,
1641 changeset: Changeset::Full {
1642 version: CrsqlDbVersion(1),
1643 changes: vec![change1, change2],
1644 seqs: dbsr!(0, 1),
1645 last_seq: CrsqlSeq(1),
1646 ts: Default::default(),
1647 },
1648 };
1649
1650 process_multiple_changes(
1651 ta1.agent.clone(),
1652 ta1.bookie.clone(),
1653 vec![(changes, ChangeSource::Sync, Instant::now())],
1654 tx_timeout,
1655 )
1656 .await?;
1657
1658 let bcast_cache: SharedMatcherBroadcastCache = Default::default();
1659 let update_bcast_cache: SharedUpdateBroadcastCache = Default::default();
1660 let mut res = api_v1_subs(
1661 Extension(ta1.agent.clone()),
1662 Extension(bcast_cache.clone()),
1663 Extension(tripwire.clone()),
1664 axum::extract::Query(SubParams::default()),
1665 axum::Json(Statement::Simple("select * from buftests".into())),
1666 )
1667 .await
1668 .into_response();
1669
1670 if !res.status().is_success() {
1671 let b = res.body_mut().collect().await.unwrap().to_bytes();
1672 println!("body: {}", String::from_utf8_lossy(&b));
1673 }
1674
1675 assert_eq!(res.status(), StatusCode::OK);
1676
1677 let mut notify_res = api_v1_updates(
1679 Extension(ta1.agent.clone()),
1680 Extension(update_bcast_cache.clone()),
1681 Extension(tripwire.clone()),
1682 axum::extract::Path("buftests".to_string()),
1683 )
1684 .await
1685 .into_response();
1686
1687 if !notify_res.status().is_success() {
1688 let b = notify_res.body_mut().collect().await.unwrap().to_bytes();
1689 println!("body: {}", String::from_utf8_lossy(&b));
1690 }
1691
1692 assert_eq!(notify_res.status(), StatusCode::OK);
1693
1694 let mut notify_rows = RowsIter {
1695 body: notify_res.into_body(),
1696 codec: LinesCodec::new(),
1697 buf: BytesMut::new(),
1698 done: false,
1699 };
1700
1701 let mut rows = RowsIter {
1702 body: res.into_body(),
1703 codec: LinesCodec::new(),
1704 buf: BytesMut::new(),
1705 done: false,
1706 };
1707
1708 assert_eq!(
1709 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1710 QueryEvent::Columns(vec!["pk".into(), "col1".into(), "col2".into()])
1711 );
1712
1713 assert_eq!(
1714 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1715 QueryEvent::Row(RowId(1), vec![Integer(1), "one".into(), "one line".into()])
1716 );
1717
1718 assert!(matches!(
1719 rows.recv::<QueryEvent>().await.unwrap().unwrap(),
1720 QueryEvent::EndOfQuery { .. }
1721 ));
1722
1723 let change3 = Change {
1725 table: TableName("buftests".into()),
1726 pk: pack_columns(&[2i64.into()])?,
1727 cid: ColumnName("col1".into()),
1728 val: "two".into(),
1729 col_version: 1,
1730 db_version: CrsqlDbVersion(2),
1731 seq: CrsqlSeq(0),
1732 site_id: actor_id.to_bytes(),
1733 cl: 1,
1734 };
1735
1736 let changes = ChangeV1 {
1737 actor_id,
1738 changeset: Changeset::Full {
1739 version: CrsqlDbVersion(2),
1740 changes: vec![change3],
1741 seqs: dbsr!(0, 0),
1742 last_seq: CrsqlSeq(1),
1743 ts: Default::default(),
1744 },
1745 };
1746
1747 process_multiple_changes(
1748 ta1.agent.clone(),
1749 ta1.bookie.clone(),
1750 vec![(changes, ChangeSource::Sync, Instant::now())],
1751 tx_timeout,
1752 )
1753 .await?;
1754
1755 {
1757 let conn = ta1.agent.pool().read().await?;
1758 let end = conn.query_row(
1759 "SELECT end_seq FROM __corro_seq_bookkeeping WHERE site_id = ? AND db_version = ?",
1760 (actor_id, 2),
1761 |row| row.get::<_, CrsqlSeq>(0),
1762 )?;
1763 assert_eq!(end, CrsqlSeq(0));
1764 }
1765
1766 let change4 = Change {
1767 table: TableName("buftests".into()),
1768 pk: pack_columns(&[2i64.into()])?,
1769 cid: ColumnName("col2".into()),
1770 val: "two line".into(),
1771 col_version: 1,
1772 db_version: CrsqlDbVersion(2),
1773 seq: CrsqlSeq(1),
1774 site_id: actor_id.to_bytes(),
1775 cl: 1,
1776 };
1777
1778 let changes = ChangeV1 {
1779 actor_id,
1780 changeset: Changeset::Full {
1781 version: CrsqlDbVersion(2),
1782 changes: vec![change4],
1783 seqs: dbsr!(1, 1),
1784 last_seq: CrsqlSeq(1),
1785 ts: Default::default(),
1786 },
1787 };
1788
1789 process_multiple_changes(
1790 ta1.agent.clone(),
1791 ta1.bookie.clone(),
1792 vec![(changes, ChangeSource::Sync, Instant::now())],
1793 tx_timeout,
1794 )
1795 .await?;
1796
1797 let res = timeout(Duration::from_secs(5), rows.recv::<QueryEvent>()).await?;
1798
1799 assert_eq!(
1800 res.unwrap().unwrap(),
1801 QueryEvent::Change(
1802 ChangeType::Insert,
1803 RowId(2),
1804 vec![Integer(2), "two".into(), "two line".into()],
1805 ChangeId(1)
1806 )
1807 );
1808
1809 let notify_res = timeout(Duration::from_secs(5), notify_rows.recv::<NotifyEvent>()).await?;
1810 assert_eq!(
1811 notify_res.unwrap().unwrap(),
1812 NotifyEvent::Notify(ChangeType::Update, vec![Integer(2)],)
1813 );
1814
1815 tripwire_tx.send(()).await.ok();
1816 tripwire_worker.await;
1817 ta1.agent.subs_manager().drop_handles().await;
1818 wait_for_all_pending_handles().await;
1819
1820 Ok(())
1821 }
1822
1823 struct RowsIter {
1824 body: axum::body::Body,
1825 codec: LinesCodec,
1826 buf: BytesMut,
1827 done: bool,
1828 }
1829
1830 impl RowsIter {
1831 async fn recv<T: DeserializeOwned>(&mut self) -> Option<eyre::Result<T>> {
1832 if self.done {
1833 return None;
1834 }
1835 loop {
1836 match self.codec.decode(&mut self.buf) {
1837 Ok(Some(line)) => match serde_json::from_str(&line) {
1838 Ok(res) => return Some(Ok(res)),
1839 Err(e) => {
1840 self.done = true;
1841 return Some(Err(e.into()));
1842 }
1843 },
1844 Ok(None) => {
1845 }
1847 Err(e) => {
1848 self.done = true;
1849 return Some(Err(e.into()));
1850 }
1851 }
1852
1853 let frame_res = self.body.frame().await;
1854 match frame_res {
1855 Some(Ok(frame)) => {
1856 if let Some(data) = frame.data_ref() {
1857 self.buf.extend_from_slice(data);
1859 }
1860 }
1862 Some(Err(e)) => {
1863 self.done = true;
1864 return Some(Err(e.into()));
1865 }
1866 None => {
1867 self.done = true;
1868 return None;
1869 }
1870 }
1871 }
1872 }
1873 }
1874}