@@ -106,12 +106,9 @@ where
106106 <QE as TryFrom < E > >:: Error : StdError + Send + Sync + ' static ,
107107 {
108108 let sql = format ! (
109- r#"
110- WITH epoch AS (
111- SELECT MAX(event_id) as __epoch FROM event
112- )
113- SELECT event_id, payload, __epoch
114- FROM event CROSS JOIN epoch WHERE ({criteria}) AND event_id <= __epoch
109+ r#"SELECT event.event_id, event.payload, epoch.__epoch_id
110+ FROM (SELECT MAX(event_id) AS __epoch_id FROM event) AS epoch
111+ LEFT JOIN event ON event.event_id <= epoch.__epoch_id AND ({criteria})
115112 ORDER BY event_id ASC"# ,
116113 criteria = CriteriaBuilder :: new( query) . build( )
117114 ) ;
@@ -121,13 +118,15 @@ where
121118 let mut epoch_id: PgEventId = 0 ;
122119 while let Some ( row) = rows. next( ) . await {
123120 let row = row?;
124- let event_id = row. get( 0 ) ;
121+ let event_id: Option < i64 > = row. get( 0 ) ;
125122 epoch_id = row. get( 2 ) ;
126- let payload = self . serde. deserialize( row. get( 1 ) ) ?;
127- let payload: QE = payload
123+ if let Some ( event_id) = event_id {
124+ let payload = self . serde. deserialize( row. get( 1 ) ) ?;
125+ let payload: QE = payload
128126 . try_into( )
129127 . map_err( |e| Error :: QueryEventMapping ( Box :: new( e) ) ) ?;
130- yield Ok ( StreamItem :: Event ( PersistedEvent :: new( event_id, payload) ) ) ;
128+ yield Ok ( StreamItem :: Event ( PersistedEvent :: new( event_id, payload) ) ) ;
129+ }
131130 }
132131 yield Ok ( StreamItem :: End ( epoch_id) ) ;
133132 }
@@ -176,20 +175,18 @@ where
176175
177176 /// Appends new events to the event store.
178177 ///
179- /// This function inserts the provided `events` into the PostgreSQL event store by performing
180- /// two separate inserts. First, it inserts the events into the `event_sequence` table to reclaim
181- /// a set of IDs for the events. Then, it inserts the events into the `event` table along with
182- /// their IDs, event types, domain identifiers, and payloads. Finally, it marks the event IDs as `consumed`
183- /// in the event sequence table. If marking the event IDs as consumed fails (e.g., another process has already consumed the IDs),
184- /// a conflict error is raised. This conflict indicates that the data retrieved by the query is stale,
185- /// meaning that the events generated are no longer valid due to being generated from an old version
186- /// of the event store.
178+ /// This function inserts the provided `events` into the PostgreSQL-backed event store.
179+ /// Before inserting, it queries the `event` table to ensure that no events have been
180+ /// appended since the given `version`. If newer events are found, a concurrency error
181+ /// is returned to prevent invalid state transitions.
182+ ///
183+ /// If the concurrency check succeeds, the events are inserted into the `event` table.
187184 ///
188185 /// # Arguments
189186 ///
190- /// * `events` - A vector of events to be appended .
191- /// * `query` - The stream query specifying the criteria for filtering events .
192- /// * `version` - The ID of the last consumed event.
187+ /// * `events` - The events to append to the event store .
188+ /// * `query` - The stream query that identifies the target event stream .
189+ /// * `version` - The ID of the last consumed event, used for optimistic concurrency control .
193190 ///
194191 /// # Returns
195192 ///
@@ -206,13 +203,16 @@ where
206203 QE : Event + Clone + Send + Sync ,
207204 {
208205 let mut tx = self . pool . begin ( ) . await ?;
209- sqlx:: query ( "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" ) . execute ( & mut * tx) . await ?;
210- let inv_events: i64 = sqlx:: query_scalar ( & format ! ( "SELECT count(*) FROM event WHERE {}" , CriteriaBuilder :: new( & query. change_origin( version) ) . build( ) ) )
211- . fetch_one ( & mut * tx)
212- . await
213- . map_err ( map_concurrency_err) ?;
206+ sqlx:: query ( "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE" )
207+ . execute ( & mut * tx)
208+ . await ?;
214209
215- if inv_events > 0 {
210+ if sqlx:: query_scalar ( & format ! (
211+ "SELECT EXISTS (SELECT 1 FROM event WHERE {})" ,
212+ CriteriaBuilder :: new( & query. change_origin( version) ) . build( )
213+ ) )
214+ . fetch_one ( & mut * tx)
215+ . await ? {
216216 return Err ( Error :: Concurrency ) ;
217217 }
218218
@@ -231,16 +231,55 @@ where
231231 . map ( |( event_id, event) | PersistedEvent :: new ( * event_id, event) )
232232 . collect :: < Vec < _ > > ( ) ;
233233
234- tx. commit ( ) . await ?;
234+ tx. commit ( ) . await . map_err ( map_concurrency_err ) ?;
235235
236236 Ok ( persisted_events)
237237 }
238238
239+ /// Appends a batch of events to the PostgreSQL-backed event store **without** verifying
240+ /// whether new events have been added since the last read.
241+ ///
242+ /// # Arguments
243+ ///
244+ /// * `events` - A vector of events to be appended.
245+ ///
246+ /// # Returns
247+ ///
248+ /// A `Result` containing a vector of `PersistedEvent` representing the appended events,
249+ /// or an error of type `Self::Error`.
250+ async fn append_without_validation (
251+ & self ,
252+ events : Vec < E > ,
253+ ) -> Result < Vec < PersistedEvent < PgEventId , E > > , Self :: Error >
254+ where
255+ E : Clone + ' async_trait ,
256+ {
257+ let mut tx = self . pool . begin ( ) . await ?;
258+
259+ let mut sequence_insert = InsertEventsBuilder :: new ( & events, & self . serde ) ;
260+ let event_ids: Vec < PgEventId > = sequence_insert
261+ . build ( )
262+ . fetch_all ( & mut * tx)
263+ . await ?
264+ . into_iter ( )
265+ . map ( |r| r. get ( 0 ) )
266+ . collect ( ) ;
267+
268+ let persisted_events = event_ids
269+ . iter ( )
270+ . zip ( events)
271+ . map ( |( event_id, event) | PersistedEvent :: new ( * event_id, event) )
272+ . collect :: < Vec < _ > > ( ) ;
273+
274+ tx. commit ( ) . await ?;
275+
276+ Ok ( persisted_events)
277+ }
239278}
240279
241280fn map_concurrency_err ( err : sqlx:: Error ) -> Error {
242281 if let sqlx:: Error :: Database ( ref description) = err {
243- if description. code ( ) . as_deref ( ) == Some ( "23514 " ) {
282+ if description. code ( ) . as_deref ( ) == Some ( "40001 " ) {
244283 return Error :: Concurrency ;
245284 }
246285 }
0 commit comments