Skip to main content

evento_sql/
sql.rs

1//! Core SQL implementation for event sourcing.
2
3use std::ops::{Deref, DerefMut};
4
5#[cfg(feature = "mysql")]
6use sea_query::MysqlQueryBuilder;
7#[cfg(feature = "postgres")]
8use sea_query::PostgresQueryBuilder;
9#[cfg(feature = "sqlite")]
10use sea_query::SqliteQueryBuilder;
11use sea_query::{
12    Cond, Expr, ExprTrait, Func, Iden, IntoColumnRef, OnConflict, Query, SelectStatement,
13};
14use sea_query_sqlx::SqlxBinder;
15use sqlx::{Database, Pool};
16use ulid::Ulid;
17
18use evento_core::{
19    cursor::{self, Args, Cursor, Edge, PageInfo, ReadResult, Value},
20    EventFilter, Executor, WriteError,
21};
22
23/// Column identifiers for the `event` table.
24///
25/// Used with sea-query for type-safe SQL query construction.
26///
27/// # Columns
28///
29/// - `Id` - Event identifier (ULID format, VARCHAR(26))
30/// - `Name` - Event type name (VARCHAR(50))
31/// - `AggregatorType` - Aggregate root type (VARCHAR(50))
32/// - `AggregatorId` - Aggregate root instance ID (VARCHAR(26))
33/// - `Version` - Event sequence number within the aggregate
34/// - `Data` - Serialized event payload (BLOB, bitcode format)
35/// - `Metadata` - Serialized event metadata (BLOB, bitcode format)
36/// - `RoutingKey` - Optional routing key for partitioning (VARCHAR(50))
37/// - `Timestamp` - Event timestamp in seconds (BIGINT)
38/// - `TimestampSubsec` - Sub-second precision (BIGINT)
39#[derive(Iden, Clone)]
40pub enum Event {
41    /// The table name: `event`
42    Table,
43    /// Event ID column (ULID)
44    Id,
45    /// Event type name
46    Name,
47    /// Aggregate root type
48    AggregatorType,
49    /// Aggregate root instance ID
50    AggregatorId,
51    /// Event version/sequence number
52    Version,
53    /// Serialized event data
54    Data,
55    /// Serialized event metadata
56    Metadata,
57    /// Optional routing key
58    RoutingKey,
59    /// Timestamp in seconds
60    Timestamp,
61    /// Sub-second precision
62    TimestampSubsec,
63}
64
65/// Column identifiers for the `snapshot` table.
66///
67/// Used with sea-query for type-safe SQL query construction. The snapshot table
68/// backs projection snapshots via [`Executor::get_snapshot`], [`Executor::save_snapshot`],
69/// and [`Executor::delete_snapshot`].
70#[derive(Iden)]
71pub enum Snapshot {
72    /// The table name: `snapshot`
73    Table,
74    /// Snapshot ID
75    Id,
76    /// Snapshot type
77    Type,
78    /// Event stream cursor position
79    Cursor,
80    /// Revision identifier
81    Revision,
82    /// Serialized snapshot data
83    Data,
84    /// Creation timestamp
85    CreatedAt,
86    /// Last update timestamp
87    UpdatedAt,
88}
89
90/// Column identifiers for the `subscriber` table.
91///
92/// Used with sea-query for type-safe SQL query construction.
93///
94/// # Columns
95///
96/// - `Key` - Subscriber identifier (primary key)
97/// - `WorkerId` - ULID of the current worker processing events
98/// - `Cursor` - Current position in the event stream
99/// - `Lag` - Number of events behind the latest
100/// - `Enabled` - Whether the subscription is active
101/// - `CreatedAt` / `UpdatedAt` - Timestamps
102#[derive(Iden)]
103pub enum Subscriber {
104    /// The table name: `subscriber`
105    Table,
106    /// Subscriber key (primary key)
107    Key,
108    /// Current worker ID (ULID)
109    WorkerId,
110    /// Current cursor position
111    Cursor,
112    /// Event lag counter
113    Lag,
114    /// Whether subscription is enabled
115    Enabled,
116    /// Creation timestamp
117    CreatedAt,
118    /// Last update timestamp
119    UpdatedAt,
120}
121
122/// Type alias for MySQL executor.
123///
124/// Equivalent to `Sql<sqlx::MySql>`.
125#[cfg(feature = "mysql")]
126pub type MySql = Sql<sqlx::MySql>;
127
128/// Read-write executor pair for MySQL.
129///
130/// Used in CQRS patterns where you may have separate read and write connections.
131#[cfg(feature = "mysql")]
132pub type RwMySql = evento_core::Rw<MySql, MySql>;
133
134/// Type alias for PostgreSQL executor.
135///
136/// Equivalent to `Sql<sqlx::Postgres>`.
137#[cfg(feature = "postgres")]
138pub type Postgres = Sql<sqlx::Postgres>;
139
140/// Read-write executor pair for PostgreSQL.
141///
142/// Used in CQRS patterns where you may have separate read and write connections.
143#[cfg(feature = "postgres")]
144pub type RwPostgres = evento_core::Rw<Postgres, Postgres>;
145
146/// Type alias for SQLite executor.
147///
148/// Equivalent to `Sql<sqlx::Sqlite>`.
149#[cfg(feature = "sqlite")]
150pub type Sqlite = Sql<sqlx::Sqlite>;
151
152/// Read-write executor pair for SQLite.
153///
154/// Used in CQRS patterns where you may have separate read and write connections.
155#[cfg(feature = "sqlite")]
156pub type RwSqlite = evento_core::Rw<Sqlite, Sqlite>;
157
158/// SQL database executor for event sourcing operations.
159///
160/// A generic wrapper around a SQLx connection pool that implements the
161/// [`Executor`](evento_core::Executor) trait for storing and querying events.
162///
163/// # Type Parameters
164///
165/// - `DB` - The SQLx database type (e.g., `sqlx::Sqlite`, `sqlx::MySql`, `sqlx::Postgres`)
166///
167/// # Example
168///
169/// ```rust,ignore
170/// use evento_sql::Sql;
171/// use sqlx::sqlite::SqlitePoolOptions;
172///
173/// // Create a connection pool
174/// let pool = SqlitePoolOptions::new()
175///     .connect(":memory:")
176///     .await?;
177///
178/// // Convert to Sql executor
179/// let executor: Sql<sqlx::Sqlite> = pool.into();
180///
181/// // Or use the type alias
182/// let executor: evento_sql::Sqlite = pool.into();
183/// ```
184///
185/// # Executor Implementation
186///
187/// The `Sql` type implements [`Executor`](evento_core::Executor) with the following operations:
188///
189/// - **`read`** - Query events with filtering and cursor-based pagination
190/// - **`write`** - Persist events with optimistic concurrency control
191/// - **`get_subscriber_cursor`** - Get the current cursor position for a subscriber
192/// - **`is_subscriber_running`** - Check if a subscriber is active with a specific worker
193/// - **`upsert_subscriber`** - Create or update a subscriber record
194/// - **`acknowledge`** - Update subscriber cursor after processing events
195pub struct Sql<DB: Database>(Pool<DB>, tokio::sync::watch::Sender<u64>);
196
197impl<DB: Database> Sql<DB> {
198    fn build_sqlx<S: SqlxBinder>(statement: S) -> (String, sea_query_sqlx::SqlxValues) {
199        match DB::NAME {
200            #[cfg(feature = "sqlite")]
201            "SQLite" => statement.build_sqlx(SqliteQueryBuilder),
202            #[cfg(feature = "mysql")]
203            "MySQL" => statement.build_sqlx(MysqlQueryBuilder),
204            #[cfg(feature = "postgres")]
205            "PostgreSQL" => statement.build_sqlx(PostgresQueryBuilder),
206            name => panic!("'{name}' not supported, consider using SQLite, PostgreSQL or MySQL"),
207        }
208    }
209}
210
211#[async_trait::async_trait]
212impl<DB> Executor for Sql<DB>
213where
214    DB: Database,
215    for<'c> &'c mut DB::Connection: sqlx::Executor<'c, Database = DB>,
216    sea_query_sqlx::SqlxValues: sqlx::IntoArguments<DB>,
217    String: for<'r> sqlx::Decode<'r, DB> + sqlx::Type<DB>,
218    bool: for<'r> sqlx::Decode<'r, DB> + sqlx::Type<DB>,
219    Vec<u8>: for<'r> sqlx::Decode<'r, DB> + sqlx::Type<DB>,
220    i64: for<'r> sqlx::Decode<'r, DB> + sqlx::Type<DB>,
221    usize: sqlx::ColumnIndex<DB::Row>,
222    SqlEvent: for<'r> sqlx::FromRow<'r, DB::Row>,
223{
224    async fn read(
225        &self,
226        aggregators: Option<Vec<EventFilter>>,
227        routing_key: Option<evento_core::RoutingKey>,
228        args: Args,
229    ) -> anyhow::Result<ReadResult<evento_core::Event>> {
230        let statement = Query::select()
231            .columns([
232                Event::Id,
233                Event::Name,
234                Event::AggregatorType,
235                Event::AggregatorId,
236                Event::Version,
237                Event::Data,
238                Event::Metadata,
239                Event::RoutingKey,
240                Event::Timestamp,
241                Event::TimestampSubsec,
242            ])
243            .from(Event::Table)
244            .conditions(
245                aggregators.is_some(),
246                |q| {
247                    let Some(aggregators) = aggregators else {
248                        return;
249                    };
250
251                    let mut cond = Cond::any();
252
253                    for aggregator in aggregators {
254                        let mut aggregator_cond = Cond::all()
255                            .add(Expr::col(Event::AggregatorType).eq(aggregator.aggregate_type));
256
257                        if let Some(id) = aggregator.aggregate_id {
258                            aggregator_cond =
259                                aggregator_cond.add(Expr::col(Event::AggregatorId).eq(id));
260                        }
261
262                        if let Some(name) = aggregator.name {
263                            aggregator_cond = aggregator_cond.add(Expr::col(Event::Name).eq(name));
264                        }
265
266                        cond = cond.add(aggregator_cond);
267                    }
268
269                    q.and_where(cond.into());
270                },
271                |_| {},
272            )
273            .conditions(
274                matches!(routing_key, Some(evento_core::RoutingKey::Value(_))),
275                |q| {
276                    if let Some(evento_core::RoutingKey::Value(Some(ref routing_key))) = routing_key
277                    {
278                        q.and_where(Expr::col(Event::RoutingKey).eq(routing_key));
279                    }
280
281                    if let Some(evento_core::RoutingKey::Value(None)) = routing_key {
282                        q.and_where(Expr::col(Event::RoutingKey).is_null());
283                    }
284                },
285                |_q| {},
286            )
287            .to_owned();
288
289        Ok(Reader::new(statement)
290            .args(args)
291            .execute::<_, SqlEvent, _>(&self.0)
292            .await?
293            .map(|e| e.0))
294    }
295
296    async fn latest_timestamp(
297        &self,
298        aggregators: Option<Vec<EventFilter>>,
299        routing_key: Option<evento_core::RoutingKey>,
300    ) -> anyhow::Result<u64> {
301        let statement = Query::select()
302            .expr(Func::max(Expr::col(Event::Timestamp)))
303            .from(Event::Table)
304            .conditions(
305                aggregators.is_some(),
306                |q| {
307                    let Some(aggregators) = aggregators else {
308                        return;
309                    };
310
311                    let mut cond = Cond::any();
312
313                    for aggregator in aggregators {
314                        let mut aggregator_cond = Cond::all()
315                            .add(Expr::col(Event::AggregatorType).eq(aggregator.aggregate_type));
316
317                        if let Some(id) = aggregator.aggregate_id {
318                            aggregator_cond =
319                                aggregator_cond.add(Expr::col(Event::AggregatorId).eq(id));
320                        }
321
322                        if let Some(name) = aggregator.name {
323                            aggregator_cond = aggregator_cond.add(Expr::col(Event::Name).eq(name));
324                        }
325
326                        cond = cond.add(aggregator_cond);
327                    }
328
329                    q.and_where(cond.into());
330                },
331                |_| {},
332            )
333            .conditions(
334                matches!(routing_key, Some(evento_core::RoutingKey::Value(_))),
335                |q| {
336                    if let Some(evento_core::RoutingKey::Value(Some(ref routing_key))) = routing_key
337                    {
338                        q.and_where(Expr::col(Event::RoutingKey).eq(routing_key));
339                    }
340
341                    if let Some(evento_core::RoutingKey::Value(None)) = routing_key {
342                        q.and_where(Expr::col(Event::RoutingKey).is_null());
343                    }
344                },
345                |_q| {},
346            )
347            .to_owned();
348
349        let (sql, values) = Self::build_sqlx(statement);
350
351        let (ts,): (Option<i64>,) =
352            sqlx::query_as_with::<DB, (Option<i64>,), _>(sqlx::AssertSqlSafe(sql.as_str()), values)
353                .fetch_one(&self.0)
354                .await?;
355
356        Ok(ts.map(|v| if v < 0 { 0 } else { v as u64 }).unwrap_or(0))
357    }
358
359    async fn get_subscriber_cursor(&self, key: String) -> anyhow::Result<Option<Value>> {
360        let statement = Query::select()
361            .columns([Subscriber::Cursor])
362            .from(Subscriber::Table)
363            .and_where(Expr::col(Subscriber::Key).eq(Expr::value(key)))
364            .limit(1)
365            .to_owned();
366
367        let (sql, values) = Self::build_sqlx(statement);
368
369        let Some((cursor,)) = sqlx::query_as_with::<DB, (Option<String>,), _>(
370            sqlx::AssertSqlSafe(sql.as_str()),
371            values,
372        )
373        .fetch_optional(&self.0)
374        .await?
375        else {
376            return Ok(None);
377        };
378
379        Ok(cursor.map(|c| c.into()))
380    }
381
382    async fn is_subscriber_running(&self, key: String, worker_id: Ulid) -> anyhow::Result<bool> {
383        let statement = Query::select()
384            .columns([Subscriber::WorkerId, Subscriber::Enabled])
385            .from(Subscriber::Table)
386            .and_where(Expr::col(Subscriber::Key).eq(Expr::value(key)))
387            .limit(1)
388            .to_owned();
389
390        let (sql, values) = Self::build_sqlx(statement);
391
392        let (id, enabled) =
393            sqlx::query_as_with::<DB, (String, bool), _>(sqlx::AssertSqlSafe(sql.as_str()), values)
394                .fetch_one(&self.0)
395                .await?;
396
397        Ok(worker_id.to_string() == id && enabled)
398    }
399
400    async fn upsert_subscriber(&self, key: String, worker_id: Ulid) -> anyhow::Result<()> {
401        let statement = Query::insert()
402            .into_table(Subscriber::Table)
403            .columns([Subscriber::Key, Subscriber::WorkerId, Subscriber::Lag])
404            .values_panic([key.into(), worker_id.to_string().into(), 0.into()])
405            .on_conflict(
406                OnConflict::column(Subscriber::Key)
407                    .update_columns([Subscriber::WorkerId])
408                    .value(Subscriber::UpdatedAt, Expr::current_timestamp())
409                    .to_owned(),
410            )
411            .to_owned();
412
413        let (sql, values) = Self::build_sqlx(statement);
414
415        sqlx::query_with::<DB, _>(sqlx::AssertSqlSafe(sql.as_str()), values)
416            .execute(&self.0)
417            .await?;
418
419        Ok(())
420    }
421
422    async fn write(&self, events: Vec<evento_core::Event>) -> Result<(), WriteError> {
423        let mut statement = Query::insert()
424            .into_table(Event::Table)
425            .columns([
426                Event::Id,
427                Event::Name,
428                Event::Data,
429                Event::Metadata,
430                Event::AggregatorType,
431                Event::AggregatorId,
432                Event::Version,
433                Event::RoutingKey,
434                Event::Timestamp,
435                Event::TimestampSubsec,
436            ])
437            .to_owned();
438
439        for event in events {
440            let metadata = bitcode::encode(&event.metadata);
441            statement.values_panic([
442                event.id.to_string().into(),
443                event.name.into(),
444                event.data.into(),
445                metadata.into(),
446                event.aggregate_type.into(),
447                event.aggregate_id.into(),
448                event.version.into(),
449                event.routing_key.into(),
450                event.timestamp.into(),
451                event.timestamp_subsec.into(),
452            ]);
453        }
454
455        let (sql, values) = Self::build_sqlx(statement);
456
457        sqlx::query_with::<DB, _>(sqlx::AssertSqlSafe(sql.as_str()), values)
458            .execute(&self.0)
459            .await
460            .map_err(|err| {
461                let err_str = err.to_string();
462                if err_str.contains("(code: 2067)") {
463                    return WriteError::InvalidOriginalVersion;
464                }
465                if err_str.contains("1062 (23000): Duplicate entry") {
466                    return WriteError::InvalidOriginalVersion;
467                }
468                if err_str.contains("duplicate key value violates unique constraint") {
469                    return WriteError::InvalidOriginalVersion;
470                }
471                WriteError::Unknown(err.into())
472            })?;
473
474        // Wake any in-process subscriptions immediately instead of waiting for
475        // their next poll tick.
476        self.1.send_modify(|v| *v += 1);
477
478        Ok(())
479    }
480
481    fn write_watch(&self) -> Option<tokio::sync::watch::Receiver<u64>> {
482        Some(self.1.subscribe())
483    }
484
485    async fn acknowledge(&self, key: String, cursor: Value, lag: u64) -> anyhow::Result<()> {
486        let statement = Query::update()
487            .table(Subscriber::Table)
488            .values([
489                (Subscriber::Cursor, cursor.0.into()),
490                (Subscriber::Lag, lag.into()),
491                (Subscriber::UpdatedAt, Expr::current_timestamp()),
492            ])
493            .and_where(Expr::col(Subscriber::Key).eq(key))
494            .to_owned();
495
496        let (sql, values) = Self::build_sqlx(statement);
497
498        sqlx::query_with::<DB, _>(sqlx::AssertSqlSafe(sql.as_str()), values)
499            .execute(&self.0)
500            .await?;
501
502        Ok(())
503    }
504
505    async fn get_snapshot(
506        &self,
507        aggregate_type: String,
508        aggregate_revision: String,
509        id: String,
510    ) -> anyhow::Result<Option<(Vec<u8>, Value)>> {
511        let statement = Query::select()
512            .columns([Snapshot::Data, Snapshot::Cursor])
513            .from(Snapshot::Table)
514            .and_where(Expr::col(Snapshot::Type).eq(Expr::value(aggregate_type)))
515            .and_where(Expr::col(Snapshot::Id).eq(Expr::value(id)))
516            .and_where(Expr::col(Snapshot::Revision).eq(Expr::value(aggregate_revision)))
517            .limit(1)
518            .to_owned();
519
520        let (sql, values) = Self::build_sqlx(statement);
521
522        Ok(sqlx::query_as_with::<DB, (Vec<u8>, String), _>(
523            sqlx::AssertSqlSafe(sql.as_str()),
524            values,
525        )
526        .fetch_optional(&self.0)
527        .await
528        .map(|res| res.map(|(data, cursor)| (data, cursor.into())))?)
529    }
530
531    async fn save_snapshot(
532        &self,
533        aggregate_type: String,
534        aggregate_revision: String,
535        id: String,
536        data: Vec<u8>,
537        cursor: Value,
538    ) -> anyhow::Result<()> {
539        let statement = Query::insert()
540            .into_table(Snapshot::Table)
541            .columns([
542                Snapshot::Type,
543                Snapshot::Id,
544                Snapshot::Cursor,
545                Snapshot::Revision,
546                Snapshot::Data,
547            ])
548            .values_panic([
549                aggregate_type.into(),
550                id.to_string().into(),
551                cursor.to_string().into(),
552                aggregate_revision.into(),
553                data.into(),
554            ])
555            .on_conflict(
556                OnConflict::columns([Snapshot::Type, Snapshot::Id])
557                    .update_columns([Snapshot::Data, Snapshot::Cursor, Snapshot::Revision])
558                    .value(Snapshot::UpdatedAt, Expr::current_timestamp())
559                    .to_owned(),
560            )
561            .to_owned();
562
563        let (sql, values) = Self::build_sqlx(statement);
564
565        sqlx::query_with::<DB, _>(sqlx::AssertSqlSafe(sql.as_str()), values)
566            .execute(&self.0)
567            .await?;
568
569        Ok(())
570    }
571
572    async fn delete_snapshot(&self, aggregate_type: String, id: String) -> anyhow::Result<()> {
573        let statement = Query::delete()
574            .from_table(Snapshot::Table)
575            .and_where(Expr::col(Snapshot::Type).eq(Expr::value(aggregate_type)))
576            .and_where(Expr::col(Snapshot::Id).eq(Expr::value(id)))
577            .to_owned();
578
579        let (sql, values) = Self::build_sqlx(statement);
580
581        sqlx::query_with::<DB, _>(sqlx::AssertSqlSafe(sql.as_str()), values)
582            .execute(&self.0)
583            .await?;
584
585        Ok(())
586    }
587}
588
589impl<D: Database> Clone for Sql<D> {
590    fn clone(&self) -> Self {
591        // `watch::Sender` clones share the same channel, so all clones of this
592        // executor notify the same set of subscription receivers on write.
593        Self(self.0.clone(), self.1.clone())
594    }
595}
596
597impl<D: Database> From<Pool<D>> for Sql<D> {
598    fn from(value: Pool<D>) -> Self {
599        Self(value, tokio::sync::watch::channel(0).0)
600    }
601}
602
603/// Query builder for reading events with cursor-based pagination.
604///
605/// `Reader` wraps a sea-query [`SelectStatement`] and adds support for:
606/// - Forward pagination (first N after cursor)
607/// - Backward pagination (last N before cursor)
608/// - Ascending/descending order
609///
610/// # Example
611///
612/// ```rust,ignore
613/// use evento_sql::{Reader, Event};
614/// use sea_query::Query;
615///
616/// let statement = Query::select()
617///     .columns([Event::Id, Event::Name, Event::Data])
618///     .from(Event::Table)
619///     .to_owned();
620///
621/// let result = Reader::new(statement)
622///     .forward(10, None)  // First 10 events
623///     .execute::<_, MyEvent, _>(&pool)
624///     .await?;
625///
626/// for edge in result.edges {
627///     println!("Event: {:?}, Cursor: {:?}", edge.node, edge.cursor);
628/// }
629///
630/// // Continue with next page
631/// if result.page_info.has_next_page {
632///     let next_result = Reader::new(statement)
633///         .forward(10, result.page_info.end_cursor)
634///         .execute::<_, MyEvent, _>(&pool)
635///         .await?;
636/// }
637/// ```
638///
639/// # Deref
640///
641/// `Reader` implements `Deref` and `DerefMut` to the underlying `SelectStatement`,
642/// allowing direct access to sea-query builder methods.
643pub struct Reader {
644    statement: SelectStatement,
645    args: Args,
646    order: cursor::Order,
647}
648
649impl Reader {
650    /// Creates a new reader from a sea-query select statement.
651    pub fn new(statement: SelectStatement) -> Self {
652        Self {
653            statement,
654            args: Args::default(),
655            order: cursor::Order::Asc,
656        }
657    }
658
659    /// Sets the sort order for results.
660    pub fn order(&mut self, order: cursor::Order) -> &mut Self {
661        self.order = order;
662
663        self
664    }
665
666    /// Sets descending sort order.
667    pub fn desc(&mut self) -> &mut Self {
668        self.order(cursor::Order::Desc)
669    }
670
671    /// Sets pagination arguments directly.
672    pub fn args(&mut self, args: Args) -> &mut Self {
673        self.args = args;
674
675        self
676    }
677
678    /// Configures backward pagination (last N before cursor).
679    ///
680    /// # Arguments
681    ///
682    /// - `last` - Number of items to return
683    /// - `before` - Optional cursor to paginate before
684    pub fn backward(&mut self, last: u16, before: Option<Value>) -> &mut Self {
685        self.args(Args {
686            last: Some(last),
687            before,
688            ..Default::default()
689        })
690    }
691
692    /// Configures forward pagination (first N after cursor).
693    ///
694    /// # Arguments
695    ///
696    /// - `first` - Number of items to return
697    /// - `after` - Optional cursor to paginate after
698    pub fn forward(&mut self, first: u16, after: Option<Value>) -> &mut Self {
699        self.args(Args {
700            first: Some(first),
701            after,
702            ..Default::default()
703        })
704    }
705
706    /// Executes the query and returns paginated results.
707    ///
708    /// # Type Parameters
709    ///
710    /// - `DB` - The SQLx database type
711    /// - `O` - The output row type (must implement `FromRow`, `Cursor`, and `Bind`)
712    /// - `E` - The executor type
713    ///
714    /// # Returns
715    ///
716    /// A [`ReadResult`](evento_core::cursor::ReadResult) containing edges with nodes and cursors,
717    /// plus pagination info.
718    pub async fn execute<'e, 'c: 'e, DB, O, E>(
719        &mut self,
720        executor: E,
721    ) -> anyhow::Result<ReadResult<O>>
722    where
723        DB: Database,
724        E: 'e + sqlx::Executor<'c, Database = DB>,
725        O: for<'r> sqlx::FromRow<'r, DB::Row>,
726        O: Cursor,
727        O: Send + Unpin,
728        O: Bind<Cursor = O>,
729        <<O as Bind>::I as IntoIterator>::IntoIter: DoubleEndedIterator,
730        <<O as Bind>::V as IntoIterator>::IntoIter: DoubleEndedIterator,
731        sea_query_sqlx::SqlxValues: sqlx::IntoArguments<DB>,
732    {
733        let limit = self.build_reader::<O, O>()?;
734
735        let (sql, values) = match DB::NAME {
736            #[cfg(feature = "sqlite")]
737            "SQLite" => self.statement.build_sqlx(SqliteQueryBuilder),
738            #[cfg(feature = "mysql")]
739            "MySQL" => self.build_sqlx(MysqlQueryBuilder),
740            #[cfg(feature = "postgres")]
741            "PostgreSQL" => self.build_sqlx(PostgresQueryBuilder),
742            name => panic!("'{name}' not supported, consider using SQLite, PostgreSQL or MySQL"),
743        };
744
745        let mut rows = sqlx::query_as_with::<DB, O, _>(sqlx::AssertSqlSafe(sql.as_str()), values)
746            .fetch_all(executor)
747            .await?;
748
749        let has_more = rows.len() > limit as usize;
750        if has_more {
751            rows.pop();
752        }
753
754        let mut edges = vec![];
755        for node in rows.into_iter() {
756            edges.push(Edge {
757                cursor: node.serialize_cursor()?,
758                node,
759            });
760        }
761
762        if self.args.is_backward() {
763            edges = edges.into_iter().rev().collect();
764        }
765
766        let page_info = if self.args.is_backward() {
767            let start_cursor = edges.first().map(|e| e.cursor.clone());
768
769            PageInfo {
770                has_previous_page: has_more,
771                has_next_page: false,
772                start_cursor,
773                end_cursor: None,
774            }
775        } else {
776            let end_cursor = edges.last().map(|e| e.cursor.clone());
777            PageInfo {
778                has_previous_page: false,
779                has_next_page: has_more,
780                start_cursor: None,
781                end_cursor,
782            }
783        };
784
785        Ok(ReadResult { edges, page_info })
786    }
787
788    fn build_reader<O: Cursor, B: Bind<Cursor = O>>(&mut self) -> Result<u16, cursor::CursorError>
789    where
790        B::T: Clone,
791        <<B as Bind>::I as IntoIterator>::IntoIter: DoubleEndedIterator,
792        <<B as Bind>::V as IntoIterator>::IntoIter: DoubleEndedIterator,
793    {
794        let (limit, cursor) = self.args.get_info();
795
796        if let Some(cursor) = cursor.as_ref() {
797            self.build_reader_where::<O, B>(cursor)?;
798        }
799
800        self.build_reader_order::<B>();
801        self.limit((limit + 1).into());
802
803        Ok(limit)
804    }
805
806    fn build_reader_where<O, B>(&mut self, cursor: &Value) -> Result<(), cursor::CursorError>
807    where
808        O: Cursor,
809        B: Bind<Cursor = O>,
810        B::T: Clone,
811        <<B as Bind>::I as IntoIterator>::IntoIter: DoubleEndedIterator,
812        <<B as Bind>::V as IntoIterator>::IntoIter: DoubleEndedIterator,
813    {
814        let is_order_desc = self.is_order_desc();
815        let cursor = O::deserialize_cursor(cursor)?;
816        let columns = B::columns().into_iter().rev();
817        let values = B::values(cursor).into_iter().rev();
818
819        let mut expr = None::<Expr>;
820        for (col, value) in columns.zip(values) {
821            let current_expr = if is_order_desc {
822                Expr::col(col.clone()).lt(value.clone())
823            } else {
824                Expr::col(col.clone()).gt(value.clone())
825            };
826
827            let Some(ref prev_expr) = expr else {
828                expr = Some(current_expr.clone());
829                continue;
830            };
831
832            expr = Some(current_expr.or(Expr::col(col).eq(value).and(prev_expr.clone())));
833        }
834
835        self.and_where(expr.unwrap());
836
837        Ok(())
838    }
839
840    fn build_reader_order<O: Bind>(&mut self) {
841        let order = if self.is_order_desc() {
842            sea_query::Order::Desc
843        } else {
844            sea_query::Order::Asc
845        };
846
847        let columns = O::columns();
848        for col in columns {
849            self.order_by(col, order.clone());
850        }
851    }
852
853    fn is_order_desc(&self) -> bool {
854        matches!(
855            (&self.order, self.args.is_backward()),
856            (cursor::Order::Asc, true) | (cursor::Order::Desc, false)
857        )
858    }
859}
860
861impl Deref for Reader {
862    type Target = SelectStatement;
863
864    fn deref(&self) -> &Self::Target {
865        &self.statement
866    }
867}
868
869impl DerefMut for Reader {
870    fn deref_mut(&mut self) -> &mut Self::Target {
871        &mut self.statement
872    }
873}
874
875/// Trait for binding cursor values in paginated queries.
876///
877/// This trait defines how to serialize cursor data for keyset pagination.
878/// It specifies which columns are used for ordering and how to extract
879/// their values from a cursor.
880///
881/// # Implementation
882///
883/// The trait is implemented for [`evento_core::Event`] to enable pagination
884/// over the event table using timestamp, version, and ID columns.
885///
886/// # Associated Types
887///
888/// - `T` - Column reference type
889/// - `I` - Iterator over column references
890/// - `V` - Iterator over value expressions
891/// - `Cursor` - The cursor type that provides pagination data
892pub trait Bind {
893    /// Column reference type (e.g., `Event` enum variant).
894    type T: IntoColumnRef + Clone;
895    /// Iterator type for columns.
896    type I: IntoIterator<Item = Self::T>;
897    /// Iterator type for values.
898    type V: IntoIterator<Item = Expr>;
899    /// The cursor type used for pagination.
900    type Cursor: Cursor;
901
902    /// Returns the columns used for cursor-based ordering.
903    fn columns() -> Self::I;
904    /// Extracts values from a cursor for WHERE clause construction.
905    fn values(cursor: <<Self as Bind>::Cursor as Cursor>::T) -> Self::V;
906}
907
908impl evento_core::cursor::Cursor for SqlEvent {
909    type T = evento_core::EventCursor;
910
911    fn serialize(&self) -> Self::T {
912        evento_core::EventCursor {
913            i: self.0.id.to_string(),
914            v: self.0.version,
915            t: self.0.timestamp,
916            s: self.0.timestamp_subsec,
917        }
918    }
919}
920
921impl Bind for SqlEvent {
922    type T = Event;
923    type I = [Self::T; 4];
924    type V = [Expr; 4];
925    type Cursor = Self;
926
927    fn columns() -> Self::I {
928        [
929            Event::Timestamp,
930            Event::TimestampSubsec,
931            Event::Version,
932            Event::Id,
933        ]
934    }
935
936    fn values(cursor: <<Self as Bind>::Cursor as Cursor>::T) -> Self::V {
937        [
938            cursor.t.into(),
939            cursor.s.into(),
940            cursor.v.into(),
941            cursor.i.into(),
942        ]
943    }
944}
945
946#[cfg(feature = "sqlite")]
947impl From<Sqlite> for evento_core::Evento {
948    fn from(value: Sqlite) -> Self {
949        evento_core::Evento::new(value)
950    }
951}
952
953#[cfg(feature = "sqlite")]
954impl From<&Sqlite> for evento_core::Evento {
955    fn from(value: &Sqlite) -> Self {
956        evento_core::Evento::new(value.clone())
957    }
958}
959
960#[cfg(feature = "mysql")]
961impl From<MySql> for evento_core::Evento {
962    fn from(value: MySql) -> Self {
963        evento_core::Evento::new(value)
964    }
965}
966
967#[cfg(feature = "mysql")]
968impl From<&MySql> for evento_core::Evento {
969    fn from(value: &MySql) -> Self {
970        evento_core::Evento::new(value.clone())
971    }
972}
973
974#[cfg(feature = "postgres")]
975impl From<Postgres> for evento_core::Evento {
976    fn from(value: Postgres) -> Self {
977        evento_core::Evento::new(value)
978    }
979}
980
981#[cfg(feature = "postgres")]
982impl From<&Postgres> for evento_core::Evento {
983    fn from(value: &Postgres) -> Self {
984        evento_core::Evento::new(value.clone())
985    }
986}
987
988#[derive(Debug, Clone, PartialEq, Default)]
989pub struct SqlEvent(pub evento_core::Event);
990
991impl<R: sqlx::Row> sqlx::FromRow<'_, R> for SqlEvent
992where
993    i32: sqlx::Type<R::Database> + for<'r> sqlx::Decode<'r, R::Database>,
994    Vec<u8>: sqlx::Type<R::Database> + for<'r> sqlx::Decode<'r, R::Database>,
995    String: sqlx::Type<R::Database> + for<'r> sqlx::Decode<'r, R::Database>,
996    i64: sqlx::Type<R::Database> + for<'r> sqlx::Decode<'r, R::Database>,
997    for<'r> &'r str: sqlx::Type<R::Database> + sqlx::Decode<'r, R::Database>,
998    for<'r> &'r str: sqlx::ColumnIndex<R>,
999{
1000    fn from_row(row: &R) -> Result<Self, sqlx::Error> {
1001        let timestamp: i64 = sqlx::Row::try_get(row, "timestamp")?;
1002        let timestamp_subsec: i64 = sqlx::Row::try_get(row, "timestamp_subsec")?;
1003        let version: i32 = sqlx::Row::try_get(row, "version")?;
1004        let metadata: Vec<u8> = sqlx::Row::try_get(row, "metadata")?;
1005        let metadata: evento_core::metadata::Metadata =
1006            bitcode::decode(&metadata).map_err(|e| sqlx::Error::Decode(e.into()))?;
1007
1008        Ok(SqlEvent(evento_core::Event {
1009            id: Ulid::from_string(sqlx::Row::try_get(row, "id")?)
1010                .map_err(|err| sqlx::Error::InvalidArgument(err.to_string()))?,
1011            aggregate_id: sqlx::Row::try_get(row, "aggregator_id")?,
1012            aggregate_type: sqlx::Row::try_get(row, "aggregator_type")?,
1013            version: version as u16,
1014            name: sqlx::Row::try_get(row, "name")?,
1015            routing_key: sqlx::Row::try_get(row, "routing_key")?,
1016            data: sqlx::Row::try_get(row, "data")?,
1017            timestamp: timestamp as u64,
1018            timestamp_subsec: timestamp_subsec as u32,
1019            metadata,
1020        }))
1021    }
1022}