Skip to main content

common_event_recorder/
recorder.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::collections::{HashMap, HashSet};
17use std::fmt::Debug;
18use std::sync::Arc;
19use std::time::Duration;
20
21use api::v1::value::ValueData;
22use api::v1::{ColumnSchema, Row, RowInsertRequest, RowInsertRequests, Rows};
23use async_trait::async_trait;
24use backon::{BackoffBuilder, ExponentialBuilder};
25use common_telemetry::{debug, error, info, warn};
26use common_time::timestamp::{TimeUnit, Timestamp};
27use humantime::format_duration;
28use itertools::Itertools;
29use serde::{Deserialize, Serialize};
30use store_api::mito_engine_options::{APPEND_MODE_KEY, TTL_KEY};
31use tokio::sync::mpsc::{Receiver, Sender, channel};
32use tokio::task::JoinHandle;
33use tokio::time::sleep;
34use tokio_util::sync::CancellationToken;
35
36use crate::error::{MismatchedSchemaSnafu, Result};
37use crate::event_table::{
38    PAYLOAD_COLUMN, TIMESTAMP_COLUMN, TYPE_COLUMN, base_column_schemas, jsonb_value,
39};
40
41/// The default table name for storing the events.
42pub const DEFAULT_EVENTS_TABLE_NAME: &str = "events";
43
44/// The column name for the event type.
45pub const EVENTS_TABLE_TYPE_COLUMN_NAME: &str = TYPE_COLUMN.name();
46/// The column name for the event payload.
47pub const EVENTS_TABLE_PAYLOAD_COLUMN_NAME: &str = PAYLOAD_COLUMN.name();
48/// The column name for the event timestamp.
49pub const EVENTS_TABLE_TIMESTAMP_COLUMN_NAME: &str = TIMESTAMP_COLUMN.name();
50
51/// EventRecorderRef is the reference to the event recorder.
52pub type EventRecorderRef = Arc<dyn EventRecorder>;
53
54/// A shared event-type filter used by event producers and recorders.
55pub type EventTypeFilterRef = Arc<EventTypeFilter>;
56
57/// Restricts the event types that are recorded.
58#[derive(Debug, Clone, PartialEq, Eq, Default)]
59pub enum EventTypeFilter {
60    /// Records all current and future event types.
61    #[default]
62    All,
63    /// Records only the event types in the set.
64    Only(HashSet<String>),
65}
66
67impl EventTypeFilter {
68    /// Returns whether the filter retains `event_type`.
69    pub fn allows(&self, event_type: &str) -> bool {
70        match self {
71            Self::All => true,
72            Self::Only(event_types) => event_types.contains(event_type),
73        }
74    }
75}
76
77fn deserialize_event_types<'de, D>(
78    deserializer: D,
79) -> std::result::Result<EventTypeFilterRef, D::Error>
80where
81    D: serde::Deserializer<'de>,
82{
83    HashSet::<String>::deserialize(deserializer)
84        .map(|event_types| Arc::new(EventTypeFilter::Only(event_types)))
85}
86
87fn serialize_event_types<S>(
88    event_types: &EventTypeFilterRef,
89    serializer: S,
90) -> std::result::Result<S::Ok, S::Error>
91where
92    S: serde::Serializer,
93{
94    match event_types.as_ref() {
95        EventTypeFilter::All => serializer.serialize_none(),
96        EventTypeFilter::Only(event_types) => {
97            let mut event_types = event_types.iter().collect::<Vec<_>>();
98            event_types.sort_unstable();
99            event_types.serialize(serializer)
100        }
101    }
102}
103
104fn event_type_filter_is_all(event_types: &EventTypeFilterRef) -> bool {
105    matches!(event_types.as_ref(), EventTypeFilter::All)
106}
107
108/// The time interval for flushing batched events to the event handler.
109pub const DEFAULT_FLUSH_INTERVAL_SECONDS: Duration = Duration::from_secs(5);
110/// The default TTL(90 days) for the events table.
111const DEFAULT_EVENTS_TABLE_TTL: Duration = Duration::from_secs(90 * 86400);
112/// The default compaction time window for the events table.
113pub const DEFAULT_COMPACTION_TIME_WINDOW: Duration = Duration::from_secs(86400);
114// The capacity of the tokio channel for transmitting events to background processor.
115const DEFAULT_CHANNEL_SIZE: usize = 2048;
116// The size of the buffer for batching events before flushing to event handler.
117const DEFAULT_BUFFER_SIZE: usize = 100;
118// The maximum number of retry attempts when event handler processing fails.
119const DEFAULT_MAX_RETRY_TIMES: u64 = 3;
120
121/// Event trait defines the interface for events that can be recorded and persisted as the system table.
122/// By default, the event will be persisted as the system table with the following schema:
123///
124/// - `type`: the type of the event.
125/// - `payload`: the JSON bytes of the event.
126/// - `timestamp`: the timestamp of the event.
127///
128/// The event can also add the extra schema and row to the event by overriding the `extra_schema` and `extra_row` methods.
129pub trait Event: Send + Sync + Debug {
130    /// Returns the table name of the event.
131    fn table_name(&self) -> &str {
132        DEFAULT_EVENTS_TABLE_NAME
133    }
134
135    /// Returns the type of the event.
136    fn event_type(&self) -> &str;
137
138    /// Returns the timestamp of the event. Default to the current time.
139    fn timestamp(&self) -> Timestamp {
140        Timestamp::current_time(TimeUnit::Nanosecond)
141    }
142
143    /// Returns the event payload as a structured JSON value. It will be encoded as JSONB when stored.
144    fn json_payload(&self) -> Result<serde_json::Value> {
145        Ok(serde_json::Value::Null)
146    }
147
148    /// Add the extra schema to the event with the default schema.
149    fn extra_schema(&self) -> Vec<ColumnSchema> {
150        vec![]
151    }
152
153    /// Add the extra rows to the event with the default row.
154    fn extra_rows(&self) -> Result<Vec<Row>> {
155        Ok(vec![Row { values: vec![] }])
156    }
157
158    /// Returns the event as any type.
159    fn as_any(&self) -> &dyn Any;
160}
161
162/// Eventable trait defines the interface for objects that can be converted to [Event].
163pub trait Eventable: Send + Sync + Debug {
164    /// Converts the object to an [Event].
165    fn to_event(&self) -> Option<Box<dyn Event>> {
166        None
167    }
168}
169
170/// Groups events by its `event_type`.
171#[allow(clippy::borrowed_box)]
172pub fn group_events_by_type(events: &[Box<dyn Event>]) -> HashMap<&str, Vec<&Box<dyn Event>>> {
173    events
174        .iter()
175        .into_grouping_map_by(|event| event.event_type())
176        .collect()
177}
178
179/// Builds the row inserts request for the events that will be persisted to the events table. The `events` should have the same event type, or it will return an error.
180#[allow(clippy::borrowed_box)]
181pub fn build_row_inserts_request(events: &[&Box<dyn Event>]) -> Result<RowInsertRequests> {
182    // Ensure all the events are the same type.
183    validate_events(events)?;
184
185    // We already validated the events, so it's safe to get the first event to build the schema for the RowInsertRequest.
186    let event = &events[0];
187    let extra_schema = event.extra_schema();
188    let mut schema: Vec<ColumnSchema> = Vec::with_capacity(3 + extra_schema.len());
189    schema.extend(base_column_schemas());
190    schema.extend(extra_schema);
191
192    let mut rows: Vec<Row> = Vec::with_capacity(events.len());
193    for event in events {
194        let extra_rows = event.extra_rows()?;
195        for extra_row in extra_rows {
196            let mut values = Vec::with_capacity(3 + extra_row.values.len());
197            values.extend([
198                ValueData::StringValue(event.event_type().to_string()).into(),
199                jsonb_value(&event.json_payload()?),
200                ValueData::TimestampNanosecondValue(event.timestamp().value()).into(),
201            ]);
202            values.extend(extra_row.values);
203            rows.push(Row { values });
204        }
205    }
206
207    Ok(RowInsertRequests {
208        inserts: vec![RowInsertRequest {
209            table_name: event.table_name().to_string(),
210            rows: Some(Rows { schema, rows }),
211        }],
212    })
213}
214
215// Ensure the events with the same event type have the same extra schema.
216#[allow(clippy::borrowed_box)]
217fn validate_events(events: &[&Box<dyn Event>]) -> Result<()> {
218    // It's safe to get the first event because the events are already grouped by the event type.
219    let extra_schema = events[0].extra_schema();
220    for event in events {
221        if event.extra_schema() != extra_schema {
222            MismatchedSchemaSnafu {
223                expected: extra_schema.clone(),
224                actual: event.extra_schema(),
225            }
226            .fail()?;
227        }
228    }
229    Ok(())
230}
231
232/// EventRecorder trait defines the interface for recording events.
233pub trait EventRecorder: Send + Sync + Debug + 'static {
234    /// Records an event for persistence and processing by [EventHandler].
235    fn record(&self, event: Box<dyn Event>);
236
237    /// Returns the event types accepted by this recorder.
238    fn event_type_filter(&self) -> EventTypeFilterRef;
239
240    /// Cancels the event recorder.
241    fn close(&self);
242}
243
244/// EventHandlerOptions is the options for the event handler.
245#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
246pub struct EventHandlerOptions {
247    /// TTL for the events table that will be used to store the events.
248    pub ttl: Duration,
249    /// Append mode for the events table that will be used to store the events.
250    pub append_mode: bool,
251}
252
253impl Default for EventHandlerOptions {
254    fn default() -> Self {
255        Self {
256            ttl: DEFAULT_EVENTS_TABLE_TTL,
257            append_mode: true,
258        }
259    }
260}
261
262impl EventHandlerOptions {
263    /// Converts the options to the hints for the insert operation.
264    pub fn to_hints(&self) -> Vec<(&str, String)> {
265        vec![
266            (TTL_KEY, format_duration(self.ttl).to_string()),
267            (APPEND_MODE_KEY, self.append_mode.to_string()),
268        ]
269    }
270}
271
272/// EventHandler trait defines the interface for how to handle the event.
273#[async_trait]
274pub trait EventHandler: Send + Sync + 'static {
275    /// Processes and handles incoming events. The [DefaultEventHandlerImpl] implementation forwards events to frontend instances for persistence.
276    /// We use `&[Box<dyn Event>]` to avoid consuming the events, so the caller can buffer the events and retry if the handler fails.
277    async fn handle(&self, events: &[Box<dyn Event>]) -> Result<()>;
278}
279
280/// Configuration options for the event recorder.
281#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
282pub struct EventRecorderOptions {
283    /// TTL for the events table that will be used to store the events.
284    #[serde(default = "default_events_table_ttl", with = "humantime_serde")]
285    pub ttl: Duration,
286    /// Event types that the recorder retains. When omitted, all event types are retained.
287    #[serde(
288        default,
289        deserialize_with = "deserialize_event_types",
290        serialize_with = "serialize_event_types",
291        skip_serializing_if = "event_type_filter_is_all"
292    )]
293    pub event_types: EventTypeFilterRef,
294    /// How long buffered events may wait before reaching the event handler.
295    ///
296    /// Not a configuration option: it is skipped by serde so it never appears in
297    /// config files or `config/config.md`. Integration tests shorten it so they
298    /// do not pay a flush window per DDL they assert on.
299    #[serde(skip, default = "default_flush_interval")]
300    pub flush_interval: Duration,
301}
302
303fn default_events_table_ttl() -> Duration {
304    DEFAULT_EVENTS_TABLE_TTL
305}
306
307fn default_flush_interval() -> Duration {
308    DEFAULT_FLUSH_INTERVAL_SECONDS
309}
310
311impl Default for EventRecorderOptions {
312    fn default() -> Self {
313        Self {
314            ttl: DEFAULT_EVENTS_TABLE_TTL,
315            event_types: Arc::new(EventTypeFilter::All),
316            flush_interval: DEFAULT_FLUSH_INTERVAL_SECONDS,
317        }
318    }
319}
320
321/// Implementation of [EventRecorder] that records the events and processes them in the background by the [EventHandler].
322#[derive(Debug)]
323pub struct EventRecorderImpl {
324    // The channel to send the events to the background processor.
325    tx: Sender<Box<dyn Event>>,
326    // The event types this recorder accepts before sending to the background processor.
327    event_types: EventTypeFilterRef,
328    // The cancel token to cancel the background processor.
329    cancel_token: CancellationToken,
330    // The background processor to process the events.
331    handle: Option<JoinHandle<()>>,
332}
333
334impl EventRecorderImpl {
335    pub fn new(event_handler: Box<dyn EventHandler>) -> Self {
336        Self::with_event_type_filter(
337            event_handler,
338            Arc::new(EventTypeFilter::All),
339            DEFAULT_FLUSH_INTERVAL_SECONDS,
340        )
341    }
342
343    /// Creates an event recorder with an event-type filter.
344    pub fn with_event_type_filter(
345        event_handler: Box<dyn EventHandler>,
346        event_types: EventTypeFilterRef,
347        flush_interval: Duration,
348    ) -> Self {
349        let (tx, rx) = channel(DEFAULT_CHANNEL_SIZE);
350        let cancel_token = CancellationToken::new();
351
352        let mut recorder = Self {
353            tx,
354            event_types,
355            handle: None,
356            cancel_token: cancel_token.clone(),
357        };
358
359        let processor =
360            EventProcessor::new(rx, event_handler, flush_interval, DEFAULT_MAX_RETRY_TIMES)
361                .with_cancel_token(cancel_token);
362
363        // Spawn a background task to process the events.
364        let handle = tokio::spawn(async move {
365            processor.process(DEFAULT_BUFFER_SIZE).await;
366        });
367
368        recorder.handle = Some(handle);
369
370        recorder
371    }
372}
373
374impl EventRecorder for EventRecorderImpl {
375    // Accepts an event and send it to the background handler.
376    fn record(&self, event: Box<dyn Event>) {
377        if !self.event_types.allows(event.event_type()) {
378            return;
379        }
380
381        if let Err(e) = self.tx.try_send(event) {
382            error!("Failed to send event to the background processor: {}", e);
383        }
384    }
385
386    fn event_type_filter(&self) -> EventTypeFilterRef {
387        self.event_types.clone()
388    }
389
390    // Closes the event recorder. It will stop the background processor and flush the buffer.
391    fn close(&self) {
392        self.cancel_token.cancel();
393    }
394}
395
396impl Drop for EventRecorderImpl {
397    fn drop(&mut self) {
398        if let Some(handle) = self.handle.take() {
399            handle.abort();
400            info!("Aborted the background processor in event recorder");
401        }
402    }
403}
404
405struct EventProcessor {
406    rx: Receiver<Box<dyn Event>>,
407    event_handler: Box<dyn EventHandler>,
408    max_retry_times: u64,
409    process_interval: Duration,
410    cancel_token: CancellationToken,
411}
412
413impl EventProcessor {
414    fn new(
415        rx: Receiver<Box<dyn Event>>,
416        event_handler: Box<dyn EventHandler>,
417        process_interval: Duration,
418        max_retry_times: u64,
419    ) -> Self {
420        Self {
421            rx,
422            event_handler,
423            max_retry_times,
424            process_interval,
425            cancel_token: CancellationToken::new(),
426        }
427    }
428
429    fn with_cancel_token(mut self, cancel_token: CancellationToken) -> Self {
430        self.cancel_token = cancel_token;
431        self
432    }
433
434    async fn process(mut self, buffer_size: usize) {
435        info!("Start the background processor in event recorder to handle the received events.");
436
437        let mut buffer = Vec::with_capacity(buffer_size);
438        let mut interval = tokio::time::interval(self.process_interval);
439
440        loop {
441            tokio::select! {
442                maybe_event = self.rx.recv() => {
443                    if let Some(maybe_event) = maybe_event {
444                        debug!("Received event: {:?}", maybe_event);
445
446                        if buffer.len() >= buffer_size {
447                            debug!(
448                                "Flushing events to the event handler because the buffer is full with {} events",
449                                buffer.len()
450                            );
451                            self.flush_events_to_handler(&mut buffer).await;
452                        }
453
454                        // Push the event to the buffer, the buffer will be flushed when the interval is triggered or received a closed signal.
455                        buffer.push(maybe_event);
456                    } else {
457                        // When received a closed signal, flush the buffer and exit the loop.
458                        self.flush_events_to_handler(&mut buffer).await;
459                        break;
460                    }
461                }
462                // Cancel the processor through the cancel token.
463                _ = self.cancel_token.cancelled() => {
464                    warn!("Received a cancel signal, flushing the buffer and exiting the loop");
465                    self.flush_events_to_handler(&mut buffer).await;
466                    break;
467                }
468                // When the interval is triggered, flush the buffer and send the events to the event handler.
469                _ = interval.tick() => {
470                    self.flush_events_to_handler(&mut buffer).await;
471                }
472            }
473        }
474    }
475
476    // NOTE: While we implement a retry mechanism for failed event handling, there is no guarantee that all events will be processed successfully.
477    async fn flush_events_to_handler(&self, buffer: &mut Vec<Box<dyn Event>>) {
478        if !buffer.is_empty() {
479            debug!("Flushing {} events to the event handler", buffer.len());
480
481            let mut backoff = ExponentialBuilder::default()
482                .with_min_delay(Duration::from_millis(
483                    DEFAULT_FLUSH_INTERVAL_SECONDS.as_millis() as u64 / self.max_retry_times.max(1),
484                ))
485                .with_max_delay(Duration::from_millis(
486                    DEFAULT_FLUSH_INTERVAL_SECONDS.as_millis() as u64,
487                ))
488                .with_max_times(self.max_retry_times as usize)
489                .build();
490
491            loop {
492                match self.event_handler.handle(buffer).await {
493                    Ok(()) => {
494                        debug!("Successfully handled {} events", buffer.len());
495                        break;
496                    }
497                    Err(e) => {
498                        if let Some(d) = backoff.next() {
499                            warn!(e; "Failed to handle events, retrying...");
500                            sleep(d).await;
501                            continue;
502                        } else {
503                            warn!(
504                                e; "Failed to handle events after {} retries",
505                                self.max_retry_times
506                            );
507                            break;
508                        }
509                    }
510                }
511            }
512        }
513
514        // Clear the buffer to prevent unbounded memory growth, regardless of whether event processing succeeded or failed.
515        buffer.clear();
516    }
517}
518
519#[cfg(test)]
520mod tests {
521    use std::collections::HashSet;
522    use std::sync::atomic::{AtomicUsize, Ordering};
523
524    use serde_json::json;
525
526    use super::*;
527
528    #[derive(Debug)]
529    struct TestEvent {}
530
531    impl Event for TestEvent {
532        fn event_type(&self) -> &str {
533            "test_event"
534        }
535
536        fn json_payload(&self) -> Result<serde_json::Value> {
537            Ok(json!({"procedure_id": "1234567890"}))
538        }
539
540        fn as_any(&self) -> &dyn Any {
541            self
542        }
543    }
544
545    struct TestEventHandlerImpl {}
546
547    #[async_trait]
548    impl EventHandler for TestEventHandlerImpl {
549        async fn handle(&self, events: &[Box<dyn Event>]) -> Result<()> {
550            let event = events
551                .first()
552                .unwrap()
553                .as_any()
554                .downcast_ref::<TestEvent>()
555                .unwrap();
556            assert_eq!(
557                event.json_payload().unwrap(),
558                json!({"procedure_id": "1234567890"}),
559            );
560            assert_eq!(event.event_type(), "test_event");
561            Ok(())
562        }
563    }
564
565    #[test]
566    fn test_event_type_filter_defaults_to_all() {
567        let options = toml::from_str::<EventRecorderOptions>("ttl = '90d'").unwrap();
568
569        assert!(options.event_types.allows("slow_query"));
570        assert!(options.event_types.allows("future_event"));
571    }
572
573    #[test]
574    fn test_event_type_filter_deserializes_explicit_empty_array() {
575        let options =
576            toml::from_str::<EventRecorderOptions>("ttl = '90d'\nevent_types = []").unwrap();
577
578        assert_eq!(
579            options.event_types.as_ref(),
580            &EventTypeFilter::Only(HashSet::new())
581        );
582    }
583
584    #[test]
585    fn test_event_recorder_options_default_ttl_for_partial_table() {
586        let options = toml::from_str::<EventRecorderOptions>("event_types = []").unwrap();
587
588        assert_eq!(DEFAULT_EVENTS_TABLE_TTL, options.ttl);
589        assert_eq!(
590            options.event_types.as_ref(),
591            &EventTypeFilter::Only(HashSet::new())
592        );
593    }
594
595    #[test]
596    fn test_event_type_filter_deserializes_selected_types() {
597        let options = toml::from_str::<EventRecorderOptions>(
598            "ttl = '90d'\nevent_types = ['create_database']",
599        )
600        .unwrap();
601
602        assert!(options.event_types.allows("create_database"));
603        assert!(!options.event_types.allows("drop_database"));
604    }
605
606    struct CountingEventHandler {
607        count: Arc<AtomicUsize>,
608    }
609
610    #[async_trait]
611    impl EventHandler for CountingEventHandler {
612        async fn handle(&self, _events: &[Box<dyn Event>]) -> Result<()> {
613            self.count.fetch_add(1, Ordering::Relaxed);
614            Ok(())
615        }
616    }
617
618    #[tokio::test]
619    async fn test_event_recorder_rejects_filtered_event_before_queueing() {
620        let count = Arc::new(AtomicUsize::new(0));
621        let event_type_filter = Arc::new(EventTypeFilter::Only(HashSet::new()));
622        let mut event_recorder = EventRecorderImpl::with_event_type_filter(
623            Box::new(CountingEventHandler {
624                count: count.clone(),
625            }),
626            event_type_filter.clone(),
627            DEFAULT_FLUSH_INTERVAL_SECONDS,
628        );
629
630        assert!(Arc::ptr_eq(
631            &event_type_filter,
632            &event_recorder.event_type_filter()
633        ));
634
635        event_recorder.record(Box::new(TestEvent {}));
636        event_recorder.close();
637
638        if let Some(handle) = event_recorder.handle.take() {
639            assert!(handle.await.is_ok());
640        }
641        assert_eq!(count.load(Ordering::Relaxed), 0);
642    }
643
644    #[tokio::test]
645    async fn test_event_recorder() {
646        let mut event_recorder = EventRecorderImpl::new(Box::new(TestEventHandlerImpl {}));
647        event_recorder.record(Box::new(TestEvent {}));
648
649        // Sleep for a while to let the event be sent to the event handler.
650        sleep(Duration::from_millis(500)).await;
651
652        // Close the event recorder to flush the buffer.
653        event_recorder.close();
654
655        // Sleep for a while to let the background task process the event.
656        sleep(Duration::from_millis(500)).await;
657
658        if let Some(handle) = event_recorder.handle.take() {
659            assert!(handle.await.is_ok());
660        }
661    }
662
663    struct TestEventHandlerImplShouldPanic {}
664
665    #[async_trait]
666    impl EventHandler for TestEventHandlerImplShouldPanic {
667        async fn handle(&self, events: &[Box<dyn Event>]) -> Result<()> {
668            let event = events
669                .first()
670                .unwrap()
671                .as_any()
672                .downcast_ref::<TestEvent>()
673                .unwrap();
674
675            // Set the incorrect payload and event type to trigger the panic.
676            assert_eq!(
677                event.json_payload().unwrap(),
678                "{\"procedure_id\": \"should_panic\"}"
679            );
680            assert_eq!(event.event_type(), "should_panic");
681            Ok(())
682        }
683    }
684
685    #[tokio::test]
686    async fn test_event_recorder_should_panic() {
687        let mut event_recorder =
688            EventRecorderImpl::new(Box::new(TestEventHandlerImplShouldPanic {}));
689
690        event_recorder.record(Box::new(TestEvent {}));
691
692        // Sleep for a while to let the event be sent to the event handler.
693        sleep(Duration::from_millis(500)).await;
694
695        // Close the event recorder to flush the buffer.
696        event_recorder.close();
697
698        // Sleep for a while to let the background task process the event.
699        sleep(Duration::from_millis(500)).await;
700
701        if let Some(handle) = event_recorder.handle.take() {
702            assert!(handle.await.unwrap_err().is_panic());
703        }
704    }
705
706    #[derive(Debug)]
707    struct TestEventA {}
708
709    impl Event for TestEventA {
710        fn event_type(&self) -> &str {
711            "A"
712        }
713
714        fn as_any(&self) -> &dyn Any {
715            self
716        }
717    }
718
719    #[derive(Debug)]
720    struct TestEventB {}
721
722    impl Event for TestEventB {
723        fn table_name(&self) -> &str {
724            "table_B"
725        }
726
727        fn event_type(&self) -> &str {
728            "B"
729        }
730
731        fn as_any(&self) -> &dyn Any {
732            self
733        }
734    }
735
736    #[derive(Debug)]
737    struct TestEventC {}
738
739    impl Event for TestEventC {
740        fn table_name(&self) -> &str {
741            "table_C"
742        }
743
744        fn event_type(&self) -> &str {
745            "C"
746        }
747
748        fn as_any(&self) -> &dyn Any {
749            self
750        }
751    }
752
753    #[test]
754    fn test_group_events_by_type() {
755        let events: Vec<Box<dyn Event>> = vec![
756            Box::new(TestEventA {}),
757            Box::new(TestEventB {}),
758            Box::new(TestEventA {}),
759            Box::new(TestEventC {}),
760            Box::new(TestEventB {}),
761            Box::new(TestEventC {}),
762            Box::new(TestEventA {}),
763        ];
764
765        let event_groups = group_events_by_type(&events);
766        assert_eq!(event_groups.len(), 3);
767        assert_eq!(event_groups.get("A").unwrap().len(), 3);
768        assert_eq!(event_groups.get("B").unwrap().len(), 2);
769        assert_eq!(event_groups.get("C").unwrap().len(), 2);
770    }
771
772    #[test]
773    fn test_build_row_inserts_request() {
774        let events: Vec<Box<dyn Event>> = vec![
775            Box::new(TestEventA {}),
776            Box::new(TestEventB {}),
777            Box::new(TestEventA {}),
778            Box::new(TestEventC {}),
779            Box::new(TestEventB {}),
780            Box::new(TestEventC {}),
781            Box::new(TestEventA {}),
782        ];
783
784        let event_groups = group_events_by_type(&events);
785        assert_eq!(event_groups.len(), 3);
786        assert_eq!(event_groups.get("A").unwrap().len(), 3);
787        assert_eq!(event_groups.get("B").unwrap().len(), 2);
788        assert_eq!(event_groups.get("C").unwrap().len(), 2);
789
790        for (event_type, events) in event_groups {
791            let row_inserts_request = build_row_inserts_request(&events).unwrap();
792            if event_type == "A" {
793                assert_eq!(row_inserts_request.inserts.len(), 1);
794                assert_eq!(
795                    row_inserts_request.inserts[0].table_name,
796                    DEFAULT_EVENTS_TABLE_NAME
797                );
798                assert_eq!(
799                    row_inserts_request.inserts[0]
800                        .rows
801                        .as_ref()
802                        .unwrap()
803                        .rows
804                        .len(),
805                    3
806                );
807            } else if event_type == "B" {
808                assert_eq!(row_inserts_request.inserts.len(), 1);
809                assert_eq!(row_inserts_request.inserts[0].table_name, "table_B");
810                assert_eq!(
811                    row_inserts_request.inserts[0]
812                        .rows
813                        .as_ref()
814                        .unwrap()
815                        .rows
816                        .len(),
817                    2
818                );
819            } else if event_type == "C" {
820                assert_eq!(row_inserts_request.inserts.len(), 1);
821                assert_eq!(row_inserts_request.inserts[0].table_name, "table_C");
822                assert_eq!(
823                    row_inserts_request.inserts[0]
824                        .rows
825                        .as_ref()
826                        .unwrap()
827                        .rows
828                        .len(),
829                    2
830                );
831            } else {
832                panic!("Unexpected event type: {}", event_type);
833            }
834        }
835    }
836}