Skip to main content

klukai_agent/api/public/
pubsub.rs

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            // ensure this goes!
77            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                // Create error stream for NOT_FOUND response
96                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    // Create a channel for streaming
124    let (stream_tx, stream_rx) =
125        tokio::sync::mpsc::channel::<Result<hyper::body::Frame<Bytes>, std::io::Error>>(512);
126
127    // Create a streaming body from the receiver
128    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        // NOTE: I think that's infaillible...
152        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);
161// this should be a fraction of the MAX_UNSUB_TIME
162const 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    // even if there are no more subscribers
179    // useful for queries that don't change often so we can cleanup...
180    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                // reset the deadline if there are receivers!
201                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    // remove and get handle from the agent's "matchers"
243    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    // clean up the subscription
255    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        // NOTE: I think that's infaillible...
372        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    // buffer events while we catch up...
460    let (queue_tx, mut queue_rx) = mpsc::channel(10240);
461
462    let cancel = CancellationToken::new();
463    // just in case...
464    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                // abnormal
547                _ = 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                // missed some updates!
565                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            // sleep 100 millis
584            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 queue task!
616    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            // Create error stream
710            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            // Create error stream
745            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            // do nothing
881        }
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        // For Hyper 1.x, we can't convert io::Error to hyper::Error easily
896        // Just return unit type error or use a different error handling approach
897        warn!(sub_id = %sub_id, "stream channel closed, stopping event handling");
898        return Ok(()); // Continue gracefully instead of erroring
899    }
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            // only want notifications
1067            // small sleep here to make sure `broadcast_changes` has already run
1068            // for earlier transactions
1069            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            // new subscriber for updates
1211            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            // subscriber who arrives later!
1273
1274            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            // when we make changes to the same primary key in quick succession,
1353            // the newer event might get sent first (but in that case, the older one should be dropped)
1354            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                    // check that we dont get an update after
1365                    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        // previous subs have been dropped.
1379
1380        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        // change id 0 should start subs afresh
1428        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        // skip rows!
1496        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        // skip rows AND from
1546
1547        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        // only notifications
1678        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        // send partial change so it is buffered
1724        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        // confirm that change is buffered in db
1756        {
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                        // fall through
1846                    }
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                            // debug!("read {} bytes", data.len());
1858                            self.buf.extend_from_slice(data);
1859                        }
1860                        // If it's not a data frame, just continue the loop
1861                    }
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}