1use std::collections::{HashMap, VecDeque};
2use std::sync::Arc;
3use std::time::Duration;
4
5use chrono::{DateTime, Utc};
6use rusqlite::types::Value;
7use tokio::sync::Mutex;
8use tokio::sync::Notify;
9use tokio::sync::mpsc;
10
11use eventuary_core::io::cursor::{CursorOrder, JsonCursorCodec};
12use eventuary_core::io::filter::{EventFilter, NamespacePattern, TopicPattern};
13use eventuary_core::io::stream::SpawnedStream;
14use eventuary_core::io::{Acker, Cursor, Filter, Message, Reader};
15use eventuary_core::partition::{PartitionGroup, PartitionSelection};
16use eventuary_core::{
17 Error, Partition, PartitionableSubscription, Result, SerializedEvent, SerializedPayload,
18 StartFrom, StartableSubscription, StopAt,
19};
20
21use crate::database::SqliteConn;
22use crate::event_log::{SqliteEventLogSchema, SqliteEventLogSchemaConfig};
23use crate::relation::SqliteRelationName;
24
25#[derive(
26 Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize,
27)]
28#[serde(transparent)]
29pub struct SqliteCursor {
30 pub sequence: i64,
31}
32
33impl SqliteCursor {
34 pub fn new(sequence: i64) -> Self {
35 Self { sequence }
36 }
37
38 pub fn sequence(&self) -> i64 {
39 self.sequence
40 }
41}
42
43impl Cursor for SqliteCursor {
44 fn order_key(&self) -> CursorOrder {
45 CursorOrder::from_i64(self.sequence)
46 }
47}
48
49impl SqliteCursor {
50 pub fn codec() -> Result<JsonCursorCodec<Self>> {
51 JsonCursorCodec::new("eventuary.sqlite.sqlite_cursor.v1")
52 }
53}
54
55#[derive(Debug, Clone)]
56pub struct SqliteSubscription {
57 pub start: StartFrom<SqliteCursor>,
58 pub stop_at: StopAt<SqliteCursor>,
59 pub filter: EventFilter,
60 pub partitions: PartitionSelection,
61 pub batch_size: Option<usize>,
62 pub limit: Option<usize>,
63}
64
65impl Default for SqliteSubscription {
66 fn default() -> Self {
67 Self {
68 start: StartFrom::Latest,
69 stop_at: StopAt::Never,
70 filter: EventFilter::default(),
71 batch_size: None,
72 limit: None,
73 partitions: PartitionSelection::default(),
74 }
75 }
76}
77
78impl StartableSubscription<SqliteCursor> for SqliteSubscription {
79 fn with_start(mut self, start: StartFrom<SqliteCursor>) -> Self {
80 self.start = start;
81 self
82 }
83}
84
85impl PartitionableSubscription<SqliteCursor> for SqliteSubscription {
86 fn with_partition(mut self, partition: Partition) -> Self {
87 self.partitions = PartitionSelection::One(partition);
88 self
89 }
90}
91
92impl SqliteSubscription {
93 pub fn with_partitions(mut self, group: PartitionGroup) -> Self {
98 self.partitions = PartitionSelection::Many(group);
99 self
100 }
101}
102
103#[derive(Debug, Clone)]
104pub struct SqliteReaderConfig {
105 pub events_relation: SqliteRelationName,
106 pub poll_interval: Duration,
107 pub default_batch_size: usize,
108}
109
110impl Default for SqliteReaderConfig {
111 fn default() -> Self {
112 Self {
113 events_relation: SqliteRelationName::new("events").expect("default events relation"),
114 poll_interval: Duration::from_millis(100),
115 default_batch_size: 100,
116 }
117 }
118}
119
120#[derive(Clone)]
121pub struct SqliteCursorAcker {
122 state: Arc<Mutex<CursorState>>,
123 notify: Arc<Notify>,
124 sequence: i64,
125}
126
127struct CursorState {
128 last_acked: i64,
129 pending_nack: bool,
130}
131
132impl Acker for SqliteCursorAcker {
133 async fn ack(&self) -> Result<()> {
134 let mut state = self.state.lock().await;
135 if self.sequence > state.last_acked {
136 state.last_acked = self.sequence;
137 }
138 state.pending_nack = false;
139 self.notify.notify_waiters();
140 Ok(())
141 }
142
143 async fn nack(&self) -> Result<()> {
144 let mut state = self.state.lock().await;
145 state.pending_nack = true;
146 self.notify.notify_waiters();
147 Ok(())
148 }
149}
150
151pub struct SqliteReader {
152 conn: SqliteConn,
153 config: SqliteReaderConfig,
154}
155
156impl Clone for SqliteReader {
157 fn clone(&self) -> Self {
158 Self {
159 conn: Arc::clone(&self.conn),
160 config: self.config.clone(),
161 }
162 }
163}
164
165impl SqliteReader {
166 pub fn connect(conn: SqliteConn, config: SqliteReaderConfig) -> Result<Self> {
167 Self::prepare_schema(&conn, &config)?;
168 Ok(Self::new(conn, config))
169 }
170
171 pub fn prepare_schema(conn: &SqliteConn, config: &SqliteReaderConfig) -> Result<()> {
172 let guard = conn.lock().map_err(|e| Error::Store(e.to_string()))?;
173 SqliteEventLogSchema::prepare(
174 &guard,
175 &SqliteEventLogSchemaConfig {
176 events_relation: config.events_relation.clone(),
177 },
178 )
179 }
180
181 pub fn schema_sql(config: &SqliteReaderConfig) -> String {
182 SqliteEventLogSchema::schema_sql(&SqliteEventLogSchemaConfig {
183 events_relation: config.events_relation.clone(),
184 })
185 }
186
187 pub fn new(conn: SqliteConn, config: SqliteReaderConfig) -> Self {
188 Self { conn, config }
189 }
190}
191
192impl Reader for SqliteReader {
193 type Subscription = SqliteSubscription;
194 type Acker = SqliteCursorAcker;
195 type Cursor = SqliteCursor;
196 type Stream = SpawnedStream<SqliteCursorAcker, SqliteCursor>;
197
198 async fn read(&self, subscription: Self::Subscription) -> Result<Self::Stream> {
199 let conn = Arc::clone(&self.conn);
200 let events_relation = self.config.events_relation.render();
201 let poll_interval = self.config.poll_interval;
202 let batch_size = subscription
203 .batch_size
204 .unwrap_or(self.config.default_batch_size)
205 .clamp(1, 1000);
206 let filter = subscription.filter.clone();
207 let limit = subscription.limit;
208 let partitions = subscription.partitions.clone();
209 let (tx, rx) = mpsc::channel(64);
210
211 let (mut after_seq, lower_bound_ts) =
212 match resolve_initial_position(&conn, &events_relation, &subscription).await {
213 Ok(pos) => pos,
214 Err(e) => {
215 let _ = tx.send(Err(e)).await;
216 return Ok(SpawnedStream::from_receiver(rx));
217 }
218 };
219
220 let stop_seq = match resolve_stop_position(&conn, &events_relation, &subscription).await {
221 Ok(pos) => pos,
222 Err(e) => {
223 let _ = tx.send(Err(e)).await;
224 return Ok(SpawnedStream::from_receiver(rx));
225 }
226 };
227
228 let state = Arc::new(Mutex::new(CursorState {
229 last_acked: after_seq,
230 pending_nack: false,
231 }));
232 let notify = Arc::new(Notify::new());
233
234 let handle = tokio::spawn(async move {
235 let mut delivered = 0usize;
236 let mut buffer: VecDeque<(SerializedEvent, i64)> = VecDeque::new();
237 loop {
238 if buffer.is_empty() {
239 let fetched = match fetch_batch(
240 &conn,
241 FetchBatchParams {
242 events_relation: &events_relation,
243 after_seq,
244 stop_seq,
245 take: batch_size,
246 lower_bound_ts,
247 filter: &filter,
248 partitions: &partitions,
249 },
250 )
251 .await
252 {
253 Ok(b) => b,
254 Err(e) => {
255 let _ = tx.send(Err(e)).await;
256 return;
257 }
258 };
259 if fetched.is_empty() {
260 if stop_seq.is_some() {
261 return;
262 }
263 tokio::time::sleep(poll_interval).await;
264 continue;
265 }
266 buffer.extend(fetched);
267 }
268
269 while let Some((serialized, sequence)) = buffer.front() {
270 let sequence = *sequence;
271 let event = match serialized.to_event() {
272 Ok(e) => e,
273 Err(e) => {
274 let _ = tx
275 .send(Err(Error::Serialization(format!(
276 "decode event at sequence {sequence}: {e}"
277 ))))
278 .await;
279 return;
280 }
281 };
282 if !filter.matches(&event) {
283 buffer.pop_front();
284 after_seq = sequence;
285 continue;
286 }
287 if let Some(l) = limit
288 && delivered >= l
289 {
290 return;
291 }
292 let acker = SqliteCursorAcker {
293 state: Arc::clone(&state),
294 notify: Arc::clone(¬ify),
295 sequence,
296 };
297 let cursor = SqliteCursor { sequence };
298 if tx
299 .send(Ok(Message::new(event, acker, cursor)))
300 .await
301 .is_err()
302 {
303 return;
304 }
305 delivered += 1;
306
307 loop {
308 {
309 let guard = state.lock().await;
310 if guard.last_acked >= sequence {
311 after_seq = sequence;
312 buffer.pop_front();
313 break;
314 }
315 if guard.pending_nack {
316 break;
317 }
318 if tx.is_closed() {
319 return;
320 }
321 }
322 notify.notified().await;
323 }
324 }
325 }
326 });
327
328 Ok(SpawnedStream::new(rx, handle))
329 }
330}
331
332async fn resolve_initial_position(
333 conn: &SqliteConn,
334 events_relation: &str,
335 subscription: &SqliteSubscription,
336) -> Result<(i64, Option<DateTime<Utc>>)> {
337 match subscription.start.clone() {
338 StartFrom::After(cursor) => Ok((cursor.sequence, None)),
339 StartFrom::Earliest => Ok((0, None)),
340 StartFrom::Latest => {
341 let conn = Arc::clone(conn);
342 let org = subscription
343 .filter
344 .organization
345 .as_ref()
346 .map(|o| o.as_str().to_owned());
347 let relation = events_relation.to_owned();
348 tokio::task::spawn_blocking(move || {
349 let guard = conn.lock().map_err(|e| Error::Store(e.to_string()))?;
350 let seq: i64 = match org {
351 Some(o) => guard
352 .query_row(
353 &format!(
354 "SELECT COALESCE(MAX(sequence), 0) FROM {relation} WHERE organization = ?1"
355 ),
356 rusqlite::params![o],
357 |r| r.get(0),
358 )
359 .map_err(|e| Error::Store(e.to_string()))?,
360 None => guard
361 .query_row(
362 &format!("SELECT COALESCE(MAX(sequence), 0) FROM {relation}"),
363 [],
364 |r| r.get(0),
365 )
366 .map_err(|e| Error::Store(e.to_string()))?,
367 };
368 Ok::<(i64, Option<DateTime<Utc>>), Error>((seq, None))
369 })
370 .await
371 .map_err(|e| Error::Store(format!("blocking task panicked: {e}")))?
372 }
373 StartFrom::Timestamp(ts) => {
374 let conn = Arc::clone(conn);
375 let org = subscription
376 .filter
377 .organization
378 .as_ref()
379 .map(|o| o.as_str().to_owned());
380 let ts_str = ts.to_rfc3339();
381 let relation = events_relation.to_owned();
382 tokio::task::spawn_blocking(move || {
383 let guard = conn.lock().map_err(|e| Error::Store(e.to_string()))?;
384 let seq: i64 = match org {
385 Some(o) => guard
386 .query_row(
387 &format!(
388 "SELECT COALESCE(MIN(sequence), 1) - 1 FROM {relation} \
389 WHERE organization = ?1 AND timestamp >= ?2"
390 ),
391 rusqlite::params![o, ts_str],
392 |r| r.get(0),
393 )
394 .map_err(|e| Error::Store(e.to_string()))?,
395 None => guard
396 .query_row(
397 &format!(
398 "SELECT COALESCE(MIN(sequence), 1) - 1 FROM {relation} \
399 WHERE timestamp >= ?1"
400 ),
401 rusqlite::params![ts_str],
402 |r| r.get(0),
403 )
404 .map_err(|e| Error::Store(e.to_string()))?,
405 };
406 Ok::<(i64, Option<DateTime<Utc>>), Error>((seq.max(0), Some(ts)))
407 })
408 .await
409 .map_err(|e| Error::Store(format!("blocking task panicked: {e}")))?
410 }
411 }
412}
413
414async fn resolve_stop_position(
415 conn: &SqliteConn,
416 events_relation: &str,
417 subscription: &SqliteSubscription,
418) -> Result<Option<i64>> {
419 match subscription.stop_at {
420 StopAt::Never => Ok(None),
421 StopAt::Cursor(cursor) => Ok(Some(cursor.sequence)),
422 StopAt::CurrentEnd => {
423 let conn = Arc::clone(conn);
424 let org = subscription
425 .filter
426 .organization
427 .as_ref()
428 .map(|o| o.as_str().to_owned());
429 let relation = events_relation.to_owned();
430 tokio::task::spawn_blocking(move || {
431 let guard = conn.lock().map_err(|e| Error::Store(e.to_string()))?;
432 let seq: i64 = match org {
433 Some(o) => guard
434 .query_row(
435 &format!(
436 "SELECT COALESCE(MAX(sequence), 0) FROM {relation} WHERE organization = ?1"
437 ),
438 rusqlite::params![o],
439 |r| r.get(0),
440 )
441 .map_err(|e| Error::Store(e.to_string()))?,
442 None => guard
443 .query_row(
444 &format!("SELECT COALESCE(MAX(sequence), 0) FROM {relation}"),
445 [],
446 |r| r.get(0),
447 )
448 .map_err(|e| Error::Store(e.to_string()))?,
449 };
450 Ok::<Option<i64>, Error>(Some(seq))
451 })
452 .await
453 .map_err(|e| Error::Store(format!("blocking task panicked: {e}")))?
454 }
455 }
456}
457
458struct FetchBatchParams<'a> {
459 events_relation: &'a str,
460 after_seq: i64,
461 stop_seq: Option<i64>,
462 take: usize,
463 lower_bound_ts: Option<DateTime<Utc>>,
464 filter: &'a EventFilter,
465 partitions: &'a PartitionSelection,
466}
467
468async fn fetch_batch(
469 conn: &SqliteConn,
470 p: FetchBatchParams<'_>,
471) -> Result<Vec<(SerializedEvent, i64)>> {
472 let FetchBatchParams {
473 events_relation,
474 after_seq,
475 stop_seq,
476 take,
477 lower_bound_ts,
478 filter,
479 partitions,
480 } = p;
481 let conn = Arc::clone(conn);
482 let relation = events_relation.to_owned();
483 let org = filter.organization.as_ref().map(|o| o.as_str().to_owned());
484 let exact_topic: Option<String> = filter.topic.as_ref().map(|p| match p {
485 TopicPattern::Exact(t) => t.as_str().to_owned(),
486 });
487 let ns_prefix = filter.namespace.as_ref().and_then(|p| match p {
488 NamespacePattern::Prefix(ns) if !ns.is_root() => Some(ns.as_str().to_owned()),
489 _ => None,
490 });
491 let ts_str = lower_bound_ts.map(|t| t.to_rfc3339());
492 let partitions = partitions.clone();
493
494 tokio::task::spawn_blocking(move || {
495 let guard = conn.lock().map_err(|e| Error::Store(e.to_string()))?;
496
497 let mut sql = format!(
498 "SELECT sequence, id, organization, namespace, topic, event_key, payload, content_type, metadata, \
499 timestamp, version, parent_id, correlation_id, causation_id \
500 FROM {relation} WHERE sequence > ?1"
501 );
502 let mut params: Vec<Value> = vec![Value::Integer(after_seq)];
503 let mut idx = 2usize;
504
505 if let Some(stop) = stop_seq {
506 sql.push_str(&format!(" AND sequence <= ?{idx}"));
507 params.push(Value::Integer(stop));
508 idx += 1;
509 }
510
511 if let Some(o) = &org {
512 sql.push_str(&format!(" AND organization = ?{idx}"));
513 params.push(Value::Text(o.clone()));
514 idx += 1;
515 }
516 if let Some(t) = &exact_topic {
517 sql.push_str(&format!(" AND topic = ?{idx}"));
518 params.push(Value::Text(t.clone()));
519 idx += 1;
520 }
521 if let Some(prefix) = &ns_prefix {
522 sql.push_str(&format!(
523 " AND (namespace = ?{idx} OR namespace LIKE ?{} || '/%')",
524 idx
525 ));
526 params.push(Value::Text(prefix.clone()));
527 idx += 1;
528 }
529 if let Some(ts) = &ts_str {
530 sql.push_str(&format!(" AND timestamp >= ?{idx}"));
531 params.push(Value::Text(ts.clone()));
532 idx += 1;
533 }
534 match &partitions {
535 PartitionSelection::All => {}
536 PartitionSelection::One(partition) => {
537 sql.push_str(&format!(" AND partition_count = ?{idx}"));
538 params.push(Value::Integer(partition.count() as i64));
539 idx += 1;
540 sql.push_str(&format!(" AND partition_id = ?{idx}"));
541 params.push(Value::Integer(partition.id() as i64));
542 idx += 1;
543 }
544 PartitionSelection::Many(group) => {
545 sql.push_str(&format!(" AND partition_count = ?{idx}"));
546 params.push(Value::Integer(group.count() as i64));
547 idx += 1;
548 let placeholders: Vec<String> = (0..group.partitions().len())
549 .map(|i| format!("?{}", idx + i))
550 .collect();
551 sql.push_str(&format!(
552 " AND partition_id IN ({})",
553 placeholders.join(",")
554 ));
555 for partition in group.partitions() {
556 params.push(Value::Integer(partition.id() as i64));
557 }
558 idx += group.partitions().len();
559 }
560 }
561 sql.push_str(&format!(" ORDER BY sequence ASC LIMIT ?{idx}"));
562 params.push(Value::Integer(take as i64));
563
564 let mut stmt = guard
565 .prepare(&sql)
566 .map_err(|e| Error::Store(e.to_string()))?;
567 let rows = stmt
568 .query_map(rusqlite::params_from_iter(params.iter()), |row| {
569 let sequence: i64 = row.get(0)?;
570 let id: String = row.get(1)?;
571 let organization: String = row.get(2)?;
572 let namespace: String = row.get(3)?;
573 let topic: String = row.get(4)?;
574 let key: String = row.get(5)?;
575 let payload_str: String = row.get(6)?;
576 let content_type: String = row.get(7)?;
577 let metadata_str: String = row.get(8)?;
578 let timestamp_str: String = row.get(9)?;
579 let version: i64 = row.get(10)?;
580 let parent_id: Option<String> = row.get(11)?;
581 let correlation_id: Option<String> = row.get(12)?;
582 let causation_id: Option<String> = row.get(13)?;
583 Ok((
584 sequence,
585 id,
586 organization,
587 namespace,
588 topic,
589 key,
590 payload_str,
591 content_type,
592 metadata_str,
593 timestamp_str,
594 version,
595 parent_id,
596 correlation_id,
597 causation_id,
598 ))
599 })
600 .map_err(|e| Error::Store(e.to_string()))?;
601
602 let mut out = Vec::new();
603 for row in rows {
604 let (
605 sequence,
606 id,
607 organization,
608 namespace,
609 topic,
610 key,
611 payload_str,
612 content_type,
613 metadata_str,
614 timestamp_str,
615 version,
616 parent_id,
617 correlation_id,
618 causation_id,
619 ) = row.map_err(|e| Error::Store(e.to_string()))?;
620
621 let payload: SerializedPayload = serde_json::from_str(&payload_str)
622 .map_err(|e| Error::Serialization(format!("decode payload: {e}")))?;
623 let _ = content_type;
624 let id = uuid::Uuid::parse_str(&id)
625 .map_err(|e| Error::Serialization(format!("decode id: {e}")))?;
626 let parent_id = parent_id
627 .as_deref()
628 .map(uuid::Uuid::parse_str)
629 .transpose()
630 .map_err(|e| Error::Serialization(format!("decode parent_id: {e}")))?;
631 let metadata: HashMap<String, String> = serde_json::from_str(&metadata_str)
632 .map_err(|e| Error::Serialization(format!("decode metadata: {e}")))?;
633 let timestamp = DateTime::parse_from_rfc3339(×tamp_str)
634 .map(|d| d.with_timezone(&Utc))
635 .map_err(|e| Error::Serialization(format!("decode timestamp: {e}")))?;
636 out.push((
637 SerializedEvent {
638 id,
639 organization,
640 namespace,
641 topic,
642 payload,
643 metadata,
644 timestamp,
645 version: version as u64,
646 key,
647 parent_id,
648 correlation_id,
649 causation_id,
650 },
651 sequence,
652 ));
653 }
654 Ok(out)
655 })
656 .await
657 .map_err(|e| Error::Store(format!("blocking task panicked: {e}")))?
658}
659
660#[cfg(test)]
661mod tests {
662 use std::collections::HashMap;
663 use std::num::NonZeroU16;
664 use std::time::Duration;
665
666 use futures::StreamExt;
667 use tokio::time::timeout;
668
669 use super::*;
670 use crate::database::SqliteDatabase;
671 use crate::writer::{SqlitePartitioningConfig, SqliteWriter, SqliteWriterConfig};
672 use eventuary_core::io::cursor::{CursorCodec, CursorOrder};
673 use eventuary_core::io::{Cursor, CursorId, Reader, Writer};
674 use eventuary_core::partition::{
675 EventKeyPartitionKeyResolver, Fnv1a64PartitionHasher, Partition, PartitionGroup,
676 PartitionHasher, PartitionKey,
677 };
678 use eventuary_core::{Event, PartitionableSubscription, Payload, StartFrom, StopAt};
679
680 #[test]
681 fn sqlite_subscription_with_partition_sets_partition_selection_one() {
682 let count = NonZeroU16::new(8).unwrap();
683 let partition = Partition::new(3, count).unwrap();
684 let sub = SqliteSubscription::default().with_partition(partition);
685 match sub.partitions {
686 PartitionSelection::One(p) => {
687 assert_eq!(p.id(), 3);
688 assert_eq!(p.count(), 8);
689 }
690 _ => panic!("expected PartitionSelection::One"),
691 }
692 }
693
694 #[test]
695 fn sqlite_subscription_with_partitions_sets_partition_selection_many() {
696 let count = NonZeroU16::new(8).unwrap();
697 let group = PartitionGroup::new(vec![
698 Partition::new(2, count).unwrap(),
699 Partition::new(5, count).unwrap(),
700 ])
701 .unwrap();
702 let sub = SqliteSubscription::default().with_partitions(group);
703 match sub.partitions {
704 PartitionSelection::Many(g) => {
705 assert_eq!(g.len(), 2);
706 let ids: Vec<u16> = g.partitions().iter().map(|p| p.id()).collect();
707 assert_eq!(ids, vec![2, 5]);
708 }
709 _ => panic!("expected PartitionSelection::Many"),
710 }
711 }
712
713 #[test]
714 fn sqlite_cursor_id_is_global() {
715 assert_eq!(SqliteCursor::new(42).id(), CursorId::global());
716 }
717
718 #[test]
719 fn sqlite_cursor_order_key_from_sequence() {
720 assert_eq!(SqliteCursor::new(42).order_key(), CursorOrder::from_i64(42));
721 assert!(SqliteCursor::new(9).order_key() < SqliteCursor::new(10).order_key());
722 }
723
724 #[test]
725 fn sqlite_cursor_codec_roundtrips() {
726 let codec = SqliteCursor::codec().unwrap();
727 let cursor = SqliteCursor::new(42);
728 let encoded = codec.encode(&cursor).unwrap();
729 assert_eq!(encoded.kind().as_str(), "eventuary.sqlite.sqlite_cursor.v1");
730 assert_eq!(encoded.order(), &CursorOrder::from_i64(42));
731 assert_eq!(codec.decode(&encoded).unwrap(), cursor);
732 }
733
734 #[test]
735 fn sqlite_cursor_codec_preserves_typed_ord() {
736 let codec = SqliteCursor::codec().unwrap();
737 let lo = codec.encode(&SqliteCursor::new(9)).unwrap();
738 let hi = codec.encode(&SqliteCursor::new(10)).unwrap();
739 assert!(lo < hi);
740 }
741
742 const PARTITION_COUNT: u16 = 4;
743
744 fn event_with_key(key: &str) -> Event {
745 Event::builder(
746 "acme",
747 "/orders",
748 "order.placed",
749 key,
750 Payload::from_string("{}"),
751 )
752 .unwrap()
753 .build()
754 .unwrap()
755 }
756
757 fn partition_for_key(key: &str) -> u16 {
758 let k = PartitionKey::new(key).unwrap();
759 let hash = Fnv1a64PartitionHasher.hash(&k);
760 (hash.get() % PARTITION_COUNT as u64) as u16
761 }
762
763 fn fast_config() -> SqliteReaderConfig {
764 SqliteReaderConfig {
765 poll_interval: Duration::from_millis(10),
766 ..SqliteReaderConfig::default()
767 }
768 }
769
770 #[tokio::test]
771 async fn reader_default_all_returns_every_event() {
772 let db = SqliteDatabase::open_in_memory().unwrap();
773 let config = SqliteWriterConfig {
774 partitioning: SqlitePartitioningConfig::inline(
775 NonZeroU16::new(PARTITION_COUNT).unwrap(),
776 EventKeyPartitionKeyResolver::new(),
777 Fnv1a64PartitionHasher,
778 ),
779 ..SqliteWriterConfig::default()
780 };
781 SqliteWriter::prepare_schema(&db.conn(), &config).unwrap();
782 let writer = SqliteWriter::new_with_config(db.conn(), config);
783
784 let keys = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"];
785 for key in &keys {
786 writer.write(&event_with_key(key)).await.unwrap();
787 }
788
789 let reader = SqliteReader::new(db.conn(), fast_config());
790 let subscription = SqliteSubscription {
791 start: StartFrom::Earliest,
792 stop_at: StopAt::CurrentEnd,
793 ..SqliteSubscription::default()
794 };
795 let mut stream = reader.read(subscription).await.unwrap();
796
797 let mut count = 0usize;
798 while let Ok(Some(Ok(msg))) = timeout(Duration::from_secs(5), stream.next()).await {
799 msg.acker().ack().await.unwrap();
800 count += 1;
801 }
802
803 assert_eq!(count, keys.len());
804 }
805
806 #[tokio::test]
807 async fn reader_one_filters_to_single_partition() {
808 let db = SqliteDatabase::open_in_memory().unwrap();
809 let config = SqliteWriterConfig {
810 partitioning: SqlitePartitioningConfig::inline(
811 NonZeroU16::new(PARTITION_COUNT).unwrap(),
812 EventKeyPartitionKeyResolver::new(),
813 Fnv1a64PartitionHasher,
814 ),
815 ..SqliteWriterConfig::default()
816 };
817 SqliteWriter::prepare_schema(&db.conn(), &config).unwrap();
818 let writer = SqliteWriter::new_with_config(db.conn(), config);
819
820 let keys = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"];
821 for key in &keys {
822 writer.write(&event_with_key(key)).await.unwrap();
823 }
824
825 let partitions_by_id: HashMap<u16, Vec<&str>> =
826 keys.iter().fold(HashMap::new(), |mut acc, key| {
827 acc.entry(partition_for_key(key)).or_default().push(key);
828 acc
829 });
830
831 let (chosen_partition, expected_keys) = partitions_by_id
832 .iter()
833 .find(|(_, ks)| ks.len() >= 2)
834 .map(|(id, ks)| (*id, ks.clone()))
835 .expect("expected at least one partition with >=2 events");
836
837 let reader = SqliteReader::new(db.conn(), fast_config());
838 let subscription = SqliteSubscription {
839 start: StartFrom::Earliest,
840 stop_at: StopAt::CurrentEnd,
841 partitions: PartitionSelection::One(
842 Partition::new(chosen_partition, NonZeroU16::new(PARTITION_COUNT).unwrap())
843 .unwrap(),
844 ),
845 ..SqliteSubscription::default()
846 };
847 let mut stream = reader.read(subscription).await.unwrap();
848
849 let mut received_keys: Vec<String> = Vec::new();
850 while let Ok(Some(Ok(msg))) = timeout(Duration::from_secs(5), stream.next()).await {
851 let key = msg.event().key().as_str().to_owned();
852 msg.acker().ack().await.unwrap();
853 received_keys.push(key);
854 }
855
856 assert_eq!(received_keys.len(), expected_keys.len());
857 for key in &received_keys {
858 assert_eq!(
859 partition_for_key(key),
860 chosen_partition,
861 "event key {key} maps to wrong partition"
862 );
863 }
864 }
865
866 #[tokio::test]
867 async fn reader_many_filters_to_selected_partitions() {
868 let db = SqliteDatabase::open_in_memory().unwrap();
869 let config = SqliteWriterConfig {
870 partitioning: SqlitePartitioningConfig::inline(
871 NonZeroU16::new(PARTITION_COUNT).unwrap(),
872 EventKeyPartitionKeyResolver::new(),
873 Fnv1a64PartitionHasher,
874 ),
875 ..SqliteWriterConfig::default()
876 };
877 SqliteWriter::prepare_schema(&db.conn(), &config).unwrap();
878 let writer = SqliteWriter::new_with_config(db.conn(), config);
879
880 let keys = ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7"];
881 for key in &keys {
882 writer.write(&event_with_key(key)).await.unwrap();
883 }
884
885 let count = NonZeroU16::new(PARTITION_COUNT).unwrap();
886 let mut populated: Vec<u16> = keys
887 .iter()
888 .map(|k| partition_for_key(k))
889 .collect::<std::collections::BTreeSet<_>>()
890 .into_iter()
891 .collect();
892 populated.truncate(2);
893 assert!(
894 populated.len() >= 2,
895 "fixture must populate at least 2 distinct partitions"
896 );
897
898 let selected: std::collections::HashSet<u16> = populated.iter().copied().collect();
899 let expected_len = keys
900 .iter()
901 .copied()
902 .filter(|k| selected.contains(&partition_for_key(k)))
903 .count();
904
905 let group = PartitionGroup::new(
906 populated
907 .iter()
908 .map(|id| Partition::new(*id, count).unwrap())
909 .collect(),
910 )
911 .unwrap();
912
913 let reader = SqliteReader::new(db.conn(), fast_config());
914 let subscription = SqliteSubscription {
915 start: StartFrom::Earliest,
916 stop_at: StopAt::CurrentEnd,
917 ..SqliteSubscription::default()
918 }
919 .with_partitions(group);
920 let mut stream = reader.read(subscription).await.unwrap();
921
922 let mut received: Vec<String> = Vec::new();
923 while let Ok(Some(Ok(msg))) = timeout(Duration::from_secs(5), stream.next()).await {
924 let key = msg.event().key().as_str().to_owned();
925 msg.acker().ack().await.unwrap();
926 received.push(key);
927 }
928
929 assert_eq!(received.len(), expected_len);
930 for key in &received {
931 assert!(
932 selected.contains(&partition_for_key(key)),
933 "event key {key} maps to partition outside selected group"
934 );
935 }
936 }
937}