event_sourcery-postgres 0.9.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/README.md +1 -1
- data/event_sourcery-postgres.gemspec +18 -12
- data/lib/event_sourcery/postgres/config.rb +2 -0
- data/lib/event_sourcery/postgres/event_store.rb +14 -9
- data/lib/event_sourcery/postgres/optimised_event_poll_waiter.rb +3 -1
- data/lib/event_sourcery/postgres/projector.rb +2 -0
- data/lib/event_sourcery/postgres/queue_with_interval_callback.rb +9 -1
- data/lib/event_sourcery/postgres/reactor.rb +9 -7
- data/lib/event_sourcery/postgres/schema.rb +99 -91
- data/lib/event_sourcery/postgres/table_owner.rb +3 -3
- data/lib/event_sourcery/postgres/tracker.rb +21 -21
- data/lib/event_sourcery/postgres/version.rb +3 -1
- data/lib/event_sourcery/postgres.rb +2 -0
- metadata +47 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 05b4e104f8ead624e85b64116db8a9347ff6fd4b6b0af4abc8b4c60c0d49d926
|
|
4
|
+
data.tar.gz: c019944e4b6d194088945d8e7c9fe9cfd7a494fc6d8cba8f90df56cc839bfc24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76861f96df82c5ebe3de33f29de17ab6a86e9c5fd45fb72bb092469567eb8961d32e3fcf848e646023f5b12a5a1e9d5d2e6c8b292d15181ed2ea4d05e1b2b645
|
|
7
|
+
data.tar.gz: c59050c981cc4dac1ec32d5695a872351ed8bdc45ba5cb8a7d903c15fb06ea83ca13ce924db2095b62d6dda9d335302811522eed84ec8b568e116fac6a802706
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
[Unreleased]: https://github.com/envato/event_sourcery-postgres/compare/v1.0.0...HEAD
|
|
11
|
+
|
|
12
|
+
## [1.0.0] - 2025-12-28
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Resolve issues as identified by RuboCop ([#78], [#82], [#83]).
|
|
17
|
+
- Minor fixups in gem metadata ([#79]).
|
|
18
|
+
- Remove support for older Ruby versions: Ruby 2.6 or greater is now required ([#80]).
|
|
19
|
+
|
|
20
|
+
[1.0.0]: https://github.com/envato/event_sourcery-postgres/compare/v0.9.1...v1.0.0
|
|
21
|
+
[#78]: https://github.com/envato/event_sourcery-postgres/pull/78
|
|
22
|
+
[#79]: https://github.com/envato/event_sourcery-postgres/pull/79
|
|
23
|
+
[#80]: https://github.com/envato/event_sourcery-postgres/pull/80
|
|
24
|
+
[#82]: https://github.com/envato/event_sourcery-postgres/pull/82
|
|
25
|
+
[#83]: https://github.com/envato/event_sourcery-postgres/pull/83
|
|
26
|
+
|
|
9
27
|
## [0.9.1] - 2022-01-20
|
|
10
28
|
|
|
11
29
|
### Changed
|
|
@@ -117,7 +135,6 @@ or when the loop stops
|
|
|
117
135
|
- Postgres related configuration is through `EventSourcery::Postgres.configure`
|
|
118
136
|
instead of `EventSourcery.configure`.
|
|
119
137
|
|
|
120
|
-
[Unreleased]: https://github.com/envato/event_sourcery-postgres/compare/v0.9.1...HEAD
|
|
121
138
|
[0.9.1]: https://github.com/envato/event_sourcery-postgres/compare/v0.9.0...v0.9.1
|
|
122
139
|
[0.9.0]: https://github.com/envato/event_sourcery-postgres/compare/v0.8.1...v0.9.0
|
|
123
140
|
[0.8.1]: https://github.com/envato/event_sourcery-postgres/compare/v0.8.0...v0.8.1
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# EventSourcery::Postgres
|
|
2
2
|
|
|
3
|
-
[](https://github.com/envato/event_sourcery-postgres/actions/workflows/test.yml)
|
|
4
4
|
|
|
5
5
|
## Development Status
|
|
6
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'event_sourcery/postgres/version'
|
|
5
6
|
|
|
@@ -12,27 +13,32 @@ Gem::Specification.new do |spec|
|
|
|
12
13
|
|
|
13
14
|
spec.summary = 'Postgres event store for use with EventSourcery'
|
|
14
15
|
spec.homepage = 'https://github.com/envato/event_sourcery-postgres'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
|
|
15
18
|
spec.metadata = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
'allowed_push_host' => 'https://rubygems.org',
|
|
20
|
+
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
|
21
|
+
'changelog_uri' => "#{spec.homepage}/blob/HEAD/CHANGELOG.md",
|
|
22
|
+
'documentation_uri' => "https://www.rubydoc.info/gems/event_sourcery-postgres/#{spec.version}",
|
|
23
|
+
'source_code_uri' => "#{spec.homepage}/tree/v#{spec.version}"
|
|
24
|
+
}
|
|
20
25
|
|
|
21
|
-
spec.files
|
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
22
27
|
f.match(%r{^(\.|bin/|Gemfile|Rakefile|script/|spec/)})
|
|
23
28
|
end
|
|
24
29
|
spec.bindir = 'exe'
|
|
25
30
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
26
31
|
spec.require_paths = ['lib']
|
|
27
32
|
|
|
28
|
-
spec.required_ruby_version = '>= 2.
|
|
33
|
+
spec.required_ruby_version = '>= 2.6.0'
|
|
29
34
|
|
|
30
|
-
spec.add_dependency 'sequel', '>= 4.38'
|
|
31
|
-
spec.add_dependency 'pg'
|
|
32
35
|
spec.add_dependency 'event_sourcery', '>= 0.14.0'
|
|
36
|
+
spec.add_dependency 'pg'
|
|
37
|
+
spec.add_dependency 'sequel', '>= 4.38'
|
|
38
|
+
spec.add_development_dependency 'benchmark-ips'
|
|
33
39
|
spec.add_development_dependency 'bundler'
|
|
40
|
+
spec.add_development_dependency 'pry'
|
|
34
41
|
spec.add_development_dependency 'rake', '~> 13'
|
|
35
42
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
36
|
-
spec.add_development_dependency '
|
|
37
|
-
spec.add_development_dependency 'benchmark-ips'
|
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 1'
|
|
38
44
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
class EventStore
|
|
@@ -32,6 +34,7 @@ module EventSourcery
|
|
|
32
34
|
events = Array(event_or_events)
|
|
33
35
|
aggregate_ids = events.map(&:aggregate_id).uniq
|
|
34
36
|
raise AtomicWriteToMultipleAggregatesNotSupported unless aggregate_ids.count == 1
|
|
37
|
+
|
|
35
38
|
sql = write_events_sql(aggregate_ids.first, events, expected_version)
|
|
36
39
|
@db_connection.run(sql)
|
|
37
40
|
log_events_saved(events)
|
|
@@ -40,9 +43,9 @@ module EventSourcery
|
|
|
40
43
|
rescue Sequel::DatabaseError => e
|
|
41
44
|
if e.message =~ /Concurrency conflict/
|
|
42
45
|
raise ConcurrencyError, "expected version was not #{expected_version}. Error: #{e.message}"
|
|
43
|
-
else
|
|
44
|
-
raise
|
|
45
46
|
end
|
|
47
|
+
|
|
48
|
+
raise
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
# Get the next set of events from the given event id. You can
|
|
@@ -55,10 +58,11 @@ module EventSourcery
|
|
|
55
58
|
#
|
|
56
59
|
# @return [Array] array of found events
|
|
57
60
|
def get_next_from(id, event_types: nil, limit: 1000)
|
|
58
|
-
query =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
query =
|
|
62
|
+
events_table
|
|
63
|
+
.order(:id)
|
|
64
|
+
.where(Sequel.lit('id >= ?', id))
|
|
65
|
+
.limit(limit)
|
|
62
66
|
query = query.where(type: event_types) if event_types
|
|
63
67
|
query.map { |event_row| build_event(event_row) }
|
|
64
68
|
end
|
|
@@ -96,7 +100,7 @@ module EventSourcery
|
|
|
96
100
|
# @param event_types the event_types to subscribe to, default all.
|
|
97
101
|
# @param after_listen the after listen call back block. default nil.
|
|
98
102
|
# @param subscription_master the subscription master block
|
|
99
|
-
def subscribe(from_id:, event_types: nil, after_listen: nil,
|
|
103
|
+
def subscribe(from_id:, subscription_master:, event_types: nil, after_listen: nil, &block)
|
|
100
104
|
poll_waiter = OptimisedEventPollWaiter.new(db_connection: @db_connection, after_listen: after_listen)
|
|
101
105
|
args = {
|
|
102
106
|
poll_waiter: poll_waiter,
|
|
@@ -157,9 +161,10 @@ module EventSourcery
|
|
|
157
161
|
|
|
158
162
|
def to_sql_literal(value)
|
|
159
163
|
return 'null' unless value
|
|
160
|
-
|
|
164
|
+
|
|
165
|
+
wrapped_value = if value.is_a?(Time)
|
|
161
166
|
value.iso8601(6)
|
|
162
|
-
elsif Hash
|
|
167
|
+
elsif value.is_a?(Hash)
|
|
163
168
|
Sequel.pg_json(value)
|
|
164
169
|
else
|
|
165
170
|
value
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
# Optimise poll interval with Postgres listen/notify
|
|
@@ -11,7 +13,7 @@ module EventSourcery
|
|
|
11
13
|
@after_listen = after_listen
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
def poll(after_listen: proc {
|
|
16
|
+
def poll(after_listen: proc {}, &block)
|
|
15
17
|
@events_queue.callback = proc do
|
|
16
18
|
ensure_listen_thread_alive!
|
|
17
19
|
block.call
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
class QueueWithIntervalCallback < ::Queue
|
|
4
6
|
attr_accessor :callback
|
|
5
7
|
|
|
6
|
-
def initialize(
|
|
8
|
+
def initialize(
|
|
9
|
+
callback: proc {},
|
|
10
|
+
callback_interval: EventSourcery::Postgres.config.callback_interval_if_no_new_events,
|
|
11
|
+
poll_interval: 0.1
|
|
12
|
+
)
|
|
7
13
|
@callback = callback
|
|
8
14
|
@callback_interval = callback_interval
|
|
9
15
|
@poll_interval = poll_interval
|
|
@@ -12,6 +18,7 @@ module EventSourcery
|
|
|
12
18
|
|
|
13
19
|
def pop(non_block_without_callback = false)
|
|
14
20
|
return super if non_block_without_callback
|
|
21
|
+
|
|
15
22
|
pop_with_interval_callback
|
|
16
23
|
end
|
|
17
24
|
|
|
@@ -21,6 +28,7 @@ module EventSourcery
|
|
|
21
28
|
time = Time.now
|
|
22
29
|
loop do
|
|
23
30
|
return pop(true) unless empty?
|
|
31
|
+
|
|
24
32
|
if @callback_interval && Time.now > time + @callback_interval
|
|
25
33
|
@callback.call
|
|
26
34
|
time = Time.now
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
module Reactor
|
|
@@ -48,11 +50,10 @@ module EventSourcery
|
|
|
48
50
|
@event_source = event_source
|
|
49
51
|
@event_sink = event_sink
|
|
50
52
|
@db_connection = db_connection
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
53
|
+
return unless self.class.emits_events?
|
|
54
|
+
return unless event_sink.nil? || event_source.nil?
|
|
55
|
+
|
|
56
|
+
raise ArgumentError, 'An event sink and source is required for processors that emit events'
|
|
56
57
|
end
|
|
57
58
|
end
|
|
58
59
|
|
|
@@ -61,15 +62,16 @@ module EventSourcery
|
|
|
61
62
|
attr_reader :event_sink, :event_source
|
|
62
63
|
|
|
63
64
|
def emit_event(event_or_hash, &block)
|
|
64
|
-
event = if Event
|
|
65
|
+
event = if event_or_hash.is_a?(Event)
|
|
65
66
|
event_or_hash
|
|
66
67
|
else
|
|
67
68
|
Event.new(event_or_hash)
|
|
68
69
|
end
|
|
69
70
|
raise UndeclaredEventEmissionError unless self.class.emits_event?(event.class)
|
|
71
|
+
|
|
70
72
|
event = event.with(causation_id: _event.uuid, correlation_id: _event.correlation_id)
|
|
71
73
|
invoke_action_and_emit_event(event, block)
|
|
72
|
-
EventSourcery.logger.debug { "[#{
|
|
74
|
+
EventSourcery.logger.debug { "[#{processor_name}] Emitted event: #{event.inspect}" }
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
def invoke_action_and_emit_event(event, action)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
module Schema
|
|
@@ -15,7 +17,12 @@ module EventSourcery
|
|
|
15
17
|
write_events_function_name: EventSourcery::Postgres.config.write_events_function_name)
|
|
16
18
|
create_events(db: db, table_name: events_table_name)
|
|
17
19
|
create_aggregates(db: db, table_name: aggregates_table_name)
|
|
18
|
-
create_or_update_functions(
|
|
20
|
+
create_or_update_functions(
|
|
21
|
+
db: db,
|
|
22
|
+
events_table_name: events_table_name,
|
|
23
|
+
function_name: write_events_function_name,
|
|
24
|
+
aggregates_table_name: aggregates_table_name
|
|
25
|
+
)
|
|
19
26
|
end
|
|
20
27
|
|
|
21
28
|
# Create the events table. Needs the database and the table name.
|
|
@@ -35,8 +42,9 @@ module EventSourcery
|
|
|
35
42
|
column :version, :bigint, null: false
|
|
36
43
|
column :correlation_id, :uuid
|
|
37
44
|
column :causation_id, :uuid
|
|
38
|
-
column :created_at, :'timestamp without time zone', null: false,
|
|
39
|
-
|
|
45
|
+
column :created_at, :'timestamp without time zone', null: false,
|
|
46
|
+
default: Sequel.lit("(now() at time zone 'utc')")
|
|
47
|
+
index %i[aggregate_id version], unique: true
|
|
40
48
|
index :uuid, unique: true
|
|
41
49
|
index :type
|
|
42
50
|
index :correlation_id
|
|
@@ -70,96 +78,96 @@ module EventSourcery
|
|
|
70
78
|
function_name: EventSourcery::Postgres.config.write_events_function_name,
|
|
71
79
|
events_table_name: EventSourcery::Postgres.config.events_table_name,
|
|
72
80
|
aggregates_table_name: EventSourcery::Postgres.config.aggregates_table_name)
|
|
73
|
-
db.run
|
|
74
|
-
create or replace function #{function_name}(_aggregateId uuid,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
declare
|
|
84
|
-
currentVersion int;
|
|
85
|
-
body json;
|
|
86
|
-
eventVersion int;
|
|
87
|
-
eventId text;
|
|
88
|
-
index int;
|
|
89
|
-
newVersion int;
|
|
90
|
-
numEvents int;
|
|
91
|
-
createdAt timestamp without time zone;
|
|
92
|
-
begin
|
|
93
|
-
numEvents := array_length(_bodies, 1);
|
|
94
|
-
select version into currentVersion from #{aggregates_table_name} where aggregate_id = _aggregateId;
|
|
95
|
-
if not found then
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
else
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
end if;
|
|
121
|
-
index := 1;
|
|
122
|
-
eventVersion := currentVersion + 1;
|
|
123
|
-
if _lockTable then
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
end if;
|
|
133
|
-
foreach body IN ARRAY(_bodies)
|
|
134
|
-
loop
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
81
|
+
db.run <<~SQL
|
|
82
|
+
create or replace function #{function_name}(_aggregateId uuid,
|
|
83
|
+
_eventTypes varchar[],
|
|
84
|
+
_expectedVersion int,
|
|
85
|
+
_bodies json[],
|
|
86
|
+
_createdAtTimes timestamp without time zone[],
|
|
87
|
+
_eventUUIDs uuid[],
|
|
88
|
+
_correlationIds uuid[],
|
|
89
|
+
_causationIds uuid[],
|
|
90
|
+
_lockTable boolean) returns void as $$
|
|
91
|
+
declare
|
|
92
|
+
currentVersion int;
|
|
93
|
+
body json;
|
|
94
|
+
eventVersion int;
|
|
95
|
+
eventId text;
|
|
96
|
+
index int;
|
|
97
|
+
newVersion int;
|
|
98
|
+
numEvents int;
|
|
99
|
+
createdAt timestamp without time zone;
|
|
100
|
+
begin
|
|
101
|
+
numEvents := array_length(_bodies, 1);
|
|
102
|
+
select version into currentVersion from #{aggregates_table_name} where aggregate_id = _aggregateId;
|
|
103
|
+
if not found then
|
|
104
|
+
-- when we have no existing version for this aggregate
|
|
105
|
+
if _expectedVersion = 0 or _expectedVersion is null then
|
|
106
|
+
-- set the version to 1 if expected version is null or 0
|
|
107
|
+
insert into #{aggregates_table_name}(aggregate_id, version) values(_aggregateId, numEvents);
|
|
108
|
+
currentVersion := 0;
|
|
109
|
+
else
|
|
110
|
+
raise 'Concurrency conflict. Current version: 0, expected version: %', _expectedVersion;
|
|
111
|
+
end if;
|
|
112
|
+
else
|
|
113
|
+
if _expectedVersion is null then
|
|
114
|
+
-- automatically increment the version
|
|
115
|
+
update #{aggregates_table_name} set version = version + numEvents where aggregate_id = _aggregateId returning version into newVersion;
|
|
116
|
+
currentVersion := newVersion - numEvents;
|
|
117
|
+
else
|
|
118
|
+
-- increment the version if it's at our expected version
|
|
119
|
+
update #{aggregates_table_name} set version = version + numEvents where aggregate_id = _aggregateId and version = _expectedVersion;
|
|
120
|
+
if not found then
|
|
121
|
+
-- version was not at expected_version, raise an error.
|
|
122
|
+
-- currentVersion may not equal what it did in the database when the
|
|
123
|
+
-- above update statement is executed (it may have been incremented by another
|
|
124
|
+
-- process)
|
|
125
|
+
raise 'Concurrency conflict. Last known current version: %, expected version: %', currentVersion, _expectedVersion;
|
|
126
|
+
end if;
|
|
127
|
+
end if;
|
|
128
|
+
end if;
|
|
129
|
+
index := 1;
|
|
130
|
+
eventVersion := currentVersion + 1;
|
|
131
|
+
if _lockTable then
|
|
132
|
+
-- Ensure this transaction is the only one writing events to guarantee
|
|
133
|
+
-- linear growth of sequence IDs.
|
|
134
|
+
-- Any value that won't conflict with other advisory locks will work.
|
|
135
|
+
-- The Postgres tracker currently obtains an advisory lock using it's
|
|
136
|
+
-- integer row ID, so values 1 to the number of ESP's in the system would
|
|
137
|
+
-- be taken if the tracker is running in the same database as your
|
|
138
|
+
-- projections.
|
|
139
|
+
perform pg_advisory_xact_lock(-1);
|
|
140
|
+
end if;
|
|
141
|
+
foreach body IN ARRAY(_bodies)
|
|
142
|
+
loop
|
|
143
|
+
if _createdAtTimes[index] is not null then
|
|
144
|
+
createdAt := _createdAtTimes[index];
|
|
145
|
+
else
|
|
146
|
+
createdAt := now() at time zone 'utc';
|
|
147
|
+
end if;
|
|
140
148
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
insert into #{events_table_name}
|
|
150
|
+
(uuid, aggregate_id, type, body, version, correlation_id, causation_id, created_at)
|
|
151
|
+
values
|
|
152
|
+
(
|
|
153
|
+
_eventUUIDs[index],
|
|
154
|
+
_aggregateId,
|
|
155
|
+
_eventTypes[index],
|
|
156
|
+
body,
|
|
157
|
+
eventVersion,
|
|
158
|
+
_correlationIds[index],
|
|
159
|
+
_causationIds[index],
|
|
160
|
+
createdAt
|
|
161
|
+
)
|
|
162
|
+
returning id into eventId;
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
end loop;
|
|
159
|
-
perform pg_notify('new_event', eventId);
|
|
160
|
-
end;
|
|
161
|
-
$$ language plpgsql;
|
|
162
|
-
SQL
|
|
164
|
+
eventVersion := eventVersion + 1;
|
|
165
|
+
index := index + 1;
|
|
166
|
+
end loop;
|
|
167
|
+
perform pg_notify('new_event', eventId);
|
|
168
|
+
end;
|
|
169
|
+
$$ language plpgsql;
|
|
170
|
+
SQL
|
|
163
171
|
end
|
|
164
172
|
|
|
165
173
|
# Create the projector tracker table. Needs the database and the table name.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
module TableOwner
|
|
@@ -38,9 +40,7 @@ module EventSourcery
|
|
|
38
40
|
def reset
|
|
39
41
|
self.class.tables.keys.each do |table_name|
|
|
40
42
|
prefixed_name = table_name_prefixed(table_name)
|
|
41
|
-
if @db_connection.table_exists?(prefixed_name)
|
|
42
|
-
@db_connection.drop_table(prefixed_name, cascade: true)
|
|
43
|
-
end
|
|
43
|
+
@db_connection.drop_table(prefixed_name, cascade: true) if @db_connection.table_exists?(prefixed_name)
|
|
44
44
|
end
|
|
45
45
|
super if defined?(super)
|
|
46
46
|
setup
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module EventSourcery
|
|
2
4
|
module Postgres
|
|
3
5
|
# This will set up a persisted event id tracker for processors.
|
|
4
6
|
class Tracker
|
|
5
|
-
|
|
6
7
|
def initialize(db_connection = EventSourcery::Postgres.config.projections_database,
|
|
7
8
|
table_name: EventSourcery::Postgres.config.tracker_table_name,
|
|
8
9
|
obtain_processor_lock: true)
|
|
@@ -19,16 +20,14 @@ module EventSourcery
|
|
|
19
20
|
def setup(processor_name = nil)
|
|
20
21
|
create_table_if_not_exists if EventSourcery::Postgres.config.auto_create_projector_tracker
|
|
21
22
|
|
|
22
|
-
unless tracker_table_exists?
|
|
23
|
-
raise UnableToLockProcessorError, 'Projector tracker table does not exist'
|
|
24
|
-
end
|
|
23
|
+
raise UnableToLockProcessorError, 'Projector tracker table does not exist' unless tracker_table_exists?
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
return unless processor_name
|
|
26
|
+
|
|
27
|
+
create_track_entry_if_not_exists(processor_name)
|
|
28
|
+
return unless @obtain_processor_lock
|
|
29
|
+
|
|
30
|
+
obtain_global_lock_on_processor(processor_name)
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
# This will updated the tracker table to the given event id value
|
|
@@ -37,9 +36,9 @@ module EventSourcery
|
|
|
37
36
|
# @param processor_name the name of the processor to update
|
|
38
37
|
# @param event_id the event id number to update to
|
|
39
38
|
def processed_event(processor_name, event_id)
|
|
40
|
-
table
|
|
41
|
-
where(name: processor_name.to_s)
|
|
42
|
-
update(last_processed_event_id: event_id)
|
|
39
|
+
table
|
|
40
|
+
.where(name: processor_name.to_s)
|
|
41
|
+
.update(last_processed_event_id: event_id)
|
|
43
42
|
true
|
|
44
43
|
end
|
|
45
44
|
|
|
@@ -82,17 +81,18 @@ module EventSourcery
|
|
|
82
81
|
private
|
|
83
82
|
|
|
84
83
|
def obtain_global_lock_on_processor(processor_name)
|
|
85
|
-
lock_obtained = @db_connection.fetch("select pg_try_advisory_lock(#{@track_entry_id})")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
lock_obtained = @db_connection.fetch("select pg_try_advisory_lock(#{@track_entry_id})")
|
|
85
|
+
.to_a.first[:pg_try_advisory_lock]
|
|
86
|
+
return unless lock_obtained == false
|
|
87
|
+
|
|
88
|
+
raise UnableToLockProcessorError, "Unable to get a lock on #{processor_name} #{@track_entry_id}"
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def create_table_if_not_exists
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
return if tracker_table_exists?
|
|
93
|
+
|
|
94
|
+
EventSourcery.logger.info { "Projector tracker missing - attempting to create 'projector_tracker' table" }
|
|
95
|
+
EventSourcery::Postgres::Schema.create_projector_tracker(db: @db_connection, table_name: @table_name)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def create_track_entry_if_not_exists(processor_name)
|
metadata
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: event_sourcery-postgres
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Envato
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: event_sourcery
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: 0.14.0
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: 0.14.0
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: pg
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,19 +38,33 @@ dependencies:
|
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: sequel
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - ">="
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
46
|
+
version: '4.38'
|
|
48
47
|
type: :runtime
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - ">="
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
53
|
+
version: '4.38'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: benchmark-ips
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
55
68
|
- !ruby/object:Gem::Dependency
|
|
56
69
|
name: bundler
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -67,62 +80,61 @@ dependencies:
|
|
|
67
80
|
- !ruby/object:Gem::Version
|
|
68
81
|
version: '0'
|
|
69
82
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
83
|
+
name: pry
|
|
71
84
|
requirement: !ruby/object:Gem::Requirement
|
|
72
85
|
requirements:
|
|
73
|
-
- - "
|
|
86
|
+
- - ">="
|
|
74
87
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
88
|
+
version: '0'
|
|
76
89
|
type: :development
|
|
77
90
|
prerelease: false
|
|
78
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
92
|
requirements:
|
|
80
|
-
- - "
|
|
93
|
+
- - ">="
|
|
81
94
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
95
|
+
version: '0'
|
|
83
96
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
97
|
+
name: rake
|
|
85
98
|
requirement: !ruby/object:Gem::Requirement
|
|
86
99
|
requirements:
|
|
87
100
|
- - "~>"
|
|
88
101
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
102
|
+
version: '13'
|
|
90
103
|
type: :development
|
|
91
104
|
prerelease: false
|
|
92
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
106
|
requirements:
|
|
94
107
|
- - "~>"
|
|
95
108
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
109
|
+
version: '13'
|
|
97
110
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
111
|
+
name: rspec
|
|
99
112
|
requirement: !ruby/object:Gem::Requirement
|
|
100
113
|
requirements:
|
|
101
|
-
- - "
|
|
114
|
+
- - "~>"
|
|
102
115
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '0'
|
|
116
|
+
version: '3.0'
|
|
104
117
|
type: :development
|
|
105
118
|
prerelease: false
|
|
106
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
120
|
requirements:
|
|
108
|
-
- - "
|
|
121
|
+
- - "~>"
|
|
109
122
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '0'
|
|
123
|
+
version: '3.0'
|
|
111
124
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
125
|
+
name: rubocop
|
|
113
126
|
requirement: !ruby/object:Gem::Requirement
|
|
114
127
|
requirements:
|
|
115
|
-
- - "
|
|
128
|
+
- - "~>"
|
|
116
129
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
130
|
+
version: '1'
|
|
118
131
|
type: :development
|
|
119
132
|
prerelease: false
|
|
120
133
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
134
|
requirements:
|
|
122
|
-
- - "
|
|
135
|
+
- - "~>"
|
|
123
136
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '
|
|
125
|
-
description:
|
|
137
|
+
version: '1'
|
|
126
138
|
email:
|
|
127
139
|
- rubygems@envato.com
|
|
128
140
|
executables: []
|
|
@@ -146,12 +158,14 @@ files:
|
|
|
146
158
|
- lib/event_sourcery/postgres/tracker.rb
|
|
147
159
|
- lib/event_sourcery/postgres/version.rb
|
|
148
160
|
homepage: https://github.com/envato/event_sourcery-postgres
|
|
149
|
-
licenses:
|
|
161
|
+
licenses:
|
|
162
|
+
- MIT
|
|
150
163
|
metadata:
|
|
164
|
+
allowed_push_host: https://rubygems.org
|
|
151
165
|
bug_tracker_uri: https://github.com/envato/event_sourcery-postgres/issues
|
|
152
166
|
changelog_uri: https://github.com/envato/event_sourcery-postgres/blob/HEAD/CHANGELOG.md
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
documentation_uri: https://www.rubydoc.info/gems/event_sourcery-postgres/1.0.0
|
|
168
|
+
source_code_uri: https://github.com/envato/event_sourcery-postgres/tree/v1.0.0
|
|
155
169
|
rdoc_options: []
|
|
156
170
|
require_paths:
|
|
157
171
|
- lib
|
|
@@ -159,15 +173,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
159
173
|
requirements:
|
|
160
174
|
- - ">="
|
|
161
175
|
- !ruby/object:Gem::Version
|
|
162
|
-
version: 2.
|
|
176
|
+
version: 2.6.0
|
|
163
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
178
|
requirements:
|
|
165
179
|
- - ">="
|
|
166
180
|
- !ruby/object:Gem::Version
|
|
167
181
|
version: '0'
|
|
168
182
|
requirements: []
|
|
169
|
-
rubygems_version:
|
|
170
|
-
signing_key:
|
|
183
|
+
rubygems_version: 4.0.3
|
|
171
184
|
specification_version: 4
|
|
172
185
|
summary: Postgres event store for use with EventSourcery
|
|
173
186
|
test_files: []
|