@@ -51,45 +51,47 @@ defmodule Commanded.Projections.Ecto do
5151 use Ecto.Schema
5252 use Commanded.Event.Handler , @ handler_opts
5353
54- import Ecto.Changeset
5554 import Ecto.Query
5655 import unquote ( __MODULE__ )
5756
5857 def update_projection ( event , metadata , multi_fn ) do
5958 projection_name = Map . fetch! ( metadata , :handler_name )
6059 event_number = Map . fetch! ( metadata , :event_number )
6160
62- changeset =
63- % ProjectionVersion { projection_name: projection_name }
64- |> ProjectionVersion . changeset ( % { last_seen_event_number: event_number } )
61+ projection_version = % ProjectionVersion {
62+ projection_name: projection_name ,
63+ last_seen_event_number: event_number
64+ }
6565
6666 prefix = schema_prefix ( event , metadata )
6767
68+ # Query to update an existing projection version with the last seen event number with
69+ # a check to ensure that the event has not already been projected.
70+ update_projection_version =
71+ from ( pv in ProjectionVersion ,
72+ where:
73+ pv . projection_name == ^ projection_name and pv . last_seen_event_number < ^ event_number ,
74+ update: [ set: [ last_seen_event_number: ^ event_number ] ]
75+ )
76+
6877 multi =
6978 Ecto.Multi . new ( )
70- |> Ecto.Multi . run ( :verify_projection_version , fn repo , _changes ->
71- version =
72- case repo . get ( ProjectionVersion , projection_name , prefix: prefix ) do
73- nil ->
74- repo . insert! (
75- % ProjectionVersion {
76- projection_name: projection_name ,
77- last_seen_event_number: 0
78- } ,
79- prefix: prefix
80- )
81-
82- version ->
83- version
84- end
85-
86- if version . last_seen_event_number < event_number do
87- { :ok , % { version: version } }
88- else
89- { :error , :already_seen_event }
79+ |> Ecto.Multi . run ( :track_projection_version , fn repo , _changes ->
80+ try do
81+ repo . insert ( projection_version ,
82+ prefix: prefix ,
83+ on_conflict: update_projection_version ,
84+ conflict_target: [ :projection_name ]
85+ )
86+ rescue
87+ exception in Ecto.StaleEntryError ->
88+ # Attempted to insert a projection version for an already seen event
89+ { :error , :already_seen_event }
90+
91+ exception ->
92+ reraise exception , __STACKTRACE__
9093 end
9194 end )
92- |> Ecto.Multi . update ( :projection_version , changeset , prefix: prefix )
9395
9496 with % Ecto.Multi { } = multi <- apply ( multi_fn , [ multi ] ) ,
9597 { :ok , changes } <- transaction ( multi ) do
@@ -99,7 +101,7 @@ defmodule Commanded.Projections.Ecto do
99101 :ok
100102 end
101103 else
102- { :error , :verify_projection_version , :already_seen_event , _changes } -> :ok
104+ { :error , :track_projection_version , :already_seen_event , _changes } -> :ok
103105 { :error , _stage , error , _changes } -> { :error , error }
104106 { :error , _error } = reply -> reply
105107 end
@@ -202,24 +204,15 @@ defmodule Commanded.Projections.Ecto do
202204 quote do
203205 defmodule ProjectionVersion do
204206 @ moduledoc false
205-
206207 use Ecto.Schema
207208
208- import Ecto.Changeset
209-
210209 @ primary_key { :projection_name , :string , [ ] }
211210
212211 schema "projection_versions" do
213212 field ( :last_seen_event_number , :integer )
214213
215214 timestamps ( type: :naive_datetime_usec )
216215 end
217-
218- @ required_fields ~w( last_seen_event_number) a
219-
220- def changeset ( model , params \\ :invalid ) do
221- cast ( model , params , @ required_fields )
222- end
223216 end
224217 end
225218 end
0 commit comments