pg_eventstore 1.13.3 → 2.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/.rubocop.yml +1 -1
- data/CHANGELOG.md +20 -3
- data/Dockerfile +3 -0
- data/README.md +20 -7
- data/db/migrations/10_setup_pg_cron.rb +23 -0
- data/db/migrations/11_add_events_link_global_position.sql +1 -0
- data/db/migrations/12_migrate_legacy_links.rb +83 -0
- data/db/migrations/13_remove_events_link_id.sql +6 -0
- data/db/migrations/14_remove_ids_events_id_index.sql +1 -0
- data/db/migrations/9_create_events_horizon.sql +21 -0
- data/docs/appending_events.md +1 -1
- data/docs/events_and_streams.md +1 -1
- data/docs/multiple_commands.md +16 -1
- data/lib/pg_eventstore/callbacks.rb +7 -5
- data/lib/pg_eventstore/cli/try_to_delete_subscriptions_set.rb +2 -2
- data/lib/pg_eventstore/client.rb +7 -5
- data/lib/pg_eventstore/commands/all_stream_read_grouped.rb +3 -3
- data/lib/pg_eventstore/commands/append.rb +3 -3
- data/lib/pg_eventstore/commands/event_modifiers/prepare_link_event.rb +5 -2
- data/lib/pg_eventstore/commands/event_modifiers/prepare_regular_event.rb +1 -1
- data/lib/pg_eventstore/commands/link_to.rb +6 -6
- data/lib/pg_eventstore/commands/multiple.rb +2 -2
- data/lib/pg_eventstore/commands/regular_stream_read_grouped.rb +1 -1
- data/lib/pg_eventstore/commands/regular_stream_read_paginated.rb +1 -1
- data/lib/pg_eventstore/commands/system_stream_read_paginated.rb +1 -1
- data/lib/pg_eventstore/connection.rb +1 -35
- data/lib/pg_eventstore/errors.rb +1 -1
- data/lib/pg_eventstore/event.rb +7 -5
- data/lib/pg_eventstore/extensions/options_extension.rb +40 -11
- data/lib/pg_eventstore/maintenance.rb +1 -1
- data/lib/pg_eventstore/queries/event_queries.rb +10 -9
- data/lib/pg_eventstore/queries/links_resolver.rb +6 -3
- data/lib/pg_eventstore/queries/partition_queries.rb +72 -7
- data/lib/pg_eventstore/queries/transaction_queries.rb +10 -4
- data/lib/pg_eventstore/query_builders/events_filtering.rb +3 -7
- data/lib/pg_eventstore/query_builders/partitions_filtering.rb +28 -18
- data/lib/pg_eventstore/sql_builder.rb +30 -12
- data/lib/pg_eventstore/stream.rb +1 -1
- data/lib/pg_eventstore/subscriptions/basic_runner.rb +4 -4
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_feeder_handlers.rb +1 -1
- data/lib/pg_eventstore/subscriptions/callback_handlers/subscription_runner_handlers.rb +2 -2
- data/lib/pg_eventstore/subscriptions/events_processor.rb +1 -1
- data/lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb +5 -5
- data/lib/pg_eventstore/subscriptions/queries/subscription_queries.rb +3 -2
- data/lib/pg_eventstore/subscriptions/queries/subscription_service_queries.rb +78 -0
- data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb +2 -2
- data/lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb +1 -1
- data/lib/pg_eventstore/subscriptions/subscription.rb +18 -7
- data/lib/pg_eventstore/subscriptions/subscription_feeder.rb +8 -2
- data/lib/pg_eventstore/subscriptions/subscription_handler_performance.rb +1 -3
- data/lib/pg_eventstore/subscriptions/subscription_runner.rb +5 -2
- data/lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb +9 -1
- data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +16 -10
- data/lib/pg_eventstore/tasks/setup.rake +30 -31
- data/lib/pg_eventstore/utils.rb +8 -0
- data/lib/pg_eventstore/version.rb +1 -1
- data/lib/pg_eventstore/web/application.rb +5 -5
- data/lib/pg_eventstore/web/paginator/events_collection.rb +4 -4
- data/lib/pg_eventstore/web/paginator/helpers.rb +3 -3
- data/lib/pg_eventstore/web/paginator/stream_ids_collection.rb +2 -2
- data/lib/pg_eventstore/web/subscriptions/helpers.rb +2 -2
- data/lib/pg_eventstore.rb +4 -4
- data/pg_eventstore.gemspec +1 -1
- data/sig/pg_eventstore/client.rbs +1 -1
- data/sig/pg_eventstore/commands/multiple.rbs +1 -1
- data/sig/pg_eventstore/event.rbs +7 -5
- data/sig/pg_eventstore/extensions/options_extension.rbs +9 -1
- data/sig/pg_eventstore/queries/event_queries.rbs +11 -11
- data/sig/pg_eventstore/queries/links_resolver.rbs +5 -5
- data/sig/pg_eventstore/queries/partition_queries.rbs +5 -1
- data/sig/pg_eventstore/queries/transaction_queries.rbs +2 -2
- data/sig/pg_eventstore/query_builders/partitions_filtering.rbs +9 -5
- data/sig/pg_eventstore/sql_builder.rbs +8 -2
- data/sig/pg_eventstore/subscriptions/queries/subscription_queries.rbs +13 -13
- data/sig/pg_eventstore/subscriptions/queries/subscription_service_queries.rbs +19 -0
- data/sig/pg_eventstore/subscriptions/subscription.rbs +2 -0
- data/sig/pg_eventstore/subscriptions/subscription_feeder.rbs +2 -0
- data/sig/pg_eventstore/subscriptions/subscription_runner.rbs +0 -2
- data/sig/pg_eventstore/subscriptions/subscription_runners_feeder.rbs +10 -3
- data/sig/pg_eventstore/subscriptions/subscriptions_manager.rbs +2 -0
- data/sig/pg_eventstore/utils.rbs +10 -2
- metadata +11 -2
|
@@ -60,7 +60,7 @@ module PgEventstore
|
|
|
60
60
|
# @return [Hash]
|
|
61
61
|
# @raise [PgEventstore::RecordNotFound]
|
|
62
62
|
def find!(id)
|
|
63
|
-
find_by(id:
|
|
63
|
+
find_by(id:) || raise(RecordNotFound.new('subscriptions', id))
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
# @param attrs [Hash]
|
|
@@ -105,7 +105,7 @@ module PgEventstore
|
|
|
105
105
|
updated_attrs
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
deserialize(updated_attrs)
|
|
108
|
+
deserialize(updated_attrs).slice(*attrs.keys)
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
# @param subscriptions_set_id [Integer] SubscriptionsSet#id
|
|
@@ -177,6 +177,7 @@ module PgEventstore
|
|
|
177
177
|
# @return [PgEventstore::SQLBuilder]
|
|
178
178
|
def query_builder(id, options)
|
|
179
179
|
builder = PgEventstore::QueryBuilders::EventsFiltering.subscriptions_events_filtering(options).to_sql_builder
|
|
180
|
+
builder.where('global_position <= ?', options[:to_position]) if options[:to_position]
|
|
180
181
|
builder.select("#{id} as runner_id")
|
|
181
182
|
end
|
|
182
183
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgEventstore
|
|
4
|
+
# @!visibility private
|
|
5
|
+
class SubscriptionServiceQueries
|
|
6
|
+
# @return [Integer]
|
|
7
|
+
DEFAULT_SAFE_POSITION = 0
|
|
8
|
+
private_constant :DEFAULT_SAFE_POSITION
|
|
9
|
+
|
|
10
|
+
# @!attribute connection
|
|
11
|
+
# @return [PgEventstore::Connection]
|
|
12
|
+
attr_reader :connection
|
|
13
|
+
private :connection
|
|
14
|
+
|
|
15
|
+
# @param connection [PgEventstore::Connection]
|
|
16
|
+
def initialize(connection)
|
|
17
|
+
@connection = connection
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Integer]
|
|
21
|
+
def safe_global_position
|
|
22
|
+
result = transaction_queries.transaction(read_only: true) do
|
|
23
|
+
connection.with do |conn|
|
|
24
|
+
conn.exec(<<~SQL)
|
|
25
|
+
SELECT MAX(global_position) as global_position
|
|
26
|
+
FROM events_horizon
|
|
27
|
+
WHERE xact_id < pg_snapshot_xmin(pg_current_snapshot())
|
|
28
|
+
SQL
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
global_position = result.to_a.first&.dig('global_position')
|
|
33
|
+
return global_position if global_position
|
|
34
|
+
|
|
35
|
+
if global_position.nil? && !events_horizon_present?
|
|
36
|
+
init_events_horizon
|
|
37
|
+
return safe_global_position
|
|
38
|
+
end
|
|
39
|
+
# events_horizon table is not empty, but there is no safe position yet. Thus, we wait for the safe position by
|
|
40
|
+
# returning default value which will prevent from fetching events with gaps
|
|
41
|
+
DEFAULT_SAFE_POSITION
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Boolean]
|
|
45
|
+
def events_horizon_present?
|
|
46
|
+
result = connection.with do |conn|
|
|
47
|
+
conn.exec(<<~SQL)
|
|
48
|
+
SELECT true as presence FROM events_horizon LIMIT 1
|
|
49
|
+
SQL
|
|
50
|
+
end
|
|
51
|
+
result.to_a.first&.dig('presence') || false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [void]
|
|
55
|
+
def init_events_horizon
|
|
56
|
+
transaction_queries.transaction do
|
|
57
|
+
return if events_horizon_present?
|
|
58
|
+
|
|
59
|
+
max_pos = connection.with do |conn|
|
|
60
|
+
conn.exec('SELECT MAX(global_position) FROM events')
|
|
61
|
+
end
|
|
62
|
+
max_pos = max_pos.to_a.first&.dig('global_position') || DEFAULT_SAFE_POSITION
|
|
63
|
+
connection.with do |conn|
|
|
64
|
+
conn.exec_params(<<~SQL, [max_pos])
|
|
65
|
+
INSERT INTO events_horizon (global_position, xact_id) VALUES ($1, DEFAULT)
|
|
66
|
+
SQL
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# @return [PgEventstore::TransactionQueries]
|
|
74
|
+
def transaction_queries
|
|
75
|
+
TransactionQueries.new(connection)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -19,8 +19,8 @@ module PgEventstore
|
|
|
19
19
|
# @return [PgEventstore::SubscriptionFeederCommands::Base]
|
|
20
20
|
def find_or_create_by(subscriptions_set_id:, command_name:, data:)
|
|
21
21
|
transaction_queries.transaction do
|
|
22
|
-
find_by(subscriptions_set_id
|
|
23
|
-
create(subscriptions_set_id
|
|
22
|
+
find_by(subscriptions_set_id:, command_name:) ||
|
|
23
|
+
create(subscriptions_set_id:, command_name:, data:)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -62,7 +62,7 @@ module PgEventstore
|
|
|
62
62
|
# @return [Hash]
|
|
63
63
|
# @raise [PgEventstore::RecordNotFound]
|
|
64
64
|
def find!(id)
|
|
65
|
-
find_by(id:
|
|
65
|
+
find_by(id:) || raise(RecordNotFound.new('subscriptions_set', id))
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# @param attrs [Hash]
|
|
@@ -6,6 +6,16 @@ module PgEventstore
|
|
|
6
6
|
include Extensions::UsingConnectionExtension
|
|
7
7
|
include Extensions::OptionsExtension
|
|
8
8
|
|
|
9
|
+
# Determines the minimal allowed value of events pull frequency of the particular subscription. You can find similar
|
|
10
|
+
# constant - SubscriptionFeeder::EVENTS_PULL_INTERVAL. Unlike it - this one is responsible to detect whether the
|
|
11
|
+
# subscription should be included in the subscriptions list to query next chunk of events. Thus, this setting only
|
|
12
|
+
# determines whether it is time to make a request, but how frequent would be the actual request - determines
|
|
13
|
+
# SubscriptionFeeder::EVENTS_PULL_INTERVAL.
|
|
14
|
+
# @see PgEventstore::SubscriptionFeeder::EVENTS_PULL_INTERVAL
|
|
15
|
+
# @return [Float]
|
|
16
|
+
MIN_EVENTS_PULL_INTERVAL = 0.2
|
|
17
|
+
private_constant :MIN_EVENTS_PULL_INTERVAL
|
|
18
|
+
|
|
9
19
|
# @return [Time]
|
|
10
20
|
DEFAULT_TIMESTAMP = Time.at(0).utc.freeze
|
|
11
21
|
|
|
@@ -103,7 +113,7 @@ module PgEventstore
|
|
|
103
113
|
# @param attrs [Hash]
|
|
104
114
|
# @return [Hash]
|
|
105
115
|
def update(attrs)
|
|
106
|
-
assign_attributes(subscription_queries.update(id, attrs
|
|
116
|
+
assign_attributes(subscription_queries.update(id, attrs:, locked_by:))
|
|
107
117
|
end
|
|
108
118
|
|
|
109
119
|
# @param attrs [Hash]
|
|
@@ -119,8 +129,8 @@ module PgEventstore
|
|
|
119
129
|
# @param force [Boolean]
|
|
120
130
|
# @return [PgEventstore::Subscription]
|
|
121
131
|
def lock!(lock_id, force: false)
|
|
122
|
-
self.id = subscription_queries.find_or_create_by(set
|
|
123
|
-
self.locked_by = subscription_queries.lock!(id, lock_id, force:
|
|
132
|
+
self.id = subscription_queries.find_or_create_by(set:, name:)[:id]
|
|
133
|
+
self.locked_by = subscription_queries.lock!(id, lock_id, force:)
|
|
124
134
|
reset_runtime_attributes
|
|
125
135
|
self
|
|
126
136
|
end
|
|
@@ -163,18 +173,19 @@ module PgEventstore
|
|
|
163
173
|
# @return [void]
|
|
164
174
|
def reset_runtime_attributes
|
|
165
175
|
update(
|
|
166
|
-
options
|
|
176
|
+
options:,
|
|
167
177
|
restart_count: 0,
|
|
168
178
|
last_restarted_at: nil,
|
|
169
|
-
max_restarts_number
|
|
170
|
-
chunk_query_interval: chunk_query_interval,
|
|
179
|
+
max_restarts_number:,
|
|
180
|
+
chunk_query_interval: [chunk_query_interval, MIN_EVENTS_PULL_INTERVAL].max,
|
|
171
181
|
last_chunk_fed_at: DEFAULT_TIMESTAMP,
|
|
172
182
|
last_chunk_greatest_position: nil,
|
|
173
183
|
last_error: nil,
|
|
174
184
|
last_error_occurred_at: nil,
|
|
175
|
-
time_between_restarts
|
|
185
|
+
time_between_restarts:,
|
|
176
186
|
state: RunnerState::STATES[:initial]
|
|
177
187
|
)
|
|
188
|
+
reload
|
|
178
189
|
end
|
|
179
190
|
|
|
180
191
|
# @return [PgEventstore::SubscriptionQueries]
|
|
@@ -7,6 +7,12 @@ module PgEventstore
|
|
|
7
7
|
class SubscriptionFeeder
|
|
8
8
|
extend Forwardable
|
|
9
9
|
|
|
10
|
+
# Determines how often to fetch events from the event store.
|
|
11
|
+
# @see PgEventstore::Subscription::MIN_EVENTS_PULL_INTERVAL
|
|
12
|
+
# @return [Float]
|
|
13
|
+
EVENTS_PULL_INTERVAL = 0.2 # seconds
|
|
14
|
+
private_constant :EVENTS_PULL_INTERVAL
|
|
15
|
+
|
|
10
16
|
attr_reader :config_name
|
|
11
17
|
|
|
12
18
|
def_delegators :@basic_runner, :start, :stop, :restore, :state, :wait_for_finish, :stop_async, :running?
|
|
@@ -17,7 +23,7 @@ module PgEventstore
|
|
|
17
23
|
def initialize(config_name:, subscriptions_set_lifecycle:, subscriptions_lifecycle:)
|
|
18
24
|
@config_name = config_name
|
|
19
25
|
@basic_runner = BasicRunner.new(
|
|
20
|
-
run_interval:
|
|
26
|
+
run_interval: EVENTS_PULL_INTERVAL,
|
|
21
27
|
async_shutdown_time: 0,
|
|
22
28
|
recovery_strategies: recovery_strategies(config_name, subscriptions_set_lifecycle)
|
|
23
29
|
)
|
|
@@ -116,7 +122,7 @@ module PgEventstore
|
|
|
116
122
|
[
|
|
117
123
|
RunnerRecoveryStrategies::RestoreConnection.new(config_name),
|
|
118
124
|
RunnerRecoveryStrategies::RestoreSubscriptionFeeder.new(
|
|
119
|
-
subscriptions_set_lifecycle:
|
|
125
|
+
subscriptions_set_lifecycle:
|
|
120
126
|
),
|
|
121
127
|
]
|
|
122
128
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'benchmark'
|
|
4
|
-
|
|
5
3
|
module PgEventstore
|
|
6
4
|
# This class measures the performance of Subscription's handler and returns the average time required to process an
|
|
7
5
|
# event.
|
|
@@ -23,7 +21,7 @@ module PgEventstore
|
|
|
23
21
|
# @return [Object] the result of yielded block
|
|
24
22
|
def track_exec_time
|
|
25
23
|
result = nil
|
|
26
|
-
time =
|
|
24
|
+
time = Utils.benchmark { result = yield }
|
|
27
25
|
synchronize do
|
|
28
26
|
@timings.shift if @timings.size == TIMINGS_TO_KEEP
|
|
29
27
|
@timings.push(time)
|
|
@@ -38,12 +38,15 @@ module PgEventstore
|
|
|
38
38
|
|
|
39
39
|
# @return [Hash]
|
|
40
40
|
def next_chunk_query_opts
|
|
41
|
-
@subscription.options.merge(
|
|
41
|
+
@subscription.options.merge(
|
|
42
|
+
from_position: next_chunk_global_position,
|
|
43
|
+
max_count: estimate_events_number
|
|
44
|
+
)
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
# @return [Boolean]
|
|
45
48
|
def time_to_feed?
|
|
46
|
-
@subscription.last_chunk_fed_at + @subscription.chunk_query_interval <= Time.now.utc
|
|
49
|
+
estimate_events_number > 0 && @subscription.last_chunk_fed_at + @subscription.chunk_query_interval <= Time.now.utc
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
private
|
|
@@ -15,7 +15,10 @@ module PgEventstore
|
|
|
15
15
|
runners = runners.select(&:running?).select(&:time_to_feed?)
|
|
16
16
|
return if runners.empty?
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
safe_pos = subscription_service_queries.safe_global_position
|
|
19
|
+
runners_query_options = runners.to_h do |runner|
|
|
20
|
+
[runner.id, runner.next_chunk_query_opts.merge(to_position: safe_pos)]
|
|
21
|
+
end
|
|
19
22
|
grouped_events = subscription_queries.subscriptions_events(runners_query_options)
|
|
20
23
|
|
|
21
24
|
runners.each do |runner|
|
|
@@ -34,5 +37,10 @@ module PgEventstore
|
|
|
34
37
|
def subscription_queries
|
|
35
38
|
SubscriptionQueries.new(connection)
|
|
36
39
|
end
|
|
40
|
+
|
|
41
|
+
# @return [PgEventstore::SubscriptionServiceQueries]
|
|
42
|
+
def subscription_service_queries
|
|
43
|
+
SubscriptionServiceQueries.new(connection)
|
|
44
|
+
end
|
|
37
45
|
end
|
|
38
46
|
end
|
|
@@ -25,6 +25,7 @@ require_relative 'subscription_feeder_commands'
|
|
|
25
25
|
require_relative 'subscription_runner_commands'
|
|
26
26
|
require_relative 'queries/subscription_command_queries'
|
|
27
27
|
require_relative 'queries/subscription_queries'
|
|
28
|
+
require_relative 'queries/subscription_service_queries'
|
|
28
29
|
require_relative 'queries/subscriptions_set_command_queries'
|
|
29
30
|
require_relative 'queries/subscriptions_set_queries'
|
|
30
31
|
require_relative 'commands_handler'
|
|
@@ -59,10 +60,10 @@ module PgEventstore
|
|
|
59
60
|
}
|
|
60
61
|
)
|
|
61
62
|
@subscriptions_lifecycle = SubscriptionsLifecycle.new(
|
|
62
|
-
config_name, @subscriptions_set_lifecycle, force_lock:
|
|
63
|
+
config_name, @subscriptions_set_lifecycle, force_lock:
|
|
63
64
|
)
|
|
64
65
|
@subscription_feeder = SubscriptionFeeder.new(
|
|
65
|
-
config_name
|
|
66
|
+
config_name:,
|
|
66
67
|
subscriptions_set_lifecycle: @subscriptions_set_lifecycle,
|
|
67
68
|
subscriptions_lifecycle: @subscriptions_lifecycle
|
|
68
69
|
)
|
|
@@ -99,17 +100,17 @@ module PgEventstore
|
|
|
99
100
|
failed_subscription_notifier: config.failed_subscription_notifier,
|
|
100
101
|
graceful_shutdown_timeout: config.subscription_graceful_shutdown_timeout)
|
|
101
102
|
subscription = Subscription.using_connection(config.name).new(
|
|
102
|
-
set: @set_name, name: subscription_name, options
|
|
103
|
+
set: @set_name, name: subscription_name, options:, chunk_query_interval: pull_interval,
|
|
103
104
|
max_restarts_number: max_retries, time_between_restarts: retries_interval
|
|
104
105
|
)
|
|
105
106
|
runner = SubscriptionRunner.new(
|
|
106
107
|
stats: SubscriptionHandlerPerformance.new,
|
|
107
108
|
events_processor: EventsProcessor.new(
|
|
108
109
|
create_raw_event_handler(middlewares, handler),
|
|
109
|
-
graceful_shutdown_timeout
|
|
110
|
+
graceful_shutdown_timeout:,
|
|
110
111
|
recovery_strategies: recovery_strategies(subscription, restart_terminator, failed_subscription_notifier)
|
|
111
112
|
),
|
|
112
|
-
subscription:
|
|
113
|
+
subscription:
|
|
113
114
|
)
|
|
114
115
|
|
|
115
116
|
@subscriptions_lifecycle.runners.push(runner)
|
|
@@ -150,10 +151,10 @@ module PgEventstore
|
|
|
150
151
|
private
|
|
151
152
|
|
|
152
153
|
# @return [Object] the result of the passed block
|
|
153
|
-
def run_cli_callbacks(&
|
|
154
|
+
def run_cli_callbacks(&)
|
|
154
155
|
return yield unless defined?(::PgEventstore::CLI)
|
|
155
156
|
|
|
156
|
-
PgEventstore::CLI.callbacks.run_callbacks(:start_manager, self, &
|
|
157
|
+
PgEventstore::CLI.callbacks.run_callbacks(:start_manager, self, &)
|
|
157
158
|
end
|
|
158
159
|
|
|
159
160
|
# @param middlewares [Array<Symbol>, nil]
|
|
@@ -180,11 +181,16 @@ module PgEventstore
|
|
|
180
181
|
[
|
|
181
182
|
RunnerRecoveryStrategies::RestoreConnection.new(config_name),
|
|
182
183
|
RunnerRecoveryStrategies::RestoreSubscriptionRunner.new(
|
|
183
|
-
subscription
|
|
184
|
-
restart_terminator
|
|
185
|
-
failed_subscription_notifier:
|
|
184
|
+
subscription:,
|
|
185
|
+
restart_terminator:,
|
|
186
|
+
failed_subscription_notifier:
|
|
186
187
|
),
|
|
187
188
|
]
|
|
188
189
|
end
|
|
190
|
+
|
|
191
|
+
# @return [PgEventstore::Connection]
|
|
192
|
+
def connection
|
|
193
|
+
PgEventstore.connection(config_name)
|
|
194
|
+
end
|
|
189
195
|
end
|
|
190
196
|
end
|
|
@@ -2,50 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
require 'uri'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
class
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
module PgEventstore
|
|
6
|
+
class MigrationHelpers
|
|
7
|
+
class << self
|
|
8
|
+
def postgres_uri
|
|
9
|
+
@postgres_uri ||=
|
|
10
|
+
begin
|
|
11
|
+
uri = URI.parse(ENV.fetch('PG_EVENTSTORE_URI'))
|
|
12
|
+
uri.path = '/postgres'
|
|
13
|
+
uri.to_s
|
|
14
|
+
end
|
|
15
|
+
end
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
def db_name
|
|
18
|
+
@db_name ||= URI.parse(ENV.fetch('PG_EVENTSTORE_URI')).path&.delete('/')
|
|
19
|
+
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
PgEventstore.configure(name: :_postgres_db_connection) do |config|
|
|
25
|
+
config.pg_uri = PgEventstore::MigrationHelpers.postgres_uri
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
PgEventstore.configure(name: :_eventstore_db_connection) do |config|
|
|
29
|
+
config.pg_uri = ENV['PG_EVENTSTORE_URI']
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
namespace :pg_eventstore do
|
|
23
33
|
desc 'Creates events table, indexes, etc.'
|
|
24
34
|
task :create do
|
|
25
|
-
PgEventstore.
|
|
26
|
-
config.pg_uri = helpers.postgres_uri
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
PgEventstore.connection.with do |conn|
|
|
35
|
+
PgEventstore.connection(:_postgres_db_connection).with do |conn|
|
|
30
36
|
exists =
|
|
31
|
-
conn.exec_params('SELECT 1 as exists FROM pg_database where datname = $1', [
|
|
37
|
+
conn.exec_params('SELECT 1 as exists FROM pg_database where datname = $1', [PgEventstore::MigrationHelpers.db_name]).
|
|
32
38
|
first&.dig('exists')
|
|
33
39
|
if exists
|
|
34
|
-
puts "#{
|
|
40
|
+
puts "#{PgEventstore::MigrationHelpers.db_name} already exists. Skipping."
|
|
35
41
|
else
|
|
36
|
-
|
|
42
|
+
escaped_db_name = conn.escape_string(PgEventstore::MigrationHelpers.db_name)
|
|
43
|
+
conn.exec("CREATE DATABASE #{escaped_db_name} WITH OWNER #{conn.escape_string(conn.user)}")
|
|
37
44
|
end
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
|
|
41
48
|
task :migrate do
|
|
42
|
-
PgEventstore.configure do |config|
|
|
43
|
-
config.pg_uri = ENV['PG_EVENTSTORE_URI']
|
|
44
|
-
end
|
|
45
|
-
|
|
46
49
|
migration_files_root = "#{Gem::Specification.find_by_name('pg_eventstore').gem_dir}/db/migrations"
|
|
47
50
|
|
|
48
|
-
PgEventstore.connection.with do |conn|
|
|
51
|
+
PgEventstore.connection(:_eventstore_db_connection).with do |conn|
|
|
49
52
|
conn.exec('CREATE TABLE IF NOT EXISTS migrations (number int NOT NULL)')
|
|
50
53
|
latest_migration =
|
|
51
54
|
conn.exec('SELECT number FROM migrations ORDER BY number DESC LIMIT 1').to_a.dig(0, 'number') || -1
|
|
@@ -68,12 +71,8 @@ namespace :pg_eventstore do
|
|
|
68
71
|
|
|
69
72
|
desc 'Drops events table and related pg_eventstore objects.'
|
|
70
73
|
task :drop do
|
|
71
|
-
PgEventstore.
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
PgEventstore.connection.with do |conn|
|
|
76
|
-
conn.exec("DROP DATABASE IF EXISTS #{conn.escape_string(helpers.db_name)}")
|
|
74
|
+
PgEventstore.connection(:_postgres_db_connection).with do |conn|
|
|
75
|
+
conn.exec("DROP DATABASE IF EXISTS #{conn.escape_string(PgEventstore::MigrationHelpers.db_name)}")
|
|
77
76
|
end
|
|
78
77
|
end
|
|
79
78
|
end
|
data/lib/pg_eventstore/utils.rb
CHANGED
|
@@ -124,6 +124,14 @@ module PgEventstore
|
|
|
124
124
|
|
|
125
125
|
wrapped_exception
|
|
126
126
|
end
|
|
127
|
+
|
|
128
|
+
# Yields the given block and measures its execution time
|
|
129
|
+
# @return [Float] number of seconds the block took to execute
|
|
130
|
+
def benchmark
|
|
131
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
132
|
+
yield
|
|
133
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
134
|
+
end
|
|
127
135
|
end
|
|
128
136
|
end
|
|
129
137
|
end
|
|
@@ -87,7 +87,7 @@ module PgEventstore
|
|
|
87
87
|
attrs.transform_values { escape_empty_string(_1) }
|
|
88
88
|
end
|
|
89
89
|
halt 200, {
|
|
90
|
-
results
|
|
90
|
+
results:,
|
|
91
91
|
pagination: { more: !collection.next_page_starting_id.nil?, starting_id: collection.next_page_starting_id },
|
|
92
92
|
}.to_json
|
|
93
93
|
end
|
|
@@ -153,7 +153,7 @@ module PgEventstore
|
|
|
153
153
|
filter: { event_types: events_filter, streams: streams_filter },
|
|
154
154
|
resolve_link_tos: resolve_link_tos?,
|
|
155
155
|
},
|
|
156
|
-
system_stream:
|
|
156
|
+
system_stream:
|
|
157
157
|
)
|
|
158
158
|
|
|
159
159
|
if request.xhr?
|
|
@@ -188,8 +188,8 @@ module PgEventstore
|
|
|
188
188
|
connection, @current_set, state: params[:state]
|
|
189
189
|
).subscriptions
|
|
190
190
|
@association = Subscriptions::SubscriptionsToSetAssociation.new(
|
|
191
|
-
subscriptions_set
|
|
192
|
-
subscriptions:
|
|
191
|
+
subscriptions_set:,
|
|
192
|
+
subscriptions:
|
|
193
193
|
)
|
|
194
194
|
erb :'subscriptions/index'
|
|
195
195
|
end
|
|
@@ -301,7 +301,7 @@ module PgEventstore
|
|
|
301
301
|
).first
|
|
302
302
|
if event&.global_position == global_position
|
|
303
303
|
begin
|
|
304
|
-
PgEventstore.maintenance(current_config).delete_event(event, force:
|
|
304
|
+
PgEventstore.maintenance(current_config).delete_event(event, force:)
|
|
305
305
|
self.flash_message = {
|
|
306
306
|
message: "An event at global position #{event.global_position} has been deleted successfully.",
|
|
307
307
|
kind: 'success',
|
|
@@ -26,7 +26,7 @@ module PgEventstore
|
|
|
26
26
|
# @param options [Hash] additional options to filter the collection
|
|
27
27
|
# @param system_stream [String, nil] a name of system stream
|
|
28
28
|
def initialize(config_name, starting_id:, per_page:, order:, options: {}, system_stream: nil)
|
|
29
|
-
super(config_name, starting_id
|
|
29
|
+
super(config_name, starting_id:, per_page:, order:, options:)
|
|
30
30
|
@stream = system_stream ? PgEventstore::Stream.system_stream(system_stream) : PgEventstore::Stream.all_stream
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -45,7 +45,7 @@ module PgEventstore
|
|
|
45
45
|
from_position = event_global_position(collection.first)
|
|
46
46
|
sql_builder = QueryBuilders::EventsFiltering.events_filtering(
|
|
47
47
|
@stream,
|
|
48
|
-
options.merge(from_position
|
|
48
|
+
options.merge(from_position:, max_count: 1, direction: order)
|
|
49
49
|
).to_sql_builder.unselect.select('global_position').offset(per_page)
|
|
50
50
|
global_position(sql_builder)
|
|
51
51
|
end
|
|
@@ -55,10 +55,10 @@ module PgEventstore
|
|
|
55
55
|
from_position = event_global_position(collection.first) || starting_id
|
|
56
56
|
sql_builder = QueryBuilders::EventsFiltering.events_filtering(
|
|
57
57
|
@stream,
|
|
58
|
-
options.merge(from_position
|
|
58
|
+
options.merge(from_position:, max_count: per_page, direction: order == :asc ? :desc : :asc)
|
|
59
59
|
).to_sql_builder.unselect.select('global_position').offset(1)
|
|
60
60
|
sql, params = sql_builder.to_exec_params
|
|
61
|
-
sql = "SELECT * FROM (#{sql})
|
|
61
|
+
sql = "SELECT * FROM (#{sql}) #{Event::PRIMARY_TABLE_NAME} ORDER BY global_position #{order} LIMIT 1"
|
|
62
62
|
connection.with do |conn|
|
|
63
63
|
conn.exec_params(sql, params)
|
|
64
64
|
end.to_a.dig(0, 'global_position')
|
|
@@ -41,13 +41,13 @@ module PgEventstore
|
|
|
41
41
|
# @param per_page [String] string representation of items per page. E.g. "10", "20", etc.
|
|
42
42
|
# @return [String]
|
|
43
43
|
def per_page_url(per_page)
|
|
44
|
-
build_path(params.merge(per_page:
|
|
44
|
+
build_path(params.merge(per_page:))
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# @param order [String] "asc"/"desc"
|
|
48
48
|
# @return [String]
|
|
49
49
|
def sort_url(order)
|
|
50
|
-
build_path(params.merge(order:
|
|
50
|
+
build_path(params.merge(order:))
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def resolve_link_tos_url(should_resolve)
|
|
@@ -113,7 +113,7 @@ module PgEventstore
|
|
|
113
113
|
def build_starting_id_link(starting_id)
|
|
114
114
|
return 'javascript: void(0);' unless starting_id
|
|
115
115
|
|
|
116
|
-
build_path(params.merge(starting_id:
|
|
116
|
+
build_path(params.merge(starting_id:))
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
# @param params [Hash, Array]
|
|
@@ -11,7 +11,7 @@ module PgEventstore
|
|
|
11
11
|
def collection
|
|
12
12
|
@collection ||=
|
|
13
13
|
begin
|
|
14
|
-
sql_builder = SQLBuilder.new.select('stream_id').from(
|
|
14
|
+
sql_builder = SQLBuilder.new.select('stream_id').from(Event::PRIMARY_TABLE_NAME)
|
|
15
15
|
sql_builder.where('context = ? and stream_name = ?', options[:context], options[:stream_name])
|
|
16
16
|
sql_builder.where('stream_id like ?', "#{options[:query]}%")
|
|
17
17
|
sql_builder.where("stream_id #{direction_operator} ?", starting_id) if starting_id
|
|
@@ -27,7 +27,7 @@ module PgEventstore
|
|
|
27
27
|
return unless collection.size == per_page
|
|
28
28
|
|
|
29
29
|
starting_id = collection.first['stream_id']
|
|
30
|
-
sql_builder = SQLBuilder.new.select('stream_id').from(
|
|
30
|
+
sql_builder = SQLBuilder.new.select('stream_id').from(Event::PRIMARY_TABLE_NAME)
|
|
31
31
|
sql_builder.where("stream_id #{direction_operator} ?", starting_id)
|
|
32
32
|
sql_builder.where('stream_id like ?', "#{options[:query]}%")
|
|
33
33
|
sql_builder.where('context = ? and stream_name = ?', options[:context], options[:stream_name])
|
|
@@ -9,7 +9,7 @@ module PgEventstore
|
|
|
9
9
|
def subscriptions_url(set_name: nil)
|
|
10
10
|
return url('/subscriptions') unless set_name
|
|
11
11
|
|
|
12
|
-
encoded_params = Rack::Utils.build_nested_query(set_name:
|
|
12
|
+
encoded_params = Rack::Utils.build_nested_query(set_name:)
|
|
13
13
|
url("/subscriptions?#{encoded_params}")
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -117,7 +117,7 @@ module PgEventstore
|
|
|
117
117
|
# @param ids [Array<Integer>]
|
|
118
118
|
# @return [String]
|
|
119
119
|
def delete_all_subscriptions_url(ids)
|
|
120
|
-
encoded_params = Rack::Utils.build_nested_query(ids:
|
|
120
|
+
encoded_params = Rack::Utils.build_nested_query(ids:)
|
|
121
121
|
url("/delete_all_subscriptions?#{encoded_params}")
|
|
122
122
|
end
|
|
123
123
|
|
data/lib/pg_eventstore.rb
CHANGED
|
@@ -39,7 +39,7 @@ module PgEventstore
|
|
|
39
39
|
# @return [Object] a result of the given block
|
|
40
40
|
def configure(name: DEFAULT_CONFIG)
|
|
41
41
|
mutex.synchronize do
|
|
42
|
-
@config[name] = @config[name] ? Config.new(name
|
|
42
|
+
@config[name] = @config[name] ? Config.new(name:, **@config[name].options_hash) : Config.new(name:)
|
|
43
43
|
connection_config_was = @config[name].connection_options
|
|
44
44
|
|
|
45
45
|
yield(@config[name]).tap do
|
|
@@ -92,9 +92,9 @@ module PgEventstore
|
|
|
92
92
|
SubscriptionsManager.new(
|
|
93
93
|
config: config(config_name),
|
|
94
94
|
set_name: subscription_set,
|
|
95
|
-
max_retries
|
|
96
|
-
retries_interval
|
|
97
|
-
force_lock:
|
|
95
|
+
max_retries:,
|
|
96
|
+
retries_interval:,
|
|
97
|
+
force_lock:
|
|
98
98
|
)
|
|
99
99
|
end
|
|
100
100
|
|
data/pg_eventstore.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = 'EventStore implementation using PostgreSQL'
|
|
13
13
|
spec.homepage = 'https://github.com/yousty/pg_eventstore'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
|
-
spec.required_ruby_version = '>= 3.
|
|
15
|
+
spec.required_ruby_version = '>= 3.2'
|
|
16
16
|
|
|
17
17
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
18
18
|
|
|
@@ -17,7 +17,7 @@ module PgEventstore
|
|
|
17
17
|
?middlewares: ::Array[untyped]?
|
|
18
18
|
) -> (PgEventstore::Event | ::Array[PgEventstore::Event])
|
|
19
19
|
|
|
20
|
-
def multiple: () { () -> untyped } -> untyped
|
|
20
|
+
def multiple: (?read_only: bool) { () -> untyped } -> untyped
|
|
21
21
|
|
|
22
22
|
# _@param_ `stream`
|
|
23
23
|
#
|